diff --git a/.gitattributes b/.gitattributes
index 87f1eb32e..9158b3979 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -4,11 +4,11 @@ build.zig.zon.json linguist-generated=true
vendor/** linguist-vendored
website/** linguist-documentation
pkg/breakpad/vendor/** linguist-vendored
-pkg/cimgui/vendor/** linguist-vendored
pkg/glfw/wayland-headers/** linguist-vendored
pkg/libintl/config.h linguist-generated=true
pkg/libintl/libintl.h linguist-generated=true
pkg/simdutf/vendor/** linguist-vendored
src/font/nerd_font_attributes.zig linguist-generated=true
src/font/nerd_font_codepoint_tables.py linguist-generated=true
+src/font/res/** linguist-vendored
src/terminal/res/** linguist-vendored
diff --git a/.github/workflows/publish-tag.yml b/.github/workflows/publish-tag.yml
index c433e7484..acb1ab1f1 100644
--- a/.github/workflows/publish-tag.yml
+++ b/.github/workflows/publish-tag.yml
@@ -64,7 +64,7 @@ jobs:
mkdir blob
mv appcast.xml blob/appcast.xml
- name: Upload Appcast to R2
- uses: ryand56/r2-upload-action@b801a390acbdeb034c5e684ff5e1361c06639e7c # v1
+ uses: ryand56/r2-upload-action@b801a390acbdeb034c5e684ff5e1361c06639e7c # v1.4
with:
r2-account-id: ${{ secrets.CF_R2_RELEASE_ACCOUNT_ID }}
r2-access-key-id: ${{ secrets.CF_R2_RELEASE_AWS_KEY }}
diff --git a/.github/workflows/release-tag.yml b/.github/workflows/release-tag.yml
index 748965513..960ff4efe 100644
--- a/.github/workflows/release-tag.yml
+++ b/.github/workflows/release-tag.yml
@@ -143,7 +143,7 @@ jobs:
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
- name: XCode Select
- run: sudo xcode-select -s /Applications/Xcode_26.0.app
+ run: sudo xcode-select -s /Applications/Xcode_26.2.app
- name: Xcode Version
run: xcodebuild -version
diff --git a/.github/workflows/release-tip.yml b/.github/workflows/release-tip.yml
index fb6aef87d..3af59e7a5 100644
--- a/.github/workflows/release-tip.yml
+++ b/.github/workflows/release-tip.yml
@@ -232,7 +232,7 @@ jobs:
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
- name: XCode Select
- run: sudo xcode-select -s /Applications/Xcode_26.0.app
+ run: sudo xcode-select -s /Applications/Xcode_26.2.app
- name: Xcode Version
run: xcodebuild -version
@@ -466,7 +466,7 @@ jobs:
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
- name: XCode Select
- run: sudo xcode-select -s /Applications/Xcode_26.0.app
+ run: sudo xcode-select -s /Applications/Xcode_26.2.app
- name: Xcode Version
run: xcodebuild -version
@@ -650,7 +650,7 @@ jobs:
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
- name: XCode Select
- run: sudo xcode-select -s /Applications/Xcode_26.0.app
+ run: sudo xcode-select -s /Applications/Xcode_26.2.app
- name: Xcode Version
run: xcodebuild -version
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 30f34120a..734b8d224 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -217,6 +217,7 @@ jobs:
x86_64-macos,
aarch64-linux,
x86_64-linux,
+ x86_64-linux-musl,
x86_64-windows,
wasm32-freestanding,
]
@@ -456,7 +457,7 @@ jobs:
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
- name: Xcode Select
- run: sudo xcode-select -s /Applications/Xcode_26.0.app
+ run: sudo xcode-select -s /Applications/Xcode_26.2.app
- name: Xcode Version
run: xcodebuild -version
@@ -499,7 +500,7 @@ jobs:
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
- name: Xcode Select
- run: sudo xcode-select -s /Applications/Xcode_26.0.app
+ run: sudo xcode-select -s /Applications/Xcode_26.2.app
- name: Xcode Version
run: xcodebuild -version
@@ -764,7 +765,7 @@ jobs:
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
- name: Xcode Select
- run: sudo xcode-select -s /Applications/Xcode_26.0.app
+ run: sudo xcode-select -s /Applications/Xcode_26.2.app
- name: Xcode Version
run: xcodebuild -version
@@ -843,6 +844,8 @@ jobs:
if: github.repository == 'ghostty-org/ghostty'
runs-on: namespace-profile-ghostty-xsm
timeout-minutes: 60
+ permissions:
+ contents: read
env:
ZIG_LOCAL_CACHE_DIR: /zig/local-cache
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
@@ -865,6 +868,8 @@ jobs:
useDaemon: false # sometimes fails on short jobs
- name: pinact check
run: nix develop -c pinact run --check
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
prettier:
if: github.repository == 'ghostty-org/ghostty'
@@ -977,8 +982,6 @@ jobs:
--check-sourced \
--color=always \
--severity=warning \
- --shell=bash \
- --external-sources \
$(find . \( -name "*.sh" -o -name "*.bash" \) -type f ! -path "./zig-out/*" ! -path "./macos/build/*" ! -path "./.git/*" | sort)
translations:
@@ -1082,7 +1085,7 @@ jobs:
uses: namespacelabs/nscloud-setup@d1c625762f7c926a54bd39252efff0705fd11c64 # v0.0.10
- name: Configure Namespace powered Buildx
- uses: namespacelabs/nscloud-setup-buildx-action@91c2e6537780e3b092cb8476406be99a8f91bd5e # v0.0.20
+ uses: namespacelabs/nscloud-setup-buildx-action@a7e525416136ee2842da3c800e7067b72a27200e # v0.0.21
- name: Download Source Tarball Artifacts
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
diff --git a/.shellcheckrc b/.shellcheckrc
new file mode 100644
index 000000000..919cc175d
--- /dev/null
+++ b/.shellcheckrc
@@ -0,0 +1,8 @@
+# ShellCheck
+# https://github.com/koalaman/shellcheck/wiki/Directive#shellcheckrc-file
+
+# Allow opening any 'source'd file, even if not specified as input
+external-sources=true
+
+# Assume bash by default
+shell=bash
diff --git a/HACKING.md b/HACKING.md
index bde50ec99..0abb3a2d8 100644
--- a/HACKING.md
+++ b/HACKING.md
@@ -164,6 +164,28 @@ alejandra .
Make sure your Alejandra version matches the version of Alejandra in [devShell.nix](https://github.com/ghostty-org/ghostty/blob/main/nix/devShell.nix).
+### ShellCheck
+
+Bash scripts are checked with [ShellCheck](https://www.shellcheck.net/) in CI.
+
+Nix users can use the following command to run ShellCheck over all of our scripts:
+
+```
+nix develop -c shellcheck \
+ --check-sourced \
+ --severity=warning \
+ $(find . \( -name "*.sh" -o -name "*.bash" \) -type f ! -path "./zig-out/*" ! -path "./macos/build/*" ! -path "./.git/*" | sort)
+```
+
+Non-Nix users can [install ShellCheck](https://github.com/koalaman/shellcheck#user-content-installing) and then run:
+
+```
+shellcheck \
+ --check-sourced \
+ --severity=warning \
+ $(find . \( -name "*.sh" -o -name "*.bash" \) -type f ! -path "./zig-out/*" ! -path "./macos/build/*" ! -path "./.git/*" | sort)
+```
+
### Updating the Zig Cache Fixed-Output Derivation Hash
The Nix package depends on a [fixed-output
diff --git a/build.zig.zon b/build.zig.zon
index 271428778..d5c06259a 100644
--- a/build.zig.zon
+++ b/build.zig.zon
@@ -21,8 +21,8 @@
},
.z2d = .{
// vancluever/z2d
- .url = "https://deps.files.ghostty.org/z2d-0.9.0-j5P_Hu-WFgA_JEfRpiFss6gdvcvS47cgOc0Via2eKD_T.tar.gz",
- .hash = "z2d-0.9.0-j5P_Hu-WFgA_JEfRpiFss6gdvcvS47cgOc0Via2eKD_T",
+ .url = "https://github.com/vancluever/z2d/archive/refs/tags/v0.10.0.tar.gz",
+ .hash = "z2d-0.10.0-j5P_Hu-6FgBsZNgwphIqh17jDnj8_yPtD8yzjO6PpHRQ",
.lazy = true,
},
.zig_objc = .{
@@ -63,7 +63,7 @@
},
// C libs
- .cimgui = .{ .path = "./pkg/cimgui", .lazy = true },
+ .dcimgui = .{ .path = "./pkg/dcimgui", .lazy = true },
.fontconfig = .{ .path = "./pkg/fontconfig", .lazy = true },
.freetype = .{ .path = "./pkg/freetype", .lazy = true },
.gtk4_layer_shell = .{ .path = "./pkg/gtk4-layer-shell", .lazy = true },
@@ -116,8 +116,8 @@
// Other
.apple_sdk = .{ .path = "./pkg/apple-sdk" },
.iterm2_themes = .{
- .url = "https://github.com/mbadolato/iTerm2-Color-Schemes/releases/download/release-20251201-150531-bfb3ee1/ghostty-themes.tgz",
- .hash = "N-V-__8AANFEAwCzzNzNs3Gaq8pzGNl2BbeyFBwTyO5iZJL-",
+ .url = "https://deps.files.ghostty.org/ghostty-themes-release-20260112-150707-28c8f5b.tgz",
+ .hash = "N-V-__8AAIdIAwDt5PxH-cwCxEcTfw4jBV8sR6fZ_XLh-cR7",
.lazy = true,
},
},
diff --git a/build.zig.zon.json b/build.zig.zon.json
index c9a64ca5f..b12216bd9 100644
--- a/build.zig.zon.json
+++ b/build.zig.zon.json
@@ -1,4 +1,9 @@
{
+ "N-V-__8AANT61wB--nJ95Gj_ctmzAtcjloZ__hRqNw5lC1Kr": {
+ "name": "bindings",
+ "url": "https://deps.files.ghostty.org/DearBindings_v0.17_ImGui_v1.92.5-docking.tar.gz",
+ "hash": "sha256-i/7FAOAJJvZ5hT7iPWfMOS08MYFzPKRwRzhlHT9wuqM="
+ },
"N-V-__8AALw2uwF_03u4JRkZwRLc3Y9hakkYV7NKRR9-RIZJ": {
"name": "breakpad",
"url": "https://deps.files.ghostty.org/breakpad-b99f444ba5f6b98cac261cbb391d8766b34a5918.tar.gz",
@@ -44,15 +49,15 @@
"url": "https://deps.files.ghostty.org/highway-66486a10623fa0d72fe91260f96c892e41aceb06.tar.gz",
"hash": "sha256-h9T4iT704I8iSXNgj/6/lCaKgTgLp5wS6IQZaMgKohI="
},
- "N-V-__8AAH0GaQC8a52s6vfIxg88OZgFgEW6DFxfSK4lX_l3": {
+ "N-V-__8AAEbOfQBnvcFcCX2W5z7tDaN8vaNZGamEQtNOe0UI": {
"name": "imgui",
- "url": "https://deps.files.ghostty.org/imgui-1220bc6b9daceaf7c8c60f3c3998058045ba0c5c5f48ae255ff97776d9cd8bfc6402.tar.gz",
- "hash": "sha256-oF/QHgTPEat4Hig4fGIdLkIPHmBEyOJ6JeYD6pnveGA="
+ "url": "https://github.com/ocornut/imgui/archive/refs/tags/v1.92.5-docking.tar.gz",
+ "hash": "sha256-yBbCDox18+Fa6Gc1DnmSVQLRpqhZOLsac7iSfl8x+cs="
},
- "N-V-__8AANFEAwCzzNzNs3Gaq8pzGNl2BbeyFBwTyO5iZJL-": {
+ "N-V-__8AAIdIAwDt5PxH-cwCxEcTfw4jBV8sR6fZ_XLh-cR7": {
"name": "iterm2_themes",
- "url": "https://github.com/mbadolato/iTerm2-Color-Schemes/releases/download/release-20251201-150531-bfb3ee1/ghostty-themes.tgz",
- "hash": "sha256-5QePBQlSsz9W2r4zTS3QD+cDAeyObhR51E2AkJ3ZIUk="
+ "url": "https://deps.files.ghostty.org/ghostty-themes-release-20260112-150707-28c8f5b.tgz",
+ "hash": "sha256-NIqF12KqXhIrP+LyBtg6WtkHxNUdWOyziAdq8S45RrU="
},
"N-V-__8AAIC5lwAVPJJzxnCAahSvZTIlG-HhtOvnM1uh-66x": {
"name": "jetbrains_mono",
@@ -139,10 +144,10 @@
"url": "https://deps.files.ghostty.org/wuffs-122037b39d577ec2db3fd7b2130e7b69ef6cc1807d68607a7c232c958315d381b5cd.tar.gz",
"hash": "sha256-nkzSCr6W5sTG7enDBXEIhgEm574uLD41UVR2wlC+HBM="
},
- "z2d-0.9.0-j5P_Hu-WFgA_JEfRpiFss6gdvcvS47cgOc0Via2eKD_T": {
+ "z2d-0.10.0-j5P_Hu-6FgBsZNgwphIqh17jDnj8_yPtD8yzjO6PpHRQ": {
"name": "z2d",
- "url": "https://deps.files.ghostty.org/z2d-0.9.0-j5P_Hu-WFgA_JEfRpiFss6gdvcvS47cgOc0Via2eKD_T.tar.gz",
- "hash": "sha256-+QqCRoXwrFA1/l+oWvYVyAVebGQitAFQNhi9U3EVrxA="
+ "url": "https://github.com/vancluever/z2d/archive/refs/tags/v0.10.0.tar.gz",
+ "hash": "sha256-afIdou/V7gk3/lXE0J5Ir8T7L5GgHvFnyMJ1rgRnl/c="
},
"zf-0.10.3-OIRy8RuJAACKA3Lohoumrt85nRbHwbpMcUaLES8vxDnh": {
"name": "zf",
diff --git a/build.zig.zon.nix b/build.zig.zon.nix
index 43a8efe46..430619e74 100644
--- a/build.zig.zon.nix
+++ b/build.zig.zon.nix
@@ -82,6 +82,14 @@
fetcher.${proto};
in
linkFarm name [
+ {
+ name = "N-V-__8AANT61wB--nJ95Gj_ctmzAtcjloZ__hRqNw5lC1Kr";
+ path = fetchZigArtifact {
+ name = "bindings";
+ url = "https://deps.files.ghostty.org/DearBindings_v0.17_ImGui_v1.92.5-docking.tar.gz";
+ hash = "sha256-i/7FAOAJJvZ5hT7iPWfMOS08MYFzPKRwRzhlHT9wuqM=";
+ };
+ }
{
name = "N-V-__8AALw2uwF_03u4JRkZwRLc3Y9hakkYV7NKRR9-RIZJ";
path = fetchZigArtifact {
@@ -155,19 +163,19 @@ in
};
}
{
- name = "N-V-__8AAH0GaQC8a52s6vfIxg88OZgFgEW6DFxfSK4lX_l3";
+ name = "N-V-__8AAEbOfQBnvcFcCX2W5z7tDaN8vaNZGamEQtNOe0UI";
path = fetchZigArtifact {
name = "imgui";
- url = "https://deps.files.ghostty.org/imgui-1220bc6b9daceaf7c8c60f3c3998058045ba0c5c5f48ae255ff97776d9cd8bfc6402.tar.gz";
- hash = "sha256-oF/QHgTPEat4Hig4fGIdLkIPHmBEyOJ6JeYD6pnveGA=";
+ url = "https://github.com/ocornut/imgui/archive/refs/tags/v1.92.5-docking.tar.gz";
+ hash = "sha256-yBbCDox18+Fa6Gc1DnmSVQLRpqhZOLsac7iSfl8x+cs=";
};
}
{
- name = "N-V-__8AANFEAwCzzNzNs3Gaq8pzGNl2BbeyFBwTyO5iZJL-";
+ name = "N-V-__8AAIdIAwDt5PxH-cwCxEcTfw4jBV8sR6fZ_XLh-cR7";
path = fetchZigArtifact {
name = "iterm2_themes";
- url = "https://github.com/mbadolato/iTerm2-Color-Schemes/releases/download/release-20251201-150531-bfb3ee1/ghostty-themes.tgz";
- hash = "sha256-5QePBQlSsz9W2r4zTS3QD+cDAeyObhR51E2AkJ3ZIUk=";
+ url = "https://deps.files.ghostty.org/ghostty-themes-release-20260112-150707-28c8f5b.tgz";
+ hash = "sha256-NIqF12KqXhIrP+LyBtg6WtkHxNUdWOyziAdq8S45RrU=";
};
}
{
@@ -307,11 +315,11 @@ in
};
}
{
- name = "z2d-0.9.0-j5P_Hu-WFgA_JEfRpiFss6gdvcvS47cgOc0Via2eKD_T";
+ name = "z2d-0.10.0-j5P_Hu-6FgBsZNgwphIqh17jDnj8_yPtD8yzjO6PpHRQ";
path = fetchZigArtifact {
name = "z2d";
- url = "https://deps.files.ghostty.org/z2d-0.9.0-j5P_Hu-WFgA_JEfRpiFss6gdvcvS47cgOc0Via2eKD_T.tar.gz";
- hash = "sha256-+QqCRoXwrFA1/l+oWvYVyAVebGQitAFQNhi9U3EVrxA=";
+ url = "https://github.com/vancluever/z2d/archive/refs/tags/v0.10.0.tar.gz";
+ hash = "sha256-afIdou/V7gk3/lXE0J5Ir8T7L5GgHvFnyMJ1rgRnl/c=";
};
}
{
diff --git a/build.zig.zon.txt b/build.zig.zon.txt
index 24a2978d6..72597a650 100644
--- a/build.zig.zon.txt
+++ b/build.zig.zon.txt
@@ -1,16 +1,17 @@
git+https://github.com/jacobsandlund/uucode#5f05f8f83a75caea201f12cc8ea32a2d82ea9732
+https://deps.files.ghostty.org/DearBindings_v0.17_ImGui_v1.92.5-docking.tar.gz
https://deps.files.ghostty.org/JetBrainsMono-2.304.tar.gz
https://deps.files.ghostty.org/NerdFontsSymbolsOnly-3.4.0.tar.gz
https://deps.files.ghostty.org/breakpad-b99f444ba5f6b98cac261cbb391d8766b34a5918.tar.gz
https://deps.files.ghostty.org/fontconfig-2.14.2.tar.gz
https://deps.files.ghostty.org/freetype-1220b81f6ecfb3fd222f76cf9106fecfa6554ab07ec7fdc4124b9bb063ae2adf969d.tar.gz
https://deps.files.ghostty.org/gettext-0.24.tar.gz
+https://deps.files.ghostty.org/ghostty-themes-release-20260112-150707-28c8f5b.tgz
https://deps.files.ghostty.org/glslang-12201278a1a05c0ce0b6eb6026c65cd3e9247aa041b1c260324bf29cee559dd23ba1.tar.gz
https://deps.files.ghostty.org/gobject-2025-11-08-23-1.tar.zst
https://deps.files.ghostty.org/gtk4-layer-shell-1.1.0.tar.gz
https://deps.files.ghostty.org/harfbuzz-11.0.0.tar.xz
https://deps.files.ghostty.org/highway-66486a10623fa0d72fe91260f96c892e41aceb06.tar.gz
-https://deps.files.ghostty.org/imgui-1220bc6b9daceaf7c8c60f3c3998058045ba0c5c5f48ae255ff97776d9cd8bfc6402.tar.gz
https://deps.files.ghostty.org/libpng-1220aa013f0c83da3fb64ea6d327f9173fa008d10e28bc9349eac3463457723b1c66.tar.gz
https://deps.files.ghostty.org/libxev-34fa50878aec6e5fa8f532867001ab3c36fae23e.tar.gz
https://deps.files.ghostty.org/libxml2-2.11.5.tar.gz
@@ -25,11 +26,11 @@ https://deps.files.ghostty.org/vaxis-7dbb9fd3122e4ffad262dd7c151d80d863b68558.ta
https://deps.files.ghostty.org/wayland-9cb3d7aa9dc995ffafdbdef7ab86a949d0fb0e7d.tar.gz
https://deps.files.ghostty.org/wayland-protocols-258d8f88f2c8c25a830c6316f87d23ce1a0f12d9.tar.gz
https://deps.files.ghostty.org/wuffs-122037b39d577ec2db3fd7b2130e7b69ef6cc1807d68607a7c232c958315d381b5cd.tar.gz
-https://deps.files.ghostty.org/z2d-0.9.0-j5P_Hu-WFgA_JEfRpiFss6gdvcvS47cgOc0Via2eKD_T.tar.gz
https://deps.files.ghostty.org/zf-3c52637b7e937c5ae61fd679717da3e276765b23.tar.gz
https://deps.files.ghostty.org/zig_js-04db83c617da1956ac5adc1cb9ba1e434c1cb6fd.tar.gz
https://deps.files.ghostty.org/zig_objc-f356ed02833f0f1b8e84d50bed9e807bf7cdc0ae.tar.gz
https://deps.files.ghostty.org/zig_wayland-1b5c038ec10da20ed3a15b0b2a6db1c21383e8ea.tar.gz
https://deps.files.ghostty.org/zlib-1220fed0c74e1019b3ee29edae2051788b080cd96e90d56836eea857b0b966742efb.tar.gz
https://github.com/ivanstepanovftw/zigimg/archive/d7b7ab0ba0899643831ef042bd73289510b39906.tar.gz
-https://github.com/mbadolato/iTerm2-Color-Schemes/releases/download/release-20251201-150531-bfb3ee1/ghostty-themes.tgz
+https://github.com/ocornut/imgui/archive/refs/tags/v1.92.5-docking.tar.gz
+https://github.com/vancluever/z2d/archive/refs/tags/v0.10.0.tar.gz
diff --git a/dist/linux/ghostty_nautilus.py b/dist/linux/ghostty_nautilus.py
index ff70ed542..f526ab713 100644
--- a/dist/linux/ghostty_nautilus.py
+++ b/dist/linux/ghostty_nautilus.py
@@ -17,87 +17,51 @@
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-from os.path import isdir
-from gi import require_version
-from gi.repository import Nautilus, GObject, Gio, GLib
import os
import gettext
+from gi.repository import Nautilus, GObject, Gio
DOMAIN = "com.mitchellh.ghostty"
share_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
locale_dir = os.path.join(share_dir, "locale")
_ = gettext.translation(DOMAIN, locale_dir, fallback=True).gettext
-class OpenInGhosttyAction(GObject.GObject, Nautilus.MenuProvider):
- def __init__(self):
- super().__init__()
- session = Gio.bus_get_sync(Gio.BusType.SESSION, None)
- self._systemd = None
- # Check if the this system runs under systemd, per sd_booted(3)
- if isdir('/run/systemd/system/'):
- self._systemd = Gio.DBusProxy.new_sync(session,
- Gio.DBusProxyFlags.NONE,
- None,
- "org.freedesktop.systemd1",
- "/org/freedesktop/systemd1",
- "org.freedesktop.systemd1.Manager", None)
-
- def _open_terminal(self, path):
+def open_in_ghostty_activated(_menu, paths):
+ for path in paths:
cmd = ['ghostty', f'--working-directory={path}', '--gtk-single-instance=false']
- child = Gio.Subprocess.new(cmd, Gio.SubprocessFlags.NONE)
- if self._systemd:
- # Move new terminal into a dedicated systemd scope to make systemd
- # track the terminal separately; in particular this makes systemd
- # keep a separate CPU and memory account for the terminal which in turn
- # ensures that oomd doesn't take nautilus down if a process in
- # ghostty consumes a lot of memory.
- pid = int(child.get_identifier())
- props = [("PIDs", GLib.Variant('au', [pid])),
- ('CollectMode', GLib.Variant('s', 'inactive-or-failed'))]
- name = 'app-nautilus-com.mitchellh.ghostty-{}.scope'.format(pid)
- args = GLib.Variant('(ssa(sv)a(sa(sv)))', (name, 'fail', props, []))
- self._systemd.call_sync('StartTransientUnit', args,
- Gio.DBusCallFlags.NO_AUTO_START, 500, None)
+ Gio.Subprocess.new(cmd, Gio.SubprocessFlags.NONE)
- def _menu_item_activated(self, _menu, paths):
- for path in paths:
- self._open_terminal(path)
- def _make_item(self, name, paths):
+def get_paths_to_open(files):
+ paths = []
+ for file in files:
+ location = file.get_location() if file.is_directory() else file.get_parent_location()
+ path = location.get_path()
+ if path and path not in paths:
+ paths.append(path)
+ if 10 < len(paths):
+ # Let's not open anything if the user selected a lot of directories,
+ # to avoid accidentally spamming their desktop with dozends of
+ # new windows or tabs. Ten is a totally arbitrary limit :)
+ return []
+ else:
+ return paths
+
+
+def get_items_for_files(name, files):
+ paths = get_paths_to_open(files)
+ if paths:
item = Nautilus.MenuItem(name=name, label=_('Open in Ghostty'),
icon='com.mitchellh.ghostty')
- item.connect('activate', self._menu_item_activated, paths)
- return item
+ item.connect('activate', open_in_ghostty_activated, paths)
+ return [item]
+ else:
+ return []
- def _paths_to_open(self, files):
- paths = []
- for file in files:
- location = file.get_location() if file.is_directory() else file.get_parent_location()
- path = location.get_path()
- if path and path not in paths:
- paths.append(path)
- if 10 < len(paths):
- # Let's not open anything if the user selected a lot of directories,
- # to avoid accidentally spamming their desktop with dozends of
- # new windows or tabs. Ten is a totally arbitrary limit :)
- return []
- else:
- return paths
- def get_file_items(self, *args):
- # Nautilus 3.0 API passes args (window, files), 4.0 API just passes files
- files = args[0] if len(args) == 1 else args[1]
- paths = self._paths_to_open(files)
- if paths:
- return [self._make_item(name='GhosttyNautilus::open_in_ghostty', paths=paths)]
- else:
- return []
+class GhosttyMenuProvider(GObject.GObject, Nautilus.MenuProvider):
+ def get_file_items(self, files):
+ return get_items_for_files('GhosttyNautilus::open_in_ghostty', files)
- def get_background_items(self, *args):
- # Nautilus 3.0 API passes args (window, file), 4.0 API just passes file
- file = args[0] if len(args) == 1 else args[1]
- paths = self._paths_to_open([file])
- if paths:
- return [self._make_item(name='GhosttyNautilus::open_folder_in_ghostty', paths=paths)]
- else:
- return []
+ def get_background_items(self, file):
+ return get_items_for_files('GhosttyNautilus::open_folder_in_ghostty', [file])
diff --git a/flake.lock b/flake.lock
index a80c2f8ae..be298785c 100644
--- a/flake.lock
+++ b/flake.lock
@@ -41,27 +41,26 @@
]
},
"locked": {
- "lastModified": 1755776884,
- "narHash": "sha256-CPM7zm6csUx7vSfKvzMDIjepEJv1u/usmaT7zydzbuI=",
+ "lastModified": 1768068402,
+ "narHash": "sha256-bAXnnJZKJiF7Xr6eNW6+PhBf1lg2P1aFUO9+xgWkXfA=",
"owner": "nix-community",
"repo": "home-manager",
- "rev": "4fb695d10890e9fc6a19deadf85ff79ffb78da86",
+ "rev": "8bc5473b6bc2b6e1529a9c4040411e1199c43b4c",
"type": "github"
},
"original": {
"owner": "nix-community",
- "ref": "release-25.05",
"repo": "home-manager",
"type": "github"
}
},
"nixpkgs": {
"locked": {
- "lastModified": 1763191728,
- "narHash": "sha256-gI9PpaoX4/f28HkjcTbFVpFhtOxSDtOEdFaHZrdETe0=",
- "rev": "1d4c88323ac36805d09657d13a5273aea1b34f0c",
+ "lastModified": 1768032153,
+ "narHash": "sha256-zvxtwlM8ZlulmZKyYCQAPpkm5dngSEnnHjmjV7Teloc=",
+ "rev": "3146c6aa9995e7351a398e17470e15305e6e18ff",
"type": "tarball",
- "url": "https://releases.nixos.org/nixpkgs/nixpkgs-25.11pre896415.1d4c88323ac3/nixexprs.tar.xz"
+ "url": "https://releases.nixos.org/nixpkgs/nixpkgs-26.05pre925418.3146c6aa9995/nixexprs.tar.xz"
},
"original": {
"type": "tarball",
@@ -126,17 +125,17 @@
]
},
"locked": {
- "lastModified": 1758405547,
- "narHash": "sha256-WgaDgvIZMPvlZcZrpPMjkaalTBnGF2lTG+62znXctWM=",
+ "lastModified": 1768231828,
+ "narHash": "sha256-wL/8Iij4T2OLkhHcc4NieOjf7YeJffaUYbCiCqKv/+0=",
"owner": "jcollie",
"repo": "zon2nix",
- "rev": "bf983aa90ff169372b9fa8c02e57ea75e0b42245",
+ "rev": "c28e93f3ba133d4c1b1d65224e2eebede61fd071",
"type": "github"
},
"original": {
"owner": "jcollie",
"repo": "zon2nix",
- "rev": "bf983aa90ff169372b9fa8c02e57ea75e0b42245",
+ "rev": "c28e93f3ba133d4c1b1d65224e2eebede61fd071",
"type": "github"
}
}
diff --git a/flake.nix b/flake.nix
index d70f23513..a854f6ea3 100644
--- a/flake.nix
+++ b/flake.nix
@@ -28,14 +28,14 @@
};
zon2nix = {
- url = "github:jcollie/zon2nix?rev=bf983aa90ff169372b9fa8c02e57ea75e0b42245";
+ url = "github:jcollie/zon2nix?rev=c28e93f3ba133d4c1b1d65224e2eebede61fd071";
inputs = {
nixpkgs.follows = "nixpkgs";
};
};
home-manager = {
- url = "github:nix-community/home-manager?ref=release-25.05";
+ url = "github:nix-community/home-manager";
inputs = {
nixpkgs.follows = "nixpkgs";
};
@@ -117,7 +117,6 @@
wayland-gnome = runVM ./nix/vm/wayland-gnome.nix;
wayland-plasma6 = runVM ./nix/vm/wayland-plasma6.nix;
x11-cinnamon = runVM ./nix/vm/x11-cinnamon.nix;
- x11-gnome = runVM ./nix/vm/x11-gnome.nix;
x11-plasma6 = runVM ./nix/vm/x11-plasma6.nix;
x11-xfce = runVM ./nix/vm/x11-xfce.nix;
};
diff --git a/flatpak/zig-packages.json b/flatpak/zig-packages.json
index 21f79ec04..3e2b1e26d 100644
--- a/flatpak/zig-packages.json
+++ b/flatpak/zig-packages.json
@@ -1,4 +1,10 @@
[
+ {
+ "type": "archive",
+ "url": "https://deps.files.ghostty.org/DearBindings_v0.17_ImGui_v1.92.5-docking.tar.gz",
+ "dest": "vendor/p/N-V-__8AANT61wB--nJ95Gj_ctmzAtcjloZ__hRqNw5lC1Kr",
+ "sha256": "8bfec500e00926f679853ee23d67cc392d3c3181733ca4704738651d3f70baa3"
+ },
{
"type": "archive",
"url": "https://deps.files.ghostty.org/breakpad-b99f444ba5f6b98cac261cbb391d8766b34a5918.tar.gz",
@@ -55,15 +61,15 @@
},
{
"type": "archive",
- "url": "https://deps.files.ghostty.org/imgui-1220bc6b9daceaf7c8c60f3c3998058045ba0c5c5f48ae255ff97776d9cd8bfc6402.tar.gz",
- "dest": "vendor/p/N-V-__8AAH0GaQC8a52s6vfIxg88OZgFgEW6DFxfSK4lX_l3",
- "sha256": "a05fd01e04cf11ab781e28387c621d2e420f1e6044c8e27a25e603ea99ef7860"
+ "url": "https://github.com/ocornut/imgui/archive/refs/tags/v1.92.5-docking.tar.gz",
+ "dest": "vendor/p/N-V-__8AAEbOfQBnvcFcCX2W5z7tDaN8vaNZGamEQtNOe0UI",
+ "sha256": "c816c20e8c75f3e15ae867350e79925502d1a6a85938bb1a73b8927e5f31f9cb"
},
{
"type": "archive",
- "url": "https://github.com/mbadolato/iTerm2-Color-Schemes/releases/download/release-20251201-150531-bfb3ee1/ghostty-themes.tgz",
- "dest": "vendor/p/N-V-__8AANFEAwCzzNzNs3Gaq8pzGNl2BbeyFBwTyO5iZJL-",
- "sha256": "e5078f050952b33f56dabe334d2dd00fe70301ec8e6e1479d44d80909dd92149"
+ "url": "https://deps.files.ghostty.org/ghostty-themes-release-20260112-150707-28c8f5b.tgz",
+ "dest": "vendor/p/N-V-__8AAIdIAwDt5PxH-cwCxEcTfw4jBV8sR6fZ_XLh-cR7",
+ "sha256": "348a85d762aa5e122b3fe2f206d83a5ad907c4d51d58ecb388076af12e3946b5"
},
{
"type": "archive",
@@ -169,9 +175,9 @@
},
{
"type": "archive",
- "url": "https://deps.files.ghostty.org/z2d-0.9.0-j5P_Hu-WFgA_JEfRpiFss6gdvcvS47cgOc0Via2eKD_T.tar.gz",
- "dest": "vendor/p/z2d-0.9.0-j5P_Hu-WFgA_JEfRpiFss6gdvcvS47cgOc0Via2eKD_T",
- "sha256": "f90a824685f0ac5035fe5fa85af615c8055e6c6422b401503618bd537115af10"
+ "url": "https://github.com/vancluever/z2d/archive/refs/tags/v0.10.0.tar.gz",
+ "dest": "vendor/p/z2d-0.10.0-j5P_Hu-6FgBsZNgwphIqh17jDnj8_yPtD8yzjO6PpHRQ",
+ "sha256": "69f21da2efd5ee0937fe55c4d09e48afc4fb2f91a01ef167c8c275ae046797f7"
},
{
"type": "archive",
diff --git a/include/ghostty.h b/include/ghostty.h
index 47db34e71..3d3973084 100644
--- a/include/ghostty.h
+++ b/include/ghostty.h
@@ -66,6 +66,14 @@ typedef enum {
GHOSTTY_MOUSE_LEFT,
GHOSTTY_MOUSE_RIGHT,
GHOSTTY_MOUSE_MIDDLE,
+ GHOSTTY_MOUSE_FOUR,
+ GHOSTTY_MOUSE_FIVE,
+ GHOSTTY_MOUSE_SIX,
+ GHOSTTY_MOUSE_SEVEN,
+ GHOSTTY_MOUSE_EIGHT,
+ GHOSTTY_MOUSE_NINE,
+ GHOSTTY_MOUSE_TEN,
+ GHOSTTY_MOUSE_ELEVEN,
} ghostty_input_mouse_button_e;
typedef enum {
@@ -102,6 +110,13 @@ typedef enum {
GHOSTTY_MODS_SUPER_RIGHT = 1 << 9,
} ghostty_input_mods_e;
+typedef enum {
+ GHOSTTY_BINDING_FLAGS_CONSUMED = 1 << 0,
+ GHOSTTY_BINDING_FLAGS_ALL = 1 << 1,
+ GHOSTTY_BINDING_FLAGS_GLOBAL = 1 << 2,
+ GHOSTTY_BINDING_FLAGS_PERFORMABLE = 1 << 3,
+} ghostty_binding_flags_e;
+
typedef enum {
GHOSTTY_ACTION_RELEASE,
GHOSTTY_ACTION_PRESS,
@@ -317,12 +332,14 @@ typedef struct {
typedef enum {
GHOSTTY_TRIGGER_PHYSICAL,
GHOSTTY_TRIGGER_UNICODE,
+ GHOSTTY_TRIGGER_CATCH_ALL,
} ghostty_input_trigger_tag_e;
typedef union {
ghostty_input_key_e translated;
ghostty_input_key_e physical;
uint32_t unicode;
+ // catch_all has no payload
} ghostty_input_trigger_key_u;
typedef struct {
@@ -414,6 +431,12 @@ typedef union {
ghostty_platform_ios_s ios;
} ghostty_platform_u;
+typedef enum {
+ GHOSTTY_SURFACE_CONTEXT_WINDOW = 0,
+ GHOSTTY_SURFACE_CONTEXT_TAB = 1,
+ GHOSTTY_SURFACE_CONTEXT_SPLIT = 2,
+} ghostty_surface_context_e;
+
typedef struct {
ghostty_platform_e platform_tag;
ghostty_platform_u platform;
@@ -426,6 +449,7 @@ typedef struct {
size_t env_var_count;
const char* initial_input;
bool wait_after_command;
+ ghostty_surface_context_e context;
} ghostty_surface_config_s;
typedef struct {
@@ -452,6 +476,12 @@ typedef struct {
size_t len;
} ghostty_config_color_list_s;
+// config.RepeatableCommand
+typedef struct {
+ const ghostty_command_s* commands;
+ size_t len;
+} ghostty_config_command_list_s;
+
// config.Palette
typedef struct {
ghostty_config_color_s colors[256];
@@ -689,6 +719,27 @@ typedef struct {
ghostty_input_trigger_s trigger;
} ghostty_action_key_sequence_s;
+// apprt.action.KeyTable.Tag
+typedef enum {
+ GHOSTTY_KEY_TABLE_ACTIVATE,
+ GHOSTTY_KEY_TABLE_DEACTIVATE,
+ GHOSTTY_KEY_TABLE_DEACTIVATE_ALL,
+} ghostty_action_key_table_tag_e;
+
+// apprt.action.KeyTable.CValue
+typedef union {
+ struct {
+ const char *name;
+ size_t len;
+ } activate;
+} ghostty_action_key_table_u;
+
+// apprt.action.KeyTable.C
+typedef struct {
+ ghostty_action_key_table_tag_e tag;
+ ghostty_action_key_table_u value;
+} ghostty_action_key_table_s;
+
// apprt.action.ColorKind
typedef enum {
GHOSTTY_ACTION_COLOR_KIND_FOREGROUND = -1,
@@ -834,6 +885,7 @@ typedef enum {
GHOSTTY_ACTION_FLOAT_WINDOW,
GHOSTTY_ACTION_SECURE_INPUT,
GHOSTTY_ACTION_KEY_SEQUENCE,
+ GHOSTTY_ACTION_KEY_TABLE,
GHOSTTY_ACTION_COLOR_CHANGE,
GHOSTTY_ACTION_RELOAD_CONFIG,
GHOSTTY_ACTION_CONFIG_CHANGE,
@@ -852,7 +904,7 @@ typedef enum {
GHOSTTY_ACTION_SEARCH_TOTAL,
GHOSTTY_ACTION_SEARCH_SELECTED,
GHOSTTY_ACTION_READONLY,
- } ghostty_action_tag_e;
+} ghostty_action_tag_e;
typedef union {
ghostty_action_split_direction_e new_split;
@@ -879,6 +931,7 @@ typedef union {
ghostty_action_float_window_e float_window;
ghostty_action_secure_input_e secure_input;
ghostty_action_key_sequence_s key_sequence;
+ ghostty_action_key_table_s key_table;
ghostty_action_color_change_s color_change;
ghostty_action_reload_config_s reload_config;
ghostty_action_config_change_s config_change;
@@ -971,6 +1024,7 @@ ghostty_config_t ghostty_config_new();
void ghostty_config_free(ghostty_config_t);
ghostty_config_t ghostty_config_clone(ghostty_config_t);
void ghostty_config_load_cli_args(ghostty_config_t);
+void ghostty_config_load_file(ghostty_config_t, const char*);
void ghostty_config_load_default_files(ghostty_config_t);
void ghostty_config_load_recursive_files(ghostty_config_t);
void ghostty_config_finalize(ghostty_config_t);
@@ -1004,7 +1058,7 @@ ghostty_surface_t ghostty_surface_new(ghostty_app_t,
void ghostty_surface_free(ghostty_surface_t);
void* ghostty_surface_userdata(ghostty_surface_t);
ghostty_app_t ghostty_surface_app(ghostty_surface_t);
-ghostty_surface_config_s ghostty_surface_inherited_config(ghostty_surface_t);
+ghostty_surface_config_s ghostty_surface_inherited_config(ghostty_surface_t, ghostty_surface_context_e);
void ghostty_surface_update_config(ghostty_surface_t, ghostty_config_t);
bool ghostty_surface_needs_confirm_quit(ghostty_surface_t);
bool ghostty_surface_process_exited(ghostty_surface_t);
@@ -1019,9 +1073,10 @@ void ghostty_surface_set_color_scheme(ghostty_surface_t,
ghostty_color_scheme_e);
ghostty_input_mods_e ghostty_surface_key_translation_mods(ghostty_surface_t,
ghostty_input_mods_e);
-void ghostty_surface_commands(ghostty_surface_t, ghostty_command_s**, size_t*);
bool ghostty_surface_key(ghostty_surface_t, ghostty_input_key_s);
-bool ghostty_surface_key_is_binding(ghostty_surface_t, ghostty_input_key_s);
+bool ghostty_surface_key_is_binding(ghostty_surface_t,
+ ghostty_input_key_s,
+ ghostty_binding_flags_e*);
void ghostty_surface_text(ghostty_surface_t, const char*, uintptr_t);
void ghostty_surface_preedit(ghostty_surface_t, const char*, uintptr_t);
bool ghostty_surface_mouse_captured(ghostty_surface_t);
diff --git a/macos/Ghostty-Info.plist b/macos/Ghostty-Info.plist
index 2bf3b0bae..5960dc0e7 100644
--- a/macos/Ghostty-Info.plist
+++ b/macos/Ghostty-Info.plist
@@ -100,5 +100,20 @@
SUPublicEDKey
wsNcGf5hirwtdXMVnYoxRIX/SqZQLMOsYlD3q3imeok=
+ UTExportedTypeDeclarations
+
+
+ UTTypeIdentifier
+ com.mitchellh.ghosttySurfaceId
+ UTTypeDescription
+ Ghostty Surface Identifier
+ UTTypeConformsTo
+
+ public.data
+
+ UTTypeTagSpecification
+
+
+
diff --git a/macos/Ghostty.xcodeproj/project.pbxproj b/macos/Ghostty.xcodeproj/project.pbxproj
index 1a810e621..adcc107e1 100644
--- a/macos/Ghostty.xcodeproj/project.pbxproj
+++ b/macos/Ghostty.xcodeproj/project.pbxproj
@@ -28,6 +28,13 @@
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
+ 810ACCA52E9D3302004F8F92 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = A5B30529299BEAAA0047F10C /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = A5B30530299BEAAA0047F10C;
+ remoteInfo = Ghostty;
+ };
A54F45F72E1F047A0046BD5C /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = A5B30529299BEAAA0047F10C /* Project object */;
@@ -42,6 +49,7 @@
3B39CAA42B33949B00DABEB8 /* GhosttyReleaseLocal.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = GhosttyReleaseLocal.entitlements; sourceTree = ""; };
55154BDF2B33911F001622DC /* ghostty */ = {isa = PBXFileReference; lastKnownFileType = folder; name = ghostty; path = "../zig-out/share/ghostty"; sourceTree = ""; };
552964E52B34A9B400030505 /* vim */ = {isa = PBXFileReference; lastKnownFileType = folder; name = vim; path = "../zig-out/share/vim"; sourceTree = ""; };
+ 810ACC9F2E9D3301004F8F92 /* GhosttyUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = GhosttyUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
9351BE8E2D22937F003B3499 /* nvim */ = {isa = PBXFileReference; lastKnownFileType = folder; name = nvim; path = "../zig-out/share/nvim"; sourceTree = ""; };
A51BFC282B30F26D00E92F16 /* GhosttyDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = GhosttyDebug.entitlements; sourceTree = ""; };
A546F1132D7B68D7003B11A0 /* locale */ = {isa = PBXFileReference; lastKnownFileType = folder; name = locale; path = "../zig-out/share/locale"; sourceTree = ""; };
@@ -66,11 +74,13 @@
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
App/macOS/AppDelegate.swift,
+ "App/macOS/AppDelegate+Ghostty.swift",
App/macOS/main.swift,
App/macOS/MainMenu.xib,
Features/About/About.xib,
Features/About/AboutController.swift,
Features/About/AboutView.swift,
+ Features/About/CyclingIconView.swift,
"Features/App Intents/CloseTerminalIntent.swift",
"Features/App Intents/CommandPaletteIntent.swift",
"Features/App Intents/Entities/CommandEntity.swift",
@@ -118,6 +128,7 @@
Features/Terminal/TerminalRestorable.swift,
Features/Terminal/TerminalTabColor.swift,
Features/Terminal/TerminalView.swift,
+ Features/Terminal/TerminalViewContainer.swift,
"Features/Terminal/Window Styles/HiddenTitlebarTerminalWindow.swift",
"Features/Terminal/Window Styles/Terminal.xib",
"Features/Terminal/Window Styles/TerminalHiddenTitlebar.xib",
@@ -137,19 +148,19 @@
Features/Update/UpdateSimulator.swift,
Features/Update/UpdateViewModel.swift,
"Ghostty/FullscreenMode+Extension.swift",
- Ghostty/Ghostty.Command.swift,
Ghostty/Ghostty.Error.swift,
Ghostty/Ghostty.Event.swift,
Ghostty/Ghostty.Input.swift,
Ghostty/Ghostty.Surface.swift,
- Ghostty/InspectorView.swift,
"Ghostty/NSEvent+Extension.swift",
- Ghostty/SurfaceScrollView.swift,
- Ghostty/SurfaceView_AppKit.swift,
+ "Ghostty/Surface View/InspectorView.swift",
+ "Ghostty/Surface View/SurfaceDragSource.swift",
+ "Ghostty/Surface View/SurfaceGrabHandle.swift",
+ "Ghostty/Surface View/SurfaceScrollView.swift",
+ "Ghostty/Surface View/SurfaceView_AppKit.swift",
Helpers/AppInfo.swift,
Helpers/CodableBridge.swift,
Helpers/Cursor.swift,
- Helpers/DraggableWindowView.swift,
Helpers/ExpiringUndoManager.swift,
"Helpers/Extensions/Double+Extension.swift",
"Helpers/Extensions/EventModifiers+Extension.swift",
@@ -166,6 +177,7 @@
"Helpers/Extensions/NSView+Extension.swift",
"Helpers/Extensions/NSWindow+Extension.swift",
"Helpers/Extensions/NSWorkspace+Extension.swift",
+ "Helpers/Extensions/Transferable+Extension.swift",
"Helpers/Extensions/UndoManager+Extension.swift",
"Helpers/Extensions/View+Extension.swift",
Helpers/Fullscreen.swift,
@@ -187,18 +199,26 @@
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
App/iOS/iOSApp.swift,
- Ghostty/SurfaceView_UIKit.swift,
+ "Ghostty/Surface View/SurfaceView_UIKit.swift",
);
target = A5B30530299BEAAA0047F10C /* Ghostty */;
};
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
/* Begin PBXFileSystemSynchronizedRootGroup section */
+ 810ACCA02E9D3302004F8F92 /* GhosttyUITests */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = GhosttyUITests; sourceTree = ""; };
81F82BC72E82815D001EDFA7 /* Sources */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (81F82CB12E8281F9001EDFA7 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, 81F82CB02E8281F5001EDFA7 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = Sources; sourceTree = ""; };
A54F45F42E1F047A0046BD5C /* Tests */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = Tests; sourceTree = ""; };
/* End PBXFileSystemSynchronizedRootGroup section */
/* Begin PBXFrameworksBuildPhase section */
+ 810ACC9C2E9D3301004F8F92 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
A54F45F02E1F047A0046BD5C /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
@@ -255,6 +275,7 @@
3B39CAA42B33949B00DABEB8 /* GhosttyReleaseLocal.entitlements */,
81F82BC72E82815D001EDFA7 /* Sources */,
A54F45F42E1F047A0046BD5C /* Tests */,
+ 810ACCA02E9D3302004F8F92 /* GhosttyUITests */,
A5D495A3299BECBA00DD1313 /* Frameworks */,
A5A1F8862A489D7400D1E8BC /* Resources */,
A5B30532299BEAAA0047F10C /* Products */,
@@ -267,6 +288,7 @@
A5B30531299BEAAA0047F10C /* Ghostty.app */,
A5D4499D2B53AE7B000F5B83 /* Ghostty-iOS.app */,
A54F45F32E1F047A0046BD5C /* GhosttyTests.xctest */,
+ 810ACC9F2E9D3301004F8F92 /* GhosttyUITests.xctest */,
);
name = Products;
sourceTree = "";
@@ -283,6 +305,29 @@
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
+ 810ACC9E2E9D3301004F8F92 /* GhosttyUITests */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 810ACCA72E9D3302004F8F92 /* Build configuration list for PBXNativeTarget "GhosttyUITests" */;
+ buildPhases = (
+ 810ACC9B2E9D3301004F8F92 /* Sources */,
+ 810ACC9C2E9D3301004F8F92 /* Frameworks */,
+ 810ACC9D2E9D3301004F8F92 /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ 810ACCA62E9D3302004F8F92 /* PBXTargetDependency */,
+ );
+ fileSystemSynchronizedGroups = (
+ 810ACCA02E9D3302004F8F92 /* GhosttyUITests */,
+ );
+ name = GhosttyUITests;
+ packageProductDependencies = (
+ );
+ productName = GhosttyUITests;
+ productReference = 810ACC9F2E9D3301004F8F92 /* GhosttyUITests.xctest */;
+ productType = "com.apple.product-type.bundle.ui-testing";
+ };
A54F45F22E1F047A0046BD5C /* GhosttyTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = A54F45FC2E1F047A0046BD5C /* Build configuration list for PBXNativeTarget "GhosttyTests" */;
@@ -356,9 +401,13 @@
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = 1;
- LastSwiftUpdateCheck = 2600;
+ LastSwiftUpdateCheck = 2610;
LastUpgradeCheck = 1610;
TargetAttributes = {
+ 810ACC9E2E9D3301004F8F92 = {
+ CreatedOnToolsVersion = 26.1;
+ TestTargetID = A5B30530299BEAAA0047F10C;
+ };
A54F45F22E1F047A0046BD5C = {
CreatedOnToolsVersion = 26.0;
TestTargetID = A5B30530299BEAAA0047F10C;
@@ -391,11 +440,19 @@
A5B30530299BEAAA0047F10C /* Ghostty */,
A5D4499C2B53AE7B000F5B83 /* Ghostty-iOS */,
A54F45F22E1F047A0046BD5C /* GhosttyTests */,
+ 810ACC9E2E9D3301004F8F92 /* GhosttyUITests */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
+ 810ACC9D2E9D3301004F8F92 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
A54F45F12E1F047A0046BD5C /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
@@ -434,6 +491,13 @@
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
+ 810ACC9B2E9D3301004F8F92 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
A54F45EF2E1F047A0046BD5C /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
@@ -458,6 +522,11 @@
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
+ 810ACCA62E9D3302004F8F92 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = A5B30530299BEAAA0047F10C /* Ghostty */;
+ targetProxy = 810ACCA52E9D3302004F8F92 /* PBXContainerItemProxy */;
+ };
A54F45F82E1F047A0046BD5C /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = A5B30530299BEAAA0047F10C /* Ghostty */;
@@ -575,6 +644,73 @@
};
name = ReleaseLocal;
};
+ 810ACCA82E9D3302004F8F92 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ ENABLE_USER_SCRIPT_SANDBOXING = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu17;
+ GENERATE_INFOPLIST_FILE = YES;
+ LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
+ MACOSX_DEPLOYMENT_TARGET = 13.0;
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = com.mitchellh.GhosttyUITests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ STRING_CATALOG_GENERATE_SYMBOLS = NO;
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
+ SWIFT_APPROACHABLE_CONCURRENCY = YES;
+ SWIFT_EMIT_LOC_STRINGS = NO;
+ SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
+ SWIFT_VERSION = 5.0;
+ TEST_TARGET_NAME = Ghostty;
+ };
+ name = Debug;
+ };
+ 810ACCA92E9D3302004F8F92 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ ENABLE_USER_SCRIPT_SANDBOXING = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu17;
+ GENERATE_INFOPLIST_FILE = YES;
+ LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
+ MACOSX_DEPLOYMENT_TARGET = 13.0;
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = com.mitchellh.GhosttyUITests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ STRING_CATALOG_GENERATE_SYMBOLS = NO;
+ SWIFT_APPROACHABLE_CONCURRENCY = YES;
+ SWIFT_EMIT_LOC_STRINGS = NO;
+ SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
+ SWIFT_VERSION = 5.0;
+ TEST_TARGET_NAME = Ghostty;
+ };
+ name = Release;
+ };
+ 810ACCAA2E9D3302004F8F92 /* ReleaseLocal */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ ENABLE_USER_SCRIPT_SANDBOXING = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu17;
+ GENERATE_INFOPLIST_FILE = YES;
+ LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
+ MACOSX_DEPLOYMENT_TARGET = 13.0;
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = com.mitchellh.GhosttyUITests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ STRING_CATALOG_GENERATE_SYMBOLS = NO;
+ SWIFT_APPROACHABLE_CONCURRENCY = YES;
+ SWIFT_EMIT_LOC_STRINGS = NO;
+ SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
+ SWIFT_VERSION = 5.0;
+ TEST_TARGET_NAME = Ghostty;
+ };
+ name = ReleaseLocal;
+ };
A54F45F92E1F047A0046BD5C /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@@ -991,6 +1127,16 @@
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
+ 810ACCA72E9D3302004F8F92 /* Build configuration list for PBXNativeTarget "GhosttyUITests" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 810ACCA82E9D3302004F8F92 /* Debug */,
+ 810ACCA92E9D3302004F8F92 /* Release */,
+ 810ACCAA2E9D3302004F8F92 /* ReleaseLocal */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = ReleaseLocal;
+ };
A54F45FC2E1F047A0046BD5C /* Build configuration list for PBXNativeTarget "GhosttyTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
diff --git a/macos/Ghostty.xcodeproj/xcshareddata/xcschemes/Ghostty.xcscheme b/macos/Ghostty.xcodeproj/xcshareddata/xcschemes/Ghostty.xcscheme
index 0d8761c9e..2b4f815ea 100644
--- a/macos/Ghostty.xcodeproj/xcshareddata/xcschemes/Ghostty.xcscheme
+++ b/macos/Ghostty.xcodeproj/xcshareddata/xcschemes/Ghostty.xcscheme
@@ -40,6 +40,17 @@
ReferencedContainer = "container:Ghostty.xcodeproj">
+
+
+
+
0.5
+ }
+
+ var luminance: Double {
+ var r: CGFloat = 0
+ var g: CGFloat = 0
+ var b: CGFloat = 0
+ var a: CGFloat = 0
+
+ guard let rgb = self.usingColorSpace(.sRGB) else { return 0 }
+ rgb.getRed(&r, green: &g, blue: &b, alpha: &a)
+ return (0.299 * r) + (0.587 * g) + (0.114 * b)
+ }
+}
+
+extension NSImage {
+ func colorAt(x: Int, y: Int) -> NSColor? {
+ guard let cgImage = self.cgImage(forProposedRect: nil, context: nil, hints: nil) else {
+ return nil
+ }
+ return NSBitmapImageRep(cgImage: cgImage).colorAt(x: x, y: y)
+ }
+}
diff --git a/macos/GhosttyUITests/GhosttyCustomConfigCase.swift b/macos/GhosttyUITests/GhosttyCustomConfigCase.swift
new file mode 100644
index 000000000..41993247a
--- /dev/null
+++ b/macos/GhosttyUITests/GhosttyCustomConfigCase.swift
@@ -0,0 +1,59 @@
+//
+// GhosttyCustomConfigCase.swift
+// Ghostty
+//
+// Created by luca on 16.10.2025.
+//
+
+import XCTest
+
+class GhosttyCustomConfigCase: XCTestCase {
+ /// We only want run these UI tests
+ /// when testing manually with Xcode IDE
+ ///
+ /// So that we don't have to wait for each ci check
+ /// to run these tedious tests
+ override class var defaultTestSuite: XCTestSuite {
+ // https://lldb.llvm.org/cpp_reference/PlatformDarwin_8cpp_source.html#:~:text==%20%22-,IDE_DISABLED_OS_ACTIVITY_DT_MODE
+
+ if ProcessInfo.processInfo.environment["IDE_DISABLED_OS_ACTIVITY_DT_MODE"] != nil {
+ return XCTestSuite(forTestCaseClass: Self.self)
+ } else {
+ return XCTestSuite(name: "Skipping \(className())")
+ }
+ }
+
+ override class var runsForEachTargetApplicationUIConfiguration: Bool {
+ true
+ }
+
+ var configFile: URL?
+ override func setUpWithError() throws {
+ continueAfterFailure = false
+ }
+
+ override func tearDown() async throws {
+ if let configFile {
+ try FileManager.default.removeItem(at: configFile)
+ }
+ }
+
+ func updateConfig(_ newConfig: String) throws {
+ if configFile == nil {
+ let temporaryConfig = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
+ .appendingPathExtension("ghostty")
+ configFile = temporaryConfig
+ }
+ try newConfig.write(to: configFile!, atomically: true, encoding: .utf8)
+ }
+
+ func ghosttyApplication() throws -> XCUIApplication {
+ let app = XCUIApplication()
+ app.launchArguments.append(contentsOf: ["-ApplePersistenceIgnoreState", "YES"])
+ guard let configFile else {
+ return app
+ }
+ app.launchEnvironment["GHOSTTY_CONFIG_PATH"] = configFile.path
+ return app
+ }
+}
diff --git a/macos/GhosttyUITests/GhosttyThemeTests.swift b/macos/GhosttyUITests/GhosttyThemeTests.swift
new file mode 100644
index 000000000..f8f5286fb
--- /dev/null
+++ b/macos/GhosttyUITests/GhosttyThemeTests.swift
@@ -0,0 +1,159 @@
+//
+// GhosttyThemeTests.swift
+// Ghostty
+//
+// Created by luca on 27.10.2025.
+//
+
+import AppKit
+import XCTest
+
+final class GhosttyThemeTests: GhosttyCustomConfigCase {
+ let windowTitle = "GhosttyThemeTests"
+ private func assertTitlebarAppearance(
+ _ appearance: XCUIDevice.Appearance,
+ for app: XCUIApplication,
+ title: String? = nil,
+ colorLocation: CGPoint? = nil,
+ file: StaticString = #filePath,
+ line: UInt = #line
+ ) throws {
+ for i in 0 ..< app.windows.count {
+ let titleView = app.windows.element(boundBy: i).staticTexts.element(matching: NSPredicate(format: "value == '\(title ?? windowTitle)'"))
+
+ let image = titleView.screenshot().image
+ guard let imageColor = image.colorAt(x: Int(colorLocation?.x ?? 1), y: Int(colorLocation?.y ?? 1)) else {
+ throw XCTSkip("failed to get pixel color", file: file, line: line)
+ }
+
+ switch appearance {
+ case .dark:
+ XCTAssertLessThanOrEqual(imageColor.luminance, 0.5, "Expected dark appearance for this test", file: file, line: line)
+ default:
+ XCTAssertGreaterThanOrEqual(imageColor.luminance, 0.5, "Expected light appearance for this test", file: file, line: line)
+ }
+ }
+ }
+
+ /// https://github.com/ghostty-org/ghostty/issues/8282
+ @MainActor
+ func testIssue8282() async throws {
+ try updateConfig("title=\(windowTitle) \n theme=light:3024 Day,dark:3024 Night")
+ XCUIDevice.shared.appearance = .dark
+
+ let app = try ghosttyApplication()
+ app.launch()
+ try assertTitlebarAppearance(.dark, for: app)
+ // create a split
+ app.groups["Terminal pane"].typeKey("d", modifierFlags: .command)
+ // reload config
+ app.typeKey(",", modifierFlags: [.command, .shift])
+ try await Task.sleep(for: .seconds(0.5))
+ // create a new window
+ app.typeKey("n", modifierFlags: [.command])
+ try assertTitlebarAppearance(.dark, for: app)
+ }
+
+ @MainActor
+ func testLightTransparentWindowThemeWithDarkTerminal() async throws {
+ try updateConfig("title=\(windowTitle) \n window-theme=light")
+ let app = try ghosttyApplication()
+ app.launch()
+ try await Task.sleep(for: .seconds(0.5))
+ try assertTitlebarAppearance(.dark, for: app)
+ }
+
+ @MainActor
+ func testLightNativeWindowThemeWithDarkTerminal() async throws {
+ try updateConfig("title=\(windowTitle) \n window-theme = light \n macos-titlebar-style = native")
+ let app = try ghosttyApplication()
+ app.launch()
+ try assertTitlebarAppearance(.light, for: app)
+ }
+
+ @MainActor
+ func testReloadingLightTransparentWindowTheme() async throws {
+ try updateConfig("title=\(windowTitle) \n ")
+ let app = try ghosttyApplication()
+ app.launch()
+ // default dark theme
+ try assertTitlebarAppearance(.dark, for: app)
+ try updateConfig("title=\(windowTitle) \n theme=light:3024 Day,dark:3024 Night \n window-theme = light")
+ // reload config
+ app.typeKey(",", modifierFlags: [.command, .shift])
+ try await Task.sleep(for: .seconds(0.5))
+ try assertTitlebarAppearance(.light, for: app)
+ }
+
+ @MainActor
+ func testSwitchingSystemTheme() async throws {
+ try updateConfig("title=\(windowTitle) \n theme=light:3024 Day,dark:3024 Night")
+ XCUIDevice.shared.appearance = .dark
+ let app = try ghosttyApplication()
+ app.launch()
+ try assertTitlebarAppearance(.dark, for: app)
+ XCUIDevice.shared.appearance = .light
+ try await Task.sleep(for: .seconds(0.5))
+ try assertTitlebarAppearance(.light, for: app)
+ }
+
+ @MainActor
+ func testReloadFromLightWindowThemeToDefaultTheme() async throws {
+ try updateConfig("title=\(windowTitle) \n theme=light:3024 Day,dark:3024 Night")
+ XCUIDevice.shared.appearance = .light
+ let app = try ghosttyApplication()
+ app.launch()
+ try assertTitlebarAppearance(.light, for: app)
+ try updateConfig("title=\(windowTitle) \n ")
+ // reload config
+ app.typeKey(",", modifierFlags: [.command, .shift])
+ try await Task.sleep(for: .seconds(0.5))
+ try assertTitlebarAppearance(.dark, for: app)
+ }
+
+ @MainActor
+ func testReloadFromDefaultThemeToDarkWindowTheme() async throws {
+ try updateConfig("title=\(windowTitle) \n ")
+ XCUIDevice.shared.appearance = .light
+ let app = try ghosttyApplication()
+ app.launch()
+ try assertTitlebarAppearance(.dark, for: app)
+ try updateConfig("title=\(windowTitle) \n theme=light:3024 Day,dark:3024 Night \n window-theme=dark")
+ // reload config
+ app.typeKey(",", modifierFlags: [.command, .shift])
+ try await Task.sleep(for: .seconds(0.5))
+ try assertTitlebarAppearance(.dark, for: app)
+ }
+
+ @MainActor
+ func testReloadingFromDarkThemeToSystemLightTheme() async throws {
+ try updateConfig("title=\(windowTitle) \n theme=light:3024 Day,dark:3024 Night \n window-theme=dark")
+ XCUIDevice.shared.appearance = .light
+ let app = try ghosttyApplication()
+ app.launch()
+ try assertTitlebarAppearance(.dark, for: app)
+ try updateConfig("title=\(windowTitle) \n theme=light:3024 Day,dark:3024 Night")
+ // reload config
+ app.typeKey(",", modifierFlags: [.command, .shift])
+ try await Task.sleep(for: .seconds(0.5))
+ try assertTitlebarAppearance(.light, for: app)
+ }
+
+ @MainActor
+ func testQuickTerminalThemeChange() async throws {
+ try updateConfig("title=\(windowTitle) \n theme=light:3024 Day,dark:3024 Night \n confirm-close-surface=false")
+ XCUIDevice.shared.appearance = .light
+ let app = try ghosttyApplication()
+ app.launch()
+ // close default window
+ app.typeKey("w", modifierFlags: [.command])
+ // open quick terminal
+ app.menuBarItems["View"].firstMatch.click()
+ app.menuItems["Quick Terminal"].firstMatch.click()
+ let title = "Debug builds of Ghostty are very slow and you may experience performance problems. Debug builds are only recommended during development."
+ try assertTitlebarAppearance(.light, for: app, title: title, colorLocation: CGPoint(x: 5, y: 5)) // to avoid dark edge
+ XCUIDevice.shared.appearance = .dark
+ try await Task.sleep(for: .seconds(0.5))
+ try assertTitlebarAppearance(.dark, for: app, title: title, colorLocation: CGPoint(x: 5, y: 5))
+ }
+}
diff --git a/macos/GhosttyUITests/GhosttyTitleUITests.swift b/macos/GhosttyUITests/GhosttyTitleUITests.swift
new file mode 100644
index 000000000..01bc64023
--- /dev/null
+++ b/macos/GhosttyUITests/GhosttyTitleUITests.swift
@@ -0,0 +1,23 @@
+//
+// GhosttyTitleUITests.swift
+// GhosttyUITests
+//
+// Created by luca on 13.10.2025.
+//
+
+import XCTest
+
+final class GhosttyTitleUITests: GhosttyCustomConfigCase {
+ override func setUp() async throws {
+ try await super.setUp()
+ try updateConfig(#"title = "GhosttyUITestsLaunchTests""#)
+ }
+
+ @MainActor
+ func testTitle() throws {
+ let app = try ghosttyApplication()
+ app.launch()
+
+ XCTAssertEqual(app.windows.firstMatch.title, "GhosttyUITestsLaunchTests", "Oops, `title=` doesn't work!")
+ }
+}
diff --git a/macos/GhosttyUITests/GhosttyTitlebarTabsUITests.swift b/macos/GhosttyUITests/GhosttyTitlebarTabsUITests.swift
new file mode 100644
index 000000000..bf8b6124e
--- /dev/null
+++ b/macos/GhosttyUITests/GhosttyTitlebarTabsUITests.swift
@@ -0,0 +1,143 @@
+//
+// GhosttyTitlebarTabsUITests.swift
+// Ghostty
+//
+// Created by luca on 16.10.2025.
+//
+
+import XCTest
+
+final class GhosttyTitlebarTabsUITests: GhosttyCustomConfigCase {
+ override func setUp() async throws {
+ try await super.setUp()
+
+ try updateConfig(
+ """
+ macos-titlebar-style = tabs
+ title = "GhosttyTitlebarTabsUITests"
+ """
+ )
+ }
+
+ @MainActor
+ func testCustomTitlebar() throws {
+ let app = try ghosttyApplication()
+ app.launch()
+ // create a split
+ app.groups["Terminal pane"].typeKey("d", modifierFlags: .command)
+ app.typeKey("\n", modifierFlags: [.command, .shift])
+ let resetZoomButton = app.groups.buttons["ResetZoom"]
+ let windowTitle = app.windows.firstMatch.title
+ let titleView = app.staticTexts.element(matching: NSPredicate(format: "value == '\(windowTitle)'"))
+
+ XCTAssertEqual(titleView.frame.midY, resetZoomButton.frame.midY, accuracy: 1, "Window title should be vertically centered with reset zoom button: \(titleView.frame.midY) != \(resetZoomButton.frame.midY)")
+ }
+
+ @MainActor
+ func testTabsGeometryInNormalWindow() throws {
+ let app = try ghosttyApplication()
+ app.launch()
+ app.groups["Terminal pane"].typeKey("t", modifierFlags: .command)
+ XCTAssertEqual(app.tabs.count, 2, "There should be 2 tabs")
+ checkTabsGeometry(app.windows.firstMatch)
+ }
+
+ @MainActor
+ func testTabsGeometryInFullscreen() throws {
+ let app = try ghosttyApplication()
+ app.launch()
+ app.typeKey("f", modifierFlags: [.command, .control])
+ // using app to type โ+t might not be able to create tabs
+ app.groups["Terminal pane"].typeKey("t", modifierFlags: .command)
+ XCTAssertEqual(app.tabs.count, 2, "There should be 2 tabs")
+ checkTabsGeometry(app.windows.firstMatch)
+ }
+
+ @MainActor
+ func testTabsGeometryAfterMovingTabs() throws {
+ let app = try ghosttyApplication()
+ app.launch()
+ XCTAssertTrue(app.windows.firstMatch.waitForExistence(timeout: 1), "Main window should exist")
+ // create another 2 tabs
+ app.groups["Terminal pane"].typeKey("t", modifierFlags: .command)
+ app.groups["Terminal pane"].typeKey("t", modifierFlags: .command)
+
+ // move to the left
+ app.menuItems["_zoomLeft:"].firstMatch.click()
+
+ // create another window with 2 tabs
+ app.windows.firstMatch.groups["Terminal pane"].typeKey("n", modifierFlags: .command)
+ XCTAssertEqual(app.windows.count, 2, "There should be 2 windows")
+
+ // move to the right
+ app.menuItems["_zoomRight:"].firstMatch.click()
+
+ // now second window is the first/main one in the list
+ app.windows.firstMatch.groups["Terminal pane"].typeKey("t", modifierFlags: .command)
+
+ app.windows.element(boundBy: 1).tabs.firstMatch.click() // focus first window
+
+ // now the first window is the main one
+ let firstTabInFirstWindow = app.windows.firstMatch.tabs.firstMatch
+ let firstTabInSecondWindow = app.windows.element(boundBy: 1).tabs.firstMatch
+
+ // drag a tab from one window to another
+ firstTabInFirstWindow.press(forDuration: 0.2, thenDragTo: firstTabInSecondWindow)
+
+ // check tabs in the first
+ checkTabsGeometry(app.windows.firstMatch)
+ // focus another window
+ app.windows.element(boundBy: 1).tabs.firstMatch.click()
+ checkTabsGeometry(app.windows.firstMatch)
+ }
+
+ @MainActor
+ func testTabsGeometryAfterMergingAllWindows() throws {
+ let app = try ghosttyApplication()
+ app.launch()
+ XCTAssertTrue(app.windows.firstMatch.waitForExistence(timeout: 1), "Main window should exist")
+
+ // create another 2 windows
+ app.typeKey("n", modifierFlags: .command)
+ app.typeKey("n", modifierFlags: .command)
+
+ // merge into one window, resulting 3 tabs
+ app.menuItems["mergeAllWindows:"].firstMatch.click()
+
+ XCTAssertTrue(app.wait(for: \.tabs.count, toEqual: 3, timeout: 1), "There should be 3 tabs")
+ checkTabsGeometry(app.windows.firstMatch)
+ }
+
+ func checkTabsGeometry(_ window: XCUIElement) {
+ let closeTabButtons = window.buttons.matching(identifier: "_closeButton")
+
+ XCTAssertEqual(closeTabButtons.count, window.tabs.count, "Close tab buttons count should match tabs count")
+
+ var previousTabHeight: CGFloat?
+ for idx in 0 ..< window.tabs.count {
+ let currentTab = window.tabs.element(boundBy: idx)
+ // focus
+ currentTab.click()
+ // switch to the tab
+ window.typeKey("\(idx + 1)", modifierFlags: .command)
+ // add a split
+ window.typeKey("d", modifierFlags: .command)
+ // zoom this split
+ // haven't found a way to locate our reset zoom button yet..
+ window.typeKey("\n", modifierFlags: [.command, .shift])
+ window.typeKey("\n", modifierFlags: [.command, .shift])
+
+ if let previousHeight = previousTabHeight {
+ XCTAssertEqual(currentTab.frame.height, previousHeight, accuracy: 1, "The tab's height should stay the same")
+ }
+ previousTabHeight = currentTab.frame.height
+
+ let titleFrame = currentTab.frame
+ let shortcutLabelFrame = window.staticTexts.element(matching: NSPredicate(format: "value CONTAINS[c] 'โ\(idx + 1)'")).firstMatch.frame
+ let closeButtonFrame = closeTabButtons.element(boundBy: idx).frame
+
+ XCTAssertEqual(titleFrame.midY, shortcutLabelFrame.midY, accuracy: 1, "Tab title should be vertically centered with its shortcut label: \(titleFrame.midY) != \(shortcutLabelFrame.midY)")
+ XCTAssertEqual(titleFrame.midY, closeButtonFrame.midY, accuracy: 1, "Tab title should be vertically centered with its close button: \(titleFrame.midY) != \(closeButtonFrame.midY)")
+ }
+ }
+}
diff --git a/macos/Sources/App/iOS/iOSApp.swift b/macos/Sources/App/iOS/iOSApp.swift
index 4af94491c..a1aafcc7d 100644
--- a/macos/Sources/App/iOS/iOSApp.swift
+++ b/macos/Sources/App/iOS/iOSApp.swift
@@ -1,8 +1,16 @@
import SwiftUI
+import GhosttyKit
@main
struct Ghostty_iOSApp: App {
- @StateObject private var ghostty_app = Ghostty.App()
+ @StateObject private var ghostty_app: Ghostty.App
+
+ init() {
+ if ghostty_init(UInt(CommandLine.argc), CommandLine.unsafeArgv) != GHOSTTY_SUCCESS {
+ preconditionFailure("Initialize ghostty backend failed")
+ }
+ _ghostty_app = StateObject(wrappedValue: Ghostty.App())
+ }
var body: some Scene {
WindowGroup {
diff --git a/macos/Sources/App/macOS/AppDelegate+Ghostty.swift b/macos/Sources/App/macOS/AppDelegate+Ghostty.swift
new file mode 100644
index 000000000..4d798a1a5
--- /dev/null
+++ b/macos/Sources/App/macOS/AppDelegate+Ghostty.swift
@@ -0,0 +1,23 @@
+import AppKit
+
+// MARK: Ghostty Delegate
+
+/// This implements the Ghostty app delegate protocol which is used by the Ghostty
+/// APIs for app-global information.
+extension AppDelegate: Ghostty.Delegate {
+ func ghosttySurface(id: UUID) -> Ghostty.SurfaceView? {
+ for window in NSApp.windows {
+ guard let controller = window.windowController as? BaseTerminalController else {
+ continue
+ }
+
+ for surface in controller.surfaceTree {
+ if surface.id == id {
+ return surface
+ }
+ }
+ }
+
+ return nil
+ }
+}
diff --git a/macos/Sources/App/macOS/AppDelegate.swift b/macos/Sources/App/macOS/AppDelegate.swift
index 57bfba828..381fb9db2 100644
--- a/macos/Sources/App/macOS/AppDelegate.swift
+++ b/macos/Sources/App/macOS/AppDelegate.swift
@@ -46,6 +46,8 @@ class AppDelegate: NSObject,
@IBOutlet private var menuSelectAll: NSMenuItem?
@IBOutlet private var menuFindParent: NSMenuItem?
@IBOutlet private var menuFind: NSMenuItem?
+ @IBOutlet private var menuSelectionForFind: NSMenuItem?
+ @IBOutlet private var menuScrollToSelection: NSMenuItem?
@IBOutlet private var menuFindNext: NSMenuItem?
@IBOutlet private var menuFindPrevious: NSMenuItem?
@IBOutlet private var menuHideFindBar: NSMenuItem?
@@ -94,7 +96,7 @@ class AppDelegate: NSObject,
private var derivedConfig: DerivedConfig = DerivedConfig()
/// The ghostty global state. Only one per process.
- let ghostty: Ghostty.App = Ghostty.App()
+ let ghostty: Ghostty.App
/// The global undo manager for app-level state such as window restoration.
lazy var undoManager = ExpiringUndoManager()
@@ -153,6 +155,11 @@ class AppDelegate: NSObject,
@Published private(set) var appIcon: NSImage? = nil
override init() {
+#if DEBUG
+ ghostty = Ghostty.App(configPath: ProcessInfo.processInfo.environment["GHOSTTY_CONFIG_PATH"])
+#else
+ ghostty = Ghostty.App()
+#endif
super.init()
ghostty.delegate = self
@@ -615,6 +622,8 @@ class AppDelegate: NSObject,
syncMenuShortcut(config, action: "paste_from_selection", menuItem: self.menuPasteSelection)
syncMenuShortcut(config, action: "select_all", menuItem: self.menuSelectAll)
syncMenuShortcut(config, action: "start_search", menuItem: self.menuFind)
+ syncMenuShortcut(config, action: "search_selection", menuItem: self.menuSelectionForFind)
+ syncMenuShortcut(config, action: "scroll_to_selection", menuItem: self.menuScrollToSelection)
syncMenuShortcut(config, action: "search:next", menuItem: self.menuFindNext)
syncMenuShortcut(config, action: "search:previous", menuItem: self.menuFindPrevious)
@@ -942,33 +951,8 @@ class AppDelegate: NSObject,
var appIconName: String? = config.macosIcon.rawValue
switch (config.macosIcon) {
- case .official:
- // Discard saved icon name
- appIconName = nil
- break
- case .blueprint:
- appIcon = NSImage(named: "BlueprintImage")!
-
- case .chalkboard:
- appIcon = NSImage(named: "ChalkboardImage")!
-
- case .glass:
- appIcon = NSImage(named: "GlassImage")!
-
- case .holographic:
- appIcon = NSImage(named: "HolographicImage")!
-
- case .microchip:
- appIcon = NSImage(named: "MicrochipImage")!
-
- case .paper:
- appIcon = NSImage(named: "PaperImage")!
-
- case .retro:
- appIcon = NSImage(named: "RetroImage")!
-
- case .xray:
- appIcon = NSImage(named: "XrayImage")!
+ case let icon where icon.assetName != nil:
+ appIcon = NSImage(named: icon.assetName!)!
case .custom:
if let userIcon = NSImage(contentsOfFile: config.macosCustomIcon) {
@@ -978,6 +962,7 @@ class AppDelegate: NSObject,
appIcon = nil // Revert back to official icon if invalid location
appIconName = nil // Discard saved icon name
}
+
case .customStyle:
// Discard saved icon name
// if no valid colours were found
@@ -993,6 +978,10 @@ class AppDelegate: NSObject,
let colorStrings = ([ghostColor] + screenColors).compactMap(\.hexString)
appIconName = (colorStrings + [config.macosIconFrame.rawValue])
.joined(separator: "_")
+
+ default:
+ // Discard saved icon name
+ appIconName = nil
}
// Only change the icon if it has actually changed from the current one,
diff --git a/macos/Sources/App/macOS/MainMenu.xib b/macos/Sources/App/macOS/MainMenu.xib
index a321061dd..e28344098 100644
--- a/macos/Sources/App/macOS/MainMenu.xib
+++ b/macos/Sources/App/macOS/MainMenu.xib
@@ -1,8 +1,8 @@
-
+
-
+
@@ -58,6 +58,7 @@
+
@@ -281,6 +282,19 @@
+
+
+
diff --git a/macos/Sources/Features/About/AboutView.swift b/macos/Sources/Features/About/AboutView.swift
index 6ed3285ed..967eb16b0 100644
--- a/macos/Sources/Features/About/AboutView.swift
+++ b/macos/Sources/Features/About/AboutView.swift
@@ -44,10 +44,7 @@ struct AboutView: View {
var body: some View {
VStack(alignment: .center) {
- ghosttyIconImage()
- .resizable()
- .aspectRatio(contentMode: .fit)
- .frame(height: 128)
+ CyclingIconView()
VStack(alignment: .center, spacing: 32) {
VStack(alignment: .center, spacing: 8) {
diff --git a/macos/Sources/Features/About/CyclingIconView.swift b/macos/Sources/Features/About/CyclingIconView.swift
new file mode 100644
index 000000000..4274278e0
--- /dev/null
+++ b/macos/Sources/Features/About/CyclingIconView.swift
@@ -0,0 +1,62 @@
+import SwiftUI
+import GhosttyKit
+
+/// A view that cycles through Ghostty's official icon variants.
+struct CyclingIconView: View {
+ @State private var currentIcon: Ghostty.MacOSIcon = .official
+ @State private var isHovering: Bool = false
+
+ private let icons: [Ghostty.MacOSIcon] = [
+ .official,
+ .blueprint,
+ .chalkboard,
+ .microchip,
+ .glass,
+ .holographic,
+ .paper,
+ .retro,
+ .xray,
+ ]
+ private let timerPublisher = Timer.publish(every: 3, on: .main, in: .common)
+
+ var body: some View {
+ ZStack {
+ iconView(for: currentIcon)
+ .id(currentIcon)
+ }
+ .animation(.easeInOut(duration: 0.5), value: currentIcon)
+ .frame(height: 128)
+ .onReceive(timerPublisher.autoconnect()) { _ in
+ if !isHovering {
+ advanceToNextIcon()
+ }
+ }
+ .onHover { hovering in
+ isHovering = hovering
+ }
+ .onTapGesture {
+ advanceToNextIcon()
+ }
+ .help("macos-icon = \(currentIcon.rawValue)")
+ .accessibilityLabel("Ghostty Application Icon")
+ .accessibilityHint("Click to cycle through icon variants")
+ }
+
+ @ViewBuilder
+ private func iconView(for icon: Ghostty.MacOSIcon) -> some View {
+ let iconImage: Image = switch icon.assetName {
+ case let assetName?: Image(assetName)
+ case nil: ghosttyIconImage()
+ }
+
+ iconImage
+ .resizable()
+ .aspectRatio(contentMode: .fit)
+ }
+
+ private func advanceToNextIcon() {
+ let currentIndex = icons.firstIndex(of: currentIcon) ?? 0
+ let nextIndex = icons.indexWrapping(after: currentIndex)
+ currentIcon = icons[nextIndex]
+ }
+}
diff --git a/macos/Sources/Features/App Intents/Entities/CommandEntity.swift b/macos/Sources/Features/App Intents/Entities/CommandEntity.swift
index f7abcc6de..3c7745e7c 100644
--- a/macos/Sources/Features/App Intents/Entities/CommandEntity.swift
+++ b/macos/Sources/Features/App Intents/Entities/CommandEntity.swift
@@ -1,4 +1,5 @@
import AppIntents
+import Cocoa
// MARK: AppEntity
@@ -94,23 +95,23 @@ struct CommandQuery: EntityQuery {
@MainActor
func entities(for identifiers: [CommandEntity.ID]) async throws -> [CommandEntity] {
+ guard let appDelegate = NSApp.delegate as? AppDelegate else { return [] }
+ let commands = appDelegate.ghostty.config.commandPaletteEntries
+
// Extract unique terminal IDs to avoid fetching duplicates
let terminalIds = Set(identifiers.map(\.terminalId))
let terminals = try await TerminalEntity.defaultQuery.entities(for: Array(terminalIds))
- // Build a cache of terminals and their available commands
- // This avoids repeated command fetching for the same terminal
- typealias Tuple = (terminal: TerminalEntity, commands: [Ghostty.Command])
- let commandMap: [TerminalEntity.ID: Tuple] =
+ // Build a lookup from terminal ID to terminal entity
+ let terminalMap: [TerminalEntity.ID: TerminalEntity] =
terminals.reduce(into: [:]) { result, terminal in
- guard let commands = try? terminal.surfaceModel?.commands() else { return }
- result[terminal.id] = (terminal: terminal, commands: commands)
+ result[terminal.id] = terminal
}
-
+
// Map each identifier to its corresponding CommandEntity. If a command doesn't
// exist it maps to nil and is removed via compactMap.
return identifiers.compactMap { id in
- guard let (terminal, commands) = commandMap[id.terminalId],
+ guard let terminal = terminalMap[id.terminalId],
let command = commands.first(where: { $0.actionKey == id.actionKey }) else {
return nil
}
@@ -121,8 +122,8 @@ struct CommandQuery: EntityQuery {
@MainActor
func suggestedEntities() async throws -> [CommandEntity] {
- guard let terminal = commandPaletteIntent?.terminal,
- let surface = terminal.surfaceModel else { return [] }
- return try surface.commands().map { CommandEntity($0, for: terminal) }
+ guard let appDelegate = NSApp.delegate as? AppDelegate,
+ let terminal = commandPaletteIntent?.terminal else { return [] }
+ return appDelegate.ghostty.config.commandPaletteEntries.map { CommandEntity($0, for: terminal) }
}
}
diff --git a/macos/Sources/Features/Command Palette/TerminalCommandPalette.swift b/macos/Sources/Features/Command Palette/TerminalCommandPalette.swift
index 902186ad3..e0237f257 100644
--- a/macos/Sources/Features/Command Palette/TerminalCommandPalette.swift
+++ b/macos/Sources/Features/Command Palette/TerminalCommandPalette.swift
@@ -83,11 +83,11 @@ struct TerminalCommandPaletteView: View {
/// Commands for installing or canceling available updates.
private var updateOptions: [CommandOption] {
var options: [CommandOption] = []
-
+
guard let updateViewModel, updateViewModel.state.isInstallable else {
return options
}
-
+
// We override the update available one only because we want to properly
// convey it'll go all the way through.
let title: String
@@ -96,7 +96,7 @@ struct TerminalCommandPaletteView: View {
} else {
title = updateViewModel.text
}
-
+
options.append(CommandOption(
title: title,
description: updateViewModel.description,
@@ -106,32 +106,27 @@ struct TerminalCommandPaletteView: View {
) {
(NSApp.delegate as? AppDelegate)?.updateController.installUpdate()
})
-
+
options.append(CommandOption(
title: "Cancel or Skip Update",
description: "Dismiss the current update process"
) {
updateViewModel.state.cancel()
})
-
+
return options
}
- /// Commands exposed by the terminal surface.
+ /// Custom commands from the command-palette-entry configuration.
private var terminalOptions: [CommandOption] {
- guard let surface = surfaceView.surfaceModel else { return [] }
- do {
- return try surface.commands().map { c in
- CommandOption(
- title: c.title,
- description: c.description,
- symbols: ghosttyConfig.keyboardShortcut(for: c.action)?.keyList,
- ) {
- onAction(c.action)
- }
+ guard let appDelegate = NSApp.delegate as? AppDelegate else { return [] }
+ return appDelegate.ghostty.config.commandPaletteEntries.map { c in
+ CommandOption(
+ title: c.title,
+ description: c.description
+ ) {
+ onAction(c.action)
}
- } catch {
- return []
}
}
diff --git a/macos/Sources/Features/QuickTerminal/QuickTerminalController.swift b/macos/Sources/Features/QuickTerminal/QuickTerminalController.swift
index 8a642034f..07c0c4c19 100644
--- a/macos/Sources/Features/QuickTerminal/QuickTerminalController.swift
+++ b/macos/Sources/Features/QuickTerminal/QuickTerminalController.swift
@@ -137,11 +137,11 @@ class QuickTerminalController: BaseTerminalController {
}
// Setup our content
- window.contentView = NSHostingView(rootView: TerminalView(
+ window.contentView = TerminalViewContainer(
ghostty: self.ghostty,
viewModel: self,
delegate: self
- ))
+ )
// Clear out our frame at this point, the fixup from above is complete.
if let qtWindow = window as? QuickTerminalWindow {
@@ -609,7 +609,7 @@ class QuickTerminalController: BaseTerminalController {
// If we have window transparency then set it transparent. Otherwise set it opaque.
// Also check if the user has overridden transparency to be fully opaque.
- if !isBackgroundOpaque && self.derivedConfig.backgroundOpacity < 1 {
+ if !isBackgroundOpaque && (self.derivedConfig.backgroundOpacity < 1 || derivedConfig.backgroundBlur.isGlassStyle) {
window.isOpaque = false
// This is weird, but we don't use ".clear" because this creates a look that
@@ -617,7 +617,9 @@ class QuickTerminalController: BaseTerminalController {
// Terminal.app more easily.
window.backgroundColor = .white.withAlphaComponent(0.001)
- ghostty_set_window_background_blur(ghostty.app, Unmanaged.passUnretained(window).toOpaque())
+ if !derivedConfig.backgroundBlur.isGlassStyle {
+ ghostty_set_window_background_blur(ghostty.app, Unmanaged.passUnretained(window).toOpaque())
+ }
} else {
window.isOpaque = true
window.backgroundColor = .windowBackgroundColor
@@ -722,6 +724,7 @@ class QuickTerminalController: BaseTerminalController {
let quickTerminalSpaceBehavior: QuickTerminalSpaceBehavior
let quickTerminalSize: QuickTerminalSize
let backgroundOpacity: Double
+ let backgroundBlur: Ghostty.Config.BackgroundBlur
init() {
self.quickTerminalScreen = .main
@@ -730,6 +733,7 @@ class QuickTerminalController: BaseTerminalController {
self.quickTerminalSpaceBehavior = .move
self.quickTerminalSize = QuickTerminalSize()
self.backgroundOpacity = 1.0
+ self.backgroundBlur = .disabled
}
init(_ config: Ghostty.Config) {
@@ -739,6 +743,7 @@ class QuickTerminalController: BaseTerminalController {
self.quickTerminalSpaceBehavior = config.quickTerminalSpaceBehavior
self.quickTerminalSize = config.quickTerminalSize
self.backgroundOpacity = config.backgroundOpacity
+ self.backgroundBlur = config.backgroundBlur
}
}
diff --git a/macos/Sources/Features/Splits/SplitTree.swift b/macos/Sources/Features/Splits/SplitTree.swift
index 23b597591..2fb83e64c 100644
--- a/macos/Sources/Features/Splits/SplitTree.swift
+++ b/macos/Sources/Features/Splits/SplitTree.swift
@@ -121,10 +121,10 @@ extension SplitTree {
/// Insert a new view at the given view point by creating a split in the given direction.
/// This will always reset the zoomed state of the tree.
- func insert(view: ViewType, at: ViewType, direction: NewDirection) throws -> Self {
+ func inserting(view: ViewType, at: ViewType, direction: NewDirection) throws -> Self {
guard let root else { throw SplitError.viewNotFound }
return .init(
- root: try root.insert(view: view, at: at, direction: direction),
+ root: try root.inserting(view: view, at: at, direction: direction),
zoomed: nil)
}
/// Find a node containing a view with the specified ID.
@@ -137,7 +137,7 @@ extension SplitTree {
/// Remove a node from the tree. If the node being removed is part of a split,
/// the sibling node takes the place of the parent split.
- func remove(_ target: Node) -> Self {
+ func removing(_ target: Node) -> Self {
guard let root else { return self }
// If we're removing the root itself, return an empty tree
@@ -155,7 +155,7 @@ extension SplitTree {
}
/// Replace a node in the tree with a new node.
- func replace(node: Node, with newNode: Node) throws -> Self {
+ func replacing(node: Node, with newNode: Node) throws -> Self {
guard let root else { throw SplitError.viewNotFound }
// Get the path to the node we want to replace
@@ -164,7 +164,7 @@ extension SplitTree {
}
// Replace the node
- let newRoot = try root.replaceNode(at: path, with: newNode)
+ let newRoot = try root.replacingNode(at: path, with: newNode)
// Update zoomed if it was the replaced node
let newZoomed = (zoomed == node) ? newNode : zoomed
@@ -232,7 +232,7 @@ extension SplitTree {
/// Equalize all splits in the tree so that each split's ratio is based on the
/// relative weight (number of leaves) of its children.
- func equalize() -> Self {
+ func equalized() -> Self {
guard let root else { return self }
let newRoot = root.equalize()
return .init(root: newRoot, zoomed: zoomed)
@@ -255,7 +255,7 @@ extension SplitTree {
/// - bounds: The bounds used to construct the spatial tree representation
/// - Returns: A new SplitTree with the adjusted split ratios
/// - Throws: SplitError.viewNotFound if the node is not found in the tree or no suitable parent split exists
- func resize(node: Node, by pixels: UInt16, in direction: Spatial.Direction, with bounds: CGRect) throws -> Self {
+ func resizing(node: Node, by pixels: UInt16, in direction: Spatial.Direction, with bounds: CGRect) throws -> Self {
guard let root else { throw SplitError.viewNotFound }
// Find the path to the target node
@@ -327,7 +327,7 @@ extension SplitTree {
)
// Replace the split node with the new one
- let newRoot = try root.replaceNode(at: splitPath, with: .split(newSplit))
+ let newRoot = try root.replacingNode(at: splitPath, with: .split(newSplit))
return .init(root: newRoot, zoomed: nil)
}
@@ -508,7 +508,7 @@ extension SplitTree.Node {
///
/// - Note: If the existing view (`at`) is not found in the tree, this method does nothing. We should
/// maybe throw instead but at the moment we just do nothing.
- func insert(view: ViewType, at: ViewType, direction: NewDirection) throws -> Self {
+ func inserting(view: ViewType, at: ViewType, direction: NewDirection) throws -> Self {
// Get the path to our insertion point. If it doesn't exist we do
// nothing.
guard let path = path(to: .leaf(view: at)) else {
@@ -544,11 +544,11 @@ extension SplitTree.Node {
))
// Replace the node at the path with the new split
- return try replaceNode(at: path, with: newSplit)
+ return try replacingNode(at: path, with: newSplit)
}
/// Helper function to replace a node at the given path from the root
- func replaceNode(at path: Path, with newNode: Self) throws -> Self {
+ func replacingNode(at path: Path, with newNode: Self) throws -> Self {
// If path is empty, replace the root
if path.isEmpty {
return newNode
@@ -635,7 +635,7 @@ extension SplitTree.Node {
/// Resize a split node to the specified ratio.
/// For leaf nodes, this returns the node unchanged.
/// For split nodes, this creates a new split with the updated ratio.
- func resize(to ratio: Double) -> Self {
+ func resizing(to ratio: Double) -> Self {
switch self {
case .leaf:
// Leaf nodes don't have a ratio to resize
diff --git a/macos/Sources/Features/Splits/TerminalSplitTreeView.swift b/macos/Sources/Features/Splits/TerminalSplitTreeView.swift
index 103413c70..2a42dc599 100644
--- a/macos/Sources/Features/Splits/TerminalSplitTreeView.swift
+++ b/macos/Sources/Features/Splits/TerminalSplitTreeView.swift
@@ -1,15 +1,40 @@
import SwiftUI
+/// A single operation within the split tree.
+///
+/// Rather than binding the split tree (which is immutable), any mutable operations are
+/// exposed via this enum to the embedder to handle.
+enum TerminalSplitOperation {
+ case resize(Resize)
+ case drop(Drop)
+
+ struct Resize {
+ let node: SplitTree.Node
+ let ratio: Double
+ }
+
+ struct Drop {
+ /// The surface being dragged.
+ let payload: Ghostty.SurfaceView
+
+ /// The surface it was dragged onto
+ let destination: Ghostty.SurfaceView
+
+ /// The zone it was dropped to determine how to split the destination.
+ let zone: TerminalSplitDropZone
+ }
+}
+
struct TerminalSplitTreeView: View {
let tree: SplitTree
- let onResize: (SplitTree.Node, Double) -> Void
+ let action: (TerminalSplitOperation) -> Void
var body: some View {
if let node = tree.zoomed ?? tree.root {
TerminalSplitSubtreeView(
node: node,
isRoot: node == tree.root,
- onResize: onResize)
+ action: action)
// This is necessary because we can't rely on SwiftUI's implicit
// structural identity to detect changes to this view. Due to
// the tree structure of splits it could result in bad behaviors.
@@ -19,21 +44,17 @@ struct TerminalSplitTreeView: View {
}
}
-struct TerminalSplitSubtreeView: View {
+fileprivate struct TerminalSplitSubtreeView: View {
@EnvironmentObject var ghostty: Ghostty.App
let node: SplitTree.Node
var isRoot: Bool = false
- let onResize: (SplitTree.Node, Double) -> Void
+ let action: (TerminalSplitOperation) -> Void
var body: some View {
switch (node) {
case .leaf(let leafView):
- Ghostty.InspectableSurface(
- surfaceView: leafView,
- isSplit: !isRoot)
- .accessibilityElement(children: .contain)
- .accessibilityLabel("Terminal pane")
+ TerminalSplitLeaf(surfaceView: leafView, isSplit: !isRoot, action: action)
case .split(let split):
let splitViewDirection: SplitViewDirection = switch (split.direction) {
@@ -46,15 +67,15 @@ struct TerminalSplitSubtreeView: View {
.init(get: {
CGFloat(split.ratio)
}, set: {
- onResize(node, $0)
+ action(.resize(.init(node: node, ratio: $0)))
}),
dividerColor: ghostty.config.splitDividerColor,
resizeIncrements: .init(width: 1, height: 1),
left: {
- TerminalSplitSubtreeView(node: split.left, onResize: onResize)
+ TerminalSplitSubtreeView(node: split.left, action: action)
},
right: {
- TerminalSplitSubtreeView(node: split.right, onResize: onResize)
+ TerminalSplitSubtreeView(node: split.right, action: action)
},
onEqualize: {
guard let surface = node.leftmostLeaf().surface else { return }
@@ -64,3 +85,173 @@ struct TerminalSplitSubtreeView: View {
}
}
}
+
+fileprivate struct TerminalSplitLeaf: View {
+ let surfaceView: Ghostty.SurfaceView
+ let isSplit: Bool
+ let action: (TerminalSplitOperation) -> Void
+
+ @State private var dropState: DropState = .idle
+ @State private var isSelfDragging: Bool = false
+
+ var body: some View {
+ GeometryReader { geometry in
+ Ghostty.InspectableSurface(
+ surfaceView: surfaceView,
+ isSplit: isSplit)
+ .background {
+ // If we're dragging ourself, we hide the entire drop zone. This makes
+ // it so that a released drop animates back to its source properly
+ // so it is a proper invalid drop zone.
+ if !isSelfDragging {
+ Color.clear
+ .onDrop(of: [.ghosttySurfaceId], delegate: SplitDropDelegate(
+ dropState: $dropState,
+ viewSize: geometry.size,
+ destinationSurface: surfaceView,
+ action: action
+ ))
+ }
+ }
+ .overlay {
+ if !isSelfDragging, case .dropping(let zone) = dropState {
+ zone.overlay(in: geometry)
+ .allowsHitTesting(false)
+ }
+ }
+ .onPreferenceChange(Ghostty.DraggingSurfaceKey.self) { value in
+ isSelfDragging = value == surfaceView.id
+ if isSelfDragging {
+ dropState = .idle
+ }
+ }
+ .accessibilityElement(children: .contain)
+ .accessibilityLabel("Terminal pane")
+ }
+ }
+
+ private enum DropState: Equatable {
+ case idle
+ case dropping(TerminalSplitDropZone)
+ }
+
+ private struct SplitDropDelegate: DropDelegate {
+ @Binding var dropState: DropState
+ let viewSize: CGSize
+ let destinationSurface: Ghostty.SurfaceView
+ let action: (TerminalSplitOperation) -> Void
+
+ func validateDrop(info: DropInfo) -> Bool {
+ info.hasItemsConforming(to: [.ghosttySurfaceId])
+ }
+
+ func dropEntered(info: DropInfo) {
+ dropState = .dropping(.calculate(at: info.location, in: viewSize))
+ }
+
+ func dropUpdated(info: DropInfo) -> DropProposal? {
+ // For some reason dropUpdated is sent after performDrop is called
+ // and we don't want to reset our drop zone to show it so we have
+ // to guard on the state here.
+ guard case .dropping = dropState else { return DropProposal(operation: .forbidden) }
+ dropState = .dropping(.calculate(at: info.location, in: viewSize))
+ return DropProposal(operation: .move)
+ }
+
+ func dropExited(info: DropInfo) {
+ dropState = .idle
+ }
+
+ func performDrop(info: DropInfo) -> Bool {
+ let zone = TerminalSplitDropZone.calculate(at: info.location, in: viewSize)
+ dropState = .idle
+
+ // Load the dropped surface asynchronously using Transferable
+ let providers = info.itemProviders(for: [.ghosttySurfaceId])
+ guard let provider = providers.first else { return false }
+
+ // Capture action before the async closure
+ _ = provider.loadTransferable(type: Ghostty.SurfaceView.self) { [weak destinationSurface] result in
+ switch result {
+ case .success(let sourceSurface):
+ DispatchQueue.main.async {
+ // Don't allow dropping on self
+ guard let destinationSurface else { return }
+ guard sourceSurface !== destinationSurface else { return }
+ action(.drop(.init(payload: sourceSurface, destination: destinationSurface, zone: zone)))
+ }
+
+ case .failure:
+ break
+ }
+ }
+
+ return true
+ }
+ }
+}
+
+enum TerminalSplitDropZone: String, Equatable {
+ case top
+ case bottom
+ case left
+ case right
+
+ /// Determines which drop zone the cursor is in based on proximity to edges.
+ ///
+ /// Divides the view into four triangular regions by drawing diagonals from
+ /// corner to corner. The drop zone is determined by which edge the cursor
+ /// is closest to, creating natural triangular hit regions for each side.
+ static func calculate(at point: CGPoint, in size: CGSize) -> TerminalSplitDropZone {
+ let relX = point.x / size.width
+ let relY = point.y / size.height
+
+ let distToLeft = relX
+ let distToRight = 1 - relX
+ let distToTop = relY
+ let distToBottom = 1 - relY
+
+ let minDist = min(distToLeft, distToRight, distToTop, distToBottom)
+
+ if minDist == distToLeft { return .left }
+ if minDist == distToRight { return .right }
+ if minDist == distToTop { return .top }
+ return .bottom
+ }
+
+ @ViewBuilder
+ func overlay(in geometry: GeometryProxy) -> some View {
+ let overlayColor = Color.accentColor.opacity(0.3)
+
+ switch self {
+ case .top:
+ VStack(spacing: 0) {
+ Rectangle()
+ .fill(overlayColor)
+ .frame(height: geometry.size.height / 2)
+ Spacer()
+ }
+ case .bottom:
+ VStack(spacing: 0) {
+ Spacer()
+ Rectangle()
+ .fill(overlayColor)
+ .frame(height: geometry.size.height / 2)
+ }
+ case .left:
+ HStack(spacing: 0) {
+ Rectangle()
+ .fill(overlayColor)
+ .frame(width: geometry.size.width / 2)
+ Spacer()
+ }
+ case .right:
+ HStack(spacing: 0) {
+ Spacer()
+ Rectangle()
+ .fill(overlayColor)
+ .frame(width: geometry.size.width / 2)
+ }
+ }
+ }
+}
diff --git a/macos/Sources/Features/Terminal/BaseTerminalController.swift b/macos/Sources/Features/Terminal/BaseTerminalController.swift
index d79c89d2d..b739e9ed1 100644
--- a/macos/Sources/Features/Terminal/BaseTerminalController.swift
+++ b/macos/Sources/Features/Terminal/BaseTerminalController.swift
@@ -200,6 +200,11 @@ class BaseTerminalController: NSWindowController,
selector: #selector(ghosttyDidPresentTerminal(_:)),
name: Ghostty.Notification.ghosttyPresentTerminal,
object: nil)
+ center.addObserver(
+ self,
+ selector: #selector(ghosttySurfaceDragEndedNoTarget(_:)),
+ name: .ghosttySurfaceDragEndedNoTarget,
+ object: nil)
// Listen for local events that we need to know of outside of
// single surface handlers.
@@ -235,7 +240,7 @@ class BaseTerminalController: NSWindowController,
// Do the split
let newTree: SplitTree
do {
- newTree = try surfaceTree.insert(
+ newTree = try surfaceTree.inserting(
view: newView,
at: oldView,
direction: direction)
@@ -445,14 +450,14 @@ class BaseTerminalController: NSWindowController,
}
replaceSurfaceTree(
- surfaceTree.remove(node),
+ surfaceTree.removing(node),
moveFocusTo: nextFocus,
moveFocusFrom: focusedSurface,
undoAction: "Close Terminal"
)
}
- private func replaceSurfaceTree(
+ func replaceSurfaceTree(
_ newTree: SplitTree,
moveFocusTo newView: Ghostty.SurfaceView? = nil,
moveFocusFrom oldView: Ghostty.SurfaceView? = nil,
@@ -466,33 +471,33 @@ class BaseTerminalController: NSWindowController,
Ghostty.moveFocus(to: newView, from: oldView)
}
}
-
+
// Setup our undo
- if let undoManager {
- if let undoAction {
- undoManager.setActionName(undoAction)
+ guard let undoManager else { return }
+ if let undoAction {
+ undoManager.setActionName(undoAction)
+ }
+
+ undoManager.registerUndo(
+ withTarget: self,
+ expiresAfter: undoExpiration
+ ) { target in
+ target.surfaceTree = oldTree
+ if let oldView {
+ DispatchQueue.main.async {
+ Ghostty.moveFocus(to: oldView, from: target.focusedSurface)
+ }
}
+
undoManager.registerUndo(
- withTarget: self,
- expiresAfter: undoExpiration
+ withTarget: target,
+ expiresAfter: target.undoExpiration
) { target in
- target.surfaceTree = oldTree
- if let oldView {
- DispatchQueue.main.async {
- Ghostty.moveFocus(to: oldView, from: target.focusedSurface)
- }
- }
-
- undoManager.registerUndo(
- withTarget: target,
- expiresAfter: target.undoExpiration
- ) { target in
- target.replaceSurfaceTree(
- newTree,
- moveFocusTo: newView,
- moveFocusFrom: target.focusedSurface,
- undoAction: undoAction)
- }
+ target.replaceSurfaceTree(
+ newTree,
+ moveFocusTo: newView,
+ moveFocusFrom: target.focusedSurface,
+ undoAction: undoAction)
}
}
}
@@ -609,7 +614,7 @@ class BaseTerminalController: NSWindowController,
guard surfaceTree.contains(target) else { return }
// Equalize the splits
- surfaceTree = surfaceTree.equalize()
+ surfaceTree = surfaceTree.equalized()
}
@objc private func ghosttyDidFocusSplit(_ notification: Notification) {
@@ -699,7 +704,7 @@ class BaseTerminalController: NSWindowController,
// Perform the resize using the new SplitTree resize method
do {
- surfaceTree = try surfaceTree.resize(node: targetNode, by: amount, in: spatialDirection, with: bounds)
+ surfaceTree = try surfaceTree.resizing(node: targetNode, by: amount, in: spatialDirection, with: bounds)
} catch {
Ghostty.logger.warning("failed to resize split: \(error)")
}
@@ -721,6 +726,42 @@ class BaseTerminalController: NSWindowController,
target.highlight()
}
+ @objc private func ghosttySurfaceDragEndedNoTarget(_ notification: Notification) {
+ guard let target = notification.object as? Ghostty.SurfaceView else { return }
+ guard let targetNode = surfaceTree.root?.node(view: target) else { return }
+
+ // If our tree isn't split, then we never create a new window, because
+ // it is already a single split.
+ guard surfaceTree.isSplit else { return }
+
+ // If we are removing our focused surface then we move it. We need to
+ // keep track of our old one so undo sends focus back to the right place.
+ let oldFocusedSurface = focusedSurface
+ if focusedSurface == target {
+ focusedSurface = findNextFocusTargetAfterClosing(node: targetNode)
+ }
+
+ // Remove the surface from our tree
+ let removedTree = surfaceTree.removing(targetNode)
+
+ // Create a new tree with the dragged surface and open a new window
+ let newTree = SplitTree(view: target)
+
+ // Treat our undo below as a full group.
+ undoManager?.beginUndoGrouping()
+ undoManager?.setActionName("Move Split")
+ defer {
+ undoManager?.endUndoGrouping()
+ }
+
+ replaceSurfaceTree(removedTree, moveFocusFrom: oldFocusedSurface)
+ _ = TerminalController.newWindow(
+ ghostty,
+ tree: newTree,
+ position: notification.userInfo?[Notification.Name.ghosttySurfaceDragEndedNoTargetPointKey] as? NSPoint,
+ confirmUndo: false)
+ }
+
// MARK: Local Events
private func localEventHandler(_ event: NSEvent) -> NSEvent? {
@@ -793,7 +834,15 @@ class BaseTerminalController: NSWindowController,
private func applyTitleToWindow() {
guard let window else { return }
- window.title = titleOverride ?? lastComputedTitle
+
+ if let titleOverride {
+ window.title = computeTitle(
+ title: titleOverride,
+ bell: focusedSurface?.bell ?? false)
+ return
+ }
+
+ window.title = lastComputedTitle
}
func pwdDidChange(to: URL?) {
@@ -817,14 +866,101 @@ class BaseTerminalController: NSWindowController,
self.window?.contentResizeIncrements = to
}
- func splitDidResize(node: SplitTree.Node, to newRatio: Double) {
- let resizedNode = node.resize(to: newRatio)
+ func performSplitAction(_ action: TerminalSplitOperation) {
+ switch action {
+ case .resize(let resize):
+ splitDidResize(node: resize.node, to: resize.ratio)
+ case .drop(let drop):
+ splitDidDrop(source: drop.payload, destination: drop.destination, zone: drop.zone)
+ }
+ }
+
+ private func splitDidResize(node: SplitTree.Node, to newRatio: Double) {
+ let resizedNode = node.resizing(to: newRatio)
do {
- surfaceTree = try surfaceTree.replace(node: node, with: resizedNode)
+ surfaceTree = try surfaceTree.replacing(node: node, with: resizedNode)
} catch {
Ghostty.logger.warning("failed to replace node during split resize: \(error)")
+ }
+ }
+
+ private func splitDidDrop(
+ source: Ghostty.SurfaceView,
+ destination: Ghostty.SurfaceView,
+ zone: TerminalSplitDropZone
+ ) {
+ // Map drop zone to split direction
+ let direction: SplitTree.NewDirection = switch zone {
+ case .top: .up
+ case .bottom: .down
+ case .left: .left
+ case .right: .right
+ }
+
+ // Check if source is in our tree
+ if let sourceNode = surfaceTree.root?.node(view: source) {
+ // Source is in our tree - same window move
+ let treeWithoutSource = surfaceTree.removing(sourceNode)
+ let newTree: SplitTree
+ do {
+ newTree = try treeWithoutSource.inserting(view: source, at: destination, direction: direction)
+ } catch {
+ Ghostty.logger.warning("failed to insert surface during drop: \(error)")
+ return
+ }
+
+ replaceSurfaceTree(
+ newTree,
+ moveFocusTo: source,
+ moveFocusFrom: focusedSurface,
+ undoAction: "Move Split")
return
}
+
+ // Source is not in our tree - search other windows
+ var sourceController: BaseTerminalController?
+ var sourceNode: SplitTree.Node?
+ for window in NSApp.windows {
+ guard let controller = window.windowController as? BaseTerminalController else { continue }
+ guard controller !== self else { continue }
+ if let node = controller.surfaceTree.root?.node(view: source) {
+ sourceController = controller
+ sourceNode = node
+ break
+ }
+ }
+
+ guard let sourceController, let sourceNode else {
+ Ghostty.logger.warning("source surface not found in any window during drop")
+ return
+ }
+
+ // Remove from source controller's tree and add it to our tree.
+ // We do this first because if there is an error then we can
+ // abort.
+ let newTree: SplitTree
+ do {
+ newTree = try surfaceTree.inserting(view: source, at: destination, direction: direction)
+ } catch {
+ Ghostty.logger.warning("failed to insert surface during cross-window drop: \(error)")
+ return
+ }
+
+ // Treat our undo below as a full group.
+ undoManager?.beginUndoGrouping()
+ undoManager?.setActionName("Move Split")
+ defer {
+ undoManager?.endUndoGrouping()
+ }
+
+ // Remove the node from the source.
+ sourceController.removeSurfaceNode(sourceNode)
+
+ // Add in the surface to our tree
+ replaceSurfaceTree(
+ newTree,
+ moveFocusTo: source,
+ moveFocusFrom: focusedSurface)
}
func performAction(_ action: String, on surfaceView: Ghostty.SurfaceView) {
@@ -1076,6 +1212,15 @@ class BaseTerminalController: NSWindowController,
}
func windowDidBecomeKey(_ notification: Notification) {
+ // If when we become key our first responder is the window itself, then we
+ // want to move focus to our focused terminal surface. This works around
+ // various weirdness with moving surfaces around.
+ if let window, window.firstResponder == window, let focusedSurface {
+ DispatchQueue.main.async {
+ Ghostty.moveFocus(to: focusedSurface)
+ }
+ }
+
// Becoming/losing key means we have to notify our surface(s) that we have focus
// so things like cursors blink, pty events are sent, etc.
self.syncFocusToSurfaceTree()
@@ -1227,7 +1372,15 @@ class BaseTerminalController: NSWindowController,
@IBAction func find(_ sender: Any) {
focusedSurface?.find(sender)
}
-
+
+ @IBAction func selectionForFind(_ sender: Any) {
+ focusedSurface?.selectionForFind(sender)
+ }
+
+ @IBAction func scrollToSelection(_ sender: Any) {
+ focusedSurface?.scrollToSelection(sender)
+ }
+
@IBAction func findNext(_ sender: Any) {
focusedSurface?.findNext(sender)
}
diff --git a/macos/Sources/Features/Terminal/TerminalController.swift b/macos/Sources/Features/Terminal/TerminalController.swift
index 8a0c5f46d..c7f9fe086 100644
--- a/macos/Sources/Features/Terminal/TerminalController.swift
+++ b/macos/Sources/Features/Terminal/TerminalController.swift
@@ -8,16 +8,16 @@ import GhosttyKit
class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Controller {
override var windowNibName: NSNib.Name? {
let defaultValue = "Terminal"
-
+
guard let appDelegate = NSApp.delegate as? AppDelegate else { return defaultValue }
let config = appDelegate.ghostty.config
-
+
// If we have no window decorations, there's no reason to do anything but
// the default titlebar (because there will be no titlebar).
if !config.windowDecorations {
return defaultValue
}
-
+
let nib = switch config.macosTitlebarStyle {
case "native": "Terminal"
case "hidden": "TerminalHiddenTitlebar"
@@ -34,33 +34,33 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr
#endif
default: defaultValue
}
-
+
return nib
}
-
+
/// This is set to true when we care about frame changes. This is a small optimization since
/// this controller registers a listener for ALL frame change notifications and this lets us bail
/// early if we don't care.
private var tabListenForFrame: Bool = false
-
+
/// This is the hash value of the last tabGroup.windows array. We use this to detect order
/// changes in the list.
private var tabWindowsHash: Int = 0
-
+
/// This is set to false by init if the window managed by this controller should not be restorable.
/// For example, terminals executing custom scripts are not restorable.
private var restorable: Bool = true
-
+
/// The configuration derived from the Ghostty config so we don't need to rely on references.
private(set) var derivedConfig: DerivedConfig
-
-
+
+
/// The notification cancellable for focused surface property changes.
private var surfaceAppearanceCancellables: Set = []
-
+
/// This will be set to the initial frame of the window from the xib on load.
private var initialFrame: NSRect? = nil
-
+
init(_ ghostty: Ghostty.App,
withBaseConfig base: Ghostty.SurfaceConfiguration? = nil,
withSurfaceTree tree: SplitTree? = nil,
@@ -72,12 +72,12 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr
// as the script. We may want to revisit this behavior when we have scrollback
// restoration.
self.restorable = (base?.command ?? "") == ""
-
+
// Setup our initial derived config based on the current app config
self.derivedConfig = DerivedConfig(ghostty.config)
-
+
super.init(ghostty, baseConfig: base, surfaceTree: tree)
-
+
// Setup our notifications for behaviors
let center = NotificationCenter.default
center.addObserver(
@@ -134,36 +134,56 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr
object: nil
)
}
-
+
required init?(coder: NSCoder) {
fatalError("init(coder:) is not supported for this view")
}
-
+
deinit {
// Remove all of our notificationcenter subscriptions
let center = NotificationCenter.default
center.removeObserver(self)
}
-
+
// MARK: Base Controller Overrides
-
+
override func surfaceTreeDidChange(from: SplitTree, to: SplitTree) {
super.surfaceTreeDidChange(from: from, to: to)
-
+
// Whenever our surface tree changes in any way (new split, close split, etc.)
// we want to invalidate our state.
invalidateRestorableState()
-
+
// Update our zoom state
if let window = window as? TerminalWindow {
window.surfaceIsZoomed = to.zoomed != nil
}
-
+
// If our surface tree is now nil then we close our window.
if (to.isEmpty) {
self.window?.close()
}
}
+
+ override func replaceSurfaceTree(
+ _ newTree: SplitTree,
+ moveFocusTo newView: Ghostty.SurfaceView? = nil,
+ moveFocusFrom oldView: Ghostty.SurfaceView? = nil,
+ undoAction: String? = nil
+ ) {
+ // We have a special case if our tree is empty to close our tab immediately.
+ // This makes it so that undo is handled properly.
+ if newTree.isEmpty {
+ closeTabImmediately()
+ return
+ }
+
+ super.replaceSurfaceTree(
+ newTree,
+ moveFocusTo: newView,
+ moveFocusFrom: oldView,
+ undoAction: undoAction)
+ }
// MARK: Terminal Creation
@@ -275,6 +295,72 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr
return c
}
+ /// Create a new window with an existing split tree.
+ /// The window will be sized to match the tree's current view bounds if available.
+ /// - Parameters:
+ /// - ghostty: The Ghostty app instance.
+ /// - tree: The split tree to use for the new window.
+ /// - position: Optional screen position (top-left corner) for the new window.
+ /// If nil, the window will cascade from the last cascade point.
+ static func newWindow(
+ _ ghostty: Ghostty.App,
+ tree: SplitTree,
+ position: NSPoint? = nil,
+ confirmUndo: Bool = true,
+ ) -> TerminalController {
+ let c = TerminalController.init(ghostty, withSurfaceTree: tree)
+
+ // Calculate the target frame based on the tree's view bounds
+ let treeSize: CGSize? = tree.root?.viewBounds()
+
+ DispatchQueue.main.async {
+ if let window = c.window {
+ // If we have a tree size, resize the window's content to match
+ if let treeSize, treeSize.width > 0, treeSize.height > 0 {
+ window.setContentSize(treeSize)
+ window.constrainToScreen()
+ }
+
+ if !window.styleMask.contains(.fullScreen) {
+ if let position {
+ window.setFrameTopLeftPoint(position)
+ window.constrainToScreen()
+ } else {
+ Self.lastCascadePoint = window.cascadeTopLeft(from: Self.lastCascadePoint)
+ }
+ }
+ }
+
+ c.showWindow(self)
+ }
+
+ // Setup our undo
+ if let undoManager = c.undoManager {
+ undoManager.setActionName("New Window")
+ undoManager.registerUndo(
+ withTarget: c,
+ expiresAfter: c.undoExpiration
+ ) { target in
+ undoManager.disableUndoRegistration {
+ if confirmUndo {
+ target.closeWindow(nil)
+ } else {
+ target.closeWindowImmediately()
+ }
+ }
+
+ undoManager.registerUndo(
+ withTarget: ghostty,
+ expiresAfter: target.undoExpiration
+ ) { ghostty in
+ _ = TerminalController.newWindow(ghostty, tree: tree)
+ }
+ }
+ }
+
+ return c
+ }
+
static func newTab(
_ ghostty: Ghostty.App,
from parent: NSWindow? = nil,
@@ -397,7 +483,7 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr
return controller
}
-
+
//MARK: - Methods
@objc private func ghosttyConfigDidChange(_ notification: Notification) {
@@ -548,7 +634,7 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr
closeWindow(nil)
}
- private func closeTabImmediately(registerRedo: Bool = true) {
+ func closeTabImmediately(registerRedo: Bool = true) {
guard let window = window else { return }
guard let tabGroup = window.tabGroup,
tabGroup.windows.count > 1 else {
@@ -671,7 +757,7 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr
/// Closes the current window (including any other tabs) immediately and without
/// confirmation. This will setup proper undo state so the action can be undone.
- private func closeWindowImmediately() {
+ func closeWindowImmediately() {
guard let window = window else { return }
registerUndoForCloseWindow()
@@ -879,13 +965,20 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr
// Make it the key window
window.makeKeyAndOrderFront(nil)
}
-
+
// Restore focus to the previously focused surface
if let focusedUUID = undoState.focusedSurface,
let focusTarget = surfaceTree.first(where: { $0.id == focusedUUID }) {
DispatchQueue.main.async {
Ghostty.moveFocus(to: focusTarget, from: nil)
}
+ } else if let focusedSurface = surfaceTree.first {
+ // No prior focused surface or we can't find it, let's focus
+ // the first.
+ self.focusedSurface = focusedSurface
+ DispatchQueue.main.async {
+ Ghostty.moveFocus(to: focusedSurface, from: nil)
+ }
}
}
}
@@ -936,11 +1029,11 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr
}
// Initialize our content view to the SwiftUI root
- window.contentView = NSHostingView(rootView: TerminalView(
+ window.contentView = TerminalViewContainer(
ghostty: self.ghostty,
viewModel: self,
delegate: self,
- ))
+ )
// If we have a default size, we want to apply it.
if let defaultSize {
@@ -952,9 +1045,13 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr
case .contentIntrinsicSize:
// Content intrinsic size requires a short delay so that AppKit
// can layout our SwiftUI views.
- DispatchQueue.main.asyncAfter(deadline: .now() + .microseconds(10_000)) { [weak window] in
- guard let window else { return }
+ DispatchQueue.main.asyncAfter(deadline: .now() + .microseconds(10_000)) { [weak self, weak window] in
+ guard let self, let window else { return }
defaultSize.apply(to: window)
+ if let screen = window.screen ?? NSScreen.main {
+ let frame = self.adjustForWindowPosition(frame: window.frame, on: screen)
+ window.setFrameOrigin(frame.origin)
+ }
}
}
}
diff --git a/macos/Sources/Features/Terminal/TerminalView.swift b/macos/Sources/Features/Terminal/TerminalView.swift
index fd53a617b..e117e0647 100644
--- a/macos/Sources/Features/Terminal/TerminalView.swift
+++ b/macos/Sources/Features/Terminal/TerminalView.swift
@@ -1,5 +1,6 @@
import SwiftUI
import GhosttyKit
+import os
/// This delegate is notified of actions and property changes regarding the terminal view. This
/// delegate is optional and can be used by a TerminalView caller to react to changes such as
@@ -16,9 +17,9 @@ protocol TerminalViewDelegate: AnyObject {
/// Perform an action. At the time of writing this is only triggered by the command palette.
func performAction(_ action: String, on: Ghostty.SurfaceView)
-
- /// A split is resizing to a given value.
- func splitDidResize(node: SplitTree.Node, to newRatio: Double)
+
+ /// A split tree operation
+ func performSplitAction(_ action: TerminalSplitOperation)
}
/// The view model is a required implementation for TerminalView callers. This contains
@@ -81,7 +82,7 @@ struct TerminalView: View {
TerminalSplitTreeView(
tree: viewModel.surfaceTree,
- onResize: { delegate?.splitDidResize(node: $0, to: $1) })
+ action: { delegate?.performSplitAction($0) })
.environmentObject(ghostty)
.focused($focused)
.onAppear { self.focused = true }
diff --git a/macos/Sources/Features/Terminal/TerminalViewContainer.swift b/macos/Sources/Features/Terminal/TerminalViewContainer.swift
new file mode 100644
index 000000000..c65dca1d2
--- /dev/null
+++ b/macos/Sources/Features/Terminal/TerminalViewContainer.swift
@@ -0,0 +1,160 @@
+import AppKit
+import SwiftUI
+
+/// Use this container to achieve a glass effect at the window level.
+/// Modifying `NSThemeFrame` can sometimes be unpredictable.
+class TerminalViewContainer: NSView {
+ private let terminalView: NSView
+
+ /// Glass effect view for liquid glass background when transparency is enabled
+ private var glassEffectView: NSView?
+ private var glassTopConstraint: NSLayoutConstraint?
+ private var derivedConfig: DerivedConfig
+
+ init(ghostty: Ghostty.App, viewModel: ViewModel, delegate: (any TerminalViewDelegate)? = nil) {
+ self.derivedConfig = DerivedConfig(config: ghostty.config)
+ self.terminalView = NSHostingView(rootView: TerminalView(
+ ghostty: ghostty,
+ viewModel: viewModel,
+ delegate: delegate
+ ))
+ super.init(frame: .zero)
+ setup()
+ }
+
+ @available(*, unavailable)
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ /// To make ``TerminalController/DefaultSize/contentIntrinsicSize``
+ /// work in ``TerminalController/windowDidLoad()``,
+ /// we override this to provide the correct size.
+ override var intrinsicContentSize: NSSize {
+ terminalView.intrinsicContentSize
+ }
+
+ private func setup() {
+ addSubview(terminalView)
+ terminalView.translatesAutoresizingMaskIntoConstraints = false
+ NSLayoutConstraint.activate([
+ terminalView.topAnchor.constraint(equalTo: topAnchor),
+ terminalView.leadingAnchor.constraint(equalTo: leadingAnchor),
+ terminalView.bottomAnchor.constraint(equalTo: bottomAnchor),
+ terminalView.trailingAnchor.constraint(equalTo: trailingAnchor),
+ ])
+
+ NotificationCenter.default.addObserver(
+ self,
+ selector: #selector(ghosttyConfigDidChange(_:)),
+ name: .ghosttyConfigDidChange,
+ object: nil
+ )
+ }
+
+ override func viewDidMoveToWindow() {
+ super.viewDidMoveToWindow()
+ updateGlassEffectIfNeeded()
+ updateGlassEffectTopInsetIfNeeded()
+ }
+
+ override func layout() {
+ super.layout()
+ updateGlassEffectTopInsetIfNeeded()
+ }
+
+ @objc private func ghosttyConfigDidChange(_ notification: Notification) {
+ guard let config = notification.userInfo?[
+ Notification.Name.GhosttyConfigChangeKey
+ ] as? Ghostty.Config else { return }
+ let newValue = DerivedConfig(config: config)
+ guard newValue != derivedConfig else { return }
+ derivedConfig = newValue
+ DispatchQueue.main.async(execute: updateGlassEffectIfNeeded)
+ }
+}
+
+// MARK: Glass
+
+private extension TerminalViewContainer {
+#if compiler(>=6.2)
+ @available(macOS 26.0, *)
+ func addGlassEffectViewIfNeeded() -> NSGlassEffectView? {
+ if let existed = glassEffectView as? NSGlassEffectView {
+ updateGlassEffectTopInsetIfNeeded()
+ return existed
+ }
+ guard let themeFrameView = window?.contentView?.superview else {
+ return nil
+ }
+ let effectView = NSGlassEffectView()
+ addSubview(effectView, positioned: .below, relativeTo: terminalView)
+ effectView.translatesAutoresizingMaskIntoConstraints = false
+ glassTopConstraint = effectView.topAnchor.constraint(
+ equalTo: topAnchor,
+ constant: -themeFrameView.safeAreaInsets.top
+ )
+ if let glassTopConstraint {
+ NSLayoutConstraint.activate([
+ glassTopConstraint,
+ effectView.leadingAnchor.constraint(equalTo: leadingAnchor),
+ effectView.bottomAnchor.constraint(equalTo: bottomAnchor),
+ effectView.trailingAnchor.constraint(equalTo: trailingAnchor),
+ ])
+ }
+ glassEffectView = effectView
+ return effectView
+ }
+#endif // compiler(>=6.2)
+
+ func updateGlassEffectIfNeeded() {
+#if compiler(>=6.2)
+ guard #available(macOS 26.0, *), derivedConfig.backgroundBlur.isGlassStyle else {
+ glassEffectView?.removeFromSuperview()
+ glassEffectView = nil
+ glassTopConstraint = nil
+ return
+ }
+ guard let effectView = addGlassEffectViewIfNeeded() else {
+ return
+ }
+ switch derivedConfig.backgroundBlur {
+ case .macosGlassRegular:
+ effectView.style = NSGlassEffectView.Style.regular
+ case .macosGlassClear:
+ effectView.style = NSGlassEffectView.Style.clear
+ default:
+ break
+ }
+ let backgroundColor = (window as? TerminalWindow)?.preferredBackgroundColor ?? NSColor(derivedConfig.backgroundColor)
+ effectView.tintColor = backgroundColor
+ .withAlphaComponent(derivedConfig.backgroundOpacity)
+ if let window, window.responds(to: Selector(("_cornerRadius"))), let cornerRadius = window.value(forKey: "_cornerRadius") as? CGFloat {
+ effectView.cornerRadius = cornerRadius
+ }
+#endif // compiler(>=6.2)
+ }
+
+ func updateGlassEffectTopInsetIfNeeded() {
+#if compiler(>=6.2)
+ guard #available(macOS 26.0, *), derivedConfig.backgroundBlur.isGlassStyle else {
+ return
+ }
+ guard glassEffectView != nil else { return }
+ guard let themeFrameView = window?.contentView?.superview else { return }
+ glassTopConstraint?.constant = -themeFrameView.safeAreaInsets.top
+#endif // compiler(>=6.2)
+ }
+
+ struct DerivedConfig: Equatable {
+ var backgroundOpacity: Double = 0
+ var backgroundBlur: Ghostty.Config.BackgroundBlur
+ var backgroundColor: Color = .clear
+
+ init(config: Ghostty.Config) {
+ self.backgroundBlur = config.backgroundBlur
+ self.backgroundOpacity = config.backgroundOpacity
+ self.backgroundColor = config.backgroundColor
+ }
+ }
+}
diff --git a/macos/Sources/Features/Terminal/Window Styles/TerminalWindow.swift b/macos/Sources/Features/Terminal/Window Styles/TerminalWindow.swift
index 4196df97f..501ac0e67 100644
--- a/macos/Sources/Features/Terminal/Window Styles/TerminalWindow.swift
+++ b/macos/Sources/Features/Terminal/Window Styles/TerminalWindow.swift
@@ -194,7 +194,7 @@ class TerminalWindow: NSWindow {
// Its possible we miss the accessory titlebar call so we check again
// whenever the window becomes main. Both of these are idempotent.
- if hasTabBar {
+ if tabBarView != nil {
tabBarDidAppear()
} else {
tabBarDidDisappear()
@@ -243,31 +243,6 @@ class TerminalWindow: NSWindow {
/// added.
static let tabBarIdentifier: NSUserInterfaceItemIdentifier = .init("_ghosttyTabBar")
- func findTitlebarView() -> NSView? {
- // Find our tab bar. If it doesn't exist we don't do anything.
- //
- // In normal window, `NSTabBar` typically appears as a subview of `NSTitlebarView` within `NSThemeFrame`.
- // In fullscreen, the system creates a dedicated fullscreen window and the view hierarchy changes;
- // in that case, the `titlebarView` is only accessible via a reference on `NSThemeFrame`.
- // ref: https://github.com/mozilla-firefox/firefox/blob/054e2b072785984455b3b59acad9444ba1eeffb4/widget/cocoa/nsCocoaWindow.mm#L7205
- guard let themeFrameView = contentView?.rootView else { return nil }
- let titlebarView = if themeFrameView.responds(to: Selector(("titlebarView"))) {
- themeFrameView.value(forKey: "titlebarView") as? NSView
- } else {
- NSView?.none
- }
- return titlebarView
- }
-
- func findTabBar() -> NSView? {
- findTitlebarView()?.firstDescendant(withClassName: "NSTabBar")
- }
-
- /// Returns true if there is a tab bar visible on this window.
- var hasTabBar: Bool {
- findTabBar() != nil
- }
-
var hasMoreThanOneTabs: Bool {
/// accessing ``tabGroup?.windows`` here
/// will cause other edge cases, be careful
@@ -474,7 +449,7 @@ class TerminalWindow: NSWindow {
let forceOpaque = terminalController?.isBackgroundOpaque ?? false
if !styleMask.contains(.fullScreen) &&
!forceOpaque &&
- surfaceConfig.backgroundOpacity < 1
+ (surfaceConfig.backgroundOpacity < 1 || surfaceConfig.backgroundBlur.isGlassStyle)
{
isOpaque = false
@@ -483,15 +458,8 @@ class TerminalWindow: NSWindow {
// Terminal.app more easily.
backgroundColor = .white.withAlphaComponent(0.001)
- // Add liquid glass behind terminal content
- if #available(macOS 26.0, *), derivedConfig.backgroundBlur.isGlassStyle {
- setupGlassLayer()
- } else if let appDelegate = NSApp.delegate as? AppDelegate {
- // If we had a prior glass layer we should remove it
- if #available(macOS 26.0, *) {
- removeGlassLayer()
- }
-
+ // We don't need to set blur when using glass
+ if !surfaceConfig.backgroundBlur.isGlassStyle, let appDelegate = NSApp.delegate as? AppDelegate {
ghostty_set_window_background_blur(
appDelegate.ghostty.app,
Unmanaged.passUnretained(self).toOpaque())
@@ -499,11 +467,6 @@ class TerminalWindow: NSWindow {
} else {
isOpaque = true
- // Remove liquid glass when not transparent
- if #available(macOS 26.0, *) {
- removeGlassLayer()
- }
-
let backgroundColor = preferredBackgroundColor ?? NSColor(surfaceConfig.backgroundColor)
self.backgroundColor = backgroundColor.withAlphaComponent(1)
}
@@ -581,50 +544,6 @@ class TerminalWindow: NSWindow {
NotificationCenter.default.removeObserver(observer)
}
}
-
-#if compiler(>=6.2)
- // MARK: Glass
-
- @available(macOS 26.0, *)
- private func setupGlassLayer() {
- // Remove existing glass effect view
- removeGlassLayer()
-
- // Get the window content view (parent of the NSHostingView)
- guard let contentView else { return }
- guard let windowContentView = contentView.superview else { return }
-
- // Create NSGlassEffectView for native glass effect
- let effectView = NSGlassEffectView()
-
- // Map Ghostty config to NSGlassEffectView style
- switch derivedConfig.backgroundBlur {
- case .macosGlassRegular:
- effectView.style = NSGlassEffectView.Style.regular
- case .macosGlassClear:
- effectView.style = NSGlassEffectView.Style.clear
- default:
- // Should not reach here since we check for glass style before calling
- // setupGlassLayer()
- assertionFailure()
- }
-
- effectView.cornerRadius = derivedConfig.windowCornerRadius
- effectView.tintColor = preferredBackgroundColor
- effectView.frame = windowContentView.bounds
- effectView.autoresizingMask = [.width, .height]
-
- // Position BELOW the terminal content to act as background
- windowContentView.addSubview(effectView, positioned: .below, relativeTo: contentView)
- glassEffectView = effectView
- }
-
- @available(macOS 26.0, *)
- private func removeGlassLayer() {
- glassEffectView?.removeFromSuperview()
- glassEffectView = nil
- }
-#endif // compiler(>=6.2)
// MARK: Config
diff --git a/macos/Sources/Features/Terminal/Window Styles/TitlebarTabsTahoeTerminalWindow.swift b/macos/Sources/Features/Terminal/Window Styles/TitlebarTabsTahoeTerminalWindow.swift
index 5d910d2e0..918191522 100644
--- a/macos/Sources/Features/Terminal/Window Styles/TitlebarTabsTahoeTerminalWindow.swift
+++ b/macos/Sources/Features/Terminal/Window Styles/TitlebarTabsTahoeTerminalWindow.swift
@@ -85,7 +85,7 @@ class TitlebarTabsTahoeTerminalWindow: TransparentTitlebarTerminalWindow, NSTool
return
}
- guard let tabBarView = findTabBar() else {
+ guard let tabBarView else {
super.sendEvent(event)
return
}
@@ -176,8 +176,8 @@ class TitlebarTabsTahoeTerminalWindow: TransparentTitlebarTerminalWindow, NSTool
guard tabBarObserver == nil else { return }
guard
- let titlebarView = findTitlebarView(),
- let tabBar = findTabBar()
+ let titlebarView,
+ let tabBarView = self.tabBarView
else { return }
// View model updates must happen on their own ticks.
@@ -186,13 +186,13 @@ class TitlebarTabsTahoeTerminalWindow: TransparentTitlebarTerminalWindow, NSTool
}
// Find our clip view
- guard let clipView = tabBar.firstSuperview(withClassName: "NSTitlebarAccessoryClipView") else { return }
+ guard let clipView = tabBarView.firstSuperview(withClassName: "NSTitlebarAccessoryClipView") else { return }
guard let accessoryView = clipView.subviews[safe: 0] else { return }
guard let toolbarView = titlebarView.firstDescendant(withClassName: "NSToolbarView") else { return }
// Make sure tabBar's height won't be stretched
guard let newTabButton = titlebarView.firstDescendant(withClassName: "NSTabBarNewTabButton") else { return }
- tabBar.frame.size.height = newTabButton.frame.width
+ tabBarView.frame.size.height = newTabButton.frame.width
// The container is the view that we'll constrain our tab bar within.
let container = toolbarView
@@ -228,10 +228,10 @@ class TitlebarTabsTahoeTerminalWindow: TransparentTitlebarTerminalWindow, NSTool
// other events occur, the tab bar can resize and clear our constraints. When this
// happens, we need to remove our custom constraints and re-apply them once the
// tab bar has proper dimensions again to avoid constraint conflicts.
- tabBar.postsFrameChangedNotifications = true
+ tabBarView.postsFrameChangedNotifications = true
tabBarObserver = NotificationCenter.default.addObserver(
forName: NSView.frameDidChangeNotification,
- object: tabBar,
+ object: tabBarView,
queue: .main
) { [weak self] _ in
guard let self else { return }
@@ -322,7 +322,8 @@ extension TitlebarTabsTahoeTerminalWindow {
} else {
// 1x1.gif strikes again! For real: if we render a zero-sized
// view here then the toolbar just disappears our view. I don't
- // know. This appears fixed in 26.1 Beta but keep it safe for 26.0.
+ // know. On macOS 26.1+ the view no longer disappears, but the
+ // toolbar still logs an ambiguous content size warning.
Color.clear.frame(width: 1, height: 1)
}
}
diff --git a/macos/Sources/Features/Terminal/Window Styles/TitlebarTabsVenturaTerminalWindow.swift b/macos/Sources/Features/Terminal/Window Styles/TitlebarTabsVenturaTerminalWindow.swift
index c0aad46b3..39db13c6d 100644
--- a/macos/Sources/Features/Terminal/Window Styles/TitlebarTabsVenturaTerminalWindow.swift
+++ b/macos/Sources/Features/Terminal/Window Styles/TitlebarTabsVenturaTerminalWindow.swift
@@ -5,7 +5,7 @@ class TitlebarTabsVenturaTerminalWindow: TerminalWindow {
/// Titlebar tabs can't support the update accessory because of the way we layout
/// the native tabs back into the menu bar.
override var supportsUpdateAccessory: Bool { false }
-
+
/// This is used to determine if certain elements should be drawn light or dark and should
/// be updated whenever the window background color or surrounding elements changes.
fileprivate var isLightTheme: Bool = false
@@ -395,7 +395,7 @@ class TitlebarTabsVenturaTerminalWindow: TerminalWindow {
// Hide the window drag handle.
windowDragHandle?.isHidden = true
- // Reenable the main toolbar title
+ // Re-enable the main toolbar title
if let toolbar = toolbar as? TerminalToolbar {
toolbar.titleIsHidden = false
}
diff --git a/macos/Sources/Features/Terminal/Window Styles/TransparentTitlebarTerminalWindow.swift b/macos/Sources/Features/Terminal/Window Styles/TransparentTitlebarTerminalWindow.swift
index 57b889b82..a72436d7f 100644
--- a/macos/Sources/Features/Terminal/Window Styles/TransparentTitlebarTerminalWindow.swift
+++ b/macos/Sources/Features/Terminal/Window Styles/TransparentTitlebarTerminalWindow.swift
@@ -7,16 +7,16 @@ class TransparentTitlebarTerminalWindow: TerminalWindow {
/// This is necessary because various macOS operations (tab switching, tab bar
/// visibility changes) can reset the titlebar appearance.
private var lastSurfaceConfig: Ghostty.SurfaceView.DerivedConfig?
-
+
/// KVO observation for tab group window changes.
private var tabGroupWindowsObservation: NSKeyValueObservation?
private var tabBarVisibleObservation: NSKeyValueObservation?
-
+
deinit {
tabGroupWindowsObservation?.invalidate()
tabBarVisibleObservation?.invalidate()
}
-
+
// MARK: NSWindow
override func awakeFromNib() {
@@ -29,7 +29,7 @@ class TransparentTitlebarTerminalWindow: TerminalWindow {
override func becomeMain() {
super.becomeMain()
-
+
guard let lastSurfaceConfig else { return }
syncAppearance(lastSurfaceConfig)
@@ -42,7 +42,7 @@ class TransparentTitlebarTerminalWindow: TerminalWindow {
}
}
}
-
+
override func update() {
super.update()
@@ -67,7 +67,7 @@ class TransparentTitlebarTerminalWindow: TerminalWindow {
// Save our config in case we need to reapply
lastSurfaceConfig = surfaceConfig
- // Everytime we change appearance, set KVO up again in case any of our
+ // Every time we change appearance, set KVO up again in case any of our
// references changed (e.g. tabGroup is new).
setupKVO()
@@ -99,7 +99,7 @@ class TransparentTitlebarTerminalWindow: TerminalWindow {
? NSColor.clear.cgColor
: preferredBackgroundColor?.cgColor
}
-
+
// In all cases, we have to hide the background view since this has multiple subviews
// that force a background color.
titlebarBackgroundView?.isHidden = true
@@ -108,14 +108,14 @@ class TransparentTitlebarTerminalWindow: TerminalWindow {
@available(macOS 13.0, *)
private func syncAppearanceVentura(_ surfaceConfig: Ghostty.SurfaceView.DerivedConfig) {
guard let titlebarContainer else { return }
-
+
// Setup the titlebar background color to match ours
titlebarContainer.wantsLayer = true
titlebarContainer.layer?.backgroundColor = preferredBackgroundColor?.cgColor
-
+
// See the docs for the function that sets this to true on why
effectViewIsHidden = false
-
+
// Necessary to not draw the border around the title
titlebarAppearsTransparent = true
}
@@ -141,7 +141,7 @@ class TransparentTitlebarTerminalWindow: TerminalWindow {
// Remove existing observation if any
tabGroupWindowsObservation?.invalidate()
tabGroupWindowsObservation = nil
-
+
// Check if tabGroup is available
guard let tabGroup else { return }
@@ -170,7 +170,7 @@ class TransparentTitlebarTerminalWindow: TerminalWindow {
// Remove existing observation if any
tabBarVisibleObservation?.invalidate()
tabBarVisibleObservation = nil
-
+
// Set up KVO observation for isTabBarVisible
tabBarVisibleObservation = tabGroup?.observe(
\.isTabBarVisible,
@@ -181,18 +181,18 @@ class TransparentTitlebarTerminalWindow: TerminalWindow {
self.syncAppearance(lastSurfaceConfig)
}
}
-
+
// MARK: macOS 13 to 15
-
+
// We only need to set this once, but need to do it after the window has been created in order
// to determine if the theme is using a very dark background, in which case we don't want to
// remove the effect view if the default tab bar is being used since the effect created in
// `updateTabsForVeryDarkBackgrounds` creates a confusing visual design.
private var effectViewIsHidden = false
-
+
private func hideEffectView() {
guard !effectViewIsHidden else { return }
-
+
// By hiding the visual effect view, we allow the window's (or titlebar's in this case)
// background color to show through. If we were to set `titlebarAppearsTransparent` to true
// the selected tab would look fine, but the unselected ones and new tab button backgrounds
diff --git a/macos/Sources/Ghostty/Ghostty.Action.swift b/macos/Sources/Ghostty/Ghostty.Action.swift
index 9eb7a8e46..91f1491dd 100644
--- a/macos/Sources/Ghostty/Ghostty.Action.swift
+++ b/macos/Sources/Ghostty/Ghostty.Action.swift
@@ -141,6 +141,27 @@ extension Ghostty.Action {
}
}
}
+
+ enum KeyTable {
+ case activate(name: String)
+ case deactivate
+ case deactivateAll
+
+ init?(c: ghostty_action_key_table_s) {
+ switch c.tag {
+ case GHOSTTY_KEY_TABLE_ACTIVATE:
+ let data = Data(bytes: c.value.activate.name, count: c.value.activate.len)
+ let name = String(data: data, encoding: .utf8) ?? ""
+ self = .activate(name: name)
+ case GHOSTTY_KEY_TABLE_DEACTIVATE:
+ self = .deactivate
+ case GHOSTTY_KEY_TABLE_DEACTIVATE_ALL:
+ self = .deactivateAll
+ default:
+ return nil
+ }
+ }
+ }
}
// Putting the initializer in an extension preserves the automatic one.
diff --git a/macos/Sources/Ghostty/Ghostty.App.swift b/macos/Sources/Ghostty/Ghostty.App.swift
index 3348ab714..5ec42158f 100644
--- a/macos/Sources/Ghostty/Ghostty.App.swift
+++ b/macos/Sources/Ghostty/Ghostty.App.swift
@@ -29,6 +29,8 @@ extension Ghostty {
/// configuration (i.e. font size) from the previously focused window. This would override this.
@Published private(set) var config: Config
+ /// Preferred config file than the default ones
+ private var configPath: String?
/// The ghostty app instance. We only have one of these for the entire app, although I guess
/// in theory you can have multiple... I don't know why you would...
@Published var app: ghostty_app_t? = nil {
@@ -44,9 +46,10 @@ extension Ghostty {
return ghostty_app_needs_confirm_quit(app)
}
- init() {
+ init(configPath: String? = nil) {
+ self.configPath = configPath
// Initialize the global configuration.
- self.config = Config()
+ self.config = Config(at: configPath)
if self.config.config == nil {
readiness = .error
return
@@ -143,7 +146,7 @@ extension Ghostty {
}
// Hard or full updates have to reload the full configuration
- let newConfig = Config()
+ let newConfig = Config(at: configPath)
guard newConfig.loaded else {
Ghostty.logger.warning("failed to reload configuration")
return
@@ -163,7 +166,7 @@ extension Ghostty {
// Hard or full updates have to reload the full configuration.
// NOTE: We never set this on self.config because this is a surface-only
// config. We free it after the call.
- let newConfig = Config()
+ let newConfig = Config(at: configPath)
guard newConfig.loaded else {
Ghostty.logger.warning("failed to reload configuration")
return
@@ -578,7 +581,10 @@ extension Ghostty {
case GHOSTTY_ACTION_KEY_SEQUENCE:
keySequence(app, target: target, v: action.action.key_sequence)
-
+
+ case GHOSTTY_ACTION_KEY_TABLE:
+ keyTable(app, target: target, v: action.action.key_table)
+
case GHOSTTY_ACTION_PROGRESS_REPORT:
progressReport(app, target: target, v: action.action.progress_report)
@@ -770,7 +776,7 @@ extension Ghostty {
name: Notification.ghosttyNewWindow,
object: surfaceView,
userInfo: [
- Notification.NewSurfaceConfigKey: SurfaceConfiguration(from: ghostty_surface_inherited_config(surface)),
+ Notification.NewSurfaceConfigKey: SurfaceConfiguration(from: ghostty_surface_inherited_config(surface, GHOSTTY_SURFACE_CONTEXT_WINDOW)),
]
)
@@ -807,7 +813,7 @@ extension Ghostty {
name: Notification.ghosttyNewTab,
object: surfaceView,
userInfo: [
- Notification.NewSurfaceConfigKey: SurfaceConfiguration(from: ghostty_surface_inherited_config(surface)),
+ Notification.NewSurfaceConfigKey: SurfaceConfiguration(from: ghostty_surface_inherited_config(surface, GHOSTTY_SURFACE_CONTEXT_TAB)),
]
)
@@ -836,7 +842,7 @@ extension Ghostty {
object: surfaceView,
userInfo: [
"direction": direction,
- Notification.NewSurfaceConfigKey: SurfaceConfiguration(from: ghostty_surface_inherited_config(surface)),
+ Notification.NewSurfaceConfigKey: SurfaceConfiguration(from: ghostty_surface_inherited_config(surface, GHOSTTY_SURFACE_CONTEXT_SPLIT)),
]
)
@@ -1771,7 +1777,32 @@ extension Ghostty {
assertionFailure()
}
}
-
+
+ private static func keyTable(
+ _ app: ghostty_app_t,
+ target: ghostty_target_s,
+ v: ghostty_action_key_table_s) {
+ switch (target.tag) {
+ case GHOSTTY_TARGET_APP:
+ Ghostty.logger.warning("key table does nothing with an app target")
+ return
+
+ case GHOSTTY_TARGET_SURFACE:
+ guard let surface = target.target.surface else { return }
+ guard let surfaceView = self.surfaceView(from: surface) else { return }
+ guard let action = Ghostty.Action.KeyTable(c: v) else { return }
+
+ NotificationCenter.default.post(
+ name: Notification.didChangeKeyTable,
+ object: surfaceView,
+ userInfo: [Notification.KeyTableKey: action]
+ )
+
+ default:
+ assertionFailure()
+ }
+ }
+
private static func progressReport(
_ app: ghostty_app_t,
target: ghostty_target_s,
@@ -1841,11 +1872,15 @@ extension Ghostty {
let startSearch = Ghostty.Action.StartSearch(c: v)
DispatchQueue.main.async {
- if surfaceView.searchState != nil {
- NotificationCenter.default.post(name: .ghosttySearchFocus, object: surfaceView)
+ if let searchState = surfaceView.searchState {
+ if let needle = startSearch.needle, !needle.isEmpty {
+ searchState.needle = needle
+ }
} else {
surfaceView.searchState = Ghostty.SurfaceView.SearchState(from: startSearch)
}
+
+ NotificationCenter.default.post(name: .ghosttySearchFocus, object: surfaceView)
}
default:
diff --git a/macos/Sources/Ghostty/Ghostty.Command.swift b/macos/Sources/Ghostty/Ghostty.Command.swift
index 1479ae92d..797d469c5 100644
--- a/macos/Sources/Ghostty/Ghostty.Command.swift
+++ b/macos/Sources/Ghostty/Ghostty.Command.swift
@@ -3,28 +3,18 @@ import GhosttyKit
extension Ghostty {
/// `ghostty_command_s`
struct Command: Sendable {
- private let cValue: ghostty_command_s
-
/// The title of the command.
- var title: String {
- String(cString: cValue.title)
- }
+ let title: String
/// Human-friendly description of what this command will do.
- var description: String {
- String(cString: cValue.description)
- }
+ let description: String
/// The full action that must be performed to invoke this command.
- var action: String {
- String(cString: cValue.action)
- }
+ let action: String
/// Only the key portion of the action so you can compare action types, e.g. `goto_split`
/// instead of `goto_split:left`.
- var actionKey: String {
- String(cString: cValue.action_key)
- }
+ let actionKey: String
/// True if this can be performed on this target.
var isSupported: Bool {
@@ -40,7 +30,10 @@ extension Ghostty {
]
init(cValue: ghostty_command_s) {
- self.cValue = cValue
+ self.title = String(cString: cValue.title)
+ self.description = String(cString: cValue.description)
+ self.action = String(cString: cValue.action)
+ self.actionKey = String(cString: cValue.action_key)
}
}
}
diff --git a/macos/Sources/Ghostty/Ghostty.Config.swift b/macos/Sources/Ghostty/Ghostty.Config.swift
index 7ea545f7a..c64646e25 100644
--- a/macos/Sources/Ghostty/Ghostty.Config.swift
+++ b/macos/Sources/Ghostty/Ghostty.Config.swift
@@ -33,14 +33,16 @@ extension Ghostty {
return diags
}
- init() {
- if let cfg = Self.loadConfig() {
- self.config = cfg
- }
+ init(config: ghostty_config_t?) {
+ self.config = config
}
- init(clone config: ghostty_config_t) {
- self.config = ghostty_config_clone(config)
+ convenience init(at path: String? = nil, finalize: Bool = true) {
+ self.init(config: Self.loadConfig(at: path, finalize: finalize))
+ }
+
+ convenience init(clone config: ghostty_config_t) {
+ self.init(config: ghostty_config_clone(config))
}
deinit {
@@ -48,7 +50,10 @@ extension Ghostty {
}
/// Initializes a new configuration and loads all the values.
- static private func loadConfig() -> ghostty_config_t? {
+ /// - Parameters:
+ /// - path: An optional preferred config file path. Pass `nil` to load the default configuration files.
+ /// - finalize: Whether to finalize the configuration to populate default values.
+ static private func loadConfig(at path: String?, finalize: Bool) -> ghostty_config_t? {
// Initialize the global configuration.
guard let cfg = ghostty_config_new() else {
logger.critical("ghostty_config_new failed")
@@ -59,7 +64,11 @@ extension Ghostty {
// We only do this on macOS because other Apple platforms do not have the
// same filesystem concept.
#if os(macOS)
- ghostty_config_load_default_files(cfg);
+ if let path {
+ ghostty_config_load_file(cfg, path)
+ } else {
+ ghostty_config_load_default_files(cfg)
+ }
// We only load CLI args when not running in Xcode because in Xcode we
// pass some special parameters to control the debugger.
@@ -74,9 +83,10 @@ extension Ghostty {
// have to do this synchronously. When we support config updating we can do
// this async and update later.
- // Finalize will make our defaults available.
- ghostty_config_finalize(cfg)
-
+ if finalize {
+ // Finalize will make our defaults available.
+ ghostty_config_finalize(cfg)
+ }
// Log any configuration errors. These will be automatically shown in a
// pop-up window too.
let diagsCount = ghostty_config_diagnostics_count(cfg)
@@ -622,6 +632,16 @@ extension Ghostty {
let str = String(cString: ptr)
return Scrollbar(rawValue: str) ?? defaultValue
}
+
+ var commandPaletteEntries: [Ghostty.Command] {
+ guard let config = self.config else { return [] }
+ var v: ghostty_config_command_list_s = .init()
+ let key = "command-palette-entry"
+ guard ghostty_config_get(config, &v, key, UInt(key.lengthOfBytes(using: .utf8))) else { return [] }
+ guard v.len > 0 else { return [] }
+ let buffer = UnsafeBufferPointer(start: v.commands, count: v.len)
+ return buffer.map { Ghostty.Command(cValue: $0) }
+ }
}
}
@@ -648,9 +668,17 @@ extension Ghostty.Config {
case 0:
self = .disabled
case -1:
- self = .macosGlassRegular
+ if #available(macOS 26.0, *) {
+ self = .macosGlassRegular
+ } else {
+ self = .disabled
+ }
case -2:
- self = .macosGlassClear
+ if #available(macOS 26.0, *) {
+ self = .macosGlassClear
+ } else {
+ self = .disabled
+ }
default:
self = .radius(Int(value))
}
diff --git a/macos/Sources/Ghostty/Ghostty.Input.swift b/macos/Sources/Ghostty/Ghostty.Input.swift
index e05911c06..7b2905abb 100644
--- a/macos/Sources/Ghostty/Ghostty.Input.swift
+++ b/macos/Sources/Ghostty/Ghostty.Input.swift
@@ -32,6 +32,10 @@ extension Ghostty {
guard let scalar = UnicodeScalar(trigger.key.unicode) else { return nil }
key = KeyEquivalent(Character(scalar))
+ case GHOSTTY_TRIGGER_CATCH_ALL:
+ // catch_all matches any key, so it can't be represented as a KeyboardShortcut
+ return nil
+
default:
return nil
}
@@ -64,7 +68,7 @@ extension Ghostty {
if (flags.contains(.capsLock)) { mods |= GHOSTTY_MODS_CAPS.rawValue }
// Handle sided input. We can't tell that both are pressed in the
- // Ghostty structure but thats okay -- we don't use that information.
+ // Ghostty structure but that's okay -- we don't use that information.
let rawFlags = flags.rawValue
if (rawFlags & UInt(NX_DEVICERSHIFTKEYMASK) != 0) { mods |= GHOSTTY_MODS_SHIFT_RIGHT.rawValue }
if (rawFlags & UInt(NX_DEVICERCTLKEYMASK) != 0) { mods |= GHOSTTY_MODS_CTRL_RIGHT.rawValue }
@@ -96,6 +100,32 @@ extension Ghostty {
]
}
+// MARK: Ghostty.Input.BindingFlags
+
+extension Ghostty.Input {
+ /// `ghostty_binding_flags_e`
+ struct BindingFlags: OptionSet, Sendable {
+ let rawValue: UInt32
+
+ static let consumed = BindingFlags(rawValue: GHOSTTY_BINDING_FLAGS_CONSUMED.rawValue)
+ static let all = BindingFlags(rawValue: GHOSTTY_BINDING_FLAGS_ALL.rawValue)
+ static let global = BindingFlags(rawValue: GHOSTTY_BINDING_FLAGS_GLOBAL.rawValue)
+ static let performable = BindingFlags(rawValue: GHOSTTY_BINDING_FLAGS_PERFORMABLE.rawValue)
+
+ init(rawValue: UInt32) {
+ self.rawValue = rawValue
+ }
+
+ init(cFlags: ghostty_binding_flags_e) {
+ self.rawValue = cFlags.rawValue
+ }
+
+ var cFlags: ghostty_binding_flags_e {
+ ghostty_binding_flags_e(rawValue)
+ }
+ }
+}
+
// MARK: Ghostty.Input.KeyEvent
extension Ghostty.Input {
@@ -135,7 +165,7 @@ extension Ghostty.Input {
case GHOSTTY_ACTION_REPEAT: self.action = .repeat
default: self.action = .press
}
-
+
// Convert key from keycode
guard let key = Key(keyCode: UInt16(cValue.keycode)) else { return nil }
self.key = key
@@ -146,18 +176,18 @@ extension Ghostty.Input {
} else {
self.text = nil
}
-
+
// Set composing state
self.composing = cValue.composing
-
+
// Convert modifiers
self.mods = Mods(cMods: cValue.mods)
self.consumedMods = Mods(cMods: cValue.consumed_mods)
-
+
// Set unshifted codepoint
self.unshiftedCodepoint = cValue.unshifted_codepoint
}
-
+
/// Executes a closure with a temporary C representation of this KeyEvent.
///
/// This method safely converts the Swift KeyEntity to a C `ghostty_input_key_s` struct
@@ -176,7 +206,7 @@ extension Ghostty.Input {
keyEvent.mods = mods.cMods
keyEvent.consumed_mods = consumedMods.cMods
keyEvent.unshifted_codepoint = unshiftedCodepoint
-
+
// Handle text with proper memory management
if let text = text {
return text.withCString { textPtr in
@@ -199,7 +229,7 @@ extension Ghostty.Input {
case release
case press
case `repeat`
-
+
var cAction: ghostty_input_action_e {
switch self {
case .release: GHOSTTY_ACTION_RELEASE
@@ -228,7 +258,7 @@ extension Ghostty.Input {
let action: MouseState
let button: MouseButton
let mods: Mods
-
+
init(
action: MouseState,
button: MouseButton,
@@ -238,7 +268,7 @@ extension Ghostty.Input {
self.button = button
self.mods = mods
}
-
+
/// Creates a MouseEvent from C enum values.
///
/// This initializer converts C-style mouse input enums to Swift types.
@@ -255,7 +285,7 @@ extension Ghostty.Input {
case GHOSTTY_MOUSE_PRESS: self.action = .press
default: return nil
}
-
+
// Convert button
switch button {
case GHOSTTY_MOUSE_UNKNOWN: self.button = .unknown
@@ -264,7 +294,7 @@ extension Ghostty.Input {
case GHOSTTY_MOUSE_MIDDLE: self.button = .middle
default: return nil
}
-
+
// Convert modifiers
self.mods = Mods(cMods: mods)
}
@@ -275,7 +305,7 @@ extension Ghostty.Input {
let x: Double
let y: Double
let mods: Mods
-
+
init(
x: Double,
y: Double,
@@ -312,7 +342,7 @@ extension Ghostty.Input {
enum MouseState: String, CaseIterable {
case release
case press
-
+
var cMouseState: ghostty_input_mouse_state_e {
switch self {
case .release: GHOSTTY_MOUSE_RELEASE
@@ -340,13 +370,48 @@ extension Ghostty.Input {
case left
case right
case middle
-
+ case four
+ case five
+ case six
+ case seven
+ case eight
+ case nine
+ case ten
+ case eleven
+
var cMouseButton: ghostty_input_mouse_button_e {
switch self {
case .unknown: GHOSTTY_MOUSE_UNKNOWN
case .left: GHOSTTY_MOUSE_LEFT
case .right: GHOSTTY_MOUSE_RIGHT
case .middle: GHOSTTY_MOUSE_MIDDLE
+ case .four: GHOSTTY_MOUSE_FOUR
+ case .five: GHOSTTY_MOUSE_FIVE
+ case .six: GHOSTTY_MOUSE_SIX
+ case .seven: GHOSTTY_MOUSE_SEVEN
+ case .eight: GHOSTTY_MOUSE_EIGHT
+ case .nine: GHOSTTY_MOUSE_NINE
+ case .ten: GHOSTTY_MOUSE_TEN
+ case .eleven: GHOSTTY_MOUSE_ELEVEN
+ }
+ }
+
+ /// Initialize from NSEvent.buttonNumber
+ /// NSEvent buttonNumber: 0=left, 1=right, 2=middle, 3=back (button 8), 4=forward (button 9), etc.
+ init(fromNSEventButtonNumber buttonNumber: Int) {
+ switch buttonNumber {
+ case 0: self = .left
+ case 1: self = .right
+ case 2: self = .middle
+ case 3: self = .eight // Back button
+ case 4: self = .nine // Forward button
+ case 5: self = .six
+ case 6: self = .seven
+ case 7: self = .four
+ case 8: self = .five
+ case 9: self = .ten
+ case 10: self = .eleven
+ default: self = .unknown
}
}
}
@@ -378,18 +443,18 @@ extension Ghostty.Input {
/// for scroll events, matching the Zig `ScrollMods` packed struct.
struct ScrollMods {
let rawValue: Int32
-
+
/// True if this is a high-precision scroll event (e.g., trackpad, Magic Mouse)
var precision: Bool {
rawValue & 0b0000_0001 != 0
}
-
+
/// The momentum phase of the scroll event for inertial scrolling
var momentum: Momentum {
let momentumBits = (rawValue >> 1) & 0b0000_0111
return Momentum(rawValue: UInt8(momentumBits)) ?? .none
}
-
+
init(precision: Bool = false, momentum: Momentum = .none) {
var value: Int32 = 0
if precision {
@@ -398,11 +463,11 @@ extension Ghostty.Input {
value |= Int32(momentum.rawValue) << 1
self.rawValue = value
}
-
+
init(rawValue: Int32) {
self.rawValue = rawValue
}
-
+
var cScrollMods: ghostty_input_scroll_mods_t {
rawValue
}
@@ -421,7 +486,7 @@ extension Ghostty.Input {
case ended = 4
case cancelled = 5
case mayBegin = 6
-
+
var cMomentum: ghostty_input_mouse_momentum_e {
switch self {
case .none: GHOSTTY_MOUSE_MOMENTUM_NONE
@@ -438,7 +503,7 @@ extension Ghostty.Input {
extension Ghostty.Input.Momentum: AppEnum {
static var typeDisplayRepresentation = TypeDisplayRepresentation(name: "Scroll Momentum")
-
+
static var caseDisplayRepresentations: [Ghostty.Input.Momentum : DisplayRepresentation] = [
.none: "None",
.began: "Began",
@@ -475,7 +540,7 @@ extension Ghostty.Input {
/// `ghostty_input_mods_e`
struct Mods: OptionSet {
let rawValue: UInt32
-
+
static let none = Mods(rawValue: GHOSTTY_MODS_NONE.rawValue)
static let shift = Mods(rawValue: GHOSTTY_MODS_SHIFT.rawValue)
static let ctrl = Mods(rawValue: GHOSTTY_MODS_CTRL.rawValue)
@@ -486,23 +551,23 @@ extension Ghostty.Input {
static let ctrlRight = Mods(rawValue: GHOSTTY_MODS_CTRL_RIGHT.rawValue)
static let altRight = Mods(rawValue: GHOSTTY_MODS_ALT_RIGHT.rawValue)
static let superRight = Mods(rawValue: GHOSTTY_MODS_SUPER_RIGHT.rawValue)
-
+
var cMods: ghostty_input_mods_e {
ghostty_input_mods_e(rawValue)
}
-
+
init(rawValue: UInt32) {
self.rawValue = rawValue
}
-
+
init(cMods: ghostty_input_mods_e) {
self.rawValue = cMods.rawValue
}
-
+
init(nsFlags: NSEvent.ModifierFlags) {
self.init(cMods: Ghostty.ghosttyMods(nsFlags))
}
-
+
var nsFlags: NSEvent.ModifierFlags {
Ghostty.eventModifierFlags(mods: cMods)
}
@@ -1116,43 +1181,43 @@ extension Ghostty.Input.Key: AppEnum {
return [
// Letters (A-Z)
.a, .b, .c, .d, .e, .f, .g, .h, .i, .j, .k, .l, .m, .n, .o, .p, .q, .r, .s, .t, .u, .v, .w, .x, .y, .z,
-
+
// Numbers (0-9)
.digit0, .digit1, .digit2, .digit3, .digit4, .digit5, .digit6, .digit7, .digit8, .digit9,
-
+
// Common Control Keys
.space, .enter, .tab, .backspace, .escape, .delete,
-
+
// Arrow Keys
.arrowUp, .arrowDown, .arrowLeft, .arrowRight,
-
+
// Navigation Keys
.home, .end, .pageUp, .pageDown, .insert,
-
+
// Function Keys (F1-F20)
.f1, .f2, .f3, .f4, .f5, .f6, .f7, .f8, .f9, .f10, .f11, .f12,
.f13, .f14, .f15, .f16, .f17, .f18, .f19, .f20,
-
+
// Modifier Keys
.shiftLeft, .shiftRight, .controlLeft, .controlRight, .altLeft, .altRight,
.metaLeft, .metaRight, .capsLock,
-
+
// Punctuation & Symbols
.minus, .equal, .backquote, .bracketLeft, .bracketRight, .backslash,
.semicolon, .quote, .comma, .period, .slash,
-
+
// Numpad
.numLock, .numpad0, .numpad1, .numpad2, .numpad3, .numpad4, .numpad5,
.numpad6, .numpad7, .numpad8, .numpad9, .numpadAdd, .numpadSubtract,
.numpadMultiply, .numpadDivide, .numpadDecimal, .numpadEqual,
.numpadEnter, .numpadComma,
-
+
// Media Keys
.audioVolumeUp, .audioVolumeDown, .audioVolumeMute,
-
+
// International Keys
.intlBackslash, .intlRo, .intlYen,
-
+
// Other
.contextMenu
]
@@ -1163,11 +1228,11 @@ extension Ghostty.Input.Key: AppEnum {
.a: "A", .b: "B", .c: "C", .d: "D", .e: "E", .f: "F", .g: "G", .h: "H", .i: "I", .j: "J",
.k: "K", .l: "L", .m: "M", .n: "N", .o: "O", .p: "P", .q: "Q", .r: "R", .s: "S", .t: "T",
.u: "U", .v: "V", .w: "W", .x: "X", .y: "Y", .z: "Z",
-
+
// Numbers (0-9)
.digit0: "0", .digit1: "1", .digit2: "2", .digit3: "3", .digit4: "4",
.digit5: "5", .digit6: "6", .digit7: "7", .digit8: "8", .digit9: "9",
-
+
// Common Control Keys
.space: "Space",
.enter: "Enter",
@@ -1175,26 +1240,26 @@ extension Ghostty.Input.Key: AppEnum {
.backspace: "Backspace",
.escape: "Escape",
.delete: "Delete",
-
+
// Arrow Keys
.arrowUp: "Up Arrow",
.arrowDown: "Down Arrow",
.arrowLeft: "Left Arrow",
.arrowRight: "Right Arrow",
-
+
// Navigation Keys
.home: "Home",
.end: "End",
.pageUp: "Page Up",
.pageDown: "Page Down",
.insert: "Insert",
-
+
// Function Keys (F1-F20)
.f1: "F1", .f2: "F2", .f3: "F3", .f4: "F4", .f5: "F5", .f6: "F6",
.f7: "F7", .f8: "F8", .f9: "F9", .f10: "F10", .f11: "F11", .f12: "F12",
.f13: "F13", .f14: "F14", .f15: "F15", .f16: "F16", .f17: "F17",
.f18: "F18", .f19: "F19", .f20: "F20",
-
+
// Modifier Keys
.shiftLeft: "Left Shift",
.shiftRight: "Right Shift",
@@ -1205,7 +1270,7 @@ extension Ghostty.Input.Key: AppEnum {
.metaLeft: "Left Command",
.metaRight: "Right Command",
.capsLock: "Caps Lock",
-
+
// Punctuation & Symbols
.minus: "Minus (-)",
.equal: "Equal (=)",
@@ -1218,7 +1283,7 @@ extension Ghostty.Input.Key: AppEnum {
.comma: "Comma (,)",
.period: "Period (.)",
.slash: "Slash (/)",
-
+
// Numpad
.numLock: "Num Lock",
.numpad0: "Numpad 0", .numpad1: "Numpad 1", .numpad2: "Numpad 2",
@@ -1232,17 +1297,17 @@ extension Ghostty.Input.Key: AppEnum {
.numpadEqual: "Numpad Equal",
.numpadEnter: "Numpad Enter",
.numpadComma: "Numpad Comma",
-
+
// Media Keys
.audioVolumeUp: "Volume Up",
.audioVolumeDown: "Volume Down",
.audioVolumeMute: "Volume Mute",
-
+
// International Keys
.intlBackslash: "International Backslash",
.intlRo: "International Ro",
.intlYen: "International Yen",
-
+
// Other
.contextMenu: "Context Menu"
]
diff --git a/macos/Sources/Ghostty/Ghostty.Surface.swift b/macos/Sources/Ghostty/Ghostty.Surface.swift
index c7198e147..7cb32ed71 100644
--- a/macos/Sources/Ghostty/Ghostty.Surface.swift
+++ b/macos/Sources/Ghostty/Ghostty.Surface.swift
@@ -62,6 +62,26 @@ extension Ghostty {
}
}
+ /// Check if a key event matches a keybinding.
+ ///
+ /// This checks whether the given key event would trigger a keybinding in the terminal.
+ /// If it matches, returns the binding flags indicating properties of the matched binding.
+ ///
+ /// - Parameter event: The key event to check
+ /// - Returns: The binding flags if a binding matches, or nil if no binding matches
+ @MainActor
+ func keyIsBinding(_ event: ghostty_input_key_s) -> Input.BindingFlags? {
+ var flags = ghostty_binding_flags_e(0)
+ guard ghostty_surface_key_is_binding(surface, event, &flags) else { return nil }
+ return Input.BindingFlags(cFlags: flags)
+ }
+
+ /// See `keyIsBinding(_ event: ghostty_input_key_s)`.
+ @MainActor
+ func keyIsBinding(_ event: Input.KeyEvent) -> Input.BindingFlags? {
+ event.withCValue { keyIsBinding($0) }
+ }
+
/// Whether the terminal has captured mouse input.
///
/// When the mouse is captured, the terminal application is receiving mouse events
@@ -134,16 +154,5 @@ extension Ghostty {
ghostty_surface_binding_action(surface, cString, UInt(len - 1))
}
}
-
- /// Command options for this surface.
- @MainActor
- func commands() throws -> [Command] {
- var ptr: UnsafeMutablePointer? = nil
- var count: Int = 0
- ghostty_surface_commands(surface, &ptr, &count)
- guard let ptr else { throw Error.apiFailed }
- let buffer = UnsafeBufferPointer(start: ptr, count: count)
- return Array(buffer).map { Command(cValue: $0) }.filter { $0.isSupported }
- }
}
}
diff --git a/macos/Sources/Ghostty/GhosttyDelegate.swift b/macos/Sources/Ghostty/GhosttyDelegate.swift
new file mode 100644
index 000000000..a9d255737
--- /dev/null
+++ b/macos/Sources/Ghostty/GhosttyDelegate.swift
@@ -0,0 +1,10 @@
+import Foundation
+
+extension Ghostty {
+ /// This is a delegate that should be applied to your global app delegate for GhosttyKit
+ /// to perform app-global operations.
+ protocol Delegate {
+ /// Look up a surface within the application by ID.
+ func ghosttySurface(id: UUID) -> SurfaceView?
+ }
+}
diff --git a/macos/Sources/Ghostty/Package.swift b/macos/Sources/Ghostty/Package.swift
index 375e5c37b..15cb3a51e 100644
--- a/macos/Sources/Ghostty/Package.swift
+++ b/macos/Sources/Ghostty/Package.swift
@@ -330,6 +330,22 @@ extension Ghostty {
case xray
case custom
case customStyle = "custom-style"
+
+ /// Bundled asset name for built-in icons
+ var assetName: String? {
+ switch self {
+ case .official: return nil
+ case .blueprint: return "BlueprintImage"
+ case .chalkboard: return "ChalkboardImage"
+ case .microchip: return "MicrochipImage"
+ case .glass: return "GlassImage"
+ case .holographic: return "HolographicImage"
+ case .paper: return "PaperImage"
+ case .retro: return "RetroImage"
+ case .xray: return "XrayImage"
+ case .custom, .customStyle: return nil
+ }
+ }
}
/// macos-icon-frame
@@ -475,6 +491,10 @@ extension Ghostty.Notification {
static let didContinueKeySequence = Notification.Name("com.mitchellh.ghostty.didContinueKeySequence")
static let didEndKeySequence = Notification.Name("com.mitchellh.ghostty.didEndKeySequence")
static let KeySequenceKey = didContinueKeySequence.rawValue + ".key"
+
+ /// Notifications related to key tables
+ static let didChangeKeyTable = Notification.Name("com.mitchellh.ghostty.didChangeKeyTable")
+ static let KeyTableKey = didChangeKeyTable.rawValue + ".action"
}
// Make the input enum hashable.
diff --git a/macos/Sources/Ghostty/InspectorView.swift b/macos/Sources/Ghostty/Surface View/InspectorView.swift
similarity index 100%
rename from macos/Sources/Ghostty/InspectorView.swift
rename to macos/Sources/Ghostty/Surface View/InspectorView.swift
diff --git a/macos/Sources/Ghostty/Surface View/SurfaceDragSource.swift b/macos/Sources/Ghostty/Surface View/SurfaceDragSource.swift
new file mode 100644
index 000000000..37a69852e
--- /dev/null
+++ b/macos/Sources/Ghostty/Surface View/SurfaceDragSource.swift
@@ -0,0 +1,268 @@
+import AppKit
+import SwiftUI
+
+extension Ghostty {
+ /// A preference key that propagates the ID of the SurfaceView currently being dragged,
+ /// or nil if no surface is being dragged.
+ struct DraggingSurfaceKey: PreferenceKey {
+ static var defaultValue: SurfaceView.ID? = nil
+
+ static func reduce(value: inout SurfaceView.ID?, nextValue: () -> SurfaceView.ID?) {
+ value = nextValue() ?? value
+ }
+ }
+
+ /// A SwiftUI view that provides drag source functionality for terminal surfaces.
+ ///
+ /// This view wraps an AppKit-based drag source to enable drag-and-drop reordering
+ /// of terminal surfaces within split views. When the user drags this view, it initiates
+ /// an `NSDraggingSession` with the surface's UUID encoded in the pasteboard, allowing
+ /// drop targets to identify which surface is being moved.
+ ///
+ /// The view also publishes the dragging state via `DraggingSurfaceKey` preference,
+ /// enabling parent views to react to ongoing drag operations.
+ struct SurfaceDragSource: View {
+ /// The surface view that will be dragged.
+ let surfaceView: SurfaceView
+
+ /// Binding that reflects whether a drag session is currently active.
+ @Binding var isDragging: Bool
+
+ /// Binding that reflects whether the mouse is hovering over this view.
+ @Binding var isHovering: Bool
+
+ var body: some View {
+ SurfaceDragSourceViewRepresentable(
+ surfaceView: surfaceView,
+ isDragging: $isDragging,
+ isHovering: $isHovering)
+ .preference(key: DraggingSurfaceKey.self, value: isDragging ? surfaceView.id : nil)
+ }
+ }
+
+ /// An NSViewRepresentable that provides AppKit-based drag source functionality.
+ /// This gives us control over the drag lifecycle, particularly detecting drag start.
+ fileprivate struct SurfaceDragSourceViewRepresentable: NSViewRepresentable {
+ let surfaceView: SurfaceView
+ @Binding var isDragging: Bool
+ @Binding var isHovering: Bool
+
+ func makeNSView(context: Context) -> SurfaceDragSourceView {
+ let view = SurfaceDragSourceView()
+ view.surfaceView = surfaceView
+ view.onDragStateChanged = { dragging in
+ isDragging = dragging
+ }
+ view.onHoverChanged = { hovering in
+ withAnimation(.easeInOut(duration: 0.15)) {
+ isHovering = hovering
+ }
+ }
+ return view
+ }
+
+ func updateNSView(_ nsView: SurfaceDragSourceView, context: Context) {
+ nsView.surfaceView = surfaceView
+ nsView.onDragStateChanged = { dragging in
+ isDragging = dragging
+ }
+ nsView.onHoverChanged = { hovering in
+ withAnimation(.easeInOut(duration: 0.15)) {
+ isHovering = hovering
+ }
+ }
+ }
+ }
+
+ /// The underlying NSView that handles drag operations.
+ ///
+ /// This view manages mouse tracking and drag initiation for surface reordering.
+ /// It uses a local event loop to detect drag gestures and initiates an
+ /// `NSDraggingSession` when the user drags beyond the threshold distance.
+ fileprivate class SurfaceDragSourceView: NSView, NSDraggingSource {
+ /// Scale factor applied to the surface snapshot for the drag preview image.
+ private static let previewScale: CGFloat = 0.2
+
+ /// The surface view that will be dragged. Its UUID is encoded into the
+ /// pasteboard for drop targets to identify which surface is being moved.
+ var surfaceView: SurfaceView?
+
+ /// Callback invoked when the drag state changes. Called with `true` when
+ /// a drag session begins, and `false` when it ends (completed or cancelled).
+ var onDragStateChanged: ((Bool) -> Void)?
+
+ /// Callback invoked when the mouse enters or exits this view's bounds.
+ /// Used to update the hover state for visual feedback in the parent view.
+ var onHoverChanged: ((Bool) -> Void)?
+
+ /// Whether we are currently in a mouse tracking loop (between mouseDown
+ /// and either mouseUp or drag initiation). Used to determine cursor state.
+ private var isTracking: Bool = false
+
+ /// Local event monitor to detect escape key presses during drag.
+ private var escapeMonitor: Any?
+
+ /// Whether the current drag was cancelled by pressing escape.
+ private var dragCancelledByEscape: Bool = false
+
+ deinit {
+ if let escapeMonitor {
+ NSEvent.removeMonitor(escapeMonitor)
+ }
+ }
+
+ override func acceptsFirstMouse(for event: NSEvent?) -> Bool {
+ // Ensure this view gets the mouse event before window dragging handlers
+ return true
+ }
+
+ override func mouseDown(with event: NSEvent) {
+ // Consume the mouseDown event to prevent it from propagating to the
+ // window's drag handler. This fixes issue #10110 where grab handles
+ // would drag the window instead of initiating pane drags.
+ // Don't call super - the drag will be initiated in mouseDragged.
+ }
+
+ override func updateTrackingAreas() {
+ super.updateTrackingAreas()
+
+ // To update our tracking area we just recreate it all.
+ trackingAreas.forEach { removeTrackingArea($0) }
+
+ // Add our tracking area for mouse events
+ addTrackingArea(NSTrackingArea(
+ rect: bounds,
+ options: [.mouseEnteredAndExited, .activeInActiveApp],
+ owner: self,
+ userInfo: nil
+ ))
+ }
+
+ override func resetCursorRects() {
+ addCursorRect(bounds, cursor: isTracking ? .closedHand : .openHand)
+ }
+
+ override func mouseEntered(with event: NSEvent) {
+ onHoverChanged?(true)
+ }
+
+ override func mouseExited(with event: NSEvent) {
+ onHoverChanged?(false)
+ }
+
+ override func mouseDragged(with event: NSEvent) {
+ guard !isTracking, let surfaceView = surfaceView else { return }
+
+ // Create our dragging item from our transferable
+ guard let pasteboardItem = surfaceView.pasteboardItem() else { return }
+ let item = NSDraggingItem(pasteboardWriter: pasteboardItem)
+
+ // Create a scaled preview image from the surface snapshot
+ if let snapshot = surfaceView.asImage {
+ let imageSize = NSSize(
+ width: snapshot.size.width * Self.previewScale,
+ height: snapshot.size.height * Self.previewScale
+ )
+ let scaledImage = NSImage(size: imageSize)
+ scaledImage.lockFocus()
+ snapshot.draw(
+ in: NSRect(origin: .zero, size: imageSize),
+ from: NSRect(origin: .zero, size: snapshot.size),
+ operation: .copy,
+ fraction: 1.0
+ )
+ scaledImage.unlockFocus()
+
+ // Position the drag image so the mouse is at the center of the image.
+ // I personally like the top middle or top left corner best but
+ // this matches macOS native tab dragging behavior (at least, as of
+ // macOS 26.2 on Dec 29, 2025).
+ let mouseLocation = convert(event.locationInWindow, from: nil)
+ let origin = NSPoint(
+ x: mouseLocation.x - imageSize.width / 2,
+ y: mouseLocation.y - imageSize.height / 2
+ )
+ item.setDraggingFrame(
+ NSRect(origin: origin, size: imageSize),
+ contents: scaledImage
+ )
+ }
+
+ onDragStateChanged?(true)
+ let session = beginDraggingSession(with: [item], event: event, source: self)
+
+ // We need to disable this so that endedAt happens immediately for our
+ // drags outside of any targets.
+ session.animatesToStartingPositionsOnCancelOrFail = false
+ }
+
+ // MARK: NSDraggingSource
+
+ func draggingSession(
+ _ session: NSDraggingSession,
+ sourceOperationMaskFor context: NSDraggingContext
+ ) -> NSDragOperation {
+ return context == .withinApplication ? .move : []
+ }
+
+ func draggingSession(
+ _ session: NSDraggingSession,
+ willBeginAt screenPoint: NSPoint
+ ) {
+ isTracking = true
+
+ // Reset our escape tracking
+ dragCancelledByEscape = false
+ escapeMonitor = NSEvent.addLocalMonitorForEvents(matching: .keyDown) { [weak self] event in
+ if event.keyCode == 53 { // Escape key
+ self?.dragCancelledByEscape = true
+ }
+ return event
+ }
+ }
+
+ func draggingSession(
+ _ session: NSDraggingSession,
+ movedTo screenPoint: NSPoint
+ ) {
+ NSCursor.closedHand.set()
+ }
+
+ func draggingSession(
+ _ session: NSDraggingSession,
+ endedAt screenPoint: NSPoint,
+ operation: NSDragOperation
+ ) {
+ if let escapeMonitor {
+ NSEvent.removeMonitor(escapeMonitor)
+ self.escapeMonitor = nil
+ }
+
+ if operation == [] && !dragCancelledByEscape {
+ let endsInWindow = NSApplication.shared.windows.contains { window in
+ window.isVisible && window.frame.contains(screenPoint)
+ }
+ if !endsInWindow {
+ NotificationCenter.default.post(
+ name: .ghosttySurfaceDragEndedNoTarget,
+ object: surfaceView,
+ userInfo: [Foundation.Notification.Name.ghosttySurfaceDragEndedNoTargetPointKey: screenPoint]
+ )
+ }
+ }
+
+ isTracking = false
+ onDragStateChanged?(false)
+ }
+ }
+}
+
+extension Notification.Name {
+ /// Posted when a surface drag session ends with no operation (the drag was
+ /// released outside a valid drop target) and was not cancelled by the user
+ /// pressing escape. The notification's object is the SurfaceView that was dragged.
+ static let ghosttySurfaceDragEndedNoTarget = Notification.Name("ghosttySurfaceDragEndedNoTarget")
+
+ /// Key for the screen point where the drag ended in the userInfo dictionary.
+ static let ghosttySurfaceDragEndedNoTargetPointKey = "endedAtPoint"
+}
diff --git a/macos/Sources/Ghostty/Surface View/SurfaceGrabHandle.swift b/macos/Sources/Ghostty/Surface View/SurfaceGrabHandle.swift
new file mode 100644
index 000000000..f3ee80874
--- /dev/null
+++ b/macos/Sources/Ghostty/Surface View/SurfaceGrabHandle.swift
@@ -0,0 +1,41 @@
+import AppKit
+import SwiftUI
+
+extension Ghostty {
+ /// A grab handle overlay at the top of the surface for dragging the window.
+ /// Only appears when hovering in the top region of the surface.
+ struct SurfaceGrabHandle: View {
+ private let handleHeight: CGFloat = 10
+
+ let surfaceView: SurfaceView
+
+ @State private var isHovering: Bool = false
+ @State private var isDragging: Bool = false
+
+ var body: some View {
+ VStack(spacing: 0) {
+ Rectangle()
+ .fill(Color.primary.opacity(isHovering || isDragging ? 0.15 : 0))
+ .frame(height: handleHeight)
+ .overlay(alignment: .center) {
+ if isHovering || isDragging {
+ Image(systemName: "ellipsis")
+ .font(.system(size: 14, weight: .semibold))
+ .foregroundColor(.primary.opacity(0.5))
+ }
+ }
+ .contentShape(Rectangle())
+ .overlay {
+ SurfaceDragSource(
+ surfaceView: surfaceView,
+ isDragging: $isDragging,
+ isHovering: $isHovering
+ )
+ }
+
+ Spacer()
+ }
+ .frame(maxWidth: .infinity, maxHeight: .infinity)
+ }
+ }
+}
diff --git a/macos/Sources/Ghostty/SurfaceProgressBar.swift b/macos/Sources/Ghostty/Surface View/SurfaceProgressBar.swift
similarity index 100%
rename from macos/Sources/Ghostty/SurfaceProgressBar.swift
rename to macos/Sources/Ghostty/Surface View/SurfaceProgressBar.swift
diff --git a/macos/Sources/Ghostty/SurfaceScrollView.swift b/macos/Sources/Ghostty/Surface View/SurfaceScrollView.swift
similarity index 94%
rename from macos/Sources/Ghostty/SurfaceScrollView.swift
rename to macos/Sources/Ghostty/Surface View/SurfaceScrollView.swift
index 157136136..b55f2e231 100644
--- a/macos/Sources/Ghostty/SurfaceScrollView.swift
+++ b/macos/Sources/Ghostty/Surface View/SurfaceScrollView.swift
@@ -120,18 +120,20 @@ class SurfaceScrollView: NSView {
self?.handleScrollerStyleChange()
})
- // Listen for frame change events. See the docstring for
- // handleFrameChange for why this is necessary.
- observers.append(NotificationCenter.default.addObserver(
- forName: NSView.frameDidChangeNotification,
- object: nil,
- // Since this observer is used to immediately override the event
- // that produced the notification, we let it run synchronously on
- // the posting thread.
- queue: nil
- ) { [weak self] notification in
- self?.handleFrameChange(notification)
- })
+ // Listen for frame change events on macOS 26.0. See the docstring for
+ // handleFrameChangeForNSScrollPocket for why this is necessary.
+ if #unavailable(macOS 26.1) { if #available(macOS 26.0, *) {
+ observers.append(NotificationCenter.default.addObserver(
+ forName: NSView.frameDidChangeNotification,
+ object: nil,
+ // Since this observer is used to immediately override the event
+ // that produced the notification, we let it run synchronously on
+ // the posting thread.
+ queue: nil
+ ) { [weak self] notification in
+ self?.handleFrameChangeForNSScrollPocket(notification)
+ })
+ }}
// Listen for derived config changes to update scrollbar settings live
surfaceView.$derivedConfig
@@ -328,7 +330,10 @@ class SurfaceScrollView: NSView {
/// and reset their frame to zero.
///
/// See also https://developer.apple.com/forums/thread/798392.
- private func handleFrameChange(_ notification: Notification) {
+ ///
+ /// This bug is only present in macOS 26.0.
+ @available(macOS, introduced: 26.0, obsoleted: 26.1)
+ private func handleFrameChangeForNSScrollPocket(_ notification: Notification) {
guard let window = window as? HiddenTitlebarTerminalWindow else { return }
guard !window.styleMask.contains(.fullScreen) else { return }
guard let view = notification.object as? NSView else { return }
diff --git a/macos/Sources/Ghostty/Surface View/SurfaceView+Image.swift b/macos/Sources/Ghostty/Surface View/SurfaceView+Image.swift
new file mode 100644
index 000000000..11b7b4694
--- /dev/null
+++ b/macos/Sources/Ghostty/Surface View/SurfaceView+Image.swift
@@ -0,0 +1,28 @@
+#if canImport(AppKit)
+import AppKit
+#elseif canImport(UIKit)
+import UIKit
+#endif
+
+extension Ghostty.SurfaceView {
+ #if canImport(AppKit)
+ /// A snapshot image of the current surface view.
+ var asImage: NSImage? {
+ guard let bitmapRep = bitmapImageRepForCachingDisplay(in: bounds) else {
+ return nil
+ }
+ cacheDisplay(in: bounds, to: bitmapRep)
+ let image = NSImage(size: bounds.size)
+ image.addRepresentation(bitmapRep)
+ return image
+ }
+ #elseif canImport(UIKit)
+ /// A snapshot image of the current surface view.
+ var asImage: UIImage? {
+ let renderer = UIGraphicsImageRenderer(bounds: bounds)
+ return renderer.image { _ in
+ drawHierarchy(in: bounds, afterScreenUpdates: true)
+ }
+ }
+ #endif
+}
diff --git a/macos/Sources/Ghostty/Surface View/SurfaceView+Transferable.swift b/macos/Sources/Ghostty/Surface View/SurfaceView+Transferable.swift
new file mode 100644
index 000000000..509713309
--- /dev/null
+++ b/macos/Sources/Ghostty/Surface View/SurfaceView+Transferable.swift
@@ -0,0 +1,58 @@
+#if canImport(AppKit)
+import AppKit
+#endif
+import CoreTransferable
+import UniformTypeIdentifiers
+
+/// Conformance to `Transferable` enables drag-and-drop.
+extension Ghostty.SurfaceView: Transferable {
+ static var transferRepresentation: some TransferRepresentation {
+ DataRepresentation(contentType: .ghosttySurfaceId) { surface in
+ withUnsafeBytes(of: surface.id.uuid) { Data($0) }
+ } importing: { data in
+ guard data.count == 16 else {
+ throw TransferError.invalidData
+ }
+
+ let uuid = data.withUnsafeBytes {
+ $0.load(as: UUID.self)
+ }
+
+ guard let imported = await Self.find(uuid: uuid) else {
+ throw TransferError.invalidData
+ }
+
+ return imported
+ }
+ }
+
+ enum TransferError: Error {
+ case invalidData
+ }
+
+ @MainActor
+ static func find(uuid: UUID) -> Self? {
+ #if canImport(AppKit)
+ guard let del = NSApp.delegate as? Ghostty.Delegate else { return nil }
+ return del.ghosttySurface(id: uuid) as? Self
+ #elseif canImport(UIKit)
+ // We should be able to use UIApplication here.
+ return nil
+ #else
+ return nil
+ #endif
+ }
+}
+
+extension UTType {
+ /// A format that encodes the bare UUID only for the surface. This can be used if you have
+ /// a way to look up a surface by ID.
+ static let ghosttySurfaceId = UTType(exportedAs: "com.mitchellh.ghosttySurfaceId")
+}
+
+#if canImport(AppKit)
+extension NSPasteboard.PasteboardType {
+ /// Pasteboard type for dragging surface IDs.
+ static let ghosttySurfaceId = NSPasteboard.PasteboardType(UTType.ghosttySurfaceId.identifier)
+}
+#endif
diff --git a/macos/Sources/Ghostty/SurfaceView.swift b/macos/Sources/Ghostty/Surface View/SurfaceView.swift
similarity index 79%
rename from macos/Sources/Ghostty/SurfaceView.swift
rename to macos/Sources/Ghostty/Surface View/SurfaceView.swift
index 49c6a4982..a0e735715 100644
--- a/macos/Sources/Ghostty/SurfaceView.swift
+++ b/macos/Sources/Ghostty/Surface View/SurfaceView.swift
@@ -123,31 +123,11 @@ extension Ghostty {
}
}
- // If we are in the middle of a key sequence, then we show a visual element. We only
- // support this on macOS currently although in theory we can support mobile with keyboards!
- if !surfaceView.keySequence.isEmpty {
- let padding: CGFloat = 5
- VStack {
- Spacer()
-
- HStack {
- Text(verbatim: "Pending Key Sequence:")
- ForEach(0.. dragThreshold {
+ position = .bottom
+ }
+ dragOffset = .zero
+ }
+ }
+ )
+ }
+
+ @ViewBuilder
+ private var indicatorContent: some View {
+ HStack(alignment: .center, spacing: 8) {
+ // Key table indicator
+ if !keyTables.isEmpty {
+ HStack(spacing: 5) {
+ Image(systemName: "keyboard.badge.ellipsis")
+ .font(.system(size: 13))
+ .foregroundStyle(.secondary)
+
+ // Show table stack with arrows between them
+ ForEach(Array(keyTables.enumerated()), id: \.offset) { index, table in
+ if index > 0 {
+ Image(systemName: "chevron.right")
+ .font(.system(size: 10, weight: .semibold))
+ .foregroundStyle(.tertiary)
+ }
+ Text(verbatim: table)
+ .font(.system(size: 13, weight: .medium, design: .rounded))
+ }
+ }
+ }
+
+ // Separator when both are active
+ if !keyTables.isEmpty && !keySequence.isEmpty {
+ Divider()
+ .frame(height: 14)
+ }
+
+ // Key sequence indicator
+ if !keySequence.isEmpty {
+ HStack(alignment: .center, spacing: 4) {
+ ForEach(Array(keySequence.enumerated()), id: \.offset) { index, key in
+ KeyCap(key.description)
+ }
+
+ // Animated ellipsis to indicate waiting for next key
+ PendingIndicator(paused: isDragging)
+ }
+ }
+ }
+ .padding(.horizontal, 12)
+ .padding(.vertical, 6)
+ .background {
+ Capsule()
+ .fill(.regularMaterial)
+ .overlay {
+ Capsule()
+ .strokeBorder(Color.primary.opacity(0.15), lineWidth: 1)
+ }
+ .shadow(color: .black.opacity(0.2), radius: 8, y: 2)
+ }
+ .contentShape(Capsule())
+ .backport.pointerStyle(.link)
+ .popover(isPresented: $isShowingPopover, arrowEdge: position.popoverEdge) {
+ VStack(alignment: .leading, spacing: 8) {
+ if !keyTables.isEmpty {
+ VStack(alignment: .leading, spacing: 4) {
+ Label("Key Table", systemImage: "keyboard.badge.ellipsis")
+ .font(.headline)
+ Text("A key table is a named set of keybindings, activated by some other key. Keys are interpreted using this table until it is deactivated.")
+ .font(.subheadline)
+ .foregroundStyle(.secondary)
+ }
+ }
+
+ if !keyTables.isEmpty && !keySequence.isEmpty {
+ Divider()
+ }
+
+ if !keySequence.isEmpty {
+ VStack(alignment: .leading, spacing: 4) {
+ Label("Key Sequence", systemImage: "character.cursor.ibeam")
+ .font(.headline)
+ Text("A key sequence is a series of key presses that trigger an action. A pending key sequence is currently active.")
+ .font(.subheadline)
+ .foregroundStyle(.secondary)
+ }
+ }
+ }
+ .padding()
+ .frame(maxWidth: 400)
+ .fixedSize(horizontal: false, vertical: true)
+ }
+ .onTapGesture {
+ isShowingPopover.toggle()
+ }
+ }
+
+ /// A small keycap-style view for displaying keyboard shortcuts
+ struct KeyCap: View {
+ let text: String
+
+ init(_ text: String) {
+ self.text = text
+ }
+
+ var body: some View {
+ Text(verbatim: text)
+ .font(.system(size: 12, weight: .medium, design: .rounded))
+ .padding(.horizontal, 5)
+ .padding(.vertical, 2)
+ .background(
+ RoundedRectangle(cornerRadius: 4)
+ .fill(Color(NSColor.controlBackgroundColor))
+ .shadow(color: .black.opacity(0.12), radius: 0.5, y: 0.5)
+ )
+ .overlay(
+ RoundedRectangle(cornerRadius: 4)
+ .strokeBorder(Color.primary.opacity(0.15), lineWidth: 0.5)
+ )
+ }
+ }
+
+ /// Animated dots to indicate waiting for the next key
+ struct PendingIndicator: View {
+ @State private var animationPhase: Double = 0
+ let paused: Bool
+
+ var body: some View {
+ TimelineView(.animation(paused: paused)) { context in
+ HStack(spacing: 2) {
+ ForEach(0..<3, id: \.self) { index in
+ Circle()
+ .fill(Color.secondary)
+ .frame(width: 4, height: 4)
+ .opacity(dotOpacity(for: index))
+ }
+ }
+ .onChange(of: context.date.timeIntervalSinceReferenceDate) { newValue in
+ animationPhase = newValue
+ }
+ }
+ }
+
+ private func dotOpacity(for index: Int) -> Double {
+ let phase = animationPhase
+ let offset = Double(index) / 3.0
+ let wave = sin((phase + offset) * .pi * 2)
+ return 0.3 + 0.7 * ((wave + 1) / 2)
+ }
+ }
+ }
+#endif
+
/// Visual overlay that shows a border around the edges when the bell rings with border feature enabled.
struct BellBorderOverlay: View {
let bell: Bool
diff --git a/macos/Sources/Ghostty/SurfaceView_AppKit.swift b/macos/Sources/Ghostty/Surface View/SurfaceView_AppKit.swift
similarity index 95%
rename from macos/Sources/Ghostty/SurfaceView_AppKit.swift
rename to macos/Sources/Ghostty/Surface View/SurfaceView_AppKit.swift
index 88a0bb6e8..0ddfe57b8 100644
--- a/macos/Sources/Ghostty/SurfaceView_AppKit.swift
+++ b/macos/Sources/Ghostty/Surface View/SurfaceView_AppKit.swift
@@ -9,7 +9,7 @@ extension Ghostty {
/// The NSView implementation for a terminal surface.
class SurfaceView: OSView, ObservableObject, Codable, Identifiable {
typealias ID = UUID
-
+
/// Unique ID per surface
let id: UUID
@@ -44,14 +44,14 @@ extension Ghostty {
// The hovered URL string
@Published var hoverUrl: String? = nil
-
+
// The progress report (if any)
@Published var progressReport: Action.ProgressReport? = nil {
didSet {
// Cancel any existing timer
progressReportTimer?.invalidate()
progressReportTimer = nil
-
+
// If we have a new progress report, start a timer to remove it after 15 seconds
if progressReport != nil {
progressReportTimer = Timer.scheduledTimer(withTimeInterval: 15.0, repeats: false) { [weak self] _ in
@@ -65,6 +65,9 @@ extension Ghostty {
// The currently active key sequence. The sequence is not active if this is empty.
@Published var keySequence: [KeyboardShortcut] = []
+ // The currently active key tables. Empty if no tables are active.
+ @Published var keyTables: [String] = []
+
// The current search state. When non-nil, the search overlay should be shown.
@Published var searchState: SearchState? = nil {
didSet {
@@ -98,7 +101,7 @@ extension Ghostty {
}
}
}
-
+
// Cancellable for search state needle changes
private var searchNeedleCancellable: AnyCancellable?
@@ -216,7 +219,7 @@ extension Ghostty {
// A timer to fallback to ghost emoji if no title is set within the grace period
private var titleFallbackTimer: Timer?
-
+
// Timer to remove progress report after 15 seconds
private var progressReportTimer: Timer?
@@ -324,6 +327,11 @@ extension Ghostty {
selector: #selector(ghosttyDidEndKeySequence),
name: Ghostty.Notification.didEndKeySequence,
object: self)
+ center.addObserver(
+ self,
+ selector: #selector(ghosttyDidChangeKeyTable),
+ name: Ghostty.Notification.didChangeKeyTable,
+ object: self)
center.addObserver(
self,
selector: #selector(ghosttyConfigDidChange(_:)),
@@ -410,7 +418,7 @@ extension Ghostty {
// Remove any notifications associated with this surface
let identifiers = Array(self.notificationIdentifiers)
UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: identifiers)
-
+
// Cancel progress report timer
progressReportTimer?.invalidate()
}
@@ -547,16 +555,16 @@ extension Ghostty {
// Add buttons
alert.addButton(withTitle: "OK")
alert.addButton(withTitle: "Cancel")
-
+
// Make the text field the first responder so it gets focus
alert.window.initialFirstResponder = textField
-
+
let completionHandler: (NSApplication.ModalResponse) -> Void = { [weak self] response in
guard let self else { return }
-
+
// Check if the user clicked "OK"
guard response == .alertFirstButtonReturn else { return }
-
+
// Get the input text
let newTitle = textField.stringValue
if newTitle.isEmpty {
@@ -680,6 +688,22 @@ extension Ghostty {
}
}
+ @objc private func ghosttyDidChangeKeyTable(notification: SwiftUI.Notification) {
+ guard let action = notification.userInfo?[Ghostty.Notification.KeyTableKey] as? Ghostty.Action.KeyTable else { return }
+
+ DispatchQueue.main.async { [weak self] in
+ guard let self else { return }
+ switch action {
+ case .activate(let name):
+ self.keyTables.append(name)
+ case .deactivate:
+ _ = self.keyTables.popLast()
+ case .deactivateAll:
+ self.keyTables.removeAll()
+ }
+ }
+ }
+
@objc private func ghosttyConfigDidChange(_ notification: SwiftUI.Notification) {
// Get our managed configuration object out
guard let config = notification.userInfo?[
@@ -836,16 +860,16 @@ extension Ghostty {
override func otherMouseDown(with event: NSEvent) {
guard let surface = self.surface else { return }
- guard event.buttonNumber == 2 else { return }
let mods = Ghostty.ghosttyMods(event.modifierFlags)
- ghostty_surface_mouse_button(surface, GHOSTTY_MOUSE_PRESS, GHOSTTY_MOUSE_MIDDLE, mods)
+ let button = Ghostty.Input.MouseButton(fromNSEventButtonNumber: event.buttonNumber)
+ ghostty_surface_mouse_button(surface, GHOSTTY_MOUSE_PRESS, button.cMouseButton, mods)
}
override func otherMouseUp(with event: NSEvent) {
guard let surface = self.surface else { return }
- guard event.buttonNumber == 2 else { return }
let mods = Ghostty.ghosttyMods(event.modifierFlags)
- ghostty_surface_mouse_button(surface, GHOSTTY_MOUSE_RELEASE, GHOSTTY_MOUSE_MIDDLE, mods)
+ let button = Ghostty.Input.MouseButton(fromNSEventButtonNumber: event.buttonNumber)
+ ghostty_surface_mouse_button(surface, GHOSTTY_MOUSE_RELEASE, button.cMouseButton, mods)
}
@@ -964,7 +988,7 @@ extension Ghostty {
var x = event.scrollingDeltaX
var y = event.scrollingDeltaY
let precision = event.hasPreciseScrollingDeltas
-
+
if precision {
// We do a 2x speed multiplier. This is subjective, it "feels" better to me.
x *= 2;
@@ -1157,17 +1181,10 @@ extension Ghostty {
/// Special case handling for some control keys
override func performKeyEquivalent(with event: NSEvent) -> Bool {
- switch (event.type) {
- case .keyDown:
- // Continue, we care about key down events
- break
-
- default:
- // Any other key event we don't care about. I don't think its even
- // possible to receive any other event type.
- return false
- }
-
+ // We only care about key down events. It might not even be possible
+ // to receive any other event type here.
+ guard event.type == .keyDown else { return false }
+
// Only process events if we're focused. Some key events like C-/ macOS
// appears to send to the first view in the hierarchy rather than the
// the first responder (I don't know why). This prevents us from handling it.
@@ -1177,18 +1194,35 @@ extension Ghostty {
if (!focused) {
return false
}
-
- // If this event as-is would result in a key binding then we send it.
- if let surface {
+
+ // Get information about if this is a binding.
+ let bindingFlags = surfaceModel.flatMap { surface in
var ghosttyEvent = event.ghosttyKeyEvent(GHOSTTY_ACTION_PRESS)
- let match = (event.characters ?? "").withCString { ptr in
+ return (event.characters ?? "").withCString { ptr in
ghosttyEvent.text = ptr
- return ghostty_surface_key_is_binding(surface, ghosttyEvent)
+ return surface.keyIsBinding(ghosttyEvent)
}
- if match {
- self.keyDown(with: event)
- return true
+ }
+
+ // If this is a binding then we want to perform it.
+ if let bindingFlags {
+ // Attempt to trigger a menu item for this key binding. We only do this if:
+ // - We're not in a key sequence or table (those are separate bindings)
+ // - The binding is NOT `all` (menu uses FirstResponder chain)
+ // - The binding is NOT `performable` (menu will always consume)
+ // - The binding is `consumed` (unconsumed bindings should pass through
+ // to the terminal, so we must not intercept them for the menu)
+ if keySequence.isEmpty,
+ keyTables.isEmpty,
+ bindingFlags.isDisjoint(with: [.all, .performable]),
+ bindingFlags.contains(.consumed) {
+ if let menu = NSApp.mainMenu, menu.performKeyEquivalent(with: event) {
+ return true
+ }
}
+
+ self.keyDown(with: event)
+ return true
}
let equivalent: String
@@ -1326,7 +1360,7 @@ extension Ghostty {
var key_ev = event.ghosttyKeyEvent(action, translationMods: translationEvent?.modifierFlags)
key_ev.composing = composing
-
+
// For text, we only encode UTF8 if we don't have a single control
// character. Control characters are encoded by Ghostty itself.
// Without this, `ctrl+enter` does the wrong thing.
@@ -1485,7 +1519,7 @@ extension Ghostty {
AppDelegate.logger.warning("action failed action=\(action)")
}
}
-
+
@IBAction func find(_ sender: Any?) {
guard let surface = self.surface else { return }
let action = "start_search"
@@ -1493,7 +1527,23 @@ extension Ghostty {
AppDelegate.logger.warning("action failed action=\(action)")
}
}
-
+
+ @IBAction func selectionForFind(_ sender: Any?) {
+ guard let surface = self.surface else { return }
+ let action = "search_selection"
+ if (!ghostty_surface_binding_action(surface, action, UInt(action.lengthOfBytes(using: .utf8)))) {
+ AppDelegate.logger.warning("action failed action=\(action)")
+ }
+ }
+
+ @IBAction func scrollToSelection(_ sender: Any?) {
+ guard let surface = self.surface else { return }
+ let action = "scroll_to_selection"
+ if (!ghostty_surface_binding_action(surface, action, UInt(action.lengthOfBytes(using: .utf8)))) {
+ AppDelegate.logger.warning("action failed action=\(action)")
+ }
+ }
+
@IBAction func findNext(_ sender: Any?) {
guard let surface = self.surface else { return }
let action = "search:next"
@@ -1509,7 +1559,7 @@ extension Ghostty {
AppDelegate.logger.warning("action failed action=\(action)")
}
}
-
+
@IBAction func findHide(_ sender: Any?) {
guard let surface = self.surface else { return }
let action = "end_search"
@@ -1569,7 +1619,7 @@ extension Ghostty {
AppDelegate.logger.warning("action failed action=\(action)")
}
}
-
+
@IBAction func changeTitle(_ sender: Any) {
promptTitle()
}
@@ -1630,6 +1680,7 @@ extension Ghostty {
struct DerivedConfig {
let backgroundColor: Color
let backgroundOpacity: Double
+ let backgroundBlur: Ghostty.Config.BackgroundBlur
let macosWindowShadow: Bool
let windowTitleFontFamily: String?
let windowAppearance: NSAppearance?
@@ -1638,6 +1689,7 @@ extension Ghostty {
init() {
self.backgroundColor = Color(NSColor.windowBackgroundColor)
self.backgroundOpacity = 1
+ self.backgroundBlur = .disabled
self.macosWindowShadow = true
self.windowTitleFontFamily = nil
self.windowAppearance = nil
@@ -1647,6 +1699,7 @@ extension Ghostty {
init(_ config: Ghostty.Config) {
self.backgroundColor = config.backgroundColor
self.backgroundOpacity = config.backgroundOpacity
+ self.backgroundBlur = config.backgroundBlur
self.macosWindowShadow = config.macosWindowShadow
self.windowTitleFontFamily = config.windowTitleFontFamily
self.windowAppearance = .init(ghosttyConfig: config)
@@ -1679,7 +1732,7 @@ extension Ghostty {
let isUserSetTitle = try container.decodeIfPresent(Bool.self, forKey: .isUserSetTitle) ?? false
self.init(app, baseConfig: config, uuid: uuid)
-
+
// Restore the saved title after initialization
if let title = savedTitle {
self.title = title
@@ -1896,6 +1949,17 @@ extension Ghostty.SurfaceView: NSTextInputClient {
return
}
+ guard let surfaceModel else { return }
+ // Process MacOS native scroll events
+ switch selector {
+ case #selector(moveToBeginningOfDocument(_:)):
+ _ = surfaceModel.perform(action: "scroll_to_top")
+ case #selector(moveToEndOfDocument(_:)):
+ _ = surfaceModel.perform(action: "scroll_to_bottom")
+ default:
+ break
+ }
+
print("SEL: \(selector)")
}
@@ -1936,14 +2000,14 @@ extension Ghostty.SurfaceView: NSServicesMenuRequestor {
// The "COMBINATION" bit is key: we might get sent a string (we can handle that)
// but get requested an image (we can't handle that at the time of writing this),
// so we must bubble up.
-
+
// Types we can receive
let receivable: [NSPasteboard.PasteboardType] = [.string, .init("public.utf8-plain-text")]
-
+
// Types that we can send. Currently the same as receivable but I'm separating
// this out so we can modify this in the future.
let sendable: [NSPasteboard.PasteboardType] = receivable
-
+
// The sendable types that require a selection (currently all)
let sendableRequiresSelection = sendable
@@ -1960,7 +2024,7 @@ extension Ghostty.SurfaceView: NSServicesMenuRequestor {
return super.validRequestor(forSendType: sendType, returnType: returnType)
}
}
-
+
return self
}
@@ -2006,7 +2070,7 @@ extension Ghostty.SurfaceView: NSMenuItemValidation {
let pb = NSPasteboard.ghosttySelection
guard let str = pb.getOpinionatedStringContents() else { return false }
return !str.isEmpty
-
+
case #selector(findHide):
return searchState != nil
@@ -2111,7 +2175,7 @@ extension Ghostty.SurfaceView {
override func accessibilitySelectedTextRange() -> NSRange {
return selectedRange()
}
-
+
/// Returns the currently selected text as a string.
/// This allows assistive technologies to read the selected content.
override func accessibilitySelectedText() -> String? {
@@ -2125,21 +2189,21 @@ extension Ghostty.SurfaceView {
let str = String(cString: text.text)
return str.isEmpty ? nil : str
}
-
+
/// Returns the number of characters in the terminal content.
/// This helps assistive technologies understand the size of the content.
override func accessibilityNumberOfCharacters() -> Int {
let content = cachedScreenContents.get()
return content.count
}
-
+
/// Returns the visible character range for the terminal.
/// For terminals, we typically show all content as visible.
override func accessibilityVisibleCharacterRange() -> NSRange {
let content = cachedScreenContents.get()
return NSRange(location: 0, length: content.count)
}
-
+
/// Returns the line number for a given character index.
/// This helps assistive technologies navigate by line.
override func accessibilityLine(for index: Int) -> Int {
@@ -2147,7 +2211,7 @@ extension Ghostty.SurfaceView {
let substring = String(content.prefix(index))
return substring.components(separatedBy: .newlines).count - 1
}
-
+
/// Returns a substring for the given range.
/// This allows assistive technologies to read specific portions of the content.
override func accessibilityString(for range: NSRange) -> String? {
@@ -2155,7 +2219,7 @@ extension Ghostty.SurfaceView {
guard let swiftRange = Range(range, in: content) else { return nil }
return String(content[swiftRange])
}
-
+
/// Returns an attributed string for the given range.
///
/// Note: right now this only applies font information. One day it'd be nice to extend
@@ -2166,9 +2230,9 @@ extension Ghostty.SurfaceView {
override func accessibilityAttributedString(for range: NSRange) -> NSAttributedString? {
guard let surface = self.surface else { return nil }
guard let plainString = accessibilityString(for: range) else { return nil }
-
+
var attributes: [NSAttributedString.Key: Any] = [:]
-
+
// Try to get the font from the surface
if let fontRaw = ghostty_surface_quicklook_font(surface) {
let font = Unmanaged.fromOpaque(fontRaw)
@@ -2178,6 +2242,7 @@ extension Ghostty.SurfaceView {
return NSAttributedString(string: plainString, attributes: attributes)
}
+
}
/// Caches a value for some period of time, evicting it automatically when that time expires.
diff --git a/macos/Sources/Ghostty/SurfaceView_UIKit.swift b/macos/Sources/Ghostty/Surface View/SurfaceView_UIKit.swift
similarity index 95%
rename from macos/Sources/Ghostty/SurfaceView_UIKit.swift
rename to macos/Sources/Ghostty/Surface View/SurfaceView_UIKit.swift
index b2e429455..f9baf56c9 100644
--- a/macos/Sources/Ghostty/SurfaceView_UIKit.swift
+++ b/macos/Sources/Ghostty/Surface View/SurfaceView_UIKit.swift
@@ -4,8 +4,10 @@ import GhosttyKit
extension Ghostty {
/// The UIView implementation for a terminal surface.
class SurfaceView: UIView, ObservableObject {
+ typealias ID = UUID
+
/// Unique ID per surface
- let uuid: UUID
+ let id: UUID
// The current title of the surface as defined by the pty. This can be
// changed with escape codes. This is public because the callbacks go
@@ -43,7 +45,10 @@ extension Ghostty {
// The current search state. When non-nil, the search overlay should be shown.
@Published var searchState: SearchState? = nil
-
+
+ // The currently active key tables. Empty if no tables are active.
+ @Published var keyTables: [String] = []
+
/// True when the surface is in readonly mode.
@Published private(set) var readonly: Bool = false
@@ -60,7 +65,7 @@ extension Ghostty {
private(set) var surface: ghostty_surface_t?
init(_ app: ghostty_app_t, baseConfig: SurfaceConfiguration? = nil, uuid: UUID? = nil) {
- self.uuid = uuid ?? .init()
+ self.id = uuid ?? .init()
// Initialize with some default frame size. The important thing is that this
// is non-zero so that our layer bounds are non-zero so that our renderer
diff --git a/macos/Sources/Helpers/DraggableWindowView.swift b/macos/Sources/Helpers/DraggableWindowView.swift
deleted file mode 100644
index 8d88e2f66..000000000
--- a/macos/Sources/Helpers/DraggableWindowView.swift
+++ /dev/null
@@ -1,19 +0,0 @@
-import Cocoa
-import SwiftUI
-
-struct DraggableWindowView: NSViewRepresentable {
- func makeNSView(context: Context) -> DraggableWindowNSView {
- return DraggableWindowNSView()
- }
-
- func updateNSView(_ nsView: DraggableWindowNSView, context: Context) {
- // No need to update anything here
- }
-}
-
-class DraggableWindowNSView: NSView {
- override func mouseDown(with event: NSEvent) {
- guard let window = self.window else { return }
- window.performDrag(with: event)
- }
-}
diff --git a/macos/Sources/Helpers/Extensions/NSWindow+Extension.swift b/macos/Sources/Helpers/Extensions/NSWindow+Extension.swift
index d834f5e63..5d1831f26 100644
--- a/macos/Sources/Helpers/Extensions/NSWindow+Extension.swift
+++ b/macos/Sources/Helpers/Extensions/NSWindow+Extension.swift
@@ -10,25 +10,73 @@ extension NSWindow {
return CGWindowID(windowNumber)
}
- /// True if this is the first window in the tab group.
- var isFirstWindowInTabGroup: Bool {
- guard let firstWindow = tabGroup?.windows.first else { return true }
- return firstWindow === self
- }
-
- /// Adjusts the window origin if necessary to ensure the window remains visible on screen.
+ /// Adjusts the window frame if necessary to ensure the window remains visible on screen.
+ /// This constrains both the size (to not exceed the screen) and the origin (to keep the window on screen).
func constrainToScreen() {
guard let screen = screen ?? NSScreen.main else { return }
let visibleFrame = screen.visibleFrame
var windowFrame = frame
+ windowFrame.size.width = min(windowFrame.size.width, visibleFrame.size.width)
+ windowFrame.size.height = min(windowFrame.size.height, visibleFrame.size.height)
+
windowFrame.origin.x = max(visibleFrame.minX,
min(windowFrame.origin.x, visibleFrame.maxX - windowFrame.width))
windowFrame.origin.y = max(visibleFrame.minY,
min(windowFrame.origin.y, visibleFrame.maxY - windowFrame.height))
- if windowFrame.origin != frame.origin {
- setFrameOrigin(windowFrame.origin)
+ if windowFrame != frame {
+ setFrame(windowFrame, display: true)
}
}
}
+
+// MARK: Native Tabbing
+
+extension NSWindow {
+ /// True if this is the first window in the tab group.
+ var isFirstWindowInTabGroup: Bool {
+ guard let firstWindow = tabGroup?.windows.first else { return true }
+ return firstWindow === self
+ }
+}
+
+/// Native tabbing private API usage. :(
+extension NSWindow {
+ var titlebarView: NSView? {
+ // In normal window, `NSTabBar` typically appears as a subview of `NSTitlebarView` within `NSThemeFrame`.
+ // In fullscreen, the system creates a dedicated fullscreen window and the view hierarchy changes;
+ // in that case, the `titlebarView` is only accessible via a reference on `NSThemeFrame`.
+ // ref: https://github.com/mozilla-firefox/firefox/blob/054e2b072785984455b3b59acad9444ba1eeffb4/widget/cocoa/nsCocoaWindow.mm#L7205
+ guard let themeFrameView = contentView?.rootView else { return nil }
+ guard themeFrameView.responds(to: Selector(("titlebarView"))) else { return nil }
+ return themeFrameView.value(forKey: "titlebarView") as? NSView
+ }
+
+ /// Returns the [private] NSTabBar view, if it exists.
+ var tabBarView: NSView? {
+ titlebarView?.firstDescendant(withClassName: "NSTabBar")
+ }
+
+ /// Returns the index of the tab button at the given screen point, if any.
+ func tabIndex(atScreenPoint screenPoint: NSPoint) -> Int? {
+ guard let tabBarView else { return nil }
+ let locationInWindow = convertPoint(fromScreen: screenPoint)
+ let locationInTabBar = tabBarView.convert(locationInWindow, from: nil)
+ guard tabBarView.bounds.contains(locationInTabBar) else { return nil }
+
+ // Find all tab buttons and sort by x position to get visual order.
+ // The view hierarchy order doesn't match the visual tab order.
+ let tabItemViews = tabBarView.descendants(withClassName: "NSTabButton")
+ .sorted { $0.frame.origin.x < $1.frame.origin.x }
+
+ for (index, tabItemView) in tabItemViews.enumerated() {
+ let locationInTab = tabItemView.convert(locationInWindow, from: nil)
+ if tabItemView.bounds.contains(locationInTab) {
+ return index
+ }
+ }
+
+ return nil
+ }
+}
diff --git a/macos/Sources/Helpers/Extensions/Transferable+Extension.swift b/macos/Sources/Helpers/Extensions/Transferable+Extension.swift
new file mode 100644
index 000000000..3bcc9057f
--- /dev/null
+++ b/macos/Sources/Helpers/Extensions/Transferable+Extension.swift
@@ -0,0 +1,58 @@
+import AppKit
+import CoreTransferable
+import UniformTypeIdentifiers
+
+extension Transferable {
+ /// Converts this Transferable to an NSPasteboardItem with lazy data loading.
+ /// Data is only fetched when the pasteboard consumer requests it. This allows
+ /// bridging a Transferable to NSDraggingSource.
+ func pasteboardItem() -> NSPasteboardItem? {
+ let itemProvider = NSItemProvider()
+ itemProvider.register(self)
+
+ let types = itemProvider.registeredTypeIdentifiers.compactMap { UTType($0) }
+ guard !types.isEmpty else { return nil }
+
+ let item = NSPasteboardItem()
+ let dataProvider = TransferableDataProvider(itemProvider: itemProvider)
+ let pasteboardTypes = types.map { NSPasteboard.PasteboardType($0.identifier) }
+ item.setDataProvider(dataProvider, forTypes: pasteboardTypes)
+
+ return item
+ }
+}
+
+private final class TransferableDataProvider: NSObject, NSPasteboardItemDataProvider {
+ private let itemProvider: NSItemProvider
+
+ init(itemProvider: NSItemProvider) {
+ self.itemProvider = itemProvider
+ super.init()
+ }
+
+ func pasteboard(
+ _ pasteboard: NSPasteboard?,
+ item: NSPasteboardItem,
+ provideDataForType type: NSPasteboard.PasteboardType
+ ) {
+ // NSPasteboardItemDataProvider requires synchronous data return, but
+ // NSItemProvider.loadDataRepresentation is async. We use a semaphore
+ // to block until the async load completes. This is safe because AppKit
+ // calls this method on a background thread during drag operations.
+ let semaphore = DispatchSemaphore(value: 0)
+
+ var result: Data?
+ itemProvider.loadDataRepresentation(forTypeIdentifier: type.rawValue) { data, _ in
+ result = data
+ semaphore.signal()
+ }
+
+ // Wait for the data to load
+ semaphore.wait()
+
+ // Set it. I honestly don't know what happens here if this fails.
+ if let data = result {
+ item.setData(data, forType: type)
+ }
+ }
+}
diff --git a/macos/Sources/Helpers/Fullscreen.swift b/macos/Sources/Helpers/Fullscreen.swift
index 78c967661..8ab476267 100644
--- a/macos/Sources/Helpers/Fullscreen.swift
+++ b/macos/Sources/Helpers/Fullscreen.swift
@@ -130,7 +130,7 @@ class NativeFullscreen: FullscreenBase, FullscreenStyle {
class NonNativeFullscreen: FullscreenBase, FullscreenStyle {
var fullscreenMode: FullscreenMode { .nonNative }
-
+
// Non-native fullscreen never supports tabs because tabs require
// the "titled" style and we don't have it for non-native fullscreen.
var supportsTabs: Bool { false }
@@ -223,7 +223,7 @@ class NonNativeFullscreen: FullscreenBase, FullscreenStyle {
// Being untitled let's our content take up the full frame.
window.styleMask.remove(.titled)
- // We dont' want the non-native fullscreen window to be resizable
+ // We don't want the non-native fullscreen window to be resizable
// from the edges.
window.styleMask.remove(.resizable)
@@ -277,7 +277,7 @@ class NonNativeFullscreen: FullscreenBase, FullscreenStyle {
if let window = window as? TerminalWindow, window.isTabBar(c) {
continue
}
-
+
if window.titlebarAccessoryViewControllers.firstIndex(of: c) == nil {
window.addTitlebarAccessoryViewController(c)
}
@@ -286,7 +286,7 @@ class NonNativeFullscreen: FullscreenBase, FullscreenStyle {
// Removing "titled" also clears our toolbar
window.toolbar = savedState.toolbar
window.toolbarStyle = savedState.toolbarStyle
-
+
// If the window was previously in a tab group that isn't empty now,
// we re-add it. We have to do this because our process of doing non-native
// fullscreen removes the window from the tab group.
@@ -412,7 +412,7 @@ class NonNativeFullscreen: FullscreenBase, FullscreenStyle {
self.toolbar = window.toolbar
self.toolbarStyle = window.toolbarStyle
self.dock = window.screen?.hasDock ?? false
-
+
self.titlebarAccessoryViewControllers = if (window.hasTitleBar) {
// Accessing titlebarAccessoryViewControllers without a titlebar triggers a crash.
window.titlebarAccessoryViewControllers
diff --git a/macos/Tests/Helpers/TransferablePasteboardTests.swift b/macos/Tests/Helpers/TransferablePasteboardTests.swift
new file mode 100644
index 000000000..055dd5785
--- /dev/null
+++ b/macos/Tests/Helpers/TransferablePasteboardTests.swift
@@ -0,0 +1,124 @@
+import Testing
+import AppKit
+import CoreTransferable
+import UniformTypeIdentifiers
+@testable import Ghostty
+
+struct TransferablePasteboardTests {
+ // MARK: - Test Helpers
+
+ /// A simple Transferable type for testing pasteboard conversion.
+ private struct DummyTransferable: Transferable, Equatable {
+ let payload: String
+
+ static var transferRepresentation: some TransferRepresentation {
+ DataRepresentation(contentType: .utf8PlainText) { value in
+ value.payload.data(using: .utf8)!
+ } importing: { data in
+ let string = String(data: data, encoding: .utf8)!
+ return DummyTransferable(payload: string)
+ }
+ }
+ }
+
+ /// A Transferable type that registers multiple content types.
+ private struct MultiTypeTransferable: Transferable {
+ let text: String
+
+ static var transferRepresentation: some TransferRepresentation {
+ DataRepresentation(contentType: .utf8PlainText) { value in
+ value.text.data(using: .utf8)!
+ } importing: { data in
+ MultiTypeTransferable(text: String(data: data, encoding: .utf8)!)
+ }
+ DataRepresentation(contentType: .plainText) { value in
+ value.text.data(using: .utf8)!
+ } importing: { data in
+ MultiTypeTransferable(text: String(data: data, encoding: .utf8)!)
+ }
+ }
+ }
+
+ // MARK: - Basic Functionality
+
+ @Test func pasteboardItemIsCreated() {
+ let transferable = DummyTransferable(payload: "hello")
+ let item = transferable.pasteboardItem()
+ #expect(item != nil)
+ }
+
+ @Test func pasteboardItemContainsExpectedType() {
+ let transferable = DummyTransferable(payload: "hello")
+ guard let item = transferable.pasteboardItem() else {
+ Issue.record("Expected pasteboard item to be created")
+ return
+ }
+
+ let expectedType = NSPasteboard.PasteboardType(UTType.utf8PlainText.identifier)
+ #expect(item.types.contains(expectedType))
+ }
+
+ @Test func pasteboardItemProvidesCorrectData() {
+ let transferable = DummyTransferable(payload: "test data")
+ guard let item = transferable.pasteboardItem() else {
+ Issue.record("Expected pasteboard item to be created")
+ return
+ }
+
+ let pasteboardType = NSPasteboard.PasteboardType(UTType.utf8PlainText.identifier)
+
+ // Write to a pasteboard to trigger data provider
+ let pasteboard = NSPasteboard(name: .init("test-\(UUID().uuidString)"))
+ pasteboard.clearContents()
+ pasteboard.writeObjects([item])
+
+ // Read back the data
+ guard let data = pasteboard.data(forType: pasteboardType) else {
+ Issue.record("Expected data to be available on pasteboard")
+ return
+ }
+
+ let string = String(data: data, encoding: .utf8)
+ #expect(string == "test data")
+ }
+
+ // MARK: - Multiple Content Types
+
+ @Test func multipleTypesAreRegistered() {
+ let transferable = MultiTypeTransferable(text: "multi")
+ guard let item = transferable.pasteboardItem() else {
+ Issue.record("Expected pasteboard item to be created")
+ return
+ }
+
+ let utf8Type = NSPasteboard.PasteboardType(UTType.utf8PlainText.identifier)
+ let plainType = NSPasteboard.PasteboardType(UTType.plainText.identifier)
+
+ #expect(item.types.contains(utf8Type))
+ #expect(item.types.contains(plainType))
+ }
+
+ @Test func multipleTypesProvideCorrectData() {
+ let transferable = MultiTypeTransferable(text: "shared content")
+ guard let item = transferable.pasteboardItem() else {
+ Issue.record("Expected pasteboard item to be created")
+ return
+ }
+
+ let pasteboard = NSPasteboard(name: .init("test-\(UUID().uuidString)"))
+ pasteboard.clearContents()
+ pasteboard.writeObjects([item])
+
+ // Both types should provide the same content
+ let utf8Type = NSPasteboard.PasteboardType(UTType.utf8PlainText.identifier)
+ let plainType = NSPasteboard.PasteboardType(UTType.plainText.identifier)
+
+ if let utf8Data = pasteboard.data(forType: utf8Type) {
+ #expect(String(data: utf8Data, encoding: .utf8) == "shared content")
+ }
+
+ if let plainData = pasteboard.data(forType: plainType) {
+ #expect(String(data: plainData, encoding: .utf8) == "shared content")
+ }
+ }
+}
diff --git a/macos/Tests/Splits/TerminalSplitDropZoneTests.swift b/macos/Tests/Splits/TerminalSplitDropZoneTests.swift
new file mode 100644
index 000000000..5c956fcc8
--- /dev/null
+++ b/macos/Tests/Splits/TerminalSplitDropZoneTests.swift
@@ -0,0 +1,128 @@
+import Testing
+import Foundation
+@testable import Ghostty
+
+struct TerminalSplitDropZoneTests {
+ private let standardSize = CGSize(width: 100, height: 100)
+
+ // MARK: - Basic Edge Detection
+
+ @Test func topEdge() {
+ let zone = TerminalSplitDropZone.calculate(at: CGPoint(x: 50, y: 5), in: standardSize)
+ #expect(zone == .top)
+ }
+
+ @Test func bottomEdge() {
+ let zone = TerminalSplitDropZone.calculate(at: CGPoint(x: 50, y: 95), in: standardSize)
+ #expect(zone == .bottom)
+ }
+
+ @Test func leftEdge() {
+ let zone = TerminalSplitDropZone.calculate(at: CGPoint(x: 5, y: 50), in: standardSize)
+ #expect(zone == .left)
+ }
+
+ @Test func rightEdge() {
+ let zone = TerminalSplitDropZone.calculate(at: CGPoint(x: 95, y: 50), in: standardSize)
+ #expect(zone == .right)
+ }
+
+ // MARK: - Corner Tie-Breaking
+ // When distances are equal, the check order determines the result:
+ // left -> right -> top -> bottom
+
+ @Test func topLeftCornerSelectsLeft() {
+ let zone = TerminalSplitDropZone.calculate(at: CGPoint(x: 0, y: 0), in: standardSize)
+ #expect(zone == .left)
+ }
+
+ @Test func topRightCornerSelectsRight() {
+ let zone = TerminalSplitDropZone.calculate(at: CGPoint(x: 100, y: 0), in: standardSize)
+ #expect(zone == .right)
+ }
+
+ @Test func bottomLeftCornerSelectsLeft() {
+ let zone = TerminalSplitDropZone.calculate(at: CGPoint(x: 0, y: 100), in: standardSize)
+ #expect(zone == .left)
+ }
+
+ @Test func bottomRightCornerSelectsRight() {
+ let zone = TerminalSplitDropZone.calculate(at: CGPoint(x: 100, y: 100), in: standardSize)
+ #expect(zone == .right)
+ }
+
+ // MARK: - Center Point (All Distances Equal)
+
+ @Test func centerSelectsLeft() {
+ let zone = TerminalSplitDropZone.calculate(at: CGPoint(x: 50, y: 50), in: standardSize)
+ #expect(zone == .left)
+ }
+
+ // MARK: - Non-Square Aspect Ratio
+
+ @Test func rectangularViewTopEdge() {
+ let size = CGSize(width: 200, height: 100)
+ let zone = TerminalSplitDropZone.calculate(at: CGPoint(x: 100, y: 10), in: size)
+ #expect(zone == .top)
+ }
+
+ @Test func rectangularViewLeftEdge() {
+ let size = CGSize(width: 200, height: 100)
+ let zone = TerminalSplitDropZone.calculate(at: CGPoint(x: 10, y: 50), in: size)
+ #expect(zone == .left)
+ }
+
+ @Test func tallRectangleTopEdge() {
+ let size = CGSize(width: 100, height: 200)
+ let zone = TerminalSplitDropZone.calculate(at: CGPoint(x: 50, y: 10), in: size)
+ #expect(zone == .top)
+ }
+
+ // MARK: - Out-of-Bounds Points
+
+ @Test func pointLeftOfViewSelectsLeft() {
+ let zone = TerminalSplitDropZone.calculate(at: CGPoint(x: -10, y: 50), in: standardSize)
+ #expect(zone == .left)
+ }
+
+ @Test func pointAboveViewSelectsTop() {
+ let zone = TerminalSplitDropZone.calculate(at: CGPoint(x: 50, y: -10), in: standardSize)
+ #expect(zone == .top)
+ }
+
+ @Test func pointRightOfViewSelectsRight() {
+ let zone = TerminalSplitDropZone.calculate(at: CGPoint(x: 110, y: 50), in: standardSize)
+ #expect(zone == .right)
+ }
+
+ @Test func pointBelowViewSelectsBottom() {
+ let zone = TerminalSplitDropZone.calculate(at: CGPoint(x: 50, y: 110), in: standardSize)
+ #expect(zone == .bottom)
+ }
+
+ // MARK: - Diagonal Regions (Triangular Zones)
+
+ @Test func upperLeftTriangleSelectsLeft() {
+ // Point in the upper-left triangle, closer to left than top
+ let zone = TerminalSplitDropZone.calculate(at: CGPoint(x: 20, y: 30), in: standardSize)
+ #expect(zone == .left)
+ }
+
+ @Test func upperRightTriangleSelectsRight() {
+ // Point in the upper-right triangle, closer to right than top
+ let zone = TerminalSplitDropZone.calculate(at: CGPoint(x: 80, y: 30), in: standardSize)
+ #expect(zone == .right)
+ }
+
+ @Test func lowerLeftTriangleSelectsLeft() {
+ // Point in the lower-left triangle, closer to left than bottom
+ let zone = TerminalSplitDropZone.calculate(at: CGPoint(x: 20, y: 70), in: standardSize)
+ #expect(zone == .left)
+ }
+
+ @Test func lowerRightTriangleSelectsRight() {
+ // Point in the lower-right triangle, closer to right than bottom
+ let zone = TerminalSplitDropZone.calculate(at: CGPoint(x: 80, y: 70), in: standardSize)
+ #expect(zone == .right)
+ }
+}
diff --git a/nix/devShell.nix b/nix/devShell.nix
index d37107133..90059a730 100644
--- a/nix/devShell.nix
+++ b/nix/devShell.nix
@@ -26,6 +26,7 @@
wasmtime,
wraptest,
zig,
+ zig_0_15,
zip,
llvmPackages_latest,
bzip2,
diff --git a/nix/package.nix b/nix/package.nix
index 3d00648ec..b2decc7bc 100644
--- a/nix/package.nix
+++ b/nix/package.nix
@@ -20,16 +20,6 @@
wayland-scanner,
pkgs,
}: let
- # The Zig hook has no way to select the release type without actual
- # overriding of the default flags.
- #
- # TODO: Once
- # https://github.com/ziglang/zig/issues/14281#issuecomment-1624220653 is
- # ultimately acted on and has made its way to a nixpkgs implementation, this
- # can probably be removed in favor of that.
- zig_hook = zig_0_15.hook.overrideAttrs {
- zig_default_flags = "-Dcpu=baseline -Doptimize=${optimize} --color off";
- };
gi_typelib_path = import ./build-support/gi-typelib-path.nix {
inherit pkgs lib stdenv;
};
@@ -73,7 +63,7 @@ in
ncurses
pandoc
pkg-config
- zig_hook
+ zig_0_15
gobject-introspection
wrapGAppsHook4
blueprint-compiler
@@ -92,12 +82,16 @@ in
GI_TYPELIB_PATH = gi_typelib_path;
+ dontSetZigDefaultFlags = true;
+
zigBuildFlags = [
"--system"
"${finalAttrs.deps}"
"-Dversion-string=${finalAttrs.version}-${revision}-nix"
"-Dgtk-x11=${lib.boolToString enableX11}"
"-Dgtk-wayland=${lib.boolToString enableWayland}"
+ "-Dcpu=baseline"
+ "-Doptimize=${optimize}"
"-Dstrip=${lib.boolToString strip}"
];
diff --git a/nix/tests.nix b/nix/tests.nix
index a9970e80c..3949877cf 100644
--- a/nix/tests.nix
+++ b/nix/tests.nix
@@ -274,7 +274,7 @@ in {
client.succeed("${su "${ghostty} +new-window"}")
client.wait_until_succeeds("${wm_class} | grep -q 'com.mitchellh.ghostty-debug'")
- with subtest("SSH from client to server and verify that the Ghostty terminfo is copied.")
+ with subtest("SSH from client to server and verify that the Ghostty terminfo is copied."):
client.sleep(2)
client.send_chars("ssh ghostty@server\n")
server.wait_for_file("${user.home}/.terminfo/x/xterm-ghostty", timeout=30)
diff --git a/nix/vm/common-gnome.nix b/nix/vm/common-gnome.nix
index ab4aab9e9..d8d484071 100644
--- a/nix/vm/common-gnome.nix
+++ b/nix/vm/common-gnome.nix
@@ -8,7 +8,7 @@
./common.nix
];
- services.xserver = {
+ services = {
displayManager = {
gdm = {
enable = true;
diff --git a/nix/vm/x11-gnome.nix b/nix/vm/x11-gnome.nix
deleted file mode 100644
index 1994aea82..000000000
--- a/nix/vm/x11-gnome.nix
+++ /dev/null
@@ -1,9 +0,0 @@
-{...}: {
- imports = [
- ./common-gnome.nix
- ];
-
- services.displayManager = {
- defaultSession = "gnome-xorg";
- };
-}
diff --git a/pkg/cimgui/build.zig b/pkg/cimgui/build.zig
deleted file mode 100644
index b94f11943..000000000
--- a/pkg/cimgui/build.zig
+++ /dev/null
@@ -1,125 +0,0 @@
-const std = @import("std");
-const NativeTargetInfo = std.zig.system.NativeTargetInfo;
-
-pub fn build(b: *std.Build) !void {
- const target = b.standardTargetOptions(.{});
- const optimize = b.standardOptimizeOption(.{});
-
- const module = b.addModule("cimgui", .{
- .root_source_file = b.path("main.zig"),
- .target = target,
- .optimize = optimize,
- });
-
- const imgui_ = b.lazyDependency("imgui", .{});
- const lib = b.addLibrary(.{
- .name = "cimgui",
- .root_module = b.createModule(.{
- .target = target,
- .optimize = optimize,
- }),
- .linkage = .static,
- });
- lib.linkLibC();
- lib.linkLibCpp();
- if (target.result.os.tag == .windows) {
- lib.linkSystemLibrary("imm32");
- }
-
- // For dynamic linking, we prefer dynamic linking and to search by
- // mode first. Mode first will search all paths for a dynamic library
- // before falling back to static.
- const dynamic_link_opts: std.Build.Module.LinkSystemLibraryOptions = .{
- .preferred_link_mode = .dynamic,
- .search_strategy = .mode_first,
- };
-
- if (b.systemIntegrationOption("freetype", .{})) {
- lib.linkSystemLibrary2("freetype2", dynamic_link_opts);
- } else {
- const freetype = b.dependency("freetype", .{
- .target = target,
- .optimize = optimize,
- .@"enable-libpng" = true,
- });
- lib.linkLibrary(freetype.artifact("freetype"));
-
- if (freetype.builder.lazyDependency(
- "freetype",
- .{},
- )) |freetype_dep| {
- module.addIncludePath(freetype_dep.path("include"));
- }
- }
-
- if (imgui_) |imgui| lib.addIncludePath(imgui.path(""));
- module.addIncludePath(b.path("vendor"));
-
- var flags: std.ArrayList([]const u8) = .empty;
- defer flags.deinit(b.allocator);
- try flags.appendSlice(b.allocator, &.{
- "-DCIMGUI_FREETYPE=1",
- "-DIMGUI_USE_WCHAR32=1",
- "-DIMGUI_DISABLE_OBSOLETE_FUNCTIONS=1",
- });
- if (target.result.os.tag == .windows) {
- try flags.appendSlice(b.allocator, &.{
- "-DIMGUI_IMPL_API=extern\t\"C\"\t__declspec(dllexport)",
- });
- } else {
- try flags.appendSlice(b.allocator, &.{
- "-DIMGUI_IMPL_API=extern\t\"C\"",
- });
- }
-
- if (imgui_) |imgui| {
- lib.addCSourceFile(.{ .file = b.path("vendor/cimgui.cpp"), .flags = flags.items });
- lib.addCSourceFile(.{ .file = imgui.path("imgui.cpp"), .flags = flags.items });
- lib.addCSourceFile(.{ .file = imgui.path("imgui_draw.cpp"), .flags = flags.items });
- lib.addCSourceFile(.{ .file = imgui.path("imgui_demo.cpp"), .flags = flags.items });
- lib.addCSourceFile(.{ .file = imgui.path("imgui_widgets.cpp"), .flags = flags.items });
- lib.addCSourceFile(.{ .file = imgui.path("imgui_tables.cpp"), .flags = flags.items });
- lib.addCSourceFile(.{ .file = imgui.path("misc/freetype/imgui_freetype.cpp"), .flags = flags.items });
- lib.addCSourceFile(.{
- .file = imgui.path("backends/imgui_impl_opengl3.cpp"),
- .flags = flags.items,
- });
-
- if (target.result.os.tag.isDarwin()) {
- if (!target.query.isNative()) {
- try @import("apple_sdk").addPaths(b, lib);
- }
- lib.addCSourceFile(.{
- .file = imgui.path("backends/imgui_impl_metal.mm"),
- .flags = flags.items,
- });
- if (target.result.os.tag == .macos) {
- lib.addCSourceFile(.{
- .file = imgui.path("backends/imgui_impl_osx.mm"),
- .flags = flags.items,
- });
- }
- }
- }
-
- lib.installHeadersDirectory(
- b.path("vendor"),
- "",
- .{ .include_extensions = &.{".h"} },
- );
-
- b.installArtifact(lib);
-
- const test_exe = b.addTest(.{
- .name = "test",
- .root_module = b.createModule(.{
- .root_source_file = b.path("main.zig"),
- .target = target,
- .optimize = optimize,
- }),
- });
- test_exe.linkLibrary(lib);
- const tests_run = b.addRunArtifact(test_exe);
- const test_step = b.step("test", "Run tests");
- test_step.dependOn(&tests_run.step);
-}
diff --git a/pkg/cimgui/build.zig.zon b/pkg/cimgui/build.zig.zon
deleted file mode 100644
index f539a8fd6..000000000
--- a/pkg/cimgui/build.zig.zon
+++ /dev/null
@@ -1,19 +0,0 @@
-.{
- .name = .cimgui,
- .version = "1.90.6", // -docking branch
- .fingerprint = 0x49726f5f8acbc90d,
- .paths = .{""},
- .dependencies = .{
- // This should be kept in sync with the submodule in the cimgui source
- // code in ./vendor/ to be safe that they're compatible.
- .imgui = .{
- // ocornut/imgui
- .url = "https://deps.files.ghostty.org/imgui-1220bc6b9daceaf7c8c60f3c3998058045ba0c5c5f48ae255ff97776d9cd8bfc6402.tar.gz",
- .hash = "N-V-__8AAH0GaQC8a52s6vfIxg88OZgFgEW6DFxfSK4lX_l3",
- .lazy = true,
- },
-
- .apple_sdk = .{ .path = "../apple-sdk" },
- .freetype = .{ .path = "../freetype" },
- },
-}
diff --git a/pkg/cimgui/c.zig b/pkg/cimgui/c.zig
deleted file mode 100644
index f9b8ff920..000000000
--- a/pkg/cimgui/c.zig
+++ /dev/null
@@ -1,4 +0,0 @@
-pub const c = @cImport({
- @cDefine("CIMGUI_DEFINE_ENUMS_AND_STRUCTS", "1");
- @cInclude("cimgui.h");
-});
diff --git a/pkg/cimgui/main.zig b/pkg/cimgui/main.zig
deleted file mode 100644
index b890a49ee..000000000
--- a/pkg/cimgui/main.zig
+++ /dev/null
@@ -1,20 +0,0 @@
-pub const c = @import("c.zig").c;
-
-// OpenGL
-pub extern fn ImGui_ImplOpenGL3_Init(?[*:0]const u8) callconv(.c) bool;
-pub extern fn ImGui_ImplOpenGL3_Shutdown() callconv(.c) void;
-pub extern fn ImGui_ImplOpenGL3_NewFrame() callconv(.c) void;
-pub extern fn ImGui_ImplOpenGL3_RenderDrawData(*c.ImDrawData) callconv(.c) void;
-
-// Metal
-pub extern fn ImGui_ImplMetal_Init(*anyopaque) callconv(.c) bool;
-pub extern fn ImGui_ImplMetal_Shutdown() callconv(.c) void;
-pub extern fn ImGui_ImplMetal_NewFrame(*anyopaque) callconv(.c) void;
-pub extern fn ImGui_ImplMetal_RenderDrawData(*c.ImDrawData, *anyopaque, *anyopaque) callconv(.c) void;
-
-// OSX
-pub extern fn ImGui_ImplOSX_Init(*anyopaque) callconv(.c) bool;
-pub extern fn ImGui_ImplOSX_Shutdown() callconv(.c) void;
-pub extern fn ImGui_ImplOSX_NewFrame(*anyopaque) callconv(.c) void;
-
-test {}
diff --git a/pkg/cimgui/vendor/cimgui.cpp b/pkg/cimgui/vendor/cimgui.cpp
deleted file mode 100644
index 3b36df7d9..000000000
--- a/pkg/cimgui/vendor/cimgui.cpp
+++ /dev/null
@@ -1,5943 +0,0 @@
-// This file is automatically generated by generator.lua from
-// https://github.com/cimgui/cimgui based on imgui.h file version "1.90.6" 19060
-// from Dear ImGui https://github.com/ocornut/imgui with imgui_internal.h api
-// docking branch
-#define IMGUI_ENABLE_FREETYPE
-#ifdef IMGUI_ENABLE_FREETYPE
-#ifndef CIMGUI_FREETYPE
-#error "IMGUI_FREETYPE should be defined for Freetype linking"
-#endif
-#else
-#ifdef CIMGUI_FREETYPE
-#error "IMGUI_FREETYPE should not be defined without freetype generated cimgui"
-#endif
-#endif
-#include "imgui.h"
-#ifdef IMGUI_ENABLE_FREETYPE
-#include "misc/freetype/imgui_freetype.h"
-#endif
-#include "imgui_internal.h"
-
-#include "cimgui.h"
-
-CIMGUI_API ImVec2* ImVec2_ImVec2_Nil(void) {
- return IM_NEW(ImVec2)();
-}
-CIMGUI_API void ImVec2_destroy(ImVec2* self) {
- IM_DELETE(self);
-}
-CIMGUI_API ImVec2* ImVec2_ImVec2_Float(float _x, float _y) {
- return IM_NEW(ImVec2)(_x, _y);
-}
-CIMGUI_API ImVec4* ImVec4_ImVec4_Nil(void) {
- return IM_NEW(ImVec4)();
-}
-CIMGUI_API void ImVec4_destroy(ImVec4* self) {
- IM_DELETE(self);
-}
-CIMGUI_API ImVec4* ImVec4_ImVec4_Float(float _x, float _y, float _z, float _w) {
- return IM_NEW(ImVec4)(_x, _y, _z, _w);
-}
-CIMGUI_API ImGuiContext* igCreateContext(ImFontAtlas* shared_font_atlas) {
- return ImGui::CreateContext(shared_font_atlas);
-}
-CIMGUI_API void igDestroyContext(ImGuiContext* ctx) {
- return ImGui::DestroyContext(ctx);
-}
-CIMGUI_API ImGuiContext* igGetCurrentContext() {
- return ImGui::GetCurrentContext();
-}
-CIMGUI_API void igSetCurrentContext(ImGuiContext* ctx) {
- return ImGui::SetCurrentContext(ctx);
-}
-CIMGUI_API ImGuiIO* igGetIO() {
- return &ImGui::GetIO();
-}
-CIMGUI_API ImGuiStyle* igGetStyle() {
- return &ImGui::GetStyle();
-}
-CIMGUI_API void igNewFrame() {
- return ImGui::NewFrame();
-}
-CIMGUI_API void igEndFrame() {
- return ImGui::EndFrame();
-}
-CIMGUI_API void igRender() {
- return ImGui::Render();
-}
-CIMGUI_API ImDrawData* igGetDrawData() {
- return ImGui::GetDrawData();
-}
-CIMGUI_API void igShowDemoWindow(bool* p_open) {
- return ImGui::ShowDemoWindow(p_open);
-}
-CIMGUI_API void igShowMetricsWindow(bool* p_open) {
- return ImGui::ShowMetricsWindow(p_open);
-}
-CIMGUI_API void igShowDebugLogWindow(bool* p_open) {
- return ImGui::ShowDebugLogWindow(p_open);
-}
-CIMGUI_API void igShowIDStackToolWindow(bool* p_open) {
- return ImGui::ShowIDStackToolWindow(p_open);
-}
-CIMGUI_API void igShowAboutWindow(bool* p_open) {
- return ImGui::ShowAboutWindow(p_open);
-}
-CIMGUI_API void igShowStyleEditor(ImGuiStyle* ref) {
- return ImGui::ShowStyleEditor(ref);
-}
-CIMGUI_API bool igShowStyleSelector(const char* label) {
- return ImGui::ShowStyleSelector(label);
-}
-CIMGUI_API void igShowFontSelector(const char* label) {
- return ImGui::ShowFontSelector(label);
-}
-CIMGUI_API void igShowUserGuide() {
- return ImGui::ShowUserGuide();
-}
-CIMGUI_API const char* igGetVersion() {
- return ImGui::GetVersion();
-}
-CIMGUI_API void igStyleColorsDark(ImGuiStyle* dst) {
- return ImGui::StyleColorsDark(dst);
-}
-CIMGUI_API void igStyleColorsLight(ImGuiStyle* dst) {
- return ImGui::StyleColorsLight(dst);
-}
-CIMGUI_API void igStyleColorsClassic(ImGuiStyle* dst) {
- return ImGui::StyleColorsClassic(dst);
-}
-CIMGUI_API bool igBegin(const char* name,
- bool* p_open,
- ImGuiWindowFlags flags) {
- return ImGui::Begin(name, p_open, flags);
-}
-CIMGUI_API void igEnd() {
- return ImGui::End();
-}
-CIMGUI_API bool igBeginChild_Str(const char* str_id,
- const ImVec2 size,
- ImGuiChildFlags child_flags,
- ImGuiWindowFlags window_flags) {
- return ImGui::BeginChild(str_id, size, child_flags, window_flags);
-}
-CIMGUI_API bool igBeginChild_ID(ImGuiID id,
- const ImVec2 size,
- ImGuiChildFlags child_flags,
- ImGuiWindowFlags window_flags) {
- return ImGui::BeginChild(id, size, child_flags, window_flags);
-}
-CIMGUI_API void igEndChild() {
- return ImGui::EndChild();
-}
-CIMGUI_API bool igIsWindowAppearing() {
- return ImGui::IsWindowAppearing();
-}
-CIMGUI_API bool igIsWindowCollapsed() {
- return ImGui::IsWindowCollapsed();
-}
-CIMGUI_API bool igIsWindowFocused(ImGuiFocusedFlags flags) {
- return ImGui::IsWindowFocused(flags);
-}
-CIMGUI_API bool igIsWindowHovered(ImGuiHoveredFlags flags) {
- return ImGui::IsWindowHovered(flags);
-}
-CIMGUI_API ImDrawList* igGetWindowDrawList() {
- return ImGui::GetWindowDrawList();
-}
-CIMGUI_API float igGetWindowDpiScale() {
- return ImGui::GetWindowDpiScale();
-}
-CIMGUI_API void igGetWindowPos(ImVec2* pOut) {
- *pOut = ImGui::GetWindowPos();
-}
-CIMGUI_API void igGetWindowSize(ImVec2* pOut) {
- *pOut = ImGui::GetWindowSize();
-}
-CIMGUI_API float igGetWindowWidth() {
- return ImGui::GetWindowWidth();
-}
-CIMGUI_API float igGetWindowHeight() {
- return ImGui::GetWindowHeight();
-}
-CIMGUI_API ImGuiViewport* igGetWindowViewport() {
- return ImGui::GetWindowViewport();
-}
-CIMGUI_API void igSetNextWindowPos(const ImVec2 pos,
- ImGuiCond cond,
- const ImVec2 pivot) {
- return ImGui::SetNextWindowPos(pos, cond, pivot);
-}
-CIMGUI_API void igSetNextWindowSize(const ImVec2 size, ImGuiCond cond) {
- return ImGui::SetNextWindowSize(size, cond);
-}
-CIMGUI_API void igSetNextWindowSizeConstraints(
- const ImVec2 size_min,
- const ImVec2 size_max,
- ImGuiSizeCallback custom_callback,
- void* custom_callback_data) {
- return ImGui::SetNextWindowSizeConstraints(
- size_min, size_max, custom_callback, custom_callback_data);
-}
-CIMGUI_API void igSetNextWindowContentSize(const ImVec2 size) {
- return ImGui::SetNextWindowContentSize(size);
-}
-CIMGUI_API void igSetNextWindowCollapsed(bool collapsed, ImGuiCond cond) {
- return ImGui::SetNextWindowCollapsed(collapsed, cond);
-}
-CIMGUI_API void igSetNextWindowFocus() {
- return ImGui::SetNextWindowFocus();
-}
-CIMGUI_API void igSetNextWindowScroll(const ImVec2 scroll) {
- return ImGui::SetNextWindowScroll(scroll);
-}
-CIMGUI_API void igSetNextWindowBgAlpha(float alpha) {
- return ImGui::SetNextWindowBgAlpha(alpha);
-}
-CIMGUI_API void igSetNextWindowViewport(ImGuiID viewport_id) {
- return ImGui::SetNextWindowViewport(viewport_id);
-}
-CIMGUI_API void igSetWindowPos_Vec2(const ImVec2 pos, ImGuiCond cond) {
- return ImGui::SetWindowPos(pos, cond);
-}
-CIMGUI_API void igSetWindowSize_Vec2(const ImVec2 size, ImGuiCond cond) {
- return ImGui::SetWindowSize(size, cond);
-}
-CIMGUI_API void igSetWindowCollapsed_Bool(bool collapsed, ImGuiCond cond) {
- return ImGui::SetWindowCollapsed(collapsed, cond);
-}
-CIMGUI_API void igSetWindowFocus_Nil() {
- return ImGui::SetWindowFocus();
-}
-CIMGUI_API void igSetWindowFontScale(float scale) {
- return ImGui::SetWindowFontScale(scale);
-}
-CIMGUI_API void igSetWindowPos_Str(const char* name,
- const ImVec2 pos,
- ImGuiCond cond) {
- return ImGui::SetWindowPos(name, pos, cond);
-}
-CIMGUI_API void igSetWindowSize_Str(const char* name,
- const ImVec2 size,
- ImGuiCond cond) {
- return ImGui::SetWindowSize(name, size, cond);
-}
-CIMGUI_API void igSetWindowCollapsed_Str(const char* name,
- bool collapsed,
- ImGuiCond cond) {
- return ImGui::SetWindowCollapsed(name, collapsed, cond);
-}
-CIMGUI_API void igSetWindowFocus_Str(const char* name) {
- return ImGui::SetWindowFocus(name);
-}
-CIMGUI_API void igGetContentRegionAvail(ImVec2* pOut) {
- *pOut = ImGui::GetContentRegionAvail();
-}
-CIMGUI_API void igGetContentRegionMax(ImVec2* pOut) {
- *pOut = ImGui::GetContentRegionMax();
-}
-CIMGUI_API void igGetWindowContentRegionMin(ImVec2* pOut) {
- *pOut = ImGui::GetWindowContentRegionMin();
-}
-CIMGUI_API void igGetWindowContentRegionMax(ImVec2* pOut) {
- *pOut = ImGui::GetWindowContentRegionMax();
-}
-CIMGUI_API float igGetScrollX() {
- return ImGui::GetScrollX();
-}
-CIMGUI_API float igGetScrollY() {
- return ImGui::GetScrollY();
-}
-CIMGUI_API void igSetScrollX_Float(float scroll_x) {
- return ImGui::SetScrollX(scroll_x);
-}
-CIMGUI_API void igSetScrollY_Float(float scroll_y) {
- return ImGui::SetScrollY(scroll_y);
-}
-CIMGUI_API float igGetScrollMaxX() {
- return ImGui::GetScrollMaxX();
-}
-CIMGUI_API float igGetScrollMaxY() {
- return ImGui::GetScrollMaxY();
-}
-CIMGUI_API void igSetScrollHereX(float center_x_ratio) {
- return ImGui::SetScrollHereX(center_x_ratio);
-}
-CIMGUI_API void igSetScrollHereY(float center_y_ratio) {
- return ImGui::SetScrollHereY(center_y_ratio);
-}
-CIMGUI_API void igSetScrollFromPosX_Float(float local_x, float center_x_ratio) {
- return ImGui::SetScrollFromPosX(local_x, center_x_ratio);
-}
-CIMGUI_API void igSetScrollFromPosY_Float(float local_y, float center_y_ratio) {
- return ImGui::SetScrollFromPosY(local_y, center_y_ratio);
-}
-CIMGUI_API void igPushFont(ImFont* font) {
- return ImGui::PushFont(font);
-}
-CIMGUI_API void igPopFont() {
- return ImGui::PopFont();
-}
-CIMGUI_API void igPushStyleColor_U32(ImGuiCol idx, ImU32 col) {
- return ImGui::PushStyleColor(idx, col);
-}
-CIMGUI_API void igPushStyleColor_Vec4(ImGuiCol idx, const ImVec4 col) {
- return ImGui::PushStyleColor(idx, col);
-}
-CIMGUI_API void igPopStyleColor(int count) {
- return ImGui::PopStyleColor(count);
-}
-CIMGUI_API void igPushStyleVar_Float(ImGuiStyleVar idx, float val) {
- return ImGui::PushStyleVar(idx, val);
-}
-CIMGUI_API void igPushStyleVar_Vec2(ImGuiStyleVar idx, const ImVec2 val) {
- return ImGui::PushStyleVar(idx, val);
-}
-CIMGUI_API void igPopStyleVar(int count) {
- return ImGui::PopStyleVar(count);
-}
-CIMGUI_API void igPushTabStop(bool tab_stop) {
- return ImGui::PushTabStop(tab_stop);
-}
-CIMGUI_API void igPopTabStop() {
- return ImGui::PopTabStop();
-}
-CIMGUI_API void igPushButtonRepeat(bool repeat) {
- return ImGui::PushButtonRepeat(repeat);
-}
-CIMGUI_API void igPopButtonRepeat() {
- return ImGui::PopButtonRepeat();
-}
-CIMGUI_API void igPushItemWidth(float item_width) {
- return ImGui::PushItemWidth(item_width);
-}
-CIMGUI_API void igPopItemWidth() {
- return ImGui::PopItemWidth();
-}
-CIMGUI_API void igSetNextItemWidth(float item_width) {
- return ImGui::SetNextItemWidth(item_width);
-}
-CIMGUI_API float igCalcItemWidth() {
- return ImGui::CalcItemWidth();
-}
-CIMGUI_API void igPushTextWrapPos(float wrap_local_pos_x) {
- return ImGui::PushTextWrapPos(wrap_local_pos_x);
-}
-CIMGUI_API void igPopTextWrapPos() {
- return ImGui::PopTextWrapPos();
-}
-CIMGUI_API ImFont* igGetFont() {
- return ImGui::GetFont();
-}
-CIMGUI_API float igGetFontSize() {
- return ImGui::GetFontSize();
-}
-CIMGUI_API void igGetFontTexUvWhitePixel(ImVec2* pOut) {
- *pOut = ImGui::GetFontTexUvWhitePixel();
-}
-CIMGUI_API ImU32 igGetColorU32_Col(ImGuiCol idx, float alpha_mul) {
- return ImGui::GetColorU32(idx, alpha_mul);
-}
-CIMGUI_API ImU32 igGetColorU32_Vec4(const ImVec4 col) {
- return ImGui::GetColorU32(col);
-}
-CIMGUI_API ImU32 igGetColorU32_U32(ImU32 col, float alpha_mul) {
- return ImGui::GetColorU32(col, alpha_mul);
-}
-CIMGUI_API const ImVec4* igGetStyleColorVec4(ImGuiCol idx) {
- return &ImGui::GetStyleColorVec4(idx);
-}
-CIMGUI_API void igGetCursorScreenPos(ImVec2* pOut) {
- *pOut = ImGui::GetCursorScreenPos();
-}
-CIMGUI_API void igSetCursorScreenPos(const ImVec2 pos) {
- return ImGui::SetCursorScreenPos(pos);
-}
-CIMGUI_API void igGetCursorPos(ImVec2* pOut) {
- *pOut = ImGui::GetCursorPos();
-}
-CIMGUI_API float igGetCursorPosX() {
- return ImGui::GetCursorPosX();
-}
-CIMGUI_API float igGetCursorPosY() {
- return ImGui::GetCursorPosY();
-}
-CIMGUI_API void igSetCursorPos(const ImVec2 local_pos) {
- return ImGui::SetCursorPos(local_pos);
-}
-CIMGUI_API void igSetCursorPosX(float local_x) {
- return ImGui::SetCursorPosX(local_x);
-}
-CIMGUI_API void igSetCursorPosY(float local_y) {
- return ImGui::SetCursorPosY(local_y);
-}
-CIMGUI_API void igGetCursorStartPos(ImVec2* pOut) {
- *pOut = ImGui::GetCursorStartPos();
-}
-CIMGUI_API void igSeparator() {
- return ImGui::Separator();
-}
-CIMGUI_API void igSameLine(float offset_from_start_x, float spacing) {
- return ImGui::SameLine(offset_from_start_x, spacing);
-}
-CIMGUI_API void igNewLine() {
- return ImGui::NewLine();
-}
-CIMGUI_API void igSpacing() {
- return ImGui::Spacing();
-}
-CIMGUI_API void igDummy(const ImVec2 size) {
- return ImGui::Dummy(size);
-}
-CIMGUI_API void igIndent(float indent_w) {
- return ImGui::Indent(indent_w);
-}
-CIMGUI_API void igUnindent(float indent_w) {
- return ImGui::Unindent(indent_w);
-}
-CIMGUI_API void igBeginGroup() {
- return ImGui::BeginGroup();
-}
-CIMGUI_API void igEndGroup() {
- return ImGui::EndGroup();
-}
-CIMGUI_API void igAlignTextToFramePadding() {
- return ImGui::AlignTextToFramePadding();
-}
-CIMGUI_API float igGetTextLineHeight() {
- return ImGui::GetTextLineHeight();
-}
-CIMGUI_API float igGetTextLineHeightWithSpacing() {
- return ImGui::GetTextLineHeightWithSpacing();
-}
-CIMGUI_API float igGetFrameHeight() {
- return ImGui::GetFrameHeight();
-}
-CIMGUI_API float igGetFrameHeightWithSpacing() {
- return ImGui::GetFrameHeightWithSpacing();
-}
-CIMGUI_API void igPushID_Str(const char* str_id) {
- return ImGui::PushID(str_id);
-}
-CIMGUI_API void igPushID_StrStr(const char* str_id_begin,
- const char* str_id_end) {
- return ImGui::PushID(str_id_begin, str_id_end);
-}
-CIMGUI_API void igPushID_Ptr(const void* ptr_id) {
- return ImGui::PushID(ptr_id);
-}
-CIMGUI_API void igPushID_Int(int int_id) {
- return ImGui::PushID(int_id);
-}
-CIMGUI_API void igPopID() {
- return ImGui::PopID();
-}
-CIMGUI_API ImGuiID igGetID_Str(const char* str_id) {
- return ImGui::GetID(str_id);
-}
-CIMGUI_API ImGuiID igGetID_StrStr(const char* str_id_begin,
- const char* str_id_end) {
- return ImGui::GetID(str_id_begin, str_id_end);
-}
-CIMGUI_API ImGuiID igGetID_Ptr(const void* ptr_id) {
- return ImGui::GetID(ptr_id);
-}
-CIMGUI_API void igTextUnformatted(const char* text, const char* text_end) {
- return ImGui::TextUnformatted(text, text_end);
-}
-CIMGUI_API void igText(const char* fmt, ...) {
- va_list args;
- va_start(args, fmt);
- ImGui::TextV(fmt, args);
- va_end(args);
-}
-CIMGUI_API void igTextV(const char* fmt, va_list args) {
- return ImGui::TextV(fmt, args);
-}
-CIMGUI_API void igTextColored(const ImVec4 col, const char* fmt, ...) {
- va_list args;
- va_start(args, fmt);
- ImGui::TextColoredV(col, fmt, args);
- va_end(args);
-}
-CIMGUI_API void igTextColoredV(const ImVec4 col,
- const char* fmt,
- va_list args) {
- return ImGui::TextColoredV(col, fmt, args);
-}
-CIMGUI_API void igTextDisabled(const char* fmt, ...) {
- va_list args;
- va_start(args, fmt);
- ImGui::TextDisabledV(fmt, args);
- va_end(args);
-}
-CIMGUI_API void igTextDisabledV(const char* fmt, va_list args) {
- return ImGui::TextDisabledV(fmt, args);
-}
-CIMGUI_API void igTextWrapped(const char* fmt, ...) {
- va_list args;
- va_start(args, fmt);
- ImGui::TextWrappedV(fmt, args);
- va_end(args);
-}
-CIMGUI_API void igTextWrappedV(const char* fmt, va_list args) {
- return ImGui::TextWrappedV(fmt, args);
-}
-CIMGUI_API void igLabelText(const char* label, const char* fmt, ...) {
- va_list args;
- va_start(args, fmt);
- ImGui::LabelTextV(label, fmt, args);
- va_end(args);
-}
-CIMGUI_API void igLabelTextV(const char* label, const char* fmt, va_list args) {
- return ImGui::LabelTextV(label, fmt, args);
-}
-CIMGUI_API void igBulletText(const char* fmt, ...) {
- va_list args;
- va_start(args, fmt);
- ImGui::BulletTextV(fmt, args);
- va_end(args);
-}
-CIMGUI_API void igBulletTextV(const char* fmt, va_list args) {
- return ImGui::BulletTextV(fmt, args);
-}
-CIMGUI_API void igSeparatorText(const char* label) {
- return ImGui::SeparatorText(label);
-}
-CIMGUI_API bool igButton(const char* label, const ImVec2 size) {
- return ImGui::Button(label, size);
-}
-CIMGUI_API bool igSmallButton(const char* label) {
- return ImGui::SmallButton(label);
-}
-CIMGUI_API bool igInvisibleButton(const char* str_id,
- const ImVec2 size,
- ImGuiButtonFlags flags) {
- return ImGui::InvisibleButton(str_id, size, flags);
-}
-CIMGUI_API bool igArrowButton(const char* str_id, ImGuiDir dir) {
- return ImGui::ArrowButton(str_id, dir);
-}
-CIMGUI_API bool igCheckbox(const char* label, bool* v) {
- return ImGui::Checkbox(label, v);
-}
-CIMGUI_API bool igCheckboxFlags_IntPtr(const char* label,
- int* flags,
- int flags_value) {
- return ImGui::CheckboxFlags(label, flags, flags_value);
-}
-CIMGUI_API bool igCheckboxFlags_UintPtr(const char* label,
- unsigned int* flags,
- unsigned int flags_value) {
- return ImGui::CheckboxFlags(label, flags, flags_value);
-}
-CIMGUI_API bool igRadioButton_Bool(const char* label, bool active) {
- return ImGui::RadioButton(label, active);
-}
-CIMGUI_API bool igRadioButton_IntPtr(const char* label, int* v, int v_button) {
- return ImGui::RadioButton(label, v, v_button);
-}
-CIMGUI_API void igProgressBar(float fraction,
- const ImVec2 size_arg,
- const char* overlay) {
- return ImGui::ProgressBar(fraction, size_arg, overlay);
-}
-CIMGUI_API void igBullet() {
- return ImGui::Bullet();
-}
-CIMGUI_API void igImage(ImTextureID user_texture_id,
- const ImVec2 image_size,
- const ImVec2 uv0,
- const ImVec2 uv1,
- const ImVec4 tint_col,
- const ImVec4 border_col) {
- return ImGui::Image(user_texture_id, image_size, uv0, uv1, tint_col,
- border_col);
-}
-CIMGUI_API bool igImageButton(const char* str_id,
- ImTextureID user_texture_id,
- const ImVec2 image_size,
- const ImVec2 uv0,
- const ImVec2 uv1,
- const ImVec4 bg_col,
- const ImVec4 tint_col) {
- return ImGui::ImageButton(str_id, user_texture_id, image_size, uv0, uv1,
- bg_col, tint_col);
-}
-CIMGUI_API bool igBeginCombo(const char* label,
- const char* preview_value,
- ImGuiComboFlags flags) {
- return ImGui::BeginCombo(label, preview_value, flags);
-}
-CIMGUI_API void igEndCombo() {
- return ImGui::EndCombo();
-}
-CIMGUI_API bool igCombo_Str_arr(const char* label,
- int* current_item,
- const char* const items[],
- int items_count,
- int popup_max_height_in_items) {
- return ImGui::Combo(label, current_item, items, items_count,
- popup_max_height_in_items);
-}
-CIMGUI_API bool igCombo_Str(const char* label,
- int* current_item,
- const char* items_separated_by_zeros,
- int popup_max_height_in_items) {
- return ImGui::Combo(label, current_item, items_separated_by_zeros,
- popup_max_height_in_items);
-}
-CIMGUI_API bool igCombo_FnStrPtr(const char* label,
- int* current_item,
- const char* (*getter)(void* user_data,
- int idx),
- void* user_data,
- int items_count,
- int popup_max_height_in_items) {
- return ImGui::Combo(label, current_item, getter, user_data, items_count,
- popup_max_height_in_items);
-}
-CIMGUI_API bool igDragFloat(const char* label,
- float* v,
- float v_speed,
- float v_min,
- float v_max,
- const char* format,
- ImGuiSliderFlags flags) {
- return ImGui::DragFloat(label, v, v_speed, v_min, v_max, format, flags);
-}
-CIMGUI_API bool igDragFloat2(const char* label,
- float v[2],
- float v_speed,
- float v_min,
- float v_max,
- const char* format,
- ImGuiSliderFlags flags) {
- return ImGui::DragFloat2(label, v, v_speed, v_min, v_max, format, flags);
-}
-CIMGUI_API bool igDragFloat3(const char* label,
- float v[3],
- float v_speed,
- float v_min,
- float v_max,
- const char* format,
- ImGuiSliderFlags flags) {
- return ImGui::DragFloat3(label, v, v_speed, v_min, v_max, format, flags);
-}
-CIMGUI_API bool igDragFloat4(const char* label,
- float v[4],
- float v_speed,
- float v_min,
- float v_max,
- const char* format,
- ImGuiSliderFlags flags) {
- return ImGui::DragFloat4(label, v, v_speed, v_min, v_max, format, flags);
-}
-CIMGUI_API bool igDragFloatRange2(const char* label,
- float* v_current_min,
- float* v_current_max,
- float v_speed,
- float v_min,
- float v_max,
- const char* format,
- const char* format_max,
- ImGuiSliderFlags flags) {
- return ImGui::DragFloatRange2(label, v_current_min, v_current_max, v_speed,
- v_min, v_max, format, format_max, flags);
-}
-CIMGUI_API bool igDragInt(const char* label,
- int* v,
- float v_speed,
- int v_min,
- int v_max,
- const char* format,
- ImGuiSliderFlags flags) {
- return ImGui::DragInt(label, v, v_speed, v_min, v_max, format, flags);
-}
-CIMGUI_API bool igDragInt2(const char* label,
- int v[2],
- float v_speed,
- int v_min,
- int v_max,
- const char* format,
- ImGuiSliderFlags flags) {
- return ImGui::DragInt2(label, v, v_speed, v_min, v_max, format, flags);
-}
-CIMGUI_API bool igDragInt3(const char* label,
- int v[3],
- float v_speed,
- int v_min,
- int v_max,
- const char* format,
- ImGuiSliderFlags flags) {
- return ImGui::DragInt3(label, v, v_speed, v_min, v_max, format, flags);
-}
-CIMGUI_API bool igDragInt4(const char* label,
- int v[4],
- float v_speed,
- int v_min,
- int v_max,
- const char* format,
- ImGuiSliderFlags flags) {
- return ImGui::DragInt4(label, v, v_speed, v_min, v_max, format, flags);
-}
-CIMGUI_API bool igDragIntRange2(const char* label,
- int* v_current_min,
- int* v_current_max,
- float v_speed,
- int v_min,
- int v_max,
- const char* format,
- const char* format_max,
- ImGuiSliderFlags flags) {
- return ImGui::DragIntRange2(label, v_current_min, v_current_max, v_speed,
- v_min, v_max, format, format_max, flags);
-}
-CIMGUI_API bool igDragScalar(const char* label,
- ImGuiDataType data_type,
- void* p_data,
- float v_speed,
- const void* p_min,
- const void* p_max,
- const char* format,
- ImGuiSliderFlags flags) {
- return ImGui::DragScalar(label, data_type, p_data, v_speed, p_min, p_max,
- format, flags);
-}
-CIMGUI_API bool igDragScalarN(const char* label,
- ImGuiDataType data_type,
- void* p_data,
- int components,
- float v_speed,
- const void* p_min,
- const void* p_max,
- const char* format,
- ImGuiSliderFlags flags) {
- return ImGui::DragScalarN(label, data_type, p_data, components, v_speed,
- p_min, p_max, format, flags);
-}
-CIMGUI_API bool igSliderFloat(const char* label,
- float* v,
- float v_min,
- float v_max,
- const char* format,
- ImGuiSliderFlags flags) {
- return ImGui::SliderFloat(label, v, v_min, v_max, format, flags);
-}
-CIMGUI_API bool igSliderFloat2(const char* label,
- float v[2],
- float v_min,
- float v_max,
- const char* format,
- ImGuiSliderFlags flags) {
- return ImGui::SliderFloat2(label, v, v_min, v_max, format, flags);
-}
-CIMGUI_API bool igSliderFloat3(const char* label,
- float v[3],
- float v_min,
- float v_max,
- const char* format,
- ImGuiSliderFlags flags) {
- return ImGui::SliderFloat3(label, v, v_min, v_max, format, flags);
-}
-CIMGUI_API bool igSliderFloat4(const char* label,
- float v[4],
- float v_min,
- float v_max,
- const char* format,
- ImGuiSliderFlags flags) {
- return ImGui::SliderFloat4(label, v, v_min, v_max, format, flags);
-}
-CIMGUI_API bool igSliderAngle(const char* label,
- float* v_rad,
- float v_degrees_min,
- float v_degrees_max,
- const char* format,
- ImGuiSliderFlags flags) {
- return ImGui::SliderAngle(label, v_rad, v_degrees_min, v_degrees_max, format,
- flags);
-}
-CIMGUI_API bool igSliderInt(const char* label,
- int* v,
- int v_min,
- int v_max,
- const char* format,
- ImGuiSliderFlags flags) {
- return ImGui::SliderInt(label, v, v_min, v_max, format, flags);
-}
-CIMGUI_API bool igSliderInt2(const char* label,
- int v[2],
- int v_min,
- int v_max,
- const char* format,
- ImGuiSliderFlags flags) {
- return ImGui::SliderInt2(label, v, v_min, v_max, format, flags);
-}
-CIMGUI_API bool igSliderInt3(const char* label,
- int v[3],
- int v_min,
- int v_max,
- const char* format,
- ImGuiSliderFlags flags) {
- return ImGui::SliderInt3(label, v, v_min, v_max, format, flags);
-}
-CIMGUI_API bool igSliderInt4(const char* label,
- int v[4],
- int v_min,
- int v_max,
- const char* format,
- ImGuiSliderFlags flags) {
- return ImGui::SliderInt4(label, v, v_min, v_max, format, flags);
-}
-CIMGUI_API bool igSliderScalar(const char* label,
- ImGuiDataType data_type,
- void* p_data,
- const void* p_min,
- const void* p_max,
- const char* format,
- ImGuiSliderFlags flags) {
- return ImGui::SliderScalar(label, data_type, p_data, p_min, p_max, format,
- flags);
-}
-CIMGUI_API bool igSliderScalarN(const char* label,
- ImGuiDataType data_type,
- void* p_data,
- int components,
- const void* p_min,
- const void* p_max,
- const char* format,
- ImGuiSliderFlags flags) {
- return ImGui::SliderScalarN(label, data_type, p_data, components, p_min,
- p_max, format, flags);
-}
-CIMGUI_API bool igVSliderFloat(const char* label,
- const ImVec2 size,
- float* v,
- float v_min,
- float v_max,
- const char* format,
- ImGuiSliderFlags flags) {
- return ImGui::VSliderFloat(label, size, v, v_min, v_max, format, flags);
-}
-CIMGUI_API bool igVSliderInt(const char* label,
- const ImVec2 size,
- int* v,
- int v_min,
- int v_max,
- const char* format,
- ImGuiSliderFlags flags) {
- return ImGui::VSliderInt(label, size, v, v_min, v_max, format, flags);
-}
-CIMGUI_API bool igVSliderScalar(const char* label,
- const ImVec2 size,
- ImGuiDataType data_type,
- void* p_data,
- const void* p_min,
- const void* p_max,
- const char* format,
- ImGuiSliderFlags flags) {
- return ImGui::VSliderScalar(label, size, data_type, p_data, p_min, p_max,
- format, flags);
-}
-CIMGUI_API bool igInputText(const char* label,
- char* buf,
- size_t buf_size,
- ImGuiInputTextFlags flags,
- ImGuiInputTextCallback callback,
- void* user_data) {
- return ImGui::InputText(label, buf, buf_size, flags, callback, user_data);
-}
-CIMGUI_API bool igInputTextMultiline(const char* label,
- char* buf,
- size_t buf_size,
- const ImVec2 size,
- ImGuiInputTextFlags flags,
- ImGuiInputTextCallback callback,
- void* user_data) {
- return ImGui::InputTextMultiline(label, buf, buf_size, size, flags, callback,
- user_data);
-}
-CIMGUI_API bool igInputTextWithHint(const char* label,
- const char* hint,
- char* buf,
- size_t buf_size,
- ImGuiInputTextFlags flags,
- ImGuiInputTextCallback callback,
- void* user_data) {
- return ImGui::InputTextWithHint(label, hint, buf, buf_size, flags, callback,
- user_data);
-}
-CIMGUI_API bool igInputFloat(const char* label,
- float* v,
- float step,
- float step_fast,
- const char* format,
- ImGuiInputTextFlags flags) {
- return ImGui::InputFloat(label, v, step, step_fast, format, flags);
-}
-CIMGUI_API bool igInputFloat2(const char* label,
- float v[2],
- const char* format,
- ImGuiInputTextFlags flags) {
- return ImGui::InputFloat2(label, v, format, flags);
-}
-CIMGUI_API bool igInputFloat3(const char* label,
- float v[3],
- const char* format,
- ImGuiInputTextFlags flags) {
- return ImGui::InputFloat3(label, v, format, flags);
-}
-CIMGUI_API bool igInputFloat4(const char* label,
- float v[4],
- const char* format,
- ImGuiInputTextFlags flags) {
- return ImGui::InputFloat4(label, v, format, flags);
-}
-CIMGUI_API bool igInputInt(const char* label,
- int* v,
- int step,
- int step_fast,
- ImGuiInputTextFlags flags) {
- return ImGui::InputInt(label, v, step, step_fast, flags);
-}
-CIMGUI_API bool igInputInt2(const char* label,
- int v[2],
- ImGuiInputTextFlags flags) {
- return ImGui::InputInt2(label, v, flags);
-}
-CIMGUI_API bool igInputInt3(const char* label,
- int v[3],
- ImGuiInputTextFlags flags) {
- return ImGui::InputInt3(label, v, flags);
-}
-CIMGUI_API bool igInputInt4(const char* label,
- int v[4],
- ImGuiInputTextFlags flags) {
- return ImGui::InputInt4(label, v, flags);
-}
-CIMGUI_API bool igInputDouble(const char* label,
- double* v,
- double step,
- double step_fast,
- const char* format,
- ImGuiInputTextFlags flags) {
- return ImGui::InputDouble(label, v, step, step_fast, format, flags);
-}
-CIMGUI_API bool igInputScalar(const char* label,
- ImGuiDataType data_type,
- void* p_data,
- const void* p_step,
- const void* p_step_fast,
- const char* format,
- ImGuiInputTextFlags flags) {
- return ImGui::InputScalar(label, data_type, p_data, p_step, p_step_fast,
- format, flags);
-}
-CIMGUI_API bool igInputScalarN(const char* label,
- ImGuiDataType data_type,
- void* p_data,
- int components,
- const void* p_step,
- const void* p_step_fast,
- const char* format,
- ImGuiInputTextFlags flags) {
- return ImGui::InputScalarN(label, data_type, p_data, components, p_step,
- p_step_fast, format, flags);
-}
-CIMGUI_API bool igColorEdit3(const char* label,
- float col[3],
- ImGuiColorEditFlags flags) {
- return ImGui::ColorEdit3(label, col, flags);
-}
-CIMGUI_API bool igColorEdit4(const char* label,
- float col[4],
- ImGuiColorEditFlags flags) {
- return ImGui::ColorEdit4(label, col, flags);
-}
-CIMGUI_API bool igColorPicker3(const char* label,
- float col[3],
- ImGuiColorEditFlags flags) {
- return ImGui::ColorPicker3(label, col, flags);
-}
-CIMGUI_API bool igColorPicker4(const char* label,
- float col[4],
- ImGuiColorEditFlags flags,
- const float* ref_col) {
- return ImGui::ColorPicker4(label, col, flags, ref_col);
-}
-CIMGUI_API bool igColorButton(const char* desc_id,
- const ImVec4 col,
- ImGuiColorEditFlags flags,
- const ImVec2 size) {
- return ImGui::ColorButton(desc_id, col, flags, size);
-}
-CIMGUI_API void igSetColorEditOptions(ImGuiColorEditFlags flags) {
- return ImGui::SetColorEditOptions(flags);
-}
-CIMGUI_API bool igTreeNode_Str(const char* label) {
- return ImGui::TreeNode(label);
-}
-CIMGUI_API bool igTreeNode_StrStr(const char* str_id, const char* fmt, ...) {
- va_list args;
- va_start(args, fmt);
- bool ret = ImGui::TreeNodeV(str_id, fmt, args);
- va_end(args);
- return ret;
-}
-CIMGUI_API bool igTreeNode_Ptr(const void* ptr_id, const char* fmt, ...) {
- va_list args;
- va_start(args, fmt);
- bool ret = ImGui::TreeNodeV(ptr_id, fmt, args);
- va_end(args);
- return ret;
-}
-CIMGUI_API bool igTreeNodeV_Str(const char* str_id,
- const char* fmt,
- va_list args) {
- return ImGui::TreeNodeV(str_id, fmt, args);
-}
-CIMGUI_API bool igTreeNodeV_Ptr(const void* ptr_id,
- const char* fmt,
- va_list args) {
- return ImGui::TreeNodeV(ptr_id, fmt, args);
-}
-CIMGUI_API bool igTreeNodeEx_Str(const char* label, ImGuiTreeNodeFlags flags) {
- return ImGui::TreeNodeEx(label, flags);
-}
-CIMGUI_API bool igTreeNodeEx_StrStr(const char* str_id,
- ImGuiTreeNodeFlags flags,
- const char* fmt,
- ...) {
- va_list args;
- va_start(args, fmt);
- bool ret = ImGui::TreeNodeExV(str_id, flags, fmt, args);
- va_end(args);
- return ret;
-}
-CIMGUI_API bool igTreeNodeEx_Ptr(const void* ptr_id,
- ImGuiTreeNodeFlags flags,
- const char* fmt,
- ...) {
- va_list args;
- va_start(args, fmt);
- bool ret = ImGui::TreeNodeExV(ptr_id, flags, fmt, args);
- va_end(args);
- return ret;
-}
-CIMGUI_API bool igTreeNodeExV_Str(const char* str_id,
- ImGuiTreeNodeFlags flags,
- const char* fmt,
- va_list args) {
- return ImGui::TreeNodeExV(str_id, flags, fmt, args);
-}
-CIMGUI_API bool igTreeNodeExV_Ptr(const void* ptr_id,
- ImGuiTreeNodeFlags flags,
- const char* fmt,
- va_list args) {
- return ImGui::TreeNodeExV(ptr_id, flags, fmt, args);
-}
-CIMGUI_API void igTreePush_Str(const char* str_id) {
- return ImGui::TreePush(str_id);
-}
-CIMGUI_API void igTreePush_Ptr(const void* ptr_id) {
- return ImGui::TreePush(ptr_id);
-}
-CIMGUI_API void igTreePop() {
- return ImGui::TreePop();
-}
-CIMGUI_API float igGetTreeNodeToLabelSpacing() {
- return ImGui::GetTreeNodeToLabelSpacing();
-}
-CIMGUI_API bool igCollapsingHeader_TreeNodeFlags(const char* label,
- ImGuiTreeNodeFlags flags) {
- return ImGui::CollapsingHeader(label, flags);
-}
-CIMGUI_API bool igCollapsingHeader_BoolPtr(const char* label,
- bool* p_visible,
- ImGuiTreeNodeFlags flags) {
- return ImGui::CollapsingHeader(label, p_visible, flags);
-}
-CIMGUI_API void igSetNextItemOpen(bool is_open, ImGuiCond cond) {
- return ImGui::SetNextItemOpen(is_open, cond);
-}
-CIMGUI_API bool igSelectable_Bool(const char* label,
- bool selected,
- ImGuiSelectableFlags flags,
- const ImVec2 size) {
- return ImGui::Selectable(label, selected, flags, size);
-}
-CIMGUI_API bool igSelectable_BoolPtr(const char* label,
- bool* p_selected,
- ImGuiSelectableFlags flags,
- const ImVec2 size) {
- return ImGui::Selectable(label, p_selected, flags, size);
-}
-CIMGUI_API bool igBeginListBox(const char* label, const ImVec2 size) {
- return ImGui::BeginListBox(label, size);
-}
-CIMGUI_API void igEndListBox() {
- return ImGui::EndListBox();
-}
-CIMGUI_API bool igListBox_Str_arr(const char* label,
- int* current_item,
- const char* const items[],
- int items_count,
- int height_in_items) {
- return ImGui::ListBox(label, current_item, items, items_count,
- height_in_items);
-}
-CIMGUI_API bool igListBox_FnStrPtr(const char* label,
- int* current_item,
- const char* (*getter)(void* user_data,
- int idx),
- void* user_data,
- int items_count,
- int height_in_items) {
- return ImGui::ListBox(label, current_item, getter, user_data, items_count,
- height_in_items);
-}
-CIMGUI_API void igPlotLines_FloatPtr(const char* label,
- const float* values,
- int values_count,
- int values_offset,
- const char* overlay_text,
- float scale_min,
- float scale_max,
- ImVec2 graph_size,
- int stride) {
- return ImGui::PlotLines(label, values, values_count, values_offset,
- overlay_text, scale_min, scale_max, graph_size,
- stride);
-}
-CIMGUI_API void igPlotLines_FnFloatPtr(const char* label,
- float (*values_getter)(void* data,
- int idx),
- void* data,
- int values_count,
- int values_offset,
- const char* overlay_text,
- float scale_min,
- float scale_max,
- ImVec2 graph_size) {
- return ImGui::PlotLines(label, values_getter, data, values_count,
- values_offset, overlay_text, scale_min, scale_max,
- graph_size);
-}
-CIMGUI_API void igPlotHistogram_FloatPtr(const char* label,
- const float* values,
- int values_count,
- int values_offset,
- const char* overlay_text,
- float scale_min,
- float scale_max,
- ImVec2 graph_size,
- int stride) {
- return ImGui::PlotHistogram(label, values, values_count, values_offset,
- overlay_text, scale_min, scale_max, graph_size,
- stride);
-}
-CIMGUI_API void igPlotHistogram_FnFloatPtr(const char* label,
- float (*values_getter)(void* data,
- int idx),
- void* data,
- int values_count,
- int values_offset,
- const char* overlay_text,
- float scale_min,
- float scale_max,
- ImVec2 graph_size) {
- return ImGui::PlotHistogram(label, values_getter, data, values_count,
- values_offset, overlay_text, scale_min, scale_max,
- graph_size);
-}
-CIMGUI_API void igValue_Bool(const char* prefix, bool b) {
- return ImGui::Value(prefix, b);
-}
-CIMGUI_API void igValue_Int(const char* prefix, int v) {
- return ImGui::Value(prefix, v);
-}
-CIMGUI_API void igValue_Uint(const char* prefix, unsigned int v) {
- return ImGui::Value(prefix, v);
-}
-CIMGUI_API void igValue_Float(const char* prefix,
- float v,
- const char* float_format) {
- return ImGui::Value(prefix, v, float_format);
-}
-CIMGUI_API bool igBeginMenuBar() {
- return ImGui::BeginMenuBar();
-}
-CIMGUI_API void igEndMenuBar() {
- return ImGui::EndMenuBar();
-}
-CIMGUI_API bool igBeginMainMenuBar() {
- return ImGui::BeginMainMenuBar();
-}
-CIMGUI_API void igEndMainMenuBar() {
- return ImGui::EndMainMenuBar();
-}
-CIMGUI_API bool igBeginMenu(const char* label, bool enabled) {
- return ImGui::BeginMenu(label, enabled);
-}
-CIMGUI_API void igEndMenu() {
- return ImGui::EndMenu();
-}
-CIMGUI_API bool igMenuItem_Bool(const char* label,
- const char* shortcut,
- bool selected,
- bool enabled) {
- return ImGui::MenuItem(label, shortcut, selected, enabled);
-}
-CIMGUI_API bool igMenuItem_BoolPtr(const char* label,
- const char* shortcut,
- bool* p_selected,
- bool enabled) {
- return ImGui::MenuItem(label, shortcut, p_selected, enabled);
-}
-CIMGUI_API bool igBeginTooltip() {
- return ImGui::BeginTooltip();
-}
-CIMGUI_API void igEndTooltip() {
- return ImGui::EndTooltip();
-}
-CIMGUI_API void igSetTooltip(const char* fmt, ...) {
- va_list args;
- va_start(args, fmt);
- ImGui::SetTooltipV(fmt, args);
- va_end(args);
-}
-CIMGUI_API void igSetTooltipV(const char* fmt, va_list args) {
- return ImGui::SetTooltipV(fmt, args);
-}
-CIMGUI_API bool igBeginItemTooltip() {
- return ImGui::BeginItemTooltip();
-}
-CIMGUI_API void igSetItemTooltip(const char* fmt, ...) {
- va_list args;
- va_start(args, fmt);
- ImGui::SetItemTooltipV(fmt, args);
- va_end(args);
-}
-CIMGUI_API void igSetItemTooltipV(const char* fmt, va_list args) {
- return ImGui::SetItemTooltipV(fmt, args);
-}
-CIMGUI_API bool igBeginPopup(const char* str_id, ImGuiWindowFlags flags) {
- return ImGui::BeginPopup(str_id, flags);
-}
-CIMGUI_API bool igBeginPopupModal(const char* name,
- bool* p_open,
- ImGuiWindowFlags flags) {
- return ImGui::BeginPopupModal(name, p_open, flags);
-}
-CIMGUI_API void igEndPopup() {
- return ImGui::EndPopup();
-}
-CIMGUI_API void igOpenPopup_Str(const char* str_id,
- ImGuiPopupFlags popup_flags) {
- return ImGui::OpenPopup(str_id, popup_flags);
-}
-CIMGUI_API void igOpenPopup_ID(ImGuiID id, ImGuiPopupFlags popup_flags) {
- return ImGui::OpenPopup(id, popup_flags);
-}
-CIMGUI_API void igOpenPopupOnItemClick(const char* str_id,
- ImGuiPopupFlags popup_flags) {
- return ImGui::OpenPopupOnItemClick(str_id, popup_flags);
-}
-CIMGUI_API void igCloseCurrentPopup() {
- return ImGui::CloseCurrentPopup();
-}
-CIMGUI_API bool igBeginPopupContextItem(const char* str_id,
- ImGuiPopupFlags popup_flags) {
- return ImGui::BeginPopupContextItem(str_id, popup_flags);
-}
-CIMGUI_API bool igBeginPopupContextWindow(const char* str_id,
- ImGuiPopupFlags popup_flags) {
- return ImGui::BeginPopupContextWindow(str_id, popup_flags);
-}
-CIMGUI_API bool igBeginPopupContextVoid(const char* str_id,
- ImGuiPopupFlags popup_flags) {
- return ImGui::BeginPopupContextVoid(str_id, popup_flags);
-}
-CIMGUI_API bool igIsPopupOpen_Str(const char* str_id, ImGuiPopupFlags flags) {
- return ImGui::IsPopupOpen(str_id, flags);
-}
-CIMGUI_API bool igBeginTable(const char* str_id,
- int column,
- ImGuiTableFlags flags,
- const ImVec2 outer_size,
- float inner_width) {
- return ImGui::BeginTable(str_id, column, flags, outer_size, inner_width);
-}
-CIMGUI_API void igEndTable() {
- return ImGui::EndTable();
-}
-CIMGUI_API void igTableNextRow(ImGuiTableRowFlags row_flags,
- float min_row_height) {
- return ImGui::TableNextRow(row_flags, min_row_height);
-}
-CIMGUI_API bool igTableNextColumn() {
- return ImGui::TableNextColumn();
-}
-CIMGUI_API bool igTableSetColumnIndex(int column_n) {
- return ImGui::TableSetColumnIndex(column_n);
-}
-CIMGUI_API void igTableSetupColumn(const char* label,
- ImGuiTableColumnFlags flags,
- float init_width_or_weight,
- ImGuiID user_id) {
- return ImGui::TableSetupColumn(label, flags, init_width_or_weight, user_id);
-}
-CIMGUI_API void igTableSetupScrollFreeze(int cols, int rows) {
- return ImGui::TableSetupScrollFreeze(cols, rows);
-}
-CIMGUI_API void igTableHeader(const char* label) {
- return ImGui::TableHeader(label);
-}
-CIMGUI_API void igTableHeadersRow() {
- return ImGui::TableHeadersRow();
-}
-CIMGUI_API void igTableAngledHeadersRow() {
- return ImGui::TableAngledHeadersRow();
-}
-CIMGUI_API ImGuiTableSortSpecs* igTableGetSortSpecs() {
- return ImGui::TableGetSortSpecs();
-}
-CIMGUI_API int igTableGetColumnCount() {
- return ImGui::TableGetColumnCount();
-}
-CIMGUI_API int igTableGetColumnIndex() {
- return ImGui::TableGetColumnIndex();
-}
-CIMGUI_API int igTableGetRowIndex() {
- return ImGui::TableGetRowIndex();
-}
-CIMGUI_API const char* igTableGetColumnName_Int(int column_n) {
- return ImGui::TableGetColumnName(column_n);
-}
-CIMGUI_API ImGuiTableColumnFlags igTableGetColumnFlags(int column_n) {
- return ImGui::TableGetColumnFlags(column_n);
-}
-CIMGUI_API void igTableSetColumnEnabled(int column_n, bool v) {
- return ImGui::TableSetColumnEnabled(column_n, v);
-}
-CIMGUI_API void igTableSetBgColor(ImGuiTableBgTarget target,
- ImU32 color,
- int column_n) {
- return ImGui::TableSetBgColor(target, color, column_n);
-}
-CIMGUI_API void igColumns(int count, const char* id, bool border) {
- return ImGui::Columns(count, id, border);
-}
-CIMGUI_API void igNextColumn() {
- return ImGui::NextColumn();
-}
-CIMGUI_API int igGetColumnIndex() {
- return ImGui::GetColumnIndex();
-}
-CIMGUI_API float igGetColumnWidth(int column_index) {
- return ImGui::GetColumnWidth(column_index);
-}
-CIMGUI_API void igSetColumnWidth(int column_index, float width) {
- return ImGui::SetColumnWidth(column_index, width);
-}
-CIMGUI_API float igGetColumnOffset(int column_index) {
- return ImGui::GetColumnOffset(column_index);
-}
-CIMGUI_API void igSetColumnOffset(int column_index, float offset_x) {
- return ImGui::SetColumnOffset(column_index, offset_x);
-}
-CIMGUI_API int igGetColumnsCount() {
- return ImGui::GetColumnsCount();
-}
-CIMGUI_API bool igBeginTabBar(const char* str_id, ImGuiTabBarFlags flags) {
- return ImGui::BeginTabBar(str_id, flags);
-}
-CIMGUI_API void igEndTabBar() {
- return ImGui::EndTabBar();
-}
-CIMGUI_API bool igBeginTabItem(const char* label,
- bool* p_open,
- ImGuiTabItemFlags flags) {
- return ImGui::BeginTabItem(label, p_open, flags);
-}
-CIMGUI_API void igEndTabItem() {
- return ImGui::EndTabItem();
-}
-CIMGUI_API bool igTabItemButton(const char* label, ImGuiTabItemFlags flags) {
- return ImGui::TabItemButton(label, flags);
-}
-CIMGUI_API void igSetTabItemClosed(const char* tab_or_docked_window_label) {
- return ImGui::SetTabItemClosed(tab_or_docked_window_label);
-}
-CIMGUI_API ImGuiID igDockSpace(ImGuiID id,
- const ImVec2 size,
- ImGuiDockNodeFlags flags,
- const ImGuiWindowClass* window_class) {
- return ImGui::DockSpace(id, size, flags, window_class);
-}
-CIMGUI_API ImGuiID
-igDockSpaceOverViewport(const ImGuiViewport* viewport,
- ImGuiDockNodeFlags flags,
- const ImGuiWindowClass* window_class) {
- return ImGui::DockSpaceOverViewport(viewport, flags, window_class);
-}
-CIMGUI_API void igSetNextWindowDockID(ImGuiID dock_id, ImGuiCond cond) {
- return ImGui::SetNextWindowDockID(dock_id, cond);
-}
-CIMGUI_API void igSetNextWindowClass(const ImGuiWindowClass* window_class) {
- return ImGui::SetNextWindowClass(window_class);
-}
-CIMGUI_API ImGuiID igGetWindowDockID() {
- return ImGui::GetWindowDockID();
-}
-CIMGUI_API bool igIsWindowDocked() {
- return ImGui::IsWindowDocked();
-}
-CIMGUI_API void igLogToTTY(int auto_open_depth) {
- return ImGui::LogToTTY(auto_open_depth);
-}
-CIMGUI_API void igLogToFile(int auto_open_depth, const char* filename) {
- return ImGui::LogToFile(auto_open_depth, filename);
-}
-CIMGUI_API void igLogToClipboard(int auto_open_depth) {
- return ImGui::LogToClipboard(auto_open_depth);
-}
-CIMGUI_API void igLogFinish() {
- return ImGui::LogFinish();
-}
-CIMGUI_API void igLogButtons() {
- return ImGui::LogButtons();
-}
-CIMGUI_API void igLogTextV(const char* fmt, va_list args) {
- return ImGui::LogTextV(fmt, args);
-}
-CIMGUI_API bool igBeginDragDropSource(ImGuiDragDropFlags flags) {
- return ImGui::BeginDragDropSource(flags);
-}
-CIMGUI_API bool igSetDragDropPayload(const char* type,
- const void* data,
- size_t sz,
- ImGuiCond cond) {
- return ImGui::SetDragDropPayload(type, data, sz, cond);
-}
-CIMGUI_API void igEndDragDropSource() {
- return ImGui::EndDragDropSource();
-}
-CIMGUI_API bool igBeginDragDropTarget() {
- return ImGui::BeginDragDropTarget();
-}
-CIMGUI_API const ImGuiPayload* igAcceptDragDropPayload(
- const char* type,
- ImGuiDragDropFlags flags) {
- return ImGui::AcceptDragDropPayload(type, flags);
-}
-CIMGUI_API void igEndDragDropTarget() {
- return ImGui::EndDragDropTarget();
-}
-CIMGUI_API const ImGuiPayload* igGetDragDropPayload() {
- return ImGui::GetDragDropPayload();
-}
-CIMGUI_API void igBeginDisabled(bool disabled) {
- return ImGui::BeginDisabled(disabled);
-}
-CIMGUI_API void igEndDisabled() {
- return ImGui::EndDisabled();
-}
-CIMGUI_API void igPushClipRect(const ImVec2 clip_rect_min,
- const ImVec2 clip_rect_max,
- bool intersect_with_current_clip_rect) {
- return ImGui::PushClipRect(clip_rect_min, clip_rect_max,
- intersect_with_current_clip_rect);
-}
-CIMGUI_API void igPopClipRect() {
- return ImGui::PopClipRect();
-}
-CIMGUI_API void igSetItemDefaultFocus() {
- return ImGui::SetItemDefaultFocus();
-}
-CIMGUI_API void igSetKeyboardFocusHere(int offset) {
- return ImGui::SetKeyboardFocusHere(offset);
-}
-CIMGUI_API void igSetNextItemAllowOverlap() {
- return ImGui::SetNextItemAllowOverlap();
-}
-CIMGUI_API bool igIsItemHovered(ImGuiHoveredFlags flags) {
- return ImGui::IsItemHovered(flags);
-}
-CIMGUI_API bool igIsItemActive() {
- return ImGui::IsItemActive();
-}
-CIMGUI_API bool igIsItemFocused() {
- return ImGui::IsItemFocused();
-}
-CIMGUI_API bool igIsItemClicked(ImGuiMouseButton mouse_button) {
- return ImGui::IsItemClicked(mouse_button);
-}
-CIMGUI_API bool igIsItemVisible() {
- return ImGui::IsItemVisible();
-}
-CIMGUI_API bool igIsItemEdited() {
- return ImGui::IsItemEdited();
-}
-CIMGUI_API bool igIsItemActivated() {
- return ImGui::IsItemActivated();
-}
-CIMGUI_API bool igIsItemDeactivated() {
- return ImGui::IsItemDeactivated();
-}
-CIMGUI_API bool igIsItemDeactivatedAfterEdit() {
- return ImGui::IsItemDeactivatedAfterEdit();
-}
-CIMGUI_API bool igIsItemToggledOpen() {
- return ImGui::IsItemToggledOpen();
-}
-CIMGUI_API bool igIsAnyItemHovered() {
- return ImGui::IsAnyItemHovered();
-}
-CIMGUI_API bool igIsAnyItemActive() {
- return ImGui::IsAnyItemActive();
-}
-CIMGUI_API bool igIsAnyItemFocused() {
- return ImGui::IsAnyItemFocused();
-}
-CIMGUI_API ImGuiID igGetItemID() {
- return ImGui::GetItemID();
-}
-CIMGUI_API void igGetItemRectMin(ImVec2* pOut) {
- *pOut = ImGui::GetItemRectMin();
-}
-CIMGUI_API void igGetItemRectMax(ImVec2* pOut) {
- *pOut = ImGui::GetItemRectMax();
-}
-CIMGUI_API void igGetItemRectSize(ImVec2* pOut) {
- *pOut = ImGui::GetItemRectSize();
-}
-CIMGUI_API ImGuiViewport* igGetMainViewport() {
- return ImGui::GetMainViewport();
-}
-CIMGUI_API ImDrawList* igGetBackgroundDrawList_Nil() {
- return ImGui::GetBackgroundDrawList();
-}
-CIMGUI_API ImDrawList* igGetForegroundDrawList_Nil() {
- return ImGui::GetForegroundDrawList();
-}
-CIMGUI_API ImDrawList* igGetBackgroundDrawList_ViewportPtr(
- ImGuiViewport* viewport) {
- return ImGui::GetBackgroundDrawList(viewport);
-}
-CIMGUI_API ImDrawList* igGetForegroundDrawList_ViewportPtr(
- ImGuiViewport* viewport) {
- return ImGui::GetForegroundDrawList(viewport);
-}
-CIMGUI_API bool igIsRectVisible_Nil(const ImVec2 size) {
- return ImGui::IsRectVisible(size);
-}
-CIMGUI_API bool igIsRectVisible_Vec2(const ImVec2 rect_min,
- const ImVec2 rect_max) {
- return ImGui::IsRectVisible(rect_min, rect_max);
-}
-CIMGUI_API double igGetTime() {
- return ImGui::GetTime();
-}
-CIMGUI_API int igGetFrameCount() {
- return ImGui::GetFrameCount();
-}
-CIMGUI_API ImDrawListSharedData* igGetDrawListSharedData() {
- return ImGui::GetDrawListSharedData();
-}
-CIMGUI_API const char* igGetStyleColorName(ImGuiCol idx) {
- return ImGui::GetStyleColorName(idx);
-}
-CIMGUI_API void igSetStateStorage(ImGuiStorage* storage) {
- return ImGui::SetStateStorage(storage);
-}
-CIMGUI_API ImGuiStorage* igGetStateStorage() {
- return ImGui::GetStateStorage();
-}
-CIMGUI_API void igCalcTextSize(ImVec2* pOut,
- const char* text,
- const char* text_end,
- bool hide_text_after_double_hash,
- float wrap_width) {
- *pOut = ImGui::CalcTextSize(text, text_end, hide_text_after_double_hash,
- wrap_width);
-}
-CIMGUI_API void igColorConvertU32ToFloat4(ImVec4* pOut, ImU32 in) {
- *pOut = ImGui::ColorConvertU32ToFloat4(in);
-}
-CIMGUI_API ImU32 igColorConvertFloat4ToU32(const ImVec4 in) {
- return ImGui::ColorConvertFloat4ToU32(in);
-}
-CIMGUI_API void igColorConvertRGBtoHSV(float r,
- float g,
- float b,
- float* out_h,
- float* out_s,
- float* out_v) {
- return ImGui::ColorConvertRGBtoHSV(r, g, b, *out_h, *out_s, *out_v);
-}
-CIMGUI_API void igColorConvertHSVtoRGB(float h,
- float s,
- float v,
- float* out_r,
- float* out_g,
- float* out_b) {
- return ImGui::ColorConvertHSVtoRGB(h, s, v, *out_r, *out_g, *out_b);
-}
-CIMGUI_API bool igIsKeyDown_Nil(ImGuiKey key) {
- return ImGui::IsKeyDown(key);
-}
-CIMGUI_API bool igIsKeyPressed_Bool(ImGuiKey key, bool repeat) {
- return ImGui::IsKeyPressed(key, repeat);
-}
-CIMGUI_API bool igIsKeyReleased_Nil(ImGuiKey key) {
- return ImGui::IsKeyReleased(key);
-}
-CIMGUI_API bool igIsKeyChordPressed_Nil(ImGuiKeyChord key_chord) {
- return ImGui::IsKeyChordPressed(key_chord);
-}
-CIMGUI_API int igGetKeyPressedAmount(ImGuiKey key,
- float repeat_delay,
- float rate) {
- return ImGui::GetKeyPressedAmount(key, repeat_delay, rate);
-}
-CIMGUI_API const char* igGetKeyName(ImGuiKey key) {
- return ImGui::GetKeyName(key);
-}
-CIMGUI_API void igSetNextFrameWantCaptureKeyboard(bool want_capture_keyboard) {
- return ImGui::SetNextFrameWantCaptureKeyboard(want_capture_keyboard);
-}
-CIMGUI_API bool igIsMouseDown_Nil(ImGuiMouseButton button) {
- return ImGui::IsMouseDown(button);
-}
-CIMGUI_API bool igIsMouseClicked_Bool(ImGuiMouseButton button, bool repeat) {
- return ImGui::IsMouseClicked(button, repeat);
-}
-CIMGUI_API bool igIsMouseReleased_Nil(ImGuiMouseButton button) {
- return ImGui::IsMouseReleased(button);
-}
-CIMGUI_API bool igIsMouseDoubleClicked_Nil(ImGuiMouseButton button) {
- return ImGui::IsMouseDoubleClicked(button);
-}
-CIMGUI_API int igGetMouseClickedCount(ImGuiMouseButton button) {
- return ImGui::GetMouseClickedCount(button);
-}
-CIMGUI_API bool igIsMouseHoveringRect(const ImVec2 r_min,
- const ImVec2 r_max,
- bool clip) {
- return ImGui::IsMouseHoveringRect(r_min, r_max, clip);
-}
-CIMGUI_API bool igIsMousePosValid(const ImVec2* mouse_pos) {
- return ImGui::IsMousePosValid(mouse_pos);
-}
-CIMGUI_API bool igIsAnyMouseDown() {
- return ImGui::IsAnyMouseDown();
-}
-CIMGUI_API void igGetMousePos(ImVec2* pOut) {
- *pOut = ImGui::GetMousePos();
-}
-CIMGUI_API void igGetMousePosOnOpeningCurrentPopup(ImVec2* pOut) {
- *pOut = ImGui::GetMousePosOnOpeningCurrentPopup();
-}
-CIMGUI_API bool igIsMouseDragging(ImGuiMouseButton button,
- float lock_threshold) {
- return ImGui::IsMouseDragging(button, lock_threshold);
-}
-CIMGUI_API void igGetMouseDragDelta(ImVec2* pOut,
- ImGuiMouseButton button,
- float lock_threshold) {
- *pOut = ImGui::GetMouseDragDelta(button, lock_threshold);
-}
-CIMGUI_API void igResetMouseDragDelta(ImGuiMouseButton button) {
- return ImGui::ResetMouseDragDelta(button);
-}
-CIMGUI_API ImGuiMouseCursor igGetMouseCursor() {
- return ImGui::GetMouseCursor();
-}
-CIMGUI_API void igSetMouseCursor(ImGuiMouseCursor cursor_type) {
- return ImGui::SetMouseCursor(cursor_type);
-}
-CIMGUI_API void igSetNextFrameWantCaptureMouse(bool want_capture_mouse) {
- return ImGui::SetNextFrameWantCaptureMouse(want_capture_mouse);
-}
-CIMGUI_API const char* igGetClipboardText() {
- return ImGui::GetClipboardText();
-}
-CIMGUI_API void igSetClipboardText(const char* text) {
- return ImGui::SetClipboardText(text);
-}
-CIMGUI_API void igLoadIniSettingsFromDisk(const char* ini_filename) {
- return ImGui::LoadIniSettingsFromDisk(ini_filename);
-}
-CIMGUI_API void igLoadIniSettingsFromMemory(const char* ini_data,
- size_t ini_size) {
- return ImGui::LoadIniSettingsFromMemory(ini_data, ini_size);
-}
-CIMGUI_API void igSaveIniSettingsToDisk(const char* ini_filename) {
- return ImGui::SaveIniSettingsToDisk(ini_filename);
-}
-CIMGUI_API const char* igSaveIniSettingsToMemory(size_t* out_ini_size) {
- return ImGui::SaveIniSettingsToMemory(out_ini_size);
-}
-CIMGUI_API void igDebugTextEncoding(const char* text) {
- return ImGui::DebugTextEncoding(text);
-}
-CIMGUI_API void igDebugFlashStyleColor(ImGuiCol idx) {
- return ImGui::DebugFlashStyleColor(idx);
-}
-CIMGUI_API void igDebugStartItemPicker() {
- return ImGui::DebugStartItemPicker();
-}
-CIMGUI_API bool igDebugCheckVersionAndDataLayout(const char* version_str,
- size_t sz_io,
- size_t sz_style,
- size_t sz_vec2,
- size_t sz_vec4,
- size_t sz_drawvert,
- size_t sz_drawidx) {
- return ImGui::DebugCheckVersionAndDataLayout(
- version_str, sz_io, sz_style, sz_vec2, sz_vec4, sz_drawvert, sz_drawidx);
-}
-CIMGUI_API void igSetAllocatorFunctions(ImGuiMemAllocFunc alloc_func,
- ImGuiMemFreeFunc free_func,
- void* user_data) {
- return ImGui::SetAllocatorFunctions(alloc_func, free_func, user_data);
-}
-CIMGUI_API void igGetAllocatorFunctions(ImGuiMemAllocFunc* p_alloc_func,
- ImGuiMemFreeFunc* p_free_func,
- void** p_user_data) {
- return ImGui::GetAllocatorFunctions(p_alloc_func, p_free_func, p_user_data);
-}
-CIMGUI_API void* igMemAlloc(size_t size) {
- return ImGui::MemAlloc(size);
-}
-CIMGUI_API void igMemFree(void* ptr) {
- return ImGui::MemFree(ptr);
-}
-CIMGUI_API ImGuiPlatformIO* igGetPlatformIO() {
- return &ImGui::GetPlatformIO();
-}
-CIMGUI_API void igUpdatePlatformWindows() {
- return ImGui::UpdatePlatformWindows();
-}
-CIMGUI_API void igRenderPlatformWindowsDefault(void* platform_render_arg,
- void* renderer_render_arg) {
- return ImGui::RenderPlatformWindowsDefault(platform_render_arg,
- renderer_render_arg);
-}
-CIMGUI_API void igDestroyPlatformWindows() {
- return ImGui::DestroyPlatformWindows();
-}
-CIMGUI_API ImGuiViewport* igFindViewportByID(ImGuiID id) {
- return ImGui::FindViewportByID(id);
-}
-CIMGUI_API ImGuiViewport* igFindViewportByPlatformHandle(
- void* platform_handle) {
- return ImGui::FindViewportByPlatformHandle(platform_handle);
-}
-CIMGUI_API ImGuiTableSortSpecs* ImGuiTableSortSpecs_ImGuiTableSortSpecs(void) {
- return IM_NEW(ImGuiTableSortSpecs)();
-}
-CIMGUI_API void ImGuiTableSortSpecs_destroy(ImGuiTableSortSpecs* self) {
- IM_DELETE(self);
-}
-CIMGUI_API ImGuiTableColumnSortSpecs*
-ImGuiTableColumnSortSpecs_ImGuiTableColumnSortSpecs(void) {
- return IM_NEW(ImGuiTableColumnSortSpecs)();
-}
-CIMGUI_API void ImGuiTableColumnSortSpecs_destroy(
- ImGuiTableColumnSortSpecs* self) {
- IM_DELETE(self);
-}
-CIMGUI_API ImGuiStyle* ImGuiStyle_ImGuiStyle(void) {
- return IM_NEW(ImGuiStyle)();
-}
-CIMGUI_API void ImGuiStyle_destroy(ImGuiStyle* self) {
- IM_DELETE(self);
-}
-CIMGUI_API void ImGuiStyle_ScaleAllSizes(ImGuiStyle* self, float scale_factor) {
- return self->ScaleAllSizes(scale_factor);
-}
-CIMGUI_API void ImGuiIO_AddKeyEvent(ImGuiIO* self, ImGuiKey key, bool down) {
- return self->AddKeyEvent(key, down);
-}
-CIMGUI_API void ImGuiIO_AddKeyAnalogEvent(ImGuiIO* self,
- ImGuiKey key,
- bool down,
- float v) {
- return self->AddKeyAnalogEvent(key, down, v);
-}
-CIMGUI_API void ImGuiIO_AddMousePosEvent(ImGuiIO* self, float x, float y) {
- return self->AddMousePosEvent(x, y);
-}
-CIMGUI_API void ImGuiIO_AddMouseButtonEvent(ImGuiIO* self,
- int button,
- bool down) {
- return self->AddMouseButtonEvent(button, down);
-}
-CIMGUI_API void ImGuiIO_AddMouseWheelEvent(ImGuiIO* self,
- float wheel_x,
- float wheel_y) {
- return self->AddMouseWheelEvent(wheel_x, wheel_y);
-}
-CIMGUI_API void ImGuiIO_AddMouseSourceEvent(ImGuiIO* self,
- ImGuiMouseSource source) {
- return self->AddMouseSourceEvent(source);
-}
-CIMGUI_API void ImGuiIO_AddMouseViewportEvent(ImGuiIO* self, ImGuiID id) {
- return self->AddMouseViewportEvent(id);
-}
-CIMGUI_API void ImGuiIO_AddFocusEvent(ImGuiIO* self, bool focused) {
- return self->AddFocusEvent(focused);
-}
-CIMGUI_API void ImGuiIO_AddInputCharacter(ImGuiIO* self, unsigned int c) {
- return self->AddInputCharacter(c);
-}
-CIMGUI_API void ImGuiIO_AddInputCharacterUTF16(ImGuiIO* self, ImWchar16 c) {
- return self->AddInputCharacterUTF16(c);
-}
-CIMGUI_API void ImGuiIO_AddInputCharactersUTF8(ImGuiIO* self, const char* str) {
- return self->AddInputCharactersUTF8(str);
-}
-CIMGUI_API void ImGuiIO_SetKeyEventNativeData(ImGuiIO* self,
- ImGuiKey key,
- int native_keycode,
- int native_scancode,
- int native_legacy_index) {
- return self->SetKeyEventNativeData(key, native_keycode, native_scancode,
- native_legacy_index);
-}
-CIMGUI_API void ImGuiIO_SetAppAcceptingEvents(ImGuiIO* self,
- bool accepting_events) {
- return self->SetAppAcceptingEvents(accepting_events);
-}
-CIMGUI_API void ImGuiIO_ClearEventsQueue(ImGuiIO* self) {
- return self->ClearEventsQueue();
-}
-CIMGUI_API void ImGuiIO_ClearInputKeys(ImGuiIO* self) {
- return self->ClearInputKeys();
-}
-CIMGUI_API ImGuiIO* ImGuiIO_ImGuiIO(void) {
- return IM_NEW(ImGuiIO)();
-}
-CIMGUI_API void ImGuiIO_destroy(ImGuiIO* self) {
- IM_DELETE(self);
-}
-CIMGUI_API ImGuiInputTextCallbackData*
-ImGuiInputTextCallbackData_ImGuiInputTextCallbackData(void) {
- return IM_NEW(ImGuiInputTextCallbackData)();
-}
-CIMGUI_API void ImGuiInputTextCallbackData_destroy(
- ImGuiInputTextCallbackData* self) {
- IM_DELETE(self);
-}
-CIMGUI_API void ImGuiInputTextCallbackData_DeleteChars(
- ImGuiInputTextCallbackData* self,
- int pos,
- int bytes_count) {
- return self->DeleteChars(pos, bytes_count);
-}
-CIMGUI_API void ImGuiInputTextCallbackData_InsertChars(
- ImGuiInputTextCallbackData* self,
- int pos,
- const char* text,
- const char* text_end) {
- return self->InsertChars(pos, text, text_end);
-}
-CIMGUI_API void ImGuiInputTextCallbackData_SelectAll(
- ImGuiInputTextCallbackData* self) {
- return self->SelectAll();
-}
-CIMGUI_API void ImGuiInputTextCallbackData_ClearSelection(
- ImGuiInputTextCallbackData* self) {
- return self->ClearSelection();
-}
-CIMGUI_API bool ImGuiInputTextCallbackData_HasSelection(
- ImGuiInputTextCallbackData* self) {
- return self->HasSelection();
-}
-CIMGUI_API ImGuiWindowClass* ImGuiWindowClass_ImGuiWindowClass(void) {
- return IM_NEW(ImGuiWindowClass)();
-}
-CIMGUI_API void ImGuiWindowClass_destroy(ImGuiWindowClass* self) {
- IM_DELETE(self);
-}
-CIMGUI_API ImGuiPayload* ImGuiPayload_ImGuiPayload(void) {
- return IM_NEW(ImGuiPayload)();
-}
-CIMGUI_API void ImGuiPayload_destroy(ImGuiPayload* self) {
- IM_DELETE(self);
-}
-CIMGUI_API void ImGuiPayload_Clear(ImGuiPayload* self) {
- return self->Clear();
-}
-CIMGUI_API bool ImGuiPayload_IsDataType(ImGuiPayload* self, const char* type) {
- return self->IsDataType(type);
-}
-CIMGUI_API bool ImGuiPayload_IsPreview(ImGuiPayload* self) {
- return self->IsPreview();
-}
-CIMGUI_API bool ImGuiPayload_IsDelivery(ImGuiPayload* self) {
- return self->IsDelivery();
-}
-CIMGUI_API ImGuiOnceUponAFrame* ImGuiOnceUponAFrame_ImGuiOnceUponAFrame(void) {
- return IM_NEW(ImGuiOnceUponAFrame)();
-}
-CIMGUI_API void ImGuiOnceUponAFrame_destroy(ImGuiOnceUponAFrame* self) {
- IM_DELETE(self);
-}
-CIMGUI_API ImGuiTextFilter* ImGuiTextFilter_ImGuiTextFilter(
- const char* default_filter) {
- return IM_NEW(ImGuiTextFilter)(default_filter);
-}
-CIMGUI_API void ImGuiTextFilter_destroy(ImGuiTextFilter* self) {
- IM_DELETE(self);
-}
-CIMGUI_API bool ImGuiTextFilter_Draw(ImGuiTextFilter* self,
- const char* label,
- float width) {
- return self->Draw(label, width);
-}
-CIMGUI_API bool ImGuiTextFilter_PassFilter(ImGuiTextFilter* self,
- const char* text,
- const char* text_end) {
- return self->PassFilter(text, text_end);
-}
-CIMGUI_API void ImGuiTextFilter_Build(ImGuiTextFilter* self) {
- return self->Build();
-}
-CIMGUI_API void ImGuiTextFilter_Clear(ImGuiTextFilter* self) {
- return self->Clear();
-}
-CIMGUI_API bool ImGuiTextFilter_IsActive(ImGuiTextFilter* self) {
- return self->IsActive();
-}
-CIMGUI_API ImGuiTextRange* ImGuiTextRange_ImGuiTextRange_Nil(void) {
- return IM_NEW(ImGuiTextRange)();
-}
-CIMGUI_API void ImGuiTextRange_destroy(ImGuiTextRange* self) {
- IM_DELETE(self);
-}
-CIMGUI_API ImGuiTextRange* ImGuiTextRange_ImGuiTextRange_Str(const char* _b,
- const char* _e) {
- return IM_NEW(ImGuiTextRange)(_b, _e);
-}
-CIMGUI_API bool ImGuiTextRange_empty(ImGuiTextRange* self) {
- return self->empty();
-}
-CIMGUI_API void ImGuiTextRange_split(ImGuiTextRange* self,
- char separator,
- ImVector_ImGuiTextRange* out) {
- return self->split(separator, out);
-}
-CIMGUI_API ImGuiTextBuffer* ImGuiTextBuffer_ImGuiTextBuffer(void) {
- return IM_NEW(ImGuiTextBuffer)();
-}
-CIMGUI_API void ImGuiTextBuffer_destroy(ImGuiTextBuffer* self) {
- IM_DELETE(self);
-}
-CIMGUI_API const char* ImGuiTextBuffer_begin(ImGuiTextBuffer* self) {
- return self->begin();
-}
-CIMGUI_API const char* ImGuiTextBuffer_end(ImGuiTextBuffer* self) {
- return self->end();
-}
-CIMGUI_API int ImGuiTextBuffer_size(ImGuiTextBuffer* self) {
- return self->size();
-}
-CIMGUI_API bool ImGuiTextBuffer_empty(ImGuiTextBuffer* self) {
- return self->empty();
-}
-CIMGUI_API void ImGuiTextBuffer_clear(ImGuiTextBuffer* self) {
- return self->clear();
-}
-CIMGUI_API void ImGuiTextBuffer_reserve(ImGuiTextBuffer* self, int capacity) {
- return self->reserve(capacity);
-}
-CIMGUI_API const char* ImGuiTextBuffer_c_str(ImGuiTextBuffer* self) {
- return self->c_str();
-}
-CIMGUI_API void ImGuiTextBuffer_append(ImGuiTextBuffer* self,
- const char* str,
- const char* str_end) {
- return self->append(str, str_end);
-}
-CIMGUI_API void ImGuiTextBuffer_appendfv(ImGuiTextBuffer* self,
- const char* fmt,
- va_list args) {
- return self->appendfv(fmt, args);
-}
-CIMGUI_API ImGuiStoragePair* ImGuiStoragePair_ImGuiStoragePair_Int(ImGuiID _key,
- int _val) {
- return IM_NEW(ImGuiStoragePair)(_key, _val);
-}
-CIMGUI_API void ImGuiStoragePair_destroy(ImGuiStoragePair* self) {
- IM_DELETE(self);
-}
-CIMGUI_API ImGuiStoragePair* ImGuiStoragePair_ImGuiStoragePair_Float(
- ImGuiID _key,
- float _val) {
- return IM_NEW(ImGuiStoragePair)(_key, _val);
-}
-CIMGUI_API ImGuiStoragePair* ImGuiStoragePair_ImGuiStoragePair_Ptr(ImGuiID _key,
- void* _val) {
- return IM_NEW(ImGuiStoragePair)(_key, _val);
-}
-CIMGUI_API void ImGuiStorage_Clear(ImGuiStorage* self) {
- return self->Clear();
-}
-CIMGUI_API int ImGuiStorage_GetInt(ImGuiStorage* self,
- ImGuiID key,
- int default_val) {
- return self->GetInt(key, default_val);
-}
-CIMGUI_API void ImGuiStorage_SetInt(ImGuiStorage* self, ImGuiID key, int val) {
- return self->SetInt(key, val);
-}
-CIMGUI_API bool ImGuiStorage_GetBool(ImGuiStorage* self,
- ImGuiID key,
- bool default_val) {
- return self->GetBool(key, default_val);
-}
-CIMGUI_API void ImGuiStorage_SetBool(ImGuiStorage* self,
- ImGuiID key,
- bool val) {
- return self->SetBool(key, val);
-}
-CIMGUI_API float ImGuiStorage_GetFloat(ImGuiStorage* self,
- ImGuiID key,
- float default_val) {
- return self->GetFloat(key, default_val);
-}
-CIMGUI_API void ImGuiStorage_SetFloat(ImGuiStorage* self,
- ImGuiID key,
- float val) {
- return self->SetFloat(key, val);
-}
-CIMGUI_API void* ImGuiStorage_GetVoidPtr(ImGuiStorage* self, ImGuiID key) {
- return self->GetVoidPtr(key);
-}
-CIMGUI_API void ImGuiStorage_SetVoidPtr(ImGuiStorage* self,
- ImGuiID key,
- void* val) {
- return self->SetVoidPtr(key, val);
-}
-CIMGUI_API int* ImGuiStorage_GetIntRef(ImGuiStorage* self,
- ImGuiID key,
- int default_val) {
- return self->GetIntRef(key, default_val);
-}
-CIMGUI_API bool* ImGuiStorage_GetBoolRef(ImGuiStorage* self,
- ImGuiID key,
- bool default_val) {
- return self->GetBoolRef(key, default_val);
-}
-CIMGUI_API float* ImGuiStorage_GetFloatRef(ImGuiStorage* self,
- ImGuiID key,
- float default_val) {
- return self->GetFloatRef(key, default_val);
-}
-CIMGUI_API void** ImGuiStorage_GetVoidPtrRef(ImGuiStorage* self,
- ImGuiID key,
- void* default_val) {
- return self->GetVoidPtrRef(key, default_val);
-}
-CIMGUI_API void ImGuiStorage_BuildSortByKey(ImGuiStorage* self) {
- return self->BuildSortByKey();
-}
-CIMGUI_API void ImGuiStorage_SetAllInt(ImGuiStorage* self, int val) {
- return self->SetAllInt(val);
-}
-CIMGUI_API ImGuiListClipper* ImGuiListClipper_ImGuiListClipper(void) {
- return IM_NEW(ImGuiListClipper)();
-}
-CIMGUI_API void ImGuiListClipper_destroy(ImGuiListClipper* self) {
- IM_DELETE(self);
-}
-CIMGUI_API void ImGuiListClipper_Begin(ImGuiListClipper* self,
- int items_count,
- float items_height) {
- return self->Begin(items_count, items_height);
-}
-CIMGUI_API void ImGuiListClipper_End(ImGuiListClipper* self) {
- return self->End();
-}
-CIMGUI_API bool ImGuiListClipper_Step(ImGuiListClipper* self) {
- return self->Step();
-}
-CIMGUI_API void ImGuiListClipper_IncludeItemByIndex(ImGuiListClipper* self,
- int item_index) {
- return self->IncludeItemByIndex(item_index);
-}
-CIMGUI_API void ImGuiListClipper_IncludeItemsByIndex(ImGuiListClipper* self,
- int item_begin,
- int item_end) {
- return self->IncludeItemsByIndex(item_begin, item_end);
-}
-CIMGUI_API ImColor* ImColor_ImColor_Nil(void) {
- return IM_NEW(ImColor)();
-}
-CIMGUI_API void ImColor_destroy(ImColor* self) {
- IM_DELETE(self);
-}
-CIMGUI_API ImColor* ImColor_ImColor_Float(float r, float g, float b, float a) {
- return IM_NEW(ImColor)(r, g, b, a);
-}
-CIMGUI_API ImColor* ImColor_ImColor_Vec4(const ImVec4 col) {
- return IM_NEW(ImColor)(col);
-}
-CIMGUI_API ImColor* ImColor_ImColor_Int(int r, int g, int b, int a) {
- return IM_NEW(ImColor)(r, g, b, a);
-}
-CIMGUI_API ImColor* ImColor_ImColor_U32(ImU32 rgba) {
- return IM_NEW(ImColor)(rgba);
-}
-CIMGUI_API void ImColor_SetHSV(ImColor* self,
- float h,
- float s,
- float v,
- float a) {
- return self->SetHSV(h, s, v, a);
-}
-CIMGUI_API void ImColor_HSV(ImColor* pOut, float h, float s, float v, float a) {
- *pOut = ImColor::HSV(h, s, v, a);
-}
-CIMGUI_API ImDrawCmd* ImDrawCmd_ImDrawCmd(void) {
- return IM_NEW(ImDrawCmd)();
-}
-CIMGUI_API void ImDrawCmd_destroy(ImDrawCmd* self) {
- IM_DELETE(self);
-}
-CIMGUI_API ImTextureID ImDrawCmd_GetTexID(ImDrawCmd* self) {
- return self->GetTexID();
-}
-CIMGUI_API ImDrawListSplitter* ImDrawListSplitter_ImDrawListSplitter(void) {
- return IM_NEW(ImDrawListSplitter)();
-}
-CIMGUI_API void ImDrawListSplitter_destroy(ImDrawListSplitter* self) {
- IM_DELETE(self);
-}
-CIMGUI_API void ImDrawListSplitter_Clear(ImDrawListSplitter* self) {
- return self->Clear();
-}
-CIMGUI_API void ImDrawListSplitter_ClearFreeMemory(ImDrawListSplitter* self) {
- return self->ClearFreeMemory();
-}
-CIMGUI_API void ImDrawListSplitter_Split(ImDrawListSplitter* self,
- ImDrawList* draw_list,
- int count) {
- return self->Split(draw_list, count);
-}
-CIMGUI_API void ImDrawListSplitter_Merge(ImDrawListSplitter* self,
- ImDrawList* draw_list) {
- return self->Merge(draw_list);
-}
-CIMGUI_API void ImDrawListSplitter_SetCurrentChannel(ImDrawListSplitter* self,
- ImDrawList* draw_list,
- int channel_idx) {
- return self->SetCurrentChannel(draw_list, channel_idx);
-}
-CIMGUI_API ImDrawList* ImDrawList_ImDrawList(
- ImDrawListSharedData* shared_data) {
- return IM_NEW(ImDrawList)(shared_data);
-}
-CIMGUI_API void ImDrawList_destroy(ImDrawList* self) {
- IM_DELETE(self);
-}
-CIMGUI_API void ImDrawList_PushClipRect(ImDrawList* self,
- const ImVec2 clip_rect_min,
- const ImVec2 clip_rect_max,
- bool intersect_with_current_clip_rect) {
- return self->PushClipRect(clip_rect_min, clip_rect_max,
- intersect_with_current_clip_rect);
-}
-CIMGUI_API void ImDrawList_PushClipRectFullScreen(ImDrawList* self) {
- return self->PushClipRectFullScreen();
-}
-CIMGUI_API void ImDrawList_PopClipRect(ImDrawList* self) {
- return self->PopClipRect();
-}
-CIMGUI_API void ImDrawList_PushTextureID(ImDrawList* self,
- ImTextureID texture_id) {
- return self->PushTextureID(texture_id);
-}
-CIMGUI_API void ImDrawList_PopTextureID(ImDrawList* self) {
- return self->PopTextureID();
-}
-CIMGUI_API void ImDrawList_GetClipRectMin(ImVec2* pOut, ImDrawList* self) {
- *pOut = self->GetClipRectMin();
-}
-CIMGUI_API void ImDrawList_GetClipRectMax(ImVec2* pOut, ImDrawList* self) {
- *pOut = self->GetClipRectMax();
-}
-CIMGUI_API void ImDrawList_AddLine(ImDrawList* self,
- const ImVec2 p1,
- const ImVec2 p2,
- ImU32 col,
- float thickness) {
- return self->AddLine(p1, p2, col, thickness);
-}
-CIMGUI_API void ImDrawList_AddRect(ImDrawList* self,
- const ImVec2 p_min,
- const ImVec2 p_max,
- ImU32 col,
- float rounding,
- ImDrawFlags flags,
- float thickness) {
- return self->AddRect(p_min, p_max, col, rounding, flags, thickness);
-}
-CIMGUI_API void ImDrawList_AddRectFilled(ImDrawList* self,
- const ImVec2 p_min,
- const ImVec2 p_max,
- ImU32 col,
- float rounding,
- ImDrawFlags flags) {
- return self->AddRectFilled(p_min, p_max, col, rounding, flags);
-}
-CIMGUI_API void ImDrawList_AddRectFilledMultiColor(ImDrawList* self,
- const ImVec2 p_min,
- const ImVec2 p_max,
- ImU32 col_upr_left,
- ImU32 col_upr_right,
- ImU32 col_bot_right,
- ImU32 col_bot_left) {
- return self->AddRectFilledMultiColor(
- p_min, p_max, col_upr_left, col_upr_right, col_bot_right, col_bot_left);
-}
-CIMGUI_API void ImDrawList_AddQuad(ImDrawList* self,
- const ImVec2 p1,
- const ImVec2 p2,
- const ImVec2 p3,
- const ImVec2 p4,
- ImU32 col,
- float thickness) {
- return self->AddQuad(p1, p2, p3, p4, col, thickness);
-}
-CIMGUI_API void ImDrawList_AddQuadFilled(ImDrawList* self,
- const ImVec2 p1,
- const ImVec2 p2,
- const ImVec2 p3,
- const ImVec2 p4,
- ImU32 col) {
- return self->AddQuadFilled(p1, p2, p3, p4, col);
-}
-CIMGUI_API void ImDrawList_AddTriangle(ImDrawList* self,
- const ImVec2 p1,
- const ImVec2 p2,
- const ImVec2 p3,
- ImU32 col,
- float thickness) {
- return self->AddTriangle(p1, p2, p3, col, thickness);
-}
-CIMGUI_API void ImDrawList_AddTriangleFilled(ImDrawList* self,
- const ImVec2 p1,
- const ImVec2 p2,
- const ImVec2 p3,
- ImU32 col) {
- return self->AddTriangleFilled(p1, p2, p3, col);
-}
-CIMGUI_API void ImDrawList_AddCircle(ImDrawList* self,
- const ImVec2 center,
- float radius,
- ImU32 col,
- int num_segments,
- float thickness) {
- return self->AddCircle(center, radius, col, num_segments, thickness);
-}
-CIMGUI_API void ImDrawList_AddCircleFilled(ImDrawList* self,
- const ImVec2 center,
- float radius,
- ImU32 col,
- int num_segments) {
- return self->AddCircleFilled(center, radius, col, num_segments);
-}
-CIMGUI_API void ImDrawList_AddNgon(ImDrawList* self,
- const ImVec2 center,
- float radius,
- ImU32 col,
- int num_segments,
- float thickness) {
- return self->AddNgon(center, radius, col, num_segments, thickness);
-}
-CIMGUI_API void ImDrawList_AddNgonFilled(ImDrawList* self,
- const ImVec2 center,
- float radius,
- ImU32 col,
- int num_segments) {
- return self->AddNgonFilled(center, radius, col, num_segments);
-}
-CIMGUI_API void ImDrawList_AddEllipse(ImDrawList* self,
- const ImVec2 center,
- const ImVec2 radius,
- ImU32 col,
- float rot,
- int num_segments,
- float thickness) {
- return self->AddEllipse(center, radius, col, rot, num_segments, thickness);
-}
-CIMGUI_API void ImDrawList_AddEllipseFilled(ImDrawList* self,
- const ImVec2 center,
- const ImVec2 radius,
- ImU32 col,
- float rot,
- int num_segments) {
- return self->AddEllipseFilled(center, radius, col, rot, num_segments);
-}
-CIMGUI_API void ImDrawList_AddText_Vec2(ImDrawList* self,
- const ImVec2 pos,
- ImU32 col,
- const char* text_begin,
- const char* text_end) {
- return self->AddText(pos, col, text_begin, text_end);
-}
-CIMGUI_API void ImDrawList_AddText_FontPtr(ImDrawList* self,
- const ImFont* font,
- float font_size,
- const ImVec2 pos,
- ImU32 col,
- const char* text_begin,
- const char* text_end,
- float wrap_width,
- const ImVec4* cpu_fine_clip_rect) {
- return self->AddText(font, font_size, pos, col, text_begin, text_end,
- wrap_width, cpu_fine_clip_rect);
-}
-CIMGUI_API void ImDrawList_AddBezierCubic(ImDrawList* self,
- const ImVec2 p1,
- const ImVec2 p2,
- const ImVec2 p3,
- const ImVec2 p4,
- ImU32 col,
- float thickness,
- int num_segments) {
- return self->AddBezierCubic(p1, p2, p3, p4, col, thickness, num_segments);
-}
-CIMGUI_API void ImDrawList_AddBezierQuadratic(ImDrawList* self,
- const ImVec2 p1,
- const ImVec2 p2,
- const ImVec2 p3,
- ImU32 col,
- float thickness,
- int num_segments) {
- return self->AddBezierQuadratic(p1, p2, p3, col, thickness, num_segments);
-}
-CIMGUI_API void ImDrawList_AddPolyline(ImDrawList* self,
- const ImVec2* points,
- int num_points,
- ImU32 col,
- ImDrawFlags flags,
- float thickness) {
- return self->AddPolyline(points, num_points, col, flags, thickness);
-}
-CIMGUI_API void ImDrawList_AddConvexPolyFilled(ImDrawList* self,
- const ImVec2* points,
- int num_points,
- ImU32 col) {
- return self->AddConvexPolyFilled(points, num_points, col);
-}
-CIMGUI_API void ImDrawList_AddConcavePolyFilled(ImDrawList* self,
- const ImVec2* points,
- int num_points,
- ImU32 col) {
- return self->AddConcavePolyFilled(points, num_points, col);
-}
-CIMGUI_API void ImDrawList_AddImage(ImDrawList* self,
- ImTextureID user_texture_id,
- const ImVec2 p_min,
- const ImVec2 p_max,
- const ImVec2 uv_min,
- const ImVec2 uv_max,
- ImU32 col) {
- return self->AddImage(user_texture_id, p_min, p_max, uv_min, uv_max, col);
-}
-CIMGUI_API void ImDrawList_AddImageQuad(ImDrawList* self,
- ImTextureID user_texture_id,
- const ImVec2 p1,
- const ImVec2 p2,
- const ImVec2 p3,
- const ImVec2 p4,
- const ImVec2 uv1,
- const ImVec2 uv2,
- const ImVec2 uv3,
- const ImVec2 uv4,
- ImU32 col) {
- return self->AddImageQuad(user_texture_id, p1, p2, p3, p4, uv1, uv2, uv3, uv4,
- col);
-}
-CIMGUI_API void ImDrawList_AddImageRounded(ImDrawList* self,
- ImTextureID user_texture_id,
- const ImVec2 p_min,
- const ImVec2 p_max,
- const ImVec2 uv_min,
- const ImVec2 uv_max,
- ImU32 col,
- float rounding,
- ImDrawFlags flags) {
- return self->AddImageRounded(user_texture_id, p_min, p_max, uv_min, uv_max,
- col, rounding, flags);
-}
-CIMGUI_API void ImDrawList_PathClear(ImDrawList* self) {
- return self->PathClear();
-}
-CIMGUI_API void ImDrawList_PathLineTo(ImDrawList* self, const ImVec2 pos) {
- return self->PathLineTo(pos);
-}
-CIMGUI_API void ImDrawList_PathLineToMergeDuplicate(ImDrawList* self,
- const ImVec2 pos) {
- return self->PathLineToMergeDuplicate(pos);
-}
-CIMGUI_API void ImDrawList_PathFillConvex(ImDrawList* self, ImU32 col) {
- return self->PathFillConvex(col);
-}
-CIMGUI_API void ImDrawList_PathFillConcave(ImDrawList* self, ImU32 col) {
- return self->PathFillConcave(col);
-}
-CIMGUI_API void ImDrawList_PathStroke(ImDrawList* self,
- ImU32 col,
- ImDrawFlags flags,
- float thickness) {
- return self->PathStroke(col, flags, thickness);
-}
-CIMGUI_API void ImDrawList_PathArcTo(ImDrawList* self,
- const ImVec2 center,
- float radius,
- float a_min,
- float a_max,
- int num_segments) {
- return self->PathArcTo(center, radius, a_min, a_max, num_segments);
-}
-CIMGUI_API void ImDrawList_PathArcToFast(ImDrawList* self,
- const ImVec2 center,
- float radius,
- int a_min_of_12,
- int a_max_of_12) {
- return self->PathArcToFast(center, radius, a_min_of_12, a_max_of_12);
-}
-CIMGUI_API void ImDrawList_PathEllipticalArcTo(ImDrawList* self,
- const ImVec2 center,
- const ImVec2 radius,
- float rot,
- float a_min,
- float a_max,
- int num_segments) {
- return self->PathEllipticalArcTo(center, radius, rot, a_min, a_max,
- num_segments);
-}
-CIMGUI_API void ImDrawList_PathBezierCubicCurveTo(ImDrawList* self,
- const ImVec2 p2,
- const ImVec2 p3,
- const ImVec2 p4,
- int num_segments) {
- return self->PathBezierCubicCurveTo(p2, p3, p4, num_segments);
-}
-CIMGUI_API void ImDrawList_PathBezierQuadraticCurveTo(ImDrawList* self,
- const ImVec2 p2,
- const ImVec2 p3,
- int num_segments) {
- return self->PathBezierQuadraticCurveTo(p2, p3, num_segments);
-}
-CIMGUI_API void ImDrawList_PathRect(ImDrawList* self,
- const ImVec2 rect_min,
- const ImVec2 rect_max,
- float rounding,
- ImDrawFlags flags) {
- return self->PathRect(rect_min, rect_max, rounding, flags);
-}
-CIMGUI_API void ImDrawList_AddCallback(ImDrawList* self,
- ImDrawCallback callback,
- void* callback_data) {
- return self->AddCallback(callback, callback_data);
-}
-CIMGUI_API void ImDrawList_AddDrawCmd(ImDrawList* self) {
- return self->AddDrawCmd();
-}
-CIMGUI_API ImDrawList* ImDrawList_CloneOutput(ImDrawList* self) {
- return self->CloneOutput();
-}
-CIMGUI_API void ImDrawList_ChannelsSplit(ImDrawList* self, int count) {
- return self->ChannelsSplit(count);
-}
-CIMGUI_API void ImDrawList_ChannelsMerge(ImDrawList* self) {
- return self->ChannelsMerge();
-}
-CIMGUI_API void ImDrawList_ChannelsSetCurrent(ImDrawList* self, int n) {
- return self->ChannelsSetCurrent(n);
-}
-CIMGUI_API void ImDrawList_PrimReserve(ImDrawList* self,
- int idx_count,
- int vtx_count) {
- return self->PrimReserve(idx_count, vtx_count);
-}
-CIMGUI_API void ImDrawList_PrimUnreserve(ImDrawList* self,
- int idx_count,
- int vtx_count) {
- return self->PrimUnreserve(idx_count, vtx_count);
-}
-CIMGUI_API void ImDrawList_PrimRect(ImDrawList* self,
- const ImVec2 a,
- const ImVec2 b,
- ImU32 col) {
- return self->PrimRect(a, b, col);
-}
-CIMGUI_API void ImDrawList_PrimRectUV(ImDrawList* self,
- const ImVec2 a,
- const ImVec2 b,
- const ImVec2 uv_a,
- const ImVec2 uv_b,
- ImU32 col) {
- return self->PrimRectUV(a, b, uv_a, uv_b, col);
-}
-CIMGUI_API void ImDrawList_PrimQuadUV(ImDrawList* self,
- const ImVec2 a,
- const ImVec2 b,
- const ImVec2 c,
- const ImVec2 d,
- const ImVec2 uv_a,
- const ImVec2 uv_b,
- const ImVec2 uv_c,
- const ImVec2 uv_d,
- ImU32 col) {
- return self->PrimQuadUV(a, b, c, d, uv_a, uv_b, uv_c, uv_d, col);
-}
-CIMGUI_API void ImDrawList_PrimWriteVtx(ImDrawList* self,
- const ImVec2 pos,
- const ImVec2 uv,
- ImU32 col) {
- return self->PrimWriteVtx(pos, uv, col);
-}
-CIMGUI_API void ImDrawList_PrimWriteIdx(ImDrawList* self, ImDrawIdx idx) {
- return self->PrimWriteIdx(idx);
-}
-CIMGUI_API void ImDrawList_PrimVtx(ImDrawList* self,
- const ImVec2 pos,
- const ImVec2 uv,
- ImU32 col) {
- return self->PrimVtx(pos, uv, col);
-}
-CIMGUI_API void ImDrawList__ResetForNewFrame(ImDrawList* self) {
- return self->_ResetForNewFrame();
-}
-CIMGUI_API void ImDrawList__ClearFreeMemory(ImDrawList* self) {
- return self->_ClearFreeMemory();
-}
-CIMGUI_API void ImDrawList__PopUnusedDrawCmd(ImDrawList* self) {
- return self->_PopUnusedDrawCmd();
-}
-CIMGUI_API void ImDrawList__TryMergeDrawCmds(ImDrawList* self) {
- return self->_TryMergeDrawCmds();
-}
-CIMGUI_API void ImDrawList__OnChangedClipRect(ImDrawList* self) {
- return self->_OnChangedClipRect();
-}
-CIMGUI_API void ImDrawList__OnChangedTextureID(ImDrawList* self) {
- return self->_OnChangedTextureID();
-}
-CIMGUI_API void ImDrawList__OnChangedVtxOffset(ImDrawList* self) {
- return self->_OnChangedVtxOffset();
-}
-CIMGUI_API int ImDrawList__CalcCircleAutoSegmentCount(ImDrawList* self,
- float radius) {
- return self->_CalcCircleAutoSegmentCount(radius);
-}
-CIMGUI_API void ImDrawList__PathArcToFastEx(ImDrawList* self,
- const ImVec2 center,
- float radius,
- int a_min_sample,
- int a_max_sample,
- int a_step) {
- return self->_PathArcToFastEx(center, radius, a_min_sample, a_max_sample,
- a_step);
-}
-CIMGUI_API void ImDrawList__PathArcToN(ImDrawList* self,
- const ImVec2 center,
- float radius,
- float a_min,
- float a_max,
- int num_segments) {
- return self->_PathArcToN(center, radius, a_min, a_max, num_segments);
-}
-CIMGUI_API ImDrawData* ImDrawData_ImDrawData(void) {
- return IM_NEW(ImDrawData)();
-}
-CIMGUI_API void ImDrawData_destroy(ImDrawData* self) {
- IM_DELETE(self);
-}
-CIMGUI_API void ImDrawData_Clear(ImDrawData* self) {
- return self->Clear();
-}
-CIMGUI_API void ImDrawData_AddDrawList(ImDrawData* self,
- ImDrawList* draw_list) {
- return self->AddDrawList(draw_list);
-}
-CIMGUI_API void ImDrawData_DeIndexAllBuffers(ImDrawData* self) {
- return self->DeIndexAllBuffers();
-}
-CIMGUI_API void ImDrawData_ScaleClipRects(ImDrawData* self,
- const ImVec2 fb_scale) {
- return self->ScaleClipRects(fb_scale);
-}
-CIMGUI_API ImFontConfig* ImFontConfig_ImFontConfig(void) {
- return IM_NEW(ImFontConfig)();
-}
-CIMGUI_API void ImFontConfig_destroy(ImFontConfig* self) {
- IM_DELETE(self);
-}
-CIMGUI_API ImFontGlyphRangesBuilder*
-ImFontGlyphRangesBuilder_ImFontGlyphRangesBuilder(void) {
- return IM_NEW(ImFontGlyphRangesBuilder)();
-}
-CIMGUI_API void ImFontGlyphRangesBuilder_destroy(
- ImFontGlyphRangesBuilder* self) {
- IM_DELETE(self);
-}
-CIMGUI_API void ImFontGlyphRangesBuilder_Clear(ImFontGlyphRangesBuilder* self) {
- return self->Clear();
-}
-CIMGUI_API bool ImFontGlyphRangesBuilder_GetBit(ImFontGlyphRangesBuilder* self,
- size_t n) {
- return self->GetBit(n);
-}
-CIMGUI_API void ImFontGlyphRangesBuilder_SetBit(ImFontGlyphRangesBuilder* self,
- size_t n) {
- return self->SetBit(n);
-}
-CIMGUI_API void ImFontGlyphRangesBuilder_AddChar(ImFontGlyphRangesBuilder* self,
- ImWchar c) {
- return self->AddChar(c);
-}
-CIMGUI_API void ImFontGlyphRangesBuilder_AddText(ImFontGlyphRangesBuilder* self,
- const char* text,
- const char* text_end) {
- return self->AddText(text, text_end);
-}
-CIMGUI_API void ImFontGlyphRangesBuilder_AddRanges(
- ImFontGlyphRangesBuilder* self,
- const ImWchar* ranges) {
- return self->AddRanges(ranges);
-}
-CIMGUI_API void ImFontGlyphRangesBuilder_BuildRanges(
- ImFontGlyphRangesBuilder* self,
- ImVector_ImWchar* out_ranges) {
- return self->BuildRanges(out_ranges);
-}
-CIMGUI_API ImFontAtlasCustomRect* ImFontAtlasCustomRect_ImFontAtlasCustomRect(
- void) {
- return IM_NEW(ImFontAtlasCustomRect)();
-}
-CIMGUI_API void ImFontAtlasCustomRect_destroy(ImFontAtlasCustomRect* self) {
- IM_DELETE(self);
-}
-CIMGUI_API bool ImFontAtlasCustomRect_IsPacked(ImFontAtlasCustomRect* self) {
- return self->IsPacked();
-}
-CIMGUI_API ImFontAtlas* ImFontAtlas_ImFontAtlas(void) {
- return IM_NEW(ImFontAtlas)();
-}
-CIMGUI_API void ImFontAtlas_destroy(ImFontAtlas* self) {
- IM_DELETE(self);
-}
-CIMGUI_API ImFont* ImFontAtlas_AddFont(ImFontAtlas* self,
- const ImFontConfig* font_cfg) {
- return self->AddFont(font_cfg);
-}
-CIMGUI_API ImFont* ImFontAtlas_AddFontDefault(ImFontAtlas* self,
- const ImFontConfig* font_cfg) {
- return self->AddFontDefault(font_cfg);
-}
-CIMGUI_API ImFont* ImFontAtlas_AddFontFromFileTTF(ImFontAtlas* self,
- const char* filename,
- float size_pixels,
- const ImFontConfig* font_cfg,
- const ImWchar* glyph_ranges) {
- return self->AddFontFromFileTTF(filename, size_pixels, font_cfg,
- glyph_ranges);
-}
-CIMGUI_API ImFont* ImFontAtlas_AddFontFromMemoryTTF(
- ImFontAtlas* self,
- void* font_data,
- int font_data_size,
- float size_pixels,
- const ImFontConfig* font_cfg,
- const ImWchar* glyph_ranges) {
- return self->AddFontFromMemoryTTF(font_data, font_data_size, size_pixels,
- font_cfg, glyph_ranges);
-}
-CIMGUI_API ImFont* ImFontAtlas_AddFontFromMemoryCompressedTTF(
- ImFontAtlas* self,
- const void* compressed_font_data,
- int compressed_font_data_size,
- float size_pixels,
- const ImFontConfig* font_cfg,
- const ImWchar* glyph_ranges) {
- return self->AddFontFromMemoryCompressedTTF(
- compressed_font_data, compressed_font_data_size, size_pixels, font_cfg,
- glyph_ranges);
-}
-CIMGUI_API ImFont* ImFontAtlas_AddFontFromMemoryCompressedBase85TTF(
- ImFontAtlas* self,
- const char* compressed_font_data_base85,
- float size_pixels,
- const ImFontConfig* font_cfg,
- const ImWchar* glyph_ranges) {
- return self->AddFontFromMemoryCompressedBase85TTF(
- compressed_font_data_base85, size_pixels, font_cfg, glyph_ranges);
-}
-CIMGUI_API void ImFontAtlas_ClearInputData(ImFontAtlas* self) {
- return self->ClearInputData();
-}
-CIMGUI_API void ImFontAtlas_ClearTexData(ImFontAtlas* self) {
- return self->ClearTexData();
-}
-CIMGUI_API void ImFontAtlas_ClearFonts(ImFontAtlas* self) {
- return self->ClearFonts();
-}
-CIMGUI_API void ImFontAtlas_Clear(ImFontAtlas* self) {
- return self->Clear();
-}
-CIMGUI_API bool ImFontAtlas_Build(ImFontAtlas* self) {
- return self->Build();
-}
-CIMGUI_API void ImFontAtlas_GetTexDataAsAlpha8(ImFontAtlas* self,
- unsigned char** out_pixels,
- int* out_width,
- int* out_height,
- int* out_bytes_per_pixel) {
- return self->GetTexDataAsAlpha8(out_pixels, out_width, out_height,
- out_bytes_per_pixel);
-}
-CIMGUI_API void ImFontAtlas_GetTexDataAsRGBA32(ImFontAtlas* self,
- unsigned char** out_pixels,
- int* out_width,
- int* out_height,
- int* out_bytes_per_pixel) {
- return self->GetTexDataAsRGBA32(out_pixels, out_width, out_height,
- out_bytes_per_pixel);
-}
-CIMGUI_API bool ImFontAtlas_IsBuilt(ImFontAtlas* self) {
- return self->IsBuilt();
-}
-CIMGUI_API void ImFontAtlas_SetTexID(ImFontAtlas* self, ImTextureID id) {
- return self->SetTexID(id);
-}
-CIMGUI_API const ImWchar* ImFontAtlas_GetGlyphRangesDefault(ImFontAtlas* self) {
- return self->GetGlyphRangesDefault();
-}
-CIMGUI_API const ImWchar* ImFontAtlas_GetGlyphRangesGreek(ImFontAtlas* self) {
- return self->GetGlyphRangesGreek();
-}
-CIMGUI_API const ImWchar* ImFontAtlas_GetGlyphRangesKorean(ImFontAtlas* self) {
- return self->GetGlyphRangesKorean();
-}
-CIMGUI_API const ImWchar* ImFontAtlas_GetGlyphRangesJapanese(
- ImFontAtlas* self) {
- return self->GetGlyphRangesJapanese();
-}
-CIMGUI_API const ImWchar* ImFontAtlas_GetGlyphRangesChineseFull(
- ImFontAtlas* self) {
- return self->GetGlyphRangesChineseFull();
-}
-CIMGUI_API const ImWchar* ImFontAtlas_GetGlyphRangesChineseSimplifiedCommon(
- ImFontAtlas* self) {
- return self->GetGlyphRangesChineseSimplifiedCommon();
-}
-CIMGUI_API const ImWchar* ImFontAtlas_GetGlyphRangesCyrillic(
- ImFontAtlas* self) {
- return self->GetGlyphRangesCyrillic();
-}
-CIMGUI_API const ImWchar* ImFontAtlas_GetGlyphRangesThai(ImFontAtlas* self) {
- return self->GetGlyphRangesThai();
-}
-CIMGUI_API const ImWchar* ImFontAtlas_GetGlyphRangesVietnamese(
- ImFontAtlas* self) {
- return self->GetGlyphRangesVietnamese();
-}
-CIMGUI_API int ImFontAtlas_AddCustomRectRegular(ImFontAtlas* self,
- int width,
- int height) {
- return self->AddCustomRectRegular(width, height);
-}
-CIMGUI_API int ImFontAtlas_AddCustomRectFontGlyph(ImFontAtlas* self,
- ImFont* font,
- ImWchar id,
- int width,
- int height,
- float advance_x,
- const ImVec2 offset) {
- return self->AddCustomRectFontGlyph(font, id, width, height, advance_x,
- offset);
-}
-CIMGUI_API ImFontAtlasCustomRect* ImFontAtlas_GetCustomRectByIndex(
- ImFontAtlas* self,
- int index) {
- return self->GetCustomRectByIndex(index);
-}
-CIMGUI_API void ImFontAtlas_CalcCustomRectUV(ImFontAtlas* self,
- const ImFontAtlasCustomRect* rect,
- ImVec2* out_uv_min,
- ImVec2* out_uv_max) {
- return self->CalcCustomRectUV(rect, out_uv_min, out_uv_max);
-}
-CIMGUI_API bool ImFontAtlas_GetMouseCursorTexData(ImFontAtlas* self,
- ImGuiMouseCursor cursor,
- ImVec2* out_offset,
- ImVec2* out_size,
- ImVec2 out_uv_border[2],
- ImVec2 out_uv_fill[2]) {
- return self->GetMouseCursorTexData(cursor, out_offset, out_size,
- out_uv_border, out_uv_fill);
-}
-CIMGUI_API ImFont* ImFont_ImFont(void) {
- return IM_NEW(ImFont)();
-}
-CIMGUI_API void ImFont_destroy(ImFont* self) {
- IM_DELETE(self);
-}
-CIMGUI_API const ImFontGlyph* ImFont_FindGlyph(ImFont* self, ImWchar c) {
- return self->FindGlyph(c);
-}
-CIMGUI_API const ImFontGlyph* ImFont_FindGlyphNoFallback(ImFont* self,
- ImWchar c) {
- return self->FindGlyphNoFallback(c);
-}
-CIMGUI_API float ImFont_GetCharAdvance(ImFont* self, ImWchar c) {
- return self->GetCharAdvance(c);
-}
-CIMGUI_API bool ImFont_IsLoaded(ImFont* self) {
- return self->IsLoaded();
-}
-CIMGUI_API const char* ImFont_GetDebugName(ImFont* self) {
- return self->GetDebugName();
-}
-CIMGUI_API void ImFont_CalcTextSizeA(ImVec2* pOut,
- ImFont* self,
- float size,
- float max_width,
- float wrap_width,
- const char* text_begin,
- const char* text_end,
- const char** remaining) {
- *pOut = self->CalcTextSizeA(size, max_width, wrap_width, text_begin, text_end,
- remaining);
-}
-CIMGUI_API const char* ImFont_CalcWordWrapPositionA(ImFont* self,
- float scale,
- const char* text,
- const char* text_end,
- float wrap_width) {
- return self->CalcWordWrapPositionA(scale, text, text_end, wrap_width);
-}
-CIMGUI_API void ImFont_RenderChar(ImFont* self,
- ImDrawList* draw_list,
- float size,
- const ImVec2 pos,
- ImU32 col,
- ImWchar c) {
- return self->RenderChar(draw_list, size, pos, col, c);
-}
-CIMGUI_API void ImFont_RenderText(ImFont* self,
- ImDrawList* draw_list,
- float size,
- const ImVec2 pos,
- ImU32 col,
- const ImVec4 clip_rect,
- const char* text_begin,
- const char* text_end,
- float wrap_width,
- bool cpu_fine_clip) {
- return self->RenderText(draw_list, size, pos, col, clip_rect, text_begin,
- text_end, wrap_width, cpu_fine_clip);
-}
-CIMGUI_API void ImFont_BuildLookupTable(ImFont* self) {
- return self->BuildLookupTable();
-}
-CIMGUI_API void ImFont_ClearOutputData(ImFont* self) {
- return self->ClearOutputData();
-}
-CIMGUI_API void ImFont_GrowIndex(ImFont* self, int new_size) {
- return self->GrowIndex(new_size);
-}
-CIMGUI_API void ImFont_AddGlyph(ImFont* self,
- const ImFontConfig* src_cfg,
- ImWchar c,
- float x0,
- float y0,
- float x1,
- float y1,
- float u0,
- float v0,
- float u1,
- float v1,
- float advance_x) {
- return self->AddGlyph(src_cfg, c, x0, y0, x1, y1, u0, v0, u1, v1, advance_x);
-}
-CIMGUI_API void ImFont_AddRemapChar(ImFont* self,
- ImWchar dst,
- ImWchar src,
- bool overwrite_dst) {
- return self->AddRemapChar(dst, src, overwrite_dst);
-}
-CIMGUI_API void ImFont_SetGlyphVisible(ImFont* self, ImWchar c, bool visible) {
- return self->SetGlyphVisible(c, visible);
-}
-CIMGUI_API bool ImFont_IsGlyphRangeUnused(ImFont* self,
- unsigned int c_begin,
- unsigned int c_last) {
- return self->IsGlyphRangeUnused(c_begin, c_last);
-}
-CIMGUI_API ImGuiViewport* ImGuiViewport_ImGuiViewport(void) {
- return IM_NEW(ImGuiViewport)();
-}
-CIMGUI_API void ImGuiViewport_destroy(ImGuiViewport* self) {
- IM_DELETE(self);
-}
-CIMGUI_API void ImGuiViewport_GetCenter(ImVec2* pOut, ImGuiViewport* self) {
- *pOut = self->GetCenter();
-}
-CIMGUI_API void ImGuiViewport_GetWorkCenter(ImVec2* pOut, ImGuiViewport* self) {
- *pOut = self->GetWorkCenter();
-}
-CIMGUI_API ImGuiPlatformIO* ImGuiPlatformIO_ImGuiPlatformIO(void) {
- return IM_NEW(ImGuiPlatformIO)();
-}
-CIMGUI_API void ImGuiPlatformIO_destroy(ImGuiPlatformIO* self) {
- IM_DELETE(self);
-}
-CIMGUI_API ImGuiPlatformMonitor* ImGuiPlatformMonitor_ImGuiPlatformMonitor(
- void) {
- return IM_NEW(ImGuiPlatformMonitor)();
-}
-CIMGUI_API void ImGuiPlatformMonitor_destroy(ImGuiPlatformMonitor* self) {
- IM_DELETE(self);
-}
-CIMGUI_API ImGuiPlatformImeData* ImGuiPlatformImeData_ImGuiPlatformImeData(
- void) {
- return IM_NEW(ImGuiPlatformImeData)();
-}
-CIMGUI_API void ImGuiPlatformImeData_destroy(ImGuiPlatformImeData* self) {
- IM_DELETE(self);
-}
-CIMGUI_API ImGuiID igImHashData(const void* data,
- size_t data_size,
- ImGuiID seed) {
- return ImHashData(data, data_size, seed);
-}
-CIMGUI_API ImGuiID igImHashStr(const char* data,
- size_t data_size,
- ImGuiID seed) {
- return ImHashStr(data, data_size, seed);
-}
-CIMGUI_API void igImQsort(void* base,
- size_t count,
- size_t size_of_element,
- int (*compare_func)(void const*, void const*)) {
- return ImQsort(base, count, size_of_element, compare_func);
-}
-CIMGUI_API ImU32 igImAlphaBlendColors(ImU32 col_a, ImU32 col_b) {
- return ImAlphaBlendColors(col_a, col_b);
-}
-CIMGUI_API bool igImIsPowerOfTwo_Int(int v) {
- return ImIsPowerOfTwo(v);
-}
-CIMGUI_API bool igImIsPowerOfTwo_U64(ImU64 v) {
- return ImIsPowerOfTwo(v);
-}
-CIMGUI_API int igImUpperPowerOfTwo(int v) {
- return ImUpperPowerOfTwo(v);
-}
-CIMGUI_API int igImStricmp(const char* str1, const char* str2) {
- return ImStricmp(str1, str2);
-}
-CIMGUI_API int igImStrnicmp(const char* str1, const char* str2, size_t count) {
- return ImStrnicmp(str1, str2, count);
-}
-CIMGUI_API void igImStrncpy(char* dst, const char* src, size_t count) {
- return ImStrncpy(dst, src, count);
-}
-CIMGUI_API char* igImStrdup(const char* str) {
- return ImStrdup(str);
-}
-CIMGUI_API char* igImStrdupcpy(char* dst, size_t* p_dst_size, const char* str) {
- return ImStrdupcpy(dst, p_dst_size, str);
-}
-CIMGUI_API const char* igImStrchrRange(const char* str_begin,
- const char* str_end,
- char c) {
- return ImStrchrRange(str_begin, str_end, c);
-}
-CIMGUI_API const char* igImStreolRange(const char* str, const char* str_end) {
- return ImStreolRange(str, str_end);
-}
-CIMGUI_API const char* igImStristr(const char* haystack,
- const char* haystack_end,
- const char* needle,
- const char* needle_end) {
- return ImStristr(haystack, haystack_end, needle, needle_end);
-}
-CIMGUI_API void igImStrTrimBlanks(char* str) {
- return ImStrTrimBlanks(str);
-}
-CIMGUI_API const char* igImStrSkipBlank(const char* str) {
- return ImStrSkipBlank(str);
-}
-CIMGUI_API int igImStrlenW(const ImWchar* str) {
- return ImStrlenW(str);
-}
-CIMGUI_API const ImWchar* igImStrbolW(const ImWchar* buf_mid_line,
- const ImWchar* buf_begin) {
- return ImStrbolW(buf_mid_line, buf_begin);
-}
-CIMGUI_API char igImToUpper(char c) {
- return ImToUpper(c);
-}
-CIMGUI_API bool igImCharIsBlankA(char c) {
- return ImCharIsBlankA(c);
-}
-CIMGUI_API bool igImCharIsBlankW(unsigned int c) {
- return ImCharIsBlankW(c);
-}
-CIMGUI_API int igImFormatString(char* buf,
- size_t buf_size,
- const char* fmt,
- ...) {
- va_list args;
- va_start(args, fmt);
- int ret = ImFormatStringV(buf, buf_size, fmt, args);
- va_end(args);
- return ret;
-}
-CIMGUI_API int igImFormatStringV(char* buf,
- size_t buf_size,
- const char* fmt,
- va_list args) {
- return ImFormatStringV(buf, buf_size, fmt, args);
-}
-CIMGUI_API void igImFormatStringToTempBuffer(const char** out_buf,
- const char** out_buf_end,
- const char* fmt,
- ...) {
- va_list args;
- va_start(args, fmt);
- ImFormatStringToTempBufferV(out_buf, out_buf_end, fmt, args);
- va_end(args);
-}
-CIMGUI_API void igImFormatStringToTempBufferV(const char** out_buf,
- const char** out_buf_end,
- const char* fmt,
- va_list args) {
- return ImFormatStringToTempBufferV(out_buf, out_buf_end, fmt, args);
-}
-CIMGUI_API const char* igImParseFormatFindStart(const char* format) {
- return ImParseFormatFindStart(format);
-}
-CIMGUI_API const char* igImParseFormatFindEnd(const char* format) {
- return ImParseFormatFindEnd(format);
-}
-CIMGUI_API const char* igImParseFormatTrimDecorations(const char* format,
- char* buf,
- size_t buf_size) {
- return ImParseFormatTrimDecorations(format, buf, buf_size);
-}
-CIMGUI_API void igImParseFormatSanitizeForPrinting(const char* fmt_in,
- char* fmt_out,
- size_t fmt_out_size) {
- return ImParseFormatSanitizeForPrinting(fmt_in, fmt_out, fmt_out_size);
-}
-CIMGUI_API const char* igImParseFormatSanitizeForScanning(const char* fmt_in,
- char* fmt_out,
- size_t fmt_out_size) {
- return ImParseFormatSanitizeForScanning(fmt_in, fmt_out, fmt_out_size);
-}
-CIMGUI_API int igImParseFormatPrecision(const char* format, int default_value) {
- return ImParseFormatPrecision(format, default_value);
-}
-CIMGUI_API const char* igImTextCharToUtf8(char out_buf[5], unsigned int c) {
- return ImTextCharToUtf8(out_buf, c);
-}
-CIMGUI_API int igImTextStrToUtf8(char* out_buf,
- int out_buf_size,
- const ImWchar* in_text,
- const ImWchar* in_text_end) {
- return ImTextStrToUtf8(out_buf, out_buf_size, in_text, in_text_end);
-}
-CIMGUI_API int igImTextCharFromUtf8(unsigned int* out_char,
- const char* in_text,
- const char* in_text_end) {
- return ImTextCharFromUtf8(out_char, in_text, in_text_end);
-}
-CIMGUI_API int igImTextStrFromUtf8(ImWchar* out_buf,
- int out_buf_size,
- const char* in_text,
- const char* in_text_end,
- const char** in_remaining) {
- return ImTextStrFromUtf8(out_buf, out_buf_size, in_text, in_text_end,
- in_remaining);
-}
-CIMGUI_API int igImTextCountCharsFromUtf8(const char* in_text,
- const char* in_text_end) {
- return ImTextCountCharsFromUtf8(in_text, in_text_end);
-}
-CIMGUI_API int igImTextCountUtf8BytesFromChar(const char* in_text,
- const char* in_text_end) {
- return ImTextCountUtf8BytesFromChar(in_text, in_text_end);
-}
-CIMGUI_API int igImTextCountUtf8BytesFromStr(const ImWchar* in_text,
- const ImWchar* in_text_end) {
- return ImTextCountUtf8BytesFromStr(in_text, in_text_end);
-}
-CIMGUI_API const char* igImTextFindPreviousUtf8Codepoint(
- const char* in_text_start,
- const char* in_text_curr) {
- return ImTextFindPreviousUtf8Codepoint(in_text_start, in_text_curr);
-}
-CIMGUI_API int igImTextCountLines(const char* in_text,
- const char* in_text_end) {
- return ImTextCountLines(in_text, in_text_end);
-}
-CIMGUI_API ImFileHandle igImFileOpen(const char* filename, const char* mode) {
- return ImFileOpen(filename, mode);
-}
-CIMGUI_API bool igImFileClose(ImFileHandle file) {
- return ImFileClose(file);
-}
-CIMGUI_API ImU64 igImFileGetSize(ImFileHandle file) {
- return ImFileGetSize(file);
-}
-CIMGUI_API ImU64 igImFileRead(void* data,
- ImU64 size,
- ImU64 count,
- ImFileHandle file) {
- return ImFileRead(data, size, count, file);
-}
-CIMGUI_API ImU64 igImFileWrite(const void* data,
- ImU64 size,
- ImU64 count,
- ImFileHandle file) {
- return ImFileWrite(data, size, count, file);
-}
-CIMGUI_API void* igImFileLoadToMemory(const char* filename,
- const char* mode,
- size_t* out_file_size,
- int padding_bytes) {
- return ImFileLoadToMemory(filename, mode, out_file_size, padding_bytes);
-}
-CIMGUI_API float igImPow_Float(float x, float y) {
- return ImPow(x, y);
-}
-CIMGUI_API double igImPow_double(double x, double y) {
- return ImPow(x, y);
-}
-CIMGUI_API float igImLog_Float(float x) {
- return ImLog(x);
-}
-CIMGUI_API double igImLog_double(double x) {
- return ImLog(x);
-}
-CIMGUI_API int igImAbs_Int(int x) {
- return ImAbs(x);
-}
-CIMGUI_API float igImAbs_Float(float x) {
- return ImAbs(x);
-}
-CIMGUI_API double igImAbs_double(double x) {
- return ImAbs(x);
-}
-CIMGUI_API float igImSign_Float(float x) {
- return ImSign(x);
-}
-CIMGUI_API double igImSign_double(double x) {
- return ImSign(x);
-}
-CIMGUI_API float igImRsqrt_Float(float x) {
- return ImRsqrt(x);
-}
-CIMGUI_API double igImRsqrt_double(double x) {
- return ImRsqrt(x);
-}
-CIMGUI_API void igImMin(ImVec2* pOut, const ImVec2 lhs, const ImVec2 rhs) {
- *pOut = ImMin(lhs, rhs);
-}
-CIMGUI_API void igImMax(ImVec2* pOut, const ImVec2 lhs, const ImVec2 rhs) {
- *pOut = ImMax(lhs, rhs);
-}
-CIMGUI_API void igImClamp(ImVec2* pOut,
- const ImVec2 v,
- const ImVec2 mn,
- ImVec2 mx) {
- *pOut = ImClamp(v, mn, mx);
-}
-CIMGUI_API void igImLerp_Vec2Float(ImVec2* pOut,
- const ImVec2 a,
- const ImVec2 b,
- float t) {
- *pOut = ImLerp(a, b, t);
-}
-CIMGUI_API void igImLerp_Vec2Vec2(ImVec2* pOut,
- const ImVec2 a,
- const ImVec2 b,
- const ImVec2 t) {
- *pOut = ImLerp(a, b, t);
-}
-CIMGUI_API void igImLerp_Vec4(ImVec4* pOut,
- const ImVec4 a,
- const ImVec4 b,
- float t) {
- *pOut = ImLerp(a, b, t);
-}
-CIMGUI_API float igImSaturate(float f) {
- return ImSaturate(f);
-}
-CIMGUI_API float igImLengthSqr_Vec2(const ImVec2 lhs) {
- return ImLengthSqr(lhs);
-}
-CIMGUI_API float igImLengthSqr_Vec4(const ImVec4 lhs) {
- return ImLengthSqr(lhs);
-}
-CIMGUI_API float igImInvLength(const ImVec2 lhs, float fail_value) {
- return ImInvLength(lhs, fail_value);
-}
-CIMGUI_API float igImTrunc_Float(float f) {
- return ImTrunc(f);
-}
-CIMGUI_API void igImTrunc_Vec2(ImVec2* pOut, const ImVec2 v) {
- *pOut = ImTrunc(v);
-}
-CIMGUI_API float igImFloor_Float(float f) {
- return ImFloor(f);
-}
-CIMGUI_API void igImFloor_Vec2(ImVec2* pOut, const ImVec2 v) {
- *pOut = ImFloor(v);
-}
-CIMGUI_API int igImModPositive(int a, int b) {
- return ImModPositive(a, b);
-}
-CIMGUI_API float igImDot(const ImVec2 a, const ImVec2 b) {
- return ImDot(a, b);
-}
-CIMGUI_API void igImRotate(ImVec2* pOut,
- const ImVec2 v,
- float cos_a,
- float sin_a) {
- *pOut = ImRotate(v, cos_a, sin_a);
-}
-CIMGUI_API float igImLinearSweep(float current, float target, float speed) {
- return ImLinearSweep(current, target, speed);
-}
-CIMGUI_API void igImMul(ImVec2* pOut, const ImVec2 lhs, const ImVec2 rhs) {
- *pOut = ImMul(lhs, rhs);
-}
-CIMGUI_API bool igImIsFloatAboveGuaranteedIntegerPrecision(float f) {
- return ImIsFloatAboveGuaranteedIntegerPrecision(f);
-}
-CIMGUI_API float igImExponentialMovingAverage(float avg, float sample, int n) {
- return ImExponentialMovingAverage(avg, sample, n);
-}
-CIMGUI_API void igImBezierCubicCalc(ImVec2* pOut,
- const ImVec2 p1,
- const ImVec2 p2,
- const ImVec2 p3,
- const ImVec2 p4,
- float t) {
- *pOut = ImBezierCubicCalc(p1, p2, p3, p4, t);
-}
-CIMGUI_API void igImBezierCubicClosestPoint(ImVec2* pOut,
- const ImVec2 p1,
- const ImVec2 p2,
- const ImVec2 p3,
- const ImVec2 p4,
- const ImVec2 p,
- int num_segments) {
- *pOut = ImBezierCubicClosestPoint(p1, p2, p3, p4, p, num_segments);
-}
-CIMGUI_API void igImBezierCubicClosestPointCasteljau(ImVec2* pOut,
- const ImVec2 p1,
- const ImVec2 p2,
- const ImVec2 p3,
- const ImVec2 p4,
- const ImVec2 p,
- float tess_tol) {
- *pOut = ImBezierCubicClosestPointCasteljau(p1, p2, p3, p4, p, tess_tol);
-}
-CIMGUI_API void igImBezierQuadraticCalc(ImVec2* pOut,
- const ImVec2 p1,
- const ImVec2 p2,
- const ImVec2 p3,
- float t) {
- *pOut = ImBezierQuadraticCalc(p1, p2, p3, t);
-}
-CIMGUI_API void igImLineClosestPoint(ImVec2* pOut,
- const ImVec2 a,
- const ImVec2 b,
- const ImVec2 p) {
- *pOut = ImLineClosestPoint(a, b, p);
-}
-CIMGUI_API bool igImTriangleContainsPoint(const ImVec2 a,
- const ImVec2 b,
- const ImVec2 c,
- const ImVec2 p) {
- return ImTriangleContainsPoint(a, b, c, p);
-}
-CIMGUI_API void igImTriangleClosestPoint(ImVec2* pOut,
- const ImVec2 a,
- const ImVec2 b,
- const ImVec2 c,
- const ImVec2 p) {
- *pOut = ImTriangleClosestPoint(a, b, c, p);
-}
-CIMGUI_API void igImTriangleBarycentricCoords(const ImVec2 a,
- const ImVec2 b,
- const ImVec2 c,
- const ImVec2 p,
- float* out_u,
- float* out_v,
- float* out_w) {
- return ImTriangleBarycentricCoords(a, b, c, p, *out_u, *out_v, *out_w);
-}
-CIMGUI_API float igImTriangleArea(const ImVec2 a,
- const ImVec2 b,
- const ImVec2 c) {
- return ImTriangleArea(a, b, c);
-}
-CIMGUI_API bool igImTriangleIsClockwise(const ImVec2 a,
- const ImVec2 b,
- const ImVec2 c) {
- return ImTriangleIsClockwise(a, b, c);
-}
-CIMGUI_API ImVec1* ImVec1_ImVec1_Nil(void) {
- return IM_NEW(ImVec1)();
-}
-CIMGUI_API void ImVec1_destroy(ImVec1* self) {
- IM_DELETE(self);
-}
-CIMGUI_API ImVec1* ImVec1_ImVec1_Float(float _x) {
- return IM_NEW(ImVec1)(_x);
-}
-CIMGUI_API ImVec2ih* ImVec2ih_ImVec2ih_Nil(void) {
- return IM_NEW(ImVec2ih)();
-}
-CIMGUI_API void ImVec2ih_destroy(ImVec2ih* self) {
- IM_DELETE(self);
-}
-CIMGUI_API ImVec2ih* ImVec2ih_ImVec2ih_short(short _x, short _y) {
- return IM_NEW(ImVec2ih)(_x, _y);
-}
-CIMGUI_API ImVec2ih* ImVec2ih_ImVec2ih_Vec2(const ImVec2 rhs) {
- return IM_NEW(ImVec2ih)(rhs);
-}
-CIMGUI_API ImRect* ImRect_ImRect_Nil(void) {
- return IM_NEW(ImRect)();
-}
-CIMGUI_API void ImRect_destroy(ImRect* self) {
- IM_DELETE(self);
-}
-CIMGUI_API ImRect* ImRect_ImRect_Vec2(const ImVec2 min, const ImVec2 max) {
- return IM_NEW(ImRect)(min, max);
-}
-CIMGUI_API ImRect* ImRect_ImRect_Vec4(const ImVec4 v) {
- return IM_NEW(ImRect)(v);
-}
-CIMGUI_API ImRect* ImRect_ImRect_Float(float x1, float y1, float x2, float y2) {
- return IM_NEW(ImRect)(x1, y1, x2, y2);
-}
-CIMGUI_API void ImRect_GetCenter(ImVec2* pOut, ImRect* self) {
- *pOut = self->GetCenter();
-}
-CIMGUI_API void ImRect_GetSize(ImVec2* pOut, ImRect* self) {
- *pOut = self->GetSize();
-}
-CIMGUI_API float ImRect_GetWidth(ImRect* self) {
- return self->GetWidth();
-}
-CIMGUI_API float ImRect_GetHeight(ImRect* self) {
- return self->GetHeight();
-}
-CIMGUI_API float ImRect_GetArea(ImRect* self) {
- return self->GetArea();
-}
-CIMGUI_API void ImRect_GetTL(ImVec2* pOut, ImRect* self) {
- *pOut = self->GetTL();
-}
-CIMGUI_API void ImRect_GetTR(ImVec2* pOut, ImRect* self) {
- *pOut = self->GetTR();
-}
-CIMGUI_API void ImRect_GetBL(ImVec2* pOut, ImRect* self) {
- *pOut = self->GetBL();
-}
-CIMGUI_API void ImRect_GetBR(ImVec2* pOut, ImRect* self) {
- *pOut = self->GetBR();
-}
-CIMGUI_API bool ImRect_Contains_Vec2(ImRect* self, const ImVec2 p) {
- return self->Contains(p);
-}
-CIMGUI_API bool ImRect_Contains_Rect(ImRect* self, const ImRect r) {
- return self->Contains(r);
-}
-CIMGUI_API bool ImRect_ContainsWithPad(ImRect* self,
- const ImVec2 p,
- const ImVec2 pad) {
- return self->ContainsWithPad(p, pad);
-}
-CIMGUI_API bool ImRect_Overlaps(ImRect* self, const ImRect r) {
- return self->Overlaps(r);
-}
-CIMGUI_API void ImRect_Add_Vec2(ImRect* self, const ImVec2 p) {
- return self->Add(p);
-}
-CIMGUI_API void ImRect_Add_Rect(ImRect* self, const ImRect r) {
- return self->Add(r);
-}
-CIMGUI_API void ImRect_Expand_Float(ImRect* self, const float amount) {
- return self->Expand(amount);
-}
-CIMGUI_API void ImRect_Expand_Vec2(ImRect* self, const ImVec2 amount) {
- return self->Expand(amount);
-}
-CIMGUI_API void ImRect_Translate(ImRect* self, const ImVec2 d) {
- return self->Translate(d);
-}
-CIMGUI_API void ImRect_TranslateX(ImRect* self, float dx) {
- return self->TranslateX(dx);
-}
-CIMGUI_API void ImRect_TranslateY(ImRect* self, float dy) {
- return self->TranslateY(dy);
-}
-CIMGUI_API void ImRect_ClipWith(ImRect* self, const ImRect r) {
- return self->ClipWith(r);
-}
-CIMGUI_API void ImRect_ClipWithFull(ImRect* self, const ImRect r) {
- return self->ClipWithFull(r);
-}
-CIMGUI_API void ImRect_Floor(ImRect* self) {
- return self->Floor();
-}
-CIMGUI_API bool ImRect_IsInverted(ImRect* self) {
- return self->IsInverted();
-}
-CIMGUI_API void ImRect_ToVec4(ImVec4* pOut, ImRect* self) {
- *pOut = self->ToVec4();
-}
-CIMGUI_API size_t igImBitArrayGetStorageSizeInBytes(int bitcount) {
- return ImBitArrayGetStorageSizeInBytes(bitcount);
-}
-CIMGUI_API void igImBitArrayClearAllBits(ImU32* arr, int bitcount) {
- return ImBitArrayClearAllBits(arr, bitcount);
-}
-CIMGUI_API bool igImBitArrayTestBit(const ImU32* arr, int n) {
- return ImBitArrayTestBit(arr, n);
-}
-CIMGUI_API void igImBitArrayClearBit(ImU32* arr, int n) {
- return ImBitArrayClearBit(arr, n);
-}
-CIMGUI_API void igImBitArraySetBit(ImU32* arr, int n) {
- return ImBitArraySetBit(arr, n);
-}
-CIMGUI_API void igImBitArraySetBitRange(ImU32* arr, int n, int n2) {
- return ImBitArraySetBitRange(arr, n, n2);
-}
-CIMGUI_API void ImBitVector_Create(ImBitVector* self, int sz) {
- return self->Create(sz);
-}
-CIMGUI_API void ImBitVector_Clear(ImBitVector* self) {
- return self->Clear();
-}
-CIMGUI_API bool ImBitVector_TestBit(ImBitVector* self, int n) {
- return self->TestBit(n);
-}
-CIMGUI_API void ImBitVector_SetBit(ImBitVector* self, int n) {
- return self->SetBit(n);
-}
-CIMGUI_API void ImBitVector_ClearBit(ImBitVector* self, int n) {
- return self->ClearBit(n);
-}
-CIMGUI_API void ImGuiTextIndex_clear(ImGuiTextIndex* self) {
- return self->clear();
-}
-CIMGUI_API int ImGuiTextIndex_size(ImGuiTextIndex* self) {
- return self->size();
-}
-CIMGUI_API const char* ImGuiTextIndex_get_line_begin(ImGuiTextIndex* self,
- const char* base,
- int n) {
- return self->get_line_begin(base, n);
-}
-CIMGUI_API const char* ImGuiTextIndex_get_line_end(ImGuiTextIndex* self,
- const char* base,
- int n) {
- return self->get_line_end(base, n);
-}
-CIMGUI_API void ImGuiTextIndex_append(ImGuiTextIndex* self,
- const char* base,
- int old_size,
- int new_size) {
- return self->append(base, old_size, new_size);
-}
-CIMGUI_API ImDrawListSharedData* ImDrawListSharedData_ImDrawListSharedData(
- void) {
- return IM_NEW(ImDrawListSharedData)();
-}
-CIMGUI_API void ImDrawListSharedData_destroy(ImDrawListSharedData* self) {
- IM_DELETE(self);
-}
-CIMGUI_API void ImDrawListSharedData_SetCircleTessellationMaxError(
- ImDrawListSharedData* self,
- float max_error) {
- return self->SetCircleTessellationMaxError(max_error);
-}
-CIMGUI_API ImDrawDataBuilder* ImDrawDataBuilder_ImDrawDataBuilder(void) {
- return IM_NEW(ImDrawDataBuilder)();
-}
-CIMGUI_API void ImDrawDataBuilder_destroy(ImDrawDataBuilder* self) {
- IM_DELETE(self);
-}
-CIMGUI_API ImGuiStyleMod* ImGuiStyleMod_ImGuiStyleMod_Int(ImGuiStyleVar idx,
- int v) {
- return IM_NEW(ImGuiStyleMod)(idx, v);
-}
-CIMGUI_API void ImGuiStyleMod_destroy(ImGuiStyleMod* self) {
- IM_DELETE(self);
-}
-CIMGUI_API ImGuiStyleMod* ImGuiStyleMod_ImGuiStyleMod_Float(ImGuiStyleVar idx,
- float v) {
- return IM_NEW(ImGuiStyleMod)(idx, v);
-}
-CIMGUI_API ImGuiStyleMod* ImGuiStyleMod_ImGuiStyleMod_Vec2(ImGuiStyleVar idx,
- ImVec2 v) {
- return IM_NEW(ImGuiStyleMod)(idx, v);
-}
-CIMGUI_API ImGuiComboPreviewData* ImGuiComboPreviewData_ImGuiComboPreviewData(
- void) {
- return IM_NEW(ImGuiComboPreviewData)();
-}
-CIMGUI_API void ImGuiComboPreviewData_destroy(ImGuiComboPreviewData* self) {
- IM_DELETE(self);
-}
-CIMGUI_API ImGuiMenuColumns* ImGuiMenuColumns_ImGuiMenuColumns(void) {
- return IM_NEW(ImGuiMenuColumns)();
-}
-CIMGUI_API void ImGuiMenuColumns_destroy(ImGuiMenuColumns* self) {
- IM_DELETE(self);
-}
-CIMGUI_API void ImGuiMenuColumns_Update(ImGuiMenuColumns* self,
- float spacing,
- bool window_reappearing) {
- return self->Update(spacing, window_reappearing);
-}
-CIMGUI_API float ImGuiMenuColumns_DeclColumns(ImGuiMenuColumns* self,
- float w_icon,
- float w_label,
- float w_shortcut,
- float w_mark) {
- return self->DeclColumns(w_icon, w_label, w_shortcut, w_mark);
-}
-CIMGUI_API void ImGuiMenuColumns_CalcNextTotalWidth(ImGuiMenuColumns* self,
- bool update_offsets) {
- return self->CalcNextTotalWidth(update_offsets);
-}
-CIMGUI_API ImGuiInputTextDeactivatedState*
-ImGuiInputTextDeactivatedState_ImGuiInputTextDeactivatedState(void) {
- return IM_NEW(ImGuiInputTextDeactivatedState)();
-}
-CIMGUI_API void ImGuiInputTextDeactivatedState_destroy(
- ImGuiInputTextDeactivatedState* self) {
- IM_DELETE(self);
-}
-CIMGUI_API void ImGuiInputTextDeactivatedState_ClearFreeMemory(
- ImGuiInputTextDeactivatedState* self) {
- return self->ClearFreeMemory();
-}
-CIMGUI_API ImGuiInputTextState* ImGuiInputTextState_ImGuiInputTextState(void) {
- return IM_NEW(ImGuiInputTextState)();
-}
-CIMGUI_API void ImGuiInputTextState_destroy(ImGuiInputTextState* self) {
- IM_DELETE(self);
-}
-CIMGUI_API void ImGuiInputTextState_ClearText(ImGuiInputTextState* self) {
- return self->ClearText();
-}
-CIMGUI_API void ImGuiInputTextState_ClearFreeMemory(ImGuiInputTextState* self) {
- return self->ClearFreeMemory();
-}
-CIMGUI_API int ImGuiInputTextState_GetUndoAvailCount(
- ImGuiInputTextState* self) {
- return self->GetUndoAvailCount();
-}
-CIMGUI_API int ImGuiInputTextState_GetRedoAvailCount(
- ImGuiInputTextState* self) {
- return self->GetRedoAvailCount();
-}
-CIMGUI_API void ImGuiInputTextState_OnKeyPressed(ImGuiInputTextState* self,
- int key) {
- return self->OnKeyPressed(key);
-}
-CIMGUI_API void ImGuiInputTextState_CursorAnimReset(ImGuiInputTextState* self) {
- return self->CursorAnimReset();
-}
-CIMGUI_API void ImGuiInputTextState_CursorClamp(ImGuiInputTextState* self) {
- return self->CursorClamp();
-}
-CIMGUI_API bool ImGuiInputTextState_HasSelection(ImGuiInputTextState* self) {
- return self->HasSelection();
-}
-CIMGUI_API void ImGuiInputTextState_ClearSelection(ImGuiInputTextState* self) {
- return self->ClearSelection();
-}
-CIMGUI_API int ImGuiInputTextState_GetCursorPos(ImGuiInputTextState* self) {
- return self->GetCursorPos();
-}
-CIMGUI_API int ImGuiInputTextState_GetSelectionStart(
- ImGuiInputTextState* self) {
- return self->GetSelectionStart();
-}
-CIMGUI_API int ImGuiInputTextState_GetSelectionEnd(ImGuiInputTextState* self) {
- return self->GetSelectionEnd();
-}
-CIMGUI_API void ImGuiInputTextState_SelectAll(ImGuiInputTextState* self) {
- return self->SelectAll();
-}
-CIMGUI_API void ImGuiInputTextState_ReloadUserBufAndSelectAll(
- ImGuiInputTextState* self) {
- return self->ReloadUserBufAndSelectAll();
-}
-CIMGUI_API void ImGuiInputTextState_ReloadUserBufAndKeepSelection(
- ImGuiInputTextState* self) {
- return self->ReloadUserBufAndKeepSelection();
-}
-CIMGUI_API void ImGuiInputTextState_ReloadUserBufAndMoveToEnd(
- ImGuiInputTextState* self) {
- return self->ReloadUserBufAndMoveToEnd();
-}
-CIMGUI_API ImGuiNextWindowData* ImGuiNextWindowData_ImGuiNextWindowData(void) {
- return IM_NEW(ImGuiNextWindowData)();
-}
-CIMGUI_API void ImGuiNextWindowData_destroy(ImGuiNextWindowData* self) {
- IM_DELETE(self);
-}
-CIMGUI_API void ImGuiNextWindowData_ClearFlags(ImGuiNextWindowData* self) {
- return self->ClearFlags();
-}
-CIMGUI_API ImGuiNextItemData* ImGuiNextItemData_ImGuiNextItemData(void) {
- return IM_NEW(ImGuiNextItemData)();
-}
-CIMGUI_API void ImGuiNextItemData_destroy(ImGuiNextItemData* self) {
- IM_DELETE(self);
-}
-CIMGUI_API void ImGuiNextItemData_ClearFlags(ImGuiNextItemData* self) {
- return self->ClearFlags();
-}
-CIMGUI_API ImGuiLastItemData* ImGuiLastItemData_ImGuiLastItemData(void) {
- return IM_NEW(ImGuiLastItemData)();
-}
-CIMGUI_API void ImGuiLastItemData_destroy(ImGuiLastItemData* self) {
- IM_DELETE(self);
-}
-CIMGUI_API ImGuiStackSizes* ImGuiStackSizes_ImGuiStackSizes(void) {
- return IM_NEW(ImGuiStackSizes)();
-}
-CIMGUI_API void ImGuiStackSizes_destroy(ImGuiStackSizes* self) {
- IM_DELETE(self);
-}
-CIMGUI_API void ImGuiStackSizes_SetToContextState(ImGuiStackSizes* self,
- ImGuiContext* ctx) {
- return self->SetToContextState(ctx);
-}
-CIMGUI_API void ImGuiStackSizes_CompareWithContextState(ImGuiStackSizes* self,
- ImGuiContext* ctx) {
- return self->CompareWithContextState(ctx);
-}
-CIMGUI_API ImGuiPtrOrIndex* ImGuiPtrOrIndex_ImGuiPtrOrIndex_Ptr(void* ptr) {
- return IM_NEW(ImGuiPtrOrIndex)(ptr);
-}
-CIMGUI_API void ImGuiPtrOrIndex_destroy(ImGuiPtrOrIndex* self) {
- IM_DELETE(self);
-}
-CIMGUI_API ImGuiPtrOrIndex* ImGuiPtrOrIndex_ImGuiPtrOrIndex_Int(int index) {
- return IM_NEW(ImGuiPtrOrIndex)(index);
-}
-CIMGUI_API void* ImGuiDataVarInfo_GetVarPtr(ImGuiDataVarInfo* self,
- void* parent) {
- return self->GetVarPtr(parent);
-}
-CIMGUI_API ImGuiPopupData* ImGuiPopupData_ImGuiPopupData(void) {
- return IM_NEW(ImGuiPopupData)();
-}
-CIMGUI_API void ImGuiPopupData_destroy(ImGuiPopupData* self) {
- IM_DELETE(self);
-}
-CIMGUI_API ImGuiInputEvent* ImGuiInputEvent_ImGuiInputEvent(void) {
- return IM_NEW(ImGuiInputEvent)();
-}
-CIMGUI_API void ImGuiInputEvent_destroy(ImGuiInputEvent* self) {
- IM_DELETE(self);
-}
-CIMGUI_API ImGuiKeyRoutingData* ImGuiKeyRoutingData_ImGuiKeyRoutingData(void) {
- return IM_NEW(ImGuiKeyRoutingData)();
-}
-CIMGUI_API void ImGuiKeyRoutingData_destroy(ImGuiKeyRoutingData* self) {
- IM_DELETE(self);
-}
-CIMGUI_API ImGuiKeyRoutingTable* ImGuiKeyRoutingTable_ImGuiKeyRoutingTable(
- void) {
- return IM_NEW(ImGuiKeyRoutingTable)();
-}
-CIMGUI_API void ImGuiKeyRoutingTable_destroy(ImGuiKeyRoutingTable* self) {
- IM_DELETE(self);
-}
-CIMGUI_API void ImGuiKeyRoutingTable_Clear(ImGuiKeyRoutingTable* self) {
- return self->Clear();
-}
-CIMGUI_API ImGuiKeyOwnerData* ImGuiKeyOwnerData_ImGuiKeyOwnerData(void) {
- return IM_NEW(ImGuiKeyOwnerData)();
-}
-CIMGUI_API void ImGuiKeyOwnerData_destroy(ImGuiKeyOwnerData* self) {
- IM_DELETE(self);
-}
-CIMGUI_API ImGuiListClipperRange ImGuiListClipperRange_FromIndices(int min,
- int max) {
- return ImGuiListClipperRange::FromIndices(min, max);
-}
-CIMGUI_API ImGuiListClipperRange
-ImGuiListClipperRange_FromPositions(float y1,
- float y2,
- int off_min,
- int off_max) {
- return ImGuiListClipperRange::FromPositions(y1, y2, off_min, off_max);
-}
-CIMGUI_API ImGuiListClipperData* ImGuiListClipperData_ImGuiListClipperData(
- void) {
- return IM_NEW(ImGuiListClipperData)();
-}
-CIMGUI_API void ImGuiListClipperData_destroy(ImGuiListClipperData* self) {
- IM_DELETE(self);
-}
-CIMGUI_API void ImGuiListClipperData_Reset(ImGuiListClipperData* self,
- ImGuiListClipper* clipper) {
- return self->Reset(clipper);
-}
-CIMGUI_API ImGuiNavItemData* ImGuiNavItemData_ImGuiNavItemData(void) {
- return IM_NEW(ImGuiNavItemData)();
-}
-CIMGUI_API void ImGuiNavItemData_destroy(ImGuiNavItemData* self) {
- IM_DELETE(self);
-}
-CIMGUI_API void ImGuiNavItemData_Clear(ImGuiNavItemData* self) {
- return self->Clear();
-}
-CIMGUI_API ImGuiTypingSelectState*
-ImGuiTypingSelectState_ImGuiTypingSelectState(void) {
- return IM_NEW(ImGuiTypingSelectState)();
-}
-CIMGUI_API void ImGuiTypingSelectState_destroy(ImGuiTypingSelectState* self) {
- IM_DELETE(self);
-}
-CIMGUI_API void ImGuiTypingSelectState_Clear(ImGuiTypingSelectState* self) {
- return self->Clear();
-}
-CIMGUI_API ImGuiOldColumnData* ImGuiOldColumnData_ImGuiOldColumnData(void) {
- return IM_NEW(ImGuiOldColumnData)();
-}
-CIMGUI_API void ImGuiOldColumnData_destroy(ImGuiOldColumnData* self) {
- IM_DELETE(self);
-}
-CIMGUI_API ImGuiOldColumns* ImGuiOldColumns_ImGuiOldColumns(void) {
- return IM_NEW(ImGuiOldColumns)();
-}
-CIMGUI_API void ImGuiOldColumns_destroy(ImGuiOldColumns* self) {
- IM_DELETE(self);
-}
-CIMGUI_API ImGuiDockNode* ImGuiDockNode_ImGuiDockNode(ImGuiID id) {
- return IM_NEW(ImGuiDockNode)(id);
-}
-CIMGUI_API void ImGuiDockNode_destroy(ImGuiDockNode* self) {
- IM_DELETE(self);
-}
-CIMGUI_API bool ImGuiDockNode_IsRootNode(ImGuiDockNode* self) {
- return self->IsRootNode();
-}
-CIMGUI_API bool ImGuiDockNode_IsDockSpace(ImGuiDockNode* self) {
- return self->IsDockSpace();
-}
-CIMGUI_API bool ImGuiDockNode_IsFloatingNode(ImGuiDockNode* self) {
- return self->IsFloatingNode();
-}
-CIMGUI_API bool ImGuiDockNode_IsCentralNode(ImGuiDockNode* self) {
- return self->IsCentralNode();
-}
-CIMGUI_API bool ImGuiDockNode_IsHiddenTabBar(ImGuiDockNode* self) {
- return self->IsHiddenTabBar();
-}
-CIMGUI_API bool ImGuiDockNode_IsNoTabBar(ImGuiDockNode* self) {
- return self->IsNoTabBar();
-}
-CIMGUI_API bool ImGuiDockNode_IsSplitNode(ImGuiDockNode* self) {
- return self->IsSplitNode();
-}
-CIMGUI_API bool ImGuiDockNode_IsLeafNode(ImGuiDockNode* self) {
- return self->IsLeafNode();
-}
-CIMGUI_API bool ImGuiDockNode_IsEmpty(ImGuiDockNode* self) {
- return self->IsEmpty();
-}
-CIMGUI_API void ImGuiDockNode_Rect(ImRect* pOut, ImGuiDockNode* self) {
- *pOut = self->Rect();
-}
-CIMGUI_API void ImGuiDockNode_SetLocalFlags(ImGuiDockNode* self,
- ImGuiDockNodeFlags flags) {
- return self->SetLocalFlags(flags);
-}
-CIMGUI_API void ImGuiDockNode_UpdateMergedFlags(ImGuiDockNode* self) {
- return self->UpdateMergedFlags();
-}
-CIMGUI_API ImGuiDockContext* ImGuiDockContext_ImGuiDockContext(void) {
- return IM_NEW(ImGuiDockContext)();
-}
-CIMGUI_API void ImGuiDockContext_destroy(ImGuiDockContext* self) {
- IM_DELETE(self);
-}
-CIMGUI_API ImGuiViewportP* ImGuiViewportP_ImGuiViewportP(void) {
- return IM_NEW(ImGuiViewportP)();
-}
-CIMGUI_API void ImGuiViewportP_destroy(ImGuiViewportP* self) {
- IM_DELETE(self);
-}
-CIMGUI_API void ImGuiViewportP_ClearRequestFlags(ImGuiViewportP* self) {
- return self->ClearRequestFlags();
-}
-CIMGUI_API void ImGuiViewportP_CalcWorkRectPos(ImVec2* pOut,
- ImGuiViewportP* self,
- const ImVec2 off_min) {
- *pOut = self->CalcWorkRectPos(off_min);
-}
-CIMGUI_API void ImGuiViewportP_CalcWorkRectSize(ImVec2* pOut,
- ImGuiViewportP* self,
- const ImVec2 off_min,
- const ImVec2 off_max) {
- *pOut = self->CalcWorkRectSize(off_min, off_max);
-}
-CIMGUI_API void ImGuiViewportP_UpdateWorkRect(ImGuiViewportP* self) {
- return self->UpdateWorkRect();
-}
-CIMGUI_API void ImGuiViewportP_GetMainRect(ImRect* pOut, ImGuiViewportP* self) {
- *pOut = self->GetMainRect();
-}
-CIMGUI_API void ImGuiViewportP_GetWorkRect(ImRect* pOut, ImGuiViewportP* self) {
- *pOut = self->GetWorkRect();
-}
-CIMGUI_API void ImGuiViewportP_GetBuildWorkRect(ImRect* pOut,
- ImGuiViewportP* self) {
- *pOut = self->GetBuildWorkRect();
-}
-CIMGUI_API ImGuiWindowSettings* ImGuiWindowSettings_ImGuiWindowSettings(void) {
- return IM_NEW(ImGuiWindowSettings)();
-}
-CIMGUI_API void ImGuiWindowSettings_destroy(ImGuiWindowSettings* self) {
- IM_DELETE(self);
-}
-CIMGUI_API char* ImGuiWindowSettings_GetName(ImGuiWindowSettings* self) {
- return self->GetName();
-}
-CIMGUI_API ImGuiSettingsHandler* ImGuiSettingsHandler_ImGuiSettingsHandler(
- void) {
- return IM_NEW(ImGuiSettingsHandler)();
-}
-CIMGUI_API void ImGuiSettingsHandler_destroy(ImGuiSettingsHandler* self) {
- IM_DELETE(self);
-}
-CIMGUI_API ImGuiDebugAllocInfo* ImGuiDebugAllocInfo_ImGuiDebugAllocInfo(void) {
- return IM_NEW(ImGuiDebugAllocInfo)();
-}
-CIMGUI_API void ImGuiDebugAllocInfo_destroy(ImGuiDebugAllocInfo* self) {
- IM_DELETE(self);
-}
-CIMGUI_API ImGuiStackLevelInfo* ImGuiStackLevelInfo_ImGuiStackLevelInfo(void) {
- return IM_NEW(ImGuiStackLevelInfo)();
-}
-CIMGUI_API void ImGuiStackLevelInfo_destroy(ImGuiStackLevelInfo* self) {
- IM_DELETE(self);
-}
-CIMGUI_API ImGuiIDStackTool* ImGuiIDStackTool_ImGuiIDStackTool(void) {
- return IM_NEW(ImGuiIDStackTool)();
-}
-CIMGUI_API void ImGuiIDStackTool_destroy(ImGuiIDStackTool* self) {
- IM_DELETE(self);
-}
-CIMGUI_API ImGuiContextHook* ImGuiContextHook_ImGuiContextHook(void) {
- return IM_NEW(ImGuiContextHook)();
-}
-CIMGUI_API void ImGuiContextHook_destroy(ImGuiContextHook* self) {
- IM_DELETE(self);
-}
-CIMGUI_API ImGuiContext* ImGuiContext_ImGuiContext(
- ImFontAtlas* shared_font_atlas) {
- return IM_NEW(ImGuiContext)(shared_font_atlas);
-}
-CIMGUI_API void ImGuiContext_destroy(ImGuiContext* self) {
- IM_DELETE(self);
-}
-CIMGUI_API ImGuiWindow* ImGuiWindow_ImGuiWindow(ImGuiContext* context,
- const char* name) {
- return IM_NEW(ImGuiWindow)(context, name);
-}
-CIMGUI_API void ImGuiWindow_destroy(ImGuiWindow* self) {
- IM_DELETE(self);
-}
-CIMGUI_API ImGuiID ImGuiWindow_GetID_Str(ImGuiWindow* self,
- const char* str,
- const char* str_end) {
- return self->GetID(str, str_end);
-}
-CIMGUI_API ImGuiID ImGuiWindow_GetID_Ptr(ImGuiWindow* self, const void* ptr) {
- return self->GetID(ptr);
-}
-CIMGUI_API ImGuiID ImGuiWindow_GetID_Int(ImGuiWindow* self, int n) {
- return self->GetID(n);
-}
-CIMGUI_API ImGuiID ImGuiWindow_GetIDFromRectangle(ImGuiWindow* self,
- const ImRect r_abs) {
- return self->GetIDFromRectangle(r_abs);
-}
-CIMGUI_API void ImGuiWindow_Rect(ImRect* pOut, ImGuiWindow* self) {
- *pOut = self->Rect();
-}
-CIMGUI_API float ImGuiWindow_CalcFontSize(ImGuiWindow* self) {
- return self->CalcFontSize();
-}
-CIMGUI_API float ImGuiWindow_TitleBarHeight(ImGuiWindow* self) {
- return self->TitleBarHeight();
-}
-CIMGUI_API void ImGuiWindow_TitleBarRect(ImRect* pOut, ImGuiWindow* self) {
- *pOut = self->TitleBarRect();
-}
-CIMGUI_API float ImGuiWindow_MenuBarHeight(ImGuiWindow* self) {
- return self->MenuBarHeight();
-}
-CIMGUI_API void ImGuiWindow_MenuBarRect(ImRect* pOut, ImGuiWindow* self) {
- *pOut = self->MenuBarRect();
-}
-CIMGUI_API ImGuiTabItem* ImGuiTabItem_ImGuiTabItem(void) {
- return IM_NEW(ImGuiTabItem)();
-}
-CIMGUI_API void ImGuiTabItem_destroy(ImGuiTabItem* self) {
- IM_DELETE(self);
-}
-CIMGUI_API ImGuiTabBar* ImGuiTabBar_ImGuiTabBar(void) {
- return IM_NEW(ImGuiTabBar)();
-}
-CIMGUI_API void ImGuiTabBar_destroy(ImGuiTabBar* self) {
- IM_DELETE(self);
-}
-CIMGUI_API ImGuiTableColumn* ImGuiTableColumn_ImGuiTableColumn(void) {
- return IM_NEW(ImGuiTableColumn)();
-}
-CIMGUI_API void ImGuiTableColumn_destroy(ImGuiTableColumn* self) {
- IM_DELETE(self);
-}
-CIMGUI_API ImGuiTableInstanceData*
-ImGuiTableInstanceData_ImGuiTableInstanceData(void) {
- return IM_NEW(ImGuiTableInstanceData)();
-}
-CIMGUI_API void ImGuiTableInstanceData_destroy(ImGuiTableInstanceData* self) {
- IM_DELETE(self);
-}
-CIMGUI_API ImGuiTable* ImGuiTable_ImGuiTable(void) {
- return IM_NEW(ImGuiTable)();
-}
-CIMGUI_API void ImGuiTable_destroy(ImGuiTable* self) {
- IM_DELETE(self);
-}
-CIMGUI_API ImGuiTableTempData* ImGuiTableTempData_ImGuiTableTempData(void) {
- return IM_NEW(ImGuiTableTempData)();
-}
-CIMGUI_API void ImGuiTableTempData_destroy(ImGuiTableTempData* self) {
- IM_DELETE(self);
-}
-CIMGUI_API ImGuiTableColumnSettings*
-ImGuiTableColumnSettings_ImGuiTableColumnSettings(void) {
- return IM_NEW(ImGuiTableColumnSettings)();
-}
-CIMGUI_API void ImGuiTableColumnSettings_destroy(
- ImGuiTableColumnSettings* self) {
- IM_DELETE(self);
-}
-CIMGUI_API ImGuiTableSettings* ImGuiTableSettings_ImGuiTableSettings(void) {
- return IM_NEW(ImGuiTableSettings)();
-}
-CIMGUI_API void ImGuiTableSettings_destroy(ImGuiTableSettings* self) {
- IM_DELETE(self);
-}
-CIMGUI_API ImGuiTableColumnSettings* ImGuiTableSettings_GetColumnSettings(
- ImGuiTableSettings* self) {
- return self->GetColumnSettings();
-}
-CIMGUI_API ImGuiWindow* igGetCurrentWindowRead() {
- return ImGui::GetCurrentWindowRead();
-}
-CIMGUI_API ImGuiWindow* igGetCurrentWindow() {
- return ImGui::GetCurrentWindow();
-}
-CIMGUI_API ImGuiWindow* igFindWindowByID(ImGuiID id) {
- return ImGui::FindWindowByID(id);
-}
-CIMGUI_API ImGuiWindow* igFindWindowByName(const char* name) {
- return ImGui::FindWindowByName(name);
-}
-CIMGUI_API void igUpdateWindowParentAndRootLinks(ImGuiWindow* window,
- ImGuiWindowFlags flags,
- ImGuiWindow* parent_window) {
- return ImGui::UpdateWindowParentAndRootLinks(window, flags, parent_window);
-}
-CIMGUI_API void igUpdateWindowSkipRefresh(ImGuiWindow* window) {
- return ImGui::UpdateWindowSkipRefresh(window);
-}
-CIMGUI_API void igCalcWindowNextAutoFitSize(ImVec2* pOut, ImGuiWindow* window) {
- *pOut = ImGui::CalcWindowNextAutoFitSize(window);
-}
-CIMGUI_API bool igIsWindowChildOf(ImGuiWindow* window,
- ImGuiWindow* potential_parent,
- bool popup_hierarchy,
- bool dock_hierarchy) {
- return ImGui::IsWindowChildOf(window, potential_parent, popup_hierarchy,
- dock_hierarchy);
-}
-CIMGUI_API bool igIsWindowWithinBeginStackOf(ImGuiWindow* window,
- ImGuiWindow* potential_parent) {
- return ImGui::IsWindowWithinBeginStackOf(window, potential_parent);
-}
-CIMGUI_API bool igIsWindowAbove(ImGuiWindow* potential_above,
- ImGuiWindow* potential_below) {
- return ImGui::IsWindowAbove(potential_above, potential_below);
-}
-CIMGUI_API bool igIsWindowNavFocusable(ImGuiWindow* window) {
- return ImGui::IsWindowNavFocusable(window);
-}
-CIMGUI_API void igSetWindowPos_WindowPtr(ImGuiWindow* window,
- const ImVec2 pos,
- ImGuiCond cond) {
- return ImGui::SetWindowPos(window, pos, cond);
-}
-CIMGUI_API void igSetWindowSize_WindowPtr(ImGuiWindow* window,
- const ImVec2 size,
- ImGuiCond cond) {
- return ImGui::SetWindowSize(window, size, cond);
-}
-CIMGUI_API void igSetWindowCollapsed_WindowPtr(ImGuiWindow* window,
- bool collapsed,
- ImGuiCond cond) {
- return ImGui::SetWindowCollapsed(window, collapsed, cond);
-}
-CIMGUI_API void igSetWindowHitTestHole(ImGuiWindow* window,
- const ImVec2 pos,
- const ImVec2 size) {
- return ImGui::SetWindowHitTestHole(window, pos, size);
-}
-CIMGUI_API void igSetWindowHiddenAndSkipItemsForCurrentFrame(
- ImGuiWindow* window) {
- return ImGui::SetWindowHiddenAndSkipItemsForCurrentFrame(window);
-}
-CIMGUI_API void igSetWindowParentWindowForFocusRoute(
- ImGuiWindow* window,
- ImGuiWindow* parent_window) {
- return ImGui::SetWindowParentWindowForFocusRoute(window, parent_window);
-}
-CIMGUI_API void igWindowRectAbsToRel(ImRect* pOut,
- ImGuiWindow* window,
- const ImRect r) {
- *pOut = ImGui::WindowRectAbsToRel(window, r);
-}
-CIMGUI_API void igWindowRectRelToAbs(ImRect* pOut,
- ImGuiWindow* window,
- const ImRect r) {
- *pOut = ImGui::WindowRectRelToAbs(window, r);
-}
-CIMGUI_API void igWindowPosRelToAbs(ImVec2* pOut,
- ImGuiWindow* window,
- const ImVec2 p) {
- *pOut = ImGui::WindowPosRelToAbs(window, p);
-}
-CIMGUI_API void igFocusWindow(ImGuiWindow* window,
- ImGuiFocusRequestFlags flags) {
- return ImGui::FocusWindow(window, flags);
-}
-CIMGUI_API void igFocusTopMostWindowUnderOne(ImGuiWindow* under_this_window,
- ImGuiWindow* ignore_window,
- ImGuiViewport* filter_viewport,
- ImGuiFocusRequestFlags flags) {
- return ImGui::FocusTopMostWindowUnderOne(under_this_window, ignore_window,
- filter_viewport, flags);
-}
-CIMGUI_API void igBringWindowToFocusFront(ImGuiWindow* window) {
- return ImGui::BringWindowToFocusFront(window);
-}
-CIMGUI_API void igBringWindowToDisplayFront(ImGuiWindow* window) {
- return ImGui::BringWindowToDisplayFront(window);
-}
-CIMGUI_API void igBringWindowToDisplayBack(ImGuiWindow* window) {
- return ImGui::BringWindowToDisplayBack(window);
-}
-CIMGUI_API void igBringWindowToDisplayBehind(ImGuiWindow* window,
- ImGuiWindow* above_window) {
- return ImGui::BringWindowToDisplayBehind(window, above_window);
-}
-CIMGUI_API int igFindWindowDisplayIndex(ImGuiWindow* window) {
- return ImGui::FindWindowDisplayIndex(window);
-}
-CIMGUI_API ImGuiWindow* igFindBottomMostVisibleWindowWithinBeginStack(
- ImGuiWindow* window) {
- return ImGui::FindBottomMostVisibleWindowWithinBeginStack(window);
-}
-CIMGUI_API void igSetNextWindowRefreshPolicy(ImGuiWindowRefreshFlags flags) {
- return ImGui::SetNextWindowRefreshPolicy(flags);
-}
-CIMGUI_API void igSetCurrentFont(ImFont* font) {
- return ImGui::SetCurrentFont(font);
-}
-CIMGUI_API ImFont* igGetDefaultFont() {
- return ImGui::GetDefaultFont();
-}
-CIMGUI_API ImDrawList* igGetForegroundDrawList_WindowPtr(ImGuiWindow* window) {
- return ImGui::GetForegroundDrawList(window);
-}
-CIMGUI_API void igAddDrawListToDrawDataEx(ImDrawData* draw_data,
- ImVector_ImDrawListPtr* out_list,
- ImDrawList* draw_list) {
- return ImGui::AddDrawListToDrawDataEx(draw_data, out_list, draw_list);
-}
-CIMGUI_API void igInitialize() {
- return ImGui::Initialize();
-}
-CIMGUI_API void igShutdown() {
- return ImGui::Shutdown();
-}
-CIMGUI_API void igUpdateInputEvents(bool trickle_fast_inputs) {
- return ImGui::UpdateInputEvents(trickle_fast_inputs);
-}
-CIMGUI_API void igUpdateHoveredWindowAndCaptureFlags() {
- return ImGui::UpdateHoveredWindowAndCaptureFlags();
-}
-CIMGUI_API void igStartMouseMovingWindow(ImGuiWindow* window) {
- return ImGui::StartMouseMovingWindow(window);
-}
-CIMGUI_API void igStartMouseMovingWindowOrNode(ImGuiWindow* window,
- ImGuiDockNode* node,
- bool undock) {
- return ImGui::StartMouseMovingWindowOrNode(window, node, undock);
-}
-CIMGUI_API void igUpdateMouseMovingWindowNewFrame() {
- return ImGui::UpdateMouseMovingWindowNewFrame();
-}
-CIMGUI_API void igUpdateMouseMovingWindowEndFrame() {
- return ImGui::UpdateMouseMovingWindowEndFrame();
-}
-CIMGUI_API ImGuiID igAddContextHook(ImGuiContext* context,
- const ImGuiContextHook* hook) {
- return ImGui::AddContextHook(context, hook);
-}
-CIMGUI_API void igRemoveContextHook(ImGuiContext* context,
- ImGuiID hook_to_remove) {
- return ImGui::RemoveContextHook(context, hook_to_remove);
-}
-CIMGUI_API void igCallContextHooks(ImGuiContext* context,
- ImGuiContextHookType type) {
- return ImGui::CallContextHooks(context, type);
-}
-CIMGUI_API void igTranslateWindowsInViewport(ImGuiViewportP* viewport,
- const ImVec2 old_pos,
- const ImVec2 new_pos) {
- return ImGui::TranslateWindowsInViewport(viewport, old_pos, new_pos);
-}
-CIMGUI_API void igScaleWindowsInViewport(ImGuiViewportP* viewport,
- float scale) {
- return ImGui::ScaleWindowsInViewport(viewport, scale);
-}
-CIMGUI_API void igDestroyPlatformWindow(ImGuiViewportP* viewport) {
- return ImGui::DestroyPlatformWindow(viewport);
-}
-CIMGUI_API void igSetWindowViewport(ImGuiWindow* window,
- ImGuiViewportP* viewport) {
- return ImGui::SetWindowViewport(window, viewport);
-}
-CIMGUI_API void igSetCurrentViewport(ImGuiWindow* window,
- ImGuiViewportP* viewport) {
- return ImGui::SetCurrentViewport(window, viewport);
-}
-CIMGUI_API const ImGuiPlatformMonitor* igGetViewportPlatformMonitor(
- ImGuiViewport* viewport) {
- return ImGui::GetViewportPlatformMonitor(viewport);
-}
-CIMGUI_API ImGuiViewportP* igFindHoveredViewportFromPlatformWindowStack(
- const ImVec2 mouse_platform_pos) {
- return ImGui::FindHoveredViewportFromPlatformWindowStack(mouse_platform_pos);
-}
-CIMGUI_API void igMarkIniSettingsDirty_Nil() {
- return ImGui::MarkIniSettingsDirty();
-}
-CIMGUI_API void igMarkIniSettingsDirty_WindowPtr(ImGuiWindow* window) {
- return ImGui::MarkIniSettingsDirty(window);
-}
-CIMGUI_API void igClearIniSettings() {
- return ImGui::ClearIniSettings();
-}
-CIMGUI_API void igAddSettingsHandler(const ImGuiSettingsHandler* handler) {
- return ImGui::AddSettingsHandler(handler);
-}
-CIMGUI_API void igRemoveSettingsHandler(const char* type_name) {
- return ImGui::RemoveSettingsHandler(type_name);
-}
-CIMGUI_API ImGuiSettingsHandler* igFindSettingsHandler(const char* type_name) {
- return ImGui::FindSettingsHandler(type_name);
-}
-CIMGUI_API ImGuiWindowSettings* igCreateNewWindowSettings(const char* name) {
- return ImGui::CreateNewWindowSettings(name);
-}
-CIMGUI_API ImGuiWindowSettings* igFindWindowSettingsByID(ImGuiID id) {
- return ImGui::FindWindowSettingsByID(id);
-}
-CIMGUI_API ImGuiWindowSettings* igFindWindowSettingsByWindow(
- ImGuiWindow* window) {
- return ImGui::FindWindowSettingsByWindow(window);
-}
-CIMGUI_API void igClearWindowSettings(const char* name) {
- return ImGui::ClearWindowSettings(name);
-}
-CIMGUI_API void igLocalizeRegisterEntries(const ImGuiLocEntry* entries,
- int count) {
- return ImGui::LocalizeRegisterEntries(entries, count);
-}
-CIMGUI_API const char* igLocalizeGetMsg(ImGuiLocKey key) {
- return ImGui::LocalizeGetMsg(key);
-}
-CIMGUI_API void igSetScrollX_WindowPtr(ImGuiWindow* window, float scroll_x) {
- return ImGui::SetScrollX(window, scroll_x);
-}
-CIMGUI_API void igSetScrollY_WindowPtr(ImGuiWindow* window, float scroll_y) {
- return ImGui::SetScrollY(window, scroll_y);
-}
-CIMGUI_API void igSetScrollFromPosX_WindowPtr(ImGuiWindow* window,
- float local_x,
- float center_x_ratio) {
- return ImGui::SetScrollFromPosX(window, local_x, center_x_ratio);
-}
-CIMGUI_API void igSetScrollFromPosY_WindowPtr(ImGuiWindow* window,
- float local_y,
- float center_y_ratio) {
- return ImGui::SetScrollFromPosY(window, local_y, center_y_ratio);
-}
-CIMGUI_API void igScrollToItem(ImGuiScrollFlags flags) {
- return ImGui::ScrollToItem(flags);
-}
-CIMGUI_API void igScrollToRect(ImGuiWindow* window,
- const ImRect rect,
- ImGuiScrollFlags flags) {
- return ImGui::ScrollToRect(window, rect, flags);
-}
-CIMGUI_API void igScrollToRectEx(ImVec2* pOut,
- ImGuiWindow* window,
- const ImRect rect,
- ImGuiScrollFlags flags) {
- *pOut = ImGui::ScrollToRectEx(window, rect, flags);
-}
-CIMGUI_API void igScrollToBringRectIntoView(ImGuiWindow* window,
- const ImRect rect) {
- return ImGui::ScrollToBringRectIntoView(window, rect);
-}
-CIMGUI_API ImGuiItemStatusFlags igGetItemStatusFlags() {
- return ImGui::GetItemStatusFlags();
-}
-CIMGUI_API ImGuiItemFlags igGetItemFlags() {
- return ImGui::GetItemFlags();
-}
-CIMGUI_API ImGuiID igGetActiveID() {
- return ImGui::GetActiveID();
-}
-CIMGUI_API ImGuiID igGetFocusID() {
- return ImGui::GetFocusID();
-}
-CIMGUI_API void igSetActiveID(ImGuiID id, ImGuiWindow* window) {
- return ImGui::SetActiveID(id, window);
-}
-CIMGUI_API void igSetFocusID(ImGuiID id, ImGuiWindow* window) {
- return ImGui::SetFocusID(id, window);
-}
-CIMGUI_API void igClearActiveID() {
- return ImGui::ClearActiveID();
-}
-CIMGUI_API ImGuiID igGetHoveredID() {
- return ImGui::GetHoveredID();
-}
-CIMGUI_API void igSetHoveredID(ImGuiID id) {
- return ImGui::SetHoveredID(id);
-}
-CIMGUI_API void igKeepAliveID(ImGuiID id) {
- return ImGui::KeepAliveID(id);
-}
-CIMGUI_API void igMarkItemEdited(ImGuiID id) {
- return ImGui::MarkItemEdited(id);
-}
-CIMGUI_API void igPushOverrideID(ImGuiID id) {
- return ImGui::PushOverrideID(id);
-}
-CIMGUI_API ImGuiID igGetIDWithSeed_Str(const char* str_id_begin,
- const char* str_id_end,
- ImGuiID seed) {
- return ImGui::GetIDWithSeed(str_id_begin, str_id_end, seed);
-}
-CIMGUI_API ImGuiID igGetIDWithSeed_Int(int n, ImGuiID seed) {
- return ImGui::GetIDWithSeed(n, seed);
-}
-CIMGUI_API void igItemSize_Vec2(const ImVec2 size, float text_baseline_y) {
- return ImGui::ItemSize(size, text_baseline_y);
-}
-CIMGUI_API void igItemSize_Rect(const ImRect bb, float text_baseline_y) {
- return ImGui::ItemSize(bb, text_baseline_y);
-}
-CIMGUI_API bool igItemAdd(const ImRect bb,
- ImGuiID id,
- const ImRect* nav_bb,
- ImGuiItemFlags extra_flags) {
- return ImGui::ItemAdd(bb, id, nav_bb, extra_flags);
-}
-CIMGUI_API bool igItemHoverable(const ImRect bb,
- ImGuiID id,
- ImGuiItemFlags item_flags) {
- return ImGui::ItemHoverable(bb, id, item_flags);
-}
-CIMGUI_API bool igIsWindowContentHoverable(ImGuiWindow* window,
- ImGuiHoveredFlags flags) {
- return ImGui::IsWindowContentHoverable(window, flags);
-}
-CIMGUI_API bool igIsClippedEx(const ImRect bb, ImGuiID id) {
- return ImGui::IsClippedEx(bb, id);
-}
-CIMGUI_API void igSetLastItemData(ImGuiID item_id,
- ImGuiItemFlags in_flags,
- ImGuiItemStatusFlags status_flags,
- const ImRect item_rect) {
- return ImGui::SetLastItemData(item_id, in_flags, status_flags, item_rect);
-}
-CIMGUI_API void igCalcItemSize(ImVec2* pOut,
- ImVec2 size,
- float default_w,
- float default_h) {
- *pOut = ImGui::CalcItemSize(size, default_w, default_h);
-}
-CIMGUI_API float igCalcWrapWidthForPos(const ImVec2 pos, float wrap_pos_x) {
- return ImGui::CalcWrapWidthForPos(pos, wrap_pos_x);
-}
-CIMGUI_API void igPushMultiItemsWidths(int components, float width_full) {
- return ImGui::PushMultiItemsWidths(components, width_full);
-}
-CIMGUI_API bool igIsItemToggledSelection() {
- return ImGui::IsItemToggledSelection();
-}
-CIMGUI_API void igGetContentRegionMaxAbs(ImVec2* pOut) {
- *pOut = ImGui::GetContentRegionMaxAbs();
-}
-CIMGUI_API void igShrinkWidths(ImGuiShrinkWidthItem* items,
- int count,
- float width_excess) {
- return ImGui::ShrinkWidths(items, count, width_excess);
-}
-CIMGUI_API void igPushItemFlag(ImGuiItemFlags option, bool enabled) {
- return ImGui::PushItemFlag(option, enabled);
-}
-CIMGUI_API void igPopItemFlag() {
- return ImGui::PopItemFlag();
-}
-CIMGUI_API const ImGuiDataVarInfo* igGetStyleVarInfo(ImGuiStyleVar idx) {
- return ImGui::GetStyleVarInfo(idx);
-}
-CIMGUI_API void igLogBegin(ImGuiLogType type, int auto_open_depth) {
- return ImGui::LogBegin(type, auto_open_depth);
-}
-CIMGUI_API void igLogToBuffer(int auto_open_depth) {
- return ImGui::LogToBuffer(auto_open_depth);
-}
-CIMGUI_API void igLogRenderedText(const ImVec2* ref_pos,
- const char* text,
- const char* text_end) {
- return ImGui::LogRenderedText(ref_pos, text, text_end);
-}
-CIMGUI_API void igLogSetNextTextDecoration(const char* prefix,
- const char* suffix) {
- return ImGui::LogSetNextTextDecoration(prefix, suffix);
-}
-CIMGUI_API bool igBeginChildEx(const char* name,
- ImGuiID id,
- const ImVec2 size_arg,
- ImGuiChildFlags child_flags,
- ImGuiWindowFlags window_flags) {
- return ImGui::BeginChildEx(name, id, size_arg, child_flags, window_flags);
-}
-CIMGUI_API void igOpenPopupEx(ImGuiID id, ImGuiPopupFlags popup_flags) {
- return ImGui::OpenPopupEx(id, popup_flags);
-}
-CIMGUI_API void igClosePopupToLevel(int remaining,
- bool restore_focus_to_window_under_popup) {
- return ImGui::ClosePopupToLevel(remaining,
- restore_focus_to_window_under_popup);
-}
-CIMGUI_API void igClosePopupsOverWindow(
- ImGuiWindow* ref_window,
- bool restore_focus_to_window_under_popup) {
- return ImGui::ClosePopupsOverWindow(ref_window,
- restore_focus_to_window_under_popup);
-}
-CIMGUI_API void igClosePopupsExceptModals() {
- return ImGui::ClosePopupsExceptModals();
-}
-CIMGUI_API bool igIsPopupOpen_ID(ImGuiID id, ImGuiPopupFlags popup_flags) {
- return ImGui::IsPopupOpen(id, popup_flags);
-}
-CIMGUI_API bool igBeginPopupEx(ImGuiID id, ImGuiWindowFlags extra_flags) {
- return ImGui::BeginPopupEx(id, extra_flags);
-}
-CIMGUI_API bool igBeginTooltipEx(ImGuiTooltipFlags tooltip_flags,
- ImGuiWindowFlags extra_window_flags) {
- return ImGui::BeginTooltipEx(tooltip_flags, extra_window_flags);
-}
-CIMGUI_API bool igBeginTooltipHidden() {
- return ImGui::BeginTooltipHidden();
-}
-CIMGUI_API void igGetPopupAllowedExtentRect(ImRect* pOut, ImGuiWindow* window) {
- *pOut = ImGui::GetPopupAllowedExtentRect(window);
-}
-CIMGUI_API ImGuiWindow* igGetTopMostPopupModal() {
- return ImGui::GetTopMostPopupModal();
-}
-CIMGUI_API ImGuiWindow* igGetTopMostAndVisiblePopupModal() {
- return ImGui::GetTopMostAndVisiblePopupModal();
-}
-CIMGUI_API ImGuiWindow* igFindBlockingModal(ImGuiWindow* window) {
- return ImGui::FindBlockingModal(window);
-}
-CIMGUI_API void igFindBestWindowPosForPopup(ImVec2* pOut, ImGuiWindow* window) {
- *pOut = ImGui::FindBestWindowPosForPopup(window);
-}
-CIMGUI_API void igFindBestWindowPosForPopupEx(ImVec2* pOut,
- const ImVec2 ref_pos,
- const ImVec2 size,
- ImGuiDir* last_dir,
- const ImRect r_outer,
- const ImRect r_avoid,
- ImGuiPopupPositionPolicy policy) {
- *pOut = ImGui::FindBestWindowPosForPopupEx(ref_pos, size, last_dir, r_outer,
- r_avoid, policy);
-}
-CIMGUI_API bool igBeginViewportSideBar(const char* name,
- ImGuiViewport* viewport,
- ImGuiDir dir,
- float size,
- ImGuiWindowFlags window_flags) {
- return ImGui::BeginViewportSideBar(name, viewport, dir, size, window_flags);
-}
-CIMGUI_API bool igBeginMenuEx(const char* label,
- const char* icon,
- bool enabled) {
- return ImGui::BeginMenuEx(label, icon, enabled);
-}
-CIMGUI_API bool igMenuItemEx(const char* label,
- const char* icon,
- const char* shortcut,
- bool selected,
- bool enabled) {
- return ImGui::MenuItemEx(label, icon, shortcut, selected, enabled);
-}
-CIMGUI_API bool igBeginComboPopup(ImGuiID popup_id,
- const ImRect bb,
- ImGuiComboFlags flags) {
- return ImGui::BeginComboPopup(popup_id, bb, flags);
-}
-CIMGUI_API bool igBeginComboPreview() {
- return ImGui::BeginComboPreview();
-}
-CIMGUI_API void igEndComboPreview() {
- return ImGui::EndComboPreview();
-}
-CIMGUI_API void igNavInitWindow(ImGuiWindow* window, bool force_reinit) {
- return ImGui::NavInitWindow(window, force_reinit);
-}
-CIMGUI_API void igNavInitRequestApplyResult() {
- return ImGui::NavInitRequestApplyResult();
-}
-CIMGUI_API bool igNavMoveRequestButNoResultYet() {
- return ImGui::NavMoveRequestButNoResultYet();
-}
-CIMGUI_API void igNavMoveRequestSubmit(ImGuiDir move_dir,
- ImGuiDir clip_dir,
- ImGuiNavMoveFlags move_flags,
- ImGuiScrollFlags scroll_flags) {
- return ImGui::NavMoveRequestSubmit(move_dir, clip_dir, move_flags,
- scroll_flags);
-}
-CIMGUI_API void igNavMoveRequestForward(ImGuiDir move_dir,
- ImGuiDir clip_dir,
- ImGuiNavMoveFlags move_flags,
- ImGuiScrollFlags scroll_flags) {
- return ImGui::NavMoveRequestForward(move_dir, clip_dir, move_flags,
- scroll_flags);
-}
-CIMGUI_API void igNavMoveRequestResolveWithLastItem(ImGuiNavItemData* result) {
- return ImGui::NavMoveRequestResolveWithLastItem(result);
-}
-CIMGUI_API void igNavMoveRequestResolveWithPastTreeNode(
- ImGuiNavItemData* result,
- ImGuiNavTreeNodeData* tree_node_data) {
- return ImGui::NavMoveRequestResolveWithPastTreeNode(result, tree_node_data);
-}
-CIMGUI_API void igNavMoveRequestCancel() {
- return ImGui::NavMoveRequestCancel();
-}
-CIMGUI_API void igNavMoveRequestApplyResult() {
- return ImGui::NavMoveRequestApplyResult();
-}
-CIMGUI_API void igNavMoveRequestTryWrapping(ImGuiWindow* window,
- ImGuiNavMoveFlags move_flags) {
- return ImGui::NavMoveRequestTryWrapping(window, move_flags);
-}
-CIMGUI_API void igNavHighlightActivated(ImGuiID id) {
- return ImGui::NavHighlightActivated(id);
-}
-CIMGUI_API void igNavClearPreferredPosForAxis(ImGuiAxis axis) {
- return ImGui::NavClearPreferredPosForAxis(axis);
-}
-CIMGUI_API void igNavRestoreHighlightAfterMove() {
- return ImGui::NavRestoreHighlightAfterMove();
-}
-CIMGUI_API void igNavUpdateCurrentWindowIsScrollPushableX() {
- return ImGui::NavUpdateCurrentWindowIsScrollPushableX();
-}
-CIMGUI_API void igSetNavWindow(ImGuiWindow* window) {
- return ImGui::SetNavWindow(window);
-}
-CIMGUI_API void igSetNavID(ImGuiID id,
- ImGuiNavLayer nav_layer,
- ImGuiID focus_scope_id,
- const ImRect rect_rel) {
- return ImGui::SetNavID(id, nav_layer, focus_scope_id, rect_rel);
-}
-CIMGUI_API void igSetNavFocusScope(ImGuiID focus_scope_id) {
- return ImGui::SetNavFocusScope(focus_scope_id);
-}
-CIMGUI_API void igFocusItem() {
- return ImGui::FocusItem();
-}
-CIMGUI_API void igActivateItemByID(ImGuiID id) {
- return ImGui::ActivateItemByID(id);
-}
-CIMGUI_API bool igIsNamedKey(ImGuiKey key) {
- return ImGui::IsNamedKey(key);
-}
-CIMGUI_API bool igIsNamedKeyOrModKey(ImGuiKey key) {
- return ImGui::IsNamedKeyOrModKey(key);
-}
-CIMGUI_API bool igIsLegacyKey(ImGuiKey key) {
- return ImGui::IsLegacyKey(key);
-}
-CIMGUI_API bool igIsKeyboardKey(ImGuiKey key) {
- return ImGui::IsKeyboardKey(key);
-}
-CIMGUI_API bool igIsGamepadKey(ImGuiKey key) {
- return ImGui::IsGamepadKey(key);
-}
-CIMGUI_API bool igIsMouseKey(ImGuiKey key) {
- return ImGui::IsMouseKey(key);
-}
-CIMGUI_API bool igIsAliasKey(ImGuiKey key) {
- return ImGui::IsAliasKey(key);
-}
-CIMGUI_API bool igIsModKey(ImGuiKey key) {
- return ImGui::IsModKey(key);
-}
-CIMGUI_API ImGuiKeyChord igFixupKeyChord(ImGuiContext* ctx,
- ImGuiKeyChord key_chord) {
- return ImGui::FixupKeyChord(ctx, key_chord);
-}
-CIMGUI_API ImGuiKey igConvertSingleModFlagToKey(ImGuiContext* ctx,
- ImGuiKey key) {
- return ImGui::ConvertSingleModFlagToKey(ctx, key);
-}
-CIMGUI_API ImGuiKeyData* igGetKeyData_ContextPtr(ImGuiContext* ctx,
- ImGuiKey key) {
- return ImGui::GetKeyData(ctx, key);
-}
-CIMGUI_API ImGuiKeyData* igGetKeyData_Key(ImGuiKey key) {
- return ImGui::GetKeyData(key);
-}
-CIMGUI_API const char* igGetKeyChordName(ImGuiKeyChord key_chord) {
- return ImGui::GetKeyChordName(key_chord);
-}
-CIMGUI_API ImGuiKey igMouseButtonToKey(ImGuiMouseButton button) {
- return ImGui::MouseButtonToKey(button);
-}
-CIMGUI_API bool igIsMouseDragPastThreshold(ImGuiMouseButton button,
- float lock_threshold) {
- return ImGui::IsMouseDragPastThreshold(button, lock_threshold);
-}
-CIMGUI_API void igGetKeyMagnitude2d(ImVec2* pOut,
- ImGuiKey key_left,
- ImGuiKey key_right,
- ImGuiKey key_up,
- ImGuiKey key_down) {
- *pOut = ImGui::GetKeyMagnitude2d(key_left, key_right, key_up, key_down);
-}
-CIMGUI_API float igGetNavTweakPressedAmount(ImGuiAxis axis) {
- return ImGui::GetNavTweakPressedAmount(axis);
-}
-CIMGUI_API int igCalcTypematicRepeatAmount(float t0,
- float t1,
- float repeat_delay,
- float repeat_rate) {
- return ImGui::CalcTypematicRepeatAmount(t0, t1, repeat_delay, repeat_rate);
-}
-CIMGUI_API void igGetTypematicRepeatRate(ImGuiInputFlags flags,
- float* repeat_delay,
- float* repeat_rate) {
- return ImGui::GetTypematicRepeatRate(flags, repeat_delay, repeat_rate);
-}
-CIMGUI_API void igTeleportMousePos(const ImVec2 pos) {
- return ImGui::TeleportMousePos(pos);
-}
-CIMGUI_API void igSetActiveIdUsingAllKeyboardKeys() {
- return ImGui::SetActiveIdUsingAllKeyboardKeys();
-}
-CIMGUI_API bool igIsActiveIdUsingNavDir(ImGuiDir dir) {
- return ImGui::IsActiveIdUsingNavDir(dir);
-}
-CIMGUI_API ImGuiID igGetKeyOwner(ImGuiKey key) {
- return ImGui::GetKeyOwner(key);
-}
-CIMGUI_API void igSetKeyOwner(ImGuiKey key,
- ImGuiID owner_id,
- ImGuiInputFlags flags) {
- return ImGui::SetKeyOwner(key, owner_id, flags);
-}
-CIMGUI_API void igSetKeyOwnersForKeyChord(ImGuiKeyChord key,
- ImGuiID owner_id,
- ImGuiInputFlags flags) {
- return ImGui::SetKeyOwnersForKeyChord(key, owner_id, flags);
-}
-CIMGUI_API void igSetItemKeyOwner(ImGuiKey key, ImGuiInputFlags flags) {
- return ImGui::SetItemKeyOwner(key, flags);
-}
-CIMGUI_API bool igTestKeyOwner(ImGuiKey key, ImGuiID owner_id) {
- return ImGui::TestKeyOwner(key, owner_id);
-}
-CIMGUI_API ImGuiKeyOwnerData* igGetKeyOwnerData(ImGuiContext* ctx,
- ImGuiKey key) {
- return ImGui::GetKeyOwnerData(ctx, key);
-}
-CIMGUI_API bool igIsKeyDown_ID(ImGuiKey key, ImGuiID owner_id) {
- return ImGui::IsKeyDown(key, owner_id);
-}
-CIMGUI_API bool igIsKeyPressed_ID(ImGuiKey key,
- ImGuiID owner_id,
- ImGuiInputFlags flags) {
- return ImGui::IsKeyPressed(key, owner_id, flags);
-}
-CIMGUI_API bool igIsKeyReleased_ID(ImGuiKey key, ImGuiID owner_id) {
- return ImGui::IsKeyReleased(key, owner_id);
-}
-CIMGUI_API bool igIsMouseDown_ID(ImGuiMouseButton button, ImGuiID owner_id) {
- return ImGui::IsMouseDown(button, owner_id);
-}
-CIMGUI_API bool igIsMouseClicked_ID(ImGuiMouseButton button,
- ImGuiID owner_id,
- ImGuiInputFlags flags) {
- return ImGui::IsMouseClicked(button, owner_id, flags);
-}
-CIMGUI_API bool igIsMouseReleased_ID(ImGuiMouseButton button,
- ImGuiID owner_id) {
- return ImGui::IsMouseReleased(button, owner_id);
-}
-CIMGUI_API bool igIsMouseDoubleClicked_ID(ImGuiMouseButton button,
- ImGuiID owner_id) {
- return ImGui::IsMouseDoubleClicked(button, owner_id);
-}
-CIMGUI_API bool igIsKeyChordPressed_ID(ImGuiKeyChord key_chord,
- ImGuiID owner_id,
- ImGuiInputFlags flags) {
- return ImGui::IsKeyChordPressed(key_chord, owner_id, flags);
-}
-CIMGUI_API void igSetNextItemShortcut(ImGuiKeyChord key_chord) {
- return ImGui::SetNextItemShortcut(key_chord);
-}
-CIMGUI_API bool igShortcut(ImGuiKeyChord key_chord,
- ImGuiID owner_id,
- ImGuiInputFlags flags) {
- return ImGui::Shortcut(key_chord, owner_id, flags);
-}
-CIMGUI_API bool igSetShortcutRouting(ImGuiKeyChord key_chord,
- ImGuiID owner_id,
- ImGuiInputFlags flags) {
- return ImGui::SetShortcutRouting(key_chord, owner_id, flags);
-}
-CIMGUI_API bool igTestShortcutRouting(ImGuiKeyChord key_chord,
- ImGuiID owner_id) {
- return ImGui::TestShortcutRouting(key_chord, owner_id);
-}
-CIMGUI_API ImGuiKeyRoutingData* igGetShortcutRoutingData(
- ImGuiKeyChord key_chord) {
- return ImGui::GetShortcutRoutingData(key_chord);
-}
-CIMGUI_API void igDockContextInitialize(ImGuiContext* ctx) {
- return ImGui::DockContextInitialize(ctx);
-}
-CIMGUI_API void igDockContextShutdown(ImGuiContext* ctx) {
- return ImGui::DockContextShutdown(ctx);
-}
-CIMGUI_API void igDockContextClearNodes(ImGuiContext* ctx,
- ImGuiID root_id,
- bool clear_settings_refs) {
- return ImGui::DockContextClearNodes(ctx, root_id, clear_settings_refs);
-}
-CIMGUI_API void igDockContextRebuildNodes(ImGuiContext* ctx) {
- return ImGui::DockContextRebuildNodes(ctx);
-}
-CIMGUI_API void igDockContextNewFrameUpdateUndocking(ImGuiContext* ctx) {
- return ImGui::DockContextNewFrameUpdateUndocking(ctx);
-}
-CIMGUI_API void igDockContextNewFrameUpdateDocking(ImGuiContext* ctx) {
- return ImGui::DockContextNewFrameUpdateDocking(ctx);
-}
-CIMGUI_API void igDockContextEndFrame(ImGuiContext* ctx) {
- return ImGui::DockContextEndFrame(ctx);
-}
-CIMGUI_API ImGuiID igDockContextGenNodeID(ImGuiContext* ctx) {
- return ImGui::DockContextGenNodeID(ctx);
-}
-CIMGUI_API void igDockContextQueueDock(ImGuiContext* ctx,
- ImGuiWindow* target,
- ImGuiDockNode* target_node,
- ImGuiWindow* payload,
- ImGuiDir split_dir,
- float split_ratio,
- bool split_outer) {
- return ImGui::DockContextQueueDock(ctx, target, target_node, payload,
- split_dir, split_ratio, split_outer);
-}
-CIMGUI_API void igDockContextQueueUndockWindow(ImGuiContext* ctx,
- ImGuiWindow* window) {
- return ImGui::DockContextQueueUndockWindow(ctx, window);
-}
-CIMGUI_API void igDockContextQueueUndockNode(ImGuiContext* ctx,
- ImGuiDockNode* node) {
- return ImGui::DockContextQueueUndockNode(ctx, node);
-}
-CIMGUI_API void igDockContextProcessUndockWindow(
- ImGuiContext* ctx,
- ImGuiWindow* window,
- bool clear_persistent_docking_ref) {
- return ImGui::DockContextProcessUndockWindow(ctx, window,
- clear_persistent_docking_ref);
-}
-CIMGUI_API void igDockContextProcessUndockNode(ImGuiContext* ctx,
- ImGuiDockNode* node) {
- return ImGui::DockContextProcessUndockNode(ctx, node);
-}
-CIMGUI_API bool igDockContextCalcDropPosForDocking(ImGuiWindow* target,
- ImGuiDockNode* target_node,
- ImGuiWindow* payload_window,
- ImGuiDockNode* payload_node,
- ImGuiDir split_dir,
- bool split_outer,
- ImVec2* out_pos) {
- return ImGui::DockContextCalcDropPosForDocking(
- target, target_node, payload_window, payload_node, split_dir, split_outer,
- out_pos);
-}
-CIMGUI_API ImGuiDockNode* igDockContextFindNodeByID(ImGuiContext* ctx,
- ImGuiID id) {
- return ImGui::DockContextFindNodeByID(ctx, id);
-}
-CIMGUI_API void igDockNodeWindowMenuHandler_Default(ImGuiContext* ctx,
- ImGuiDockNode* node,
- ImGuiTabBar* tab_bar) {
- return ImGui::DockNodeWindowMenuHandler_Default(ctx, node, tab_bar);
-}
-CIMGUI_API bool igDockNodeBeginAmendTabBar(ImGuiDockNode* node) {
- return ImGui::DockNodeBeginAmendTabBar(node);
-}
-CIMGUI_API void igDockNodeEndAmendTabBar() {
- return ImGui::DockNodeEndAmendTabBar();
-}
-CIMGUI_API ImGuiDockNode* igDockNodeGetRootNode(ImGuiDockNode* node) {
- return ImGui::DockNodeGetRootNode(node);
-}
-CIMGUI_API bool igDockNodeIsInHierarchyOf(ImGuiDockNode* node,
- ImGuiDockNode* parent) {
- return ImGui::DockNodeIsInHierarchyOf(node, parent);
-}
-CIMGUI_API int igDockNodeGetDepth(const ImGuiDockNode* node) {
- return ImGui::DockNodeGetDepth(node);
-}
-CIMGUI_API ImGuiID igDockNodeGetWindowMenuButtonId(const ImGuiDockNode* node) {
- return ImGui::DockNodeGetWindowMenuButtonId(node);
-}
-CIMGUI_API ImGuiDockNode* igGetWindowDockNode() {
- return ImGui::GetWindowDockNode();
-}
-CIMGUI_API bool igGetWindowAlwaysWantOwnTabBar(ImGuiWindow* window) {
- return ImGui::GetWindowAlwaysWantOwnTabBar(window);
-}
-CIMGUI_API void igBeginDocked(ImGuiWindow* window, bool* p_open) {
- return ImGui::BeginDocked(window, p_open);
-}
-CIMGUI_API void igBeginDockableDragDropSource(ImGuiWindow* window) {
- return ImGui::BeginDockableDragDropSource(window);
-}
-CIMGUI_API void igBeginDockableDragDropTarget(ImGuiWindow* window) {
- return ImGui::BeginDockableDragDropTarget(window);
-}
-CIMGUI_API void igSetWindowDock(ImGuiWindow* window,
- ImGuiID dock_id,
- ImGuiCond cond) {
- return ImGui::SetWindowDock(window, dock_id, cond);
-}
-CIMGUI_API void igDockBuilderDockWindow(const char* window_name,
- ImGuiID node_id) {
- return ImGui::DockBuilderDockWindow(window_name, node_id);
-}
-CIMGUI_API ImGuiDockNode* igDockBuilderGetNode(ImGuiID node_id) {
- return ImGui::DockBuilderGetNode(node_id);
-}
-CIMGUI_API ImGuiDockNode* igDockBuilderGetCentralNode(ImGuiID node_id) {
- return ImGui::DockBuilderGetCentralNode(node_id);
-}
-CIMGUI_API ImGuiID igDockBuilderAddNode(ImGuiID node_id,
- ImGuiDockNodeFlags flags) {
- return ImGui::DockBuilderAddNode(node_id, flags);
-}
-CIMGUI_API void igDockBuilderRemoveNode(ImGuiID node_id) {
- return ImGui::DockBuilderRemoveNode(node_id);
-}
-CIMGUI_API void igDockBuilderRemoveNodeDockedWindows(ImGuiID node_id,
- bool clear_settings_refs) {
- return ImGui::DockBuilderRemoveNodeDockedWindows(node_id,
- clear_settings_refs);
-}
-CIMGUI_API void igDockBuilderRemoveNodeChildNodes(ImGuiID node_id) {
- return ImGui::DockBuilderRemoveNodeChildNodes(node_id);
-}
-CIMGUI_API void igDockBuilderSetNodePos(ImGuiID node_id, ImVec2 pos) {
- return ImGui::DockBuilderSetNodePos(node_id, pos);
-}
-CIMGUI_API void igDockBuilderSetNodeSize(ImGuiID node_id, ImVec2 size) {
- return ImGui::DockBuilderSetNodeSize(node_id, size);
-}
-CIMGUI_API ImGuiID igDockBuilderSplitNode(ImGuiID node_id,
- ImGuiDir split_dir,
- float size_ratio_for_node_at_dir,
- ImGuiID* out_id_at_dir,
- ImGuiID* out_id_at_opposite_dir) {
- return ImGui::DockBuilderSplitNode(node_id, split_dir,
- size_ratio_for_node_at_dir, out_id_at_dir,
- out_id_at_opposite_dir);
-}
-CIMGUI_API void igDockBuilderCopyDockSpace(
- ImGuiID src_dockspace_id,
- ImGuiID dst_dockspace_id,
- ImVector_const_charPtr* in_window_remap_pairs) {
- return ImGui::DockBuilderCopyDockSpace(src_dockspace_id, dst_dockspace_id,
- in_window_remap_pairs);
-}
-CIMGUI_API void igDockBuilderCopyNode(ImGuiID src_node_id,
- ImGuiID dst_node_id,
- ImVector_ImGuiID* out_node_remap_pairs) {
- return ImGui::DockBuilderCopyNode(src_node_id, dst_node_id,
- out_node_remap_pairs);
-}
-CIMGUI_API void igDockBuilderCopyWindowSettings(const char* src_name,
- const char* dst_name) {
- return ImGui::DockBuilderCopyWindowSettings(src_name, dst_name);
-}
-CIMGUI_API void igDockBuilderFinish(ImGuiID node_id) {
- return ImGui::DockBuilderFinish(node_id);
-}
-CIMGUI_API void igPushFocusScope(ImGuiID id) {
- return ImGui::PushFocusScope(id);
-}
-CIMGUI_API void igPopFocusScope() {
- return ImGui::PopFocusScope();
-}
-CIMGUI_API ImGuiID igGetCurrentFocusScope() {
- return ImGui::GetCurrentFocusScope();
-}
-CIMGUI_API bool igIsDragDropActive() {
- return ImGui::IsDragDropActive();
-}
-CIMGUI_API bool igBeginDragDropTargetCustom(const ImRect bb, ImGuiID id) {
- return ImGui::BeginDragDropTargetCustom(bb, id);
-}
-CIMGUI_API void igClearDragDrop() {
- return ImGui::ClearDragDrop();
-}
-CIMGUI_API bool igIsDragDropPayloadBeingAccepted() {
- return ImGui::IsDragDropPayloadBeingAccepted();
-}
-CIMGUI_API void igRenderDragDropTargetRect(const ImRect bb,
- const ImRect item_clip_rect) {
- return ImGui::RenderDragDropTargetRect(bb, item_clip_rect);
-}
-CIMGUI_API ImGuiTypingSelectRequest* igGetTypingSelectRequest(
- ImGuiTypingSelectFlags flags) {
- return ImGui::GetTypingSelectRequest(flags);
-}
-CIMGUI_API int igTypingSelectFindMatch(ImGuiTypingSelectRequest* req,
- int items_count,
- const char* (*get_item_name_func)(void*,
- int),
- void* user_data,
- int nav_item_idx) {
- return ImGui::TypingSelectFindMatch(req, items_count, get_item_name_func,
- user_data, nav_item_idx);
-}
-CIMGUI_API int igTypingSelectFindNextSingleCharMatch(
- ImGuiTypingSelectRequest* req,
- int items_count,
- const char* (*get_item_name_func)(void*, int),
- void* user_data,
- int nav_item_idx) {
- return ImGui::TypingSelectFindNextSingleCharMatch(
- req, items_count, get_item_name_func, user_data, nav_item_idx);
-}
-CIMGUI_API int igTypingSelectFindBestLeadingMatch(
- ImGuiTypingSelectRequest* req,
- int items_count,
- const char* (*get_item_name_func)(void*, int),
- void* user_data) {
- return ImGui::TypingSelectFindBestLeadingMatch(req, items_count,
- get_item_name_func, user_data);
-}
-CIMGUI_API void igSetWindowClipRectBeforeSetChannel(ImGuiWindow* window,
- const ImRect clip_rect) {
- return ImGui::SetWindowClipRectBeforeSetChannel(window, clip_rect);
-}
-CIMGUI_API void igBeginColumns(const char* str_id,
- int count,
- ImGuiOldColumnFlags flags) {
- return ImGui::BeginColumns(str_id, count, flags);
-}
-CIMGUI_API void igEndColumns() {
- return ImGui::EndColumns();
-}
-CIMGUI_API void igPushColumnClipRect(int column_index) {
- return ImGui::PushColumnClipRect(column_index);
-}
-CIMGUI_API void igPushColumnsBackground() {
- return ImGui::PushColumnsBackground();
-}
-CIMGUI_API void igPopColumnsBackground() {
- return ImGui::PopColumnsBackground();
-}
-CIMGUI_API ImGuiID igGetColumnsID(const char* str_id, int count) {
- return ImGui::GetColumnsID(str_id, count);
-}
-CIMGUI_API ImGuiOldColumns* igFindOrCreateColumns(ImGuiWindow* window,
- ImGuiID id) {
- return ImGui::FindOrCreateColumns(window, id);
-}
-CIMGUI_API float igGetColumnOffsetFromNorm(const ImGuiOldColumns* columns,
- float offset_norm) {
- return ImGui::GetColumnOffsetFromNorm(columns, offset_norm);
-}
-CIMGUI_API float igGetColumnNormFromOffset(const ImGuiOldColumns* columns,
- float offset) {
- return ImGui::GetColumnNormFromOffset(columns, offset);
-}
-CIMGUI_API void igTableOpenContextMenu(int column_n) {
- return ImGui::TableOpenContextMenu(column_n);
-}
-CIMGUI_API void igTableSetColumnWidth(int column_n, float width) {
- return ImGui::TableSetColumnWidth(column_n, width);
-}
-CIMGUI_API void igTableSetColumnSortDirection(int column_n,
- ImGuiSortDirection sort_direction,
- bool append_to_sort_specs) {
- return ImGui::TableSetColumnSortDirection(column_n, sort_direction,
- append_to_sort_specs);
-}
-CIMGUI_API int igTableGetHoveredColumn() {
- return ImGui::TableGetHoveredColumn();
-}
-CIMGUI_API int igTableGetHoveredRow() {
- return ImGui::TableGetHoveredRow();
-}
-CIMGUI_API float igTableGetHeaderRowHeight() {
- return ImGui::TableGetHeaderRowHeight();
-}
-CIMGUI_API float igTableGetHeaderAngledMaxLabelWidth() {
- return ImGui::TableGetHeaderAngledMaxLabelWidth();
-}
-CIMGUI_API void igTablePushBackgroundChannel() {
- return ImGui::TablePushBackgroundChannel();
-}
-CIMGUI_API void igTablePopBackgroundChannel() {
- return ImGui::TablePopBackgroundChannel();
-}
-CIMGUI_API void igTableAngledHeadersRowEx(ImGuiID row_id,
- float angle,
- float max_label_width,
- const ImGuiTableHeaderData* data,
- int data_count) {
- return ImGui::TableAngledHeadersRowEx(row_id, angle, max_label_width, data,
- data_count);
-}
-CIMGUI_API ImGuiTable* igGetCurrentTable() {
- return ImGui::GetCurrentTable();
-}
-CIMGUI_API ImGuiTable* igTableFindByID(ImGuiID id) {
- return ImGui::TableFindByID(id);
-}
-CIMGUI_API bool igBeginTableEx(const char* name,
- ImGuiID id,
- int columns_count,
- ImGuiTableFlags flags,
- const ImVec2 outer_size,
- float inner_width) {
- return ImGui::BeginTableEx(name, id, columns_count, flags, outer_size,
- inner_width);
-}
-CIMGUI_API void igTableBeginInitMemory(ImGuiTable* table, int columns_count) {
- return ImGui::TableBeginInitMemory(table, columns_count);
-}
-CIMGUI_API void igTableBeginApplyRequests(ImGuiTable* table) {
- return ImGui::TableBeginApplyRequests(table);
-}
-CIMGUI_API void igTableSetupDrawChannels(ImGuiTable* table) {
- return ImGui::TableSetupDrawChannels(table);
-}
-CIMGUI_API void igTableUpdateLayout(ImGuiTable* table) {
- return ImGui::TableUpdateLayout(table);
-}
-CIMGUI_API void igTableUpdateBorders(ImGuiTable* table) {
- return ImGui::TableUpdateBorders(table);
-}
-CIMGUI_API void igTableUpdateColumnsWeightFromWidth(ImGuiTable* table) {
- return ImGui::TableUpdateColumnsWeightFromWidth(table);
-}
-CIMGUI_API void igTableDrawBorders(ImGuiTable* table) {
- return ImGui::TableDrawBorders(table);
-}
-CIMGUI_API void igTableDrawDefaultContextMenu(
- ImGuiTable* table,
- ImGuiTableFlags flags_for_section_to_display) {
- return ImGui::TableDrawDefaultContextMenu(table,
- flags_for_section_to_display);
-}
-CIMGUI_API bool igTableBeginContextMenuPopup(ImGuiTable* table) {
- return ImGui::TableBeginContextMenuPopup(table);
-}
-CIMGUI_API void igTableMergeDrawChannels(ImGuiTable* table) {
- return ImGui::TableMergeDrawChannels(table);
-}
-CIMGUI_API ImGuiTableInstanceData* igTableGetInstanceData(ImGuiTable* table,
- int instance_no) {
- return ImGui::TableGetInstanceData(table, instance_no);
-}
-CIMGUI_API ImGuiID igTableGetInstanceID(ImGuiTable* table, int instance_no) {
- return ImGui::TableGetInstanceID(table, instance_no);
-}
-CIMGUI_API void igTableSortSpecsSanitize(ImGuiTable* table) {
- return ImGui::TableSortSpecsSanitize(table);
-}
-CIMGUI_API void igTableSortSpecsBuild(ImGuiTable* table) {
- return ImGui::TableSortSpecsBuild(table);
-}
-CIMGUI_API ImGuiSortDirection
-igTableGetColumnNextSortDirection(ImGuiTableColumn* column) {
- return ImGui::TableGetColumnNextSortDirection(column);
-}
-CIMGUI_API void igTableFixColumnSortDirection(ImGuiTable* table,
- ImGuiTableColumn* column) {
- return ImGui::TableFixColumnSortDirection(table, column);
-}
-CIMGUI_API float igTableGetColumnWidthAuto(ImGuiTable* table,
- ImGuiTableColumn* column) {
- return ImGui::TableGetColumnWidthAuto(table, column);
-}
-CIMGUI_API void igTableBeginRow(ImGuiTable* table) {
- return ImGui::TableBeginRow(table);
-}
-CIMGUI_API void igTableEndRow(ImGuiTable* table) {
- return ImGui::TableEndRow(table);
-}
-CIMGUI_API void igTableBeginCell(ImGuiTable* table, int column_n) {
- return ImGui::TableBeginCell(table, column_n);
-}
-CIMGUI_API void igTableEndCell(ImGuiTable* table) {
- return ImGui::TableEndCell(table);
-}
-CIMGUI_API void igTableGetCellBgRect(ImRect* pOut,
- const ImGuiTable* table,
- int column_n) {
- *pOut = ImGui::TableGetCellBgRect(table, column_n);
-}
-CIMGUI_API const char* igTableGetColumnName_TablePtr(const ImGuiTable* table,
- int column_n) {
- return ImGui::TableGetColumnName(table, column_n);
-}
-CIMGUI_API ImGuiID igTableGetColumnResizeID(ImGuiTable* table,
- int column_n,
- int instance_no) {
- return ImGui::TableGetColumnResizeID(table, column_n, instance_no);
-}
-CIMGUI_API float igTableGetMaxColumnWidth(const ImGuiTable* table,
- int column_n) {
- return ImGui::TableGetMaxColumnWidth(table, column_n);
-}
-CIMGUI_API void igTableSetColumnWidthAutoSingle(ImGuiTable* table,
- int column_n) {
- return ImGui::TableSetColumnWidthAutoSingle(table, column_n);
-}
-CIMGUI_API void igTableSetColumnWidthAutoAll(ImGuiTable* table) {
- return ImGui::TableSetColumnWidthAutoAll(table);
-}
-CIMGUI_API void igTableRemove(ImGuiTable* table) {
- return ImGui::TableRemove(table);
-}
-CIMGUI_API void igTableGcCompactTransientBuffers_TablePtr(ImGuiTable* table) {
- return ImGui::TableGcCompactTransientBuffers(table);
-}
-CIMGUI_API void igTableGcCompactTransientBuffers_TableTempDataPtr(
- ImGuiTableTempData* table) {
- return ImGui::TableGcCompactTransientBuffers(table);
-}
-CIMGUI_API void igTableGcCompactSettings() {
- return ImGui::TableGcCompactSettings();
-}
-CIMGUI_API void igTableLoadSettings(ImGuiTable* table) {
- return ImGui::TableLoadSettings(table);
-}
-CIMGUI_API void igTableSaveSettings(ImGuiTable* table) {
- return ImGui::TableSaveSettings(table);
-}
-CIMGUI_API void igTableResetSettings(ImGuiTable* table) {
- return ImGui::TableResetSettings(table);
-}
-CIMGUI_API ImGuiTableSettings* igTableGetBoundSettings(ImGuiTable* table) {
- return ImGui::TableGetBoundSettings(table);
-}
-CIMGUI_API void igTableSettingsAddSettingsHandler() {
- return ImGui::TableSettingsAddSettingsHandler();
-}
-CIMGUI_API ImGuiTableSettings* igTableSettingsCreate(ImGuiID id,
- int columns_count) {
- return ImGui::TableSettingsCreate(id, columns_count);
-}
-CIMGUI_API ImGuiTableSettings* igTableSettingsFindByID(ImGuiID id) {
- return ImGui::TableSettingsFindByID(id);
-}
-CIMGUI_API ImGuiTabBar* igGetCurrentTabBar() {
- return ImGui::GetCurrentTabBar();
-}
-CIMGUI_API bool igBeginTabBarEx(ImGuiTabBar* tab_bar,
- const ImRect bb,
- ImGuiTabBarFlags flags) {
- return ImGui::BeginTabBarEx(tab_bar, bb, flags);
-}
-CIMGUI_API ImGuiTabItem* igTabBarFindTabByID(ImGuiTabBar* tab_bar,
- ImGuiID tab_id) {
- return ImGui::TabBarFindTabByID(tab_bar, tab_id);
-}
-CIMGUI_API ImGuiTabItem* igTabBarFindTabByOrder(ImGuiTabBar* tab_bar,
- int order) {
- return ImGui::TabBarFindTabByOrder(tab_bar, order);
-}
-CIMGUI_API ImGuiTabItem* igTabBarFindMostRecentlySelectedTabForActiveWindow(
- ImGuiTabBar* tab_bar) {
- return ImGui::TabBarFindMostRecentlySelectedTabForActiveWindow(tab_bar);
-}
-CIMGUI_API ImGuiTabItem* igTabBarGetCurrentTab(ImGuiTabBar* tab_bar) {
- return ImGui::TabBarGetCurrentTab(tab_bar);
-}
-CIMGUI_API int igTabBarGetTabOrder(ImGuiTabBar* tab_bar, ImGuiTabItem* tab) {
- return ImGui::TabBarGetTabOrder(tab_bar, tab);
-}
-CIMGUI_API const char* igTabBarGetTabName(ImGuiTabBar* tab_bar,
- ImGuiTabItem* tab) {
- return ImGui::TabBarGetTabName(tab_bar, tab);
-}
-CIMGUI_API void igTabBarAddTab(ImGuiTabBar* tab_bar,
- ImGuiTabItemFlags tab_flags,
- ImGuiWindow* window) {
- return ImGui::TabBarAddTab(tab_bar, tab_flags, window);
-}
-CIMGUI_API void igTabBarRemoveTab(ImGuiTabBar* tab_bar, ImGuiID tab_id) {
- return ImGui::TabBarRemoveTab(tab_bar, tab_id);
-}
-CIMGUI_API void igTabBarCloseTab(ImGuiTabBar* tab_bar, ImGuiTabItem* tab) {
- return ImGui::TabBarCloseTab(tab_bar, tab);
-}
-CIMGUI_API void igTabBarQueueFocus(ImGuiTabBar* tab_bar, ImGuiTabItem* tab) {
- return ImGui::TabBarQueueFocus(tab_bar, tab);
-}
-CIMGUI_API void igTabBarQueueReorder(ImGuiTabBar* tab_bar,
- ImGuiTabItem* tab,
- int offset) {
- return ImGui::TabBarQueueReorder(tab_bar, tab, offset);
-}
-CIMGUI_API void igTabBarQueueReorderFromMousePos(ImGuiTabBar* tab_bar,
- ImGuiTabItem* tab,
- ImVec2 mouse_pos) {
- return ImGui::TabBarQueueReorderFromMousePos(tab_bar, tab, mouse_pos);
-}
-CIMGUI_API bool igTabBarProcessReorder(ImGuiTabBar* tab_bar) {
- return ImGui::TabBarProcessReorder(tab_bar);
-}
-CIMGUI_API bool igTabItemEx(ImGuiTabBar* tab_bar,
- const char* label,
- bool* p_open,
- ImGuiTabItemFlags flags,
- ImGuiWindow* docked_window) {
- return ImGui::TabItemEx(tab_bar, label, p_open, flags, docked_window);
-}
-CIMGUI_API void igTabItemCalcSize_Str(ImVec2* pOut,
- const char* label,
- bool has_close_button_or_unsaved_marker) {
- *pOut = ImGui::TabItemCalcSize(label, has_close_button_or_unsaved_marker);
-}
-CIMGUI_API void igTabItemCalcSize_WindowPtr(ImVec2* pOut, ImGuiWindow* window) {
- *pOut = ImGui::TabItemCalcSize(window);
-}
-CIMGUI_API void igTabItemBackground(ImDrawList* draw_list,
- const ImRect bb,
- ImGuiTabItemFlags flags,
- ImU32 col) {
- return ImGui::TabItemBackground(draw_list, bb, flags, col);
-}
-CIMGUI_API void igTabItemLabelAndCloseButton(ImDrawList* draw_list,
- const ImRect bb,
- ImGuiTabItemFlags flags,
- ImVec2 frame_padding,
- const char* label,
- ImGuiID tab_id,
- ImGuiID close_button_id,
- bool is_contents_visible,
- bool* out_just_closed,
- bool* out_text_clipped) {
- return ImGui::TabItemLabelAndCloseButton(
- draw_list, bb, flags, frame_padding, label, tab_id, close_button_id,
- is_contents_visible, out_just_closed, out_text_clipped);
-}
-CIMGUI_API void igRenderText(ImVec2 pos,
- const char* text,
- const char* text_end,
- bool hide_text_after_hash) {
- return ImGui::RenderText(pos, text, text_end, hide_text_after_hash);
-}
-CIMGUI_API void igRenderTextWrapped(ImVec2 pos,
- const char* text,
- const char* text_end,
- float wrap_width) {
- return ImGui::RenderTextWrapped(pos, text, text_end, wrap_width);
-}
-CIMGUI_API void igRenderTextClipped(const ImVec2 pos_min,
- const ImVec2 pos_max,
- const char* text,
- const char* text_end,
- const ImVec2* text_size_if_known,
- const ImVec2 align,
- const ImRect* clip_rect) {
- return ImGui::RenderTextClipped(pos_min, pos_max, text, text_end,
- text_size_if_known, align, clip_rect);
-}
-CIMGUI_API void igRenderTextClippedEx(ImDrawList* draw_list,
- const ImVec2 pos_min,
- const ImVec2 pos_max,
- const char* text,
- const char* text_end,
- const ImVec2* text_size_if_known,
- const ImVec2 align,
- const ImRect* clip_rect) {
- return ImGui::RenderTextClippedEx(draw_list, pos_min, pos_max, text, text_end,
- text_size_if_known, align, clip_rect);
-}
-CIMGUI_API void igRenderTextEllipsis(ImDrawList* draw_list,
- const ImVec2 pos_min,
- const ImVec2 pos_max,
- float clip_max_x,
- float ellipsis_max_x,
- const char* text,
- const char* text_end,
- const ImVec2* text_size_if_known) {
- return ImGui::RenderTextEllipsis(draw_list, pos_min, pos_max, clip_max_x,
- ellipsis_max_x, text, text_end,
- text_size_if_known);
-}
-CIMGUI_API void igRenderFrame(ImVec2 p_min,
- ImVec2 p_max,
- ImU32 fill_col,
- bool border,
- float rounding) {
- return ImGui::RenderFrame(p_min, p_max, fill_col, border, rounding);
-}
-CIMGUI_API void igRenderFrameBorder(ImVec2 p_min,
- ImVec2 p_max,
- float rounding) {
- return ImGui::RenderFrameBorder(p_min, p_max, rounding);
-}
-CIMGUI_API void igRenderColorRectWithAlphaCheckerboard(ImDrawList* draw_list,
- ImVec2 p_min,
- ImVec2 p_max,
- ImU32 fill_col,
- float grid_step,
- ImVec2 grid_off,
- float rounding,
- ImDrawFlags flags) {
- return ImGui::RenderColorRectWithAlphaCheckerboard(
- draw_list, p_min, p_max, fill_col, grid_step, grid_off, rounding, flags);
-}
-CIMGUI_API void igRenderNavHighlight(const ImRect bb,
- ImGuiID id,
- ImGuiNavHighlightFlags flags) {
- return ImGui::RenderNavHighlight(bb, id, flags);
-}
-CIMGUI_API const char* igFindRenderedTextEnd(const char* text,
- const char* text_end) {
- return ImGui::FindRenderedTextEnd(text, text_end);
-}
-CIMGUI_API void igRenderMouseCursor(ImVec2 pos,
- float scale,
- ImGuiMouseCursor mouse_cursor,
- ImU32 col_fill,
- ImU32 col_border,
- ImU32 col_shadow) {
- return ImGui::RenderMouseCursor(pos, scale, mouse_cursor, col_fill,
- col_border, col_shadow);
-}
-CIMGUI_API void igRenderArrow(ImDrawList* draw_list,
- ImVec2 pos,
- ImU32 col,
- ImGuiDir dir,
- float scale) {
- return ImGui::RenderArrow(draw_list, pos, col, dir, scale);
-}
-CIMGUI_API void igRenderBullet(ImDrawList* draw_list, ImVec2 pos, ImU32 col) {
- return ImGui::RenderBullet(draw_list, pos, col);
-}
-CIMGUI_API void igRenderCheckMark(ImDrawList* draw_list,
- ImVec2 pos,
- ImU32 col,
- float sz) {
- return ImGui::RenderCheckMark(draw_list, pos, col, sz);
-}
-CIMGUI_API void igRenderArrowPointingAt(ImDrawList* draw_list,
- ImVec2 pos,
- ImVec2 half_sz,
- ImGuiDir direction,
- ImU32 col) {
- return ImGui::RenderArrowPointingAt(draw_list, pos, half_sz, direction, col);
-}
-CIMGUI_API void igRenderArrowDockMenu(ImDrawList* draw_list,
- ImVec2 p_min,
- float sz,
- ImU32 col) {
- return ImGui::RenderArrowDockMenu(draw_list, p_min, sz, col);
-}
-CIMGUI_API void igRenderRectFilledRangeH(ImDrawList* draw_list,
- const ImRect rect,
- ImU32 col,
- float x_start_norm,
- float x_end_norm,
- float rounding) {
- return ImGui::RenderRectFilledRangeH(draw_list, rect, col, x_start_norm,
- x_end_norm, rounding);
-}
-CIMGUI_API void igRenderRectFilledWithHole(ImDrawList* draw_list,
- const ImRect outer,
- const ImRect inner,
- ImU32 col,
- float rounding) {
- return ImGui::RenderRectFilledWithHole(draw_list, outer, inner, col,
- rounding);
-}
-CIMGUI_API ImDrawFlags igCalcRoundingFlagsForRectInRect(const ImRect r_in,
- const ImRect r_outer,
- float threshold) {
- return ImGui::CalcRoundingFlagsForRectInRect(r_in, r_outer, threshold);
-}
-CIMGUI_API void igTextEx(const char* text,
- const char* text_end,
- ImGuiTextFlags flags) {
- return ImGui::TextEx(text, text_end, flags);
-}
-CIMGUI_API bool igButtonEx(const char* label,
- const ImVec2 size_arg,
- ImGuiButtonFlags flags) {
- return ImGui::ButtonEx(label, size_arg, flags);
-}
-CIMGUI_API bool igArrowButtonEx(const char* str_id,
- ImGuiDir dir,
- ImVec2 size_arg,
- ImGuiButtonFlags flags) {
- return ImGui::ArrowButtonEx(str_id, dir, size_arg, flags);
-}
-CIMGUI_API bool igImageButtonEx(ImGuiID id,
- ImTextureID texture_id,
- const ImVec2 image_size,
- const ImVec2 uv0,
- const ImVec2 uv1,
- const ImVec4 bg_col,
- const ImVec4 tint_col,
- ImGuiButtonFlags flags) {
- return ImGui::ImageButtonEx(id, texture_id, image_size, uv0, uv1, bg_col,
- tint_col, flags);
-}
-CIMGUI_API void igSeparatorEx(ImGuiSeparatorFlags flags, float thickness) {
- return ImGui::SeparatorEx(flags, thickness);
-}
-CIMGUI_API void igSeparatorTextEx(ImGuiID id,
- const char* label,
- const char* label_end,
- float extra_width) {
- return ImGui::SeparatorTextEx(id, label, label_end, extra_width);
-}
-CIMGUI_API bool igCheckboxFlags_S64Ptr(const char* label,
- ImS64* flags,
- ImS64 flags_value) {
- return ImGui::CheckboxFlags(label, flags, flags_value);
-}
-CIMGUI_API bool igCheckboxFlags_U64Ptr(const char* label,
- ImU64* flags,
- ImU64 flags_value) {
- return ImGui::CheckboxFlags(label, flags, flags_value);
-}
-CIMGUI_API bool igCloseButton(ImGuiID id, const ImVec2 pos) {
- return ImGui::CloseButton(id, pos);
-}
-CIMGUI_API bool igCollapseButton(ImGuiID id,
- const ImVec2 pos,
- ImGuiDockNode* dock_node) {
- return ImGui::CollapseButton(id, pos, dock_node);
-}
-CIMGUI_API void igScrollbar(ImGuiAxis axis) {
- return ImGui::Scrollbar(axis);
-}
-CIMGUI_API bool igScrollbarEx(const ImRect bb,
- ImGuiID id,
- ImGuiAxis axis,
- ImS64* p_scroll_v,
- ImS64 avail_v,
- ImS64 contents_v,
- ImDrawFlags flags) {
- return ImGui::ScrollbarEx(bb, id, axis, p_scroll_v, avail_v, contents_v,
- flags);
-}
-CIMGUI_API void igGetWindowScrollbarRect(ImRect* pOut,
- ImGuiWindow* window,
- ImGuiAxis axis) {
- *pOut = ImGui::GetWindowScrollbarRect(window, axis);
-}
-CIMGUI_API ImGuiID igGetWindowScrollbarID(ImGuiWindow* window, ImGuiAxis axis) {
- return ImGui::GetWindowScrollbarID(window, axis);
-}
-CIMGUI_API ImGuiID igGetWindowResizeCornerID(ImGuiWindow* window, int n) {
- return ImGui::GetWindowResizeCornerID(window, n);
-}
-CIMGUI_API ImGuiID igGetWindowResizeBorderID(ImGuiWindow* window,
- ImGuiDir dir) {
- return ImGui::GetWindowResizeBorderID(window, dir);
-}
-CIMGUI_API bool igButtonBehavior(const ImRect bb,
- ImGuiID id,
- bool* out_hovered,
- bool* out_held,
- ImGuiButtonFlags flags) {
- return ImGui::ButtonBehavior(bb, id, out_hovered, out_held, flags);
-}
-CIMGUI_API bool igDragBehavior(ImGuiID id,
- ImGuiDataType data_type,
- void* p_v,
- float v_speed,
- const void* p_min,
- const void* p_max,
- const char* format,
- ImGuiSliderFlags flags) {
- return ImGui::DragBehavior(id, data_type, p_v, v_speed, p_min, p_max, format,
- flags);
-}
-CIMGUI_API bool igSliderBehavior(const ImRect bb,
- ImGuiID id,
- ImGuiDataType data_type,
- void* p_v,
- const void* p_min,
- const void* p_max,
- const char* format,
- ImGuiSliderFlags flags,
- ImRect* out_grab_bb) {
- return ImGui::SliderBehavior(bb, id, data_type, p_v, p_min, p_max, format,
- flags, out_grab_bb);
-}
-CIMGUI_API bool igSplitterBehavior(const ImRect bb,
- ImGuiID id,
- ImGuiAxis axis,
- float* size1,
- float* size2,
- float min_size1,
- float min_size2,
- float hover_extend,
- float hover_visibility_delay,
- ImU32 bg_col) {
- return ImGui::SplitterBehavior(bb, id, axis, size1, size2, min_size1,
- min_size2, hover_extend,
- hover_visibility_delay, bg_col);
-}
-CIMGUI_API bool igTreeNodeBehavior(ImGuiID id,
- ImGuiTreeNodeFlags flags,
- const char* label,
- const char* label_end) {
- return ImGui::TreeNodeBehavior(id, flags, label, label_end);
-}
-CIMGUI_API void igTreePushOverrideID(ImGuiID id) {
- return ImGui::TreePushOverrideID(id);
-}
-CIMGUI_API void igTreeNodeSetOpen(ImGuiID id, bool open) {
- return ImGui::TreeNodeSetOpen(id, open);
-}
-CIMGUI_API bool igTreeNodeUpdateNextOpen(ImGuiID id, ImGuiTreeNodeFlags flags) {
- return ImGui::TreeNodeUpdateNextOpen(id, flags);
-}
-CIMGUI_API void igSetNextItemSelectionUserData(
- ImGuiSelectionUserData selection_user_data) {
- return ImGui::SetNextItemSelectionUserData(selection_user_data);
-}
-CIMGUI_API const ImGuiDataTypeInfo* igDataTypeGetInfo(ImGuiDataType data_type) {
- return ImGui::DataTypeGetInfo(data_type);
-}
-CIMGUI_API int igDataTypeFormatString(char* buf,
- int buf_size,
- ImGuiDataType data_type,
- const void* p_data,
- const char* format) {
- return ImGui::DataTypeFormatString(buf, buf_size, data_type, p_data, format);
-}
-CIMGUI_API void igDataTypeApplyOp(ImGuiDataType data_type,
- int op,
- void* output,
- const void* arg_1,
- const void* arg_2) {
- return ImGui::DataTypeApplyOp(data_type, op, output, arg_1, arg_2);
-}
-CIMGUI_API bool igDataTypeApplyFromText(const char* buf,
- ImGuiDataType data_type,
- void* p_data,
- const char* format) {
- return ImGui::DataTypeApplyFromText(buf, data_type, p_data, format);
-}
-CIMGUI_API int igDataTypeCompare(ImGuiDataType data_type,
- const void* arg_1,
- const void* arg_2) {
- return ImGui::DataTypeCompare(data_type, arg_1, arg_2);
-}
-CIMGUI_API bool igDataTypeClamp(ImGuiDataType data_type,
- void* p_data,
- const void* p_min,
- const void* p_max) {
- return ImGui::DataTypeClamp(data_type, p_data, p_min, p_max);
-}
-CIMGUI_API bool igInputTextEx(const char* label,
- const char* hint,
- char* buf,
- int buf_size,
- const ImVec2 size_arg,
- ImGuiInputTextFlags flags,
- ImGuiInputTextCallback callback,
- void* user_data) {
- return ImGui::InputTextEx(label, hint, buf, buf_size, size_arg, flags,
- callback, user_data);
-}
-CIMGUI_API void igInputTextDeactivateHook(ImGuiID id) {
- return ImGui::InputTextDeactivateHook(id);
-}
-CIMGUI_API bool igTempInputText(const ImRect bb,
- ImGuiID id,
- const char* label,
- char* buf,
- int buf_size,
- ImGuiInputTextFlags flags) {
- return ImGui::TempInputText(bb, id, label, buf, buf_size, flags);
-}
-CIMGUI_API bool igTempInputScalar(const ImRect bb,
- ImGuiID id,
- const char* label,
- ImGuiDataType data_type,
- void* p_data,
- const char* format,
- const void* p_clamp_min,
- const void* p_clamp_max) {
- return ImGui::TempInputScalar(bb, id, label, data_type, p_data, format,
- p_clamp_min, p_clamp_max);
-}
-CIMGUI_API bool igTempInputIsActive(ImGuiID id) {
- return ImGui::TempInputIsActive(id);
-}
-CIMGUI_API ImGuiInputTextState* igGetInputTextState(ImGuiID id) {
- return ImGui::GetInputTextState(id);
-}
-CIMGUI_API void igColorTooltip(const char* text,
- const float* col,
- ImGuiColorEditFlags flags) {
- return ImGui::ColorTooltip(text, col, flags);
-}
-CIMGUI_API void igColorEditOptionsPopup(const float* col,
- ImGuiColorEditFlags flags) {
- return ImGui::ColorEditOptionsPopup(col, flags);
-}
-CIMGUI_API void igColorPickerOptionsPopup(const float* ref_col,
- ImGuiColorEditFlags flags) {
- return ImGui::ColorPickerOptionsPopup(ref_col, flags);
-}
-CIMGUI_API int igPlotEx(ImGuiPlotType plot_type,
- const char* label,
- float (*values_getter)(void* data, int idx),
- void* data,
- int values_count,
- int values_offset,
- const char* overlay_text,
- float scale_min,
- float scale_max,
- const ImVec2 size_arg) {
- return ImGui::PlotEx(plot_type, label, values_getter, data, values_count,
- values_offset, overlay_text, scale_min, scale_max,
- size_arg);
-}
-CIMGUI_API void igShadeVertsLinearColorGradientKeepAlpha(ImDrawList* draw_list,
- int vert_start_idx,
- int vert_end_idx,
- ImVec2 gradient_p0,
- ImVec2 gradient_p1,
- ImU32 col0,
- ImU32 col1) {
- return ImGui::ShadeVertsLinearColorGradientKeepAlpha(
- draw_list, vert_start_idx, vert_end_idx, gradient_p0, gradient_p1, col0,
- col1);
-}
-CIMGUI_API void igShadeVertsLinearUV(ImDrawList* draw_list,
- int vert_start_idx,
- int vert_end_idx,
- const ImVec2 a,
- const ImVec2 b,
- const ImVec2 uv_a,
- const ImVec2 uv_b,
- bool clamp) {
- return ImGui::ShadeVertsLinearUV(draw_list, vert_start_idx, vert_end_idx, a,
- b, uv_a, uv_b, clamp);
-}
-CIMGUI_API void igShadeVertsTransformPos(ImDrawList* draw_list,
- int vert_start_idx,
- int vert_end_idx,
- const ImVec2 pivot_in,
- float cos_a,
- float sin_a,
- const ImVec2 pivot_out) {
- return ImGui::ShadeVertsTransformPos(draw_list, vert_start_idx, vert_end_idx,
- pivot_in, cos_a, sin_a, pivot_out);
-}
-CIMGUI_API void igGcCompactTransientMiscBuffers() {
- return ImGui::GcCompactTransientMiscBuffers();
-}
-CIMGUI_API void igGcCompactTransientWindowBuffers(ImGuiWindow* window) {
- return ImGui::GcCompactTransientWindowBuffers(window);
-}
-CIMGUI_API void igGcAwakeTransientWindowBuffers(ImGuiWindow* window) {
- return ImGui::GcAwakeTransientWindowBuffers(window);
-}
-CIMGUI_API void igDebugLog(const char* fmt, ...) {
- va_list args;
- va_start(args, fmt);
- ImGui::DebugLogV(fmt, args);
- va_end(args);
-}
-CIMGUI_API void igDebugLogV(const char* fmt, va_list args) {
- return ImGui::DebugLogV(fmt, args);
-}
-CIMGUI_API void igDebugAllocHook(ImGuiDebugAllocInfo* info,
- int frame_count,
- void* ptr,
- size_t size) {
- return ImGui::DebugAllocHook(info, frame_count, ptr, size);
-}
-CIMGUI_API void igErrorCheckEndFrameRecover(ImGuiErrorLogCallback log_callback,
- void* user_data) {
- return ImGui::ErrorCheckEndFrameRecover(log_callback, user_data);
-}
-CIMGUI_API void igErrorCheckEndWindowRecover(ImGuiErrorLogCallback log_callback,
- void* user_data) {
- return ImGui::ErrorCheckEndWindowRecover(log_callback, user_data);
-}
-CIMGUI_API void igErrorCheckUsingSetCursorPosToExtendParentBoundaries() {
- return ImGui::ErrorCheckUsingSetCursorPosToExtendParentBoundaries();
-}
-CIMGUI_API void igDebugDrawCursorPos(ImU32 col) {
- return ImGui::DebugDrawCursorPos(col);
-}
-CIMGUI_API void igDebugDrawLineExtents(ImU32 col) {
- return ImGui::DebugDrawLineExtents(col);
-}
-CIMGUI_API void igDebugDrawItemRect(ImU32 col) {
- return ImGui::DebugDrawItemRect(col);
-}
-CIMGUI_API void igDebugLocateItem(ImGuiID target_id) {
- return ImGui::DebugLocateItem(target_id);
-}
-CIMGUI_API void igDebugLocateItemOnHover(ImGuiID target_id) {
- return ImGui::DebugLocateItemOnHover(target_id);
-}
-CIMGUI_API void igDebugLocateItemResolveWithLastItem() {
- return ImGui::DebugLocateItemResolveWithLastItem();
-}
-CIMGUI_API void igDebugBreakClearData() {
- return ImGui::DebugBreakClearData();
-}
-CIMGUI_API bool igDebugBreakButton(const char* label,
- const char* description_of_location) {
- return ImGui::DebugBreakButton(label, description_of_location);
-}
-CIMGUI_API void igDebugBreakButtonTooltip(bool keyboard_only,
- const char* description_of_location) {
- return ImGui::DebugBreakButtonTooltip(keyboard_only, description_of_location);
-}
-CIMGUI_API void igShowFontAtlas(ImFontAtlas* atlas) {
- return ImGui::ShowFontAtlas(atlas);
-}
-CIMGUI_API void igDebugHookIdInfo(ImGuiID id,
- ImGuiDataType data_type,
- const void* data_id,
- const void* data_id_end) {
- return ImGui::DebugHookIdInfo(id, data_type, data_id, data_id_end);
-}
-CIMGUI_API void igDebugNodeColumns(ImGuiOldColumns* columns) {
- return ImGui::DebugNodeColumns(columns);
-}
-CIMGUI_API void igDebugNodeDockNode(ImGuiDockNode* node, const char* label) {
- return ImGui::DebugNodeDockNode(node, label);
-}
-CIMGUI_API void igDebugNodeDrawList(ImGuiWindow* window,
- ImGuiViewportP* viewport,
- const ImDrawList* draw_list,
- const char* label) {
- return ImGui::DebugNodeDrawList(window, viewport, draw_list, label);
-}
-CIMGUI_API void igDebugNodeDrawCmdShowMeshAndBoundingBox(
- ImDrawList* out_draw_list,
- const ImDrawList* draw_list,
- const ImDrawCmd* draw_cmd,
- bool show_mesh,
- bool show_aabb) {
- return ImGui::DebugNodeDrawCmdShowMeshAndBoundingBox(
- out_draw_list, draw_list, draw_cmd, show_mesh, show_aabb);
-}
-CIMGUI_API void igDebugNodeFont(ImFont* font) {
- return ImGui::DebugNodeFont(font);
-}
-CIMGUI_API void igDebugNodeFontGlyph(ImFont* font, const ImFontGlyph* glyph) {
- return ImGui::DebugNodeFontGlyph(font, glyph);
-}
-CIMGUI_API void igDebugNodeStorage(ImGuiStorage* storage, const char* label) {
- return ImGui::DebugNodeStorage(storage, label);
-}
-CIMGUI_API void igDebugNodeTabBar(ImGuiTabBar* tab_bar, const char* label) {
- return ImGui::DebugNodeTabBar(tab_bar, label);
-}
-CIMGUI_API void igDebugNodeTable(ImGuiTable* table) {
- return ImGui::DebugNodeTable(table);
-}
-CIMGUI_API void igDebugNodeTableSettings(ImGuiTableSettings* settings) {
- return ImGui::DebugNodeTableSettings(settings);
-}
-CIMGUI_API void igDebugNodeInputTextState(ImGuiInputTextState* state) {
- return ImGui::DebugNodeInputTextState(state);
-}
-CIMGUI_API void igDebugNodeTypingSelectState(ImGuiTypingSelectState* state) {
- return ImGui::DebugNodeTypingSelectState(state);
-}
-CIMGUI_API void igDebugNodeWindow(ImGuiWindow* window, const char* label) {
- return ImGui::DebugNodeWindow(window, label);
-}
-CIMGUI_API void igDebugNodeWindowSettings(ImGuiWindowSettings* settings) {
- return ImGui::DebugNodeWindowSettings(settings);
-}
-CIMGUI_API void igDebugNodeWindowsList(ImVector_ImGuiWindowPtr* windows,
- const char* label) {
- return ImGui::DebugNodeWindowsList(windows, label);
-}
-CIMGUI_API void igDebugNodeWindowsListByBeginStackParent(
- ImGuiWindow** windows,
- int windows_size,
- ImGuiWindow* parent_in_begin_stack) {
- return ImGui::DebugNodeWindowsListByBeginStackParent(windows, windows_size,
- parent_in_begin_stack);
-}
-CIMGUI_API void igDebugNodeViewport(ImGuiViewportP* viewport) {
- return ImGui::DebugNodeViewport(viewport);
-}
-CIMGUI_API void igDebugRenderKeyboardPreview(ImDrawList* draw_list) {
- return ImGui::DebugRenderKeyboardPreview(draw_list);
-}
-CIMGUI_API void igDebugRenderViewportThumbnail(ImDrawList* draw_list,
- ImGuiViewportP* viewport,
- const ImRect bb) {
- return ImGui::DebugRenderViewportThumbnail(draw_list, viewport, bb);
-}
-
-CIMGUI_API void igImFontAtlasUpdateConfigDataPointers(ImFontAtlas* atlas) {
- return ImFontAtlasUpdateConfigDataPointers(atlas);
-}
-CIMGUI_API void igImFontAtlasBuildInit(ImFontAtlas* atlas) {
- return ImFontAtlasBuildInit(atlas);
-}
-CIMGUI_API void igImFontAtlasBuildSetupFont(ImFontAtlas* atlas,
- ImFont* font,
- ImFontConfig* font_config,
- float ascent,
- float descent) {
- return ImFontAtlasBuildSetupFont(atlas, font, font_config, ascent, descent);
-}
-CIMGUI_API void igImFontAtlasBuildPackCustomRects(ImFontAtlas* atlas,
- void* stbrp_context_opaque) {
- return ImFontAtlasBuildPackCustomRects(atlas, stbrp_context_opaque);
-}
-CIMGUI_API void igImFontAtlasBuildFinish(ImFontAtlas* atlas) {
- return ImFontAtlasBuildFinish(atlas);
-}
-CIMGUI_API void igImFontAtlasBuildRender8bppRectFromString(
- ImFontAtlas* atlas,
- int x,
- int y,
- int w,
- int h,
- const char* in_str,
- char in_marker_char,
- unsigned char in_marker_pixel_value) {
- return ImFontAtlasBuildRender8bppRectFromString(
- atlas, x, y, w, h, in_str, in_marker_char, in_marker_pixel_value);
-}
-CIMGUI_API void igImFontAtlasBuildRender32bppRectFromString(
- ImFontAtlas* atlas,
- int x,
- int y,
- int w,
- int h,
- const char* in_str,
- char in_marker_char,
- unsigned int in_marker_pixel_value) {
- return ImFontAtlasBuildRender32bppRectFromString(
- atlas, x, y, w, h, in_str, in_marker_char, in_marker_pixel_value);
-}
-CIMGUI_API void igImFontAtlasBuildMultiplyCalcLookupTable(
- unsigned char out_table[256],
- float in_multiply_factor) {
- return ImFontAtlasBuildMultiplyCalcLookupTable(out_table, in_multiply_factor);
-}
-CIMGUI_API void igImFontAtlasBuildMultiplyRectAlpha8(
- const unsigned char table[256],
- unsigned char* pixels,
- int x,
- int y,
- int w,
- int h,
- int stride) {
- return ImFontAtlasBuildMultiplyRectAlpha8(table, pixels, x, y, w, h, stride);
-}
-
-/////////////////////////////manual written functions
-CIMGUI_API void igLogText(CONST char* fmt, ...) {
- char buffer[256];
- va_list args;
- va_start(args, fmt);
- vsnprintf(buffer, 256, fmt, args);
- va_end(args);
-
- ImGui::LogText("%s", buffer);
-}
-CIMGUI_API void ImGuiTextBuffer_appendf(struct ImGuiTextBuffer* buffer,
- const char* fmt,
- ...) {
- va_list args;
- va_start(args, fmt);
- buffer->appendfv(fmt, args);
- va_end(args);
-}
-
-CIMGUI_API float igGET_FLT_MAX() {
- return FLT_MAX;
-}
-
-CIMGUI_API float igGET_FLT_MIN() {
- return FLT_MIN;
-}
-
-CIMGUI_API ImVector_ImWchar* ImVector_ImWchar_create() {
- return IM_NEW(ImVector)();
-}
-
-CIMGUI_API void ImVector_ImWchar_destroy(ImVector_ImWchar* self) {
- IM_DELETE(self);
-}
-
-CIMGUI_API void ImVector_ImWchar_Init(ImVector_ImWchar* p) {
- IM_PLACEMENT_NEW(p) ImVector();
-}
-CIMGUI_API void ImVector_ImWchar_UnInit(ImVector_ImWchar* p) {
- p->~ImVector();
-}
-
-#ifdef IMGUI_HAS_DOCK
-
-// NOTE: Some function pointers in the ImGuiPlatformIO structure are not
-// C-compatible because of their use of a complex return type. To work around
-// this, we store a custom CimguiStorage object inside
-// ImGuiIO::BackendLanguageUserData, which contains C-compatible function
-// pointer variants for these functions. When a user function pointer is
-// provided, we hook up the underlying ImGuiPlatformIO function pointer to a
-// thunk which accesses the user function pointer through CimguiStorage.
-
-struct CimguiStorage {
- void (*Platform_GetWindowPos)(ImGuiViewport* vp, ImVec2* out_pos);
- void (*Platform_GetWindowSize)(ImGuiViewport* vp, ImVec2* out_pos);
-};
-
-// Gets a reference to the CimguiStorage object stored in the current ImGui
-// context's BackendLanguageUserData.
-CimguiStorage& GetCimguiStorage() {
- ImGuiIO& io = ImGui::GetIO();
- if (io.BackendLanguageUserData == NULL) {
- io.BackendLanguageUserData = new CimguiStorage();
- }
-
- return *(CimguiStorage*)io.BackendLanguageUserData;
-}
-
-// Thunk satisfying the signature of ImGuiPlatformIO::Platform_GetWindowPos.
-ImVec2 Platform_GetWindowPos_hook(ImGuiViewport* vp) {
- ImVec2 pos;
- GetCimguiStorage().Platform_GetWindowPos(vp, &pos);
- return pos;
-};
-
-// Fully C-compatible function pointer setter for
-// ImGuiPlatformIO::Platform_GetWindowPos.
-CIMGUI_API void ImGuiPlatformIO_Set_Platform_GetWindowPos(
- ImGuiPlatformIO* platform_io,
- void (*user_callback)(ImGuiViewport* vp, ImVec2* out_pos)) {
- CimguiStorage& storage = GetCimguiStorage();
- storage.Platform_GetWindowPos = user_callback;
- platform_io->Platform_GetWindowPos = &Platform_GetWindowPos_hook;
-}
-
-// Thunk satisfying the signature of ImGuiPlatformIO::Platform_GetWindowSize.
-ImVec2 Platform_GetWindowSize_hook(ImGuiViewport* vp) {
- ImVec2 size;
- GetCimguiStorage().Platform_GetWindowSize(vp, &size);
- return size;
-};
-
-// Fully C-compatible function pointer setter for
-// ImGuiPlatformIO::Platform_GetWindowSize.
-CIMGUI_API void ImGuiPlatformIO_Set_Platform_GetWindowSize(
- ImGuiPlatformIO* platform_io,
- void (*user_callback)(ImGuiViewport* vp, ImVec2* out_size)) {
- CimguiStorage& storage = GetCimguiStorage();
- storage.Platform_GetWindowSize = user_callback;
- platform_io->Platform_GetWindowSize = &Platform_GetWindowSize_hook;
-}
-
-#endif
diff --git a/pkg/cimgui/vendor/cimgui.h b/pkg/cimgui/vendor/cimgui.h
deleted file mode 100644
index f00b4d9b7..000000000
--- a/pkg/cimgui/vendor/cimgui.h
+++ /dev/null
@@ -1,6554 +0,0 @@
-// This file is automatically generated by generator.lua from
-// https://github.com/cimgui/cimgui based on imgui.h file version "1.90.6" 19060
-// from Dear ImGui https://github.com/ocornut/imgui with imgui_internal.h api
-// docking branch
-#ifndef CIMGUI_INCLUDED
-#define CIMGUI_INCLUDED
-#include
-#include
-#if defined _WIN32 || defined __CYGWIN__
-#ifdef CIMGUI_NO_EXPORT
-#define API
-#else
-#define API __declspec(dllexport)
-#endif
-#else
-#ifdef __GNUC__
-#define API __attribute__((__visibility__("default")))
-#else
-#define API
-#endif
-#endif
-
-#if defined __cplusplus
-#define EXTERN extern "C"
-#else
-#include
-#include
-#define EXTERN extern
-#endif
-
-#define CIMGUI_API EXTERN API
-#define CONST const
-
-#ifdef _MSC_VER
-typedef unsigned __int64 ImU64;
-#else
-// typedef unsigned long long ImU64;
-#endif
-
-#ifdef CIMGUI_DEFINE_ENUMS_AND_STRUCTS
-
-typedef struct ImDrawChannel ImDrawChannel;
-typedef struct ImDrawCmd ImDrawCmd;
-typedef struct ImDrawData ImDrawData;
-typedef struct ImDrawList ImDrawList;
-typedef struct ImDrawListSharedData ImDrawListSharedData;
-typedef struct ImDrawListSplitter ImDrawListSplitter;
-typedef struct ImDrawVert ImDrawVert;
-typedef struct ImFont ImFont;
-typedef struct ImFontAtlas ImFontAtlas;
-typedef struct ImFontBuilderIO ImFontBuilderIO;
-typedef struct ImFontConfig ImFontConfig;
-typedef struct ImFontGlyph ImFontGlyph;
-typedef struct ImFontGlyphRangesBuilder ImFontGlyphRangesBuilder;
-typedef struct ImColor ImColor;
-typedef struct ImGuiContext ImGuiContext;
-typedef struct ImGuiIO ImGuiIO;
-typedef struct ImGuiInputTextCallbackData ImGuiInputTextCallbackData;
-typedef struct ImGuiKeyData ImGuiKeyData;
-typedef struct ImGuiListClipper ImGuiListClipper;
-typedef struct ImGuiOnceUponAFrame ImGuiOnceUponAFrame;
-typedef struct ImGuiPayload ImGuiPayload;
-typedef struct ImGuiPlatformIO ImGuiPlatformIO;
-typedef struct ImGuiPlatformMonitor ImGuiPlatformMonitor;
-typedef struct ImGuiPlatformImeData ImGuiPlatformImeData;
-typedef struct ImGuiSizeCallbackData ImGuiSizeCallbackData;
-typedef struct ImGuiStorage ImGuiStorage;
-typedef struct ImGuiStyle ImGuiStyle;
-typedef struct ImGuiTableSortSpecs ImGuiTableSortSpecs;
-typedef struct ImGuiTableColumnSortSpecs ImGuiTableColumnSortSpecs;
-typedef struct ImGuiTextBuffer ImGuiTextBuffer;
-typedef struct ImGuiTextFilter ImGuiTextFilter;
-typedef struct ImGuiViewport ImGuiViewport;
-typedef struct ImGuiWindowClass ImGuiWindowClass;
-typedef struct ImBitVector ImBitVector;
-typedef struct ImRect ImRect;
-typedef struct ImDrawDataBuilder ImDrawDataBuilder;
-typedef struct ImGuiColorMod ImGuiColorMod;
-typedef struct ImGuiContextHook ImGuiContextHook;
-typedef struct ImGuiDataVarInfo ImGuiDataVarInfo;
-typedef struct ImGuiDataTypeInfo ImGuiDataTypeInfo;
-typedef struct ImGuiDockContext ImGuiDockContext;
-typedef struct ImGuiDockRequest ImGuiDockRequest;
-typedef struct ImGuiDockNode ImGuiDockNode;
-typedef struct ImGuiDockNodeSettings ImGuiDockNodeSettings;
-typedef struct ImGuiGroupData ImGuiGroupData;
-typedef struct ImGuiInputTextState ImGuiInputTextState;
-typedef struct ImGuiInputTextDeactivateData ImGuiInputTextDeactivateData;
-typedef struct ImGuiLastItemData ImGuiLastItemData;
-typedef struct ImGuiLocEntry ImGuiLocEntry;
-typedef struct ImGuiMenuColumns ImGuiMenuColumns;
-typedef struct ImGuiNavItemData ImGuiNavItemData;
-typedef struct ImGuiNavTreeNodeData ImGuiNavTreeNodeData;
-typedef struct ImGuiMetricsConfig ImGuiMetricsConfig;
-typedef struct ImGuiNextWindowData ImGuiNextWindowData;
-typedef struct ImGuiNextItemData ImGuiNextItemData;
-typedef struct ImGuiOldColumnData ImGuiOldColumnData;
-typedef struct ImGuiOldColumns ImGuiOldColumns;
-typedef struct ImGuiPopupData ImGuiPopupData;
-typedef struct ImGuiSettingsHandler ImGuiSettingsHandler;
-typedef struct ImGuiStackSizes ImGuiStackSizes;
-typedef struct ImGuiStyleMod ImGuiStyleMod;
-typedef struct ImGuiTabBar ImGuiTabBar;
-typedef struct ImGuiTabItem ImGuiTabItem;
-typedef struct ImGuiTable ImGuiTable;
-typedef struct ImGuiTableHeaderData ImGuiTableHeaderData;
-typedef struct ImGuiTableColumn ImGuiTableColumn;
-typedef struct ImGuiTableInstanceData ImGuiTableInstanceData;
-typedef struct ImGuiTableTempData ImGuiTableTempData;
-typedef struct ImGuiTableSettings ImGuiTableSettings;
-typedef struct ImGuiTableColumnsSettings ImGuiTableColumnsSettings;
-typedef struct ImGuiTypingSelectState ImGuiTypingSelectState;
-typedef struct ImGuiTypingSelectRequest ImGuiTypingSelectRequest;
-typedef struct ImGuiWindow ImGuiWindow;
-typedef struct ImGuiWindowDockStyle ImGuiWindowDockStyle;
-typedef struct ImGuiWindowTempData ImGuiWindowTempData;
-typedef struct ImGuiWindowSettings ImGuiWindowSettings;
-typedef struct ImVector_const_charPtr {
- int Size;
- int Capacity;
- const char** Data;
-} ImVector_const_charPtr;
-
-struct ImDrawChannel;
-struct ImDrawCmd;
-struct ImDrawData;
-struct ImDrawList;
-struct ImDrawListSharedData;
-struct ImDrawListSplitter;
-struct ImDrawVert;
-struct ImFont;
-struct ImFontAtlas;
-struct ImFontBuilderIO;
-struct ImFontConfig;
-struct ImFontGlyph;
-struct ImFontGlyphRangesBuilder;
-struct ImColor;
-struct ImGuiContext;
-struct ImGuiIO;
-struct ImGuiInputTextCallbackData;
-struct ImGuiKeyData;
-struct ImGuiListClipper;
-struct ImGuiOnceUponAFrame;
-struct ImGuiPayload;
-struct ImGuiPlatformIO;
-struct ImGuiPlatformMonitor;
-struct ImGuiPlatformImeData;
-struct ImGuiSizeCallbackData;
-struct ImGuiStorage;
-struct ImGuiStyle;
-struct ImGuiTableSortSpecs;
-struct ImGuiTableColumnSortSpecs;
-struct ImGuiTextBuffer;
-struct ImGuiTextFilter;
-struct ImGuiViewport;
-struct ImGuiWindowClass;
-typedef int ImGuiCol;
-typedef int ImGuiCond;
-typedef int ImGuiDataType;
-typedef int ImGuiDir;
-typedef int ImGuiMouseButton;
-typedef int ImGuiMouseCursor;
-typedef int ImGuiSortDirection;
-typedef int ImGuiStyleVar;
-typedef int ImGuiTableBgTarget;
-typedef int ImDrawFlags;
-typedef int ImDrawListFlags;
-typedef int ImFontAtlasFlags;
-typedef int ImGuiBackendFlags;
-typedef int ImGuiButtonFlags;
-typedef int ImGuiChildFlags;
-typedef int ImGuiColorEditFlags;
-typedef int ImGuiConfigFlags;
-typedef int ImGuiComboFlags;
-typedef int ImGuiDockNodeFlags;
-typedef int ImGuiDragDropFlags;
-typedef int ImGuiFocusedFlags;
-typedef int ImGuiHoveredFlags;
-typedef int ImGuiInputTextFlags;
-typedef int ImGuiKeyChord;
-typedef int ImGuiPopupFlags;
-typedef int ImGuiSelectableFlags;
-typedef int ImGuiSliderFlags;
-typedef int ImGuiTabBarFlags;
-typedef int ImGuiTabItemFlags;
-typedef int ImGuiTableFlags;
-typedef int ImGuiTableColumnFlags;
-typedef int ImGuiTableRowFlags;
-typedef int ImGuiTreeNodeFlags;
-typedef int ImGuiViewportFlags;
-typedef int ImGuiWindowFlags;
-typedef void* ImTextureID;
-typedef unsigned short ImDrawIdx;
-typedef unsigned int ImGuiID;
-typedef signed char ImS8;
-typedef unsigned char ImU8;
-typedef signed short ImS16;
-typedef unsigned short ImU16;
-typedef signed int ImS32;
-typedef unsigned int ImU32;
-typedef signed long long ImS64;
-typedef unsigned long long ImU64;
-typedef unsigned int ImWchar32;
-typedef unsigned short ImWchar16;
-typedef ImWchar16 ImWchar;
-typedef int (*ImGuiInputTextCallback)(ImGuiInputTextCallbackData* data);
-typedef void (*ImGuiSizeCallback)(ImGuiSizeCallbackData* data);
-typedef void* (*ImGuiMemAllocFunc)(size_t sz, void* user_data);
-typedef void (*ImGuiMemFreeFunc)(void* ptr, void* user_data);
-typedef struct ImVec2 ImVec2;
-struct ImVec2 {
- float x, y;
-};
-typedef struct ImVec4 ImVec4;
-struct ImVec4 {
- float x, y, z, w;
-};
-typedef enum {
- ImGuiWindowFlags_None = 0,
- ImGuiWindowFlags_NoTitleBar = 1 << 0,
- ImGuiWindowFlags_NoResize = 1 << 1,
- ImGuiWindowFlags_NoMove = 1 << 2,
- ImGuiWindowFlags_NoScrollbar = 1 << 3,
- ImGuiWindowFlags_NoScrollWithMouse = 1 << 4,
- ImGuiWindowFlags_NoCollapse = 1 << 5,
- ImGuiWindowFlags_AlwaysAutoResize = 1 << 6,
- ImGuiWindowFlags_NoBackground = 1 << 7,
- ImGuiWindowFlags_NoSavedSettings = 1 << 8,
- ImGuiWindowFlags_NoMouseInputs = 1 << 9,
- ImGuiWindowFlags_MenuBar = 1 << 10,
- ImGuiWindowFlags_HorizontalScrollbar = 1 << 11,
- ImGuiWindowFlags_NoFocusOnAppearing = 1 << 12,
- ImGuiWindowFlags_NoBringToFrontOnFocus = 1 << 13,
- ImGuiWindowFlags_AlwaysVerticalScrollbar = 1 << 14,
- ImGuiWindowFlags_AlwaysHorizontalScrollbar = 1 << 15,
- ImGuiWindowFlags_NoNavInputs = 1 << 16,
- ImGuiWindowFlags_NoNavFocus = 1 << 17,
- ImGuiWindowFlags_UnsavedDocument = 1 << 18,
- ImGuiWindowFlags_NoDocking = 1 << 19,
- ImGuiWindowFlags_NoNav =
- ImGuiWindowFlags_NoNavInputs | ImGuiWindowFlags_NoNavFocus,
- ImGuiWindowFlags_NoDecoration =
- ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
- ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoCollapse,
- ImGuiWindowFlags_NoInputs = ImGuiWindowFlags_NoMouseInputs |
- ImGuiWindowFlags_NoNavInputs |
- ImGuiWindowFlags_NoNavFocus,
- ImGuiWindowFlags_NavFlattened = 1 << 23,
- ImGuiWindowFlags_ChildWindow = 1 << 24,
- ImGuiWindowFlags_Tooltip = 1 << 25,
- ImGuiWindowFlags_Popup = 1 << 26,
- ImGuiWindowFlags_Modal = 1 << 27,
- ImGuiWindowFlags_ChildMenu = 1 << 28,
- ImGuiWindowFlags_DockNodeHost = 1 << 29,
-} ImGuiWindowFlags_;
-typedef enum {
- ImGuiChildFlags_None = 0,
- ImGuiChildFlags_Border = 1 << 0,
- ImGuiChildFlags_AlwaysUseWindowPadding = 1 << 1,
- ImGuiChildFlags_ResizeX = 1 << 2,
- ImGuiChildFlags_ResizeY = 1 << 3,
- ImGuiChildFlags_AutoResizeX = 1 << 4,
- ImGuiChildFlags_AutoResizeY = 1 << 5,
- ImGuiChildFlags_AlwaysAutoResize = 1 << 6,
- ImGuiChildFlags_FrameStyle = 1 << 7,
-} ImGuiChildFlags_;
-typedef enum {
- ImGuiInputTextFlags_None = 0,
- ImGuiInputTextFlags_CharsDecimal = 1 << 0,
- ImGuiInputTextFlags_CharsHexadecimal = 1 << 1,
- ImGuiInputTextFlags_CharsUppercase = 1 << 2,
- ImGuiInputTextFlags_CharsNoBlank = 1 << 3,
- ImGuiInputTextFlags_AutoSelectAll = 1 << 4,
- ImGuiInputTextFlags_EnterReturnsTrue = 1 << 5,
- ImGuiInputTextFlags_CallbackCompletion = 1 << 6,
- ImGuiInputTextFlags_CallbackHistory = 1 << 7,
- ImGuiInputTextFlags_CallbackAlways = 1 << 8,
- ImGuiInputTextFlags_CallbackCharFilter = 1 << 9,
- ImGuiInputTextFlags_AllowTabInput = 1 << 10,
- ImGuiInputTextFlags_CtrlEnterForNewLine = 1 << 11,
- ImGuiInputTextFlags_NoHorizontalScroll = 1 << 12,
- ImGuiInputTextFlags_AlwaysOverwrite = 1 << 13,
- ImGuiInputTextFlags_ReadOnly = 1 << 14,
- ImGuiInputTextFlags_Password = 1 << 15,
- ImGuiInputTextFlags_NoUndoRedo = 1 << 16,
- ImGuiInputTextFlags_CharsScientific = 1 << 17,
- ImGuiInputTextFlags_CallbackResize = 1 << 18,
- ImGuiInputTextFlags_CallbackEdit = 1 << 19,
- ImGuiInputTextFlags_EscapeClearsAll = 1 << 20,
-} ImGuiInputTextFlags_;
-typedef enum {
- ImGuiTreeNodeFlags_None = 0,
- ImGuiTreeNodeFlags_Selected = 1 << 0,
- ImGuiTreeNodeFlags_Framed = 1 << 1,
- ImGuiTreeNodeFlags_AllowOverlap = 1 << 2,
- ImGuiTreeNodeFlags_NoTreePushOnOpen = 1 << 3,
- ImGuiTreeNodeFlags_NoAutoOpenOnLog = 1 << 4,
- ImGuiTreeNodeFlags_DefaultOpen = 1 << 5,
- ImGuiTreeNodeFlags_OpenOnDoubleClick = 1 << 6,
- ImGuiTreeNodeFlags_OpenOnArrow = 1 << 7,
- ImGuiTreeNodeFlags_Leaf = 1 << 8,
- ImGuiTreeNodeFlags_Bullet = 1 << 9,
- ImGuiTreeNodeFlags_FramePadding = 1 << 10,
- ImGuiTreeNodeFlags_SpanAvailWidth = 1 << 11,
- ImGuiTreeNodeFlags_SpanFullWidth = 1 << 12,
- ImGuiTreeNodeFlags_SpanTextWidth = 1 << 13,
- ImGuiTreeNodeFlags_SpanAllColumns = 1 << 14,
- ImGuiTreeNodeFlags_NavLeftJumpsBackHere = 1 << 15,
- ImGuiTreeNodeFlags_CollapsingHeader = ImGuiTreeNodeFlags_Framed |
- ImGuiTreeNodeFlags_NoTreePushOnOpen |
- ImGuiTreeNodeFlags_NoAutoOpenOnLog,
-} ImGuiTreeNodeFlags_;
-typedef enum {
- ImGuiPopupFlags_None = 0,
- ImGuiPopupFlags_MouseButtonLeft = 0,
- ImGuiPopupFlags_MouseButtonRight = 1,
- ImGuiPopupFlags_MouseButtonMiddle = 2,
- ImGuiPopupFlags_MouseButtonMask_ = 0x1F,
- ImGuiPopupFlags_MouseButtonDefault_ = 1,
- ImGuiPopupFlags_NoReopen = 1 << 5,
- ImGuiPopupFlags_NoOpenOverExistingPopup = 1 << 7,
- ImGuiPopupFlags_NoOpenOverItems = 1 << 8,
- ImGuiPopupFlags_AnyPopupId = 1 << 10,
- ImGuiPopupFlags_AnyPopupLevel = 1 << 11,
- ImGuiPopupFlags_AnyPopup =
- ImGuiPopupFlags_AnyPopupId | ImGuiPopupFlags_AnyPopupLevel,
-} ImGuiPopupFlags_;
-typedef enum {
- ImGuiSelectableFlags_None = 0,
- ImGuiSelectableFlags_DontClosePopups = 1 << 0,
- ImGuiSelectableFlags_SpanAllColumns = 1 << 1,
- ImGuiSelectableFlags_AllowDoubleClick = 1 << 2,
- ImGuiSelectableFlags_Disabled = 1 << 3,
- ImGuiSelectableFlags_AllowOverlap = 1 << 4,
-} ImGuiSelectableFlags_;
-typedef enum {
- ImGuiComboFlags_None = 0,
- ImGuiComboFlags_PopupAlignLeft = 1 << 0,
- ImGuiComboFlags_HeightSmall = 1 << 1,
- ImGuiComboFlags_HeightRegular = 1 << 2,
- ImGuiComboFlags_HeightLarge = 1 << 3,
- ImGuiComboFlags_HeightLargest = 1 << 4,
- ImGuiComboFlags_NoArrowButton = 1 << 5,
- ImGuiComboFlags_NoPreview = 1 << 6,
- ImGuiComboFlags_WidthFitPreview = 1 << 7,
- ImGuiComboFlags_HeightMask_ =
- ImGuiComboFlags_HeightSmall | ImGuiComboFlags_HeightRegular |
- ImGuiComboFlags_HeightLarge | ImGuiComboFlags_HeightLargest,
-} ImGuiComboFlags_;
-typedef enum {
- ImGuiTabBarFlags_None = 0,
- ImGuiTabBarFlags_Reorderable = 1 << 0,
- ImGuiTabBarFlags_AutoSelectNewTabs = 1 << 1,
- ImGuiTabBarFlags_TabListPopupButton = 1 << 2,
- ImGuiTabBarFlags_NoCloseWithMiddleMouseButton = 1 << 3,
- ImGuiTabBarFlags_NoTabListScrollingButtons = 1 << 4,
- ImGuiTabBarFlags_NoTooltip = 1 << 5,
- ImGuiTabBarFlags_FittingPolicyResizeDown = 1 << 6,
- ImGuiTabBarFlags_FittingPolicyScroll = 1 << 7,
- ImGuiTabBarFlags_FittingPolicyMask_ =
- ImGuiTabBarFlags_FittingPolicyResizeDown |
- ImGuiTabBarFlags_FittingPolicyScroll,
- ImGuiTabBarFlags_FittingPolicyDefault_ =
- ImGuiTabBarFlags_FittingPolicyResizeDown,
-} ImGuiTabBarFlags_;
-typedef enum {
- ImGuiTabItemFlags_None = 0,
- ImGuiTabItemFlags_UnsavedDocument = 1 << 0,
- ImGuiTabItemFlags_SetSelected = 1 << 1,
- ImGuiTabItemFlags_NoCloseWithMiddleMouseButton = 1 << 2,
- ImGuiTabItemFlags_NoPushId = 1 << 3,
- ImGuiTabItemFlags_NoTooltip = 1 << 4,
- ImGuiTabItemFlags_NoReorder = 1 << 5,
- ImGuiTabItemFlags_Leading = 1 << 6,
- ImGuiTabItemFlags_Trailing = 1 << 7,
- ImGuiTabItemFlags_NoAssumedClosure = 1 << 8,
-} ImGuiTabItemFlags_;
-typedef enum {
- ImGuiFocusedFlags_None = 0,
- ImGuiFocusedFlags_ChildWindows = 1 << 0,
- ImGuiFocusedFlags_RootWindow = 1 << 1,
- ImGuiFocusedFlags_AnyWindow = 1 << 2,
- ImGuiFocusedFlags_NoPopupHierarchy = 1 << 3,
- ImGuiFocusedFlags_DockHierarchy = 1 << 4,
- ImGuiFocusedFlags_RootAndChildWindows =
- ImGuiFocusedFlags_RootWindow | ImGuiFocusedFlags_ChildWindows,
-} ImGuiFocusedFlags_;
-typedef enum {
- ImGuiHoveredFlags_None = 0,
- ImGuiHoveredFlags_ChildWindows = 1 << 0,
- ImGuiHoveredFlags_RootWindow = 1 << 1,
- ImGuiHoveredFlags_AnyWindow = 1 << 2,
- ImGuiHoveredFlags_NoPopupHierarchy = 1 << 3,
- ImGuiHoveredFlags_DockHierarchy = 1 << 4,
- ImGuiHoveredFlags_AllowWhenBlockedByPopup = 1 << 5,
- ImGuiHoveredFlags_AllowWhenBlockedByActiveItem = 1 << 7,
- ImGuiHoveredFlags_AllowWhenOverlappedByItem = 1 << 8,
- ImGuiHoveredFlags_AllowWhenOverlappedByWindow = 1 << 9,
- ImGuiHoveredFlags_AllowWhenDisabled = 1 << 10,
- ImGuiHoveredFlags_NoNavOverride = 1 << 11,
- ImGuiHoveredFlags_AllowWhenOverlapped =
- ImGuiHoveredFlags_AllowWhenOverlappedByItem |
- ImGuiHoveredFlags_AllowWhenOverlappedByWindow,
- ImGuiHoveredFlags_RectOnly = ImGuiHoveredFlags_AllowWhenBlockedByPopup |
- ImGuiHoveredFlags_AllowWhenBlockedByActiveItem |
- ImGuiHoveredFlags_AllowWhenOverlapped,
- ImGuiHoveredFlags_RootAndChildWindows =
- ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows,
- ImGuiHoveredFlags_ForTooltip = 1 << 12,
- ImGuiHoveredFlags_Stationary = 1 << 13,
- ImGuiHoveredFlags_DelayNone = 1 << 14,
- ImGuiHoveredFlags_DelayShort = 1 << 15,
- ImGuiHoveredFlags_DelayNormal = 1 << 16,
- ImGuiHoveredFlags_NoSharedDelay = 1 << 17,
-} ImGuiHoveredFlags_;
-typedef enum {
- ImGuiDockNodeFlags_None = 0,
- ImGuiDockNodeFlags_KeepAliveOnly = 1 << 0,
- ImGuiDockNodeFlags_NoDockingOverCentralNode = 1 << 2,
- ImGuiDockNodeFlags_PassthruCentralNode = 1 << 3,
- ImGuiDockNodeFlags_NoDockingSplit = 1 << 4,
- ImGuiDockNodeFlags_NoResize = 1 << 5,
- ImGuiDockNodeFlags_AutoHideTabBar = 1 << 6,
- ImGuiDockNodeFlags_NoUndocking = 1 << 7,
-} ImGuiDockNodeFlags_;
-typedef enum {
- ImGuiDragDropFlags_None = 0,
- ImGuiDragDropFlags_SourceNoPreviewTooltip = 1 << 0,
- ImGuiDragDropFlags_SourceNoDisableHover = 1 << 1,
- ImGuiDragDropFlags_SourceNoHoldToOpenOthers = 1 << 2,
- ImGuiDragDropFlags_SourceAllowNullID = 1 << 3,
- ImGuiDragDropFlags_SourceExtern = 1 << 4,
- ImGuiDragDropFlags_SourceAutoExpirePayload = 1 << 5,
- ImGuiDragDropFlags_AcceptBeforeDelivery = 1 << 10,
- ImGuiDragDropFlags_AcceptNoDrawDefaultRect = 1 << 11,
- ImGuiDragDropFlags_AcceptNoPreviewTooltip = 1 << 12,
- ImGuiDragDropFlags_AcceptPeekOnly =
- ImGuiDragDropFlags_AcceptBeforeDelivery |
- ImGuiDragDropFlags_AcceptNoDrawDefaultRect,
-} ImGuiDragDropFlags_;
-typedef enum {
- ImGuiDataType_S8,
- ImGuiDataType_U8,
- ImGuiDataType_S16,
- ImGuiDataType_U16,
- ImGuiDataType_S32,
- ImGuiDataType_U32,
- ImGuiDataType_S64,
- ImGuiDataType_U64,
- ImGuiDataType_Float,
- ImGuiDataType_Double,
- ImGuiDataType_COUNT
-} ImGuiDataType_;
-typedef enum {
- ImGuiDir_None = -1,
- ImGuiDir_Left = 0,
- ImGuiDir_Right = 1,
- ImGuiDir_Up = 2,
- ImGuiDir_Down = 3,
- ImGuiDir_COUNT
-} ImGuiDir_;
-typedef enum {
- ImGuiSortDirection_None = 0,
- ImGuiSortDirection_Ascending = 1,
- ImGuiSortDirection_Descending = 2
-} ImGuiSortDirection_;
-typedef enum {
- ImGuiKey_None = 0,
- ImGuiKey_Tab = 512,
- ImGuiKey_LeftArrow = 513,
- ImGuiKey_RightArrow = 514,
- ImGuiKey_UpArrow = 515,
- ImGuiKey_DownArrow = 516,
- ImGuiKey_PageUp = 517,
- ImGuiKey_PageDown = 518,
- ImGuiKey_Home = 519,
- ImGuiKey_End = 520,
- ImGuiKey_Insert = 521,
- ImGuiKey_Delete = 522,
- ImGuiKey_Backspace = 523,
- ImGuiKey_Space = 524,
- ImGuiKey_Enter = 525,
- ImGuiKey_Escape = 526,
- ImGuiKey_LeftCtrl = 527,
- ImGuiKey_LeftShift = 528,
- ImGuiKey_LeftAlt = 529,
- ImGuiKey_LeftSuper = 530,
- ImGuiKey_RightCtrl = 531,
- ImGuiKey_RightShift = 532,
- ImGuiKey_RightAlt = 533,
- ImGuiKey_RightSuper = 534,
- ImGuiKey_Menu = 535,
- ImGuiKey_0 = 536,
- ImGuiKey_1 = 537,
- ImGuiKey_2 = 538,
- ImGuiKey_3 = 539,
- ImGuiKey_4 = 540,
- ImGuiKey_5 = 541,
- ImGuiKey_6 = 542,
- ImGuiKey_7 = 543,
- ImGuiKey_8 = 544,
- ImGuiKey_9 = 545,
- ImGuiKey_A = 546,
- ImGuiKey_B = 547,
- ImGuiKey_C = 548,
- ImGuiKey_D = 549,
- ImGuiKey_E = 550,
- ImGuiKey_F = 551,
- ImGuiKey_G = 552,
- ImGuiKey_H = 553,
- ImGuiKey_I = 554,
- ImGuiKey_J = 555,
- ImGuiKey_K = 556,
- ImGuiKey_L = 557,
- ImGuiKey_M = 558,
- ImGuiKey_N = 559,
- ImGuiKey_O = 560,
- ImGuiKey_P = 561,
- ImGuiKey_Q = 562,
- ImGuiKey_R = 563,
- ImGuiKey_S = 564,
- ImGuiKey_T = 565,
- ImGuiKey_U = 566,
- ImGuiKey_V = 567,
- ImGuiKey_W = 568,
- ImGuiKey_X = 569,
- ImGuiKey_Y = 570,
- ImGuiKey_Z = 571,
- ImGuiKey_F1 = 572,
- ImGuiKey_F2 = 573,
- ImGuiKey_F3 = 574,
- ImGuiKey_F4 = 575,
- ImGuiKey_F5 = 576,
- ImGuiKey_F6 = 577,
- ImGuiKey_F7 = 578,
- ImGuiKey_F8 = 579,
- ImGuiKey_F9 = 580,
- ImGuiKey_F10 = 581,
- ImGuiKey_F11 = 582,
- ImGuiKey_F12 = 583,
- ImGuiKey_F13 = 584,
- ImGuiKey_F14 = 585,
- ImGuiKey_F15 = 586,
- ImGuiKey_F16 = 587,
- ImGuiKey_F17 = 588,
- ImGuiKey_F18 = 589,
- ImGuiKey_F19 = 590,
- ImGuiKey_F20 = 591,
- ImGuiKey_F21 = 592,
- ImGuiKey_F22 = 593,
- ImGuiKey_F23 = 594,
- ImGuiKey_F24 = 595,
- ImGuiKey_Apostrophe = 596,
- ImGuiKey_Comma = 597,
- ImGuiKey_Minus = 598,
- ImGuiKey_Period = 599,
- ImGuiKey_Slash = 600,
- ImGuiKey_Semicolon = 601,
- ImGuiKey_Equal = 602,
- ImGuiKey_LeftBracket = 603,
- ImGuiKey_Backslash = 604,
- ImGuiKey_RightBracket = 605,
- ImGuiKey_GraveAccent = 606,
- ImGuiKey_CapsLock = 607,
- ImGuiKey_ScrollLock = 608,
- ImGuiKey_NumLock = 609,
- ImGuiKey_PrintScreen = 610,
- ImGuiKey_Pause = 611,
- ImGuiKey_Keypad0 = 612,
- ImGuiKey_Keypad1 = 613,
- ImGuiKey_Keypad2 = 614,
- ImGuiKey_Keypad3 = 615,
- ImGuiKey_Keypad4 = 616,
- ImGuiKey_Keypad5 = 617,
- ImGuiKey_Keypad6 = 618,
- ImGuiKey_Keypad7 = 619,
- ImGuiKey_Keypad8 = 620,
- ImGuiKey_Keypad9 = 621,
- ImGuiKey_KeypadDecimal = 622,
- ImGuiKey_KeypadDivide = 623,
- ImGuiKey_KeypadMultiply = 624,
- ImGuiKey_KeypadSubtract = 625,
- ImGuiKey_KeypadAdd = 626,
- ImGuiKey_KeypadEnter = 627,
- ImGuiKey_KeypadEqual = 628,
- ImGuiKey_AppBack = 629,
- ImGuiKey_AppForward = 630,
- ImGuiKey_GamepadStart = 631,
- ImGuiKey_GamepadBack = 632,
- ImGuiKey_GamepadFaceLeft = 633,
- ImGuiKey_GamepadFaceRight = 634,
- ImGuiKey_GamepadFaceUp = 635,
- ImGuiKey_GamepadFaceDown = 636,
- ImGuiKey_GamepadDpadLeft = 637,
- ImGuiKey_GamepadDpadRight = 638,
- ImGuiKey_GamepadDpadUp = 639,
- ImGuiKey_GamepadDpadDown = 640,
- ImGuiKey_GamepadL1 = 641,
- ImGuiKey_GamepadR1 = 642,
- ImGuiKey_GamepadL2 = 643,
- ImGuiKey_GamepadR2 = 644,
- ImGuiKey_GamepadL3 = 645,
- ImGuiKey_GamepadR3 = 646,
- ImGuiKey_GamepadLStickLeft = 647,
- ImGuiKey_GamepadLStickRight = 648,
- ImGuiKey_GamepadLStickUp = 649,
- ImGuiKey_GamepadLStickDown = 650,
- ImGuiKey_GamepadRStickLeft = 651,
- ImGuiKey_GamepadRStickRight = 652,
- ImGuiKey_GamepadRStickUp = 653,
- ImGuiKey_GamepadRStickDown = 654,
- ImGuiKey_MouseLeft = 655,
- ImGuiKey_MouseRight = 656,
- ImGuiKey_MouseMiddle = 657,
- ImGuiKey_MouseX1 = 658,
- ImGuiKey_MouseX2 = 659,
- ImGuiKey_MouseWheelX = 660,
- ImGuiKey_MouseWheelY = 661,
- ImGuiKey_ReservedForModCtrl = 662,
- ImGuiKey_ReservedForModShift = 663,
- ImGuiKey_ReservedForModAlt = 664,
- ImGuiKey_ReservedForModSuper = 665,
- ImGuiKey_COUNT = 666,
- ImGuiMod_None = 0,
- ImGuiMod_Ctrl = 1 << 12,
- ImGuiMod_Shift = 1 << 13,
- ImGuiMod_Alt = 1 << 14,
- ImGuiMod_Super = 1 << 15,
- ImGuiMod_Shortcut = 1 << 11,
- ImGuiMod_Mask_ = 0xF800,
- ImGuiKey_NamedKey_BEGIN = 512,
- ImGuiKey_NamedKey_END = ImGuiKey_COUNT,
- ImGuiKey_NamedKey_COUNT = ImGuiKey_NamedKey_END - ImGuiKey_NamedKey_BEGIN,
- ImGuiKey_KeysData_SIZE = ImGuiKey_NamedKey_COUNT,
- ImGuiKey_KeysData_OFFSET = ImGuiKey_NamedKey_BEGIN,
-} ImGuiKey;
-typedef enum {
- ImGuiConfigFlags_None = 0,
- ImGuiConfigFlags_NavEnableKeyboard = 1 << 0,
- ImGuiConfigFlags_NavEnableGamepad = 1 << 1,
- ImGuiConfigFlags_NavEnableSetMousePos = 1 << 2,
- ImGuiConfigFlags_NavNoCaptureKeyboard = 1 << 3,
- ImGuiConfigFlags_NoMouse = 1 << 4,
- ImGuiConfigFlags_NoMouseCursorChange = 1 << 5,
- ImGuiConfigFlags_DockingEnable = 1 << 6,
- ImGuiConfigFlags_ViewportsEnable = 1 << 10,
- ImGuiConfigFlags_DpiEnableScaleViewports = 1 << 14,
- ImGuiConfigFlags_DpiEnableScaleFonts = 1 << 15,
- ImGuiConfigFlags_IsSRGB = 1 << 20,
- ImGuiConfigFlags_IsTouchScreen = 1 << 21,
-} ImGuiConfigFlags_;
-typedef enum {
- ImGuiBackendFlags_None = 0,
- ImGuiBackendFlags_HasGamepad = 1 << 0,
- ImGuiBackendFlags_HasMouseCursors = 1 << 1,
- ImGuiBackendFlags_HasSetMousePos = 1 << 2,
- ImGuiBackendFlags_RendererHasVtxOffset = 1 << 3,
- ImGuiBackendFlags_PlatformHasViewports = 1 << 10,
- ImGuiBackendFlags_HasMouseHoveredViewport = 1 << 11,
- ImGuiBackendFlags_RendererHasViewports = 1 << 12,
-} ImGuiBackendFlags_;
-typedef enum {
- ImGuiCol_Text,
- ImGuiCol_TextDisabled,
- ImGuiCol_WindowBg,
- ImGuiCol_ChildBg,
- ImGuiCol_PopupBg,
- ImGuiCol_Border,
- ImGuiCol_BorderShadow,
- ImGuiCol_FrameBg,
- ImGuiCol_FrameBgHovered,
- ImGuiCol_FrameBgActive,
- ImGuiCol_TitleBg,
- ImGuiCol_TitleBgActive,
- ImGuiCol_TitleBgCollapsed,
- ImGuiCol_MenuBarBg,
- ImGuiCol_ScrollbarBg,
- ImGuiCol_ScrollbarGrab,
- ImGuiCol_ScrollbarGrabHovered,
- ImGuiCol_ScrollbarGrabActive,
- ImGuiCol_CheckMark,
- ImGuiCol_SliderGrab,
- ImGuiCol_SliderGrabActive,
- ImGuiCol_Button,
- ImGuiCol_ButtonHovered,
- ImGuiCol_ButtonActive,
- ImGuiCol_Header,
- ImGuiCol_HeaderHovered,
- ImGuiCol_HeaderActive,
- ImGuiCol_Separator,
- ImGuiCol_SeparatorHovered,
- ImGuiCol_SeparatorActive,
- ImGuiCol_ResizeGrip,
- ImGuiCol_ResizeGripHovered,
- ImGuiCol_ResizeGripActive,
- ImGuiCol_Tab,
- ImGuiCol_TabHovered,
- ImGuiCol_TabActive,
- ImGuiCol_TabUnfocused,
- ImGuiCol_TabUnfocusedActive,
- ImGuiCol_DockingPreview,
- ImGuiCol_DockingEmptyBg,
- ImGuiCol_PlotLines,
- ImGuiCol_PlotLinesHovered,
- ImGuiCol_PlotHistogram,
- ImGuiCol_PlotHistogramHovered,
- ImGuiCol_TableHeaderBg,
- ImGuiCol_TableBorderStrong,
- ImGuiCol_TableBorderLight,
- ImGuiCol_TableRowBg,
- ImGuiCol_TableRowBgAlt,
- ImGuiCol_TextSelectedBg,
- ImGuiCol_DragDropTarget,
- ImGuiCol_NavHighlight,
- ImGuiCol_NavWindowingHighlight,
- ImGuiCol_NavWindowingDimBg,
- ImGuiCol_ModalWindowDimBg,
- ImGuiCol_COUNT
-} ImGuiCol_;
-typedef enum {
- ImGuiStyleVar_Alpha,
- ImGuiStyleVar_DisabledAlpha,
- ImGuiStyleVar_WindowPadding,
- ImGuiStyleVar_WindowRounding,
- ImGuiStyleVar_WindowBorderSize,
- ImGuiStyleVar_WindowMinSize,
- ImGuiStyleVar_WindowTitleAlign,
- ImGuiStyleVar_ChildRounding,
- ImGuiStyleVar_ChildBorderSize,
- ImGuiStyleVar_PopupRounding,
- ImGuiStyleVar_PopupBorderSize,
- ImGuiStyleVar_FramePadding,
- ImGuiStyleVar_FrameRounding,
- ImGuiStyleVar_FrameBorderSize,
- ImGuiStyleVar_ItemSpacing,
- ImGuiStyleVar_ItemInnerSpacing,
- ImGuiStyleVar_IndentSpacing,
- ImGuiStyleVar_CellPadding,
- ImGuiStyleVar_ScrollbarSize,
- ImGuiStyleVar_ScrollbarRounding,
- ImGuiStyleVar_GrabMinSize,
- ImGuiStyleVar_GrabRounding,
- ImGuiStyleVar_TabRounding,
- ImGuiStyleVar_TabBorderSize,
- ImGuiStyleVar_TabBarBorderSize,
- ImGuiStyleVar_TableAngledHeadersAngle,
- ImGuiStyleVar_TableAngledHeadersTextAlign,
- ImGuiStyleVar_ButtonTextAlign,
- ImGuiStyleVar_SelectableTextAlign,
- ImGuiStyleVar_SeparatorTextBorderSize,
- ImGuiStyleVar_SeparatorTextAlign,
- ImGuiStyleVar_SeparatorTextPadding,
- ImGuiStyleVar_DockingSeparatorSize,
- ImGuiStyleVar_COUNT
-} ImGuiStyleVar_;
-typedef enum {
- ImGuiButtonFlags_None = 0,
- ImGuiButtonFlags_MouseButtonLeft = 1 << 0,
- ImGuiButtonFlags_MouseButtonRight = 1 << 1,
- ImGuiButtonFlags_MouseButtonMiddle = 1 << 2,
- ImGuiButtonFlags_MouseButtonMask_ = ImGuiButtonFlags_MouseButtonLeft |
- ImGuiButtonFlags_MouseButtonRight |
- ImGuiButtonFlags_MouseButtonMiddle,
- ImGuiButtonFlags_MouseButtonDefault_ = ImGuiButtonFlags_MouseButtonLeft,
-} ImGuiButtonFlags_;
-typedef enum {
- ImGuiColorEditFlags_None = 0,
- ImGuiColorEditFlags_NoAlpha = 1 << 1,
- ImGuiColorEditFlags_NoPicker = 1 << 2,
- ImGuiColorEditFlags_NoOptions = 1 << 3,
- ImGuiColorEditFlags_NoSmallPreview = 1 << 4,
- ImGuiColorEditFlags_NoInputs = 1 << 5,
- ImGuiColorEditFlags_NoTooltip = 1 << 6,
- ImGuiColorEditFlags_NoLabel = 1 << 7,
- ImGuiColorEditFlags_NoSidePreview = 1 << 8,
- ImGuiColorEditFlags_NoDragDrop = 1 << 9,
- ImGuiColorEditFlags_NoBorder = 1 << 10,
- ImGuiColorEditFlags_AlphaBar = 1 << 16,
- ImGuiColorEditFlags_AlphaPreview = 1 << 17,
- ImGuiColorEditFlags_AlphaPreviewHalf = 1 << 18,
- ImGuiColorEditFlags_HDR = 1 << 19,
- ImGuiColorEditFlags_DisplayRGB = 1 << 20,
- ImGuiColorEditFlags_DisplayHSV = 1 << 21,
- ImGuiColorEditFlags_DisplayHex = 1 << 22,
- ImGuiColorEditFlags_Uint8 = 1 << 23,
- ImGuiColorEditFlags_Float = 1 << 24,
- ImGuiColorEditFlags_PickerHueBar = 1 << 25,
- ImGuiColorEditFlags_PickerHueWheel = 1 << 26,
- ImGuiColorEditFlags_InputRGB = 1 << 27,
- ImGuiColorEditFlags_InputHSV = 1 << 28,
- ImGuiColorEditFlags_DefaultOptions_ =
- ImGuiColorEditFlags_Uint8 | ImGuiColorEditFlags_DisplayRGB |
- ImGuiColorEditFlags_InputRGB | ImGuiColorEditFlags_PickerHueBar,
- ImGuiColorEditFlags_DisplayMask_ = ImGuiColorEditFlags_DisplayRGB |
- ImGuiColorEditFlags_DisplayHSV |
- ImGuiColorEditFlags_DisplayHex,
- ImGuiColorEditFlags_DataTypeMask_ =
- ImGuiColorEditFlags_Uint8 | ImGuiColorEditFlags_Float,
- ImGuiColorEditFlags_PickerMask_ =
- ImGuiColorEditFlags_PickerHueWheel | ImGuiColorEditFlags_PickerHueBar,
- ImGuiColorEditFlags_InputMask_ =
- ImGuiColorEditFlags_InputRGB | ImGuiColorEditFlags_InputHSV,
-} ImGuiColorEditFlags_;
-typedef enum {
- ImGuiSliderFlags_None = 0,
- ImGuiSliderFlags_AlwaysClamp = 1 << 4,
- ImGuiSliderFlags_Logarithmic = 1 << 5,
- ImGuiSliderFlags_NoRoundToFormat = 1 << 6,
- ImGuiSliderFlags_NoInput = 1 << 7,
- ImGuiSliderFlags_InvalidMask_ = 0x7000000F,
-} ImGuiSliderFlags_;
-typedef enum {
- ImGuiMouseButton_Left = 0,
- ImGuiMouseButton_Right = 1,
- ImGuiMouseButton_Middle = 2,
- ImGuiMouseButton_COUNT = 5
-} ImGuiMouseButton_;
-typedef enum {
- ImGuiMouseCursor_None = -1,
- ImGuiMouseCursor_Arrow = 0,
- ImGuiMouseCursor_TextInput,
- ImGuiMouseCursor_ResizeAll,
- ImGuiMouseCursor_ResizeNS,
- ImGuiMouseCursor_ResizeEW,
- ImGuiMouseCursor_ResizeNESW,
- ImGuiMouseCursor_ResizeNWSE,
- ImGuiMouseCursor_Hand,
- ImGuiMouseCursor_NotAllowed,
- ImGuiMouseCursor_COUNT
-} ImGuiMouseCursor_;
-typedef enum {
- ImGuiMouseSource_Mouse = 0,
- ImGuiMouseSource_TouchScreen = 1,
- ImGuiMouseSource_Pen = 2,
- ImGuiMouseSource_COUNT = 3,
-} ImGuiMouseSource;
-typedef enum {
- ImGuiCond_None = 0,
- ImGuiCond_Always = 1 << 0,
- ImGuiCond_Once = 1 << 1,
- ImGuiCond_FirstUseEver = 1 << 2,
- ImGuiCond_Appearing = 1 << 3,
-} ImGuiCond_;
-typedef enum {
- ImGuiTableFlags_None = 0,
- ImGuiTableFlags_Resizable = 1 << 0,
- ImGuiTableFlags_Reorderable = 1 << 1,
- ImGuiTableFlags_Hideable = 1 << 2,
- ImGuiTableFlags_Sortable = 1 << 3,
- ImGuiTableFlags_NoSavedSettings = 1 << 4,
- ImGuiTableFlags_ContextMenuInBody = 1 << 5,
- ImGuiTableFlags_RowBg = 1 << 6,
- ImGuiTableFlags_BordersInnerH = 1 << 7,
- ImGuiTableFlags_BordersOuterH = 1 << 8,
- ImGuiTableFlags_BordersInnerV = 1 << 9,
- ImGuiTableFlags_BordersOuterV = 1 << 10,
- ImGuiTableFlags_BordersH =
- ImGuiTableFlags_BordersInnerH | ImGuiTableFlags_BordersOuterH,
- ImGuiTableFlags_BordersV =
- ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_BordersOuterV,
- ImGuiTableFlags_BordersInner =
- ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_BordersInnerH,
- ImGuiTableFlags_BordersOuter =
- ImGuiTableFlags_BordersOuterV | ImGuiTableFlags_BordersOuterH,
- ImGuiTableFlags_Borders =
- ImGuiTableFlags_BordersInner | ImGuiTableFlags_BordersOuter,
- ImGuiTableFlags_NoBordersInBody = 1 << 11,
- ImGuiTableFlags_NoBordersInBodyUntilResize = 1 << 12,
- ImGuiTableFlags_SizingFixedFit = 1 << 13,
- ImGuiTableFlags_SizingFixedSame = 2 << 13,
- ImGuiTableFlags_SizingStretchProp = 3 << 13,
- ImGuiTableFlags_SizingStretchSame = 4 << 13,
- ImGuiTableFlags_NoHostExtendX = 1 << 16,
- ImGuiTableFlags_NoHostExtendY = 1 << 17,
- ImGuiTableFlags_NoKeepColumnsVisible = 1 << 18,
- ImGuiTableFlags_PreciseWidths = 1 << 19,
- ImGuiTableFlags_NoClip = 1 << 20,
- ImGuiTableFlags_PadOuterX = 1 << 21,
- ImGuiTableFlags_NoPadOuterX = 1 << 22,
- ImGuiTableFlags_NoPadInnerX = 1 << 23,
- ImGuiTableFlags_ScrollX = 1 << 24,
- ImGuiTableFlags_ScrollY = 1 << 25,
- ImGuiTableFlags_SortMulti = 1 << 26,
- ImGuiTableFlags_SortTristate = 1 << 27,
- ImGuiTableFlags_HighlightHoveredColumn = 1 << 28,
- ImGuiTableFlags_SizingMask_ =
- ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_SizingFixedSame |
- ImGuiTableFlags_SizingStretchProp | ImGuiTableFlags_SizingStretchSame,
-} ImGuiTableFlags_;
-typedef enum {
- ImGuiTableColumnFlags_None = 0,
- ImGuiTableColumnFlags_Disabled = 1 << 0,
- ImGuiTableColumnFlags_DefaultHide = 1 << 1,
- ImGuiTableColumnFlags_DefaultSort = 1 << 2,
- ImGuiTableColumnFlags_WidthStretch = 1 << 3,
- ImGuiTableColumnFlags_WidthFixed = 1 << 4,
- ImGuiTableColumnFlags_NoResize = 1 << 5,
- ImGuiTableColumnFlags_NoReorder = 1 << 6,
- ImGuiTableColumnFlags_NoHide = 1 << 7,
- ImGuiTableColumnFlags_NoClip = 1 << 8,
- ImGuiTableColumnFlags_NoSort = 1 << 9,
- ImGuiTableColumnFlags_NoSortAscending = 1 << 10,
- ImGuiTableColumnFlags_NoSortDescending = 1 << 11,
- ImGuiTableColumnFlags_NoHeaderLabel = 1 << 12,
- ImGuiTableColumnFlags_NoHeaderWidth = 1 << 13,
- ImGuiTableColumnFlags_PreferSortAscending = 1 << 14,
- ImGuiTableColumnFlags_PreferSortDescending = 1 << 15,
- ImGuiTableColumnFlags_IndentEnable = 1 << 16,
- ImGuiTableColumnFlags_IndentDisable = 1 << 17,
- ImGuiTableColumnFlags_AngledHeader = 1 << 18,
- ImGuiTableColumnFlags_IsEnabled = 1 << 24,
- ImGuiTableColumnFlags_IsVisible = 1 << 25,
- ImGuiTableColumnFlags_IsSorted = 1 << 26,
- ImGuiTableColumnFlags_IsHovered = 1 << 27,
- ImGuiTableColumnFlags_WidthMask_ =
- ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_WidthFixed,
- ImGuiTableColumnFlags_IndentMask_ =
- ImGuiTableColumnFlags_IndentEnable | ImGuiTableColumnFlags_IndentDisable,
- ImGuiTableColumnFlags_StatusMask_ =
- ImGuiTableColumnFlags_IsEnabled | ImGuiTableColumnFlags_IsVisible |
- ImGuiTableColumnFlags_IsSorted | ImGuiTableColumnFlags_IsHovered,
- ImGuiTableColumnFlags_NoDirectResize_ = 1 << 30,
-} ImGuiTableColumnFlags_;
-typedef enum {
- ImGuiTableRowFlags_None = 0,
- ImGuiTableRowFlags_Headers = 1 << 0,
-} ImGuiTableRowFlags_;
-typedef enum {
- ImGuiTableBgTarget_None = 0,
- ImGuiTableBgTarget_RowBg0 = 1,
- ImGuiTableBgTarget_RowBg1 = 2,
- ImGuiTableBgTarget_CellBg = 3,
-} ImGuiTableBgTarget_;
-struct ImGuiTableSortSpecs {
- const ImGuiTableColumnSortSpecs* Specs;
- int SpecsCount;
- bool SpecsDirty;
-};
-struct ImGuiTableColumnSortSpecs {
- ImGuiID ColumnUserID;
- ImS16 ColumnIndex;
- ImS16 SortOrder;
- ImGuiSortDirection SortDirection : 8;
-};
-struct ImGuiStyle {
- float Alpha;
- float DisabledAlpha;
- ImVec2 WindowPadding;
- float WindowRounding;
- float WindowBorderSize;
- ImVec2 WindowMinSize;
- ImVec2 WindowTitleAlign;
- ImGuiDir WindowMenuButtonPosition;
- float ChildRounding;
- float ChildBorderSize;
- float PopupRounding;
- float PopupBorderSize;
- ImVec2 FramePadding;
- float FrameRounding;
- float FrameBorderSize;
- ImVec2 ItemSpacing;
- ImVec2 ItemInnerSpacing;
- ImVec2 CellPadding;
- ImVec2 TouchExtraPadding;
- float IndentSpacing;
- float ColumnsMinSpacing;
- float ScrollbarSize;
- float ScrollbarRounding;
- float GrabMinSize;
- float GrabRounding;
- float LogSliderDeadzone;
- float TabRounding;
- float TabBorderSize;
- float TabMinWidthForCloseButton;
- float TabBarBorderSize;
- float TableAngledHeadersAngle;
- ImVec2 TableAngledHeadersTextAlign;
- ImGuiDir ColorButtonPosition;
- ImVec2 ButtonTextAlign;
- ImVec2 SelectableTextAlign;
- float SeparatorTextBorderSize;
- ImVec2 SeparatorTextAlign;
- ImVec2 SeparatorTextPadding;
- ImVec2 DisplayWindowPadding;
- ImVec2 DisplaySafeAreaPadding;
- float DockingSeparatorSize;
- float MouseCursorScale;
- bool AntiAliasedLines;
- bool AntiAliasedLinesUseTex;
- bool AntiAliasedFill;
- float CurveTessellationTol;
- float CircleTessellationMaxError;
- ImVec4 Colors[ImGuiCol_COUNT];
- float HoverStationaryDelay;
- float HoverDelayShort;
- float HoverDelayNormal;
- ImGuiHoveredFlags HoverFlagsForTooltipMouse;
- ImGuiHoveredFlags HoverFlagsForTooltipNav;
-};
-struct ImGuiKeyData {
- bool Down;
- float DownDuration;
- float DownDurationPrev;
- float AnalogValue;
-};
-typedef struct ImVector_ImWchar {
- int Size;
- int Capacity;
- ImWchar* Data;
-} ImVector_ImWchar;
-
-struct ImGuiIO {
- ImGuiConfigFlags ConfigFlags;
- ImGuiBackendFlags BackendFlags;
- ImVec2 DisplaySize;
- float DeltaTime;
- float IniSavingRate;
- const char* IniFilename;
- const char* LogFilename;
- void* UserData;
- ImFontAtlas* Fonts;
- float FontGlobalScale;
- bool FontAllowUserScaling;
- ImFont* FontDefault;
- ImVec2 DisplayFramebufferScale;
- bool ConfigDockingNoSplit;
- bool ConfigDockingWithShift;
- bool ConfigDockingAlwaysTabBar;
- bool ConfigDockingTransparentPayload;
- bool ConfigViewportsNoAutoMerge;
- bool ConfigViewportsNoTaskBarIcon;
- bool ConfigViewportsNoDecoration;
- bool ConfigViewportsNoDefaultParent;
- bool MouseDrawCursor;
- bool ConfigMacOSXBehaviors;
- bool ConfigInputTrickleEventQueue;
- bool ConfigInputTextCursorBlink;
- bool ConfigInputTextEnterKeepActive;
- bool ConfigDragClickToInputText;
- bool ConfigWindowsResizeFromEdges;
- bool ConfigWindowsMoveFromTitleBarOnly;
- float ConfigMemoryCompactTimer;
- float MouseDoubleClickTime;
- float MouseDoubleClickMaxDist;
- float MouseDragThreshold;
- float KeyRepeatDelay;
- float KeyRepeatRate;
- bool ConfigDebugIsDebuggerPresent;
- bool ConfigDebugBeginReturnValueOnce;
- bool ConfigDebugBeginReturnValueLoop;
- bool ConfigDebugIgnoreFocusLoss;
- bool ConfigDebugIniSettings;
- const char* BackendPlatformName;
- const char* BackendRendererName;
- void* BackendPlatformUserData;
- void* BackendRendererUserData;
- void* BackendLanguageUserData;
- const char* (*GetClipboardTextFn)(void* user_data);
- void (*SetClipboardTextFn)(void* user_data, const char* text);
- void* ClipboardUserData;
- void (*SetPlatformImeDataFn)(ImGuiViewport* viewport,
- ImGuiPlatformImeData* data);
- ImWchar PlatformLocaleDecimalPoint;
- bool WantCaptureMouse;
- bool WantCaptureKeyboard;
- bool WantTextInput;
- bool WantSetMousePos;
- bool WantSaveIniSettings;
- bool NavActive;
- bool NavVisible;
- float Framerate;
- int MetricsRenderVertices;
- int MetricsRenderIndices;
- int MetricsRenderWindows;
- int MetricsActiveWindows;
- ImVec2 MouseDelta;
- ImGuiContext* Ctx;
- ImVec2 MousePos;
- bool MouseDown[5];
- float MouseWheel;
- float MouseWheelH;
- ImGuiMouseSource MouseSource;
- ImGuiID MouseHoveredViewport;
- bool KeyCtrl;
- bool KeyShift;
- bool KeyAlt;
- bool KeySuper;
- ImGuiKeyChord KeyMods;
- ImGuiKeyData KeysData[ImGuiKey_KeysData_SIZE];
- bool WantCaptureMouseUnlessPopupClose;
- ImVec2 MousePosPrev;
- ImVec2 MouseClickedPos[5];
- double MouseClickedTime[5];
- bool MouseClicked[5];
- bool MouseDoubleClicked[5];
- ImU16 MouseClickedCount[5];
- ImU16 MouseClickedLastCount[5];
- bool MouseReleased[5];
- bool MouseDownOwned[5];
- bool MouseDownOwnedUnlessPopupClose[5];
- bool MouseWheelRequestAxisSwap;
- float MouseDownDuration[5];
- float MouseDownDurationPrev[5];
- ImVec2 MouseDragMaxDistanceAbs[5];
- float MouseDragMaxDistanceSqr[5];
- float PenPressure;
- bool AppFocusLost;
- bool AppAcceptingEvents;
- ImS8 BackendUsingLegacyKeyArrays;
- bool BackendUsingLegacyNavInputArray;
- ImWchar16 InputQueueSurrogate;
- ImVector_ImWchar InputQueueCharacters;
-};
-struct ImGuiInputTextCallbackData {
- ImGuiContext* Ctx;
- ImGuiInputTextFlags EventFlag;
- ImGuiInputTextFlags Flags;
- void* UserData;
- ImWchar EventChar;
- ImGuiKey EventKey;
- char* Buf;
- int BufTextLen;
- int BufSize;
- bool BufDirty;
- int CursorPos;
- int SelectionStart;
- int SelectionEnd;
-};
-struct ImGuiSizeCallbackData {
- void* UserData;
- ImVec2 Pos;
- ImVec2 CurrentSize;
- ImVec2 DesiredSize;
-};
-struct ImGuiWindowClass {
- ImGuiID ClassId;
- ImGuiID ParentViewportId;
- ImGuiID FocusRouteParentWindowId;
- ImGuiViewportFlags ViewportFlagsOverrideSet;
- ImGuiViewportFlags ViewportFlagsOverrideClear;
- ImGuiTabItemFlags TabItemFlagsOverrideSet;
- ImGuiDockNodeFlags DockNodeFlagsOverrideSet;
- bool DockingAlwaysTabBar;
- bool DockingAllowUnclassed;
-};
-struct ImGuiPayload {
- void* Data;
- int DataSize;
- ImGuiID SourceId;
- ImGuiID SourceParentId;
- int DataFrameCount;
- char DataType[32 + 1];
- bool Preview;
- bool Delivery;
-};
-struct ImGuiOnceUponAFrame {
- int RefFrame;
-};
-struct ImGuiTextRange {
- const char* b;
- const char* e;
-};
-typedef struct ImGuiTextRange ImGuiTextRange;
-
-typedef struct ImVector_ImGuiTextRange {
- int Size;
- int Capacity;
- ImGuiTextRange* Data;
-} ImVector_ImGuiTextRange;
-
-struct ImGuiTextFilter {
- char InputBuf[256];
- ImVector_ImGuiTextRange Filters;
- int CountGrep;
-};
-typedef struct ImGuiTextRange ImGuiTextRange;
-typedef struct ImVector_char {
- int Size;
- int Capacity;
- char* Data;
-} ImVector_char;
-
-struct ImGuiTextBuffer {
- ImVector_char Buf;
-};
-struct ImGuiStoragePair {
- ImGuiID key;
- union {
- int val_i;
- float val_f;
- void* val_p;
- };
-};
-typedef struct ImGuiStoragePair ImGuiStoragePair;
-
-typedef struct ImVector_ImGuiStoragePair {
- int Size;
- int Capacity;
- ImGuiStoragePair* Data;
-} ImVector_ImGuiStoragePair;
-
-struct ImGuiStorage {
- ImVector_ImGuiStoragePair Data;
-};
-typedef struct ImGuiStoragePair ImGuiStoragePair;
-struct ImGuiListClipper {
- ImGuiContext* Ctx;
- int DisplayStart;
- int DisplayEnd;
- int ItemsCount;
- float ItemsHeight;
- float StartPosY;
- void* TempData;
-};
-struct ImColor {
- ImVec4 Value;
-};
-typedef void (*ImDrawCallback)(const ImDrawList* parent_list,
- const ImDrawCmd* cmd);
-struct ImDrawCmd {
- ImVec4 ClipRect;
- ImTextureID TextureId;
- unsigned int VtxOffset;
- unsigned int IdxOffset;
- unsigned int ElemCount;
- ImDrawCallback UserCallback;
- void* UserCallbackData;
-};
-struct ImDrawVert {
- ImVec2 pos;
- ImVec2 uv;
- ImU32 col;
-};
-typedef struct ImDrawCmdHeader ImDrawCmdHeader;
-struct ImDrawCmdHeader {
- ImVec4 ClipRect;
- ImTextureID TextureId;
- unsigned int VtxOffset;
-};
-typedef struct ImVector_ImDrawCmd {
- int Size;
- int Capacity;
- ImDrawCmd* Data;
-} ImVector_ImDrawCmd;
-
-typedef struct ImVector_ImDrawIdx {
- int Size;
- int Capacity;
- ImDrawIdx* Data;
-} ImVector_ImDrawIdx;
-
-struct ImDrawChannel {
- ImVector_ImDrawCmd _CmdBuffer;
- ImVector_ImDrawIdx _IdxBuffer;
-};
-typedef struct ImVector_ImDrawChannel {
- int Size;
- int Capacity;
- ImDrawChannel* Data;
-} ImVector_ImDrawChannel;
-
-struct ImDrawListSplitter {
- int _Current;
- int _Count;
- ImVector_ImDrawChannel _Channels;
-};
-typedef enum {
- ImDrawFlags_None = 0,
- ImDrawFlags_Closed = 1 << 0,
- ImDrawFlags_RoundCornersTopLeft = 1 << 4,
- ImDrawFlags_RoundCornersTopRight = 1 << 5,
- ImDrawFlags_RoundCornersBottomLeft = 1 << 6,
- ImDrawFlags_RoundCornersBottomRight = 1 << 7,
- ImDrawFlags_RoundCornersNone = 1 << 8,
- ImDrawFlags_RoundCornersTop =
- ImDrawFlags_RoundCornersTopLeft | ImDrawFlags_RoundCornersTopRight,
- ImDrawFlags_RoundCornersBottom =
- ImDrawFlags_RoundCornersBottomLeft | ImDrawFlags_RoundCornersBottomRight,
- ImDrawFlags_RoundCornersLeft =
- ImDrawFlags_RoundCornersBottomLeft | ImDrawFlags_RoundCornersTopLeft,
- ImDrawFlags_RoundCornersRight =
- ImDrawFlags_RoundCornersBottomRight | ImDrawFlags_RoundCornersTopRight,
- ImDrawFlags_RoundCornersAll =
- ImDrawFlags_RoundCornersTopLeft | ImDrawFlags_RoundCornersTopRight |
- ImDrawFlags_RoundCornersBottomLeft | ImDrawFlags_RoundCornersBottomRight,
- ImDrawFlags_RoundCornersDefault_ = ImDrawFlags_RoundCornersAll,
- ImDrawFlags_RoundCornersMask_ =
- ImDrawFlags_RoundCornersAll | ImDrawFlags_RoundCornersNone,
-} ImDrawFlags_;
-typedef enum {
- ImDrawListFlags_None = 0,
- ImDrawListFlags_AntiAliasedLines = 1 << 0,
- ImDrawListFlags_AntiAliasedLinesUseTex = 1 << 1,
- ImDrawListFlags_AntiAliasedFill = 1 << 2,
- ImDrawListFlags_AllowVtxOffset = 1 << 3,
-} ImDrawListFlags_;
-typedef struct ImVector_ImDrawVert {
- int Size;
- int Capacity;
- ImDrawVert* Data;
-} ImVector_ImDrawVert;
-
-typedef struct ImVector_ImVec2 {
- int Size;
- int Capacity;
- ImVec2* Data;
-} ImVector_ImVec2;
-
-typedef struct ImVector_ImVec4 {
- int Size;
- int Capacity;
- ImVec4* Data;
-} ImVector_ImVec4;
-
-typedef struct ImVector_ImTextureID {
- int Size;
- int Capacity;
- ImTextureID* Data;
-} ImVector_ImTextureID;
-
-struct ImDrawList {
- ImVector_ImDrawCmd CmdBuffer;
- ImVector_ImDrawIdx IdxBuffer;
- ImVector_ImDrawVert VtxBuffer;
- ImDrawListFlags Flags;
- unsigned int _VtxCurrentIdx;
- ImDrawListSharedData* _Data;
- ImDrawVert* _VtxWritePtr;
- ImDrawIdx* _IdxWritePtr;
- ImVector_ImVec2 _Path;
- ImDrawCmdHeader _CmdHeader;
- ImDrawListSplitter _Splitter;
- ImVector_ImVec4 _ClipRectStack;
- ImVector_ImTextureID _TextureIdStack;
- float _FringeScale;
- const char* _OwnerName;
-};
-typedef struct ImVector_ImDrawListPtr {
- int Size;
- int Capacity;
- ImDrawList** Data;
-} ImVector_ImDrawListPtr;
-
-struct ImDrawData {
- bool Valid;
- int CmdListsCount;
- int TotalIdxCount;
- int TotalVtxCount;
- ImVector_ImDrawListPtr CmdLists;
- ImVec2 DisplayPos;
- ImVec2 DisplaySize;
- ImVec2 FramebufferScale;
- ImGuiViewport* OwnerViewport;
-};
-struct ImFontConfig {
- void* FontData;
- int FontDataSize;
- bool FontDataOwnedByAtlas;
- int FontNo;
- float SizePixels;
- int OversampleH;
- int OversampleV;
- bool PixelSnapH;
- ImVec2 GlyphExtraSpacing;
- ImVec2 GlyphOffset;
- const ImWchar* GlyphRanges;
- float GlyphMinAdvanceX;
- float GlyphMaxAdvanceX;
- bool MergeMode;
- unsigned int FontBuilderFlags;
- float RasterizerMultiply;
- float RasterizerDensity;
- ImWchar EllipsisChar;
- char Name[40];
- ImFont* DstFont;
-};
-struct ImFontGlyph {
- unsigned int Colored : 1;
- unsigned int Visible : 1;
- unsigned int Codepoint : 30;
- float AdvanceX;
- float X0, Y0, X1, Y1;
- float U0, V0, U1, V1;
-};
-typedef struct ImVector_ImU32 {
- int Size;
- int Capacity;
- ImU32* Data;
-} ImVector_ImU32;
-
-struct ImFontGlyphRangesBuilder {
- ImVector_ImU32 UsedChars;
-};
-typedef struct ImFontAtlasCustomRect ImFontAtlasCustomRect;
-struct ImFontAtlasCustomRect {
- unsigned short Width, Height;
- unsigned short X, Y;
- unsigned int GlyphID;
- float GlyphAdvanceX;
- ImVec2 GlyphOffset;
- ImFont* Font;
-};
-typedef enum {
- ImFontAtlasFlags_None = 0,
- ImFontAtlasFlags_NoPowerOfTwoHeight = 1 << 0,
- ImFontAtlasFlags_NoMouseCursors = 1 << 1,
- ImFontAtlasFlags_NoBakedLines = 1 << 2,
-} ImFontAtlasFlags_;
-typedef struct ImVector_ImFontPtr {
- int Size;
- int Capacity;
- ImFont** Data;
-} ImVector_ImFontPtr;
-
-typedef struct ImVector_ImFontAtlasCustomRect {
- int Size;
- int Capacity;
- ImFontAtlasCustomRect* Data;
-} ImVector_ImFontAtlasCustomRect;
-
-typedef struct ImVector_ImFontConfig {
- int Size;
- int Capacity;
- ImFontConfig* Data;
-} ImVector_ImFontConfig;
-
-struct ImFontAtlas {
- ImFontAtlasFlags Flags;
- ImTextureID TexID;
- int TexDesiredWidth;
- int TexGlyphPadding;
- bool Locked;
- void* UserData;
- bool TexReady;
- bool TexPixelsUseColors;
- unsigned char* TexPixelsAlpha8;
- unsigned int* TexPixelsRGBA32;
- int TexWidth;
- int TexHeight;
- ImVec2 TexUvScale;
- ImVec2 TexUvWhitePixel;
- ImVector_ImFontPtr Fonts;
- ImVector_ImFontAtlasCustomRect CustomRects;
- ImVector_ImFontConfig ConfigData;
- ImVec4 TexUvLines[(63) + 1];
- const ImFontBuilderIO* FontBuilderIO;
- unsigned int FontBuilderFlags;
- int PackIdMouseCursors;
- int PackIdLines;
-};
-typedef struct ImVector_float {
- int Size;
- int Capacity;
- float* Data;
-} ImVector_float;
-
-typedef struct ImVector_ImFontGlyph {
- int Size;
- int Capacity;
- ImFontGlyph* Data;
-} ImVector_ImFontGlyph;
-
-struct ImFont {
- ImVector_float IndexAdvanceX;
- float FallbackAdvanceX;
- float FontSize;
- ImVector_ImWchar IndexLookup;
- ImVector_ImFontGlyph Glyphs;
- const ImFontGlyph* FallbackGlyph;
- ImFontAtlas* ContainerAtlas;
- const ImFontConfig* ConfigData;
- short ConfigDataCount;
- ImWchar FallbackChar;
- ImWchar EllipsisChar;
- short EllipsisCharCount;
- float EllipsisWidth;
- float EllipsisCharStep;
- bool DirtyLookupTables;
- float Scale;
- float Ascent, Descent;
- int MetricsTotalSurface;
- ImU8 Used4kPagesMap[(0xFFFF + 1) / 4096 / 8];
-};
-typedef enum {
- ImGuiViewportFlags_None = 0,
- ImGuiViewportFlags_IsPlatformWindow = 1 << 0,
- ImGuiViewportFlags_IsPlatformMonitor = 1 << 1,
- ImGuiViewportFlags_OwnedByApp = 1 << 2,
- ImGuiViewportFlags_NoDecoration = 1 << 3,
- ImGuiViewportFlags_NoTaskBarIcon = 1 << 4,
- ImGuiViewportFlags_NoFocusOnAppearing = 1 << 5,
- ImGuiViewportFlags_NoFocusOnClick = 1 << 6,
- ImGuiViewportFlags_NoInputs = 1 << 7,
- ImGuiViewportFlags_NoRendererClear = 1 << 8,
- ImGuiViewportFlags_NoAutoMerge = 1 << 9,
- ImGuiViewportFlags_TopMost = 1 << 10,
- ImGuiViewportFlags_CanHostOtherWindows = 1 << 11,
- ImGuiViewportFlags_IsMinimized = 1 << 12,
- ImGuiViewportFlags_IsFocused = 1 << 13,
-} ImGuiViewportFlags_;
-struct ImGuiViewport {
- ImGuiID ID;
- ImGuiViewportFlags Flags;
- ImVec2 Pos;
- ImVec2 Size;
- ImVec2 WorkPos;
- ImVec2 WorkSize;
- float DpiScale;
- ImGuiID ParentViewportId;
- ImDrawData* DrawData;
- void* RendererUserData;
- void* PlatformUserData;
- void* PlatformHandle;
- void* PlatformHandleRaw;
- bool PlatformWindowCreated;
- bool PlatformRequestMove;
- bool PlatformRequestResize;
- bool PlatformRequestClose;
-};
-typedef struct ImVector_ImGuiPlatformMonitor {
- int Size;
- int Capacity;
- ImGuiPlatformMonitor* Data;
-} ImVector_ImGuiPlatformMonitor;
-
-typedef struct ImVector_ImGuiViewportPtr {
- int Size;
- int Capacity;
- ImGuiViewport** Data;
-} ImVector_ImGuiViewportPtr;
-
-struct ImGuiPlatformIO {
- void (*Platform_CreateWindow)(ImGuiViewport* vp);
- void (*Platform_DestroyWindow)(ImGuiViewport* vp);
- void (*Platform_ShowWindow)(ImGuiViewport* vp);
- void (*Platform_SetWindowPos)(ImGuiViewport* vp, ImVec2 pos);
- ImVec2 (*Platform_GetWindowPos)(ImGuiViewport* vp);
- void (*Platform_SetWindowSize)(ImGuiViewport* vp, ImVec2 size);
- ImVec2 (*Platform_GetWindowSize)(ImGuiViewport* vp);
- void (*Platform_SetWindowFocus)(ImGuiViewport* vp);
- bool (*Platform_GetWindowFocus)(ImGuiViewport* vp);
- bool (*Platform_GetWindowMinimized)(ImGuiViewport* vp);
- void (*Platform_SetWindowTitle)(ImGuiViewport* vp, const char* str);
- void (*Platform_SetWindowAlpha)(ImGuiViewport* vp, float alpha);
- void (*Platform_UpdateWindow)(ImGuiViewport* vp);
- void (*Platform_RenderWindow)(ImGuiViewport* vp, void* render_arg);
- void (*Platform_SwapBuffers)(ImGuiViewport* vp, void* render_arg);
- float (*Platform_GetWindowDpiScale)(ImGuiViewport* vp);
- void (*Platform_OnChangedViewport)(ImGuiViewport* vp);
- int (*Platform_CreateVkSurface)(ImGuiViewport* vp,
- ImU64 vk_inst,
- const void* vk_allocators,
- ImU64* out_vk_surface);
- void (*Renderer_CreateWindow)(ImGuiViewport* vp);
- void (*Renderer_DestroyWindow)(ImGuiViewport* vp);
- void (*Renderer_SetWindowSize)(ImGuiViewport* vp, ImVec2 size);
- void (*Renderer_RenderWindow)(ImGuiViewport* vp, void* render_arg);
- void (*Renderer_SwapBuffers)(ImGuiViewport* vp, void* render_arg);
- ImVector_ImGuiPlatformMonitor Monitors;
- ImVector_ImGuiViewportPtr Viewports;
-};
-struct ImGuiPlatformMonitor {
- ImVec2 MainPos, MainSize;
- ImVec2 WorkPos, WorkSize;
- float DpiScale;
- void* PlatformHandle;
-};
-struct ImGuiPlatformImeData {
- bool WantVisible;
- ImVec2 InputPos;
- float InputLineHeight;
-};
-struct ImBitVector;
-struct ImRect;
-struct ImDrawDataBuilder;
-struct ImDrawListSharedData;
-struct ImGuiColorMod;
-struct ImGuiContext;
-struct ImGuiContextHook;
-struct ImGuiDataVarInfo;
-struct ImGuiDataTypeInfo;
-struct ImGuiDockContext;
-struct ImGuiDockRequest;
-struct ImGuiDockNode;
-struct ImGuiDockNodeSettings;
-struct ImGuiGroupData;
-struct ImGuiInputTextState;
-struct ImGuiInputTextDeactivateData;
-struct ImGuiLastItemData;
-struct ImGuiLocEntry;
-struct ImGuiMenuColumns;
-struct ImGuiNavItemData;
-struct ImGuiNavTreeNodeData;
-struct ImGuiMetricsConfig;
-struct ImGuiNextWindowData;
-struct ImGuiNextItemData;
-struct ImGuiOldColumnData;
-struct ImGuiOldColumns;
-struct ImGuiPopupData;
-struct ImGuiSettingsHandler;
-struct ImGuiStackSizes;
-struct ImGuiStyleMod;
-struct ImGuiTabBar;
-struct ImGuiTabItem;
-struct ImGuiTable;
-struct ImGuiTableHeaderData;
-struct ImGuiTableColumn;
-struct ImGuiTableInstanceData;
-struct ImGuiTableTempData;
-struct ImGuiTableSettings;
-struct ImGuiTableColumnsSettings;
-struct ImGuiTypingSelectState;
-struct ImGuiTypingSelectRequest;
-struct ImGuiWindow;
-struct ImGuiWindowDockStyle;
-struct ImGuiWindowTempData;
-struct ImGuiWindowSettings;
-typedef int ImGuiDataAuthority;
-typedef int ImGuiLayoutType;
-typedef int ImGuiActivateFlags;
-typedef int ImGuiDebugLogFlags;
-typedef int ImGuiFocusRequestFlags;
-typedef int ImGuiInputFlags;
-typedef int ImGuiItemFlags;
-typedef int ImGuiItemStatusFlags;
-typedef int ImGuiOldColumnFlags;
-typedef int ImGuiNavHighlightFlags;
-typedef int ImGuiNavMoveFlags;
-typedef int ImGuiNextItemDataFlags;
-typedef int ImGuiNextWindowDataFlags;
-typedef int ImGuiScrollFlags;
-typedef int ImGuiSeparatorFlags;
-typedef int ImGuiTextFlags;
-typedef int ImGuiTooltipFlags;
-typedef int ImGuiTypingSelectFlags;
-typedef int ImGuiWindowRefreshFlags;
-typedef void (*ImGuiErrorLogCallback)(void* user_data, const char* fmt, ...);
-extern ImGuiContext* GImGui;
-typedef struct StbUndoRecord StbUndoRecord;
-struct StbUndoRecord {
- int where;
- int insert_length;
- int delete_length;
- int char_storage;
-};
-typedef struct StbUndoState StbUndoState;
-struct StbUndoState {
- StbUndoRecord undo_rec[99];
- ImWchar undo_char[999];
- short undo_point, redo_point;
- int undo_char_point, redo_char_point;
-};
-typedef struct STB_TexteditState STB_TexteditState;
-struct STB_TexteditState {
- int cursor;
- int select_start;
- int select_end;
- unsigned char insert_mode;
- int row_count_per_page;
- unsigned char cursor_at_end_of_line;
- unsigned char initialized;
- unsigned char has_preferred_x;
- unsigned char single_line;
- unsigned char padding1, padding2, padding3;
- float preferred_x;
- StbUndoState undostate;
-};
-typedef struct StbTexteditRow StbTexteditRow;
-struct StbTexteditRow {
- float x0, x1;
- float baseline_y_delta;
- float ymin, ymax;
- int num_chars;
-};
-typedef FILE* ImFileHandle;
-typedef struct ImVec1 ImVec1;
-struct ImVec1 {
- float x;
-};
-typedef struct ImVec2ih ImVec2ih;
-struct ImVec2ih {
- short x, y;
-};
-struct ImRect {
- ImVec2 Min;
- ImVec2 Max;
-};
-typedef ImU32* ImBitArrayPtr;
-struct ImBitVector {
- ImVector_ImU32 Storage;
-};
-typedef int ImPoolIdx;
-typedef struct ImGuiTextIndex ImGuiTextIndex;
-typedef struct ImVector_int {
- int Size;
- int Capacity;
- int* Data;
-} ImVector_int;
-
-struct ImGuiTextIndex {
- ImVector_int LineOffsets;
- int EndOffset;
-};
-struct ImDrawListSharedData {
- ImVec2 TexUvWhitePixel;
- ImFont* Font;
- float FontSize;
- float CurveTessellationTol;
- float CircleSegmentMaxError;
- ImVec4 ClipRectFullscreen;
- ImDrawListFlags InitialFlags;
- ImVector_ImVec2 TempBuffer;
- ImVec2 ArcFastVtx[48];
- float ArcFastRadiusCutoff;
- ImU8 CircleSegmentCounts[64];
- const ImVec4* TexUvLines;
-};
-struct ImDrawDataBuilder {
- ImVector_ImDrawListPtr* Layers[2];
- ImVector_ImDrawListPtr LayerData1;
-};
-typedef enum {
- ImGuiItemFlags_None = 0,
- ImGuiItemFlags_NoTabStop = 1 << 0,
- ImGuiItemFlags_ButtonRepeat = 1 << 1,
- ImGuiItemFlags_Disabled = 1 << 2,
- ImGuiItemFlags_NoNav = 1 << 3,
- ImGuiItemFlags_NoNavDefaultFocus = 1 << 4,
- ImGuiItemFlags_SelectableDontClosePopup = 1 << 5,
- ImGuiItemFlags_MixedValue = 1 << 6,
- ImGuiItemFlags_ReadOnly = 1 << 7,
- ImGuiItemFlags_NoWindowHoverableCheck = 1 << 8,
- ImGuiItemFlags_AllowOverlap = 1 << 9,
- ImGuiItemFlags_Inputable = 1 << 10,
- ImGuiItemFlags_HasSelectionUserData = 1 << 11,
-} ImGuiItemFlags_;
-typedef enum {
- ImGuiItemStatusFlags_None = 0,
- ImGuiItemStatusFlags_HoveredRect = 1 << 0,
- ImGuiItemStatusFlags_HasDisplayRect = 1 << 1,
- ImGuiItemStatusFlags_Edited = 1 << 2,
- ImGuiItemStatusFlags_ToggledSelection = 1 << 3,
- ImGuiItemStatusFlags_ToggledOpen = 1 << 4,
- ImGuiItemStatusFlags_HasDeactivated = 1 << 5,
- ImGuiItemStatusFlags_Deactivated = 1 << 6,
- ImGuiItemStatusFlags_HoveredWindow = 1 << 7,
- ImGuiItemStatusFlags_Visible = 1 << 8,
- ImGuiItemStatusFlags_HasClipRect = 1 << 9,
-} ImGuiItemStatusFlags_;
-typedef enum {
- ImGuiHoveredFlags_DelayMask_ =
- ImGuiHoveredFlags_DelayNone | ImGuiHoveredFlags_DelayShort |
- ImGuiHoveredFlags_DelayNormal | ImGuiHoveredFlags_NoSharedDelay,
- ImGuiHoveredFlags_AllowedMaskForIsWindowHovered =
- ImGuiHoveredFlags_ChildWindows | ImGuiHoveredFlags_RootWindow |
- ImGuiHoveredFlags_AnyWindow | ImGuiHoveredFlags_NoPopupHierarchy |
- ImGuiHoveredFlags_DockHierarchy |
- ImGuiHoveredFlags_AllowWhenBlockedByPopup |
- ImGuiHoveredFlags_AllowWhenBlockedByActiveItem |
- ImGuiHoveredFlags_ForTooltip | ImGuiHoveredFlags_Stationary,
- ImGuiHoveredFlags_AllowedMaskForIsItemHovered =
- ImGuiHoveredFlags_AllowWhenBlockedByPopup |
- ImGuiHoveredFlags_AllowWhenBlockedByActiveItem |
- ImGuiHoveredFlags_AllowWhenOverlapped |
- ImGuiHoveredFlags_AllowWhenDisabled | ImGuiHoveredFlags_NoNavOverride |
- ImGuiHoveredFlags_ForTooltip | ImGuiHoveredFlags_Stationary |
- ImGuiHoveredFlags_DelayMask_,
-} ImGuiHoveredFlagsPrivate_;
-typedef enum {
- ImGuiInputTextFlags_Multiline = 1 << 26,
- ImGuiInputTextFlags_NoMarkEdited = 1 << 27,
- ImGuiInputTextFlags_MergedItem = 1 << 28,
- ImGuiInputTextFlags_LocalizeDecimalPoint = 1 << 29,
-} ImGuiInputTextFlagsPrivate_;
-typedef enum {
- ImGuiButtonFlags_PressedOnClick = 1 << 4,
- ImGuiButtonFlags_PressedOnClickRelease = 1 << 5,
- ImGuiButtonFlags_PressedOnClickReleaseAnywhere = 1 << 6,
- ImGuiButtonFlags_PressedOnRelease = 1 << 7,
- ImGuiButtonFlags_PressedOnDoubleClick = 1 << 8,
- ImGuiButtonFlags_PressedOnDragDropHold = 1 << 9,
- ImGuiButtonFlags_Repeat = 1 << 10,
- ImGuiButtonFlags_FlattenChildren = 1 << 11,
- ImGuiButtonFlags_AllowOverlap = 1 << 12,
- ImGuiButtonFlags_DontClosePopups = 1 << 13,
- ImGuiButtonFlags_AlignTextBaseLine = 1 << 15,
- ImGuiButtonFlags_NoKeyModifiers = 1 << 16,
- ImGuiButtonFlags_NoHoldingActiveId = 1 << 17,
- ImGuiButtonFlags_NoNavFocus = 1 << 18,
- ImGuiButtonFlags_NoHoveredOnFocus = 1 << 19,
- ImGuiButtonFlags_NoSetKeyOwner = 1 << 20,
- ImGuiButtonFlags_NoTestKeyOwner = 1 << 21,
- ImGuiButtonFlags_PressedOnMask_ =
- ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_PressedOnClickRelease |
- ImGuiButtonFlags_PressedOnClickReleaseAnywhere |
- ImGuiButtonFlags_PressedOnRelease |
- ImGuiButtonFlags_PressedOnDoubleClick |
- ImGuiButtonFlags_PressedOnDragDropHold,
- ImGuiButtonFlags_PressedOnDefault_ = ImGuiButtonFlags_PressedOnClickRelease,
-} ImGuiButtonFlagsPrivate_;
-typedef enum {
- ImGuiComboFlags_CustomPreview = 1 << 20,
-} ImGuiComboFlagsPrivate_;
-typedef enum {
- ImGuiSliderFlags_Vertical = 1 << 20,
- ImGuiSliderFlags_ReadOnly = 1 << 21,
-} ImGuiSliderFlagsPrivate_;
-typedef enum {
- ImGuiSelectableFlags_NoHoldingActiveID = 1 << 20,
- ImGuiSelectableFlags_SelectOnNav = 1 << 21,
- ImGuiSelectableFlags_SelectOnClick = 1 << 22,
- ImGuiSelectableFlags_SelectOnRelease = 1 << 23,
- ImGuiSelectableFlags_SpanAvailWidth = 1 << 24,
- ImGuiSelectableFlags_SetNavIdOnHover = 1 << 25,
- ImGuiSelectableFlags_NoPadWithHalfSpacing = 1 << 26,
- ImGuiSelectableFlags_NoSetKeyOwner = 1 << 27,
-} ImGuiSelectableFlagsPrivate_;
-typedef enum {
- ImGuiTreeNodeFlags_ClipLabelForTrailingButton = 1 << 20,
- ImGuiTreeNodeFlags_UpsideDownArrow = 1 << 21,
-} ImGuiTreeNodeFlagsPrivate_;
-typedef enum {
- ImGuiSeparatorFlags_None = 0,
- ImGuiSeparatorFlags_Horizontal = 1 << 0,
- ImGuiSeparatorFlags_Vertical = 1 << 1,
- ImGuiSeparatorFlags_SpanAllColumns = 1 << 2,
-} ImGuiSeparatorFlags_;
-typedef enum {
- ImGuiFocusRequestFlags_None = 0,
- ImGuiFocusRequestFlags_RestoreFocusedChild = 1 << 0,
- ImGuiFocusRequestFlags_UnlessBelowModal = 1 << 1,
-} ImGuiFocusRequestFlags_;
-typedef enum {
- ImGuiTextFlags_None = 0,
- ImGuiTextFlags_NoWidthForLargeClippedText = 1 << 0,
-} ImGuiTextFlags_;
-typedef enum {
- ImGuiTooltipFlags_None = 0,
- ImGuiTooltipFlags_OverridePrevious = 1 << 1,
-} ImGuiTooltipFlags_;
-typedef enum {
- ImGuiLayoutType_Horizontal = 0,
- ImGuiLayoutType_Vertical = 1
-} ImGuiLayoutType_;
-typedef enum {
- ImGuiLogType_None = 0,
- ImGuiLogType_TTY,
- ImGuiLogType_File,
- ImGuiLogType_Buffer,
- ImGuiLogType_Clipboard,
-} ImGuiLogType;
-typedef enum {
- ImGuiAxis_None = -1,
- ImGuiAxis_X = 0,
- ImGuiAxis_Y = 1
-} ImGuiAxis;
-typedef enum {
- ImGuiPlotType_Lines,
- ImGuiPlotType_Histogram,
-} ImGuiPlotType;
-struct ImGuiColorMod {
- ImGuiCol Col;
- ImVec4 BackupValue;
-};
-struct ImGuiStyleMod {
- ImGuiStyleVar VarIdx;
- union {
- int BackupInt[2];
- float BackupFloat[2];
- };
-};
-typedef struct ImGuiComboPreviewData ImGuiComboPreviewData;
-struct ImGuiComboPreviewData {
- ImRect PreviewRect;
- ImVec2 BackupCursorPos;
- ImVec2 BackupCursorMaxPos;
- ImVec2 BackupCursorPosPrevLine;
- float BackupPrevLineTextBaseOffset;
- ImGuiLayoutType BackupLayout;
-};
-struct ImGuiGroupData {
- ImGuiID WindowID;
- ImVec2 BackupCursorPos;
- ImVec2 BackupCursorMaxPos;
- ImVec2 BackupCursorPosPrevLine;
- ImVec1 BackupIndent;
- ImVec1 BackupGroupOffset;
- ImVec2 BackupCurrLineSize;
- float BackupCurrLineTextBaseOffset;
- ImGuiID BackupActiveIdIsAlive;
- bool BackupActiveIdPreviousFrameIsAlive;
- bool BackupHoveredIdIsAlive;
- bool BackupIsSameLine;
- bool EmitItem;
-};
-struct ImGuiMenuColumns {
- ImU32 TotalWidth;
- ImU32 NextTotalWidth;
- ImU16 Spacing;
- ImU16 OffsetIcon;
- ImU16 OffsetLabel;
- ImU16 OffsetShortcut;
- ImU16 OffsetMark;
- ImU16 Widths[4];
-};
-typedef struct ImGuiInputTextDeactivatedState ImGuiInputTextDeactivatedState;
-struct ImGuiInputTextDeactivatedState {
- ImGuiID ID;
- ImVector_char TextA;
-};
-struct ImGuiInputTextState {
- ImGuiContext* Ctx;
- ImGuiID ID;
- int CurLenW, CurLenA;
- ImVector_ImWchar TextW;
- ImVector_char TextA;
- ImVector_char InitialTextA;
- bool TextAIsValid;
- int BufCapacityA;
- float ScrollX;
- STB_TexteditState Stb;
- float CursorAnim;
- bool CursorFollow;
- bool SelectedAllMouseLock;
- bool Edited;
- ImGuiInputTextFlags Flags;
- bool ReloadUserBuf;
- int ReloadSelectionStart;
- int ReloadSelectionEnd;
-};
-typedef enum {
- ImGuiWindowRefreshFlags_None = 0,
- ImGuiWindowRefreshFlags_TryToAvoidRefresh = 1 << 0,
- ImGuiWindowRefreshFlags_RefreshOnHover = 1 << 1,
- ImGuiWindowRefreshFlags_RefreshOnFocus = 1 << 2,
-} ImGuiWindowRefreshFlags_;
-typedef enum {
- ImGuiNextWindowDataFlags_None = 0,
- ImGuiNextWindowDataFlags_HasPos = 1 << 0,
- ImGuiNextWindowDataFlags_HasSize = 1 << 1,
- ImGuiNextWindowDataFlags_HasContentSize = 1 << 2,
- ImGuiNextWindowDataFlags_HasCollapsed = 1 << 3,
- ImGuiNextWindowDataFlags_HasSizeConstraint = 1 << 4,
- ImGuiNextWindowDataFlags_HasFocus = 1 << 5,
- ImGuiNextWindowDataFlags_HasBgAlpha = 1 << 6,
- ImGuiNextWindowDataFlags_HasScroll = 1 << 7,
- ImGuiNextWindowDataFlags_HasChildFlags = 1 << 8,
- ImGuiNextWindowDataFlags_HasRefreshPolicy = 1 << 9,
- ImGuiNextWindowDataFlags_HasViewport = 1 << 10,
- ImGuiNextWindowDataFlags_HasDock = 1 << 11,
- ImGuiNextWindowDataFlags_HasWindowClass = 1 << 12,
-} ImGuiNextWindowDataFlags_;
-struct ImGuiNextWindowData {
- ImGuiNextWindowDataFlags Flags;
- ImGuiCond PosCond;
- ImGuiCond SizeCond;
- ImGuiCond CollapsedCond;
- ImGuiCond DockCond;
- ImVec2 PosVal;
- ImVec2 PosPivotVal;
- ImVec2 SizeVal;
- ImVec2 ContentSizeVal;
- ImVec2 ScrollVal;
- ImGuiChildFlags ChildFlags;
- bool PosUndock;
- bool CollapsedVal;
- ImRect SizeConstraintRect;
- ImGuiSizeCallback SizeCallback;
- void* SizeCallbackUserData;
- float BgAlphaVal;
- ImGuiID ViewportId;
- ImGuiID DockId;
- ImGuiWindowClass WindowClass;
- ImVec2 MenuBarOffsetMinVal;
- ImGuiWindowRefreshFlags RefreshFlagsVal;
-};
-typedef ImS64 ImGuiSelectionUserData;
-typedef enum {
- ImGuiNextItemDataFlags_None = 0,
- ImGuiNextItemDataFlags_HasWidth = 1 << 0,
- ImGuiNextItemDataFlags_HasOpen = 1 << 1,
- ImGuiNextItemDataFlags_HasShortcut = 1 << 2,
-} ImGuiNextItemDataFlags_;
-struct ImGuiNextItemData {
- ImGuiNextItemDataFlags Flags;
- ImGuiItemFlags ItemFlags;
- ImGuiSelectionUserData SelectionUserData;
- float Width;
- ImGuiKeyChord Shortcut;
- bool OpenVal;
- ImGuiCond OpenCond : 8;
-};
-struct ImGuiLastItemData {
- ImGuiID ID;
- ImGuiItemFlags InFlags;
- ImGuiItemStatusFlags StatusFlags;
- ImRect Rect;
- ImRect NavRect;
- ImRect DisplayRect;
- ImRect ClipRect;
-};
-struct ImGuiNavTreeNodeData {
- ImGuiID ID;
- ImGuiItemFlags InFlags;
- ImRect NavRect;
-};
-struct ImGuiStackSizes {
- short SizeOfIDStack;
- short SizeOfColorStack;
- short SizeOfStyleVarStack;
- short SizeOfFontStack;
- short SizeOfFocusScopeStack;
- short SizeOfGroupStack;
- short SizeOfItemFlagsStack;
- short SizeOfBeginPopupStack;
- short SizeOfDisabledStack;
-};
-typedef struct ImGuiWindowStackData ImGuiWindowStackData;
-struct ImGuiWindowStackData {
- ImGuiWindow* Window;
- ImGuiLastItemData ParentLastItemDataBackup;
- ImGuiStackSizes StackSizesOnBegin;
-};
-typedef struct ImGuiShrinkWidthItem ImGuiShrinkWidthItem;
-struct ImGuiShrinkWidthItem {
- int Index;
- float Width;
- float InitialWidth;
-};
-typedef struct ImGuiPtrOrIndex ImGuiPtrOrIndex;
-struct ImGuiPtrOrIndex {
- void* Ptr;
- int Index;
-};
-struct ImGuiDataVarInfo {
- ImGuiDataType Type;
- ImU32 Count;
- ImU32 Offset;
-};
-typedef struct ImGuiDataTypeTempStorage ImGuiDataTypeTempStorage;
-struct ImGuiDataTypeTempStorage {
- ImU8 Data[8];
-};
-struct ImGuiDataTypeInfo {
- size_t Size;
- const char* Name;
- const char* PrintFmt;
- const char* ScanFmt;
-};
-typedef enum {
- ImGuiDataType_String = ImGuiDataType_COUNT + 1,
- ImGuiDataType_Pointer,
- ImGuiDataType_ID,
-} ImGuiDataTypePrivate_;
-typedef enum {
- ImGuiPopupPositionPolicy_Default,
- ImGuiPopupPositionPolicy_ComboBox,
- ImGuiPopupPositionPolicy_Tooltip,
-} ImGuiPopupPositionPolicy;
-struct ImGuiPopupData {
- ImGuiID PopupId;
- ImGuiWindow* Window;
- ImGuiWindow* RestoreNavWindow;
- int ParentNavLayer;
- int OpenFrameCount;
- ImGuiID OpenParentId;
- ImVec2 OpenPopupPos;
- ImVec2 OpenMousePos;
-};
-typedef struct ImBitArray_ImGuiKey_NamedKey_COUNT__lessImGuiKey_NamedKey_BEGIN {
- ImU32 Storage[(ImGuiKey_NamedKey_COUNT + 31) >> 5];
-} ImBitArray_ImGuiKey_NamedKey_COUNT__lessImGuiKey_NamedKey_BEGIN;
-
-typedef ImBitArray_ImGuiKey_NamedKey_COUNT__lessImGuiKey_NamedKey_BEGIN
- ImBitArrayForNamedKeys;
-typedef enum {
- ImGuiInputEventType_None = 0,
- ImGuiInputEventType_MousePos,
- ImGuiInputEventType_MouseWheel,
- ImGuiInputEventType_MouseButton,
- ImGuiInputEventType_MouseViewport,
- ImGuiInputEventType_Key,
- ImGuiInputEventType_Text,
- ImGuiInputEventType_Focus,
- ImGuiInputEventType_COUNT
-} ImGuiInputEventType;
-typedef enum {
- ImGuiInputSource_None = 0,
- ImGuiInputSource_Mouse,
- ImGuiInputSource_Keyboard,
- ImGuiInputSource_Gamepad,
- ImGuiInputSource_COUNT
-} ImGuiInputSource;
-typedef struct ImGuiInputEventMousePos ImGuiInputEventMousePos;
-struct ImGuiInputEventMousePos {
- float PosX, PosY;
- ImGuiMouseSource MouseSource;
-};
-typedef struct ImGuiInputEventMouseWheel ImGuiInputEventMouseWheel;
-struct ImGuiInputEventMouseWheel {
- float WheelX, WheelY;
- ImGuiMouseSource MouseSource;
-};
-typedef struct ImGuiInputEventMouseButton ImGuiInputEventMouseButton;
-struct ImGuiInputEventMouseButton {
- int Button;
- bool Down;
- ImGuiMouseSource MouseSource;
-};
-typedef struct ImGuiInputEventMouseViewport ImGuiInputEventMouseViewport;
-struct ImGuiInputEventMouseViewport {
- ImGuiID HoveredViewportID;
-};
-typedef struct ImGuiInputEventKey ImGuiInputEventKey;
-struct ImGuiInputEventKey {
- ImGuiKey Key;
- bool Down;
- float AnalogValue;
-};
-typedef struct ImGuiInputEventText ImGuiInputEventText;
-struct ImGuiInputEventText {
- unsigned int Char;
-};
-typedef struct ImGuiInputEventAppFocused ImGuiInputEventAppFocused;
-struct ImGuiInputEventAppFocused {
- bool Focused;
-};
-typedef struct ImGuiInputEvent ImGuiInputEvent;
-struct ImGuiInputEvent {
- ImGuiInputEventType Type;
- ImGuiInputSource Source;
- ImU32 EventId;
- union {
- ImGuiInputEventMousePos MousePos;
- ImGuiInputEventMouseWheel MouseWheel;
- ImGuiInputEventMouseButton MouseButton;
- ImGuiInputEventMouseViewport MouseViewport;
- ImGuiInputEventKey Key;
- ImGuiInputEventText Text;
- ImGuiInputEventAppFocused AppFocused;
- };
- bool AddedByTestEngine;
-};
-typedef ImS16 ImGuiKeyRoutingIndex;
-typedef struct ImGuiKeyRoutingData ImGuiKeyRoutingData;
-struct ImGuiKeyRoutingData {
- ImGuiKeyRoutingIndex NextEntryIndex;
- ImU16 Mods;
- ImU8 RoutingCurrScore;
- ImU8 RoutingNextScore;
- ImGuiID RoutingCurr;
- ImGuiID RoutingNext;
-};
-typedef struct ImGuiKeyRoutingTable ImGuiKeyRoutingTable;
-typedef struct ImVector_ImGuiKeyRoutingData {
- int Size;
- int Capacity;
- ImGuiKeyRoutingData* Data;
-} ImVector_ImGuiKeyRoutingData;
-
-struct ImGuiKeyRoutingTable {
- ImGuiKeyRoutingIndex Index[ImGuiKey_NamedKey_COUNT];
- ImVector_ImGuiKeyRoutingData Entries;
- ImVector_ImGuiKeyRoutingData EntriesNext;
-};
-typedef struct ImGuiKeyOwnerData ImGuiKeyOwnerData;
-struct ImGuiKeyOwnerData {
- ImGuiID OwnerCurr;
- ImGuiID OwnerNext;
- bool LockThisFrame;
- bool LockUntilRelease;
-};
-typedef enum {
- ImGuiInputFlags_None = 0,
- ImGuiInputFlags_Repeat = 1 << 0,
- ImGuiInputFlags_RepeatRateDefault = 1 << 1,
- ImGuiInputFlags_RepeatRateNavMove = 1 << 2,
- ImGuiInputFlags_RepeatRateNavTweak = 1 << 3,
- ImGuiInputFlags_RepeatUntilRelease = 1 << 4,
- ImGuiInputFlags_RepeatUntilKeyModsChange = 1 << 5,
- ImGuiInputFlags_RepeatUntilKeyModsChangeFromNone = 1 << 6,
- ImGuiInputFlags_RepeatUntilOtherKeyPress = 1 << 7,
- ImGuiInputFlags_CondHovered = 1 << 8,
- ImGuiInputFlags_CondActive = 1 << 9,
- ImGuiInputFlags_CondDefault_ =
- ImGuiInputFlags_CondHovered | ImGuiInputFlags_CondActive,
- ImGuiInputFlags_LockThisFrame = 1 << 10,
- ImGuiInputFlags_LockUntilRelease = 1 << 11,
- ImGuiInputFlags_RouteFocused = 1 << 12,
- ImGuiInputFlags_RouteGlobalLow = 1 << 13,
- ImGuiInputFlags_RouteGlobal = 1 << 14,
- ImGuiInputFlags_RouteGlobalHigh = 1 << 15,
- ImGuiInputFlags_RouteAlways = 1 << 16,
- ImGuiInputFlags_RouteUnlessBgFocused = 1 << 17,
- ImGuiInputFlags_RepeatRateMask_ = ImGuiInputFlags_RepeatRateDefault |
- ImGuiInputFlags_RepeatRateNavMove |
- ImGuiInputFlags_RepeatRateNavTweak,
- ImGuiInputFlags_RepeatUntilMask_ =
- ImGuiInputFlags_RepeatUntilRelease |
- ImGuiInputFlags_RepeatUntilKeyModsChange |
- ImGuiInputFlags_RepeatUntilKeyModsChangeFromNone |
- ImGuiInputFlags_RepeatUntilOtherKeyPress,
- ImGuiInputFlags_RepeatMask_ = ImGuiInputFlags_Repeat |
- ImGuiInputFlags_RepeatRateMask_ |
- ImGuiInputFlags_RepeatUntilMask_,
- ImGuiInputFlags_CondMask_ =
- ImGuiInputFlags_CondHovered | ImGuiInputFlags_CondActive,
- ImGuiInputFlags_RouteMask_ =
- ImGuiInputFlags_RouteFocused | ImGuiInputFlags_RouteGlobal |
- ImGuiInputFlags_RouteGlobalLow | ImGuiInputFlags_RouteGlobalHigh,
- ImGuiInputFlags_SupportedByIsKeyPressed = ImGuiInputFlags_RepeatMask_,
- ImGuiInputFlags_SupportedByIsMouseClicked = ImGuiInputFlags_Repeat,
- ImGuiInputFlags_SupportedByShortcut =
- ImGuiInputFlags_RepeatMask_ | ImGuiInputFlags_RouteMask_ |
- ImGuiInputFlags_RouteAlways | ImGuiInputFlags_RouteUnlessBgFocused,
- ImGuiInputFlags_SupportedBySetKeyOwner =
- ImGuiInputFlags_LockThisFrame | ImGuiInputFlags_LockUntilRelease,
- ImGuiInputFlags_SupportedBySetItemKeyOwner =
- ImGuiInputFlags_SupportedBySetKeyOwner | ImGuiInputFlags_CondMask_,
-} ImGuiInputFlags_;
-typedef struct ImGuiListClipperRange ImGuiListClipperRange;
-struct ImGuiListClipperRange {
- int Min;
- int Max;
- bool PosToIndexConvert;
- ImS8 PosToIndexOffsetMin;
- ImS8 PosToIndexOffsetMax;
-};
-typedef struct ImGuiListClipperData ImGuiListClipperData;
-typedef struct ImVector_ImGuiListClipperRange {
- int Size;
- int Capacity;
- ImGuiListClipperRange* Data;
-} ImVector_ImGuiListClipperRange;
-
-struct ImGuiListClipperData {
- ImGuiListClipper* ListClipper;
- float LossynessOffset;
- int StepNo;
- int ItemsFrozen;
- ImVector_ImGuiListClipperRange Ranges;
-};
-typedef enum {
- ImGuiActivateFlags_None = 0,
- ImGuiActivateFlags_PreferInput = 1 << 0,
- ImGuiActivateFlags_PreferTweak = 1 << 1,
- ImGuiActivateFlags_TryToPreserveState = 1 << 2,
- ImGuiActivateFlags_FromTabbing = 1 << 3,
- ImGuiActivateFlags_FromShortcut = 1 << 4,
-} ImGuiActivateFlags_;
-typedef enum {
- ImGuiScrollFlags_None = 0,
- ImGuiScrollFlags_KeepVisibleEdgeX = 1 << 0,
- ImGuiScrollFlags_KeepVisibleEdgeY = 1 << 1,
- ImGuiScrollFlags_KeepVisibleCenterX = 1 << 2,
- ImGuiScrollFlags_KeepVisibleCenterY = 1 << 3,
- ImGuiScrollFlags_AlwaysCenterX = 1 << 4,
- ImGuiScrollFlags_AlwaysCenterY = 1 << 5,
- ImGuiScrollFlags_NoScrollParent = 1 << 6,
- ImGuiScrollFlags_MaskX_ = ImGuiScrollFlags_KeepVisibleEdgeX |
- ImGuiScrollFlags_KeepVisibleCenterX |
- ImGuiScrollFlags_AlwaysCenterX,
- ImGuiScrollFlags_MaskY_ = ImGuiScrollFlags_KeepVisibleEdgeY |
- ImGuiScrollFlags_KeepVisibleCenterY |
- ImGuiScrollFlags_AlwaysCenterY,
-} ImGuiScrollFlags_;
-typedef enum {
- ImGuiNavHighlightFlags_None = 0,
- ImGuiNavHighlightFlags_Compact = 1 << 1,
- ImGuiNavHighlightFlags_AlwaysDraw = 1 << 2,
- ImGuiNavHighlightFlags_NoRounding = 1 << 3,
-} ImGuiNavHighlightFlags_;
-typedef enum {
- ImGuiNavMoveFlags_None = 0,
- ImGuiNavMoveFlags_LoopX = 1 << 0,
- ImGuiNavMoveFlags_LoopY = 1 << 1,
- ImGuiNavMoveFlags_WrapX = 1 << 2,
- ImGuiNavMoveFlags_WrapY = 1 << 3,
- ImGuiNavMoveFlags_WrapMask_ =
- ImGuiNavMoveFlags_LoopX | ImGuiNavMoveFlags_LoopY |
- ImGuiNavMoveFlags_WrapX | ImGuiNavMoveFlags_WrapY,
- ImGuiNavMoveFlags_AllowCurrentNavId = 1 << 4,
- ImGuiNavMoveFlags_AlsoScoreVisibleSet = 1 << 5,
- ImGuiNavMoveFlags_ScrollToEdgeY = 1 << 6,
- ImGuiNavMoveFlags_Forwarded = 1 << 7,
- ImGuiNavMoveFlags_DebugNoResult = 1 << 8,
- ImGuiNavMoveFlags_FocusApi = 1 << 9,
- ImGuiNavMoveFlags_IsTabbing = 1 << 10,
- ImGuiNavMoveFlags_IsPageMove = 1 << 11,
- ImGuiNavMoveFlags_Activate = 1 << 12,
- ImGuiNavMoveFlags_NoSelect = 1 << 13,
- ImGuiNavMoveFlags_NoSetNavHighlight = 1 << 14,
- ImGuiNavMoveFlags_NoClearActiveId = 1 << 15,
-} ImGuiNavMoveFlags_;
-typedef enum {
- ImGuiNavLayer_Main = 0,
- ImGuiNavLayer_Menu = 1,
- ImGuiNavLayer_COUNT
-} ImGuiNavLayer;
-struct ImGuiNavItemData {
- ImGuiWindow* Window;
- ImGuiID ID;
- ImGuiID FocusScopeId;
- ImRect RectRel;
- ImGuiItemFlags InFlags;
- float DistBox;
- float DistCenter;
- float DistAxial;
- ImGuiSelectionUserData SelectionUserData;
-};
-typedef struct ImGuiFocusScopeData ImGuiFocusScopeData;
-struct ImGuiFocusScopeData {
- ImGuiID ID;
- ImGuiID WindowID;
-};
-typedef enum {
- ImGuiTypingSelectFlags_None = 0,
- ImGuiTypingSelectFlags_AllowBackspace = 1 << 0,
- ImGuiTypingSelectFlags_AllowSingleCharMode = 1 << 1,
-} ImGuiTypingSelectFlags_;
-struct ImGuiTypingSelectRequest {
- ImGuiTypingSelectFlags Flags;
- int SearchBufferLen;
- const char* SearchBuffer;
- bool SelectRequest;
- bool SingleCharMode;
- ImS8 SingleCharSize;
-};
-struct ImGuiTypingSelectState {
- ImGuiTypingSelectRequest Request;
- char SearchBuffer[64];
- ImGuiID FocusScope;
- int LastRequestFrame;
- float LastRequestTime;
- bool SingleCharModeLock;
-};
-typedef enum {
- ImGuiOldColumnFlags_None = 0,
- ImGuiOldColumnFlags_NoBorder = 1 << 0,
- ImGuiOldColumnFlags_NoResize = 1 << 1,
- ImGuiOldColumnFlags_NoPreserveWidths = 1 << 2,
- ImGuiOldColumnFlags_NoForceWithinWindow = 1 << 3,
- ImGuiOldColumnFlags_GrowParentContentsSize = 1 << 4,
-} ImGuiOldColumnFlags_;
-struct ImGuiOldColumnData {
- float OffsetNorm;
- float OffsetNormBeforeResize;
- ImGuiOldColumnFlags Flags;
- ImRect ClipRect;
-};
-typedef struct ImVector_ImGuiOldColumnData {
- int Size;
- int Capacity;
- ImGuiOldColumnData* Data;
-} ImVector_ImGuiOldColumnData;
-
-struct ImGuiOldColumns {
- ImGuiID ID;
- ImGuiOldColumnFlags Flags;
- bool IsFirstFrame;
- bool IsBeingResized;
- int Current;
- int Count;
- float OffMinX, OffMaxX;
- float LineMinY, LineMaxY;
- float HostCursorPosY;
- float HostCursorMaxPosX;
- ImRect HostInitialClipRect;
- ImRect HostBackupClipRect;
- ImRect HostBackupParentWorkRect;
- ImVector_ImGuiOldColumnData Columns;
- ImDrawListSplitter Splitter;
-};
-typedef enum {
- ImGuiDockNodeFlags_DockSpace = 1 << 10,
- ImGuiDockNodeFlags_CentralNode = 1 << 11,
- ImGuiDockNodeFlags_NoTabBar = 1 << 12,
- ImGuiDockNodeFlags_HiddenTabBar = 1 << 13,
- ImGuiDockNodeFlags_NoWindowMenuButton = 1 << 14,
- ImGuiDockNodeFlags_NoCloseButton = 1 << 15,
- ImGuiDockNodeFlags_NoResizeX = 1 << 16,
- ImGuiDockNodeFlags_NoResizeY = 1 << 17,
- ImGuiDockNodeFlags_DockedWindowsInFocusRoute = 1 << 18,
- ImGuiDockNodeFlags_NoDockingSplitOther = 1 << 19,
- ImGuiDockNodeFlags_NoDockingOverMe = 1 << 20,
- ImGuiDockNodeFlags_NoDockingOverOther = 1 << 21,
- ImGuiDockNodeFlags_NoDockingOverEmpty = 1 << 22,
- ImGuiDockNodeFlags_NoDocking = ImGuiDockNodeFlags_NoDockingOverMe |
- ImGuiDockNodeFlags_NoDockingOverOther |
- ImGuiDockNodeFlags_NoDockingOverEmpty |
- ImGuiDockNodeFlags_NoDockingSplit |
- ImGuiDockNodeFlags_NoDockingSplitOther,
- ImGuiDockNodeFlags_SharedFlagsInheritMask_ = ~0,
- ImGuiDockNodeFlags_NoResizeFlagsMask_ = ImGuiDockNodeFlags_NoResize |
- ImGuiDockNodeFlags_NoResizeX |
- ImGuiDockNodeFlags_NoResizeY,
- ImGuiDockNodeFlags_LocalFlagsTransferMask_ =
- ImGuiDockNodeFlags_NoDockingSplit |
- ImGuiDockNodeFlags_NoResizeFlagsMask_ |
- ImGuiDockNodeFlags_AutoHideTabBar | ImGuiDockNodeFlags_CentralNode |
- ImGuiDockNodeFlags_NoTabBar | ImGuiDockNodeFlags_HiddenTabBar |
- ImGuiDockNodeFlags_NoWindowMenuButton | ImGuiDockNodeFlags_NoCloseButton,
- ImGuiDockNodeFlags_SavedFlagsMask_ =
- ImGuiDockNodeFlags_NoResizeFlagsMask_ | ImGuiDockNodeFlags_DockSpace |
- ImGuiDockNodeFlags_CentralNode | ImGuiDockNodeFlags_NoTabBar |
- ImGuiDockNodeFlags_HiddenTabBar | ImGuiDockNodeFlags_NoWindowMenuButton |
- ImGuiDockNodeFlags_NoCloseButton,
-} ImGuiDockNodeFlagsPrivate_;
-typedef enum {
- ImGuiDataAuthority_Auto,
- ImGuiDataAuthority_DockNode,
- ImGuiDataAuthority_Window,
-} ImGuiDataAuthority_;
-typedef enum {
- ImGuiDockNodeState_Unknown,
- ImGuiDockNodeState_HostWindowHiddenBecauseSingleWindow,
- ImGuiDockNodeState_HostWindowHiddenBecauseWindowsAreResizing,
- ImGuiDockNodeState_HostWindowVisible,
-} ImGuiDockNodeState;
-typedef struct ImVector_ImGuiWindowPtr {
- int Size;
- int Capacity;
- ImGuiWindow** Data;
-} ImVector_ImGuiWindowPtr;
-
-struct ImGuiDockNode {
- ImGuiID ID;
- ImGuiDockNodeFlags SharedFlags;
- ImGuiDockNodeFlags LocalFlags;
- ImGuiDockNodeFlags LocalFlagsInWindows;
- ImGuiDockNodeFlags MergedFlags;
- ImGuiDockNodeState State;
- ImGuiDockNode* ParentNode;
- ImGuiDockNode* ChildNodes[2];
- ImVector_ImGuiWindowPtr Windows;
- ImGuiTabBar* TabBar;
- ImVec2 Pos;
- ImVec2 Size;
- ImVec2 SizeRef;
- ImGuiAxis SplitAxis;
- ImGuiWindowClass WindowClass;
- ImU32 LastBgColor;
- ImGuiWindow* HostWindow;
- ImGuiWindow* VisibleWindow;
- ImGuiDockNode* CentralNode;
- ImGuiDockNode* OnlyNodeWithWindows;
- int CountNodeWithWindows;
- int LastFrameAlive;
- int LastFrameActive;
- int LastFrameFocused;
- ImGuiID LastFocusedNodeId;
- ImGuiID SelectedTabId;
- ImGuiID WantCloseTabId;
- ImGuiID RefViewportId;
- ImGuiDataAuthority AuthorityForPos : 3;
- ImGuiDataAuthority AuthorityForSize : 3;
- ImGuiDataAuthority AuthorityForViewport : 3;
- bool IsVisible : 1;
- bool IsFocused : 1;
- bool IsBgDrawnThisFrame : 1;
- bool HasCloseButton : 1;
- bool HasWindowMenuButton : 1;
- bool HasCentralNodeChild : 1;
- bool WantCloseAll : 1;
- bool WantLockSizeOnce : 1;
- bool WantMouseMove : 1;
- bool WantHiddenTabBarUpdate : 1;
- bool WantHiddenTabBarToggle : 1;
-};
-typedef enum {
- ImGuiWindowDockStyleCol_Text,
- ImGuiWindowDockStyleCol_Tab,
- ImGuiWindowDockStyleCol_TabHovered,
- ImGuiWindowDockStyleCol_TabActive,
- ImGuiWindowDockStyleCol_TabUnfocused,
- ImGuiWindowDockStyleCol_TabUnfocusedActive,
- ImGuiWindowDockStyleCol_COUNT
-} ImGuiWindowDockStyleCol;
-struct ImGuiWindowDockStyle {
- ImU32 Colors[ImGuiWindowDockStyleCol_COUNT];
-};
-typedef struct ImVector_ImGuiDockRequest {
- int Size;
- int Capacity;
- ImGuiDockRequest* Data;
-} ImVector_ImGuiDockRequest;
-
-typedef struct ImVector_ImGuiDockNodeSettings {
- int Size;
- int Capacity;
- ImGuiDockNodeSettings* Data;
-} ImVector_ImGuiDockNodeSettings;
-
-struct ImGuiDockContext {
- ImGuiStorage Nodes;
- ImVector_ImGuiDockRequest Requests;
- ImVector_ImGuiDockNodeSettings NodesSettings;
- bool WantFullRebuild;
-};
-typedef struct ImGuiViewportP ImGuiViewportP;
-struct ImGuiViewportP {
- ImGuiViewport _ImGuiViewport;
- ImGuiWindow* Window;
- int Idx;
- int LastFrameActive;
- int LastFocusedStampCount;
- ImGuiID LastNameHash;
- ImVec2 LastPos;
- float Alpha;
- float LastAlpha;
- bool LastFocusedHadNavWindow;
- short PlatformMonitor;
- int BgFgDrawListsLastFrame[2];
- ImDrawList* BgFgDrawLists[2];
- ImDrawData DrawDataP;
- ImDrawDataBuilder DrawDataBuilder;
- ImVec2 LastPlatformPos;
- ImVec2 LastPlatformSize;
- ImVec2 LastRendererSize;
- ImVec2 WorkOffsetMin;
- ImVec2 WorkOffsetMax;
- ImVec2 BuildWorkOffsetMin;
- ImVec2 BuildWorkOffsetMax;
-};
-struct ImGuiWindowSettings {
- ImGuiID ID;
- ImVec2ih Pos;
- ImVec2ih Size;
- ImVec2ih ViewportPos;
- ImGuiID ViewportId;
- ImGuiID DockId;
- ImGuiID ClassId;
- short DockOrder;
- bool Collapsed;
- bool IsChild;
- bool WantApply;
- bool WantDelete;
-};
-struct ImGuiSettingsHandler {
- const char* TypeName;
- ImGuiID TypeHash;
- void (*ClearAllFn)(ImGuiContext* ctx, ImGuiSettingsHandler* handler);
- void (*ReadInitFn)(ImGuiContext* ctx, ImGuiSettingsHandler* handler);
- void* (*ReadOpenFn)(ImGuiContext* ctx,
- ImGuiSettingsHandler* handler,
- const char* name);
- void (*ReadLineFn)(ImGuiContext* ctx,
- ImGuiSettingsHandler* handler,
- void* entry,
- const char* line);
- void (*ApplyAllFn)(ImGuiContext* ctx, ImGuiSettingsHandler* handler);
- void (*WriteAllFn)(ImGuiContext* ctx,
- ImGuiSettingsHandler* handler,
- ImGuiTextBuffer* out_buf);
- void* UserData;
-};
-typedef enum {
- ImGuiLocKey_VersionStr = 0,
- ImGuiLocKey_TableSizeOne = 1,
- ImGuiLocKey_TableSizeAllFit = 2,
- ImGuiLocKey_TableSizeAllDefault = 3,
- ImGuiLocKey_TableResetOrder = 4,
- ImGuiLocKey_WindowingMainMenuBar = 5,
- ImGuiLocKey_WindowingPopup = 6,
- ImGuiLocKey_WindowingUntitled = 7,
- ImGuiLocKey_DockingHideTabBar = 8,
- ImGuiLocKey_DockingHoldShiftToDock = 9,
- ImGuiLocKey_DockingDragToUndockOrMoveNode = 10,
- ImGuiLocKey_COUNT = 11,
-} ImGuiLocKey;
-struct ImGuiLocEntry {
- ImGuiLocKey Key;
- const char* Text;
-};
-typedef enum {
- ImGuiDebugLogFlags_None = 0,
- ImGuiDebugLogFlags_EventActiveId = 1 << 0,
- ImGuiDebugLogFlags_EventFocus = 1 << 1,
- ImGuiDebugLogFlags_EventPopup = 1 << 2,
- ImGuiDebugLogFlags_EventNav = 1 << 3,
- ImGuiDebugLogFlags_EventClipper = 1 << 4,
- ImGuiDebugLogFlags_EventSelection = 1 << 5,
- ImGuiDebugLogFlags_EventIO = 1 << 6,
- ImGuiDebugLogFlags_EventInputRouting = 1 << 7,
- ImGuiDebugLogFlags_EventDocking = 1 << 8,
- ImGuiDebugLogFlags_EventViewport = 1 << 9,
- ImGuiDebugLogFlags_EventMask_ =
- ImGuiDebugLogFlags_EventActiveId | ImGuiDebugLogFlags_EventFocus |
- ImGuiDebugLogFlags_EventPopup | ImGuiDebugLogFlags_EventNav |
- ImGuiDebugLogFlags_EventClipper | ImGuiDebugLogFlags_EventSelection |
- ImGuiDebugLogFlags_EventIO | ImGuiDebugLogFlags_EventInputRouting |
- ImGuiDebugLogFlags_EventDocking | ImGuiDebugLogFlags_EventViewport,
- ImGuiDebugLogFlags_OutputToTTY = 1 << 20,
- ImGuiDebugLogFlags_OutputToTestEngine = 1 << 21,
-} ImGuiDebugLogFlags_;
-typedef struct ImGuiDebugAllocEntry ImGuiDebugAllocEntry;
-struct ImGuiDebugAllocEntry {
- int FrameCount;
- ImS16 AllocCount;
- ImS16 FreeCount;
-};
-typedef struct ImGuiDebugAllocInfo ImGuiDebugAllocInfo;
-struct ImGuiDebugAllocInfo {
- int TotalAllocCount;
- int TotalFreeCount;
- ImS16 LastEntriesIdx;
- ImGuiDebugAllocEntry LastEntriesBuf[6];
-};
-struct ImGuiMetricsConfig {
- bool ShowDebugLog;
- bool ShowIDStackTool;
- bool ShowWindowsRects;
- bool ShowWindowsBeginOrder;
- bool ShowTablesRects;
- bool ShowDrawCmdMesh;
- bool ShowDrawCmdBoundingBoxes;
- bool ShowTextEncodingViewer;
- bool ShowAtlasTintedWithTextColor;
- bool ShowDockingNodes;
- int ShowWindowsRectsType;
- int ShowTablesRectsType;
- int HighlightMonitorIdx;
- ImGuiID HighlightViewportID;
-};
-typedef struct ImGuiStackLevelInfo ImGuiStackLevelInfo;
-struct ImGuiStackLevelInfo {
- ImGuiID ID;
- ImS8 QueryFrameCount;
- bool QuerySuccess;
- ImGuiDataType DataType : 8;
- char Desc[57];
-};
-typedef struct ImGuiIDStackTool ImGuiIDStackTool;
-typedef struct ImVector_ImGuiStackLevelInfo {
- int Size;
- int Capacity;
- ImGuiStackLevelInfo* Data;
-} ImVector_ImGuiStackLevelInfo;
-
-struct ImGuiIDStackTool {
- int LastActiveFrame;
- int StackLevel;
- ImGuiID QueryId;
- ImVector_ImGuiStackLevelInfo Results;
- bool CopyToClipboardOnCtrlC;
- float CopyToClipboardLastTime;
-};
-typedef void (*ImGuiContextHookCallback)(ImGuiContext* ctx,
- ImGuiContextHook* hook);
-typedef enum {
- ImGuiContextHookType_NewFramePre,
- ImGuiContextHookType_NewFramePost,
- ImGuiContextHookType_EndFramePre,
- ImGuiContextHookType_EndFramePost,
- ImGuiContextHookType_RenderPre,
- ImGuiContextHookType_RenderPost,
- ImGuiContextHookType_Shutdown,
- ImGuiContextHookType_PendingRemoval_
-} ImGuiContextHookType;
-struct ImGuiContextHook {
- ImGuiID HookId;
- ImGuiContextHookType Type;
- ImGuiID Owner;
- ImGuiContextHookCallback Callback;
- void* UserData;
-};
-typedef struct ImVector_ImGuiInputEvent {
- int Size;
- int Capacity;
- ImGuiInputEvent* Data;
-} ImVector_ImGuiInputEvent;
-
-typedef struct ImVector_ImGuiWindowStackData {
- int Size;
- int Capacity;
- ImGuiWindowStackData* Data;
-} ImVector_ImGuiWindowStackData;
-
-typedef struct ImVector_ImGuiColorMod {
- int Size;
- int Capacity;
- ImGuiColorMod* Data;
-} ImVector_ImGuiColorMod;
-
-typedef struct ImVector_ImGuiStyleMod {
- int Size;
- int Capacity;
- ImGuiStyleMod* Data;
-} ImVector_ImGuiStyleMod;
-
-typedef struct ImVector_ImGuiFocusScopeData {
- int Size;
- int Capacity;
- ImGuiFocusScopeData* Data;
-} ImVector_ImGuiFocusScopeData;
-
-typedef struct ImVector_ImGuiItemFlags {
- int Size;
- int Capacity;
- ImGuiItemFlags* Data;
-} ImVector_ImGuiItemFlags;
-
-typedef struct ImVector_ImGuiGroupData {
- int Size;
- int Capacity;
- ImGuiGroupData* Data;
-} ImVector_ImGuiGroupData;
-
-typedef struct ImVector_ImGuiPopupData {
- int Size;
- int Capacity;
- ImGuiPopupData* Data;
-} ImVector_ImGuiPopupData;
-
-typedef struct ImVector_ImGuiNavTreeNodeData {
- int Size;
- int Capacity;
- ImGuiNavTreeNodeData* Data;
-} ImVector_ImGuiNavTreeNodeData;
-
-typedef struct ImVector_ImGuiViewportPPtr {
- int Size;
- int Capacity;
- ImGuiViewportP** Data;
-} ImVector_ImGuiViewportPPtr;
-
-typedef struct ImVector_unsigned_char {
- int Size;
- int Capacity;
- unsigned char* Data;
-} ImVector_unsigned_char;
-
-typedef struct ImVector_ImGuiListClipperData {
- int Size;
- int Capacity;
- ImGuiListClipperData* Data;
-} ImVector_ImGuiListClipperData;
-
-typedef struct ImVector_ImGuiTableTempData {
- int Size;
- int Capacity;
- ImGuiTableTempData* Data;
-} ImVector_ImGuiTableTempData;
-
-typedef struct ImVector_ImGuiTable {
- int Size;
- int Capacity;
- ImGuiTable* Data;
-} ImVector_ImGuiTable;
-
-typedef struct ImPool_ImGuiTable {
- ImVector_ImGuiTable Buf;
- ImGuiStorage Map;
- ImPoolIdx FreeIdx;
- ImPoolIdx AliveCount;
-} ImPool_ImGuiTable;
-
-typedef struct ImVector_ImGuiTabBar {
- int Size;
- int Capacity;
- ImGuiTabBar* Data;
-} ImVector_ImGuiTabBar;
-
-typedef struct ImPool_ImGuiTabBar {
- ImVector_ImGuiTabBar Buf;
- ImGuiStorage Map;
- ImPoolIdx FreeIdx;
- ImPoolIdx AliveCount;
-} ImPool_ImGuiTabBar;
-
-typedef struct ImVector_ImGuiPtrOrIndex {
- int Size;
- int Capacity;
- ImGuiPtrOrIndex* Data;
-} ImVector_ImGuiPtrOrIndex;
-
-typedef struct ImVector_ImGuiShrinkWidthItem {
- int Size;
- int Capacity;
- ImGuiShrinkWidthItem* Data;
-} ImVector_ImGuiShrinkWidthItem;
-
-typedef struct ImVector_ImGuiID {
- int Size;
- int Capacity;
- ImGuiID* Data;
-} ImVector_ImGuiID;
-
-typedef struct ImVector_ImGuiSettingsHandler {
- int Size;
- int Capacity;
- ImGuiSettingsHandler* Data;
-} ImVector_ImGuiSettingsHandler;
-
-typedef struct ImChunkStream_ImGuiWindowSettings {
- ImVector_char Buf;
-} ImChunkStream_ImGuiWindowSettings;
-
-typedef struct ImChunkStream_ImGuiTableSettings {
- ImVector_char Buf;
-} ImChunkStream_ImGuiTableSettings;
-
-typedef struct ImVector_ImGuiContextHook {
- int Size;
- int Capacity;
- ImGuiContextHook* Data;
-} ImVector_ImGuiContextHook;
-
-struct ImGuiContext {
- bool Initialized;
- bool FontAtlasOwnedByContext;
- ImGuiIO IO;
- ImGuiPlatformIO PlatformIO;
- ImGuiStyle Style;
- ImGuiConfigFlags ConfigFlagsCurrFrame;
- ImGuiConfigFlags ConfigFlagsLastFrame;
- ImFont* Font;
- float FontSize;
- float FontBaseSize;
- ImDrawListSharedData DrawListSharedData;
- double Time;
- int FrameCount;
- int FrameCountEnded;
- int FrameCountPlatformEnded;
- int FrameCountRendered;
- bool WithinFrameScope;
- bool WithinFrameScopeWithImplicitWindow;
- bool WithinEndChild;
- bool GcCompactAll;
- bool TestEngineHookItems;
- void* TestEngine;
- ImVector_ImGuiInputEvent InputEventsQueue;
- ImVector_ImGuiInputEvent InputEventsTrail;
- ImGuiMouseSource InputEventsNextMouseSource;
- ImU32 InputEventsNextEventId;
- ImVector_ImGuiWindowPtr Windows;
- ImVector_ImGuiWindowPtr WindowsFocusOrder;
- ImVector_ImGuiWindowPtr WindowsTempSortBuffer;
- ImVector_ImGuiWindowStackData CurrentWindowStack;
- ImGuiStorage WindowsById;
- int WindowsActiveCount;
- ImVec2 WindowsHoverPadding;
- ImGuiID DebugBreakInWindow;
- ImGuiWindow* CurrentWindow;
- ImGuiWindow* HoveredWindow;
- ImGuiWindow* HoveredWindowUnderMovingWindow;
- ImGuiWindow* MovingWindow;
- ImGuiWindow* WheelingWindow;
- ImVec2 WheelingWindowRefMousePos;
- int WheelingWindowStartFrame;
- int WheelingWindowScrolledFrame;
- float WheelingWindowReleaseTimer;
- ImVec2 WheelingWindowWheelRemainder;
- ImVec2 WheelingAxisAvg;
- ImGuiID DebugHookIdInfo;
- ImGuiID HoveredId;
- ImGuiID HoveredIdPreviousFrame;
- bool HoveredIdAllowOverlap;
- bool HoveredIdDisabled;
- float HoveredIdTimer;
- float HoveredIdNotActiveTimer;
- ImGuiID ActiveId;
- ImGuiID ActiveIdIsAlive;
- float ActiveIdTimer;
- bool ActiveIdIsJustActivated;
- bool ActiveIdAllowOverlap;
- bool ActiveIdNoClearOnFocusLoss;
- bool ActiveIdHasBeenPressedBefore;
- bool ActiveIdHasBeenEditedBefore;
- bool ActiveIdHasBeenEditedThisFrame;
- bool ActiveIdFromShortcut;
- int ActiveIdMouseButton : 8;
- ImVec2 ActiveIdClickOffset;
- ImGuiWindow* ActiveIdWindow;
- ImGuiInputSource ActiveIdSource;
- ImGuiID ActiveIdPreviousFrame;
- bool ActiveIdPreviousFrameIsAlive;
- bool ActiveIdPreviousFrameHasBeenEditedBefore;
- ImGuiWindow* ActiveIdPreviousFrameWindow;
- ImGuiID LastActiveId;
- float LastActiveIdTimer;
- double LastKeyModsChangeTime;
- double LastKeyModsChangeFromNoneTime;
- double LastKeyboardKeyPressTime;
- ImBitArrayForNamedKeys KeysMayBeCharInput;
- ImGuiKeyOwnerData KeysOwnerData[ImGuiKey_NamedKey_COUNT];
- ImGuiKeyRoutingTable KeysRoutingTable;
- ImU32 ActiveIdUsingNavDirMask;
- bool ActiveIdUsingAllKeyboardKeys;
- ImGuiKeyChord DebugBreakInShortcutRouting;
- ImGuiID CurrentFocusScopeId;
- ImGuiItemFlags CurrentItemFlags;
- ImGuiID DebugLocateId;
- ImGuiNextItemData NextItemData;
- ImGuiLastItemData LastItemData;
- ImGuiNextWindowData NextWindowData;
- bool DebugShowGroupRects;
- ImGuiCol DebugFlashStyleColorIdx;
- ImVector_ImGuiColorMod ColorStack;
- ImVector_ImGuiStyleMod StyleVarStack;
- ImVector_ImFontPtr FontStack;
- ImVector_ImGuiFocusScopeData FocusScopeStack;
- ImVector_ImGuiItemFlags ItemFlagsStack;
- ImVector_ImGuiGroupData GroupStack;
- ImVector_ImGuiPopupData OpenPopupStack;
- ImVector_ImGuiPopupData BeginPopupStack;
- ImVector_ImGuiNavTreeNodeData NavTreeNodeStack;
- ImVector_ImGuiViewportPPtr Viewports;
- float CurrentDpiScale;
- ImGuiViewportP* CurrentViewport;
- ImGuiViewportP* MouseViewport;
- ImGuiViewportP* MouseLastHoveredViewport;
- ImGuiID PlatformLastFocusedViewportId;
- ImGuiPlatformMonitor FallbackMonitor;
- ImRect PlatformMonitorsFullWorkRect;
- int ViewportCreatedCount;
- int PlatformWindowsCreatedCount;
- int ViewportFocusedStampCount;
- ImGuiWindow* NavWindow;
- ImGuiID NavId;
- ImGuiID NavFocusScopeId;
- ImVector_ImGuiFocusScopeData NavFocusRoute;
- ImGuiID NavActivateId;
- ImGuiID NavActivateDownId;
- ImGuiID NavActivatePressedId;
- ImGuiActivateFlags NavActivateFlags;
- ImGuiID NavHighlightActivatedId;
- float NavHighlightActivatedTimer;
- ImGuiID NavJustMovedToId;
- ImGuiID NavJustMovedToFocusScopeId;
- ImGuiKeyChord NavJustMovedToKeyMods;
- ImGuiID NavNextActivateId;
- ImGuiActivateFlags NavNextActivateFlags;
- ImGuiInputSource NavInputSource;
- ImGuiNavLayer NavLayer;
- ImGuiSelectionUserData NavLastValidSelectionUserData;
- bool NavIdIsAlive;
- bool NavMousePosDirty;
- bool NavDisableHighlight;
- bool NavDisableMouseHover;
- bool NavAnyRequest;
- bool NavInitRequest;
- bool NavInitRequestFromMove;
- ImGuiNavItemData NavInitResult;
- bool NavMoveSubmitted;
- bool NavMoveScoringItems;
- bool NavMoveForwardToNextFrame;
- ImGuiNavMoveFlags NavMoveFlags;
- ImGuiScrollFlags NavMoveScrollFlags;
- ImGuiKeyChord NavMoveKeyMods;
- ImGuiDir NavMoveDir;
- ImGuiDir NavMoveDirForDebug;
- ImGuiDir NavMoveClipDir;
- ImRect NavScoringRect;
- ImRect NavScoringNoClipRect;
- int NavScoringDebugCount;
- int NavTabbingDir;
- int NavTabbingCounter;
- ImGuiNavItemData NavMoveResultLocal;
- ImGuiNavItemData NavMoveResultLocalVisible;
- ImGuiNavItemData NavMoveResultOther;
- ImGuiNavItemData NavTabbingResultFirst;
- ImGuiKeyChord ConfigNavWindowingKeyNext;
- ImGuiKeyChord ConfigNavWindowingKeyPrev;
- ImGuiWindow* NavWindowingTarget;
- ImGuiWindow* NavWindowingTargetAnim;
- ImGuiWindow* NavWindowingListWindow;
- float NavWindowingTimer;
- float NavWindowingHighlightAlpha;
- bool NavWindowingToggleLayer;
- ImGuiKey NavWindowingToggleKey;
- ImVec2 NavWindowingAccumDeltaPos;
- ImVec2 NavWindowingAccumDeltaSize;
- float DimBgRatio;
- bool DragDropActive;
- bool DragDropWithinSource;
- bool DragDropWithinTarget;
- ImGuiDragDropFlags DragDropSourceFlags;
- int DragDropSourceFrameCount;
- int DragDropMouseButton;
- ImGuiPayload DragDropPayload;
- ImRect DragDropTargetRect;
- ImRect DragDropTargetClipRect;
- ImGuiID DragDropTargetId;
- ImGuiDragDropFlags DragDropAcceptFlags;
- float DragDropAcceptIdCurrRectSurface;
- ImGuiID DragDropAcceptIdCurr;
- ImGuiID DragDropAcceptIdPrev;
- int DragDropAcceptFrameCount;
- ImGuiID DragDropHoldJustPressedId;
- ImVector_unsigned_char DragDropPayloadBufHeap;
- unsigned char DragDropPayloadBufLocal[16];
- int ClipperTempDataStacked;
- ImVector_ImGuiListClipperData ClipperTempData;
- ImGuiTable* CurrentTable;
- ImGuiID DebugBreakInTable;
- int TablesTempDataStacked;
- ImVector_ImGuiTableTempData TablesTempData;
- ImPool_ImGuiTable Tables;
- ImVector_float TablesLastTimeActive;
- ImVector_ImDrawChannel DrawChannelsTempMergeBuffer;
- ImGuiTabBar* CurrentTabBar;
- ImPool_ImGuiTabBar TabBars;
- ImVector_ImGuiPtrOrIndex CurrentTabBarStack;
- ImVector_ImGuiShrinkWidthItem ShrinkWidthBuffer;
- ImGuiID HoverItemDelayId;
- ImGuiID HoverItemDelayIdPreviousFrame;
- float HoverItemDelayTimer;
- float HoverItemDelayClearTimer;
- ImGuiID HoverItemUnlockedStationaryId;
- ImGuiID HoverWindowUnlockedStationaryId;
- ImGuiMouseCursor MouseCursor;
- float MouseStationaryTimer;
- ImVec2 MouseLastValidPos;
- ImGuiInputTextState InputTextState;
- ImGuiInputTextDeactivatedState InputTextDeactivatedState;
- ImFont InputTextPasswordFont;
- ImGuiID TempInputId;
- int BeginMenuDepth;
- int BeginComboDepth;
- ImGuiColorEditFlags ColorEditOptions;
- ImGuiID ColorEditCurrentID;
- ImGuiID ColorEditSavedID;
- float ColorEditSavedHue;
- float ColorEditSavedSat;
- ImU32 ColorEditSavedColor;
- ImVec4 ColorPickerRef;
- ImGuiComboPreviewData ComboPreviewData;
- ImRect WindowResizeBorderExpectedRect;
- bool WindowResizeRelativeMode;
- float SliderGrabClickOffset;
- float SliderCurrentAccum;
- bool SliderCurrentAccumDirty;
- bool DragCurrentAccumDirty;
- float DragCurrentAccum;
- float DragSpeedDefaultRatio;
- float ScrollbarClickDeltaToGrabCenter;
- float DisabledAlphaBackup;
- short DisabledStackSize;
- short LockMarkEdited;
- short TooltipOverrideCount;
- ImVector_char ClipboardHandlerData;
- ImVector_ImGuiID MenusIdSubmittedThisFrame;
- ImGuiTypingSelectState TypingSelectState;
- ImGuiPlatformImeData PlatformImeData;
- ImGuiPlatformImeData PlatformImeDataPrev;
- ImGuiID PlatformImeViewport;
- ImGuiDockContext DockContext;
- void (*DockNodeWindowMenuHandler)(ImGuiContext* ctx,
- ImGuiDockNode* node,
- ImGuiTabBar* tab_bar);
- bool SettingsLoaded;
- float SettingsDirtyTimer;
- ImGuiTextBuffer SettingsIniData;
- ImVector_ImGuiSettingsHandler SettingsHandlers;
- ImChunkStream_ImGuiWindowSettings SettingsWindows;
- ImChunkStream_ImGuiTableSettings SettingsTables;
- ImVector_ImGuiContextHook Hooks;
- ImGuiID HookIdNext;
- const char* LocalizationTable[ImGuiLocKey_COUNT];
- bool LogEnabled;
- ImGuiLogType LogType;
- ImFileHandle LogFile;
- ImGuiTextBuffer LogBuffer;
- const char* LogNextPrefix;
- const char* LogNextSuffix;
- float LogLinePosY;
- bool LogLineFirstItem;
- int LogDepthRef;
- int LogDepthToExpand;
- int LogDepthToExpandDefault;
- ImGuiDebugLogFlags DebugLogFlags;
- ImGuiTextBuffer DebugLogBuf;
- ImGuiTextIndex DebugLogIndex;
- ImGuiDebugLogFlags DebugLogAutoDisableFlags;
- ImU8 DebugLogAutoDisableFrames;
- ImU8 DebugLocateFrames;
- bool DebugBreakInLocateId;
- ImGuiKeyChord DebugBreakKeyChord;
- ImS8 DebugBeginReturnValueCullDepth;
- bool DebugItemPickerActive;
- ImU8 DebugItemPickerMouseButton;
- ImGuiID DebugItemPickerBreakId;
- float DebugFlashStyleColorTime;
- ImVec4 DebugFlashStyleColorBackup;
- ImGuiMetricsConfig DebugMetricsConfig;
- ImGuiIDStackTool DebugIDStackTool;
- ImGuiDebugAllocInfo DebugAllocInfo;
- ImGuiDockNode* DebugHoveredDockNode;
- float FramerateSecPerFrame[60];
- int FramerateSecPerFrameIdx;
- int FramerateSecPerFrameCount;
- float FramerateSecPerFrameAccum;
- int WantCaptureMouseNextFrame;
- int WantCaptureKeyboardNextFrame;
- int WantTextInputNextFrame;
- ImVector_char TempBuffer;
- char TempKeychordName[64];
-};
-struct ImGuiWindowTempData {
- ImVec2 CursorPos;
- ImVec2 CursorPosPrevLine;
- ImVec2 CursorStartPos;
- ImVec2 CursorMaxPos;
- ImVec2 IdealMaxPos;
- ImVec2 CurrLineSize;
- ImVec2 PrevLineSize;
- float CurrLineTextBaseOffset;
- float PrevLineTextBaseOffset;
- bool IsSameLine;
- bool IsSetPos;
- ImVec1 Indent;
- ImVec1 ColumnsOffset;
- ImVec1 GroupOffset;
- ImVec2 CursorStartPosLossyness;
- ImGuiNavLayer NavLayerCurrent;
- short NavLayersActiveMask;
- short NavLayersActiveMaskNext;
- bool NavIsScrollPushableX;
- bool NavHideHighlightOneFrame;
- bool NavWindowHasScrollY;
- bool MenuBarAppending;
- ImVec2 MenuBarOffset;
- ImGuiMenuColumns MenuColumns;
- int TreeDepth;
- ImU32 TreeJumpToParentOnPopMask;
- ImVector_ImGuiWindowPtr ChildWindows;
- ImGuiStorage* StateStorage;
- ImGuiOldColumns* CurrentColumns;
- int CurrentTableIdx;
- ImGuiLayoutType LayoutType;
- ImGuiLayoutType ParentLayoutType;
- ImU32 ModalDimBgColor;
- float ItemWidth;
- float TextWrapPos;
- ImVector_float ItemWidthStack;
- ImVector_float TextWrapPosStack;
-};
-typedef struct ImVector_ImGuiOldColumns {
- int Size;
- int Capacity;
- ImGuiOldColumns* Data;
-} ImVector_ImGuiOldColumns;
-
-struct ImGuiWindow {
- ImGuiContext* Ctx;
- char* Name;
- ImGuiID ID;
- ImGuiWindowFlags Flags, FlagsPreviousFrame;
- ImGuiChildFlags ChildFlags;
- ImGuiWindowClass WindowClass;
- ImGuiViewportP* Viewport;
- ImGuiID ViewportId;
- ImVec2 ViewportPos;
- int ViewportAllowPlatformMonitorExtend;
- ImVec2 Pos;
- ImVec2 Size;
- ImVec2 SizeFull;
- ImVec2 ContentSize;
- ImVec2 ContentSizeIdeal;
- ImVec2 ContentSizeExplicit;
- ImVec2 WindowPadding;
- float WindowRounding;
- float WindowBorderSize;
- float DecoOuterSizeX1, DecoOuterSizeY1;
- float DecoOuterSizeX2, DecoOuterSizeY2;
- float DecoInnerSizeX1, DecoInnerSizeY1;
- int NameBufLen;
- ImGuiID MoveId;
- ImGuiID TabId;
- ImGuiID ChildId;
- ImVec2 Scroll;
- ImVec2 ScrollMax;
- ImVec2 ScrollTarget;
- ImVec2 ScrollTargetCenterRatio;
- ImVec2 ScrollTargetEdgeSnapDist;
- ImVec2 ScrollbarSizes;
- bool ScrollbarX, ScrollbarY;
- bool ViewportOwned;
- bool Active;
- bool WasActive;
- bool WriteAccessed;
- bool Collapsed;
- bool WantCollapseToggle;
- bool SkipItems;
- bool SkipRefresh;
- bool Appearing;
- bool Hidden;
- bool IsFallbackWindow;
- bool IsExplicitChild;
- bool HasCloseButton;
- signed char ResizeBorderHovered;
- signed char ResizeBorderHeld;
- short BeginCount;
- short BeginCountPreviousFrame;
- short BeginOrderWithinParent;
- short BeginOrderWithinContext;
- short FocusOrder;
- ImGuiID PopupId;
- ImS8 AutoFitFramesX, AutoFitFramesY;
- bool AutoFitOnlyGrows;
- ImGuiDir AutoPosLastDirection;
- ImS8 HiddenFramesCanSkipItems;
- ImS8 HiddenFramesCannotSkipItems;
- ImS8 HiddenFramesForRenderOnly;
- ImS8 DisableInputsFrames;
- ImGuiCond SetWindowPosAllowFlags : 8;
- ImGuiCond SetWindowSizeAllowFlags : 8;
- ImGuiCond SetWindowCollapsedAllowFlags : 8;
- ImGuiCond SetWindowDockAllowFlags : 8;
- ImVec2 SetWindowPosVal;
- ImVec2 SetWindowPosPivot;
- ImVector_ImGuiID IDStack;
- ImGuiWindowTempData DC;
- ImRect OuterRectClipped;
- ImRect InnerRect;
- ImRect InnerClipRect;
- ImRect WorkRect;
- ImRect ParentWorkRect;
- ImRect ClipRect;
- ImRect ContentRegionRect;
- ImVec2ih HitTestHoleSize;
- ImVec2ih HitTestHoleOffset;
- int LastFrameActive;
- int LastFrameJustFocused;
- float LastTimeActive;
- float ItemWidthDefault;
- ImGuiStorage StateStorage;
- ImVector_ImGuiOldColumns ColumnsStorage;
- float FontWindowScale;
- float FontDpiScale;
- int SettingsOffset;
- ImDrawList* DrawList;
- ImDrawList DrawListInst;
- ImGuiWindow* ParentWindow;
- ImGuiWindow* ParentWindowInBeginStack;
- ImGuiWindow* RootWindow;
- ImGuiWindow* RootWindowPopupTree;
- ImGuiWindow* RootWindowDockTree;
- ImGuiWindow* RootWindowForTitleBarHighlight;
- ImGuiWindow* RootWindowForNav;
- ImGuiWindow* ParentWindowForFocusRoute;
- ImGuiWindow* NavLastChildNavWindow;
- ImGuiID NavLastIds[ImGuiNavLayer_COUNT];
- ImRect NavRectRel[ImGuiNavLayer_COUNT];
- ImVec2 NavPreferredScoringPosRel[ImGuiNavLayer_COUNT];
- ImGuiID NavRootFocusScopeId;
- int MemoryDrawListIdxCapacity;
- int MemoryDrawListVtxCapacity;
- bool MemoryCompacted;
- bool DockIsActive : 1;
- bool DockNodeIsVisible : 1;
- bool DockTabIsVisible : 1;
- bool DockTabWantClose : 1;
- short DockOrder;
- ImGuiWindowDockStyle DockStyle;
- ImGuiDockNode* DockNode;
- ImGuiDockNode* DockNodeAsHost;
- ImGuiID DockId;
- ImGuiItemStatusFlags DockTabItemStatusFlags;
- ImRect DockTabItemRect;
-};
-typedef enum {
- ImGuiTabBarFlags_DockNode = 1 << 20,
- ImGuiTabBarFlags_IsFocused = 1 << 21,
- ImGuiTabBarFlags_SaveSettings = 1 << 22,
-} ImGuiTabBarFlagsPrivate_;
-typedef enum {
- ImGuiTabItemFlags_SectionMask_ =
- ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_Trailing,
- ImGuiTabItemFlags_NoCloseButton = 1 << 20,
- ImGuiTabItemFlags_Button = 1 << 21,
- ImGuiTabItemFlags_Unsorted = 1 << 22,
-} ImGuiTabItemFlagsPrivate_;
-struct ImGuiTabItem {
- ImGuiID ID;
- ImGuiTabItemFlags Flags;
- ImGuiWindow* Window;
- int LastFrameVisible;
- int LastFrameSelected;
- float Offset;
- float Width;
- float ContentWidth;
- float RequestedWidth;
- ImS32 NameOffset;
- ImS16 BeginOrder;
- ImS16 IndexDuringLayout;
- bool WantClose;
-};
-typedef struct ImVector_ImGuiTabItem {
- int Size;
- int Capacity;
- ImGuiTabItem* Data;
-} ImVector_ImGuiTabItem;
-
-struct ImGuiTabBar {
- ImVector_ImGuiTabItem Tabs;
- ImGuiTabBarFlags Flags;
- ImGuiID ID;
- ImGuiID SelectedTabId;
- ImGuiID NextSelectedTabId;
- ImGuiID VisibleTabId;
- int CurrFrameVisible;
- int PrevFrameVisible;
- ImRect BarRect;
- float CurrTabsContentsHeight;
- float PrevTabsContentsHeight;
- float WidthAllTabs;
- float WidthAllTabsIdeal;
- float ScrollingAnim;
- float ScrollingTarget;
- float ScrollingTargetDistToVisibility;
- float ScrollingSpeed;
- float ScrollingRectMinX;
- float ScrollingRectMaxX;
- float SeparatorMinX;
- float SeparatorMaxX;
- ImGuiID ReorderRequestTabId;
- ImS16 ReorderRequestOffset;
- ImS8 BeginCount;
- bool WantLayout;
- bool VisibleTabWasSubmitted;
- bool TabsAddedNew;
- ImS16 TabsActiveCount;
- ImS16 LastTabItemIdx;
- float ItemSpacingY;
- ImVec2 FramePadding;
- ImVec2 BackupCursorPos;
- ImGuiTextBuffer TabsNames;
-};
-typedef ImS16 ImGuiTableColumnIdx;
-typedef ImU16 ImGuiTableDrawChannelIdx;
-struct ImGuiTableColumn {
- ImGuiTableColumnFlags Flags;
- float WidthGiven;
- float MinX;
- float MaxX;
- float WidthRequest;
- float WidthAuto;
- float StretchWeight;
- float InitStretchWeightOrWidth;
- ImRect ClipRect;
- ImGuiID UserID;
- float WorkMinX;
- float WorkMaxX;
- float ItemWidth;
- float ContentMaxXFrozen;
- float ContentMaxXUnfrozen;
- float ContentMaxXHeadersUsed;
- float ContentMaxXHeadersIdeal;
- ImS16 NameOffset;
- ImGuiTableColumnIdx DisplayOrder;
- ImGuiTableColumnIdx IndexWithinEnabledSet;
- ImGuiTableColumnIdx PrevEnabledColumn;
- ImGuiTableColumnIdx NextEnabledColumn;
- ImGuiTableColumnIdx SortOrder;
- ImGuiTableDrawChannelIdx DrawChannelCurrent;
- ImGuiTableDrawChannelIdx DrawChannelFrozen;
- ImGuiTableDrawChannelIdx DrawChannelUnfrozen;
- bool IsEnabled;
- bool IsUserEnabled;
- bool IsUserEnabledNextFrame;
- bool IsVisibleX;
- bool IsVisibleY;
- bool IsRequestOutput;
- bool IsSkipItems;
- bool IsPreserveWidthAuto;
- ImS8 NavLayerCurrent;
- ImU8 AutoFitQueue;
- ImU8 CannotSkipItemsQueue;
- ImU8 SortDirection : 2;
- ImU8 SortDirectionsAvailCount : 2;
- ImU8 SortDirectionsAvailMask : 4;
- ImU8 SortDirectionsAvailList;
-};
-typedef struct ImGuiTableCellData ImGuiTableCellData;
-struct ImGuiTableCellData {
- ImU32 BgColor;
- ImGuiTableColumnIdx Column;
-};
-struct ImGuiTableHeaderData {
- ImGuiTableColumnIdx Index;
- ImU32 TextColor;
- ImU32 BgColor0;
- ImU32 BgColor1;
-};
-struct ImGuiTableInstanceData {
- ImGuiID TableInstanceID;
- float LastOuterHeight;
- float LastTopHeadersRowHeight;
- float LastFrozenHeight;
- int HoveredRowLast;
- int HoveredRowNext;
-};
-typedef struct ImSpan_ImGuiTableColumn {
- ImGuiTableColumn* Data;
- ImGuiTableColumn* DataEnd;
-} ImSpan_ImGuiTableColumn;
-
-typedef struct ImSpan_ImGuiTableColumnIdx {
- ImGuiTableColumnIdx* Data;
- ImGuiTableColumnIdx* DataEnd;
-} ImSpan_ImGuiTableColumnIdx;
-
-typedef struct ImSpan_ImGuiTableCellData {
- ImGuiTableCellData* Data;
- ImGuiTableCellData* DataEnd;
-} ImSpan_ImGuiTableCellData;
-
-typedef struct ImVector_ImGuiTableInstanceData {
- int Size;
- int Capacity;
- ImGuiTableInstanceData* Data;
-} ImVector_ImGuiTableInstanceData;
-
-typedef struct ImVector_ImGuiTableColumnSortSpecs {
- int Size;
- int Capacity;
- ImGuiTableColumnSortSpecs* Data;
-} ImVector_ImGuiTableColumnSortSpecs;
-
-struct ImGuiTable {
- ImGuiID ID;
- ImGuiTableFlags Flags;
- void* RawData;
- ImGuiTableTempData* TempData;
- ImSpan_ImGuiTableColumn Columns;
- ImSpan_ImGuiTableColumnIdx DisplayOrderToIndex;
- ImSpan_ImGuiTableCellData RowCellData;
- ImBitArrayPtr EnabledMaskByDisplayOrder;
- ImBitArrayPtr EnabledMaskByIndex;
- ImBitArrayPtr VisibleMaskByIndex;
- ImGuiTableFlags SettingsLoadedFlags;
- int SettingsOffset;
- int LastFrameActive;
- int ColumnsCount;
- int CurrentRow;
- int CurrentColumn;
- ImS16 InstanceCurrent;
- ImS16 InstanceInteracted;
- float RowPosY1;
- float RowPosY2;
- float RowMinHeight;
- float RowCellPaddingY;
- float RowTextBaseline;
- float RowIndentOffsetX;
- ImGuiTableRowFlags RowFlags : 16;
- ImGuiTableRowFlags LastRowFlags : 16;
- int RowBgColorCounter;
- ImU32 RowBgColor[2];
- ImU32 BorderColorStrong;
- ImU32 BorderColorLight;
- float BorderX1;
- float BorderX2;
- float HostIndentX;
- float MinColumnWidth;
- float OuterPaddingX;
- float CellPaddingX;
- float CellSpacingX1;
- float CellSpacingX2;
- float InnerWidth;
- float ColumnsGivenWidth;
- float ColumnsAutoFitWidth;
- float ColumnsStretchSumWeights;
- float ResizedColumnNextWidth;
- float ResizeLockMinContentsX2;
- float RefScale;
- float AngledHeadersHeight;
- float AngledHeadersSlope;
- ImRect OuterRect;
- ImRect InnerRect;
- ImRect WorkRect;
- ImRect InnerClipRect;
- ImRect BgClipRect;
- ImRect Bg0ClipRectForDrawCmd;
- ImRect Bg2ClipRectForDrawCmd;
- ImRect HostClipRect;
- ImRect HostBackupInnerClipRect;
- ImGuiWindow* OuterWindow;
- ImGuiWindow* InnerWindow;
- ImGuiTextBuffer ColumnsNames;
- ImDrawListSplitter* DrawSplitter;
- ImGuiTableInstanceData InstanceDataFirst;
- ImVector_ImGuiTableInstanceData InstanceDataExtra;
- ImGuiTableColumnSortSpecs SortSpecsSingle;
- ImVector_ImGuiTableColumnSortSpecs SortSpecsMulti;
- ImGuiTableSortSpecs SortSpecs;
- ImGuiTableColumnIdx SortSpecsCount;
- ImGuiTableColumnIdx ColumnsEnabledCount;
- ImGuiTableColumnIdx ColumnsEnabledFixedCount;
- ImGuiTableColumnIdx DeclColumnsCount;
- ImGuiTableColumnIdx AngledHeadersCount;
- ImGuiTableColumnIdx HoveredColumnBody;
- ImGuiTableColumnIdx HoveredColumnBorder;
- ImGuiTableColumnIdx HighlightColumnHeader;
- ImGuiTableColumnIdx AutoFitSingleColumn;
- ImGuiTableColumnIdx ResizedColumn;
- ImGuiTableColumnIdx LastResizedColumn;
- ImGuiTableColumnIdx HeldHeaderColumn;
- ImGuiTableColumnIdx ReorderColumn;
- ImGuiTableColumnIdx ReorderColumnDir;
- ImGuiTableColumnIdx LeftMostEnabledColumn;
- ImGuiTableColumnIdx RightMostEnabledColumn;
- ImGuiTableColumnIdx LeftMostStretchedColumn;
- ImGuiTableColumnIdx RightMostStretchedColumn;
- ImGuiTableColumnIdx ContextPopupColumn;
- ImGuiTableColumnIdx FreezeRowsRequest;
- ImGuiTableColumnIdx FreezeRowsCount;
- ImGuiTableColumnIdx FreezeColumnsRequest;
- ImGuiTableColumnIdx FreezeColumnsCount;
- ImGuiTableColumnIdx RowCellDataCurrent;
- ImGuiTableDrawChannelIdx DummyDrawChannel;
- ImGuiTableDrawChannelIdx Bg2DrawChannelCurrent;
- ImGuiTableDrawChannelIdx Bg2DrawChannelUnfrozen;
- bool IsLayoutLocked;
- bool IsInsideRow;
- bool IsInitializing;
- bool IsSortSpecsDirty;
- bool IsUsingHeaders;
- bool IsContextPopupOpen;
- bool DisableDefaultContextMenu;
- bool IsSettingsRequestLoad;
- bool IsSettingsDirty;
- bool IsDefaultDisplayOrder;
- bool IsResetAllRequest;
- bool IsResetDisplayOrderRequest;
- bool IsUnfrozenRows;
- bool IsDefaultSizingPolicy;
- bool IsActiveIdAliveBeforeTable;
- bool IsActiveIdInTable;
- bool HasScrollbarYCurr;
- bool HasScrollbarYPrev;
- bool MemoryCompacted;
- bool HostSkipItems;
-};
-typedef struct ImVector_ImGuiTableHeaderData {
- int Size;
- int Capacity;
- ImGuiTableHeaderData* Data;
-} ImVector_ImGuiTableHeaderData;
-
-struct ImGuiTableTempData {
- int TableIndex;
- float LastTimeActive;
- float AngledHeadersExtraWidth;
- ImVector_ImGuiTableHeaderData AngledHeadersRequests;
- ImVec2 UserOuterSize;
- ImDrawListSplitter DrawSplitter;
- ImRect HostBackupWorkRect;
- ImRect HostBackupParentWorkRect;
- ImVec2 HostBackupPrevLineSize;
- ImVec2 HostBackupCurrLineSize;
- ImVec2 HostBackupCursorMaxPos;
- ImVec1 HostBackupColumnsOffset;
- float HostBackupItemWidth;
- int HostBackupItemWidthStackSize;
-};
-typedef struct ImGuiTableColumnSettings ImGuiTableColumnSettings;
-struct ImGuiTableColumnSettings {
- float WidthOrWeight;
- ImGuiID UserID;
- ImGuiTableColumnIdx Index;
- ImGuiTableColumnIdx DisplayOrder;
- ImGuiTableColumnIdx SortOrder;
- ImU8 SortDirection : 2;
- ImU8 IsEnabled : 1;
- ImU8 IsStretch : 1;
-};
-struct ImGuiTableSettings {
- ImGuiID ID;
- ImGuiTableFlags SaveFlags;
- float RefScale;
- ImGuiTableColumnIdx ColumnsCount;
- ImGuiTableColumnIdx ColumnsCountMax;
- bool WantApply;
-};
-struct ImFontBuilderIO {
- bool (*FontBuilder_Build)(ImFontAtlas* atlas);
-};
-#define IMGUI_HAS_DOCK 1
-
-#define ImDrawCallback_ResetRenderState (ImDrawCallback)(-8)
-
-#else
-struct GLFWwindow;
-struct SDL_Window;
-typedef union SDL_Event SDL_Event;
-#endif // CIMGUI_DEFINE_ENUMS_AND_STRUCTS
-
-#ifndef CIMGUI_DEFINE_ENUMS_AND_STRUCTS
-typedef struct ImGuiStorage::ImGuiStoragePair ImGuiStoragePair;
-typedef struct ImGuiTextFilter::ImGuiTextRange ImGuiTextRange;
-typedef ImStb::STB_TexteditState STB_TexteditState;
-typedef ImStb::StbTexteditRow StbTexteditRow;
-typedef ImStb::StbUndoRecord StbUndoRecord;
-typedef ImStb::StbUndoState StbUndoState;
-typedef ImChunkStream ImChunkStream_ImGuiTableSettings;
-typedef ImChunkStream ImChunkStream_ImGuiWindowSettings;
-typedef ImPool ImPool_ImGuiTabBar;
-typedef ImPool ImPool_ImGuiTable;
-typedef ImSpan ImSpan_ImGuiTableCellData;
-typedef ImSpan ImSpan_ImGuiTableColumn;
-typedef ImSpan ImSpan_ImGuiTableColumnIdx;
-typedef ImVector ImVector_ImDrawChannel;
-typedef ImVector ImVector_ImDrawCmd;
-typedef ImVector ImVector_ImDrawIdx;
-typedef ImVector ImVector_ImDrawListPtr;
-typedef ImVector ImVector_ImDrawVert;
-typedef ImVector ImVector_ImFontPtr;
-typedef ImVector ImVector_ImFontAtlasCustomRect;
-typedef ImVector ImVector_ImFontConfig;
-typedef ImVector ImVector_ImFontGlyph;
-typedef ImVector ImVector_ImGuiColorMod;
-typedef ImVector ImVector_ImGuiContextHook;
-typedef ImVector ImVector_ImGuiDockNodeSettings;
-typedef ImVector ImVector_ImGuiDockRequest;
-typedef ImVector ImVector_ImGuiFocusScopeData;
-typedef ImVector ImVector_ImGuiGroupData;
-typedef ImVector ImVector_ImGuiID;
-typedef ImVector ImVector_ImGuiInputEvent;
-typedef ImVector ImVector_ImGuiItemFlags;
-typedef ImVector ImVector_ImGuiKeyRoutingData;
-typedef ImVector ImVector_ImGuiListClipperData;
-typedef ImVector ImVector_ImGuiListClipperRange;
-typedef ImVector ImVector_ImGuiNavTreeNodeData;
-typedef ImVector ImVector_ImGuiOldColumnData;
-typedef ImVector ImVector_ImGuiOldColumns;
-typedef ImVector ImVector_ImGuiPlatformMonitor;
-typedef ImVector ImVector_ImGuiPopupData;
-typedef ImVector ImVector_ImGuiPtrOrIndex;
-typedef ImVector ImVector_ImGuiSettingsHandler;
-typedef ImVector ImVector_ImGuiShrinkWidthItem;
-typedef ImVector ImVector_ImGuiStackLevelInfo;
-typedef ImVector ImVector_ImGuiStoragePair;
-typedef ImVector ImVector_ImGuiStyleMod;
-typedef ImVector ImVector_ImGuiTabItem;
-typedef ImVector ImVector_ImGuiTableColumnSortSpecs;
-typedef ImVector ImVector_ImGuiTableHeaderData;
-typedef ImVector ImVector_ImGuiTableInstanceData;
-typedef ImVector ImVector_ImGuiTableTempData;
-typedef ImVector ImVector_ImGuiTextRange;
-typedef ImVector ImVector_ImGuiViewportPtr;
-typedef ImVector ImVector_ImGuiViewportPPtr;
-typedef ImVector ImVector_ImGuiWindowPtr;
-typedef ImVector ImVector_ImGuiWindowStackData;
-typedef ImVector ImVector_ImTextureID;
-typedef ImVector ImVector_ImU32;
-typedef ImVector ImVector_ImVec2;
-typedef ImVector ImVector_ImVec4;
-typedef ImVector ImVector_ImWchar;
-typedef ImVector ImVector_char;
-typedef ImVector ImVector_const_charPtr;
-typedef ImVector ImVector_float;
-typedef ImVector ImVector_int;
-typedef ImVector ImVector_unsigned_char;
-#endif // CIMGUI_DEFINE_ENUMS_AND_STRUCTS
-CIMGUI_API ImVec2* ImVec2_ImVec2_Nil(void);
-CIMGUI_API void ImVec2_destroy(ImVec2* self);
-CIMGUI_API ImVec2* ImVec2_ImVec2_Float(float _x, float _y);
-CIMGUI_API ImVec4* ImVec4_ImVec4_Nil(void);
-CIMGUI_API void ImVec4_destroy(ImVec4* self);
-CIMGUI_API ImVec4* ImVec4_ImVec4_Float(float _x, float _y, float _z, float _w);
-CIMGUI_API ImGuiContext* igCreateContext(ImFontAtlas* shared_font_atlas);
-CIMGUI_API void igDestroyContext(ImGuiContext* ctx);
-CIMGUI_API ImGuiContext* igGetCurrentContext(void);
-CIMGUI_API void igSetCurrentContext(ImGuiContext* ctx);
-CIMGUI_API ImGuiIO* igGetIO(void);
-CIMGUI_API ImGuiStyle* igGetStyle(void);
-CIMGUI_API void igNewFrame(void);
-CIMGUI_API void igEndFrame(void);
-CIMGUI_API void igRender(void);
-CIMGUI_API ImDrawData* igGetDrawData(void);
-CIMGUI_API void igShowDemoWindow(bool* p_open);
-CIMGUI_API void igShowMetricsWindow(bool* p_open);
-CIMGUI_API void igShowDebugLogWindow(bool* p_open);
-CIMGUI_API void igShowIDStackToolWindow(bool* p_open);
-CIMGUI_API void igShowAboutWindow(bool* p_open);
-CIMGUI_API void igShowStyleEditor(ImGuiStyle* ref);
-CIMGUI_API bool igShowStyleSelector(const char* label);
-CIMGUI_API void igShowFontSelector(const char* label);
-CIMGUI_API void igShowUserGuide(void);
-CIMGUI_API const char* igGetVersion(void);
-CIMGUI_API void igStyleColorsDark(ImGuiStyle* dst);
-CIMGUI_API void igStyleColorsLight(ImGuiStyle* dst);
-CIMGUI_API void igStyleColorsClassic(ImGuiStyle* dst);
-CIMGUI_API bool igBegin(const char* name, bool* p_open, ImGuiWindowFlags flags);
-CIMGUI_API void igEnd(void);
-CIMGUI_API bool igBeginChild_Str(const char* str_id,
- const ImVec2 size,
- ImGuiChildFlags child_flags,
- ImGuiWindowFlags window_flags);
-CIMGUI_API bool igBeginChild_ID(ImGuiID id,
- const ImVec2 size,
- ImGuiChildFlags child_flags,
- ImGuiWindowFlags window_flags);
-CIMGUI_API void igEndChild(void);
-CIMGUI_API bool igIsWindowAppearing(void);
-CIMGUI_API bool igIsWindowCollapsed(void);
-CIMGUI_API bool igIsWindowFocused(ImGuiFocusedFlags flags);
-CIMGUI_API bool igIsWindowHovered(ImGuiHoveredFlags flags);
-CIMGUI_API ImDrawList* igGetWindowDrawList(void);
-CIMGUI_API float igGetWindowDpiScale(void);
-CIMGUI_API void igGetWindowPos(ImVec2* pOut);
-CIMGUI_API void igGetWindowSize(ImVec2* pOut);
-CIMGUI_API float igGetWindowWidth(void);
-CIMGUI_API float igGetWindowHeight(void);
-CIMGUI_API ImGuiViewport* igGetWindowViewport(void);
-CIMGUI_API void igSetNextWindowPos(const ImVec2 pos,
- ImGuiCond cond,
- const ImVec2 pivot);
-CIMGUI_API void igSetNextWindowSize(const ImVec2 size, ImGuiCond cond);
-CIMGUI_API void igSetNextWindowSizeConstraints(
- const ImVec2 size_min,
- const ImVec2 size_max,
- ImGuiSizeCallback custom_callback,
- void* custom_callback_data);
-CIMGUI_API void igSetNextWindowContentSize(const ImVec2 size);
-CIMGUI_API void igSetNextWindowCollapsed(bool collapsed, ImGuiCond cond);
-CIMGUI_API void igSetNextWindowFocus(void);
-CIMGUI_API void igSetNextWindowScroll(const ImVec2 scroll);
-CIMGUI_API void igSetNextWindowBgAlpha(float alpha);
-CIMGUI_API void igSetNextWindowViewport(ImGuiID viewport_id);
-CIMGUI_API void igSetWindowPos_Vec2(const ImVec2 pos, ImGuiCond cond);
-CIMGUI_API void igSetWindowSize_Vec2(const ImVec2 size, ImGuiCond cond);
-CIMGUI_API void igSetWindowCollapsed_Bool(bool collapsed, ImGuiCond cond);
-CIMGUI_API void igSetWindowFocus_Nil(void);
-CIMGUI_API void igSetWindowFontScale(float scale);
-CIMGUI_API void igSetWindowPos_Str(const char* name,
- const ImVec2 pos,
- ImGuiCond cond);
-CIMGUI_API void igSetWindowSize_Str(const char* name,
- const ImVec2 size,
- ImGuiCond cond);
-CIMGUI_API void igSetWindowCollapsed_Str(const char* name,
- bool collapsed,
- ImGuiCond cond);
-CIMGUI_API void igSetWindowFocus_Str(const char* name);
-CIMGUI_API void igGetContentRegionAvail(ImVec2* pOut);
-CIMGUI_API void igGetContentRegionMax(ImVec2* pOut);
-CIMGUI_API void igGetWindowContentRegionMin(ImVec2* pOut);
-CIMGUI_API void igGetWindowContentRegionMax(ImVec2* pOut);
-CIMGUI_API float igGetScrollX(void);
-CIMGUI_API float igGetScrollY(void);
-CIMGUI_API void igSetScrollX_Float(float scroll_x);
-CIMGUI_API void igSetScrollY_Float(float scroll_y);
-CIMGUI_API float igGetScrollMaxX(void);
-CIMGUI_API float igGetScrollMaxY(void);
-CIMGUI_API void igSetScrollHereX(float center_x_ratio);
-CIMGUI_API void igSetScrollHereY(float center_y_ratio);
-CIMGUI_API void igSetScrollFromPosX_Float(float local_x, float center_x_ratio);
-CIMGUI_API void igSetScrollFromPosY_Float(float local_y, float center_y_ratio);
-CIMGUI_API void igPushFont(ImFont* font);
-CIMGUI_API void igPopFont(void);
-CIMGUI_API void igPushStyleColor_U32(ImGuiCol idx, ImU32 col);
-CIMGUI_API void igPushStyleColor_Vec4(ImGuiCol idx, const ImVec4 col);
-CIMGUI_API void igPopStyleColor(int count);
-CIMGUI_API void igPushStyleVar_Float(ImGuiStyleVar idx, float val);
-CIMGUI_API void igPushStyleVar_Vec2(ImGuiStyleVar idx, const ImVec2 val);
-CIMGUI_API void igPopStyleVar(int count);
-CIMGUI_API void igPushTabStop(bool tab_stop);
-CIMGUI_API void igPopTabStop(void);
-CIMGUI_API void igPushButtonRepeat(bool repeat);
-CIMGUI_API void igPopButtonRepeat(void);
-CIMGUI_API void igPushItemWidth(float item_width);
-CIMGUI_API void igPopItemWidth(void);
-CIMGUI_API void igSetNextItemWidth(float item_width);
-CIMGUI_API float igCalcItemWidth(void);
-CIMGUI_API void igPushTextWrapPos(float wrap_local_pos_x);
-CIMGUI_API void igPopTextWrapPos(void);
-CIMGUI_API ImFont* igGetFont(void);
-CIMGUI_API float igGetFontSize(void);
-CIMGUI_API void igGetFontTexUvWhitePixel(ImVec2* pOut);
-CIMGUI_API ImU32 igGetColorU32_Col(ImGuiCol idx, float alpha_mul);
-CIMGUI_API ImU32 igGetColorU32_Vec4(const ImVec4 col);
-CIMGUI_API ImU32 igGetColorU32_U32(ImU32 col, float alpha_mul);
-CIMGUI_API const ImVec4* igGetStyleColorVec4(ImGuiCol idx);
-CIMGUI_API void igGetCursorScreenPos(ImVec2* pOut);
-CIMGUI_API void igSetCursorScreenPos(const ImVec2 pos);
-CIMGUI_API void igGetCursorPos(ImVec2* pOut);
-CIMGUI_API float igGetCursorPosX(void);
-CIMGUI_API float igGetCursorPosY(void);
-CIMGUI_API void igSetCursorPos(const ImVec2 local_pos);
-CIMGUI_API void igSetCursorPosX(float local_x);
-CIMGUI_API void igSetCursorPosY(float local_y);
-CIMGUI_API void igGetCursorStartPos(ImVec2* pOut);
-CIMGUI_API void igSeparator(void);
-CIMGUI_API void igSameLine(float offset_from_start_x, float spacing);
-CIMGUI_API void igNewLine(void);
-CIMGUI_API void igSpacing(void);
-CIMGUI_API void igDummy(const ImVec2 size);
-CIMGUI_API void igIndent(float indent_w);
-CIMGUI_API void igUnindent(float indent_w);
-CIMGUI_API void igBeginGroup(void);
-CIMGUI_API void igEndGroup(void);
-CIMGUI_API void igAlignTextToFramePadding(void);
-CIMGUI_API float igGetTextLineHeight(void);
-CIMGUI_API float igGetTextLineHeightWithSpacing(void);
-CIMGUI_API float igGetFrameHeight(void);
-CIMGUI_API float igGetFrameHeightWithSpacing(void);
-CIMGUI_API void igPushID_Str(const char* str_id);
-CIMGUI_API void igPushID_StrStr(const char* str_id_begin,
- const char* str_id_end);
-CIMGUI_API void igPushID_Ptr(const void* ptr_id);
-CIMGUI_API void igPushID_Int(int int_id);
-CIMGUI_API void igPopID(void);
-CIMGUI_API ImGuiID igGetID_Str(const char* str_id);
-CIMGUI_API ImGuiID igGetID_StrStr(const char* str_id_begin,
- const char* str_id_end);
-CIMGUI_API ImGuiID igGetID_Ptr(const void* ptr_id);
-CIMGUI_API void igTextUnformatted(const char* text, const char* text_end);
-CIMGUI_API void igText(const char* fmt, ...);
-CIMGUI_API void igTextV(const char* fmt, va_list args);
-CIMGUI_API void igTextColored(const ImVec4 col, const char* fmt, ...);
-CIMGUI_API void igTextColoredV(const ImVec4 col, const char* fmt, va_list args);
-CIMGUI_API void igTextDisabled(const char* fmt, ...);
-CIMGUI_API void igTextDisabledV(const char* fmt, va_list args);
-CIMGUI_API void igTextWrapped(const char* fmt, ...);
-CIMGUI_API void igTextWrappedV(const char* fmt, va_list args);
-CIMGUI_API void igLabelText(const char* label, const char* fmt, ...);
-CIMGUI_API void igLabelTextV(const char* label, const char* fmt, va_list args);
-CIMGUI_API void igBulletText(const char* fmt, ...);
-CIMGUI_API void igBulletTextV(const char* fmt, va_list args);
-CIMGUI_API void igSeparatorText(const char* label);
-CIMGUI_API bool igButton(const char* label, const ImVec2 size);
-CIMGUI_API bool igSmallButton(const char* label);
-CIMGUI_API bool igInvisibleButton(const char* str_id,
- const ImVec2 size,
- ImGuiButtonFlags flags);
-CIMGUI_API bool igArrowButton(const char* str_id, ImGuiDir dir);
-CIMGUI_API bool igCheckbox(const char* label, bool* v);
-CIMGUI_API bool igCheckboxFlags_IntPtr(const char* label,
- int* flags,
- int flags_value);
-CIMGUI_API bool igCheckboxFlags_UintPtr(const char* label,
- unsigned int* flags,
- unsigned int flags_value);
-CIMGUI_API bool igRadioButton_Bool(const char* label, bool active);
-CIMGUI_API bool igRadioButton_IntPtr(const char* label, int* v, int v_button);
-CIMGUI_API void igProgressBar(float fraction,
- const ImVec2 size_arg,
- const char* overlay);
-CIMGUI_API void igBullet(void);
-CIMGUI_API void igImage(ImTextureID user_texture_id,
- const ImVec2 image_size,
- const ImVec2 uv0,
- const ImVec2 uv1,
- const ImVec4 tint_col,
- const ImVec4 border_col);
-CIMGUI_API bool igImageButton(const char* str_id,
- ImTextureID user_texture_id,
- const ImVec2 image_size,
- const ImVec2 uv0,
- const ImVec2 uv1,
- const ImVec4 bg_col,
- const ImVec4 tint_col);
-CIMGUI_API bool igBeginCombo(const char* label,
- const char* preview_value,
- ImGuiComboFlags flags);
-CIMGUI_API void igEndCombo(void);
-CIMGUI_API bool igCombo_Str_arr(const char* label,
- int* current_item,
- const char* const items[],
- int items_count,
- int popup_max_height_in_items);
-CIMGUI_API bool igCombo_Str(const char* label,
- int* current_item,
- const char* items_separated_by_zeros,
- int popup_max_height_in_items);
-CIMGUI_API bool igCombo_FnStrPtr(const char* label,
- int* current_item,
- const char* (*getter)(void* user_data,
- int idx),
- void* user_data,
- int items_count,
- int popup_max_height_in_items);
-CIMGUI_API bool igDragFloat(const char* label,
- float* v,
- float v_speed,
- float v_min,
- float v_max,
- const char* format,
- ImGuiSliderFlags flags);
-CIMGUI_API bool igDragFloat2(const char* label,
- float v[2],
- float v_speed,
- float v_min,
- float v_max,
- const char* format,
- ImGuiSliderFlags flags);
-CIMGUI_API bool igDragFloat3(const char* label,
- float v[3],
- float v_speed,
- float v_min,
- float v_max,
- const char* format,
- ImGuiSliderFlags flags);
-CIMGUI_API bool igDragFloat4(const char* label,
- float v[4],
- float v_speed,
- float v_min,
- float v_max,
- const char* format,
- ImGuiSliderFlags flags);
-CIMGUI_API bool igDragFloatRange2(const char* label,
- float* v_current_min,
- float* v_current_max,
- float v_speed,
- float v_min,
- float v_max,
- const char* format,
- const char* format_max,
- ImGuiSliderFlags flags);
-CIMGUI_API bool igDragInt(const char* label,
- int* v,
- float v_speed,
- int v_min,
- int v_max,
- const char* format,
- ImGuiSliderFlags flags);
-CIMGUI_API bool igDragInt2(const char* label,
- int v[2],
- float v_speed,
- int v_min,
- int v_max,
- const char* format,
- ImGuiSliderFlags flags);
-CIMGUI_API bool igDragInt3(const char* label,
- int v[3],
- float v_speed,
- int v_min,
- int v_max,
- const char* format,
- ImGuiSliderFlags flags);
-CIMGUI_API bool igDragInt4(const char* label,
- int v[4],
- float v_speed,
- int v_min,
- int v_max,
- const char* format,
- ImGuiSliderFlags flags);
-CIMGUI_API bool igDragIntRange2(const char* label,
- int* v_current_min,
- int* v_current_max,
- float v_speed,
- int v_min,
- int v_max,
- const char* format,
- const char* format_max,
- ImGuiSliderFlags flags);
-CIMGUI_API bool igDragScalar(const char* label,
- ImGuiDataType data_type,
- void* p_data,
- float v_speed,
- const void* p_min,
- const void* p_max,
- const char* format,
- ImGuiSliderFlags flags);
-CIMGUI_API bool igDragScalarN(const char* label,
- ImGuiDataType data_type,
- void* p_data,
- int components,
- float v_speed,
- const void* p_min,
- const void* p_max,
- const char* format,
- ImGuiSliderFlags flags);
-CIMGUI_API bool igSliderFloat(const char* label,
- float* v,
- float v_min,
- float v_max,
- const char* format,
- ImGuiSliderFlags flags);
-CIMGUI_API bool igSliderFloat2(const char* label,
- float v[2],
- float v_min,
- float v_max,
- const char* format,
- ImGuiSliderFlags flags);
-CIMGUI_API bool igSliderFloat3(const char* label,
- float v[3],
- float v_min,
- float v_max,
- const char* format,
- ImGuiSliderFlags flags);
-CIMGUI_API bool igSliderFloat4(const char* label,
- float v[4],
- float v_min,
- float v_max,
- const char* format,
- ImGuiSliderFlags flags);
-CIMGUI_API bool igSliderAngle(const char* label,
- float* v_rad,
- float v_degrees_min,
- float v_degrees_max,
- const char* format,
- ImGuiSliderFlags flags);
-CIMGUI_API bool igSliderInt(const char* label,
- int* v,
- int v_min,
- int v_max,
- const char* format,
- ImGuiSliderFlags flags);
-CIMGUI_API bool igSliderInt2(const char* label,
- int v[2],
- int v_min,
- int v_max,
- const char* format,
- ImGuiSliderFlags flags);
-CIMGUI_API bool igSliderInt3(const char* label,
- int v[3],
- int v_min,
- int v_max,
- const char* format,
- ImGuiSliderFlags flags);
-CIMGUI_API bool igSliderInt4(const char* label,
- int v[4],
- int v_min,
- int v_max,
- const char* format,
- ImGuiSliderFlags flags);
-CIMGUI_API bool igSliderScalar(const char* label,
- ImGuiDataType data_type,
- void* p_data,
- const void* p_min,
- const void* p_max,
- const char* format,
- ImGuiSliderFlags flags);
-CIMGUI_API bool igSliderScalarN(const char* label,
- ImGuiDataType data_type,
- void* p_data,
- int components,
- const void* p_min,
- const void* p_max,
- const char* format,
- ImGuiSliderFlags flags);
-CIMGUI_API bool igVSliderFloat(const char* label,
- const ImVec2 size,
- float* v,
- float v_min,
- float v_max,
- const char* format,
- ImGuiSliderFlags flags);
-CIMGUI_API bool igVSliderInt(const char* label,
- const ImVec2 size,
- int* v,
- int v_min,
- int v_max,
- const char* format,
- ImGuiSliderFlags flags);
-CIMGUI_API bool igVSliderScalar(const char* label,
- const ImVec2 size,
- ImGuiDataType data_type,
- void* p_data,
- const void* p_min,
- const void* p_max,
- const char* format,
- ImGuiSliderFlags flags);
-CIMGUI_API bool igInputText(const char* label,
- char* buf,
- size_t buf_size,
- ImGuiInputTextFlags flags,
- ImGuiInputTextCallback callback,
- void* user_data);
-CIMGUI_API bool igInputTextMultiline(const char* label,
- char* buf,
- size_t buf_size,
- const ImVec2 size,
- ImGuiInputTextFlags flags,
- ImGuiInputTextCallback callback,
- void* user_data);
-CIMGUI_API bool igInputTextWithHint(const char* label,
- const char* hint,
- char* buf,
- size_t buf_size,
- ImGuiInputTextFlags flags,
- ImGuiInputTextCallback callback,
- void* user_data);
-CIMGUI_API bool igInputFloat(const char* label,
- float* v,
- float step,
- float step_fast,
- const char* format,
- ImGuiInputTextFlags flags);
-CIMGUI_API bool igInputFloat2(const char* label,
- float v[2],
- const char* format,
- ImGuiInputTextFlags flags);
-CIMGUI_API bool igInputFloat3(const char* label,
- float v[3],
- const char* format,
- ImGuiInputTextFlags flags);
-CIMGUI_API bool igInputFloat4(const char* label,
- float v[4],
- const char* format,
- ImGuiInputTextFlags flags);
-CIMGUI_API bool igInputInt(const char* label,
- int* v,
- int step,
- int step_fast,
- ImGuiInputTextFlags flags);
-CIMGUI_API bool igInputInt2(const char* label,
- int v[2],
- ImGuiInputTextFlags flags);
-CIMGUI_API bool igInputInt3(const char* label,
- int v[3],
- ImGuiInputTextFlags flags);
-CIMGUI_API bool igInputInt4(const char* label,
- int v[4],
- ImGuiInputTextFlags flags);
-CIMGUI_API bool igInputDouble(const char* label,
- double* v,
- double step,
- double step_fast,
- const char* format,
- ImGuiInputTextFlags flags);
-CIMGUI_API bool igInputScalar(const char* label,
- ImGuiDataType data_type,
- void* p_data,
- const void* p_step,
- const void* p_step_fast,
- const char* format,
- ImGuiInputTextFlags flags);
-CIMGUI_API bool igInputScalarN(const char* label,
- ImGuiDataType data_type,
- void* p_data,
- int components,
- const void* p_step,
- const void* p_step_fast,
- const char* format,
- ImGuiInputTextFlags flags);
-CIMGUI_API bool igColorEdit3(const char* label,
- float col[3],
- ImGuiColorEditFlags flags);
-CIMGUI_API bool igColorEdit4(const char* label,
- float col[4],
- ImGuiColorEditFlags flags);
-CIMGUI_API bool igColorPicker3(const char* label,
- float col[3],
- ImGuiColorEditFlags flags);
-CIMGUI_API bool igColorPicker4(const char* label,
- float col[4],
- ImGuiColorEditFlags flags,
- const float* ref_col);
-CIMGUI_API bool igColorButton(const char* desc_id,
- const ImVec4 col,
- ImGuiColorEditFlags flags,
- const ImVec2 size);
-CIMGUI_API void igSetColorEditOptions(ImGuiColorEditFlags flags);
-CIMGUI_API bool igTreeNode_Str(const char* label);
-CIMGUI_API bool igTreeNode_StrStr(const char* str_id, const char* fmt, ...);
-CIMGUI_API bool igTreeNode_Ptr(const void* ptr_id, const char* fmt, ...);
-CIMGUI_API bool igTreeNodeV_Str(const char* str_id,
- const char* fmt,
- va_list args);
-CIMGUI_API bool igTreeNodeV_Ptr(const void* ptr_id,
- const char* fmt,
- va_list args);
-CIMGUI_API bool igTreeNodeEx_Str(const char* label, ImGuiTreeNodeFlags flags);
-CIMGUI_API bool igTreeNodeEx_StrStr(const char* str_id,
- ImGuiTreeNodeFlags flags,
- const char* fmt,
- ...);
-CIMGUI_API bool igTreeNodeEx_Ptr(const void* ptr_id,
- ImGuiTreeNodeFlags flags,
- const char* fmt,
- ...);
-CIMGUI_API bool igTreeNodeExV_Str(const char* str_id,
- ImGuiTreeNodeFlags flags,
- const char* fmt,
- va_list args);
-CIMGUI_API bool igTreeNodeExV_Ptr(const void* ptr_id,
- ImGuiTreeNodeFlags flags,
- const char* fmt,
- va_list args);
-CIMGUI_API void igTreePush_Str(const char* str_id);
-CIMGUI_API void igTreePush_Ptr(const void* ptr_id);
-CIMGUI_API void igTreePop(void);
-CIMGUI_API float igGetTreeNodeToLabelSpacing(void);
-CIMGUI_API bool igCollapsingHeader_TreeNodeFlags(const char* label,
- ImGuiTreeNodeFlags flags);
-CIMGUI_API bool igCollapsingHeader_BoolPtr(const char* label,
- bool* p_visible,
- ImGuiTreeNodeFlags flags);
-CIMGUI_API void igSetNextItemOpen(bool is_open, ImGuiCond cond);
-CIMGUI_API bool igSelectable_Bool(const char* label,
- bool selected,
- ImGuiSelectableFlags flags,
- const ImVec2 size);
-CIMGUI_API bool igSelectable_BoolPtr(const char* label,
- bool* p_selected,
- ImGuiSelectableFlags flags,
- const ImVec2 size);
-CIMGUI_API bool igBeginListBox(const char* label, const ImVec2 size);
-CIMGUI_API void igEndListBox(void);
-CIMGUI_API bool igListBox_Str_arr(const char* label,
- int* current_item,
- const char* const items[],
- int items_count,
- int height_in_items);
-CIMGUI_API bool igListBox_FnStrPtr(const char* label,
- int* current_item,
- const char* (*getter)(void* user_data,
- int idx),
- void* user_data,
- int items_count,
- int height_in_items);
-CIMGUI_API void igPlotLines_FloatPtr(const char* label,
- const float* values,
- int values_count,
- int values_offset,
- const char* overlay_text,
- float scale_min,
- float scale_max,
- ImVec2 graph_size,
- int stride);
-CIMGUI_API void igPlotLines_FnFloatPtr(const char* label,
- float (*values_getter)(void* data,
- int idx),
- void* data,
- int values_count,
- int values_offset,
- const char* overlay_text,
- float scale_min,
- float scale_max,
- ImVec2 graph_size);
-CIMGUI_API void igPlotHistogram_FloatPtr(const char* label,
- const float* values,
- int values_count,
- int values_offset,
- const char* overlay_text,
- float scale_min,
- float scale_max,
- ImVec2 graph_size,
- int stride);
-CIMGUI_API void igPlotHistogram_FnFloatPtr(const char* label,
- float (*values_getter)(void* data,
- int idx),
- void* data,
- int values_count,
- int values_offset,
- const char* overlay_text,
- float scale_min,
- float scale_max,
- ImVec2 graph_size);
-CIMGUI_API void igValue_Bool(const char* prefix, bool b);
-CIMGUI_API void igValue_Int(const char* prefix, int v);
-CIMGUI_API void igValue_Uint(const char* prefix, unsigned int v);
-CIMGUI_API void igValue_Float(const char* prefix,
- float v,
- const char* float_format);
-CIMGUI_API bool igBeginMenuBar(void);
-CIMGUI_API void igEndMenuBar(void);
-CIMGUI_API bool igBeginMainMenuBar(void);
-CIMGUI_API void igEndMainMenuBar(void);
-CIMGUI_API bool igBeginMenu(const char* label, bool enabled);
-CIMGUI_API void igEndMenu(void);
-CIMGUI_API bool igMenuItem_Bool(const char* label,
- const char* shortcut,
- bool selected,
- bool enabled);
-CIMGUI_API bool igMenuItem_BoolPtr(const char* label,
- const char* shortcut,
- bool* p_selected,
- bool enabled);
-CIMGUI_API bool igBeginTooltip(void);
-CIMGUI_API void igEndTooltip(void);
-CIMGUI_API void igSetTooltip(const char* fmt, ...);
-CIMGUI_API void igSetTooltipV(const char* fmt, va_list args);
-CIMGUI_API bool igBeginItemTooltip(void);
-CIMGUI_API void igSetItemTooltip(const char* fmt, ...);
-CIMGUI_API void igSetItemTooltipV(const char* fmt, va_list args);
-CIMGUI_API bool igBeginPopup(const char* str_id, ImGuiWindowFlags flags);
-CIMGUI_API bool igBeginPopupModal(const char* name,
- bool* p_open,
- ImGuiWindowFlags flags);
-CIMGUI_API void igEndPopup(void);
-CIMGUI_API void igOpenPopup_Str(const char* str_id,
- ImGuiPopupFlags popup_flags);
-CIMGUI_API void igOpenPopup_ID(ImGuiID id, ImGuiPopupFlags popup_flags);
-CIMGUI_API void igOpenPopupOnItemClick(const char* str_id,
- ImGuiPopupFlags popup_flags);
-CIMGUI_API void igCloseCurrentPopup(void);
-CIMGUI_API bool igBeginPopupContextItem(const char* str_id,
- ImGuiPopupFlags popup_flags);
-CIMGUI_API bool igBeginPopupContextWindow(const char* str_id,
- ImGuiPopupFlags popup_flags);
-CIMGUI_API bool igBeginPopupContextVoid(const char* str_id,
- ImGuiPopupFlags popup_flags);
-CIMGUI_API bool igIsPopupOpen_Str(const char* str_id, ImGuiPopupFlags flags);
-CIMGUI_API bool igBeginTable(const char* str_id,
- int column,
- ImGuiTableFlags flags,
- const ImVec2 outer_size,
- float inner_width);
-CIMGUI_API void igEndTable(void);
-CIMGUI_API void igTableNextRow(ImGuiTableRowFlags row_flags,
- float min_row_height);
-CIMGUI_API bool igTableNextColumn(void);
-CIMGUI_API bool igTableSetColumnIndex(int column_n);
-CIMGUI_API void igTableSetupColumn(const char* label,
- ImGuiTableColumnFlags flags,
- float init_width_or_weight,
- ImGuiID user_id);
-CIMGUI_API void igTableSetupScrollFreeze(int cols, int rows);
-CIMGUI_API void igTableHeader(const char* label);
-CIMGUI_API void igTableHeadersRow(void);
-CIMGUI_API void igTableAngledHeadersRow(void);
-CIMGUI_API ImGuiTableSortSpecs* igTableGetSortSpecs(void);
-CIMGUI_API int igTableGetColumnCount(void);
-CIMGUI_API int igTableGetColumnIndex(void);
-CIMGUI_API int igTableGetRowIndex(void);
-CIMGUI_API const char* igTableGetColumnName_Int(int column_n);
-CIMGUI_API ImGuiTableColumnFlags igTableGetColumnFlags(int column_n);
-CIMGUI_API void igTableSetColumnEnabled(int column_n, bool v);
-CIMGUI_API void igTableSetBgColor(ImGuiTableBgTarget target,
- ImU32 color,
- int column_n);
-CIMGUI_API void igColumns(int count, const char* id, bool border);
-CIMGUI_API void igNextColumn(void);
-CIMGUI_API int igGetColumnIndex(void);
-CIMGUI_API float igGetColumnWidth(int column_index);
-CIMGUI_API void igSetColumnWidth(int column_index, float width);
-CIMGUI_API float igGetColumnOffset(int column_index);
-CIMGUI_API void igSetColumnOffset(int column_index, float offset_x);
-CIMGUI_API int igGetColumnsCount(void);
-CIMGUI_API bool igBeginTabBar(const char* str_id, ImGuiTabBarFlags flags);
-CIMGUI_API void igEndTabBar(void);
-CIMGUI_API bool igBeginTabItem(const char* label,
- bool* p_open,
- ImGuiTabItemFlags flags);
-CIMGUI_API void igEndTabItem(void);
-CIMGUI_API bool igTabItemButton(const char* label, ImGuiTabItemFlags flags);
-CIMGUI_API void igSetTabItemClosed(const char* tab_or_docked_window_label);
-CIMGUI_API ImGuiID igDockSpace(ImGuiID id,
- const ImVec2 size,
- ImGuiDockNodeFlags flags,
- const ImGuiWindowClass* window_class);
-CIMGUI_API ImGuiID
-igDockSpaceOverViewport(const ImGuiViewport* viewport,
- ImGuiDockNodeFlags flags,
- const ImGuiWindowClass* window_class);
-CIMGUI_API void igSetNextWindowDockID(ImGuiID dock_id, ImGuiCond cond);
-CIMGUI_API void igSetNextWindowClass(const ImGuiWindowClass* window_class);
-CIMGUI_API ImGuiID igGetWindowDockID(void);
-CIMGUI_API bool igIsWindowDocked(void);
-CIMGUI_API void igLogToTTY(int auto_open_depth);
-CIMGUI_API void igLogToFile(int auto_open_depth, const char* filename);
-CIMGUI_API void igLogToClipboard(int auto_open_depth);
-CIMGUI_API void igLogFinish(void);
-CIMGUI_API void igLogButtons(void);
-CIMGUI_API void igLogTextV(const char* fmt, va_list args);
-CIMGUI_API bool igBeginDragDropSource(ImGuiDragDropFlags flags);
-CIMGUI_API bool igSetDragDropPayload(const char* type,
- const void* data,
- size_t sz,
- ImGuiCond cond);
-CIMGUI_API void igEndDragDropSource(void);
-CIMGUI_API bool igBeginDragDropTarget(void);
-CIMGUI_API const ImGuiPayload* igAcceptDragDropPayload(
- const char* type,
- ImGuiDragDropFlags flags);
-CIMGUI_API void igEndDragDropTarget(void);
-CIMGUI_API const ImGuiPayload* igGetDragDropPayload(void);
-CIMGUI_API void igBeginDisabled(bool disabled);
-CIMGUI_API void igEndDisabled(void);
-CIMGUI_API void igPushClipRect(const ImVec2 clip_rect_min,
- const ImVec2 clip_rect_max,
- bool intersect_with_current_clip_rect);
-CIMGUI_API void igPopClipRect(void);
-CIMGUI_API void igSetItemDefaultFocus(void);
-CIMGUI_API void igSetKeyboardFocusHere(int offset);
-CIMGUI_API void igSetNextItemAllowOverlap(void);
-CIMGUI_API bool igIsItemHovered(ImGuiHoveredFlags flags);
-CIMGUI_API bool igIsItemActive(void);
-CIMGUI_API bool igIsItemFocused(void);
-CIMGUI_API bool igIsItemClicked(ImGuiMouseButton mouse_button);
-CIMGUI_API bool igIsItemVisible(void);
-CIMGUI_API bool igIsItemEdited(void);
-CIMGUI_API bool igIsItemActivated(void);
-CIMGUI_API bool igIsItemDeactivated(void);
-CIMGUI_API bool igIsItemDeactivatedAfterEdit(void);
-CIMGUI_API bool igIsItemToggledOpen(void);
-CIMGUI_API bool igIsAnyItemHovered(void);
-CIMGUI_API bool igIsAnyItemActive(void);
-CIMGUI_API bool igIsAnyItemFocused(void);
-CIMGUI_API ImGuiID igGetItemID(void);
-CIMGUI_API void igGetItemRectMin(ImVec2* pOut);
-CIMGUI_API void igGetItemRectMax(ImVec2* pOut);
-CIMGUI_API void igGetItemRectSize(ImVec2* pOut);
-CIMGUI_API ImGuiViewport* igGetMainViewport(void);
-CIMGUI_API ImDrawList* igGetBackgroundDrawList_Nil(void);
-CIMGUI_API ImDrawList* igGetForegroundDrawList_Nil(void);
-CIMGUI_API ImDrawList* igGetBackgroundDrawList_ViewportPtr(
- ImGuiViewport* viewport);
-CIMGUI_API ImDrawList* igGetForegroundDrawList_ViewportPtr(
- ImGuiViewport* viewport);
-CIMGUI_API bool igIsRectVisible_Nil(const ImVec2 size);
-CIMGUI_API bool igIsRectVisible_Vec2(const ImVec2 rect_min,
- const ImVec2 rect_max);
-CIMGUI_API double igGetTime(void);
-CIMGUI_API int igGetFrameCount(void);
-CIMGUI_API ImDrawListSharedData* igGetDrawListSharedData(void);
-CIMGUI_API const char* igGetStyleColorName(ImGuiCol idx);
-CIMGUI_API void igSetStateStorage(ImGuiStorage* storage);
-CIMGUI_API ImGuiStorage* igGetStateStorage(void);
-CIMGUI_API void igCalcTextSize(ImVec2* pOut,
- const char* text,
- const char* text_end,
- bool hide_text_after_double_hash,
- float wrap_width);
-CIMGUI_API void igColorConvertU32ToFloat4(ImVec4* pOut, ImU32 in);
-CIMGUI_API ImU32 igColorConvertFloat4ToU32(const ImVec4 in);
-CIMGUI_API void igColorConvertRGBtoHSV(float r,
- float g,
- float b,
- float* out_h,
- float* out_s,
- float* out_v);
-CIMGUI_API void igColorConvertHSVtoRGB(float h,
- float s,
- float v,
- float* out_r,
- float* out_g,
- float* out_b);
-CIMGUI_API bool igIsKeyDown_Nil(ImGuiKey key);
-CIMGUI_API bool igIsKeyPressed_Bool(ImGuiKey key, bool repeat);
-CIMGUI_API bool igIsKeyReleased_Nil(ImGuiKey key);
-CIMGUI_API bool igIsKeyChordPressed_Nil(ImGuiKeyChord key_chord);
-CIMGUI_API int igGetKeyPressedAmount(ImGuiKey key,
- float repeat_delay,
- float rate);
-CIMGUI_API const char* igGetKeyName(ImGuiKey key);
-CIMGUI_API void igSetNextFrameWantCaptureKeyboard(bool want_capture_keyboard);
-CIMGUI_API bool igIsMouseDown_Nil(ImGuiMouseButton button);
-CIMGUI_API bool igIsMouseClicked_Bool(ImGuiMouseButton button, bool repeat);
-CIMGUI_API bool igIsMouseReleased_Nil(ImGuiMouseButton button);
-CIMGUI_API bool igIsMouseDoubleClicked_Nil(ImGuiMouseButton button);
-CIMGUI_API int igGetMouseClickedCount(ImGuiMouseButton button);
-CIMGUI_API bool igIsMouseHoveringRect(const ImVec2 r_min,
- const ImVec2 r_max,
- bool clip);
-CIMGUI_API bool igIsMousePosValid(const ImVec2* mouse_pos);
-CIMGUI_API bool igIsAnyMouseDown(void);
-CIMGUI_API void igGetMousePos(ImVec2* pOut);
-CIMGUI_API void igGetMousePosOnOpeningCurrentPopup(ImVec2* pOut);
-CIMGUI_API bool igIsMouseDragging(ImGuiMouseButton button,
- float lock_threshold);
-CIMGUI_API void igGetMouseDragDelta(ImVec2* pOut,
- ImGuiMouseButton button,
- float lock_threshold);
-CIMGUI_API void igResetMouseDragDelta(ImGuiMouseButton button);
-CIMGUI_API ImGuiMouseCursor igGetMouseCursor(void);
-CIMGUI_API void igSetMouseCursor(ImGuiMouseCursor cursor_type);
-CIMGUI_API void igSetNextFrameWantCaptureMouse(bool want_capture_mouse);
-CIMGUI_API const char* igGetClipboardText(void);
-CIMGUI_API void igSetClipboardText(const char* text);
-CIMGUI_API void igLoadIniSettingsFromDisk(const char* ini_filename);
-CIMGUI_API void igLoadIniSettingsFromMemory(const char* ini_data,
- size_t ini_size);
-CIMGUI_API void igSaveIniSettingsToDisk(const char* ini_filename);
-CIMGUI_API const char* igSaveIniSettingsToMemory(size_t* out_ini_size);
-CIMGUI_API void igDebugTextEncoding(const char* text);
-CIMGUI_API void igDebugFlashStyleColor(ImGuiCol idx);
-CIMGUI_API void igDebugStartItemPicker(void);
-CIMGUI_API bool igDebugCheckVersionAndDataLayout(const char* version_str,
- size_t sz_io,
- size_t sz_style,
- size_t sz_vec2,
- size_t sz_vec4,
- size_t sz_drawvert,
- size_t sz_drawidx);
-CIMGUI_API void igSetAllocatorFunctions(ImGuiMemAllocFunc alloc_func,
- ImGuiMemFreeFunc free_func,
- void* user_data);
-CIMGUI_API void igGetAllocatorFunctions(ImGuiMemAllocFunc* p_alloc_func,
- ImGuiMemFreeFunc* p_free_func,
- void** p_user_data);
-CIMGUI_API void* igMemAlloc(size_t size);
-CIMGUI_API void igMemFree(void* ptr);
-CIMGUI_API ImGuiPlatformIO* igGetPlatformIO(void);
-CIMGUI_API void igUpdatePlatformWindows(void);
-CIMGUI_API void igRenderPlatformWindowsDefault(void* platform_render_arg,
- void* renderer_render_arg);
-CIMGUI_API void igDestroyPlatformWindows(void);
-CIMGUI_API ImGuiViewport* igFindViewportByID(ImGuiID id);
-CIMGUI_API ImGuiViewport* igFindViewportByPlatformHandle(void* platform_handle);
-CIMGUI_API ImGuiTableSortSpecs* ImGuiTableSortSpecs_ImGuiTableSortSpecs(void);
-CIMGUI_API void ImGuiTableSortSpecs_destroy(ImGuiTableSortSpecs* self);
-CIMGUI_API ImGuiTableColumnSortSpecs*
-ImGuiTableColumnSortSpecs_ImGuiTableColumnSortSpecs(void);
-CIMGUI_API void ImGuiTableColumnSortSpecs_destroy(
- ImGuiTableColumnSortSpecs* self);
-CIMGUI_API ImGuiStyle* ImGuiStyle_ImGuiStyle(void);
-CIMGUI_API void ImGuiStyle_destroy(ImGuiStyle* self);
-CIMGUI_API void ImGuiStyle_ScaleAllSizes(ImGuiStyle* self, float scale_factor);
-CIMGUI_API void ImGuiIO_AddKeyEvent(ImGuiIO* self, ImGuiKey key, bool down);
-CIMGUI_API void ImGuiIO_AddKeyAnalogEvent(ImGuiIO* self,
- ImGuiKey key,
- bool down,
- float v);
-CIMGUI_API void ImGuiIO_AddMousePosEvent(ImGuiIO* self, float x, float y);
-CIMGUI_API void ImGuiIO_AddMouseButtonEvent(ImGuiIO* self,
- int button,
- bool down);
-CIMGUI_API void ImGuiIO_AddMouseWheelEvent(ImGuiIO* self,
- float wheel_x,
- float wheel_y);
-CIMGUI_API void ImGuiIO_AddMouseSourceEvent(ImGuiIO* self,
- ImGuiMouseSource source);
-CIMGUI_API void ImGuiIO_AddMouseViewportEvent(ImGuiIO* self, ImGuiID id);
-CIMGUI_API void ImGuiIO_AddFocusEvent(ImGuiIO* self, bool focused);
-CIMGUI_API void ImGuiIO_AddInputCharacter(ImGuiIO* self, unsigned int c);
-CIMGUI_API void ImGuiIO_AddInputCharacterUTF16(ImGuiIO* self, ImWchar16 c);
-CIMGUI_API void ImGuiIO_AddInputCharactersUTF8(ImGuiIO* self, const char* str);
-CIMGUI_API void ImGuiIO_SetKeyEventNativeData(ImGuiIO* self,
- ImGuiKey key,
- int native_keycode,
- int native_scancode,
- int native_legacy_index);
-CIMGUI_API void ImGuiIO_SetAppAcceptingEvents(ImGuiIO* self,
- bool accepting_events);
-CIMGUI_API void ImGuiIO_ClearEventsQueue(ImGuiIO* self);
-CIMGUI_API void ImGuiIO_ClearInputKeys(ImGuiIO* self);
-CIMGUI_API ImGuiIO* ImGuiIO_ImGuiIO(void);
-CIMGUI_API void ImGuiIO_destroy(ImGuiIO* self);
-CIMGUI_API ImGuiInputTextCallbackData*
-ImGuiInputTextCallbackData_ImGuiInputTextCallbackData(void);
-CIMGUI_API void ImGuiInputTextCallbackData_destroy(
- ImGuiInputTextCallbackData* self);
-CIMGUI_API void ImGuiInputTextCallbackData_DeleteChars(
- ImGuiInputTextCallbackData* self,
- int pos,
- int bytes_count);
-CIMGUI_API void ImGuiInputTextCallbackData_InsertChars(
- ImGuiInputTextCallbackData* self,
- int pos,
- const char* text,
- const char* text_end);
-CIMGUI_API void ImGuiInputTextCallbackData_SelectAll(
- ImGuiInputTextCallbackData* self);
-CIMGUI_API void ImGuiInputTextCallbackData_ClearSelection(
- ImGuiInputTextCallbackData* self);
-CIMGUI_API bool ImGuiInputTextCallbackData_HasSelection(
- ImGuiInputTextCallbackData* self);
-CIMGUI_API ImGuiWindowClass* ImGuiWindowClass_ImGuiWindowClass(void);
-CIMGUI_API void ImGuiWindowClass_destroy(ImGuiWindowClass* self);
-CIMGUI_API ImGuiPayload* ImGuiPayload_ImGuiPayload(void);
-CIMGUI_API void ImGuiPayload_destroy(ImGuiPayload* self);
-CIMGUI_API void ImGuiPayload_Clear(ImGuiPayload* self);
-CIMGUI_API bool ImGuiPayload_IsDataType(ImGuiPayload* self, const char* type);
-CIMGUI_API bool ImGuiPayload_IsPreview(ImGuiPayload* self);
-CIMGUI_API bool ImGuiPayload_IsDelivery(ImGuiPayload* self);
-CIMGUI_API ImGuiOnceUponAFrame* ImGuiOnceUponAFrame_ImGuiOnceUponAFrame(void);
-CIMGUI_API void ImGuiOnceUponAFrame_destroy(ImGuiOnceUponAFrame* self);
-CIMGUI_API ImGuiTextFilter* ImGuiTextFilter_ImGuiTextFilter(
- const char* default_filter);
-CIMGUI_API void ImGuiTextFilter_destroy(ImGuiTextFilter* self);
-CIMGUI_API bool ImGuiTextFilter_Draw(ImGuiTextFilter* self,
- const char* label,
- float width);
-CIMGUI_API bool ImGuiTextFilter_PassFilter(ImGuiTextFilter* self,
- const char* text,
- const char* text_end);
-CIMGUI_API void ImGuiTextFilter_Build(ImGuiTextFilter* self);
-CIMGUI_API void ImGuiTextFilter_Clear(ImGuiTextFilter* self);
-CIMGUI_API bool ImGuiTextFilter_IsActive(ImGuiTextFilter* self);
-CIMGUI_API ImGuiTextRange* ImGuiTextRange_ImGuiTextRange_Nil(void);
-CIMGUI_API void ImGuiTextRange_destroy(ImGuiTextRange* self);
-CIMGUI_API ImGuiTextRange* ImGuiTextRange_ImGuiTextRange_Str(const char* _b,
- const char* _e);
-CIMGUI_API bool ImGuiTextRange_empty(ImGuiTextRange* self);
-CIMGUI_API void ImGuiTextRange_split(ImGuiTextRange* self,
- char separator,
- ImVector_ImGuiTextRange* out);
-CIMGUI_API ImGuiTextBuffer* ImGuiTextBuffer_ImGuiTextBuffer(void);
-CIMGUI_API void ImGuiTextBuffer_destroy(ImGuiTextBuffer* self);
-CIMGUI_API const char* ImGuiTextBuffer_begin(ImGuiTextBuffer* self);
-CIMGUI_API const char* ImGuiTextBuffer_end(ImGuiTextBuffer* self);
-CIMGUI_API int ImGuiTextBuffer_size(ImGuiTextBuffer* self);
-CIMGUI_API bool ImGuiTextBuffer_empty(ImGuiTextBuffer* self);
-CIMGUI_API void ImGuiTextBuffer_clear(ImGuiTextBuffer* self);
-CIMGUI_API void ImGuiTextBuffer_reserve(ImGuiTextBuffer* self, int capacity);
-CIMGUI_API const char* ImGuiTextBuffer_c_str(ImGuiTextBuffer* self);
-CIMGUI_API void ImGuiTextBuffer_append(ImGuiTextBuffer* self,
- const char* str,
- const char* str_end);
-CIMGUI_API void ImGuiTextBuffer_appendfv(ImGuiTextBuffer* self,
- const char* fmt,
- va_list args);
-CIMGUI_API ImGuiStoragePair* ImGuiStoragePair_ImGuiStoragePair_Int(ImGuiID _key,
- int _val);
-CIMGUI_API void ImGuiStoragePair_destroy(ImGuiStoragePair* self);
-CIMGUI_API ImGuiStoragePair* ImGuiStoragePair_ImGuiStoragePair_Float(
- ImGuiID _key,
- float _val);
-CIMGUI_API ImGuiStoragePair* ImGuiStoragePair_ImGuiStoragePair_Ptr(ImGuiID _key,
- void* _val);
-CIMGUI_API void ImGuiStorage_Clear(ImGuiStorage* self);
-CIMGUI_API int ImGuiStorage_GetInt(ImGuiStorage* self,
- ImGuiID key,
- int default_val);
-CIMGUI_API void ImGuiStorage_SetInt(ImGuiStorage* self, ImGuiID key, int val);
-CIMGUI_API bool ImGuiStorage_GetBool(ImGuiStorage* self,
- ImGuiID key,
- bool default_val);
-CIMGUI_API void ImGuiStorage_SetBool(ImGuiStorage* self, ImGuiID key, bool val);
-CIMGUI_API float ImGuiStorage_GetFloat(ImGuiStorage* self,
- ImGuiID key,
- float default_val);
-CIMGUI_API void ImGuiStorage_SetFloat(ImGuiStorage* self,
- ImGuiID key,
- float val);
-CIMGUI_API void* ImGuiStorage_GetVoidPtr(ImGuiStorage* self, ImGuiID key);
-CIMGUI_API void ImGuiStorage_SetVoidPtr(ImGuiStorage* self,
- ImGuiID key,
- void* val);
-CIMGUI_API int* ImGuiStorage_GetIntRef(ImGuiStorage* self,
- ImGuiID key,
- int default_val);
-CIMGUI_API bool* ImGuiStorage_GetBoolRef(ImGuiStorage* self,
- ImGuiID key,
- bool default_val);
-CIMGUI_API float* ImGuiStorage_GetFloatRef(ImGuiStorage* self,
- ImGuiID key,
- float default_val);
-CIMGUI_API void** ImGuiStorage_GetVoidPtrRef(ImGuiStorage* self,
- ImGuiID key,
- void* default_val);
-CIMGUI_API void ImGuiStorage_BuildSortByKey(ImGuiStorage* self);
-CIMGUI_API void ImGuiStorage_SetAllInt(ImGuiStorage* self, int val);
-CIMGUI_API ImGuiListClipper* ImGuiListClipper_ImGuiListClipper(void);
-CIMGUI_API void ImGuiListClipper_destroy(ImGuiListClipper* self);
-CIMGUI_API void ImGuiListClipper_Begin(ImGuiListClipper* self,
- int items_count,
- float items_height);
-CIMGUI_API void ImGuiListClipper_End(ImGuiListClipper* self);
-CIMGUI_API bool ImGuiListClipper_Step(ImGuiListClipper* self);
-CIMGUI_API void ImGuiListClipper_IncludeItemByIndex(ImGuiListClipper* self,
- int item_index);
-CIMGUI_API void ImGuiListClipper_IncludeItemsByIndex(ImGuiListClipper* self,
- int item_begin,
- int item_end);
-CIMGUI_API ImColor* ImColor_ImColor_Nil(void);
-CIMGUI_API void ImColor_destroy(ImColor* self);
-CIMGUI_API ImColor* ImColor_ImColor_Float(float r, float g, float b, float a);
-CIMGUI_API ImColor* ImColor_ImColor_Vec4(const ImVec4 col);
-CIMGUI_API ImColor* ImColor_ImColor_Int(int r, int g, int b, int a);
-CIMGUI_API ImColor* ImColor_ImColor_U32(ImU32 rgba);
-CIMGUI_API void ImColor_SetHSV(ImColor* self,
- float h,
- float s,
- float v,
- float a);
-CIMGUI_API void ImColor_HSV(ImColor* pOut, float h, float s, float v, float a);
-CIMGUI_API ImDrawCmd* ImDrawCmd_ImDrawCmd(void);
-CIMGUI_API void ImDrawCmd_destroy(ImDrawCmd* self);
-CIMGUI_API ImTextureID ImDrawCmd_GetTexID(ImDrawCmd* self);
-CIMGUI_API ImDrawListSplitter* ImDrawListSplitter_ImDrawListSplitter(void);
-CIMGUI_API void ImDrawListSplitter_destroy(ImDrawListSplitter* self);
-CIMGUI_API void ImDrawListSplitter_Clear(ImDrawListSplitter* self);
-CIMGUI_API void ImDrawListSplitter_ClearFreeMemory(ImDrawListSplitter* self);
-CIMGUI_API void ImDrawListSplitter_Split(ImDrawListSplitter* self,
- ImDrawList* draw_list,
- int count);
-CIMGUI_API void ImDrawListSplitter_Merge(ImDrawListSplitter* self,
- ImDrawList* draw_list);
-CIMGUI_API void ImDrawListSplitter_SetCurrentChannel(ImDrawListSplitter* self,
- ImDrawList* draw_list,
- int channel_idx);
-CIMGUI_API ImDrawList* ImDrawList_ImDrawList(ImDrawListSharedData* shared_data);
-CIMGUI_API void ImDrawList_destroy(ImDrawList* self);
-CIMGUI_API void ImDrawList_PushClipRect(ImDrawList* self,
- const ImVec2 clip_rect_min,
- const ImVec2 clip_rect_max,
- bool intersect_with_current_clip_rect);
-CIMGUI_API void ImDrawList_PushClipRectFullScreen(ImDrawList* self);
-CIMGUI_API void ImDrawList_PopClipRect(ImDrawList* self);
-CIMGUI_API void ImDrawList_PushTextureID(ImDrawList* self,
- ImTextureID texture_id);
-CIMGUI_API void ImDrawList_PopTextureID(ImDrawList* self);
-CIMGUI_API void ImDrawList_GetClipRectMin(ImVec2* pOut, ImDrawList* self);
-CIMGUI_API void ImDrawList_GetClipRectMax(ImVec2* pOut, ImDrawList* self);
-CIMGUI_API void ImDrawList_AddLine(ImDrawList* self,
- const ImVec2 p1,
- const ImVec2 p2,
- ImU32 col,
- float thickness);
-CIMGUI_API void ImDrawList_AddRect(ImDrawList* self,
- const ImVec2 p_min,
- const ImVec2 p_max,
- ImU32 col,
- float rounding,
- ImDrawFlags flags,
- float thickness);
-CIMGUI_API void ImDrawList_AddRectFilled(ImDrawList* self,
- const ImVec2 p_min,
- const ImVec2 p_max,
- ImU32 col,
- float rounding,
- ImDrawFlags flags);
-CIMGUI_API void ImDrawList_AddRectFilledMultiColor(ImDrawList* self,
- const ImVec2 p_min,
- const ImVec2 p_max,
- ImU32 col_upr_left,
- ImU32 col_upr_right,
- ImU32 col_bot_right,
- ImU32 col_bot_left);
-CIMGUI_API void ImDrawList_AddQuad(ImDrawList* self,
- const ImVec2 p1,
- const ImVec2 p2,
- const ImVec2 p3,
- const ImVec2 p4,
- ImU32 col,
- float thickness);
-CIMGUI_API void ImDrawList_AddQuadFilled(ImDrawList* self,
- const ImVec2 p1,
- const ImVec2 p2,
- const ImVec2 p3,
- const ImVec2 p4,
- ImU32 col);
-CIMGUI_API void ImDrawList_AddTriangle(ImDrawList* self,
- const ImVec2 p1,
- const ImVec2 p2,
- const ImVec2 p3,
- ImU32 col,
- float thickness);
-CIMGUI_API void ImDrawList_AddTriangleFilled(ImDrawList* self,
- const ImVec2 p1,
- const ImVec2 p2,
- const ImVec2 p3,
- ImU32 col);
-CIMGUI_API void ImDrawList_AddCircle(ImDrawList* self,
- const ImVec2 center,
- float radius,
- ImU32 col,
- int num_segments,
- float thickness);
-CIMGUI_API void ImDrawList_AddCircleFilled(ImDrawList* self,
- const ImVec2 center,
- float radius,
- ImU32 col,
- int num_segments);
-CIMGUI_API void ImDrawList_AddNgon(ImDrawList* self,
- const ImVec2 center,
- float radius,
- ImU32 col,
- int num_segments,
- float thickness);
-CIMGUI_API void ImDrawList_AddNgonFilled(ImDrawList* self,
- const ImVec2 center,
- float radius,
- ImU32 col,
- int num_segments);
-CIMGUI_API void ImDrawList_AddEllipse(ImDrawList* self,
- const ImVec2 center,
- const ImVec2 radius,
- ImU32 col,
- float rot,
- int num_segments,
- float thickness);
-CIMGUI_API void ImDrawList_AddEllipseFilled(ImDrawList* self,
- const ImVec2 center,
- const ImVec2 radius,
- ImU32 col,
- float rot,
- int num_segments);
-CIMGUI_API void ImDrawList_AddText_Vec2(ImDrawList* self,
- const ImVec2 pos,
- ImU32 col,
- const char* text_begin,
- const char* text_end);
-CIMGUI_API void ImDrawList_AddText_FontPtr(ImDrawList* self,
- const ImFont* font,
- float font_size,
- const ImVec2 pos,
- ImU32 col,
- const char* text_begin,
- const char* text_end,
- float wrap_width,
- const ImVec4* cpu_fine_clip_rect);
-CIMGUI_API void ImDrawList_AddBezierCubic(ImDrawList* self,
- const ImVec2 p1,
- const ImVec2 p2,
- const ImVec2 p3,
- const ImVec2 p4,
- ImU32 col,
- float thickness,
- int num_segments);
-CIMGUI_API void ImDrawList_AddBezierQuadratic(ImDrawList* self,
- const ImVec2 p1,
- const ImVec2 p2,
- const ImVec2 p3,
- ImU32 col,
- float thickness,
- int num_segments);
-CIMGUI_API void ImDrawList_AddPolyline(ImDrawList* self,
- const ImVec2* points,
- int num_points,
- ImU32 col,
- ImDrawFlags flags,
- float thickness);
-CIMGUI_API void ImDrawList_AddConvexPolyFilled(ImDrawList* self,
- const ImVec2* points,
- int num_points,
- ImU32 col);
-CIMGUI_API void ImDrawList_AddConcavePolyFilled(ImDrawList* self,
- const ImVec2* points,
- int num_points,
- ImU32 col);
-CIMGUI_API void ImDrawList_AddImage(ImDrawList* self,
- ImTextureID user_texture_id,
- const ImVec2 p_min,
- const ImVec2 p_max,
- const ImVec2 uv_min,
- const ImVec2 uv_max,
- ImU32 col);
-CIMGUI_API void ImDrawList_AddImageQuad(ImDrawList* self,
- ImTextureID user_texture_id,
- const ImVec2 p1,
- const ImVec2 p2,
- const ImVec2 p3,
- const ImVec2 p4,
- const ImVec2 uv1,
- const ImVec2 uv2,
- const ImVec2 uv3,
- const ImVec2 uv4,
- ImU32 col);
-CIMGUI_API void ImDrawList_AddImageRounded(ImDrawList* self,
- ImTextureID user_texture_id,
- const ImVec2 p_min,
- const ImVec2 p_max,
- const ImVec2 uv_min,
- const ImVec2 uv_max,
- ImU32 col,
- float rounding,
- ImDrawFlags flags);
-CIMGUI_API void ImDrawList_PathClear(ImDrawList* self);
-CIMGUI_API void ImDrawList_PathLineTo(ImDrawList* self, const ImVec2 pos);
-CIMGUI_API void ImDrawList_PathLineToMergeDuplicate(ImDrawList* self,
- const ImVec2 pos);
-CIMGUI_API void ImDrawList_PathFillConvex(ImDrawList* self, ImU32 col);
-CIMGUI_API void ImDrawList_PathFillConcave(ImDrawList* self, ImU32 col);
-CIMGUI_API void ImDrawList_PathStroke(ImDrawList* self,
- ImU32 col,
- ImDrawFlags flags,
- float thickness);
-CIMGUI_API void ImDrawList_PathArcTo(ImDrawList* self,
- const ImVec2 center,
- float radius,
- float a_min,
- float a_max,
- int num_segments);
-CIMGUI_API void ImDrawList_PathArcToFast(ImDrawList* self,
- const ImVec2 center,
- float radius,
- int a_min_of_12,
- int a_max_of_12);
-CIMGUI_API void ImDrawList_PathEllipticalArcTo(ImDrawList* self,
- const ImVec2 center,
- const ImVec2 radius,
- float rot,
- float a_min,
- float a_max,
- int num_segments);
-CIMGUI_API void ImDrawList_PathBezierCubicCurveTo(ImDrawList* self,
- const ImVec2 p2,
- const ImVec2 p3,
- const ImVec2 p4,
- int num_segments);
-CIMGUI_API void ImDrawList_PathBezierQuadraticCurveTo(ImDrawList* self,
- const ImVec2 p2,
- const ImVec2 p3,
- int num_segments);
-CIMGUI_API void ImDrawList_PathRect(ImDrawList* self,
- const ImVec2 rect_min,
- const ImVec2 rect_max,
- float rounding,
- ImDrawFlags flags);
-CIMGUI_API void ImDrawList_AddCallback(ImDrawList* self,
- ImDrawCallback callback,
- void* callback_data);
-CIMGUI_API void ImDrawList_AddDrawCmd(ImDrawList* self);
-CIMGUI_API ImDrawList* ImDrawList_CloneOutput(ImDrawList* self);
-CIMGUI_API void ImDrawList_ChannelsSplit(ImDrawList* self, int count);
-CIMGUI_API void ImDrawList_ChannelsMerge(ImDrawList* self);
-CIMGUI_API void ImDrawList_ChannelsSetCurrent(ImDrawList* self, int n);
-CIMGUI_API void ImDrawList_PrimReserve(ImDrawList* self,
- int idx_count,
- int vtx_count);
-CIMGUI_API void ImDrawList_PrimUnreserve(ImDrawList* self,
- int idx_count,
- int vtx_count);
-CIMGUI_API void ImDrawList_PrimRect(ImDrawList* self,
- const ImVec2 a,
- const ImVec2 b,
- ImU32 col);
-CIMGUI_API void ImDrawList_PrimRectUV(ImDrawList* self,
- const ImVec2 a,
- const ImVec2 b,
- const ImVec2 uv_a,
- const ImVec2 uv_b,
- ImU32 col);
-CIMGUI_API void ImDrawList_PrimQuadUV(ImDrawList* self,
- const ImVec2 a,
- const ImVec2 b,
- const ImVec2 c,
- const ImVec2 d,
- const ImVec2 uv_a,
- const ImVec2 uv_b,
- const ImVec2 uv_c,
- const ImVec2 uv_d,
- ImU32 col);
-CIMGUI_API void ImDrawList_PrimWriteVtx(ImDrawList* self,
- const ImVec2 pos,
- const ImVec2 uv,
- ImU32 col);
-CIMGUI_API void ImDrawList_PrimWriteIdx(ImDrawList* self, ImDrawIdx idx);
-CIMGUI_API void ImDrawList_PrimVtx(ImDrawList* self,
- const ImVec2 pos,
- const ImVec2 uv,
- ImU32 col);
-CIMGUI_API void ImDrawList__ResetForNewFrame(ImDrawList* self);
-CIMGUI_API void ImDrawList__ClearFreeMemory(ImDrawList* self);
-CIMGUI_API void ImDrawList__PopUnusedDrawCmd(ImDrawList* self);
-CIMGUI_API void ImDrawList__TryMergeDrawCmds(ImDrawList* self);
-CIMGUI_API void ImDrawList__OnChangedClipRect(ImDrawList* self);
-CIMGUI_API void ImDrawList__OnChangedTextureID(ImDrawList* self);
-CIMGUI_API void ImDrawList__OnChangedVtxOffset(ImDrawList* self);
-CIMGUI_API int ImDrawList__CalcCircleAutoSegmentCount(ImDrawList* self,
- float radius);
-CIMGUI_API void ImDrawList__PathArcToFastEx(ImDrawList* self,
- const ImVec2 center,
- float radius,
- int a_min_sample,
- int a_max_sample,
- int a_step);
-CIMGUI_API void ImDrawList__PathArcToN(ImDrawList* self,
- const ImVec2 center,
- float radius,
- float a_min,
- float a_max,
- int num_segments);
-CIMGUI_API ImDrawData* ImDrawData_ImDrawData(void);
-CIMGUI_API void ImDrawData_destroy(ImDrawData* self);
-CIMGUI_API void ImDrawData_Clear(ImDrawData* self);
-CIMGUI_API void ImDrawData_AddDrawList(ImDrawData* self, ImDrawList* draw_list);
-CIMGUI_API void ImDrawData_DeIndexAllBuffers(ImDrawData* self);
-CIMGUI_API void ImDrawData_ScaleClipRects(ImDrawData* self,
- const ImVec2 fb_scale);
-CIMGUI_API ImFontConfig* ImFontConfig_ImFontConfig(void);
-CIMGUI_API void ImFontConfig_destroy(ImFontConfig* self);
-CIMGUI_API ImFontGlyphRangesBuilder*
-ImFontGlyphRangesBuilder_ImFontGlyphRangesBuilder(void);
-CIMGUI_API void ImFontGlyphRangesBuilder_destroy(
- ImFontGlyphRangesBuilder* self);
-CIMGUI_API void ImFontGlyphRangesBuilder_Clear(ImFontGlyphRangesBuilder* self);
-CIMGUI_API bool ImFontGlyphRangesBuilder_GetBit(ImFontGlyphRangesBuilder* self,
- size_t n);
-CIMGUI_API void ImFontGlyphRangesBuilder_SetBit(ImFontGlyphRangesBuilder* self,
- size_t n);
-CIMGUI_API void ImFontGlyphRangesBuilder_AddChar(ImFontGlyphRangesBuilder* self,
- ImWchar c);
-CIMGUI_API void ImFontGlyphRangesBuilder_AddText(ImFontGlyphRangesBuilder* self,
- const char* text,
- const char* text_end);
-CIMGUI_API void ImFontGlyphRangesBuilder_AddRanges(
- ImFontGlyphRangesBuilder* self,
- const ImWchar* ranges);
-CIMGUI_API void ImFontGlyphRangesBuilder_BuildRanges(
- ImFontGlyphRangesBuilder* self,
- ImVector_ImWchar* out_ranges);
-CIMGUI_API ImFontAtlasCustomRect* ImFontAtlasCustomRect_ImFontAtlasCustomRect(
- void);
-CIMGUI_API void ImFontAtlasCustomRect_destroy(ImFontAtlasCustomRect* self);
-CIMGUI_API bool ImFontAtlasCustomRect_IsPacked(ImFontAtlasCustomRect* self);
-CIMGUI_API ImFontAtlas* ImFontAtlas_ImFontAtlas(void);
-CIMGUI_API void ImFontAtlas_destroy(ImFontAtlas* self);
-CIMGUI_API ImFont* ImFontAtlas_AddFont(ImFontAtlas* self,
- const ImFontConfig* font_cfg);
-CIMGUI_API ImFont* ImFontAtlas_AddFontDefault(ImFontAtlas* self,
- const ImFontConfig* font_cfg);
-CIMGUI_API ImFont* ImFontAtlas_AddFontFromFileTTF(ImFontAtlas* self,
- const char* filename,
- float size_pixels,
- const ImFontConfig* font_cfg,
- const ImWchar* glyph_ranges);
-CIMGUI_API ImFont* ImFontAtlas_AddFontFromMemoryTTF(
- ImFontAtlas* self,
- void* font_data,
- int font_data_size,
- float size_pixels,
- const ImFontConfig* font_cfg,
- const ImWchar* glyph_ranges);
-CIMGUI_API ImFont* ImFontAtlas_AddFontFromMemoryCompressedTTF(
- ImFontAtlas* self,
- const void* compressed_font_data,
- int compressed_font_data_size,
- float size_pixels,
- const ImFontConfig* font_cfg,
- const ImWchar* glyph_ranges);
-CIMGUI_API ImFont* ImFontAtlas_AddFontFromMemoryCompressedBase85TTF(
- ImFontAtlas* self,
- const char* compressed_font_data_base85,
- float size_pixels,
- const ImFontConfig* font_cfg,
- const ImWchar* glyph_ranges);
-CIMGUI_API void ImFontAtlas_ClearInputData(ImFontAtlas* self);
-CIMGUI_API void ImFontAtlas_ClearTexData(ImFontAtlas* self);
-CIMGUI_API void ImFontAtlas_ClearFonts(ImFontAtlas* self);
-CIMGUI_API void ImFontAtlas_Clear(ImFontAtlas* self);
-CIMGUI_API bool ImFontAtlas_Build(ImFontAtlas* self);
-CIMGUI_API void ImFontAtlas_GetTexDataAsAlpha8(ImFontAtlas* self,
- unsigned char** out_pixels,
- int* out_width,
- int* out_height,
- int* out_bytes_per_pixel);
-CIMGUI_API void ImFontAtlas_GetTexDataAsRGBA32(ImFontAtlas* self,
- unsigned char** out_pixels,
- int* out_width,
- int* out_height,
- int* out_bytes_per_pixel);
-CIMGUI_API bool ImFontAtlas_IsBuilt(ImFontAtlas* self);
-CIMGUI_API void ImFontAtlas_SetTexID(ImFontAtlas* self, ImTextureID id);
-CIMGUI_API const ImWchar* ImFontAtlas_GetGlyphRangesDefault(ImFontAtlas* self);
-CIMGUI_API const ImWchar* ImFontAtlas_GetGlyphRangesGreek(ImFontAtlas* self);
-CIMGUI_API const ImWchar* ImFontAtlas_GetGlyphRangesKorean(ImFontAtlas* self);
-CIMGUI_API const ImWchar* ImFontAtlas_GetGlyphRangesJapanese(ImFontAtlas* self);
-CIMGUI_API const ImWchar* ImFontAtlas_GetGlyphRangesChineseFull(
- ImFontAtlas* self);
-CIMGUI_API const ImWchar* ImFontAtlas_GetGlyphRangesChineseSimplifiedCommon(
- ImFontAtlas* self);
-CIMGUI_API const ImWchar* ImFontAtlas_GetGlyphRangesCyrillic(ImFontAtlas* self);
-CIMGUI_API const ImWchar* ImFontAtlas_GetGlyphRangesThai(ImFontAtlas* self);
-CIMGUI_API const ImWchar* ImFontAtlas_GetGlyphRangesVietnamese(
- ImFontAtlas* self);
-CIMGUI_API int ImFontAtlas_AddCustomRectRegular(ImFontAtlas* self,
- int width,
- int height);
-CIMGUI_API int ImFontAtlas_AddCustomRectFontGlyph(ImFontAtlas* self,
- ImFont* font,
- ImWchar id,
- int width,
- int height,
- float advance_x,
- const ImVec2 offset);
-CIMGUI_API ImFontAtlasCustomRect* ImFontAtlas_GetCustomRectByIndex(
- ImFontAtlas* self,
- int index);
-CIMGUI_API void ImFontAtlas_CalcCustomRectUV(ImFontAtlas* self,
- const ImFontAtlasCustomRect* rect,
- ImVec2* out_uv_min,
- ImVec2* out_uv_max);
-CIMGUI_API bool ImFontAtlas_GetMouseCursorTexData(ImFontAtlas* self,
- ImGuiMouseCursor cursor,
- ImVec2* out_offset,
- ImVec2* out_size,
- ImVec2 out_uv_border[2],
- ImVec2 out_uv_fill[2]);
-CIMGUI_API ImFont* ImFont_ImFont(void);
-CIMGUI_API void ImFont_destroy(ImFont* self);
-CIMGUI_API const ImFontGlyph* ImFont_FindGlyph(ImFont* self, ImWchar c);
-CIMGUI_API const ImFontGlyph* ImFont_FindGlyphNoFallback(ImFont* self,
- ImWchar c);
-CIMGUI_API float ImFont_GetCharAdvance(ImFont* self, ImWchar c);
-CIMGUI_API bool ImFont_IsLoaded(ImFont* self);
-CIMGUI_API const char* ImFont_GetDebugName(ImFont* self);
-CIMGUI_API void ImFont_CalcTextSizeA(ImVec2* pOut,
- ImFont* self,
- float size,
- float max_width,
- float wrap_width,
- const char* text_begin,
- const char* text_end,
- const char** remaining);
-CIMGUI_API const char* ImFont_CalcWordWrapPositionA(ImFont* self,
- float scale,
- const char* text,
- const char* text_end,
- float wrap_width);
-CIMGUI_API void ImFont_RenderChar(ImFont* self,
- ImDrawList* draw_list,
- float size,
- const ImVec2 pos,
- ImU32 col,
- ImWchar c);
-CIMGUI_API void ImFont_RenderText(ImFont* self,
- ImDrawList* draw_list,
- float size,
- const ImVec2 pos,
- ImU32 col,
- const ImVec4 clip_rect,
- const char* text_begin,
- const char* text_end,
- float wrap_width,
- bool cpu_fine_clip);
-CIMGUI_API void ImFont_BuildLookupTable(ImFont* self);
-CIMGUI_API void ImFont_ClearOutputData(ImFont* self);
-CIMGUI_API void ImFont_GrowIndex(ImFont* self, int new_size);
-CIMGUI_API void ImFont_AddGlyph(ImFont* self,
- const ImFontConfig* src_cfg,
- ImWchar c,
- float x0,
- float y0,
- float x1,
- float y1,
- float u0,
- float v0,
- float u1,
- float v1,
- float advance_x);
-CIMGUI_API void ImFont_AddRemapChar(ImFont* self,
- ImWchar dst,
- ImWchar src,
- bool overwrite_dst);
-CIMGUI_API void ImFont_SetGlyphVisible(ImFont* self, ImWchar c, bool visible);
-CIMGUI_API bool ImFont_IsGlyphRangeUnused(ImFont* self,
- unsigned int c_begin,
- unsigned int c_last);
-CIMGUI_API ImGuiViewport* ImGuiViewport_ImGuiViewport(void);
-CIMGUI_API void ImGuiViewport_destroy(ImGuiViewport* self);
-CIMGUI_API void ImGuiViewport_GetCenter(ImVec2* pOut, ImGuiViewport* self);
-CIMGUI_API void ImGuiViewport_GetWorkCenter(ImVec2* pOut, ImGuiViewport* self);
-CIMGUI_API ImGuiPlatformIO* ImGuiPlatformIO_ImGuiPlatformIO(void);
-CIMGUI_API void ImGuiPlatformIO_destroy(ImGuiPlatformIO* self);
-CIMGUI_API ImGuiPlatformMonitor* ImGuiPlatformMonitor_ImGuiPlatformMonitor(
- void);
-CIMGUI_API void ImGuiPlatformMonitor_destroy(ImGuiPlatformMonitor* self);
-CIMGUI_API ImGuiPlatformImeData* ImGuiPlatformImeData_ImGuiPlatformImeData(
- void);
-CIMGUI_API void ImGuiPlatformImeData_destroy(ImGuiPlatformImeData* self);
-CIMGUI_API ImGuiID igImHashData(const void* data,
- size_t data_size,
- ImGuiID seed);
-CIMGUI_API ImGuiID igImHashStr(const char* data,
- size_t data_size,
- ImGuiID seed);
-CIMGUI_API void igImQsort(void* base,
- size_t count,
- size_t size_of_element,
- int (*compare_func)(void const*, void const*));
-CIMGUI_API ImU32 igImAlphaBlendColors(ImU32 col_a, ImU32 col_b);
-CIMGUI_API bool igImIsPowerOfTwo_Int(int v);
-CIMGUI_API bool igImIsPowerOfTwo_U64(ImU64 v);
-CIMGUI_API int igImUpperPowerOfTwo(int v);
-CIMGUI_API int igImStricmp(const char* str1, const char* str2);
-CIMGUI_API int igImStrnicmp(const char* str1, const char* str2, size_t count);
-CIMGUI_API void igImStrncpy(char* dst, const char* src, size_t count);
-CIMGUI_API char* igImStrdup(const char* str);
-CIMGUI_API char* igImStrdupcpy(char* dst, size_t* p_dst_size, const char* str);
-CIMGUI_API const char* igImStrchrRange(const char* str_begin,
- const char* str_end,
- char c);
-CIMGUI_API const char* igImStreolRange(const char* str, const char* str_end);
-CIMGUI_API const char* igImStristr(const char* haystack,
- const char* haystack_end,
- const char* needle,
- const char* needle_end);
-CIMGUI_API void igImStrTrimBlanks(char* str);
-CIMGUI_API const char* igImStrSkipBlank(const char* str);
-CIMGUI_API int igImStrlenW(const ImWchar* str);
-CIMGUI_API const ImWchar* igImStrbolW(const ImWchar* buf_mid_line,
- const ImWchar* buf_begin);
-CIMGUI_API char igImToUpper(char c);
-CIMGUI_API bool igImCharIsBlankA(char c);
-CIMGUI_API bool igImCharIsBlankW(unsigned int c);
-CIMGUI_API int igImFormatString(char* buf,
- size_t buf_size,
- const char* fmt,
- ...);
-CIMGUI_API int igImFormatStringV(char* buf,
- size_t buf_size,
- const char* fmt,
- va_list args);
-CIMGUI_API void igImFormatStringToTempBuffer(const char** out_buf,
- const char** out_buf_end,
- const char* fmt,
- ...);
-CIMGUI_API void igImFormatStringToTempBufferV(const char** out_buf,
- const char** out_buf_end,
- const char* fmt,
- va_list args);
-CIMGUI_API const char* igImParseFormatFindStart(const char* format);
-CIMGUI_API const char* igImParseFormatFindEnd(const char* format);
-CIMGUI_API const char* igImParseFormatTrimDecorations(const char* format,
- char* buf,
- size_t buf_size);
-CIMGUI_API void igImParseFormatSanitizeForPrinting(const char* fmt_in,
- char* fmt_out,
- size_t fmt_out_size);
-CIMGUI_API const char* igImParseFormatSanitizeForScanning(const char* fmt_in,
- char* fmt_out,
- size_t fmt_out_size);
-CIMGUI_API int igImParseFormatPrecision(const char* format, int default_value);
-CIMGUI_API const char* igImTextCharToUtf8(char out_buf[5], unsigned int c);
-CIMGUI_API int igImTextStrToUtf8(char* out_buf,
- int out_buf_size,
- const ImWchar* in_text,
- const ImWchar* in_text_end);
-CIMGUI_API int igImTextCharFromUtf8(unsigned int* out_char,
- const char* in_text,
- const char* in_text_end);
-CIMGUI_API int igImTextStrFromUtf8(ImWchar* out_buf,
- int out_buf_size,
- const char* in_text,
- const char* in_text_end,
- const char** in_remaining);
-CIMGUI_API int igImTextCountCharsFromUtf8(const char* in_text,
- const char* in_text_end);
-CIMGUI_API int igImTextCountUtf8BytesFromChar(const char* in_text,
- const char* in_text_end);
-CIMGUI_API int igImTextCountUtf8BytesFromStr(const ImWchar* in_text,
- const ImWchar* in_text_end);
-CIMGUI_API const char* igImTextFindPreviousUtf8Codepoint(
- const char* in_text_start,
- const char* in_text_curr);
-CIMGUI_API int igImTextCountLines(const char* in_text, const char* in_text_end);
-CIMGUI_API ImFileHandle igImFileOpen(const char* filename, const char* mode);
-CIMGUI_API bool igImFileClose(ImFileHandle file);
-CIMGUI_API ImU64 igImFileGetSize(ImFileHandle file);
-CIMGUI_API ImU64 igImFileRead(void* data,
- ImU64 size,
- ImU64 count,
- ImFileHandle file);
-CIMGUI_API ImU64 igImFileWrite(const void* data,
- ImU64 size,
- ImU64 count,
- ImFileHandle file);
-CIMGUI_API void* igImFileLoadToMemory(const char* filename,
- const char* mode,
- size_t* out_file_size,
- int padding_bytes);
-CIMGUI_API float igImPow_Float(float x, float y);
-CIMGUI_API double igImPow_double(double x, double y);
-CIMGUI_API float igImLog_Float(float x);
-CIMGUI_API double igImLog_double(double x);
-CIMGUI_API int igImAbs_Int(int x);
-CIMGUI_API float igImAbs_Float(float x);
-CIMGUI_API double igImAbs_double(double x);
-CIMGUI_API float igImSign_Float(float x);
-CIMGUI_API double igImSign_double(double x);
-CIMGUI_API float igImRsqrt_Float(float x);
-CIMGUI_API double igImRsqrt_double(double x);
-CIMGUI_API void igImMin(ImVec2* pOut, const ImVec2 lhs, const ImVec2 rhs);
-CIMGUI_API void igImMax(ImVec2* pOut, const ImVec2 lhs, const ImVec2 rhs);
-CIMGUI_API void igImClamp(ImVec2* pOut,
- const ImVec2 v,
- const ImVec2 mn,
- ImVec2 mx);
-CIMGUI_API void igImLerp_Vec2Float(ImVec2* pOut,
- const ImVec2 a,
- const ImVec2 b,
- float t);
-CIMGUI_API void igImLerp_Vec2Vec2(ImVec2* pOut,
- const ImVec2 a,
- const ImVec2 b,
- const ImVec2 t);
-CIMGUI_API void igImLerp_Vec4(ImVec4* pOut,
- const ImVec4 a,
- const ImVec4 b,
- float t);
-CIMGUI_API float igImSaturate(float f);
-CIMGUI_API float igImLengthSqr_Vec2(const ImVec2 lhs);
-CIMGUI_API float igImLengthSqr_Vec4(const ImVec4 lhs);
-CIMGUI_API float igImInvLength(const ImVec2 lhs, float fail_value);
-CIMGUI_API float igImTrunc_Float(float f);
-CIMGUI_API void igImTrunc_Vec2(ImVec2* pOut, const ImVec2 v);
-CIMGUI_API float igImFloor_Float(float f);
-CIMGUI_API void igImFloor_Vec2(ImVec2* pOut, const ImVec2 v);
-CIMGUI_API int igImModPositive(int a, int b);
-CIMGUI_API float igImDot(const ImVec2 a, const ImVec2 b);
-CIMGUI_API void igImRotate(ImVec2* pOut,
- const ImVec2 v,
- float cos_a,
- float sin_a);
-CIMGUI_API float igImLinearSweep(float current, float target, float speed);
-CIMGUI_API void igImMul(ImVec2* pOut, const ImVec2 lhs, const ImVec2 rhs);
-CIMGUI_API bool igImIsFloatAboveGuaranteedIntegerPrecision(float f);
-CIMGUI_API float igImExponentialMovingAverage(float avg, float sample, int n);
-CIMGUI_API void igImBezierCubicCalc(ImVec2* pOut,
- const ImVec2 p1,
- const ImVec2 p2,
- const ImVec2 p3,
- const ImVec2 p4,
- float t);
-CIMGUI_API void igImBezierCubicClosestPoint(ImVec2* pOut,
- const ImVec2 p1,
- const ImVec2 p2,
- const ImVec2 p3,
- const ImVec2 p4,
- const ImVec2 p,
- int num_segments);
-CIMGUI_API void igImBezierCubicClosestPointCasteljau(ImVec2* pOut,
- const ImVec2 p1,
- const ImVec2 p2,
- const ImVec2 p3,
- const ImVec2 p4,
- const ImVec2 p,
- float tess_tol);
-CIMGUI_API void igImBezierQuadraticCalc(ImVec2* pOut,
- const ImVec2 p1,
- const ImVec2 p2,
- const ImVec2 p3,
- float t);
-CIMGUI_API void igImLineClosestPoint(ImVec2* pOut,
- const ImVec2 a,
- const ImVec2 b,
- const ImVec2 p);
-CIMGUI_API bool igImTriangleContainsPoint(const ImVec2 a,
- const ImVec2 b,
- const ImVec2 c,
- const ImVec2 p);
-CIMGUI_API void igImTriangleClosestPoint(ImVec2* pOut,
- const ImVec2 a,
- const ImVec2 b,
- const ImVec2 c,
- const ImVec2 p);
-CIMGUI_API void igImTriangleBarycentricCoords(const ImVec2 a,
- const ImVec2 b,
- const ImVec2 c,
- const ImVec2 p,
- float* out_u,
- float* out_v,
- float* out_w);
-CIMGUI_API float igImTriangleArea(const ImVec2 a,
- const ImVec2 b,
- const ImVec2 c);
-CIMGUI_API bool igImTriangleIsClockwise(const ImVec2 a,
- const ImVec2 b,
- const ImVec2 c);
-CIMGUI_API ImVec1* ImVec1_ImVec1_Nil(void);
-CIMGUI_API void ImVec1_destroy(ImVec1* self);
-CIMGUI_API ImVec1* ImVec1_ImVec1_Float(float _x);
-CIMGUI_API ImVec2ih* ImVec2ih_ImVec2ih_Nil(void);
-CIMGUI_API void ImVec2ih_destroy(ImVec2ih* self);
-CIMGUI_API ImVec2ih* ImVec2ih_ImVec2ih_short(short _x, short _y);
-CIMGUI_API ImVec2ih* ImVec2ih_ImVec2ih_Vec2(const ImVec2 rhs);
-CIMGUI_API ImRect* ImRect_ImRect_Nil(void);
-CIMGUI_API void ImRect_destroy(ImRect* self);
-CIMGUI_API ImRect* ImRect_ImRect_Vec2(const ImVec2 min, const ImVec2 max);
-CIMGUI_API ImRect* ImRect_ImRect_Vec4(const ImVec4 v);
-CIMGUI_API ImRect* ImRect_ImRect_Float(float x1, float y1, float x2, float y2);
-CIMGUI_API void ImRect_GetCenter(ImVec2* pOut, ImRect* self);
-CIMGUI_API void ImRect_GetSize(ImVec2* pOut, ImRect* self);
-CIMGUI_API float ImRect_GetWidth(ImRect* self);
-CIMGUI_API float ImRect_GetHeight(ImRect* self);
-CIMGUI_API float ImRect_GetArea(ImRect* self);
-CIMGUI_API void ImRect_GetTL(ImVec2* pOut, ImRect* self);
-CIMGUI_API void ImRect_GetTR(ImVec2* pOut, ImRect* self);
-CIMGUI_API void ImRect_GetBL(ImVec2* pOut, ImRect* self);
-CIMGUI_API void ImRect_GetBR(ImVec2* pOut, ImRect* self);
-CIMGUI_API bool ImRect_Contains_Vec2(ImRect* self, const ImVec2 p);
-CIMGUI_API bool ImRect_Contains_Rect(ImRect* self, const ImRect r);
-CIMGUI_API bool ImRect_ContainsWithPad(ImRect* self,
- const ImVec2 p,
- const ImVec2 pad);
-CIMGUI_API bool ImRect_Overlaps(ImRect* self, const ImRect r);
-CIMGUI_API void ImRect_Add_Vec2(ImRect* self, const ImVec2 p);
-CIMGUI_API void ImRect_Add_Rect(ImRect* self, const ImRect r);
-CIMGUI_API void ImRect_Expand_Float(ImRect* self, const float amount);
-CIMGUI_API void ImRect_Expand_Vec2(ImRect* self, const ImVec2 amount);
-CIMGUI_API void ImRect_Translate(ImRect* self, const ImVec2 d);
-CIMGUI_API void ImRect_TranslateX(ImRect* self, float dx);
-CIMGUI_API void ImRect_TranslateY(ImRect* self, float dy);
-CIMGUI_API void ImRect_ClipWith(ImRect* self, const ImRect r);
-CIMGUI_API void ImRect_ClipWithFull(ImRect* self, const ImRect r);
-CIMGUI_API void ImRect_Floor(ImRect* self);
-CIMGUI_API bool ImRect_IsInverted(ImRect* self);
-CIMGUI_API void ImRect_ToVec4(ImVec4* pOut, ImRect* self);
-CIMGUI_API size_t igImBitArrayGetStorageSizeInBytes(int bitcount);
-CIMGUI_API void igImBitArrayClearAllBits(ImU32* arr, int bitcount);
-CIMGUI_API bool igImBitArrayTestBit(const ImU32* arr, int n);
-CIMGUI_API void igImBitArrayClearBit(ImU32* arr, int n);
-CIMGUI_API void igImBitArraySetBit(ImU32* arr, int n);
-CIMGUI_API void igImBitArraySetBitRange(ImU32* arr, int n, int n2);
-CIMGUI_API void ImBitVector_Create(ImBitVector* self, int sz);
-CIMGUI_API void ImBitVector_Clear(ImBitVector* self);
-CIMGUI_API bool ImBitVector_TestBit(ImBitVector* self, int n);
-CIMGUI_API void ImBitVector_SetBit(ImBitVector* self, int n);
-CIMGUI_API void ImBitVector_ClearBit(ImBitVector* self, int n);
-CIMGUI_API void ImGuiTextIndex_clear(ImGuiTextIndex* self);
-CIMGUI_API int ImGuiTextIndex_size(ImGuiTextIndex* self);
-CIMGUI_API const char* ImGuiTextIndex_get_line_begin(ImGuiTextIndex* self,
- const char* base,
- int n);
-CIMGUI_API const char* ImGuiTextIndex_get_line_end(ImGuiTextIndex* self,
- const char* base,
- int n);
-CIMGUI_API void ImGuiTextIndex_append(ImGuiTextIndex* self,
- const char* base,
- int old_size,
- int new_size);
-CIMGUI_API ImDrawListSharedData* ImDrawListSharedData_ImDrawListSharedData(
- void);
-CIMGUI_API void ImDrawListSharedData_destroy(ImDrawListSharedData* self);
-CIMGUI_API void ImDrawListSharedData_SetCircleTessellationMaxError(
- ImDrawListSharedData* self,
- float max_error);
-CIMGUI_API ImDrawDataBuilder* ImDrawDataBuilder_ImDrawDataBuilder(void);
-CIMGUI_API void ImDrawDataBuilder_destroy(ImDrawDataBuilder* self);
-CIMGUI_API ImGuiStyleMod* ImGuiStyleMod_ImGuiStyleMod_Int(ImGuiStyleVar idx,
- int v);
-CIMGUI_API void ImGuiStyleMod_destroy(ImGuiStyleMod* self);
-CIMGUI_API ImGuiStyleMod* ImGuiStyleMod_ImGuiStyleMod_Float(ImGuiStyleVar idx,
- float v);
-CIMGUI_API ImGuiStyleMod* ImGuiStyleMod_ImGuiStyleMod_Vec2(ImGuiStyleVar idx,
- ImVec2 v);
-CIMGUI_API ImGuiComboPreviewData* ImGuiComboPreviewData_ImGuiComboPreviewData(
- void);
-CIMGUI_API void ImGuiComboPreviewData_destroy(ImGuiComboPreviewData* self);
-CIMGUI_API ImGuiMenuColumns* ImGuiMenuColumns_ImGuiMenuColumns(void);
-CIMGUI_API void ImGuiMenuColumns_destroy(ImGuiMenuColumns* self);
-CIMGUI_API void ImGuiMenuColumns_Update(ImGuiMenuColumns* self,
- float spacing,
- bool window_reappearing);
-CIMGUI_API float ImGuiMenuColumns_DeclColumns(ImGuiMenuColumns* self,
- float w_icon,
- float w_label,
- float w_shortcut,
- float w_mark);
-CIMGUI_API void ImGuiMenuColumns_CalcNextTotalWidth(ImGuiMenuColumns* self,
- bool update_offsets);
-CIMGUI_API ImGuiInputTextDeactivatedState*
-ImGuiInputTextDeactivatedState_ImGuiInputTextDeactivatedState(void);
-CIMGUI_API void ImGuiInputTextDeactivatedState_destroy(
- ImGuiInputTextDeactivatedState* self);
-CIMGUI_API void ImGuiInputTextDeactivatedState_ClearFreeMemory(
- ImGuiInputTextDeactivatedState* self);
-CIMGUI_API ImGuiInputTextState* ImGuiInputTextState_ImGuiInputTextState(void);
-CIMGUI_API void ImGuiInputTextState_destroy(ImGuiInputTextState* self);
-CIMGUI_API void ImGuiInputTextState_ClearText(ImGuiInputTextState* self);
-CIMGUI_API void ImGuiInputTextState_ClearFreeMemory(ImGuiInputTextState* self);
-CIMGUI_API int ImGuiInputTextState_GetUndoAvailCount(ImGuiInputTextState* self);
-CIMGUI_API int ImGuiInputTextState_GetRedoAvailCount(ImGuiInputTextState* self);
-CIMGUI_API void ImGuiInputTextState_OnKeyPressed(ImGuiInputTextState* self,
- int key);
-CIMGUI_API void ImGuiInputTextState_CursorAnimReset(ImGuiInputTextState* self);
-CIMGUI_API void ImGuiInputTextState_CursorClamp(ImGuiInputTextState* self);
-CIMGUI_API bool ImGuiInputTextState_HasSelection(ImGuiInputTextState* self);
-CIMGUI_API void ImGuiInputTextState_ClearSelection(ImGuiInputTextState* self);
-CIMGUI_API int ImGuiInputTextState_GetCursorPos(ImGuiInputTextState* self);
-CIMGUI_API int ImGuiInputTextState_GetSelectionStart(ImGuiInputTextState* self);
-CIMGUI_API int ImGuiInputTextState_GetSelectionEnd(ImGuiInputTextState* self);
-CIMGUI_API void ImGuiInputTextState_SelectAll(ImGuiInputTextState* self);
-CIMGUI_API void ImGuiInputTextState_ReloadUserBufAndSelectAll(
- ImGuiInputTextState* self);
-CIMGUI_API void ImGuiInputTextState_ReloadUserBufAndKeepSelection(
- ImGuiInputTextState* self);
-CIMGUI_API void ImGuiInputTextState_ReloadUserBufAndMoveToEnd(
- ImGuiInputTextState* self);
-CIMGUI_API ImGuiNextWindowData* ImGuiNextWindowData_ImGuiNextWindowData(void);
-CIMGUI_API void ImGuiNextWindowData_destroy(ImGuiNextWindowData* self);
-CIMGUI_API void ImGuiNextWindowData_ClearFlags(ImGuiNextWindowData* self);
-CIMGUI_API ImGuiNextItemData* ImGuiNextItemData_ImGuiNextItemData(void);
-CIMGUI_API void ImGuiNextItemData_destroy(ImGuiNextItemData* self);
-CIMGUI_API void ImGuiNextItemData_ClearFlags(ImGuiNextItemData* self);
-CIMGUI_API ImGuiLastItemData* ImGuiLastItemData_ImGuiLastItemData(void);
-CIMGUI_API void ImGuiLastItemData_destroy(ImGuiLastItemData* self);
-CIMGUI_API ImGuiStackSizes* ImGuiStackSizes_ImGuiStackSizes(void);
-CIMGUI_API void ImGuiStackSizes_destroy(ImGuiStackSizes* self);
-CIMGUI_API void ImGuiStackSizes_SetToContextState(ImGuiStackSizes* self,
- ImGuiContext* ctx);
-CIMGUI_API void ImGuiStackSizes_CompareWithContextState(ImGuiStackSizes* self,
- ImGuiContext* ctx);
-CIMGUI_API ImGuiPtrOrIndex* ImGuiPtrOrIndex_ImGuiPtrOrIndex_Ptr(void* ptr);
-CIMGUI_API void ImGuiPtrOrIndex_destroy(ImGuiPtrOrIndex* self);
-CIMGUI_API ImGuiPtrOrIndex* ImGuiPtrOrIndex_ImGuiPtrOrIndex_Int(int index);
-CIMGUI_API void* ImGuiDataVarInfo_GetVarPtr(ImGuiDataVarInfo* self,
- void* parent);
-CIMGUI_API ImGuiPopupData* ImGuiPopupData_ImGuiPopupData(void);
-CIMGUI_API void ImGuiPopupData_destroy(ImGuiPopupData* self);
-CIMGUI_API ImGuiInputEvent* ImGuiInputEvent_ImGuiInputEvent(void);
-CIMGUI_API void ImGuiInputEvent_destroy(ImGuiInputEvent* self);
-CIMGUI_API ImGuiKeyRoutingData* ImGuiKeyRoutingData_ImGuiKeyRoutingData(void);
-CIMGUI_API void ImGuiKeyRoutingData_destroy(ImGuiKeyRoutingData* self);
-CIMGUI_API ImGuiKeyRoutingTable* ImGuiKeyRoutingTable_ImGuiKeyRoutingTable(
- void);
-CIMGUI_API void ImGuiKeyRoutingTable_destroy(ImGuiKeyRoutingTable* self);
-CIMGUI_API void ImGuiKeyRoutingTable_Clear(ImGuiKeyRoutingTable* self);
-CIMGUI_API ImGuiKeyOwnerData* ImGuiKeyOwnerData_ImGuiKeyOwnerData(void);
-CIMGUI_API void ImGuiKeyOwnerData_destroy(ImGuiKeyOwnerData* self);
-CIMGUI_API ImGuiListClipperRange ImGuiListClipperRange_FromIndices(int min,
- int max);
-CIMGUI_API ImGuiListClipperRange
-ImGuiListClipperRange_FromPositions(float y1,
- float y2,
- int off_min,
- int off_max);
-CIMGUI_API ImGuiListClipperData* ImGuiListClipperData_ImGuiListClipperData(
- void);
-CIMGUI_API void ImGuiListClipperData_destroy(ImGuiListClipperData* self);
-CIMGUI_API void ImGuiListClipperData_Reset(ImGuiListClipperData* self,
- ImGuiListClipper* clipper);
-CIMGUI_API ImGuiNavItemData* ImGuiNavItemData_ImGuiNavItemData(void);
-CIMGUI_API void ImGuiNavItemData_destroy(ImGuiNavItemData* self);
-CIMGUI_API void ImGuiNavItemData_Clear(ImGuiNavItemData* self);
-CIMGUI_API ImGuiTypingSelectState*
-ImGuiTypingSelectState_ImGuiTypingSelectState(void);
-CIMGUI_API void ImGuiTypingSelectState_destroy(ImGuiTypingSelectState* self);
-CIMGUI_API void ImGuiTypingSelectState_Clear(ImGuiTypingSelectState* self);
-CIMGUI_API ImGuiOldColumnData* ImGuiOldColumnData_ImGuiOldColumnData(void);
-CIMGUI_API void ImGuiOldColumnData_destroy(ImGuiOldColumnData* self);
-CIMGUI_API ImGuiOldColumns* ImGuiOldColumns_ImGuiOldColumns(void);
-CIMGUI_API void ImGuiOldColumns_destroy(ImGuiOldColumns* self);
-CIMGUI_API ImGuiDockNode* ImGuiDockNode_ImGuiDockNode(ImGuiID id);
-CIMGUI_API void ImGuiDockNode_destroy(ImGuiDockNode* self);
-CIMGUI_API bool ImGuiDockNode_IsRootNode(ImGuiDockNode* self);
-CIMGUI_API bool ImGuiDockNode_IsDockSpace(ImGuiDockNode* self);
-CIMGUI_API bool ImGuiDockNode_IsFloatingNode(ImGuiDockNode* self);
-CIMGUI_API bool ImGuiDockNode_IsCentralNode(ImGuiDockNode* self);
-CIMGUI_API bool ImGuiDockNode_IsHiddenTabBar(ImGuiDockNode* self);
-CIMGUI_API bool ImGuiDockNode_IsNoTabBar(ImGuiDockNode* self);
-CIMGUI_API bool ImGuiDockNode_IsSplitNode(ImGuiDockNode* self);
-CIMGUI_API bool ImGuiDockNode_IsLeafNode(ImGuiDockNode* self);
-CIMGUI_API bool ImGuiDockNode_IsEmpty(ImGuiDockNode* self);
-CIMGUI_API void ImGuiDockNode_Rect(ImRect* pOut, ImGuiDockNode* self);
-CIMGUI_API void ImGuiDockNode_SetLocalFlags(ImGuiDockNode* self,
- ImGuiDockNodeFlags flags);
-CIMGUI_API void ImGuiDockNode_UpdateMergedFlags(ImGuiDockNode* self);
-CIMGUI_API ImGuiDockContext* ImGuiDockContext_ImGuiDockContext(void);
-CIMGUI_API void ImGuiDockContext_destroy(ImGuiDockContext* self);
-CIMGUI_API ImGuiViewportP* ImGuiViewportP_ImGuiViewportP(void);
-CIMGUI_API void ImGuiViewportP_destroy(ImGuiViewportP* self);
-CIMGUI_API void ImGuiViewportP_ClearRequestFlags(ImGuiViewportP* self);
-CIMGUI_API void ImGuiViewportP_CalcWorkRectPos(ImVec2* pOut,
- ImGuiViewportP* self,
- const ImVec2 off_min);
-CIMGUI_API void ImGuiViewportP_CalcWorkRectSize(ImVec2* pOut,
- ImGuiViewportP* self,
- const ImVec2 off_min,
- const ImVec2 off_max);
-CIMGUI_API void ImGuiViewportP_UpdateWorkRect(ImGuiViewportP* self);
-CIMGUI_API void ImGuiViewportP_GetMainRect(ImRect* pOut, ImGuiViewportP* self);
-CIMGUI_API void ImGuiViewportP_GetWorkRect(ImRect* pOut, ImGuiViewportP* self);
-CIMGUI_API void ImGuiViewportP_GetBuildWorkRect(ImRect* pOut,
- ImGuiViewportP* self);
-CIMGUI_API ImGuiWindowSettings* ImGuiWindowSettings_ImGuiWindowSettings(void);
-CIMGUI_API void ImGuiWindowSettings_destroy(ImGuiWindowSettings* self);
-CIMGUI_API char* ImGuiWindowSettings_GetName(ImGuiWindowSettings* self);
-CIMGUI_API ImGuiSettingsHandler* ImGuiSettingsHandler_ImGuiSettingsHandler(
- void);
-CIMGUI_API void ImGuiSettingsHandler_destroy(ImGuiSettingsHandler* self);
-CIMGUI_API ImGuiDebugAllocInfo* ImGuiDebugAllocInfo_ImGuiDebugAllocInfo(void);
-CIMGUI_API void ImGuiDebugAllocInfo_destroy(ImGuiDebugAllocInfo* self);
-CIMGUI_API ImGuiStackLevelInfo* ImGuiStackLevelInfo_ImGuiStackLevelInfo(void);
-CIMGUI_API void ImGuiStackLevelInfo_destroy(ImGuiStackLevelInfo* self);
-CIMGUI_API ImGuiIDStackTool* ImGuiIDStackTool_ImGuiIDStackTool(void);
-CIMGUI_API void ImGuiIDStackTool_destroy(ImGuiIDStackTool* self);
-CIMGUI_API ImGuiContextHook* ImGuiContextHook_ImGuiContextHook(void);
-CIMGUI_API void ImGuiContextHook_destroy(ImGuiContextHook* self);
-CIMGUI_API ImGuiContext* ImGuiContext_ImGuiContext(
- ImFontAtlas* shared_font_atlas);
-CIMGUI_API void ImGuiContext_destroy(ImGuiContext* self);
-CIMGUI_API ImGuiWindow* ImGuiWindow_ImGuiWindow(ImGuiContext* context,
- const char* name);
-CIMGUI_API void ImGuiWindow_destroy(ImGuiWindow* self);
-CIMGUI_API ImGuiID ImGuiWindow_GetID_Str(ImGuiWindow* self,
- const char* str,
- const char* str_end);
-CIMGUI_API ImGuiID ImGuiWindow_GetID_Ptr(ImGuiWindow* self, const void* ptr);
-CIMGUI_API ImGuiID ImGuiWindow_GetID_Int(ImGuiWindow* self, int n);
-CIMGUI_API ImGuiID ImGuiWindow_GetIDFromRectangle(ImGuiWindow* self,
- const ImRect r_abs);
-CIMGUI_API void ImGuiWindow_Rect(ImRect* pOut, ImGuiWindow* self);
-CIMGUI_API float ImGuiWindow_CalcFontSize(ImGuiWindow* self);
-CIMGUI_API float ImGuiWindow_TitleBarHeight(ImGuiWindow* self);
-CIMGUI_API void ImGuiWindow_TitleBarRect(ImRect* pOut, ImGuiWindow* self);
-CIMGUI_API float ImGuiWindow_MenuBarHeight(ImGuiWindow* self);
-CIMGUI_API void ImGuiWindow_MenuBarRect(ImRect* pOut, ImGuiWindow* self);
-CIMGUI_API ImGuiTabItem* ImGuiTabItem_ImGuiTabItem(void);
-CIMGUI_API void ImGuiTabItem_destroy(ImGuiTabItem* self);
-CIMGUI_API ImGuiTabBar* ImGuiTabBar_ImGuiTabBar(void);
-CIMGUI_API void ImGuiTabBar_destroy(ImGuiTabBar* self);
-CIMGUI_API ImGuiTableColumn* ImGuiTableColumn_ImGuiTableColumn(void);
-CIMGUI_API void ImGuiTableColumn_destroy(ImGuiTableColumn* self);
-CIMGUI_API ImGuiTableInstanceData*
-ImGuiTableInstanceData_ImGuiTableInstanceData(void);
-CIMGUI_API void ImGuiTableInstanceData_destroy(ImGuiTableInstanceData* self);
-CIMGUI_API ImGuiTable* ImGuiTable_ImGuiTable(void);
-CIMGUI_API void ImGuiTable_destroy(ImGuiTable* self);
-CIMGUI_API ImGuiTableTempData* ImGuiTableTempData_ImGuiTableTempData(void);
-CIMGUI_API void ImGuiTableTempData_destroy(ImGuiTableTempData* self);
-CIMGUI_API ImGuiTableColumnSettings*
-ImGuiTableColumnSettings_ImGuiTableColumnSettings(void);
-CIMGUI_API void ImGuiTableColumnSettings_destroy(
- ImGuiTableColumnSettings* self);
-CIMGUI_API ImGuiTableSettings* ImGuiTableSettings_ImGuiTableSettings(void);
-CIMGUI_API void ImGuiTableSettings_destroy(ImGuiTableSettings* self);
-CIMGUI_API ImGuiTableColumnSettings* ImGuiTableSettings_GetColumnSettings(
- ImGuiTableSettings* self);
-CIMGUI_API ImGuiWindow* igGetCurrentWindowRead(void);
-CIMGUI_API ImGuiWindow* igGetCurrentWindow(void);
-CIMGUI_API ImGuiWindow* igFindWindowByID(ImGuiID id);
-CIMGUI_API ImGuiWindow* igFindWindowByName(const char* name);
-CIMGUI_API void igUpdateWindowParentAndRootLinks(ImGuiWindow* window,
- ImGuiWindowFlags flags,
- ImGuiWindow* parent_window);
-CIMGUI_API void igUpdateWindowSkipRefresh(ImGuiWindow* window);
-CIMGUI_API void igCalcWindowNextAutoFitSize(ImVec2* pOut, ImGuiWindow* window);
-CIMGUI_API bool igIsWindowChildOf(ImGuiWindow* window,
- ImGuiWindow* potential_parent,
- bool popup_hierarchy,
- bool dock_hierarchy);
-CIMGUI_API bool igIsWindowWithinBeginStackOf(ImGuiWindow* window,
- ImGuiWindow* potential_parent);
-CIMGUI_API bool igIsWindowAbove(ImGuiWindow* potential_above,
- ImGuiWindow* potential_below);
-CIMGUI_API bool igIsWindowNavFocusable(ImGuiWindow* window);
-CIMGUI_API void igSetWindowPos_WindowPtr(ImGuiWindow* window,
- const ImVec2 pos,
- ImGuiCond cond);
-CIMGUI_API void igSetWindowSize_WindowPtr(ImGuiWindow* window,
- const ImVec2 size,
- ImGuiCond cond);
-CIMGUI_API void igSetWindowCollapsed_WindowPtr(ImGuiWindow* window,
- bool collapsed,
- ImGuiCond cond);
-CIMGUI_API void igSetWindowHitTestHole(ImGuiWindow* window,
- const ImVec2 pos,
- const ImVec2 size);
-CIMGUI_API void igSetWindowHiddenAndSkipItemsForCurrentFrame(
- ImGuiWindow* window);
-CIMGUI_API void igSetWindowParentWindowForFocusRoute(
- ImGuiWindow* window,
- ImGuiWindow* parent_window);
-CIMGUI_API void igWindowRectAbsToRel(ImRect* pOut,
- ImGuiWindow* window,
- const ImRect r);
-CIMGUI_API void igWindowRectRelToAbs(ImRect* pOut,
- ImGuiWindow* window,
- const ImRect r);
-CIMGUI_API void igWindowPosRelToAbs(ImVec2* pOut,
- ImGuiWindow* window,
- const ImVec2 p);
-CIMGUI_API void igFocusWindow(ImGuiWindow* window,
- ImGuiFocusRequestFlags flags);
-CIMGUI_API void igFocusTopMostWindowUnderOne(ImGuiWindow* under_this_window,
- ImGuiWindow* ignore_window,
- ImGuiViewport* filter_viewport,
- ImGuiFocusRequestFlags flags);
-CIMGUI_API void igBringWindowToFocusFront(ImGuiWindow* window);
-CIMGUI_API void igBringWindowToDisplayFront(ImGuiWindow* window);
-CIMGUI_API void igBringWindowToDisplayBack(ImGuiWindow* window);
-CIMGUI_API void igBringWindowToDisplayBehind(ImGuiWindow* window,
- ImGuiWindow* above_window);
-CIMGUI_API int igFindWindowDisplayIndex(ImGuiWindow* window);
-CIMGUI_API ImGuiWindow* igFindBottomMostVisibleWindowWithinBeginStack(
- ImGuiWindow* window);
-CIMGUI_API void igSetNextWindowRefreshPolicy(ImGuiWindowRefreshFlags flags);
-CIMGUI_API void igSetCurrentFont(ImFont* font);
-CIMGUI_API ImFont* igGetDefaultFont(void);
-CIMGUI_API ImDrawList* igGetForegroundDrawList_WindowPtr(ImGuiWindow* window);
-CIMGUI_API void igAddDrawListToDrawDataEx(ImDrawData* draw_data,
- ImVector_ImDrawListPtr* out_list,
- ImDrawList* draw_list);
-CIMGUI_API void igInitialize(void);
-CIMGUI_API void igShutdown(void);
-CIMGUI_API void igUpdateInputEvents(bool trickle_fast_inputs);
-CIMGUI_API void igUpdateHoveredWindowAndCaptureFlags(void);
-CIMGUI_API void igStartMouseMovingWindow(ImGuiWindow* window);
-CIMGUI_API void igStartMouseMovingWindowOrNode(ImGuiWindow* window,
- ImGuiDockNode* node,
- bool undock);
-CIMGUI_API void igUpdateMouseMovingWindowNewFrame(void);
-CIMGUI_API void igUpdateMouseMovingWindowEndFrame(void);
-CIMGUI_API ImGuiID igAddContextHook(ImGuiContext* context,
- const ImGuiContextHook* hook);
-CIMGUI_API void igRemoveContextHook(ImGuiContext* context,
- ImGuiID hook_to_remove);
-CIMGUI_API void igCallContextHooks(ImGuiContext* context,
- ImGuiContextHookType type);
-CIMGUI_API void igTranslateWindowsInViewport(ImGuiViewportP* viewport,
- const ImVec2 old_pos,
- const ImVec2 new_pos);
-CIMGUI_API void igScaleWindowsInViewport(ImGuiViewportP* viewport, float scale);
-CIMGUI_API void igDestroyPlatformWindow(ImGuiViewportP* viewport);
-CIMGUI_API void igSetWindowViewport(ImGuiWindow* window,
- ImGuiViewportP* viewport);
-CIMGUI_API void igSetCurrentViewport(ImGuiWindow* window,
- ImGuiViewportP* viewport);
-CIMGUI_API const ImGuiPlatformMonitor* igGetViewportPlatformMonitor(
- ImGuiViewport* viewport);
-CIMGUI_API ImGuiViewportP* igFindHoveredViewportFromPlatformWindowStack(
- const ImVec2 mouse_platform_pos);
-CIMGUI_API void igMarkIniSettingsDirty_Nil(void);
-CIMGUI_API void igMarkIniSettingsDirty_WindowPtr(ImGuiWindow* window);
-CIMGUI_API void igClearIniSettings(void);
-CIMGUI_API void igAddSettingsHandler(const ImGuiSettingsHandler* handler);
-CIMGUI_API void igRemoveSettingsHandler(const char* type_name);
-CIMGUI_API ImGuiSettingsHandler* igFindSettingsHandler(const char* type_name);
-CIMGUI_API ImGuiWindowSettings* igCreateNewWindowSettings(const char* name);
-CIMGUI_API ImGuiWindowSettings* igFindWindowSettingsByID(ImGuiID id);
-CIMGUI_API ImGuiWindowSettings* igFindWindowSettingsByWindow(
- ImGuiWindow* window);
-CIMGUI_API void igClearWindowSettings(const char* name);
-CIMGUI_API void igLocalizeRegisterEntries(const ImGuiLocEntry* entries,
- int count);
-CIMGUI_API const char* igLocalizeGetMsg(ImGuiLocKey key);
-CIMGUI_API void igSetScrollX_WindowPtr(ImGuiWindow* window, float scroll_x);
-CIMGUI_API void igSetScrollY_WindowPtr(ImGuiWindow* window, float scroll_y);
-CIMGUI_API void igSetScrollFromPosX_WindowPtr(ImGuiWindow* window,
- float local_x,
- float center_x_ratio);
-CIMGUI_API void igSetScrollFromPosY_WindowPtr(ImGuiWindow* window,
- float local_y,
- float center_y_ratio);
-CIMGUI_API void igScrollToItem(ImGuiScrollFlags flags);
-CIMGUI_API void igScrollToRect(ImGuiWindow* window,
- const ImRect rect,
- ImGuiScrollFlags flags);
-CIMGUI_API void igScrollToRectEx(ImVec2* pOut,
- ImGuiWindow* window,
- const ImRect rect,
- ImGuiScrollFlags flags);
-CIMGUI_API void igScrollToBringRectIntoView(ImGuiWindow* window,
- const ImRect rect);
-CIMGUI_API ImGuiItemStatusFlags igGetItemStatusFlags(void);
-CIMGUI_API ImGuiItemFlags igGetItemFlags(void);
-CIMGUI_API ImGuiID igGetActiveID(void);
-CIMGUI_API ImGuiID igGetFocusID(void);
-CIMGUI_API void igSetActiveID(ImGuiID id, ImGuiWindow* window);
-CIMGUI_API void igSetFocusID(ImGuiID id, ImGuiWindow* window);
-CIMGUI_API void igClearActiveID(void);
-CIMGUI_API ImGuiID igGetHoveredID(void);
-CIMGUI_API void igSetHoveredID(ImGuiID id);
-CIMGUI_API void igKeepAliveID(ImGuiID id);
-CIMGUI_API void igMarkItemEdited(ImGuiID id);
-CIMGUI_API void igPushOverrideID(ImGuiID id);
-CIMGUI_API ImGuiID igGetIDWithSeed_Str(const char* str_id_begin,
- const char* str_id_end,
- ImGuiID seed);
-CIMGUI_API ImGuiID igGetIDWithSeed_Int(int n, ImGuiID seed);
-CIMGUI_API void igItemSize_Vec2(const ImVec2 size, float text_baseline_y);
-CIMGUI_API void igItemSize_Rect(const ImRect bb, float text_baseline_y);
-CIMGUI_API bool igItemAdd(const ImRect bb,
- ImGuiID id,
- const ImRect* nav_bb,
- ImGuiItemFlags extra_flags);
-CIMGUI_API bool igItemHoverable(const ImRect bb,
- ImGuiID id,
- ImGuiItemFlags item_flags);
-CIMGUI_API bool igIsWindowContentHoverable(ImGuiWindow* window,
- ImGuiHoveredFlags flags);
-CIMGUI_API bool igIsClippedEx(const ImRect bb, ImGuiID id);
-CIMGUI_API void igSetLastItemData(ImGuiID item_id,
- ImGuiItemFlags in_flags,
- ImGuiItemStatusFlags status_flags,
- const ImRect item_rect);
-CIMGUI_API void igCalcItemSize(ImVec2* pOut,
- ImVec2 size,
- float default_w,
- float default_h);
-CIMGUI_API float igCalcWrapWidthForPos(const ImVec2 pos, float wrap_pos_x);
-CIMGUI_API void igPushMultiItemsWidths(int components, float width_full);
-CIMGUI_API bool igIsItemToggledSelection(void);
-CIMGUI_API void igGetContentRegionMaxAbs(ImVec2* pOut);
-CIMGUI_API void igShrinkWidths(ImGuiShrinkWidthItem* items,
- int count,
- float width_excess);
-CIMGUI_API void igPushItemFlag(ImGuiItemFlags option, bool enabled);
-CIMGUI_API void igPopItemFlag(void);
-CIMGUI_API const ImGuiDataVarInfo* igGetStyleVarInfo(ImGuiStyleVar idx);
-CIMGUI_API void igLogBegin(ImGuiLogType type, int auto_open_depth);
-CIMGUI_API void igLogToBuffer(int auto_open_depth);
-CIMGUI_API void igLogRenderedText(const ImVec2* ref_pos,
- const char* text,
- const char* text_end);
-CIMGUI_API void igLogSetNextTextDecoration(const char* prefix,
- const char* suffix);
-CIMGUI_API bool igBeginChildEx(const char* name,
- ImGuiID id,
- const ImVec2 size_arg,
- ImGuiChildFlags child_flags,
- ImGuiWindowFlags window_flags);
-CIMGUI_API void igOpenPopupEx(ImGuiID id, ImGuiPopupFlags popup_flags);
-CIMGUI_API void igClosePopupToLevel(int remaining,
- bool restore_focus_to_window_under_popup);
-CIMGUI_API void igClosePopupsOverWindow(
- ImGuiWindow* ref_window,
- bool restore_focus_to_window_under_popup);
-CIMGUI_API void igClosePopupsExceptModals(void);
-CIMGUI_API bool igIsPopupOpen_ID(ImGuiID id, ImGuiPopupFlags popup_flags);
-CIMGUI_API bool igBeginPopupEx(ImGuiID id, ImGuiWindowFlags extra_flags);
-CIMGUI_API bool igBeginTooltipEx(ImGuiTooltipFlags tooltip_flags,
- ImGuiWindowFlags extra_window_flags);
-CIMGUI_API bool igBeginTooltipHidden(void);
-CIMGUI_API void igGetPopupAllowedExtentRect(ImRect* pOut, ImGuiWindow* window);
-CIMGUI_API ImGuiWindow* igGetTopMostPopupModal(void);
-CIMGUI_API ImGuiWindow* igGetTopMostAndVisiblePopupModal(void);
-CIMGUI_API ImGuiWindow* igFindBlockingModal(ImGuiWindow* window);
-CIMGUI_API void igFindBestWindowPosForPopup(ImVec2* pOut, ImGuiWindow* window);
-CIMGUI_API void igFindBestWindowPosForPopupEx(ImVec2* pOut,
- const ImVec2 ref_pos,
- const ImVec2 size,
- ImGuiDir* last_dir,
- const ImRect r_outer,
- const ImRect r_avoid,
- ImGuiPopupPositionPolicy policy);
-CIMGUI_API bool igBeginViewportSideBar(const char* name,
- ImGuiViewport* viewport,
- ImGuiDir dir,
- float size,
- ImGuiWindowFlags window_flags);
-CIMGUI_API bool igBeginMenuEx(const char* label,
- const char* icon,
- bool enabled);
-CIMGUI_API bool igMenuItemEx(const char* label,
- const char* icon,
- const char* shortcut,
- bool selected,
- bool enabled);
-CIMGUI_API bool igBeginComboPopup(ImGuiID popup_id,
- const ImRect bb,
- ImGuiComboFlags flags);
-CIMGUI_API bool igBeginComboPreview(void);
-CIMGUI_API void igEndComboPreview(void);
-CIMGUI_API void igNavInitWindow(ImGuiWindow* window, bool force_reinit);
-CIMGUI_API void igNavInitRequestApplyResult(void);
-CIMGUI_API bool igNavMoveRequestButNoResultYet(void);
-CIMGUI_API void igNavMoveRequestSubmit(ImGuiDir move_dir,
- ImGuiDir clip_dir,
- ImGuiNavMoveFlags move_flags,
- ImGuiScrollFlags scroll_flags);
-CIMGUI_API void igNavMoveRequestForward(ImGuiDir move_dir,
- ImGuiDir clip_dir,
- ImGuiNavMoveFlags move_flags,
- ImGuiScrollFlags scroll_flags);
-CIMGUI_API void igNavMoveRequestResolveWithLastItem(ImGuiNavItemData* result);
-CIMGUI_API void igNavMoveRequestResolveWithPastTreeNode(
- ImGuiNavItemData* result,
- ImGuiNavTreeNodeData* tree_node_data);
-CIMGUI_API void igNavMoveRequestCancel(void);
-CIMGUI_API void igNavMoveRequestApplyResult(void);
-CIMGUI_API void igNavMoveRequestTryWrapping(ImGuiWindow* window,
- ImGuiNavMoveFlags move_flags);
-CIMGUI_API void igNavHighlightActivated(ImGuiID id);
-CIMGUI_API void igNavClearPreferredPosForAxis(ImGuiAxis axis);
-CIMGUI_API void igNavRestoreHighlightAfterMove(void);
-CIMGUI_API void igNavUpdateCurrentWindowIsScrollPushableX(void);
-CIMGUI_API void igSetNavWindow(ImGuiWindow* window);
-CIMGUI_API void igSetNavID(ImGuiID id,
- ImGuiNavLayer nav_layer,
- ImGuiID focus_scope_id,
- const ImRect rect_rel);
-CIMGUI_API void igSetNavFocusScope(ImGuiID focus_scope_id);
-CIMGUI_API void igFocusItem(void);
-CIMGUI_API void igActivateItemByID(ImGuiID id);
-CIMGUI_API bool igIsNamedKey(ImGuiKey key);
-CIMGUI_API bool igIsNamedKeyOrModKey(ImGuiKey key);
-CIMGUI_API bool igIsLegacyKey(ImGuiKey key);
-CIMGUI_API bool igIsKeyboardKey(ImGuiKey key);
-CIMGUI_API bool igIsGamepadKey(ImGuiKey key);
-CIMGUI_API bool igIsMouseKey(ImGuiKey key);
-CIMGUI_API bool igIsAliasKey(ImGuiKey key);
-CIMGUI_API bool igIsModKey(ImGuiKey key);
-CIMGUI_API ImGuiKeyChord igFixupKeyChord(ImGuiContext* ctx,
- ImGuiKeyChord key_chord);
-CIMGUI_API ImGuiKey igConvertSingleModFlagToKey(ImGuiContext* ctx,
- ImGuiKey key);
-CIMGUI_API ImGuiKeyData* igGetKeyData_ContextPtr(ImGuiContext* ctx,
- ImGuiKey key);
-CIMGUI_API ImGuiKeyData* igGetKeyData_Key(ImGuiKey key);
-CIMGUI_API const char* igGetKeyChordName(ImGuiKeyChord key_chord);
-CIMGUI_API ImGuiKey igMouseButtonToKey(ImGuiMouseButton button);
-CIMGUI_API bool igIsMouseDragPastThreshold(ImGuiMouseButton button,
- float lock_threshold);
-CIMGUI_API void igGetKeyMagnitude2d(ImVec2* pOut,
- ImGuiKey key_left,
- ImGuiKey key_right,
- ImGuiKey key_up,
- ImGuiKey key_down);
-CIMGUI_API float igGetNavTweakPressedAmount(ImGuiAxis axis);
-CIMGUI_API int igCalcTypematicRepeatAmount(float t0,
- float t1,
- float repeat_delay,
- float repeat_rate);
-CIMGUI_API void igGetTypematicRepeatRate(ImGuiInputFlags flags,
- float* repeat_delay,
- float* repeat_rate);
-CIMGUI_API void igTeleportMousePos(const ImVec2 pos);
-CIMGUI_API void igSetActiveIdUsingAllKeyboardKeys(void);
-CIMGUI_API bool igIsActiveIdUsingNavDir(ImGuiDir dir);
-CIMGUI_API ImGuiID igGetKeyOwner(ImGuiKey key);
-CIMGUI_API void igSetKeyOwner(ImGuiKey key,
- ImGuiID owner_id,
- ImGuiInputFlags flags);
-CIMGUI_API void igSetKeyOwnersForKeyChord(ImGuiKeyChord key,
- ImGuiID owner_id,
- ImGuiInputFlags flags);
-CIMGUI_API void igSetItemKeyOwner(ImGuiKey key, ImGuiInputFlags flags);
-CIMGUI_API bool igTestKeyOwner(ImGuiKey key, ImGuiID owner_id);
-CIMGUI_API ImGuiKeyOwnerData* igGetKeyOwnerData(ImGuiContext* ctx,
- ImGuiKey key);
-CIMGUI_API bool igIsKeyDown_ID(ImGuiKey key, ImGuiID owner_id);
-CIMGUI_API bool igIsKeyPressed_ID(ImGuiKey key,
- ImGuiID owner_id,
- ImGuiInputFlags flags);
-CIMGUI_API bool igIsKeyReleased_ID(ImGuiKey key, ImGuiID owner_id);
-CIMGUI_API bool igIsMouseDown_ID(ImGuiMouseButton button, ImGuiID owner_id);
-CIMGUI_API bool igIsMouseClicked_ID(ImGuiMouseButton button,
- ImGuiID owner_id,
- ImGuiInputFlags flags);
-CIMGUI_API bool igIsMouseReleased_ID(ImGuiMouseButton button, ImGuiID owner_id);
-CIMGUI_API bool igIsMouseDoubleClicked_ID(ImGuiMouseButton button,
- ImGuiID owner_id);
-CIMGUI_API bool igIsKeyChordPressed_ID(ImGuiKeyChord key_chord,
- ImGuiID owner_id,
- ImGuiInputFlags flags);
-CIMGUI_API void igSetNextItemShortcut(ImGuiKeyChord key_chord);
-CIMGUI_API bool igShortcut(ImGuiKeyChord key_chord,
- ImGuiID owner_id,
- ImGuiInputFlags flags);
-CIMGUI_API bool igSetShortcutRouting(ImGuiKeyChord key_chord,
- ImGuiID owner_id,
- ImGuiInputFlags flags);
-CIMGUI_API bool igTestShortcutRouting(ImGuiKeyChord key_chord,
- ImGuiID owner_id);
-CIMGUI_API ImGuiKeyRoutingData* igGetShortcutRoutingData(
- ImGuiKeyChord key_chord);
-CIMGUI_API void igDockContextInitialize(ImGuiContext* ctx);
-CIMGUI_API void igDockContextShutdown(ImGuiContext* ctx);
-CIMGUI_API void igDockContextClearNodes(ImGuiContext* ctx,
- ImGuiID root_id,
- bool clear_settings_refs);
-CIMGUI_API void igDockContextRebuildNodes(ImGuiContext* ctx);
-CIMGUI_API void igDockContextNewFrameUpdateUndocking(ImGuiContext* ctx);
-CIMGUI_API void igDockContextNewFrameUpdateDocking(ImGuiContext* ctx);
-CIMGUI_API void igDockContextEndFrame(ImGuiContext* ctx);
-CIMGUI_API ImGuiID igDockContextGenNodeID(ImGuiContext* ctx);
-CIMGUI_API void igDockContextQueueDock(ImGuiContext* ctx,
- ImGuiWindow* target,
- ImGuiDockNode* target_node,
- ImGuiWindow* payload,
- ImGuiDir split_dir,
- float split_ratio,
- bool split_outer);
-CIMGUI_API void igDockContextQueueUndockWindow(ImGuiContext* ctx,
- ImGuiWindow* window);
-CIMGUI_API void igDockContextQueueUndockNode(ImGuiContext* ctx,
- ImGuiDockNode* node);
-CIMGUI_API void igDockContextProcessUndockWindow(
- ImGuiContext* ctx,
- ImGuiWindow* window,
- bool clear_persistent_docking_ref);
-CIMGUI_API void igDockContextProcessUndockNode(ImGuiContext* ctx,
- ImGuiDockNode* node);
-CIMGUI_API bool igDockContextCalcDropPosForDocking(ImGuiWindow* target,
- ImGuiDockNode* target_node,
- ImGuiWindow* payload_window,
- ImGuiDockNode* payload_node,
- ImGuiDir split_dir,
- bool split_outer,
- ImVec2* out_pos);
-CIMGUI_API ImGuiDockNode* igDockContextFindNodeByID(ImGuiContext* ctx,
- ImGuiID id);
-CIMGUI_API void igDockNodeWindowMenuHandler_Default(ImGuiContext* ctx,
- ImGuiDockNode* node,
- ImGuiTabBar* tab_bar);
-CIMGUI_API bool igDockNodeBeginAmendTabBar(ImGuiDockNode* node);
-CIMGUI_API void igDockNodeEndAmendTabBar(void);
-CIMGUI_API ImGuiDockNode* igDockNodeGetRootNode(ImGuiDockNode* node);
-CIMGUI_API bool igDockNodeIsInHierarchyOf(ImGuiDockNode* node,
- ImGuiDockNode* parent);
-CIMGUI_API int igDockNodeGetDepth(const ImGuiDockNode* node);
-CIMGUI_API ImGuiID igDockNodeGetWindowMenuButtonId(const ImGuiDockNode* node);
-CIMGUI_API ImGuiDockNode* igGetWindowDockNode(void);
-CIMGUI_API bool igGetWindowAlwaysWantOwnTabBar(ImGuiWindow* window);
-CIMGUI_API void igBeginDocked(ImGuiWindow* window, bool* p_open);
-CIMGUI_API void igBeginDockableDragDropSource(ImGuiWindow* window);
-CIMGUI_API void igBeginDockableDragDropTarget(ImGuiWindow* window);
-CIMGUI_API void igSetWindowDock(ImGuiWindow* window,
- ImGuiID dock_id,
- ImGuiCond cond);
-CIMGUI_API void igDockBuilderDockWindow(const char* window_name,
- ImGuiID node_id);
-CIMGUI_API ImGuiDockNode* igDockBuilderGetNode(ImGuiID node_id);
-CIMGUI_API ImGuiDockNode* igDockBuilderGetCentralNode(ImGuiID node_id);
-CIMGUI_API ImGuiID igDockBuilderAddNode(ImGuiID node_id,
- ImGuiDockNodeFlags flags);
-CIMGUI_API void igDockBuilderRemoveNode(ImGuiID node_id);
-CIMGUI_API void igDockBuilderRemoveNodeDockedWindows(ImGuiID node_id,
- bool clear_settings_refs);
-CIMGUI_API void igDockBuilderRemoveNodeChildNodes(ImGuiID node_id);
-CIMGUI_API void igDockBuilderSetNodePos(ImGuiID node_id, ImVec2 pos);
-CIMGUI_API void igDockBuilderSetNodeSize(ImGuiID node_id, ImVec2 size);
-CIMGUI_API ImGuiID igDockBuilderSplitNode(ImGuiID node_id,
- ImGuiDir split_dir,
- float size_ratio_for_node_at_dir,
- ImGuiID* out_id_at_dir,
- ImGuiID* out_id_at_opposite_dir);
-CIMGUI_API void igDockBuilderCopyDockSpace(
- ImGuiID src_dockspace_id,
- ImGuiID dst_dockspace_id,
- ImVector_const_charPtr* in_window_remap_pairs);
-CIMGUI_API void igDockBuilderCopyNode(ImGuiID src_node_id,
- ImGuiID dst_node_id,
- ImVector_ImGuiID* out_node_remap_pairs);
-CIMGUI_API void igDockBuilderCopyWindowSettings(const char* src_name,
- const char* dst_name);
-CIMGUI_API void igDockBuilderFinish(ImGuiID node_id);
-CIMGUI_API void igPushFocusScope(ImGuiID id);
-CIMGUI_API void igPopFocusScope(void);
-CIMGUI_API ImGuiID igGetCurrentFocusScope(void);
-CIMGUI_API bool igIsDragDropActive(void);
-CIMGUI_API bool igBeginDragDropTargetCustom(const ImRect bb, ImGuiID id);
-CIMGUI_API void igClearDragDrop(void);
-CIMGUI_API bool igIsDragDropPayloadBeingAccepted(void);
-CIMGUI_API void igRenderDragDropTargetRect(const ImRect bb,
- const ImRect item_clip_rect);
-CIMGUI_API ImGuiTypingSelectRequest* igGetTypingSelectRequest(
- ImGuiTypingSelectFlags flags);
-CIMGUI_API int igTypingSelectFindMatch(ImGuiTypingSelectRequest* req,
- int items_count,
- const char* (*get_item_name_func)(void*,
- int),
- void* user_data,
- int nav_item_idx);
-CIMGUI_API int igTypingSelectFindNextSingleCharMatch(
- ImGuiTypingSelectRequest* req,
- int items_count,
- const char* (*get_item_name_func)(void*, int),
- void* user_data,
- int nav_item_idx);
-CIMGUI_API int igTypingSelectFindBestLeadingMatch(
- ImGuiTypingSelectRequest* req,
- int items_count,
- const char* (*get_item_name_func)(void*, int),
- void* user_data);
-CIMGUI_API void igSetWindowClipRectBeforeSetChannel(ImGuiWindow* window,
- const ImRect clip_rect);
-CIMGUI_API void igBeginColumns(const char* str_id,
- int count,
- ImGuiOldColumnFlags flags);
-CIMGUI_API void igEndColumns(void);
-CIMGUI_API void igPushColumnClipRect(int column_index);
-CIMGUI_API void igPushColumnsBackground(void);
-CIMGUI_API void igPopColumnsBackground(void);
-CIMGUI_API ImGuiID igGetColumnsID(const char* str_id, int count);
-CIMGUI_API ImGuiOldColumns* igFindOrCreateColumns(ImGuiWindow* window,
- ImGuiID id);
-CIMGUI_API float igGetColumnOffsetFromNorm(const ImGuiOldColumns* columns,
- float offset_norm);
-CIMGUI_API float igGetColumnNormFromOffset(const ImGuiOldColumns* columns,
- float offset);
-CIMGUI_API void igTableOpenContextMenu(int column_n);
-CIMGUI_API void igTableSetColumnWidth(int column_n, float width);
-CIMGUI_API void igTableSetColumnSortDirection(int column_n,
- ImGuiSortDirection sort_direction,
- bool append_to_sort_specs);
-CIMGUI_API int igTableGetHoveredColumn(void);
-CIMGUI_API int igTableGetHoveredRow(void);
-CIMGUI_API float igTableGetHeaderRowHeight(void);
-CIMGUI_API float igTableGetHeaderAngledMaxLabelWidth(void);
-CIMGUI_API void igTablePushBackgroundChannel(void);
-CIMGUI_API void igTablePopBackgroundChannel(void);
-CIMGUI_API void igTableAngledHeadersRowEx(ImGuiID row_id,
- float angle,
- float max_label_width,
- const ImGuiTableHeaderData* data,
- int data_count);
-CIMGUI_API ImGuiTable* igGetCurrentTable(void);
-CIMGUI_API ImGuiTable* igTableFindByID(ImGuiID id);
-CIMGUI_API bool igBeginTableEx(const char* name,
- ImGuiID id,
- int columns_count,
- ImGuiTableFlags flags,
- const ImVec2 outer_size,
- float inner_width);
-CIMGUI_API void igTableBeginInitMemory(ImGuiTable* table, int columns_count);
-CIMGUI_API void igTableBeginApplyRequests(ImGuiTable* table);
-CIMGUI_API void igTableSetupDrawChannels(ImGuiTable* table);
-CIMGUI_API void igTableUpdateLayout(ImGuiTable* table);
-CIMGUI_API void igTableUpdateBorders(ImGuiTable* table);
-CIMGUI_API void igTableUpdateColumnsWeightFromWidth(ImGuiTable* table);
-CIMGUI_API void igTableDrawBorders(ImGuiTable* table);
-CIMGUI_API void igTableDrawDefaultContextMenu(
- ImGuiTable* table,
- ImGuiTableFlags flags_for_section_to_display);
-CIMGUI_API bool igTableBeginContextMenuPopup(ImGuiTable* table);
-CIMGUI_API void igTableMergeDrawChannels(ImGuiTable* table);
-CIMGUI_API ImGuiTableInstanceData* igTableGetInstanceData(ImGuiTable* table,
- int instance_no);
-CIMGUI_API ImGuiID igTableGetInstanceID(ImGuiTable* table, int instance_no);
-CIMGUI_API void igTableSortSpecsSanitize(ImGuiTable* table);
-CIMGUI_API void igTableSortSpecsBuild(ImGuiTable* table);
-CIMGUI_API ImGuiSortDirection
-igTableGetColumnNextSortDirection(ImGuiTableColumn* column);
-CIMGUI_API void igTableFixColumnSortDirection(ImGuiTable* table,
- ImGuiTableColumn* column);
-CIMGUI_API float igTableGetColumnWidthAuto(ImGuiTable* table,
- ImGuiTableColumn* column);
-CIMGUI_API void igTableBeginRow(ImGuiTable* table);
-CIMGUI_API void igTableEndRow(ImGuiTable* table);
-CIMGUI_API void igTableBeginCell(ImGuiTable* table, int column_n);
-CIMGUI_API void igTableEndCell(ImGuiTable* table);
-CIMGUI_API void igTableGetCellBgRect(ImRect* pOut,
- const ImGuiTable* table,
- int column_n);
-CIMGUI_API const char* igTableGetColumnName_TablePtr(const ImGuiTable* table,
- int column_n);
-CIMGUI_API ImGuiID igTableGetColumnResizeID(ImGuiTable* table,
- int column_n,
- int instance_no);
-CIMGUI_API float igTableGetMaxColumnWidth(const ImGuiTable* table,
- int column_n);
-CIMGUI_API void igTableSetColumnWidthAutoSingle(ImGuiTable* table,
- int column_n);
-CIMGUI_API void igTableSetColumnWidthAutoAll(ImGuiTable* table);
-CIMGUI_API void igTableRemove(ImGuiTable* table);
-CIMGUI_API void igTableGcCompactTransientBuffers_TablePtr(ImGuiTable* table);
-CIMGUI_API void igTableGcCompactTransientBuffers_TableTempDataPtr(
- ImGuiTableTempData* table);
-CIMGUI_API void igTableGcCompactSettings(void);
-CIMGUI_API void igTableLoadSettings(ImGuiTable* table);
-CIMGUI_API void igTableSaveSettings(ImGuiTable* table);
-CIMGUI_API void igTableResetSettings(ImGuiTable* table);
-CIMGUI_API ImGuiTableSettings* igTableGetBoundSettings(ImGuiTable* table);
-CIMGUI_API void igTableSettingsAddSettingsHandler(void);
-CIMGUI_API ImGuiTableSettings* igTableSettingsCreate(ImGuiID id,
- int columns_count);
-CIMGUI_API ImGuiTableSettings* igTableSettingsFindByID(ImGuiID id);
-CIMGUI_API ImGuiTabBar* igGetCurrentTabBar(void);
-CIMGUI_API bool igBeginTabBarEx(ImGuiTabBar* tab_bar,
- const ImRect bb,
- ImGuiTabBarFlags flags);
-CIMGUI_API ImGuiTabItem* igTabBarFindTabByID(ImGuiTabBar* tab_bar,
- ImGuiID tab_id);
-CIMGUI_API ImGuiTabItem* igTabBarFindTabByOrder(ImGuiTabBar* tab_bar,
- int order);
-CIMGUI_API ImGuiTabItem* igTabBarFindMostRecentlySelectedTabForActiveWindow(
- ImGuiTabBar* tab_bar);
-CIMGUI_API ImGuiTabItem* igTabBarGetCurrentTab(ImGuiTabBar* tab_bar);
-CIMGUI_API int igTabBarGetTabOrder(ImGuiTabBar* tab_bar, ImGuiTabItem* tab);
-CIMGUI_API const char* igTabBarGetTabName(ImGuiTabBar* tab_bar,
- ImGuiTabItem* tab);
-CIMGUI_API void igTabBarAddTab(ImGuiTabBar* tab_bar,
- ImGuiTabItemFlags tab_flags,
- ImGuiWindow* window);
-CIMGUI_API void igTabBarRemoveTab(ImGuiTabBar* tab_bar, ImGuiID tab_id);
-CIMGUI_API void igTabBarCloseTab(ImGuiTabBar* tab_bar, ImGuiTabItem* tab);
-CIMGUI_API void igTabBarQueueFocus(ImGuiTabBar* tab_bar, ImGuiTabItem* tab);
-CIMGUI_API void igTabBarQueueReorder(ImGuiTabBar* tab_bar,
- ImGuiTabItem* tab,
- int offset);
-CIMGUI_API void igTabBarQueueReorderFromMousePos(ImGuiTabBar* tab_bar,
- ImGuiTabItem* tab,
- ImVec2 mouse_pos);
-CIMGUI_API bool igTabBarProcessReorder(ImGuiTabBar* tab_bar);
-CIMGUI_API bool igTabItemEx(ImGuiTabBar* tab_bar,
- const char* label,
- bool* p_open,
- ImGuiTabItemFlags flags,
- ImGuiWindow* docked_window);
-CIMGUI_API void igTabItemCalcSize_Str(ImVec2* pOut,
- const char* label,
- bool has_close_button_or_unsaved_marker);
-CIMGUI_API void igTabItemCalcSize_WindowPtr(ImVec2* pOut, ImGuiWindow* window);
-CIMGUI_API void igTabItemBackground(ImDrawList* draw_list,
- const ImRect bb,
- ImGuiTabItemFlags flags,
- ImU32 col);
-CIMGUI_API void igTabItemLabelAndCloseButton(ImDrawList* draw_list,
- const ImRect bb,
- ImGuiTabItemFlags flags,
- ImVec2 frame_padding,
- const char* label,
- ImGuiID tab_id,
- ImGuiID close_button_id,
- bool is_contents_visible,
- bool* out_just_closed,
- bool* out_text_clipped);
-CIMGUI_API void igRenderText(ImVec2 pos,
- const char* text,
- const char* text_end,
- bool hide_text_after_hash);
-CIMGUI_API void igRenderTextWrapped(ImVec2 pos,
- const char* text,
- const char* text_end,
- float wrap_width);
-CIMGUI_API void igRenderTextClipped(const ImVec2 pos_min,
- const ImVec2 pos_max,
- const char* text,
- const char* text_end,
- const ImVec2* text_size_if_known,
- const ImVec2 align,
- const ImRect* clip_rect);
-CIMGUI_API void igRenderTextClippedEx(ImDrawList* draw_list,
- const ImVec2 pos_min,
- const ImVec2 pos_max,
- const char* text,
- const char* text_end,
- const ImVec2* text_size_if_known,
- const ImVec2 align,
- const ImRect* clip_rect);
-CIMGUI_API void igRenderTextEllipsis(ImDrawList* draw_list,
- const ImVec2 pos_min,
- const ImVec2 pos_max,
- float clip_max_x,
- float ellipsis_max_x,
- const char* text,
- const char* text_end,
- const ImVec2* text_size_if_known);
-CIMGUI_API void igRenderFrame(ImVec2 p_min,
- ImVec2 p_max,
- ImU32 fill_col,
- bool border,
- float rounding);
-CIMGUI_API void igRenderFrameBorder(ImVec2 p_min, ImVec2 p_max, float rounding);
-CIMGUI_API void igRenderColorRectWithAlphaCheckerboard(ImDrawList* draw_list,
- ImVec2 p_min,
- ImVec2 p_max,
- ImU32 fill_col,
- float grid_step,
- ImVec2 grid_off,
- float rounding,
- ImDrawFlags flags);
-CIMGUI_API void igRenderNavHighlight(const ImRect bb,
- ImGuiID id,
- ImGuiNavHighlightFlags flags);
-CIMGUI_API const char* igFindRenderedTextEnd(const char* text,
- const char* text_end);
-CIMGUI_API void igRenderMouseCursor(ImVec2 pos,
- float scale,
- ImGuiMouseCursor mouse_cursor,
- ImU32 col_fill,
- ImU32 col_border,
- ImU32 col_shadow);
-CIMGUI_API void igRenderArrow(ImDrawList* draw_list,
- ImVec2 pos,
- ImU32 col,
- ImGuiDir dir,
- float scale);
-CIMGUI_API void igRenderBullet(ImDrawList* draw_list, ImVec2 pos, ImU32 col);
-CIMGUI_API void igRenderCheckMark(ImDrawList* draw_list,
- ImVec2 pos,
- ImU32 col,
- float sz);
-CIMGUI_API void igRenderArrowPointingAt(ImDrawList* draw_list,
- ImVec2 pos,
- ImVec2 half_sz,
- ImGuiDir direction,
- ImU32 col);
-CIMGUI_API void igRenderArrowDockMenu(ImDrawList* draw_list,
- ImVec2 p_min,
- float sz,
- ImU32 col);
-CIMGUI_API void igRenderRectFilledRangeH(ImDrawList* draw_list,
- const ImRect rect,
- ImU32 col,
- float x_start_norm,
- float x_end_norm,
- float rounding);
-CIMGUI_API void igRenderRectFilledWithHole(ImDrawList* draw_list,
- const ImRect outer,
- const ImRect inner,
- ImU32 col,
- float rounding);
-CIMGUI_API ImDrawFlags igCalcRoundingFlagsForRectInRect(const ImRect r_in,
- const ImRect r_outer,
- float threshold);
-CIMGUI_API void igTextEx(const char* text,
- const char* text_end,
- ImGuiTextFlags flags);
-CIMGUI_API bool igButtonEx(const char* label,
- const ImVec2 size_arg,
- ImGuiButtonFlags flags);
-CIMGUI_API bool igArrowButtonEx(const char* str_id,
- ImGuiDir dir,
- ImVec2 size_arg,
- ImGuiButtonFlags flags);
-CIMGUI_API bool igImageButtonEx(ImGuiID id,
- ImTextureID texture_id,
- const ImVec2 image_size,
- const ImVec2 uv0,
- const ImVec2 uv1,
- const ImVec4 bg_col,
- const ImVec4 tint_col,
- ImGuiButtonFlags flags);
-CIMGUI_API void igSeparatorEx(ImGuiSeparatorFlags flags, float thickness);
-CIMGUI_API void igSeparatorTextEx(ImGuiID id,
- const char* label,
- const char* label_end,
- float extra_width);
-CIMGUI_API bool igCheckboxFlags_S64Ptr(const char* label,
- ImS64* flags,
- ImS64 flags_value);
-CIMGUI_API bool igCheckboxFlags_U64Ptr(const char* label,
- ImU64* flags,
- ImU64 flags_value);
-CIMGUI_API bool igCloseButton(ImGuiID id, const ImVec2 pos);
-CIMGUI_API bool igCollapseButton(ImGuiID id,
- const ImVec2 pos,
- ImGuiDockNode* dock_node);
-CIMGUI_API void igScrollbar(ImGuiAxis axis);
-CIMGUI_API bool igScrollbarEx(const ImRect bb,
- ImGuiID id,
- ImGuiAxis axis,
- ImS64* p_scroll_v,
- ImS64 avail_v,
- ImS64 contents_v,
- ImDrawFlags flags);
-CIMGUI_API void igGetWindowScrollbarRect(ImRect* pOut,
- ImGuiWindow* window,
- ImGuiAxis axis);
-CIMGUI_API ImGuiID igGetWindowScrollbarID(ImGuiWindow* window, ImGuiAxis axis);
-CIMGUI_API ImGuiID igGetWindowResizeCornerID(ImGuiWindow* window, int n);
-CIMGUI_API ImGuiID igGetWindowResizeBorderID(ImGuiWindow* window, ImGuiDir dir);
-CIMGUI_API bool igButtonBehavior(const ImRect bb,
- ImGuiID id,
- bool* out_hovered,
- bool* out_held,
- ImGuiButtonFlags flags);
-CIMGUI_API bool igDragBehavior(ImGuiID id,
- ImGuiDataType data_type,
- void* p_v,
- float v_speed,
- const void* p_min,
- const void* p_max,
- const char* format,
- ImGuiSliderFlags flags);
-CIMGUI_API bool igSliderBehavior(const ImRect bb,
- ImGuiID id,
- ImGuiDataType data_type,
- void* p_v,
- const void* p_min,
- const void* p_max,
- const char* format,
- ImGuiSliderFlags flags,
- ImRect* out_grab_bb);
-CIMGUI_API bool igSplitterBehavior(const ImRect bb,
- ImGuiID id,
- ImGuiAxis axis,
- float* size1,
- float* size2,
- float min_size1,
- float min_size2,
- float hover_extend,
- float hover_visibility_delay,
- ImU32 bg_col);
-CIMGUI_API bool igTreeNodeBehavior(ImGuiID id,
- ImGuiTreeNodeFlags flags,
- const char* label,
- const char* label_end);
-CIMGUI_API void igTreePushOverrideID(ImGuiID id);
-CIMGUI_API void igTreeNodeSetOpen(ImGuiID id, bool open);
-CIMGUI_API bool igTreeNodeUpdateNextOpen(ImGuiID id, ImGuiTreeNodeFlags flags);
-CIMGUI_API void igSetNextItemSelectionUserData(
- ImGuiSelectionUserData selection_user_data);
-CIMGUI_API const ImGuiDataTypeInfo* igDataTypeGetInfo(ImGuiDataType data_type);
-CIMGUI_API int igDataTypeFormatString(char* buf,
- int buf_size,
- ImGuiDataType data_type,
- const void* p_data,
- const char* format);
-CIMGUI_API void igDataTypeApplyOp(ImGuiDataType data_type,
- int op,
- void* output,
- const void* arg_1,
- const void* arg_2);
-CIMGUI_API bool igDataTypeApplyFromText(const char* buf,
- ImGuiDataType data_type,
- void* p_data,
- const char* format);
-CIMGUI_API int igDataTypeCompare(ImGuiDataType data_type,
- const void* arg_1,
- const void* arg_2);
-CIMGUI_API bool igDataTypeClamp(ImGuiDataType data_type,
- void* p_data,
- const void* p_min,
- const void* p_max);
-CIMGUI_API bool igInputTextEx(const char* label,
- const char* hint,
- char* buf,
- int buf_size,
- const ImVec2 size_arg,
- ImGuiInputTextFlags flags,
- ImGuiInputTextCallback callback,
- void* user_data);
-CIMGUI_API void igInputTextDeactivateHook(ImGuiID id);
-CIMGUI_API bool igTempInputText(const ImRect bb,
- ImGuiID id,
- const char* label,
- char* buf,
- int buf_size,
- ImGuiInputTextFlags flags);
-CIMGUI_API bool igTempInputScalar(const ImRect bb,
- ImGuiID id,
- const char* label,
- ImGuiDataType data_type,
- void* p_data,
- const char* format,
- const void* p_clamp_min,
- const void* p_clamp_max);
-CIMGUI_API bool igTempInputIsActive(ImGuiID id);
-CIMGUI_API ImGuiInputTextState* igGetInputTextState(ImGuiID id);
-CIMGUI_API void igColorTooltip(const char* text,
- const float* col,
- ImGuiColorEditFlags flags);
-CIMGUI_API void igColorEditOptionsPopup(const float* col,
- ImGuiColorEditFlags flags);
-CIMGUI_API void igColorPickerOptionsPopup(const float* ref_col,
- ImGuiColorEditFlags flags);
-CIMGUI_API int igPlotEx(ImGuiPlotType plot_type,
- const char* label,
- float (*values_getter)(void* data, int idx),
- void* data,
- int values_count,
- int values_offset,
- const char* overlay_text,
- float scale_min,
- float scale_max,
- const ImVec2 size_arg);
-CIMGUI_API void igShadeVertsLinearColorGradientKeepAlpha(ImDrawList* draw_list,
- int vert_start_idx,
- int vert_end_idx,
- ImVec2 gradient_p0,
- ImVec2 gradient_p1,
- ImU32 col0,
- ImU32 col1);
-CIMGUI_API void igShadeVertsLinearUV(ImDrawList* draw_list,
- int vert_start_idx,
- int vert_end_idx,
- const ImVec2 a,
- const ImVec2 b,
- const ImVec2 uv_a,
- const ImVec2 uv_b,
- bool clamp);
-CIMGUI_API void igShadeVertsTransformPos(ImDrawList* draw_list,
- int vert_start_idx,
- int vert_end_idx,
- const ImVec2 pivot_in,
- float cos_a,
- float sin_a,
- const ImVec2 pivot_out);
-CIMGUI_API void igGcCompactTransientMiscBuffers(void);
-CIMGUI_API void igGcCompactTransientWindowBuffers(ImGuiWindow* window);
-CIMGUI_API void igGcAwakeTransientWindowBuffers(ImGuiWindow* window);
-CIMGUI_API void igDebugLog(const char* fmt, ...);
-CIMGUI_API void igDebugLogV(const char* fmt, va_list args);
-CIMGUI_API void igDebugAllocHook(ImGuiDebugAllocInfo* info,
- int frame_count,
- void* ptr,
- size_t size);
-CIMGUI_API void igErrorCheckEndFrameRecover(ImGuiErrorLogCallback log_callback,
- void* user_data);
-CIMGUI_API void igErrorCheckEndWindowRecover(ImGuiErrorLogCallback log_callback,
- void* user_data);
-CIMGUI_API void igErrorCheckUsingSetCursorPosToExtendParentBoundaries(void);
-CIMGUI_API void igDebugDrawCursorPos(ImU32 col);
-CIMGUI_API void igDebugDrawLineExtents(ImU32 col);
-CIMGUI_API void igDebugDrawItemRect(ImU32 col);
-CIMGUI_API void igDebugLocateItem(ImGuiID target_id);
-CIMGUI_API void igDebugLocateItemOnHover(ImGuiID target_id);
-CIMGUI_API void igDebugLocateItemResolveWithLastItem(void);
-CIMGUI_API void igDebugBreakClearData(void);
-CIMGUI_API bool igDebugBreakButton(const char* label,
- const char* description_of_location);
-CIMGUI_API void igDebugBreakButtonTooltip(bool keyboard_only,
- const char* description_of_location);
-CIMGUI_API void igShowFontAtlas(ImFontAtlas* atlas);
-CIMGUI_API void igDebugHookIdInfo(ImGuiID id,
- ImGuiDataType data_type,
- const void* data_id,
- const void* data_id_end);
-CIMGUI_API void igDebugNodeColumns(ImGuiOldColumns* columns);
-CIMGUI_API void igDebugNodeDockNode(ImGuiDockNode* node, const char* label);
-CIMGUI_API void igDebugNodeDrawList(ImGuiWindow* window,
- ImGuiViewportP* viewport,
- const ImDrawList* draw_list,
- const char* label);
-CIMGUI_API void igDebugNodeDrawCmdShowMeshAndBoundingBox(
- ImDrawList* out_draw_list,
- const ImDrawList* draw_list,
- const ImDrawCmd* draw_cmd,
- bool show_mesh,
- bool show_aabb);
-CIMGUI_API void igDebugNodeFont(ImFont* font);
-CIMGUI_API void igDebugNodeFontGlyph(ImFont* font, const ImFontGlyph* glyph);
-CIMGUI_API void igDebugNodeStorage(ImGuiStorage* storage, const char* label);
-CIMGUI_API void igDebugNodeTabBar(ImGuiTabBar* tab_bar, const char* label);
-CIMGUI_API void igDebugNodeTable(ImGuiTable* table);
-CIMGUI_API void igDebugNodeTableSettings(ImGuiTableSettings* settings);
-CIMGUI_API void igDebugNodeInputTextState(ImGuiInputTextState* state);
-CIMGUI_API void igDebugNodeTypingSelectState(ImGuiTypingSelectState* state);
-CIMGUI_API void igDebugNodeWindow(ImGuiWindow* window, const char* label);
-CIMGUI_API void igDebugNodeWindowSettings(ImGuiWindowSettings* settings);
-CIMGUI_API void igDebugNodeWindowsList(ImVector_ImGuiWindowPtr* windows,
- const char* label);
-CIMGUI_API void igDebugNodeWindowsListByBeginStackParent(
- ImGuiWindow** windows,
- int windows_size,
- ImGuiWindow* parent_in_begin_stack);
-CIMGUI_API void igDebugNodeViewport(ImGuiViewportP* viewport);
-CIMGUI_API void igDebugRenderKeyboardPreview(ImDrawList* draw_list);
-CIMGUI_API void igDebugRenderViewportThumbnail(ImDrawList* draw_list,
- ImGuiViewportP* viewport,
- const ImRect bb);
-CIMGUI_API void igImFontAtlasUpdateConfigDataPointers(ImFontAtlas* atlas);
-CIMGUI_API void igImFontAtlasBuildInit(ImFontAtlas* atlas);
-CIMGUI_API void igImFontAtlasBuildSetupFont(ImFontAtlas* atlas,
- ImFont* font,
- ImFontConfig* font_config,
- float ascent,
- float descent);
-CIMGUI_API void igImFontAtlasBuildPackCustomRects(ImFontAtlas* atlas,
- void* stbrp_context_opaque);
-CIMGUI_API void igImFontAtlasBuildFinish(ImFontAtlas* atlas);
-CIMGUI_API void igImFontAtlasBuildRender8bppRectFromString(
- ImFontAtlas* atlas,
- int x,
- int y,
- int w,
- int h,
- const char* in_str,
- char in_marker_char,
- unsigned char in_marker_pixel_value);
-CIMGUI_API void igImFontAtlasBuildRender32bppRectFromString(
- ImFontAtlas* atlas,
- int x,
- int y,
- int w,
- int h,
- const char* in_str,
- char in_marker_char,
- unsigned int in_marker_pixel_value);
-CIMGUI_API void igImFontAtlasBuildMultiplyCalcLookupTable(
- unsigned char out_table[256],
- float in_multiply_factor);
-CIMGUI_API void igImFontAtlasBuildMultiplyRectAlpha8(
- const unsigned char table[256],
- unsigned char* pixels,
- int x,
- int y,
- int w,
- int h,
- int stride);
-
-/////////////////////////hand written functions
-// no LogTextV
-CIMGUI_API void igLogText(CONST char* fmt, ...);
-// no appendfV
-CIMGUI_API void ImGuiTextBuffer_appendf(struct ImGuiTextBuffer* buffer,
- const char* fmt,
- ...);
-// for getting FLT_MAX in bindings
-CIMGUI_API float igGET_FLT_MAX(void);
-// for getting FLT_MIN in bindings
-CIMGUI_API float igGET_FLT_MIN(void);
-
-CIMGUI_API ImVector_ImWchar* ImVector_ImWchar_create(void);
-CIMGUI_API void ImVector_ImWchar_destroy(ImVector_ImWchar* self);
-CIMGUI_API void ImVector_ImWchar_Init(ImVector_ImWchar* p);
-CIMGUI_API void ImVector_ImWchar_UnInit(ImVector_ImWchar* p);
-
-#endif // CIMGUI_INCLUDED
diff --git a/pkg/dcimgui/build.zig b/pkg/dcimgui/build.zig
new file mode 100644
index 000000000..95c4af303
--- /dev/null
+++ b/pkg/dcimgui/build.zig
@@ -0,0 +1,199 @@
+const std = @import("std");
+const NativeTargetInfo = std.zig.system.NativeTargetInfo;
+
+pub fn build(b: *std.Build) !void {
+ const target = b.standardTargetOptions(.{});
+ const optimize = b.standardOptimizeOption(.{});
+ const freetype = b.option(bool, "freetype", "Use Freetype") orelse false;
+ const backend_opengl3 = b.option(bool, "backend-opengl3", "OpenGL3 backend") orelse false;
+ const backend_metal = b.option(bool, "backend-metal", "Metal backend") orelse false;
+ const backend_osx = b.option(bool, "backend-osx", "OSX backend") orelse false;
+
+ // Build options
+ const options = b.addOptions();
+ options.addOption(bool, "freetype", freetype);
+ options.addOption(bool, "backend_opengl3", backend_opengl3);
+ options.addOption(bool, "backend_metal", backend_metal);
+ options.addOption(bool, "backend_osx", backend_osx);
+
+ // Main static lib
+ const lib = b.addLibrary(.{
+ .name = "dcimgui",
+ .root_module = b.createModule(.{
+ .target = target,
+ .optimize = optimize,
+ }),
+ .linkage = .static,
+ });
+ lib.linkLibC();
+ lib.linkLibCpp();
+ b.installArtifact(lib);
+
+ // Zig module
+ const mod = b.addModule("dcimgui", .{
+ .root_source_file = b.path("main.zig"),
+ .target = target,
+ .optimize = optimize,
+ });
+ mod.addOptions("build_options", options);
+ mod.linkLibrary(lib);
+
+ // We need to add proper Apple SDKs to find stdlib headers
+ if (target.result.os.tag.isDarwin()) {
+ if (!target.query.isNative()) {
+ try @import("apple_sdk").addPaths(b, lib);
+ }
+ }
+
+ // Flags for C compilation, common to all.
+ var flags: std.ArrayList([]const u8) = .empty;
+ defer flags.deinit(b.allocator);
+ try flags.appendSlice(b.allocator, &.{
+ "-DIMGUI_USE_WCHAR32=1",
+ "-DIMGUI_DISABLE_OBSOLETE_FUNCTIONS=1",
+ });
+ if (freetype) try flags.appendSlice(b.allocator, &.{
+ "-DIMGUI_ENABLE_FREETYPE=1",
+ });
+ if (target.result.os.tag == .windows) {
+ try flags.appendSlice(b.allocator, &.{
+ "-DIMGUI_IMPL_API=extern\t\"C\"\t__declspec(dllexport)",
+ });
+ } else {
+ try flags.appendSlice(b.allocator, &.{
+ "-DIMGUI_IMPL_API=extern\t\"C\"",
+ });
+ }
+ if (target.result.os.tag == .freebsd or target.result.abi == .musl) {
+ try flags.append(b.allocator, "-fPIC");
+ }
+
+ // Add the core Dear Imgui source files
+ if (b.lazyDependency("imgui", .{})) |upstream| {
+ lib.addIncludePath(upstream.path(""));
+ lib.addCSourceFiles(.{
+ .root = upstream.path(""),
+ .files = &.{
+ "imgui_demo.cpp",
+ "imgui_draw.cpp",
+ "imgui_tables.cpp",
+ "imgui_widgets.cpp",
+ "imgui.cpp",
+ },
+ .flags = flags.items,
+ });
+
+ lib.installHeadersDirectory(
+ upstream.path(""),
+ "",
+ .{ .include_extensions = &.{".h"} },
+ );
+
+ if (freetype) {
+ lib.addCSourceFile(.{
+ .file = upstream.path("misc/freetype/imgui_freetype.cpp"),
+ .flags = flags.items,
+ });
+
+ if (b.systemIntegrationOption("freetype", .{})) {
+ lib.linkSystemLibrary2("freetype2", dynamic_link_opts);
+ } else {
+ const freetype_dep = b.dependency("freetype", .{
+ .target = target,
+ .optimize = optimize,
+ .@"enable-libpng" = true,
+ });
+ lib.linkLibrary(freetype_dep.artifact("freetype"));
+ if (freetype_dep.builder.lazyDependency(
+ "freetype",
+ .{},
+ )) |freetype_upstream| {
+ mod.addIncludePath(freetype_upstream.path("include"));
+ }
+ }
+ }
+
+ if (backend_metal) {
+ lib.addCSourceFiles(.{
+ .root = upstream.path("backends"),
+ .files = &.{"imgui_impl_metal.mm"},
+ .flags = flags.items,
+ });
+ lib.installHeadersDirectory(
+ upstream.path("backends"),
+ "",
+ .{ .include_extensions = &.{"imgui_impl_metal.h"} },
+ );
+ }
+ if (backend_osx) {
+ lib.addCSourceFiles(.{
+ .root = upstream.path("backends"),
+ .files = &.{"imgui_impl_osx.mm"},
+ .flags = flags.items,
+ });
+ lib.installHeadersDirectory(
+ upstream.path("backends"),
+ "",
+ .{ .include_extensions = &.{"imgui_impl_osx.h"} },
+ );
+ }
+ if (backend_opengl3) {
+ lib.addCSourceFiles(.{
+ .root = upstream.path("backends"),
+ .files = &.{"imgui_impl_opengl3.cpp"},
+ .flags = flags.items,
+ });
+ lib.installHeadersDirectory(
+ upstream.path("backends"),
+ "",
+ .{ .include_extensions = &.{"imgui_impl_opengl3.h"} },
+ );
+ }
+ }
+
+ // Add the C bindings
+ if (b.lazyDependency("bindings", .{})) |upstream| {
+ lib.addIncludePath(upstream.path(""));
+ lib.addCSourceFiles(.{
+ .root = upstream.path(""),
+ .files = &.{
+ "dcimgui.cpp",
+ "dcimgui_internal.cpp",
+ },
+ .flags = flags.items,
+ });
+ lib.addCSourceFiles(.{
+ .root = b.path(""),
+ .files = &.{"ext.cpp"},
+ .flags = flags.items,
+ });
+
+ lib.installHeadersDirectory(
+ upstream.path(""),
+ "",
+ .{ .include_extensions = &.{".h"} },
+ );
+ }
+
+ const test_exe = b.addTest(.{
+ .name = "test",
+ .root_module = b.createModule(.{
+ .root_source_file = b.path("main.zig"),
+ .target = target,
+ .optimize = optimize,
+ }),
+ });
+ test_exe.root_module.addOptions("build_options", options);
+ test_exe.linkLibrary(lib);
+ const tests_run = b.addRunArtifact(test_exe);
+ const test_step = b.step("test", "Run tests");
+ test_step.dependOn(&tests_run.step);
+}
+
+// For dynamic linking, we prefer dynamic linking and to search by
+// mode first. Mode first will search all paths for a dynamic library
+// before falling back to static.
+const dynamic_link_opts: std.Build.Module.LinkSystemLibraryOptions = .{
+ .preferred_link_mode = .dynamic,
+ .search_strategy = .mode_first,
+};
diff --git a/pkg/dcimgui/build.zig.zon b/pkg/dcimgui/build.zig.zon
new file mode 100644
index 000000000..95d0120e1
--- /dev/null
+++ b/pkg/dcimgui/build.zig.zon
@@ -0,0 +1,26 @@
+.{
+ .name = .dcimgui,
+ .version = "1.92.5", // -docking branch
+ .fingerprint = 0x1a25797442c6324f,
+ .paths = .{""},
+ .dependencies = .{
+ // The bindings and imgui versions below must match exactly.
+
+ .bindings = .{
+ // https://github.com/dearimgui/dear_bindings
+ .url = "https://deps.files.ghostty.org/DearBindings_v0.17_ImGui_v1.92.5-docking.tar.gz",
+ .hash = "N-V-__8AANT61wB--nJ95Gj_ctmzAtcjloZ__hRqNw5lC1Kr",
+ .lazy = true,
+ },
+
+ .imgui = .{
+ // https://github.com/ocornut/imgui
+ .url = "https://github.com/ocornut/imgui/archive/refs/tags/v1.92.5-docking.tar.gz",
+ .hash = "N-V-__8AAEbOfQBnvcFcCX2W5z7tDaN8vaNZGamEQtNOe0UI",
+ .lazy = true,
+ },
+
+ .apple_sdk = .{ .path = "../apple-sdk" },
+ .freetype = .{ .path = "../freetype" },
+ },
+}
diff --git a/pkg/dcimgui/ext.cpp b/pkg/dcimgui/ext.cpp
new file mode 100644
index 000000000..d4732e0fa
--- /dev/null
+++ b/pkg/dcimgui/ext.cpp
@@ -0,0 +1,30 @@
+#include "imgui.h"
+
+// This file contains custom extensions for functionality that isn't
+// properly supported by Dear Bindings yet. Namely:
+// https://github.com/dearimgui/dear_bindings/issues/55
+
+// Wrap this in a namespace to keep it separate from the C++ API
+namespace cimgui
+{
+#include "dcimgui.h"
+}
+
+extern "C"
+{
+CIMGUI_API void ImFontConfig_ImFontConfig(cimgui::ImFontConfig* self)
+{
+ static_assert(sizeof(cimgui::ImFontConfig) == sizeof(::ImFontConfig), "ImFontConfig size mismatch");
+ static_assert(alignof(cimgui::ImFontConfig) == alignof(::ImFontConfig), "ImFontConfig alignment mismatch");
+ ::ImFontConfig defaults;
+ *reinterpret_cast<::ImFontConfig*>(self) = defaults;
+}
+
+CIMGUI_API void ImGuiStyle_ImGuiStyle(cimgui::ImGuiStyle* self)
+{
+ static_assert(sizeof(cimgui::ImGuiStyle) == sizeof(::ImGuiStyle), "ImGuiStyle size mismatch");
+ static_assert(alignof(cimgui::ImGuiStyle) == alignof(::ImGuiStyle), "ImGuiStyle alignment mismatch");
+ ::ImGuiStyle defaults;
+ *reinterpret_cast<::ImGuiStyle*>(self) = defaults;
+}
+}
diff --git a/pkg/dcimgui/main.zig b/pkg/dcimgui/main.zig
new file mode 100644
index 000000000..e709158f5
--- /dev/null
+++ b/pkg/dcimgui/main.zig
@@ -0,0 +1,43 @@
+pub const build_options = @import("build_options");
+
+pub const c = @cImport({
+ // This is set during the build so it also has to be set
+ // during import time to get the right types. Without this
+ // you get stack size mismatches on some structs.
+ @cDefine("IMGUI_USE_WCHAR32", "1");
+ @cInclude("dcimgui.h");
+});
+
+// OpenGL3 backend
+pub extern fn ImGui_ImplOpenGL3_Init(glsl_version: ?[*:0]const u8) callconv(.c) bool;
+pub extern fn ImGui_ImplOpenGL3_Shutdown() callconv(.c) void;
+pub extern fn ImGui_ImplOpenGL3_NewFrame() callconv(.c) void;
+pub extern fn ImGui_ImplOpenGL3_RenderDrawData(draw_data: *c.ImDrawData) callconv(.c) void;
+
+// Metal backend
+pub extern fn ImGui_ImplMetal_Init(device: *anyopaque) callconv(.c) bool;
+pub extern fn ImGui_ImplMetal_Shutdown() callconv(.c) void;
+pub extern fn ImGui_ImplMetal_NewFrame(render_pass_descriptor: *anyopaque) callconv(.c) void;
+pub extern fn ImGui_ImplMetal_RenderDrawData(draw_data: *c.ImDrawData, command_buffer: *anyopaque, command_encoder: *anyopaque) callconv(.c) void;
+
+// OSX
+pub extern fn ImGui_ImplOSX_Init(*anyopaque) callconv(.c) bool;
+pub extern fn ImGui_ImplOSX_Shutdown() callconv(.c) void;
+pub extern fn ImGui_ImplOSX_NewFrame(*anyopaque) callconv(.c) void;
+
+// Internal API functions from dcimgui_internal.h
+// We declare these manually because the internal header contains bitfields
+// that Zig's cImport cannot translate.
+pub extern fn ImGui_DockBuilderDockWindow(window_name: [*:0]const u8, node_id: c.ImGuiID) callconv(.c) void;
+pub extern fn ImGui_DockBuilderSplitNode(node_id: c.ImGuiID, split_dir: c.ImGuiDir, size_ratio_for_node_at_dir: f32, out_id_at_dir: *c.ImGuiID, out_id_at_opposite_dir: *c.ImGuiID) callconv(.c) c.ImGuiID;
+pub extern fn ImGui_DockBuilderFinish(node_id: c.ImGuiID) callconv(.c) void;
+
+// Extension functions from ext.cpp
+pub const ext = struct {
+ pub extern fn ImFontConfig_ImFontConfig(self: *c.ImFontConfig) callconv(.c) void;
+ pub extern fn ImGuiStyle_ImGuiStyle(self: *c.ImGuiStyle) callconv(.c) void;
+};
+
+test {
+ _ = c;
+}
diff --git a/pkg/freetype/build.zig b/pkg/freetype/build.zig
index a25dc18da..ecb22cb6c 100644
--- a/pkg/freetype/build.zig
+++ b/pkg/freetype/build.zig
@@ -90,6 +90,10 @@ fn buildLib(b: *std.Build, module: *std.Build.Module, options: anytype) !*std.Bu
"-fno-sanitize=undefined",
});
+ if (target.result.os.tag == .freebsd or target.result.abi == .musl) {
+ try flags.append(b.allocator, "-fPIC");
+ }
+
const dynamic_link_opts = options.dynamic_link_opts;
// Zlib
diff --git a/pkg/glslang/build.zig b/pkg/glslang/build.zig
index 746a41497..c41e05217 100644
--- a/pkg/glslang/build.zig
+++ b/pkg/glslang/build.zig
@@ -66,6 +66,10 @@ fn buildGlslang(
"-fno-sanitize-trap=undefined",
});
+ if (target.result.os.tag == .freebsd or target.result.abi == .musl) {
+ try flags.append(b.allocator, "-fPIC");
+ }
+
if (upstream_) |upstream| {
lib.addCSourceFiles(.{
.root = upstream.path(""),
diff --git a/pkg/highway/build.zig b/pkg/highway/build.zig
index fd93675e6..3715baf4a 100644
--- a/pkg/highway/build.zig
+++ b/pkg/highway/build.zig
@@ -72,6 +72,11 @@ pub fn build(b: *std.Build) !void {
"-fno-sanitize=undefined",
"-fno-sanitize-trap=undefined",
});
+
+ if (target.result.os.tag == .freebsd or target.result.abi == .musl) {
+ try flags.append(b.allocator, "-fPIC");
+ }
+
if (target.result.os.tag != .windows) {
try flags.appendSlice(b.allocator, &.{
"-fmath-errno",
diff --git a/pkg/simdutf/build.zig b/pkg/simdutf/build.zig
index 0d827c1cc..3123cab21 100644
--- a/pkg/simdutf/build.zig
+++ b/pkg/simdutf/build.zig
@@ -32,6 +32,10 @@ pub fn build(b: *std.Build) !void {
"-fno-sanitize-trap=undefined",
});
+ if (target.result.os.tag == .freebsd or target.result.abi == .musl) {
+ try flags.append(b.allocator, "-fPIC");
+ }
+
lib.addCSourceFiles(.{
.flags = flags.items,
.files = &.{
diff --git a/pkg/spirv-cross/build.zig b/pkg/spirv-cross/build.zig
index 003ec43cf..f85e74adf 100644
--- a/pkg/spirv-cross/build.zig
+++ b/pkg/spirv-cross/build.zig
@@ -74,6 +74,10 @@ fn buildSpirvCross(
"-fno-sanitize-trap=undefined",
});
+ if (target.result.os.tag == .freebsd or target.result.abi == .musl) {
+ try flags.append(b.allocator, "-fPIC");
+ }
+
if (b.lazyDependency("spirv_cross", .{})) |upstream| {
lib.addIncludePath(upstream.path(""));
module.addIncludePath(upstream.path(""));
diff --git a/po/de_DE.UTF-8.po b/po/de_DE.UTF-8.po
index bbe9ce031..f73f1c251 100644
--- a/po/de_DE.UTF-8.po
+++ b/po/de_DE.UTF-8.po
@@ -3,14 +3,15 @@
# Copyright (C) 2025 Mitchell Hashimoto, Ghostty contributors
# This file is distributed under the same license as the com.mitchellh.ghostty package.
# Robin Pfรคffle , 2025.
+# Jan Klass , 2026.
#
msgid ""
msgstr ""
"Project-Id-Version: com.mitchellh.ghostty\n"
"Report-Msgid-Bugs-To: m@mitchellh.com\n"
-"POT-Creation-Date: 2025-12-19 10:30-0500\n"
-"PO-Revision-Date: 2025-08-25 19:38+0100\n"
-"Last-Translator: Robin \n"
+"POT-Creation-Date: 2025-07-22 17:18+0000\n"
+"PO-Revision-Date: 2026-01-06 10:25+0100\n"
+"Last-Translator: Jan Klass \n"
"Language-Team: German \n"
"Language: de\n"
"MIME-Version: 1.0\n"
@@ -18,235 +19,174 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-#: src/apprt/gtk/ui/1.0/clipboard-confirmation-dialog.blp:12
-#: src/apprt/gtk/ui/1.4/clipboard-confirmation-dialog.blp:12
-#: src/apprt/gtk/class/clipboard_confirmation_dialog.zig:197
-#: src/apprt/gtk/class/clipboard_confirmation_dialog.zig:201
-msgid "Authorize Clipboard Access"
-msgstr "Zugriff auf die Zwischenablage gewรคhren"
+#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:5
+msgid "Change Terminal Title"
+msgstr "Terminal-Titel bearbeiten"
-#: src/apprt/gtk/ui/1.0/clipboard-confirmation-dialog.blp:17
-#: src/apprt/gtk/ui/1.4/clipboard-confirmation-dialog.blp:17
-msgid "Deny"
-msgstr "Nicht erlauben"
+#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:6
+msgid "Leave blank to restore the default title."
+msgstr "Leer lassen, um den Standardtitel wiederherzustellen."
-#: src/apprt/gtk/ui/1.0/clipboard-confirmation-dialog.blp:18
-#: src/apprt/gtk/ui/1.4/clipboard-confirmation-dialog.blp:18
-msgid "Allow"
-msgstr "Erlauben"
-
-#: src/apprt/gtk/ui/1.4/clipboard-confirmation-dialog.blp:92
-msgid "Remember choice for this split"
-msgstr "Auswahl fรผr dieses geteilte Fenster beibehalten"
-
-#: src/apprt/gtk/ui/1.4/clipboard-confirmation-dialog.blp:93
-msgid "Reload configuration to show this prompt again"
-msgstr ""
-"Lade die Konfiguration erneut, um diese Eingabeaufforderung erneut anzuzeigen"
-
-#: src/apprt/gtk/ui/1.2/close-confirmation-dialog.blp:7
-#: src/apprt/gtk/ui/1.5/surface-title-dialog.blp:9
+#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:9
+#: src/apprt/gtk/ui/1.5/ccw-paste.blp:10 src/apprt/gtk/ui/1.2/ccw-paste.blp:10
+#: src/apprt/gtk/CloseDialog.zig:44
msgid "Cancel"
msgstr "Abbrechen"
-#: src/apprt/gtk/ui/1.2/close-confirmation-dialog.blp:8
-#: src/apprt/gtk/ui/1.2/search-overlay.blp:85
-#: src/apprt/gtk/ui/1.3/surface-child-exited.blp:17
-msgid "Close"
-msgstr "Schlieรen"
+#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:10
+msgid "OK"
+msgstr "OK"
-#: src/apprt/gtk/ui/1.2/config-errors-dialog.blp:6
+#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:5
+#: src/apprt/gtk/ui/1.2/config-errors-dialog.blp:5
msgid "Configuration Errors"
msgstr "Konfigurationsfehler"
-#: src/apprt/gtk/ui/1.2/config-errors-dialog.blp:7
+#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:6
+#: src/apprt/gtk/ui/1.2/config-errors-dialog.blp:6
msgid ""
"One or more configuration errors were found. Please review the errors below, "
"and either reload your configuration or ignore these errors."
msgstr ""
-"Ein oder mehrere Konfigurationsfehler wurden gefunden. Bitte รผberprรผfe die "
-"untenstehenden Fehler und lade entweder deine Konfiguration erneut oder "
+"Ein oder mehrere Konfigurationsfehler wurden gefunden. Bitte รผberprรผfe "
+"die untenstehenden Fehler und lade entweder deine Konfiguration erneut oder "
"ignoriere die Fehler."
-#: src/apprt/gtk/ui/1.2/config-errors-dialog.blp:10
+#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:9
+#: src/apprt/gtk/ui/1.2/config-errors-dialog.blp:9
msgid "Ignore"
msgstr "Ignorieren"
-#: src/apprt/gtk/ui/1.2/config-errors-dialog.blp:11
-#: src/apprt/gtk/ui/1.2/surface.blp:317 src/apprt/gtk/ui/1.5/window.blp:293
+#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:10
+#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:97
+#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:100
+#: src/apprt/gtk/ui/1.2/config-errors-dialog.blp:10
msgid "Reload Configuration"
msgstr "Konfiguration neu laden"
-#: src/apprt/gtk/ui/1.2/debug-warning.blp:7
-#: src/apprt/gtk/ui/1.3/debug-warning.blp:6
-msgid ""
-"โ ๏ธ You're running a debug build of Ghostty! Performance will be degraded."
-msgstr ""
-"โ ๏ธ Du verwendest einen Debug Build von Ghostty! Die Leistung wird reduziert "
-"sein."
-
-#: src/apprt/gtk/ui/1.5/inspector-window.blp:5
-msgid "Ghostty: Terminal Inspector"
-msgstr ""
-
-#: src/apprt/gtk/ui/1.2/search-overlay.blp:29
-msgid "Findโฆ"
-msgstr ""
-
-#: src/apprt/gtk/ui/1.2/search-overlay.blp:64
-msgid "Previous Match"
-msgstr ""
-
-#: src/apprt/gtk/ui/1.2/search-overlay.blp:74
-msgid "Next Match"
-msgstr ""
-
-#: src/apprt/gtk/ui/1.2/surface.blp:6
-msgid "Oh, no."
-msgstr ""
-
-#: src/apprt/gtk/ui/1.2/surface.blp:7
-msgid "Unable to acquire an OpenGL context for rendering."
-msgstr ""
-
-#: src/apprt/gtk/ui/1.2/surface.blp:216 src/apprt/gtk/ui/1.5/window.blp:198
-msgid "Copy"
-msgstr "Kopieren"
-
-#: src/apprt/gtk/ui/1.2/surface.blp:221 src/apprt/gtk/ui/1.5/window.blp:203
-msgid "Paste"
-msgstr "Einfรผgen"
-
-#: src/apprt/gtk/ui/1.2/surface.blp:226
-msgid "Notify on Next Command Finish"
-msgstr ""
-
-#: src/apprt/gtk/ui/1.2/surface.blp:233 src/apprt/gtk/ui/1.5/window.blp:266
-msgid "Clear"
-msgstr "Leeren"
-
-#: src/apprt/gtk/ui/1.2/surface.blp:238 src/apprt/gtk/ui/1.5/window.blp:271
-msgid "Reset"
-msgstr "Zurรผcksetzen"
-
-#: src/apprt/gtk/ui/1.2/surface.blp:245 src/apprt/gtk/ui/1.5/window.blp:235
-msgid "Split"
-msgstr "Fenster teilen"
-
-#: src/apprt/gtk/ui/1.2/surface.blp:248 src/apprt/gtk/ui/1.5/window.blp:238
-msgid "Change Titleโฆ"
-msgstr "Titel bearbeitenโฆ"
-
-#: src/apprt/gtk/ui/1.2/surface.blp:253 src/apprt/gtk/ui/1.5/window.blp:175
-#: src/apprt/gtk/ui/1.5/window.blp:243
+#: src/apprt/gtk/ui/1.0/menu-headerbar-split_menu.blp:6
+#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:38
+#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:50
msgid "Split Up"
msgstr "Fenster nach oben teilen"
-#: src/apprt/gtk/ui/1.2/surface.blp:259 src/apprt/gtk/ui/1.5/window.blp:180
-#: src/apprt/gtk/ui/1.5/window.blp:248
+#: src/apprt/gtk/ui/1.0/menu-headerbar-split_menu.blp:11
+#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:43
+#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:55
msgid "Split Down"
msgstr "Fenster nach unten teilen"
-#: src/apprt/gtk/ui/1.2/surface.blp:265 src/apprt/gtk/ui/1.5/window.blp:185
-#: src/apprt/gtk/ui/1.5/window.blp:253
+#: src/apprt/gtk/ui/1.0/menu-headerbar-split_menu.blp:16
+#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:48
+#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:60
msgid "Split Left"
msgstr "Fenter nach links teilen"
-#: src/apprt/gtk/ui/1.2/surface.blp:271 src/apprt/gtk/ui/1.5/window.blp:190
-#: src/apprt/gtk/ui/1.5/window.blp:258
+#: src/apprt/gtk/ui/1.0/menu-headerbar-split_menu.blp:21
+#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:53
+#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:65
msgid "Split Right"
msgstr "Fenster nach rechts teilen"
-#: src/apprt/gtk/ui/1.2/surface.blp:278
-msgid "Tab"
-msgstr "Tab"
-
-#: src/apprt/gtk/ui/1.2/surface.blp:281 src/apprt/gtk/ui/1.5/window.blp:57
-#: src/apprt/gtk/ui/1.5/window.blp:107 src/apprt/gtk/ui/1.5/window.blp:222
-msgid "New Tab"
-msgstr "Neuer Tab"
-
-#: src/apprt/gtk/ui/1.2/surface.blp:286 src/apprt/gtk/ui/1.5/window.blp:227
-msgid "Close Tab"
-msgstr "Tab schlieรen"
-
-#: src/apprt/gtk/ui/1.2/surface.blp:293
-msgid "Window"
-msgstr "Fenster"
-
-#: src/apprt/gtk/ui/1.2/surface.blp:296 src/apprt/gtk/ui/1.5/window.blp:210
-msgid "New Window"
-msgstr "Neues Fenster"
-
-#: src/apprt/gtk/ui/1.2/surface.blp:301 src/apprt/gtk/ui/1.5/window.blp:215
-msgid "Close Window"
-msgstr "Fenster schlieรen"
-
-#: src/apprt/gtk/ui/1.2/surface.blp:309
-msgid "Config"
-msgstr "Konfiguration"
-
-#: src/apprt/gtk/ui/1.2/surface.blp:312 src/apprt/gtk/ui/1.5/window.blp:288
-msgid "Open Configuration"
-msgstr "Konfiguration รถffnen"
-
-#: src/apprt/gtk/ui/1.5/surface-title-dialog.blp:5
-msgid "Change Terminal Title"
-msgstr "Terminal-Titel bearbeiten"
-
-#: src/apprt/gtk/ui/1.5/surface-title-dialog.blp:6
-msgid "Leave blank to restore the default title."
-msgstr "Leer lassen, um den Standardtitel wiederherzustellen."
-
-#: src/apprt/gtk/ui/1.5/surface-title-dialog.blp:10
-msgid "OK"
-msgstr "OK"
-
-#: src/apprt/gtk/ui/1.5/window.blp:58 src/apprt/gtk/ui/1.5/window.blp:108
-msgid "New Split"
-msgstr "Neues geteiltes Fenster"
-
-#: src/apprt/gtk/ui/1.5/window.blp:68 src/apprt/gtk/ui/1.5/window.blp:126
-msgid "View Open Tabs"
-msgstr "Offene Tabs einblenden"
-
-#: src/apprt/gtk/ui/1.5/window.blp:78 src/apprt/gtk/ui/1.5/window.blp:140
-msgid "Main Menu"
-msgstr "Hauptmenรผ"
-
-#: src/apprt/gtk/ui/1.5/window.blp:278
-msgid "Command Palette"
-msgstr "Befehlspalette"
-
-#: src/apprt/gtk/ui/1.5/window.blp:283
-msgid "Terminal Inspector"
-msgstr "Terminalinspektor"
-
-#: src/apprt/gtk/ui/1.5/window.blp:300 src/apprt/gtk/class/window.zig:1703
-msgid "About Ghostty"
-msgstr "รber Ghostty"
-
-#: src/apprt/gtk/ui/1.5/window.blp:305
-msgid "Quit"
-msgstr "Beenden"
-
-#: src/apprt/gtk/ui/1.5/command-palette.blp:17
+#: src/apprt/gtk/ui/1.5/command-palette.blp:16
msgid "Execute a commandโฆ"
msgstr "Einen Befehl ausfรผhrenโฆ"
-#: dist/linux/ghostty_nautilus.py:67
-msgid "Open in Ghostty"
-msgstr ""
+#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:6
+#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:6
+msgid "Copy"
+msgstr "Kopieren"
-#: src/apprt/gtk/class/clipboard_confirmation_dialog.zig:198
-msgid ""
-"An application is attempting to write to the clipboard. The current "
-"clipboard contents are shown below."
-msgstr ""
-"Eine Anwendung versucht in die Zwischenablage zu schreiben. Der aktuelle "
-"Inhalt der Zwischenablage wird unten angezeigt."
+#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:11
+#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:11
+#: src/apprt/gtk/ui/1.5/ccw-paste.blp:11 src/apprt/gtk/ui/1.2/ccw-paste.blp:11
+msgid "Paste"
+msgstr "Einfรผgen"
-#: src/apprt/gtk/class/clipboard_confirmation_dialog.zig:202
+#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:18
+#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:73
+msgid "Clear"
+msgstr "Leeren"
+
+#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:23
+#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:78
+msgid "Reset"
+msgstr "Zurรผcksetzen"
+
+#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:30
+#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:42
+msgid "Split"
+msgstr "Fenster teilen"
+
+#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:33
+#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:45
+msgid "Change Titleโฆ"
+msgstr "Titel bearbeitenโฆ"
+
+#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:59
+msgid "Tab"
+msgstr "Tab"
+
+#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:62
+#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:30
+#: src/apprt/gtk/Window.zig:265
+msgid "New Tab"
+msgstr "Neuer Tab"
+
+#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:67
+#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:35
+msgid "Close Tab"
+msgstr "Tab schlieรen"
+
+#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:73
+msgid "Window"
+msgstr "Fenster"
+
+#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:76
+#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:18
+msgid "New Window"
+msgstr "Neues Fenster"
+
+#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:81
+#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:23
+msgid "Close Window"
+msgstr "Fenster schlieรen"
+
+#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:89
+msgid "Config"
+msgstr "Konfiguration"
+
+#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:92
+#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:95
+msgid "Open Configuration"
+msgstr "Konfiguration รถffnen"
+
+#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:85
+msgid "Command Palette"
+msgstr "Befehlspalette"
+
+#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:90
+msgid "Terminal Inspector"
+msgstr "Terminalinspektor"
+
+#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:107
+#: src/apprt/gtk/Window.zig:1038
+msgid "About Ghostty"
+msgstr "รber Ghostty"
+
+#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:112
+msgid "Quit"
+msgstr "Beenden"
+
+#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:6
+#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:6
+#: src/apprt/gtk/ui/1.2/ccw-osc-52-read.blp:6
+#: src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp:6
+msgid "Authorize Clipboard Access"
+msgstr "Zugriff auf die Zwischenablage gewรคhren"
+
+#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:7
+#: src/apprt/gtk/ui/1.2/ccw-osc-52-read.blp:7
msgid ""
"An application is attempting to read from the clipboard. The current "
"clipboard contents are shown below."
@@ -254,11 +194,45 @@ msgstr ""
"Eine Anwendung versucht von der Zwischenablage zu lesen. Der aktuelle Inhalt "
"der Zwischenablage wird unten angezeigt."
-#: src/apprt/gtk/class/clipboard_confirmation_dialog.zig:205
+#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:10
+#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:10
+#: src/apprt/gtk/ui/1.2/ccw-osc-52-read.blp:10
+#: src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp:10
+msgid "Deny"
+msgstr "Nicht erlauben"
+
+#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:11
+#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:11
+#: src/apprt/gtk/ui/1.2/ccw-osc-52-read.blp:11
+#: src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp:11
+msgid "Allow"
+msgstr "Erlauben"
+
+#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:81
+#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:77
+msgid "Remember choice for this split"
+msgstr "Auswahl fรผr dieses geteilte Fenster beibehalten"
+
+#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:82
+#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:78
+msgid "Reload configuration to show this prompt again"
+msgstr ""
+"Lade die Konfiguration erneut, um diese Eingabeaufforderung erneut anzuzeigen"
+
+#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:7
+#: src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp:7
+msgid ""
+"An application is attempting to write to the clipboard. The current "
+"clipboard contents are shown below."
+msgstr ""
+"Eine Anwendung versucht in die Zwischenablage zu schreiben. Der aktuelle "
+"Inhalt der Zwischenablage wird unten angezeigt."
+
+#: src/apprt/gtk/ui/1.5/ccw-paste.blp:6 src/apprt/gtk/ui/1.2/ccw-paste.blp:6
msgid "Warning: Potentially Unsafe Paste"
msgstr "Achtung: Mรถglicherweise unsicheres Einfรผgen"
-#: src/apprt/gtk/class/clipboard_confirmation_dialog.zig:206
+#: src/apprt/gtk/ui/1.5/ccw-paste.blp:7 src/apprt/gtk/ui/1.2/ccw-paste.blp:7
msgid ""
"Pasting this text into the terminal may be dangerous as it looks like some "
"commands may be executed."
@@ -266,70 +240,85 @@ msgstr ""
"Diesen Text in das Terminal einzufรผgen kรถnnte mรถglicherweise gefรคhrlich "
"sein. Es scheint, dass Anweisungen ausgefรผhrt werden kรถnnten."
-#: src/apprt/gtk/class/close_confirmation_dialog.zig:184
+#: src/apprt/gtk/CloseDialog.zig:47 src/apprt/gtk/Surface.zig:2531
+msgid "Close"
+msgstr "Schlieรen"
+
+#: src/apprt/gtk/CloseDialog.zig:87
msgid "Quit Ghostty?"
msgstr "Ghostty schlieรen?"
-#: src/apprt/gtk/class/close_confirmation_dialog.zig:185
-msgid "Close Tab?"
-msgstr "Tab schlieรen?"
-
-#: src/apprt/gtk/class/close_confirmation_dialog.zig:186
+#: src/apprt/gtk/CloseDialog.zig:88
msgid "Close Window?"
msgstr "Fenster schlieรen?"
-#: src/apprt/gtk/class/close_confirmation_dialog.zig:187
+#: src/apprt/gtk/CloseDialog.zig:89
+msgid "Close Tab?"
+msgstr "Tab schlieรen?"
+
+#: src/apprt/gtk/CloseDialog.zig:90
msgid "Close Split?"
msgstr "Geteiltes Fenster schlieรen?"
-#: src/apprt/gtk/class/close_confirmation_dialog.zig:193
+#: src/apprt/gtk/CloseDialog.zig:96
msgid "All terminal sessions will be terminated."
msgstr "Alle Terminalsitzungen werden beendet."
-#: src/apprt/gtk/class/close_confirmation_dialog.zig:194
-msgid "All terminal sessions in this tab will be terminated."
-msgstr "Alle Terminalsitzungen in diesem Tab werden beendet."
-
-#: src/apprt/gtk/class/close_confirmation_dialog.zig:195
+#: src/apprt/gtk/CloseDialog.zig:97
msgid "All terminal sessions in this window will be terminated."
msgstr "Alle Terminalsitzungen in diesem Fenster werden beendet."
-#: src/apprt/gtk/class/close_confirmation_dialog.zig:196
+#: src/apprt/gtk/CloseDialog.zig:98
+msgid "All terminal sessions in this tab will be terminated."
+msgstr "Alle Terminalsitzungen in diesem Tab werden beendet."
+
+#: src/apprt/gtk/CloseDialog.zig:99
msgid "The currently running process in this split will be terminated."
msgstr "Der aktuell laufende Prozess in diesem geteilten Fenster wird beendet."
-#: src/apprt/gtk/class/surface.zig:959
-msgid "Command Finished"
-msgstr ""
-
-#: src/apprt/gtk/class/surface.zig:960
-msgid "Command Succeeded"
-msgstr ""
-
-#: src/apprt/gtk/class/surface.zig:961
-msgid "Command Failed"
-msgstr ""
-
-#: src/apprt/gtk/class/surface_child_exited.zig:109
-msgid "Command succeeded"
-msgstr "Befehl erfolgreich"
-
-#: src/apprt/gtk/class/surface_child_exited.zig:113
-msgid "Command failed"
-msgstr "Befehl fehlgeschlagen"
-
-#: src/apprt/gtk/class/window.zig:990
-msgid "Reloaded the configuration"
-msgstr "Konfiguration wurde neu geladen"
-
-#: src/apprt/gtk/class/window.zig:1542
+#: src/apprt/gtk/Surface.zig:1266
msgid "Copied to clipboard"
msgstr "In die Zwischenablage kopiert"
-#: src/apprt/gtk/class/window.zig:1544
+#: src/apprt/gtk/Surface.zig:1268
msgid "Cleared clipboard"
msgstr "Zwischenablage geleert"
-#: src/apprt/gtk/class/window.zig:1684
+#: src/apprt/gtk/Surface.zig:2525
+msgid "Command succeeded"
+msgstr "Befehl erfolgreich"
+
+#: src/apprt/gtk/Surface.zig:2527
+msgid "Command failed"
+msgstr "Befehl fehlgeschlagen"
+
+#: src/apprt/gtk/Window.zig:216
+msgid "Main Menu"
+msgstr "Hauptmenรผ"
+
+#: src/apprt/gtk/Window.zig:239
+msgid "View Open Tabs"
+msgstr "Offene Tabs einblenden"
+
+#: src/apprt/gtk/Window.zig:266
+msgid "New Split"
+msgstr "Neues geteiltes Fenster"
+
+#: src/apprt/gtk/Window.zig:329
+msgid ""
+"โ ๏ธ You're running a debug build of Ghostty! Performance will be degraded."
+msgstr ""
+"โ ๏ธ Du verwendest einen Debug Build von Ghostty! Die Leistung wird reduziert "
+"sein."
+
+#: src/apprt/gtk/Window.zig:775
+msgid "Reloaded the configuration"
+msgstr "Konfiguration wurde neu geladen"
+
+#: src/apprt/gtk/Window.zig:1019
msgid "Ghostty Developers"
msgstr "Ghostty-Entwickler"
+
+#: src/apprt/gtk/inspector.zig:144
+msgid "Ghostty: Terminal Inspector"
+msgstr "Ghostty: Terminalinspektor"
diff --git a/snap/local/launcher b/snap/local/launcher
index 89e0d1709..71b92f5bb 100755
--- a/snap/local/launcher
+++ b/snap/local/launcher
@@ -14,7 +14,14 @@ if [ -z "$XDG_DATA_HOME" ]; then
export XDG_DATA_HOME="$SNAP_REAL_HOME/.local/share"
fi
-source "$SNAP_USER_DATA/.last_revision" 2>/dev/null || true
+if [ -f "$SNAP_USER_DATA/.last_revision" ]; then
+ if ! source "$SNAP_USER_DATA/.last_revision" 2>/dev/null; then
+ # file exist but sourcing it fails, so it's likely
+ # not good anyway
+ rm -f "$SNAP_USER_DATA/.last_revision"
+ fi
+fi
+
if [ "$LAST_REVISION" = "$SNAP_REVISION" ]; then
needs_update=false
else
diff --git a/src/App.zig b/src/App.zig
index 99d03399c..00be56f49 100644
--- a/src/App.zig
+++ b/src/App.zig
@@ -357,15 +357,17 @@ pub fn keyEvent(
// Get the keybind entry for this event. We don't support key sequences
// so we can look directly in the top-level set.
const entry = rt_app.config.keybind.set.getEvent(event) orelse return false;
- const leaf: input.Binding.Set.Leaf = switch (entry.value_ptr.*) {
+ const leaf: input.Binding.Set.GenericLeaf = switch (entry.value_ptr.*) {
// Sequences aren't supported. Our configuration parser verifies
// this for global keybinds but we may still get an entry for
// a non-global keybind.
.leader => return false,
// Leaf entries are good
- .leaf => |leaf| leaf,
+ inline .leaf, .leaf_chained => |leaf| leaf.generic(),
};
+ const actions: []const input.Binding.Action = leaf.actionsSlice();
+ assert(actions.len > 0);
// If we aren't focused, then we only process global keybinds.
if (!self.focused and !leaf.flags.global) return false;
@@ -373,13 +375,7 @@ pub fn keyEvent(
// Global keybinds are done using performAll so that they
// can target all surfaces too.
if (leaf.flags.global) {
- self.performAllAction(rt_app, leaf.action) catch |err| {
- log.warn("error performing global keybind action action={s} err={}", .{
- @tagName(leaf.action),
- err,
- });
- };
-
+ self.performAllChainedAction(rt_app, actions);
return true;
}
@@ -389,14 +385,20 @@ pub fn keyEvent(
// If we are focused, then we process keybinds only if they are
// app-scoped. Otherwise, we do nothing. Surface-scoped should
- // be processed by Surface.keyEvent.
- const app_action = leaf.action.scoped(.app) orelse return false;
- self.performAction(rt_app, app_action) catch |err| {
- log.warn("error performing app keybind action action={s} err={}", .{
- @tagName(app_action),
- err,
- });
- };
+ // be processed by Surface.keyEvent. For chained actions, all
+ // actions must be app-scoped.
+ for (actions) |action| if (action.scoped(.app) == null) return false;
+ for (actions) |action| {
+ self.performAction(
+ rt_app,
+ action.scoped(.app).?,
+ ) catch |err| {
+ log.warn("error performing app keybind action action={s} err={}", .{
+ @tagName(action),
+ err,
+ });
+ };
+ }
return true;
}
@@ -454,6 +456,23 @@ pub fn performAction(
}
}
+/// Performs a chained action. We will continue executing each action
+/// even if there is a failure in a prior action.
+pub fn performAllChainedAction(
+ self: *App,
+ rt_app: *apprt.App,
+ actions: []const input.Binding.Action,
+) void {
+ for (actions) |action| {
+ self.performAllAction(rt_app, action) catch |err| {
+ log.warn("error performing chained action action={s} err={}", .{
+ @tagName(action),
+ err,
+ });
+ };
+ }
+}
+
/// Perform an app-wide binding action. If the action is surface-specific
/// then it will be performed on all surfaces. To perform only app-scoped
/// actions, use performAction.
diff --git a/src/Surface.zig b/src/Surface.zig
index fc5a239ab..0bf3aa008 100644
--- a/src/Surface.zig
+++ b/src/Surface.zig
@@ -49,6 +49,10 @@ const Renderer = rendererpkg.Renderer;
const min_window_width_cells: u32 = 10;
const min_window_height_cells: u32 = 4;
+/// The maximum number of key tables that can be active at any
+/// given time. `activate_key_table` calls after this are ignored.
+const max_active_key_tables = 8;
+
/// Allocator
alloc: Allocator,
@@ -253,18 +257,9 @@ const Mouse = struct {
/// Keyboard state for the surface.
pub const Keyboard = struct {
- /// The currently active keybindings for the surface. This is used to
- /// implement sequences: as leader keys are pressed, the active bindings
- /// set is updated to reflect the current leader key sequence. If this is
- /// null then the root bindings are used.
- bindings: ?*const input.Binding.Set = null,
-
- /// The last handled binding. This is used to prevent encoding release
- /// events for handled bindings. We only need to keep track of one because
- /// at least at the time of writing this, its impossible for two keys of
- /// a combination to be handled by different bindings before the release
- /// of the prior (namely since you can't bind modifier-only).
- last_trigger: ?u64 = null,
+ /// The currently active key sequence for the surface. If this is null
+ /// then we're not currently in a key sequence.
+ sequence_set: ?*const input.Binding.Set = null,
/// The queued keys when we're in the middle of a sequenced binding.
/// These are flushed when the sequence is completed and unconsumed or
@@ -272,7 +267,23 @@ pub const Keyboard = struct {
///
/// This is naturally bounded due to the configuration maximum
/// length of a sequence.
- queued: std.ArrayListUnmanaged(termio.Message.WriteReq) = .{},
+ sequence_queued: std.ArrayListUnmanaged(termio.Message.WriteReq) = .empty,
+
+ /// The stack of tables that is currently active. The first value
+ /// in this is the first activated table (NOT the default keybinding set).
+ ///
+ /// This is bounded by `max_active_key_tables`.
+ table_stack: std.ArrayListUnmanaged(struct {
+ set: *const input.Binding.Set,
+ once: bool,
+ }) = .empty,
+
+ /// The last handled binding. This is used to prevent encoding release
+ /// events for handled bindings. We only need to keep track of one because
+ /// at least at the time of writing this, its impossible for two keys of
+ /// a combination to be handled by different bindings before the release
+ /// of the prior (namely since you can't bind modifier-only).
+ last_trigger: ?u64 = null,
};
/// The configuration that a surface has, this is copied from the main
@@ -305,6 +316,7 @@ const DerivedConfig = struct {
macos_option_as_alt: ?input.OptionAsAlt,
selection_clear_on_copy: bool,
selection_clear_on_typing: bool,
+ selection_word_chars: []const u21,
vt_kam_allowed: bool,
wait_after_command: bool,
window_padding_top: u32,
@@ -316,12 +328,13 @@ const DerivedConfig = struct {
window_width: u32,
title: ?[:0]const u8,
title_report: bool,
- links: []Link,
+ links: []DerivedConfig.Link,
link_previews: configpkg.LinkPreviews,
scroll_to_bottom: configpkg.Config.ScrollToBottom,
notify_on_command_finish: configpkg.Config.NotifyOnCommandFinish,
notify_on_command_finish_action: configpkg.Config.NotifyOnCommandFinishAction,
notify_on_command_finish_after: Duration,
+ key_remaps: input.KeyRemapSet,
const Link = struct {
regex: oni.Regex,
@@ -336,7 +349,7 @@ const DerivedConfig = struct {
// Build all of our links
const links = links: {
- var links: std.ArrayList(Link) = .empty;
+ var links: std.ArrayList(DerivedConfig.Link) = .empty;
defer links.deinit(alloc);
for (config.link.links.items) |link| {
var regex = try link.oniRegex();
@@ -380,6 +393,7 @@ const DerivedConfig = struct {
.macos_option_as_alt = config.@"macos-option-as-alt",
.selection_clear_on_copy = config.@"selection-clear-on-copy",
.selection_clear_on_typing = config.@"selection-clear-on-typing",
+ .selection_word_chars = try alloc.dupe(u21, config.@"selection-word-chars".codepoints),
.vt_kam_allowed = config.@"vt-kam-allowed",
.wait_after_command = config.@"wait-after-command",
.window_padding_top = config.@"window-padding-y".top_left,
@@ -397,6 +411,7 @@ const DerivedConfig = struct {
.notify_on_command_finish = config.@"notify-on-command-finish",
.notify_on_command_finish_action = config.@"notify-on-command-finish-action",
.notify_on_command_finish_after = config.@"notify-on-command-finish-after",
+ .key_remaps = try config.@"key-remap".clone(alloc),
// Assignments happen sequentially so we have to do this last
// so that the memory is captured from allocs above.
@@ -793,8 +808,9 @@ pub fn deinit(self: *Surface) void {
}
// Clean up our keyboard state
- for (self.keyboard.queued.items) |req| req.deinit();
- self.keyboard.queued.deinit(self.alloc);
+ for (self.keyboard.sequence_queued.items) |req| req.deinit();
+ self.keyboard.sequence_queued.deinit(self.alloc);
+ self.keyboard.table_stack.deinit(self.alloc);
// Clean up our font grid
self.app.font_grid_set.deref(self.font_grid_key);
@@ -1014,7 +1030,7 @@ pub fn handleMessage(self: *Surface, msg: Message) !void {
return;
}
- try self.startClipboardRequest(.standard, .{ .osc_52_read = clipboard });
+ _ = try self.startClipboardRequest(.standard, .{ .osc_52_read = clipboard });
},
.clipboard_write => |w| switch (w.req) {
@@ -1059,8 +1075,6 @@ pub fn handleMessage(self: *Surface, msg: Message) !void {
.scrollbar => |scrollbar| self.updateScrollbar(scrollbar),
- .report_color_scheme => |force| self.reportColorScheme(force),
-
.present_surface => try self.presentSurface(),
.password_input => |v| try self.passwordInput(v),
@@ -1210,7 +1224,7 @@ fn childExited(self: *Surface, info: apprt.surface.Message.ChildExited) void {
break :gui false;
}) return;
- // If a native GUI notification was not showm. update our terminal to
+ // If a native GUI notification was not shown, update our terminal to
// note the abnormal exit.
self.childExitedAbnormally(info) catch |err| {
log.err("error handling abnormal child exit err={}", .{err});
@@ -1220,7 +1234,7 @@ fn childExited(self: *Surface, info: apprt.surface.Message.ChildExited) void {
return;
}
- // We output a message so that the user knows whats going on and
+ // We output a message so that the user knows what's going on and
// doesn't think their terminal just froze. We show this unconditionally
// on close even if `wait_after_command` is false and the surface closes
// immediately because if a user does an `undo` to restore a closed
@@ -1372,26 +1386,6 @@ fn passwordInput(self: *Surface, v: bool) !void {
try self.queueRender();
}
-/// Sends a DSR response for the current color scheme to the pty. If
-/// force is false then we only send the response if the terminal mode
-/// 2031 is enabled.
-fn reportColorScheme(self: *Surface, force: bool) void {
- if (!force) {
- self.renderer_state.mutex.lock();
- defer self.renderer_state.mutex.unlock();
- if (!self.renderer_state.terminal.modes.get(.report_color_scheme)) {
- return;
- }
- }
-
- const output = switch (self.config_conditional_state.theme) {
- .light => "\x1B[?997;2n",
- .dark => "\x1B[?997;1n",
- };
-
- self.queueIo(.{ .write_stable = output }, .unlocked);
-}
-
fn searchCallback(event: terminal.search.Thread.Event, ud: ?*anyopaque) void {
// IMPORTANT: This function is run on the SEARCH THREAD! It is NOT SAFE
// to access anything other than values that never change on the surface.
@@ -1587,10 +1581,10 @@ fn mouseRefreshLinks(
}
const link = (try self.linkAtPos(pos)) orelse break :link .{ null, false };
- switch (link[0]) {
+ switch (link.action) {
.open => {
const str = try self.io.terminal.screens.active.selectionString(alloc, .{
- .sel = link[1],
+ .sel = link.selection,
.trim = false,
});
break :link .{
@@ -1601,7 +1595,7 @@ fn mouseRefreshLinks(
._open_osc8 => {
// Show the URL in the status bar
- const pin = link[1].start();
+ const pin = link.selection.start();
const uri = self.osc8URI(pin) orelse {
log.warn("failed to get URI for OSC8 hyperlink", .{});
break :link .{ null, false };
@@ -1731,6 +1725,14 @@ pub fn updateConfig(
// If we are in the middle of a key sequence, clear it.
self.endKeySequence(.drop, .free);
+ // Deactivate all key tables since they may have changed. Importantly,
+ // we store pointers into the config as part of our table stack so
+ // we can't keep them active across config changes. But this behavior
+ // also matches key sequences.
+ _ = self.deactivateAllKeyTables() catch |err| {
+ log.warn("failed to deactivate key tables err={}", .{err});
+ };
+
// Before sending any other config changes, we give the renderer a new font
// grid. We could check to see if there was an actual change to the font,
// but this is easier and pretty rare so it's not a performance concern.
@@ -2556,30 +2558,60 @@ pub fn preeditCallback(self: *Surface, preedit_: ?[]const u8) !void {
/// then Ghosty will act as though the binding does not exist.
pub fn keyEventIsBinding(
self: *Surface,
- event: input.KeyEvent,
-) bool {
+ event_orig: input.KeyEvent,
+) ?input.Binding.Flags {
+ // Apply key remappings for consistency with keyCallback
+ var event = event_orig;
+ if (self.config.key_remaps.isRemapped(event_orig.mods)) {
+ event.mods = self.config.key_remaps.apply(event_orig.mods);
+ }
+
switch (event.action) {
- .release => return false,
+ .release => return null,
.press, .repeat => {},
}
- // Our keybinding set is either our current nested set (for
- // sequences) or the root set.
- const set = self.keyboard.bindings orelse &self.config.keybind.set;
+ // Look up our entry
+ const entry: input.Binding.Set.Entry = entry: {
+ // If we're in a sequence, check the sequence set
+ if (self.keyboard.sequence_set) |set| {
+ break :entry set.getEvent(event) orelse return null;
+ }
- // log.warn("text keyEventIsBinding event={} match={}", .{ event, set.getEvent(event) != null });
+ // Check active key tables (inner-most to outer-most)
+ const table_items = self.keyboard.table_stack.items;
+ for (0..table_items.len) |i| {
+ const rev_i: usize = table_items.len - 1 - i;
+ if (table_items[rev_i].set.getEvent(event)) |entry| {
+ break :entry entry;
+ }
+ }
- // If we have a keybinding for this event then we return true.
- return set.getEvent(event) != null;
+ // Check the root set
+ break :entry self.config.keybind.set.getEvent(event) orelse return null;
+ };
+
+ // Return flags based on the
+ return switch (entry.value_ptr.*) {
+ .leader => .{},
+ inline .leaf, .leaf_chained => |v| v.flags,
+ };
}
/// Called for any key events. This handles keybindings, encoding and
/// sending to the terminal, etc.
pub fn keyCallback(
self: *Surface,
- event: input.KeyEvent,
+ event_orig: input.KeyEvent,
) !InputEffect {
- // log.warn("text keyCallback event={}", .{event});
+ // log.warn("text keyCallback event={}", .{event_orig});
+
+ // Apply key remappings to transform modifiers before any processing.
+ // This allows users to remap modifier keys at the app level.
+ var event = event_orig;
+ if (self.config.key_remaps.isRemapped(event_orig.mods)) {
+ event.mods = self.config.key_remaps.apply(event_orig.mods);
+ }
// Crash metadata in case we crash in here
crash.sentry.thread_state = self.crashThreadState();
@@ -2617,7 +2649,6 @@ pub fn keyCallback(
event,
if (insp_ev) |*ev| ev else null,
)) |v| return v;
-
// If we allow KAM and KAM is enabled then we do nothing.
if (self.config.vt_kam_allowed) {
self.renderer_state.mutex.lock();
@@ -2791,38 +2822,70 @@ fn maybeHandleBinding(
// Find an entry in the keybind set that matches our event.
const entry: input.Binding.Set.Entry = entry: {
- const set = self.keyboard.bindings orelse &self.config.keybind.set;
+ // Handle key sequences first.
+ if (self.keyboard.sequence_set) |set| {
+ // Get our entry from the set for the given event.
+ if (set.getEvent(event)) |v| break :entry v;
- // Get our entry from the set for the given event.
- if (set.getEvent(event)) |v| break :entry v;
+ // No entry found. We need to encode everything up to this
+ // point and send to the pty since we're in a sequence.
+
+ // We ignore modifiers so that nested sequences such as
+ // ctrl+a>ctrl+b>c work.
+ if (event.key.modifier()) return null;
+
+ // If we have a catch-all of ignore, then we special case our
+ // invalid sequence handling to ignore it.
+ if (self.catchAllIsIgnore()) {
+ self.endKeySequence(.drop, .retain);
+ return .ignored;
+ }
- // No entry found. If we're not looking at the root set of the
- // bindings we need to encode everything up to this point and
- // send to the pty.
- //
- // We also ignore modifiers so that nested sequences such as
- // ctrl+a>ctrl+b>c work.
- if (self.keyboard.bindings != null and
- !event.key.modifier())
- {
// Encode everything up to this point
self.endKeySequence(.flush, .retain);
+
+ return null;
}
- return null;
+ // No currently active sequence, move on to tables. For tables,
+ // we search inner-most table to outer-most. The table stack does
+ // NOT include the root set.
+ const table_items = self.keyboard.table_stack.items;
+ if (table_items.len > 0) {
+ for (0..table_items.len) |i| {
+ const rev_i: usize = table_items.len - 1 - i;
+ const table = table_items[rev_i];
+ if (table.set.getEvent(event)) |v| {
+ // If this is a one-shot activation AND its the currently
+ // active table, then we deactivate it after this.
+ // Note: we may want to change the semantics here to
+ // remove this table no matter where it is in the stack,
+ // maybe.
+ if (table.once and i == 0) _ = try self.performBindingAction(
+ .deactivate_key_table,
+ );
+
+ break :entry v;
+ }
+ }
+ }
+
+ // No table, use our default set
+ break :entry self.config.keybind.set.getEvent(event) orelse
+ return null;
};
// Determine if this entry has an action or if its a leader key.
- const leaf: input.Binding.Set.Leaf = switch (entry.value_ptr.*) {
+ const leaf: input.Binding.Set.GenericLeaf = switch (entry.value_ptr.*) {
.leader => |set| {
// Setup the next set we'll look at.
- self.keyboard.bindings = set;
+ self.keyboard.sequence_set = set;
// Store this event so that we can drain and encode on invalid.
// We don't need to cap this because it is naturally capped by
// the config validation.
if (try self.encodeKey(event, insp_ev)) |req| {
- try self.keyboard.queued.append(self.alloc, req);
+ try self.keyboard.sequence_queued.append(self.alloc, req);
}
// Start or continue our key sequence
@@ -2840,9 +2903,8 @@ fn maybeHandleBinding(
return .consumed;
},
- .leaf => |leaf| leaf,
+ inline .leaf, .leaf_chained => |leaf| leaf.generic(),
};
- const action = leaf.action;
// consumed determines if the input is consumed or if we continue
// encoding the key (if we have a key to encode).
@@ -2861,39 +2923,61 @@ fn maybeHandleBinding(
// perform an action (below)
self.keyboard.last_trigger = null;
- // An action also always resets the binding set.
- self.keyboard.bindings = null;
+ // An action also always resets the sequence set.
+ self.keyboard.sequence_set = null;
+
+ // Setup our actions
+ const actions = leaf.actionsSlice();
// Attempt to perform the action
- log.debug("key event binding flags={} action={f}", .{
+ log.debug("key event binding flags={} action={any}", .{
leaf.flags,
- action,
+ actions,
});
const performed = performed: {
// If this is a global or all action, then we perform it on
// the app and it applies to every surface.
if (leaf.flags.global or leaf.flags.all) {
- try self.app.performAllAction(self.rt_app, action);
+ self.app.performAllChainedAction(
+ self.rt_app,
+ actions,
+ );
// "All" actions are always performed since they are global.
break :performed true;
}
- break :performed try self.performBindingAction(action);
+ // Perform each action. We are performed if ANY of the chained
+ // actions perform.
+ var performed: bool = false;
+ for (actions) |action| {
+ if (self.performBindingAction(action)) |v| {
+ performed = performed or v;
+ } else |err| {
+ log.info(
+ "key binding action failed action={t} err={}",
+ .{ action, err },
+ );
+ }
+ }
+
+ break :performed performed;
};
if (performed) {
// If we performed an action and it was a closing action,
// our "self" pointer is not safe to use anymore so we need to
// just exit immediately.
- if (closingAction(action)) {
+ for (actions) |action| if (closingAction(action)) {
log.debug("key binding is a closing binding, halting key event processing", .{});
return .closed;
- }
+ };
// If our action was "ignore" then we return the special input
// effect of "ignored".
- if (action == .ignore) return .ignored;
+ for (actions) |action| if (action == .ignore) {
+ return .ignored;
+ };
}
// If we have the performable flag and the action was not performed,
@@ -2917,7 +3001,18 @@ fn maybeHandleBinding(
// Store our last trigger so we don't encode the release event
self.keyboard.last_trigger = event.bindingHash();
- if (insp_ev) |ev| ev.binding = action;
+ if (insp_ev) |ev| {
+ ev.binding = self.alloc.dupe(
+ input.Binding.Action,
+ actions,
+ ) catch |err| binding: {
+ log.warn(
+ "error allocating binding action for inspector err={}",
+ .{err},
+ );
+ break :binding &.{};
+ };
+ }
return .consumed;
}
@@ -2928,6 +3023,58 @@ fn maybeHandleBinding(
return null;
}
+fn deactivateAllKeyTables(self: *Surface) !bool {
+ switch (self.keyboard.table_stack.items.len) {
+ // No key table active. This does nothing.
+ 0 => return false,
+
+ // Clear the entire table stack.
+ else => self.keyboard.table_stack.clearAndFree(self.alloc),
+ }
+
+ // Notify the UI.
+ _ = self.rt_app.performAction(
+ .{ .surface = self },
+ .key_table,
+ .deactivate_all,
+ ) catch |err| {
+ log.warn(
+ "failed to notify app of key table err={}",
+ .{err},
+ );
+ };
+
+ return true;
+}
+
+/// This checks if the current keybinding sets have a catch_all binding
+/// with `ignore`. This is used to determine some special input cases.
+fn catchAllIsIgnore(self: *Surface) bool {
+ // Get our catch all
+ const entry: input.Binding.Set.Entry = entry: {
+ const trigger: input.Binding.Trigger = .{ .key = .catch_all };
+
+ const table_items = self.keyboard.table_stack.items;
+ for (0..table_items.len) |i| {
+ const rev_i: usize = table_items.len - 1 - i;
+ const entry = table_items[rev_i].set.get(trigger) orelse continue;
+ break :entry entry;
+ }
+
+ break :entry self.config.keybind.set.get(trigger) orelse
+ return false;
+ };
+
+ // We have a catch-all entry, see if its an ignore
+ return switch (entry.value_ptr.*) {
+ .leader => false,
+ .leaf => |leaf| leaf.action == .ignore,
+ .leaf_chained => |leaf| chained: for (leaf.actions.items) |action| {
+ if (action == .ignore) break :chained true;
+ } else false,
+ };
+}
+
const KeySequenceQueued = enum { flush, drop };
const KeySequenceMemory = enum { retain, free };
@@ -2952,27 +3099,30 @@ fn endKeySequence(
);
};
- // No matter what we clear our current binding set. This restores
+ // No matter what we clear our current sequence set. This restores
// the set we look at to the root set.
- self.keyboard.bindings = null;
+ self.keyboard.sequence_set = null;
- if (self.keyboard.queued.items.len > 0) {
- switch (action) {
- .flush => for (self.keyboard.queued.items) |write_req| {
- self.queueIo(switch (write_req) {
- .small => |v| .{ .write_small = v },
- .stable => |v| .{ .write_stable = v },
- .alloc => |v| .{ .write_alloc = v },
- }, .unlocked);
- },
+ // If we have no queued data, there is nothing else to do.
+ if (self.keyboard.sequence_queued.items.len == 0) return;
- .drop => for (self.keyboard.queued.items) |req| req.deinit(),
- }
+ // Run the proper action first
+ switch (action) {
+ .flush => for (self.keyboard.sequence_queued.items) |write_req| {
+ self.queueIo(switch (write_req) {
+ .small => |v| .{ .write_small = v },
+ .stable => |v| .{ .write_stable = v },
+ .alloc => |v| .{ .write_alloc = v },
+ }, .unlocked);
+ },
- switch (mem) {
- .free => self.keyboard.queued.clearAndFree(self.alloc),
- .retain => self.keyboard.queued.clearRetainingCapacity(),
- }
+ .drop => for (self.keyboard.sequence_queued.items) |req| req.deinit(),
+ }
+
+ // Memory handling of the sequence after the action
+ switch (mem) {
+ .free => self.keyboard.sequence_queued.clearAndFree(self.alloc),
+ .retain => self.keyboard.sequence_queued.clearRetainingCapacity(),
}
}
@@ -3994,9 +4144,24 @@ pub fn mouseButtonCallback(
}
},
- // Double click, select the word under our mouse
+ // Double click, select the word under our mouse.
+ // First try to detect if we're clicking on a URL to select the entire URL.
2 => {
- const sel_ = self.io.terminal.screens.active.selectWord(pin.*);
+ const sel_ = sel: {
+ // Try link detection without requiring modifier keys
+ if (self.linkAtPin(
+ pin.*,
+ null,
+ )) |result_| {
+ if (result_) |result| {
+ break :sel result.selection;
+ }
+ } else |_| {
+ // Ignore any errors, likely regex errors.
+ }
+
+ break :sel self.io.terminal.screens.active.selectWord(pin.*, self.config.selection_word_chars);
+ };
if (sel_) |sel| {
try self.io.terminal.screens.active.select(sel);
try self.queueRender();
@@ -4026,7 +4191,7 @@ pub fn mouseButtonCallback(
.selection
else
.standard;
- try self.startClipboardRequest(clipboard, .{ .paste = {} });
+ _ = try self.startClipboardRequest(clipboard, .{ .paste = {} });
}
// Right-click down selects word for context menus. If the apprt
@@ -4040,8 +4205,8 @@ pub fn mouseButtonCallback(
// Get our viewport pin
const screen: *terminal.Screen = self.renderer_state.terminal.screens.active;
+ const pos = try self.rt_surface.getCursorPos();
const pin = pin: {
- const pos = try self.rt_surface.getCursorPos();
const pt_viewport = self.posToViewport(pos.x, pos.y);
const pin = screen.pages.pin(.{
.viewport = .{
@@ -4072,8 +4237,17 @@ pub fn mouseButtonCallback(
// word selection where we clicked.
}
- const sel = screen.selectWord(pin) orelse break :sel;
- try self.setSelection(sel);
+ // If there is a link at this position, we want to
+ // select the link. Otherwise, select the word.
+ if (try self.linkAtPos(pos)) |link| {
+ try self.setSelection(link.selection);
+ } else {
+ const sel = screen.selectWord(
+ pin,
+ self.config.selection_word_chars,
+ ) orelse break :sel;
+ try self.setSelection(sel);
+ }
try self.queueRender();
// Don't consume so that we show the context menu in apprt.
@@ -4104,7 +4278,7 @@ pub fn mouseButtonCallback(
// request so we need to unlock.
self.renderer_state.mutex.unlock();
defer self.renderer_state.mutex.lock();
- try self.startClipboardRequest(.standard, .paste);
+ _ = try self.startClipboardRequest(.standard, .paste);
// We don't need to clear selection because we didn't have
// one to begin with.
@@ -4119,7 +4293,7 @@ pub fn mouseButtonCallback(
// request so we need to unlock.
self.renderer_state.mutex.unlock();
defer self.renderer_state.mutex.lock();
- try self.startClipboardRequest(.standard, .paste);
+ _ = try self.startClipboardRequest(.standard, .paste);
},
}
@@ -4184,16 +4358,18 @@ fn clickMoveCursor(self: *Surface, to: terminal.Pin) !void {
}
}
+const Link = struct {
+ action: input.Link.Action,
+ selection: terminal.Selection,
+};
+
/// Returns the link at the given cursor position, if any.
///
/// Requires the renderer mutex is held.
fn linkAtPos(
self: *Surface,
pos: apprt.CursorPos,
-) !?struct {
- input.Link.Action,
- terminal.Selection,
-} {
+) !?Link {
// Convert our cursor position to a screen point.
const screen: *terminal.Screen = self.renderer_state.terminal.screens.active;
const mouse_pin: terminal.Pin = mouse_pin: {
@@ -4214,14 +4390,27 @@ fn linkAtPos(
const cell = rac.cell;
if (!cell.hyperlink) break :hyperlink;
const sel = terminal.Selection.init(mouse_pin, mouse_pin, false);
- return .{ ._open_osc8, sel };
+ return .{ .action = ._open_osc8, .selection = sel };
}
- // If we have no OSC8 links then we fallback to regex-based URL detection.
- // If we have no configured links we can save a lot of work going forward.
+ // Fall back to configured links
+ return try self.linkAtPin(mouse_pin, mouse_mods);
+}
+
+/// Detects if a link is present at the given pin.
+///
+/// If mouse mods is null then mouse mod requirements are ignored (all
+/// configured links are checked).
+///
+/// Requires the renderer state mutex is held.
+fn linkAtPin(
+ self: *Surface,
+ mouse_pin: terminal.Pin,
+ mouse_mods: ?input.Mods,
+) !?Link {
if (self.config.links.len == 0) return null;
- // Get the line we're hovering over.
+ const screen: *terminal.Screen = self.renderer_state.terminal.screens.active;
const line = screen.selectLine(.{
.pin = mouse_pin,
.whitespace = null,
@@ -4236,12 +4425,12 @@ fn linkAtPos(
}));
defer strmap.deinit(self.alloc);
- // Go through each link and see if we clicked it
for (self.config.links) |link| {
- switch (link.highlight) {
+ // Skip highlight/mods check when mouse_mods is null (double-click mode)
+ if (mouse_mods) |mods| switch (link.highlight) {
.always, .hover => {},
- .always_mods, .hover_mods => |v| if (!v.equal(mouse_mods)) continue,
- }
+ .always_mods, .hover_mods => |v| if (!v.equal(mods)) continue,
+ };
var it = strmap.searchIterator(link.regex);
while (true) {
@@ -4249,7 +4438,10 @@ fn linkAtPos(
defer match.deinit();
const sel = match.selection();
if (!sel.contains(screen, mouse_pin)) continue;
- return .{ link.action, sel };
+ return .{
+ .action = link.action,
+ .selection = sel,
+ };
}
}
@@ -4280,11 +4472,11 @@ fn mouseModsWithCapture(self: *Surface, mods: input.Mods) input.Mods {
///
/// Requires the renderer state mutex is held.
fn processLinks(self: *Surface, pos: apprt.CursorPos) !bool {
- const action, const sel = try self.linkAtPos(pos) orelse return false;
- switch (action) {
+ const link = try self.linkAtPos(pos) orelse return false;
+ switch (link.action) {
.open => {
const str = try self.io.terminal.screens.active.selectionString(self.alloc, .{
- .sel = sel,
+ .sel = link.selection,
.trim = false,
});
defer self.alloc.free(str);
@@ -4297,7 +4489,7 @@ fn processLinks(self: *Surface, pos: apprt.CursorPos) !bool {
},
._open_osc8 => {
- const uri = self.osc8URI(sel.start()) orelse {
+ const uri = self.osc8URI(link.selection.start()) orelse {
log.warn("failed to get URI for OSC8 hyperlink", .{});
return false;
};
@@ -4374,7 +4566,10 @@ pub fn mousePressureCallback(
// This should always be set in this state but we don't want
// to handle state inconsistency here.
const pin = self.mouse.left_click_pin orelse break :select;
- const sel = self.io.terminal.screens.active.selectWord(pin.*) orelse break :select;
+ const sel = self.io.terminal.screens.active.selectWord(
+ pin.*,
+ self.config.selection_word_chars,
+ ) orelse break :select;
try self.io.terminal.screens.active.select(sel);
try self.queueRender();
}
@@ -4597,7 +4792,11 @@ fn dragLeftClickDouble(
const click_pin = self.mouse.left_click_pin.?.*;
// Get the word closest to our starting click.
- const word_start = screen.selectWordBetween(click_pin, drag_pin) orelse {
+ const word_start = screen.selectWordBetween(
+ click_pin,
+ drag_pin,
+ self.config.selection_word_chars,
+ ) orelse {
try self.setSelection(null);
return;
};
@@ -4606,6 +4805,7 @@ fn dragLeftClickDouble(
const word_current = screen.selectWordBetween(
drag_pin,
click_pin,
+ self.config.selection_word_chars,
) orelse {
try self.setSelection(null);
return;
@@ -4836,7 +5036,7 @@ pub fn colorSchemeCallback(self: *Surface, scheme: apprt.ColorScheme) !void {
self.notifyConfigConditionalState();
// If mode 2031 is on, then we report the change live.
- self.reportColorScheme(false);
+ self.queueIo(.{ .color_scheme_report = .{ .force = false } }, .unlocked);
}
pub fn posToViewport(self: Surface, xpos: f64, ypos: f64) terminal.point.Coordinate {
@@ -5016,6 +5216,16 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
);
},
+ .search_selection => {
+ const selection = try self.selectionString(self.alloc) orelse return false;
+ defer self.alloc.free(selection);
+ return try self.rt_app.performAction(
+ .{ .surface = self },
+ .start_search,
+ .{ .needle = selection },
+ );
+ },
+
.end_search => {
// We only return that this was performed if we actually
// stopped a search, but we also send the apprt end_search so
@@ -5131,11 +5341,11 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
self.renderer_state.mutex.lock();
defer self.renderer_state.mutex.unlock();
if (try self.linkAtPos(pos)) |link_info| {
- const url_text = switch (link_info[0]) {
+ const url_text = switch (link_info.action) {
.open => url_text: {
// For regex links, get the text from selection
break :url_text (self.io.terminal.screens.active.selectionString(self.alloc, .{
- .sel = link_info[1],
+ .sel = link_info.selection,
.trim = self.config.clipboard_trim_trailing_spaces,
})) catch |err| {
log.err("error reading url string err={}", .{err});
@@ -5145,7 +5355,7 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
._open_osc8 => url_text: {
// For OSC8 links, get the URI directly from hyperlink data
- const uri = self.osc8URI(link_info[1].start()) orelse {
+ const uri = self.osc8URI(link_info.selection.start()) orelse {
log.warn("failed to get URI for OSC8 hyperlink", .{});
return false;
};
@@ -5183,12 +5393,12 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
return true;
},
- .paste_from_clipboard => try self.startClipboardRequest(
+ .paste_from_clipboard => return try self.startClipboardRequest(
.standard,
.{ .paste = {} },
),
- .paste_from_selection => try self.startClipboardRequest(
+ .paste_from_selection => return try self.startClipboardRequest(
.selection,
.{ .paste = {} },
),
@@ -5566,6 +5776,95 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
{},
),
+ inline .activate_key_table,
+ .activate_key_table_once,
+ => |name, tag| {
+ // Look up the table in our config
+ const set = self.config.keybind.tables.getPtr(name) orelse {
+ log.debug("key table not found: {s}", .{name});
+ return false;
+ };
+
+ // If this is the same table as is currently active, then
+ // do nothing.
+ if (self.keyboard.table_stack.items.len > 0) {
+ const items = self.keyboard.table_stack.items;
+ const active = items[items.len - 1].set;
+ if (active == set) {
+ log.debug("ignoring duplicate activate table: {s}", .{name});
+ return false;
+ }
+ }
+
+ // If we're already at the max, ignore it.
+ if (self.keyboard.table_stack.items.len >= max_active_key_tables) {
+ log.info(
+ "ignoring activate table, max depth reached: {s}",
+ .{name},
+ );
+ return false;
+ }
+
+ // Add the table to the stack.
+ try self.keyboard.table_stack.append(self.alloc, .{
+ .set = set,
+ .once = tag == .activate_key_table_once,
+ });
+
+ // Notify the UI.
+ _ = self.rt_app.performAction(
+ .{ .surface = self },
+ .key_table,
+ .{ .activate = name },
+ ) catch |err| {
+ log.warn(
+ "failed to notify app of key table err={}",
+ .{err},
+ );
+ };
+
+ log.debug("key table activated: {s}", .{name});
+ },
+
+ .deactivate_key_table => {
+ switch (self.keyboard.table_stack.items.len) {
+ // No key table active. This does nothing.
+ 0 => return false,
+
+ // Final key table active, clear our state.
+ 1 => self.keyboard.table_stack.clearAndFree(self.alloc),
+
+ // Restore the prior key table. We don't free any memory in
+ // this case because we assume it will be freed later when
+ // we finish our key table.
+ else => _ = self.keyboard.table_stack.pop(),
+ }
+
+ // Notify the UI.
+ _ = self.rt_app.performAction(
+ .{ .surface = self },
+ .key_table,
+ .deactivate,
+ ) catch |err| {
+ log.warn(
+ "failed to notify app of key table err={}",
+ .{err},
+ );
+ };
+ },
+
+ .deactivate_all_key_tables => {
+ return try self.deactivateAllKeyTables();
+ },
+
+ .end_key_sequence => {
+ // End the key sequence and flush queued keys to the terminal,
+ // but don't encode the key that triggered this action. This
+ // will do that because leaf keys (keys with bindings) aren't
+ // in the queued encoding list.
+ self.endKeySequence(.flush, .retain);
+ },
+
.crash => |location| switch (location) {
.main => @panic("crash binding action, crashing intentionally"),
@@ -5821,11 +6120,15 @@ pub fn completeClipboardRequest(
/// This starts a clipboard request, with some basic validation. For example,
/// an OSC 52 request is not actually requested if OSC 52 is disabled.
+///
+/// Returns true if the request was started, false if it was not (e.g., clipboard
+/// doesn't contain text for paste requests). This allows performable keybinds
+/// to pass through when the action cannot be performed.
fn startClipboardRequest(
self: *Surface,
loc: apprt.Clipboard,
req: apprt.ClipboardRequest,
-) !void {
+) !bool {
switch (req) {
.paste => {}, // always allowed
.osc_52_read => if (self.config.clipboard_read == .deny) {
@@ -5833,14 +6136,14 @@ fn startClipboardRequest(
"application attempted to read clipboard, but 'clipboard-read' is set to deny",
.{},
);
- return;
+ return false;
},
// No clipboard write code paths travel through this function
.osc_52_write => unreachable,
}
- try self.rt_surface.clipboardRequest(loc, req);
+ return try self.rt_surface.clipboardRequest(loc, req);
}
fn completeClipboardPaste(
diff --git a/src/apprt/action.zig b/src/apprt/action.zig
index 8e0a9d018..78f4bef54 100644
--- a/src/apprt/action.zig
+++ b/src/apprt/action.zig
@@ -250,6 +250,9 @@ pub const Action = union(Key) {
/// key mode because other input may be ignored.
key_sequence: KeySequence,
+ /// A key table has been activated or deactivated.
+ key_table: KeyTable,
+
/// A terminal color was changed programmatically through things
/// such as OSC 10/11.
color_change: ColorChange,
@@ -310,7 +313,9 @@ pub const Action = union(Key) {
/// A command has finished,
command_finished: CommandFinished,
- /// Start the search overlay with an optional initial needle.
+ /// Start the search overlay with an optional initial needle. If the
+ /// search is already active and the needle is non-empty, update the
+ /// current search needle and focus the search input.
start_search: StartSearch,
/// End the search overlay, clearing the search state and hiding it.
@@ -371,6 +376,7 @@ pub const Action = union(Key) {
float_window,
secure_input,
key_sequence,
+ key_table,
color_change,
reload_config,
config_change,
@@ -711,6 +717,50 @@ pub const KeySequence = union(enum) {
}
};
+pub const KeyTable = union(enum) {
+ activate: []const u8,
+ deactivate,
+ deactivate_all,
+
+ // Sync with: ghostty_action_key_table_tag_e
+ pub const Tag = enum(c_int) {
+ activate,
+ deactivate,
+ deactivate_all,
+ };
+
+ // Sync with: ghostty_action_key_table_u
+ pub const CValue = extern union {
+ activate: extern struct {
+ name: [*]const u8,
+ len: usize,
+ },
+ };
+
+ // Sync with: ghostty_action_key_table_s
+ pub const C = extern struct {
+ tag: Tag,
+ value: CValue,
+ };
+
+ pub fn cval(self: KeyTable) C {
+ return switch (self) {
+ .activate => |name| .{
+ .tag = .activate,
+ .value = .{ .activate = .{ .name = name.ptr, .len = name.len } },
+ },
+ .deactivate => .{
+ .tag = .deactivate,
+ .value = undefined,
+ },
+ .deactivate_all => .{
+ .tag = .deactivate_all,
+ .value = undefined,
+ },
+ };
+ }
+};
+
pub const ColorChange = extern struct {
kind: ColorKind,
r: u8,
diff --git a/src/apprt/embedded.zig b/src/apprt/embedded.zig
index da7a585a5..b4ad7f885 100644
--- a/src/apprt/embedded.zig
+++ b/src/apprt/embedded.zig
@@ -155,7 +155,7 @@ pub const App = struct {
while (it.next()) |entry| {
switch (entry.value_ptr.*) {
.leader => {},
- .leaf => |leaf| if (leaf.flags.global) return true,
+ inline .leaf, .leaf_chained => |leaf| if (leaf.flags.global) return true,
}
}
@@ -456,6 +456,9 @@ pub const Surface = struct {
/// Wait after the command exits
wait_after_command: bool = false,
+
+ /// Context for the new surface
+ context: apprt.surface.NewSurfaceContext = .window,
};
pub fn init(self: *Surface, app: *App, opts: Options) !void {
@@ -477,7 +480,7 @@ pub const Surface = struct {
errdefer app.core_app.deleteSurface(self);
// Shallow copy the config so that we can modify it.
- var config = try apprt.surface.newConfig(app.core_app, &app.config);
+ var config = try apprt.surface.newConfig(app.core_app, &app.config, opts.context);
defer config.deinit();
// If we have a working directory from the options then we set it.
@@ -539,13 +542,20 @@ pub const Surface = struct {
// If we have an initial input then we set it.
if (opts.initial_input) |c_input| {
const alloc = config.arenaAlloc();
+
+ // We need to escape the string because the "raw" field
+ // expects a Zig string.
+ var buf: std.Io.Writer.Allocating = .init(alloc);
+ defer buf.deinit();
+ try std.zig.stringEscape(
+ std.mem.sliceTo(c_input, 0),
+ &buf.writer,
+ );
+
config.input.list.clearRetainingCapacity();
try config.input.list.append(
alloc,
- .{ .raw = try alloc.dupeZ(u8, std.mem.sliceTo(
- c_input,
- 0,
- )) },
+ .{ .raw = try buf.toOwnedSliceSentinel(0) },
);
}
@@ -652,7 +662,7 @@ pub const Surface = struct {
self: *Surface,
clipboard_type: apprt.Clipboard,
state: apprt.ClipboardRequest,
- ) !void {
+ ) !bool {
// We need to allocate to get a pointer to store our clipboard request
// so that it is stable until the read_clipboard callback and call
// complete_clipboard_request. This sucks but clipboard requests aren't
@@ -667,6 +677,10 @@ pub const Surface = struct {
@intCast(@intFromEnum(clipboard_type)),
state_ptr,
);
+
+ // Embedded apprt can't synchronously check clipboard content types,
+ // so we always return true to indicate the request was started.
+ return true;
}
fn completeClipboardRequest(
@@ -890,14 +904,23 @@ pub const Surface = struct {
};
}
- pub fn newSurfaceOptions(self: *const Surface) apprt.Surface.Options {
+ pub fn newSurfaceOptions(self: *const Surface, context: apprt.surface.NewSurfaceContext) apprt.Surface.Options {
const font_size: f32 = font_size: {
if (!self.app.config.@"window-inherit-font-size") break :font_size 0;
break :font_size self.core_surface.font_size.points;
};
+ const working_directory: ?[*:0]const u8 = wd: {
+ if (!apprt.surface.shouldInheritWorkingDirectory(context, &self.app.config)) break :wd null;
+ const cwd = self.core_surface.pwd(self.app.core_app.alloc) catch null orelse break :wd null;
+ defer self.app.core_app.alloc.free(cwd);
+ break :wd self.app.core_app.alloc.dupeZ(u8, cwd) catch null;
+ };
+
return .{
.font_size = font_size,
+ .working_directory = working_directory,
+ .context = context,
};
}
@@ -943,7 +966,7 @@ pub const Surface = struct {
/// Inspector is the state required for the terminal inspector. A terminal
/// inspector is 1:1 with a Surface.
pub const Inspector = struct {
- const cimgui = @import("cimgui");
+ const cimgui = @import("dcimgui");
surface: *Surface,
ig_ctx: *cimgui.c.ImGuiContext,
@@ -964,10 +987,10 @@ pub const Inspector = struct {
};
pub fn init(surface: *Surface) !Inspector {
- const ig_ctx = cimgui.c.igCreateContext(null) orelse return error.OutOfMemory;
- errdefer cimgui.c.igDestroyContext(ig_ctx);
- cimgui.c.igSetCurrentContext(ig_ctx);
- const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO();
+ const ig_ctx = cimgui.c.ImGui_CreateContext(null) orelse return error.OutOfMemory;
+ errdefer cimgui.c.ImGui_DestroyContext(ig_ctx);
+ cimgui.c.ImGui_SetCurrentContext(ig_ctx);
+ const io: *cimgui.c.ImGuiIO = cimgui.c.ImGui_GetIO();
io.BackendPlatformName = "ghostty_embedded";
// Setup our core inspector
@@ -984,9 +1007,9 @@ pub const Inspector = struct {
pub fn deinit(self: *Inspector) void {
self.surface.core_surface.deactivateInspector();
- cimgui.c.igSetCurrentContext(self.ig_ctx);
+ cimgui.c.ImGui_SetCurrentContext(self.ig_ctx);
if (self.backend) |v| v.deinit();
- cimgui.c.igDestroyContext(self.ig_ctx);
+ cimgui.c.ImGui_DestroyContext(self.ig_ctx);
}
/// Queue a render for the next frame.
@@ -997,7 +1020,7 @@ pub const Inspector = struct {
/// Initialize the inspector for a metal backend.
pub fn initMetal(self: *Inspector, device: objc.Object) bool {
defer device.msgSend(void, objc.sel("release"), .{});
- cimgui.c.igSetCurrentContext(self.ig_ctx);
+ cimgui.c.ImGui_SetCurrentContext(self.ig_ctx);
if (self.backend) |v| {
v.deinit();
@@ -1032,7 +1055,7 @@ pub const Inspector = struct {
for (0..2) |_| {
cimgui.ImGui_ImplMetal_NewFrame(desc.value);
try self.newFrame();
- cimgui.c.igNewFrame();
+ cimgui.c.ImGui_NewFrame();
// Build our UI
render: {
@@ -1042,7 +1065,7 @@ pub const Inspector = struct {
}
// Render
- cimgui.c.igRender();
+ cimgui.c.ImGui_Render();
}
// MTLRenderCommandEncoder
@@ -1053,7 +1076,7 @@ pub const Inspector = struct {
);
defer encoder.msgSend(void, objc.sel("endEncoding"), .{});
cimgui.ImGui_ImplMetal_RenderDrawData(
- cimgui.c.igGetDrawData(),
+ cimgui.c.ImGui_GetDrawData(),
command_buffer.value,
encoder.value,
);
@@ -1061,22 +1084,24 @@ pub const Inspector = struct {
pub fn updateContentScale(self: *Inspector, x: f64, y: f64) void {
_ = y;
- cimgui.c.igSetCurrentContext(self.ig_ctx);
+ cimgui.c.ImGui_SetCurrentContext(self.ig_ctx);
// Cache our scale because we use it for cursor position calculations.
self.content_scale = x;
- // Setup a new style and scale it appropriately.
- const style = cimgui.c.ImGuiStyle_ImGuiStyle();
- defer cimgui.c.ImGuiStyle_destroy(style);
- cimgui.c.ImGuiStyle_ScaleAllSizes(style, @floatCast(x));
- const active_style = cimgui.c.igGetStyle();
- active_style.* = style.*;
+ // Setup a new style and scale it appropriately. We must use the
+ // ImGuiStyle constructor to get proper default values (e.g.,
+ // CurveTessellationTol) rather than zero-initialized values.
+ var style: cimgui.c.ImGuiStyle = undefined;
+ cimgui.ext.ImGuiStyle_ImGuiStyle(&style);
+ cimgui.c.ImGuiStyle_ScaleAllSizes(&style, @floatCast(x));
+ const active_style = cimgui.c.ImGui_GetStyle();
+ active_style.* = style;
}
pub fn updateSize(self: *Inspector, width: u32, height: u32) void {
- cimgui.c.igSetCurrentContext(self.ig_ctx);
- const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO();
+ cimgui.c.ImGui_SetCurrentContext(self.ig_ctx);
+ const io: *cimgui.c.ImGuiIO = cimgui.c.ImGui_GetIO();
io.DisplaySize = .{ .x = @floatFromInt(width), .y = @floatFromInt(height) };
}
@@ -1089,8 +1114,8 @@ pub const Inspector = struct {
_ = mods;
self.queueRender();
- cimgui.c.igSetCurrentContext(self.ig_ctx);
- const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO();
+ cimgui.c.ImGui_SetCurrentContext(self.ig_ctx);
+ const io: *cimgui.c.ImGuiIO = cimgui.c.ImGui_GetIO();
const imgui_button = switch (button) {
.left => cimgui.c.ImGuiMouseButton_Left,
@@ -1111,8 +1136,8 @@ pub const Inspector = struct {
_ = mods;
self.queueRender();
- cimgui.c.igSetCurrentContext(self.ig_ctx);
- const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO();
+ cimgui.c.ImGui_SetCurrentContext(self.ig_ctx);
+ const io: *cimgui.c.ImGuiIO = cimgui.c.ImGui_GetIO();
cimgui.c.ImGuiIO_AddMouseWheelEvent(
io,
@floatCast(xoff),
@@ -1122,8 +1147,8 @@ pub const Inspector = struct {
pub fn cursorPosCallback(self: *Inspector, x: f64, y: f64) void {
self.queueRender();
- cimgui.c.igSetCurrentContext(self.ig_ctx);
- const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO();
+ cimgui.c.ImGui_SetCurrentContext(self.ig_ctx);
+ const io: *cimgui.c.ImGuiIO = cimgui.c.ImGui_GetIO();
cimgui.c.ImGuiIO_AddMousePosEvent(
io,
@floatCast(x * self.content_scale),
@@ -1133,15 +1158,15 @@ pub const Inspector = struct {
pub fn focusCallback(self: *Inspector, focused: bool) void {
self.queueRender();
- cimgui.c.igSetCurrentContext(self.ig_ctx);
- const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO();
+ cimgui.c.ImGui_SetCurrentContext(self.ig_ctx);
+ const io: *cimgui.c.ImGuiIO = cimgui.c.ImGui_GetIO();
cimgui.c.ImGuiIO_AddFocusEvent(io, focused);
}
pub fn textCallback(self: *Inspector, text: [:0]const u8) void {
self.queueRender();
- cimgui.c.igSetCurrentContext(self.ig_ctx);
- const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO();
+ cimgui.c.ImGui_SetCurrentContext(self.ig_ctx);
+ const io: *cimgui.c.ImGuiIO = cimgui.c.ImGui_GetIO();
cimgui.c.ImGuiIO_AddInputCharactersUTF8(io, text.ptr);
}
@@ -1152,8 +1177,8 @@ pub const Inspector = struct {
mods: input.Mods,
) !void {
self.queueRender();
- cimgui.c.igSetCurrentContext(self.ig_ctx);
- const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO();
+ cimgui.c.ImGui_SetCurrentContext(self.ig_ctx);
+ const io: *cimgui.c.ImGuiIO = cimgui.c.ImGui_GetIO();
// Update all our modifiers
cimgui.c.ImGuiIO_AddKeyEvent(io, cimgui.c.ImGuiKey_LeftShift, mods.shift);
@@ -1172,7 +1197,7 @@ pub const Inspector = struct {
}
fn newFrame(self: *Inspector) !void {
- const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO();
+ const io: *cimgui.c.ImGuiIO = cimgui.c.ImGui_GetIO();
// Determine our delta time
const now = try std.time.Instant.now();
@@ -1517,8 +1542,11 @@ pub const CAPI = struct {
}
/// Returns the config to use for surfaces that inherit from this one.
- export fn ghostty_surface_inherited_config(surface: *Surface) Surface.Options {
- return surface.newSurfaceOptions();
+ export fn ghostty_surface_inherited_config(
+ surface: *Surface,
+ source: apprt.surface.NewSurfaceContext,
+ ) Surface.Options {
+ return surface.newSurfaceOptions(source);
}
/// Update the configuration to the provided config for only this surface.
@@ -1700,23 +1728,6 @@ pub const CAPI = struct {
return @intCast(@as(input.Mods.Backing, @bitCast(result)));
}
- /// Returns the current possible commands for a surface
- /// in the output parameter. The memory is owned by libghostty
- /// and doesn't need to be freed.
- export fn ghostty_surface_commands(
- surface: *Surface,
- out: *[*]const input.Command.C,
- len: *usize,
- ) void {
- // In the future we may use this information to filter
- // some commands.
- _ = surface;
-
- const commands = input.command.defaultsC;
- out.* = commands.ptr;
- len.* = commands.len;
- }
-
/// Send this for raw keypresses (i.e. the keyDown event on macOS).
/// This will handle the keymap translation and send the appropriate
/// key and char events.
@@ -1740,13 +1751,18 @@ pub const CAPI = struct {
export fn ghostty_surface_key_is_binding(
surface: *Surface,
event: KeyEvent,
+ c_flags: ?*input.Binding.Flags.C,
) bool {
const core_event = event.keyEvent().core() orelse {
log.warn("error processing key event", .{});
return false;
};
- return surface.core_surface.keyEventIsBinding(core_event);
+ const flags = surface.core_surface.keyEventIsBinding(
+ core_event,
+ ) orelse return false;
+ if (c_flags) |ptr| ptr.* = flags.cval();
+ return true;
}
/// Send raw text to the terminal. This is treated like a paste
@@ -2149,7 +2165,10 @@ pub const CAPI = struct {
if (comptime std.debug.runtime_safety) unreachable;
return false;
};
- break :sel surface.io.terminal.screens.active.selectWord(pin) orelse return false;
+ break :sel surface.io.terminal.screens.active.selectWord(
+ pin,
+ surface.config.selection_word_chars,
+ ) orelse return false;
};
// Read the selection
diff --git a/src/apprt/gtk.zig b/src/apprt/gtk.zig
index 415d3773d..07b4eb0e7 100644
--- a/src/apprt/gtk.zig
+++ b/src/apprt/gtk.zig
@@ -10,4 +10,5 @@ pub const WeakRef = @import("gtk/weak_ref.zig").WeakRef;
test {
@import("std").testing.refAllDecls(@This());
_ = @import("gtk/ext.zig");
+ _ = @import("gtk/key.zig");
}
diff --git a/src/apprt/gtk/Surface.zig b/src/apprt/gtk/Surface.zig
index 009ce018d..918e77146 100644
--- a/src/apprt/gtk/Surface.zig
+++ b/src/apprt/gtk/Surface.zig
@@ -73,8 +73,8 @@ pub fn clipboardRequest(
self: *Self,
clipboard_type: apprt.Clipboard,
state: apprt.ClipboardRequest,
-) !void {
- try self.surface.clipboardRequest(
+) !bool {
+ return try self.surface.clipboardRequest(
clipboard_type,
state,
);
diff --git a/src/apprt/gtk/build/gresource.zig b/src/apprt/gtk/build/gresource.zig
index c77579aab..d3684c171 100644
--- a/src/apprt/gtk/build/gresource.zig
+++ b/src/apprt/gtk/build/gresource.zig
@@ -44,6 +44,7 @@ pub const blueprints: []const Blueprint = &.{
.{ .major = 1, .minor = 5, .name = "inspector-window" },
.{ .major = 1, .minor = 2, .name = "resize-overlay" },
.{ .major = 1, .minor = 2, .name = "search-overlay" },
+ .{ .major = 1, .minor = 2, .name = "key-state-overlay" },
.{ .major = 1, .minor = 5, .name = "split-tree" },
.{ .major = 1, .minor = 5, .name = "split-tree-split" },
.{ .major = 1, .minor = 2, .name = "surface" },
diff --git a/src/apprt/gtk/class/application.zig b/src/apprt/gtk/class/application.zig
index be0f3f2c8..bc83c09a4 100644
--- a/src/apprt/gtk/class/application.zig
+++ b/src/apprt/gtk/class/application.zig
@@ -669,6 +669,9 @@ pub const Application = extern struct {
.inspector => return Action.controlInspector(target, value),
+ .key_sequence => return Action.keySequence(target, value),
+ .key_table => return Action.keyTable(target, value),
+
.mouse_over_link => Action.mouseOverLink(target, value),
.mouse_shape => Action.mouseShape(target, value),
.mouse_visibility => Action.mouseVisibility(target, value),
@@ -731,7 +734,7 @@ pub const Application = extern struct {
.show_on_screen_keyboard => return Action.showOnScreenKeyboard(target),
.command_finished => return Action.commandFinished(target, value),
- .start_search => Action.startSearch(target),
+ .start_search => Action.startSearch(target, value),
.end_search => Action.endSearch(target),
.search_total => Action.searchTotal(target, value),
.search_selected => Action.searchSelected(target, value),
@@ -743,7 +746,6 @@ pub const Application = extern struct {
.toggle_visibility,
.toggle_background_opacity,
.cell_size,
- .key_sequence,
.render_inspector,
.renderer_health,
.color_change,
@@ -2236,8 +2238,8 @@ const Action = struct {
.{},
);
- // Create a new tab
- win.newTab(parent);
+ // Create a new tab with window context (first tab in new window)
+ win.newTabForWindow(parent);
// Show the window
gtk.Window.present(win.as(gtk.Window));
@@ -2437,17 +2439,17 @@ const Action = struct {
}
}
- pub fn startSearch(target: apprt.Target) void {
+ pub fn startSearch(target: apprt.Target, value: apprt.action.StartSearch) void {
switch (target) {
.app => {},
- .surface => |v| v.rt_surface.surface.setSearchActive(true),
+ .surface => |v| v.rt_surface.surface.setSearchActive(true, value.needle),
}
}
pub fn endSearch(target: apprt.Target) void {
switch (target) {
.app => {},
- .surface => |v| v.rt_surface.surface.setSearchActive(false),
+ .surface => |v| v.rt_surface.surface.setSearchActive(false, ""),
}
}
@@ -2659,6 +2661,36 @@ const Action = struct {
},
}
}
+
+ pub fn keySequence(target: apprt.Target, value: apprt.Action.Value(.key_sequence)) bool {
+ switch (target) {
+ .app => {
+ log.warn("key_sequence action to app is unexpected", .{});
+ return false;
+ },
+ .surface => |core| {
+ core.rt_surface.gobj().keySequenceAction(value) catch |err| {
+ log.warn("error handling key_sequence action: {}", .{err});
+ };
+ return true;
+ },
+ }
+ }
+
+ pub fn keyTable(target: apprt.Target, value: apprt.Action.Value(.key_table)) bool {
+ switch (target) {
+ .app => {
+ log.warn("key_table action to app is unexpected", .{});
+ return false;
+ },
+ .surface => |core| {
+ core.rt_surface.gobj().keyTableAction(value) catch |err| {
+ log.warn("error handling key_table action: {}", .{err});
+ };
+ return true;
+ },
+ }
+ }
};
/// This sets various GTK-related environment variables as necessary
diff --git a/src/apprt/gtk/class/command_palette.zig b/src/apprt/gtk/class/command_palette.zig
index 6da49115e..0d91c43b2 100644
--- a/src/apprt/gtk/class/command_palette.zig
+++ b/src/apprt/gtk/class/command_palette.zig
@@ -10,9 +10,12 @@ const gtk = @import("gtk");
const input = @import("../../../input.zig");
const gresource = @import("../build/gresource.zig");
const key = @import("../key.zig");
+const WeakRef = @import("../weak_ref.zig").WeakRef;
const Common = @import("../class.zig").Common;
const Application = @import("application.zig").Application;
const Window = @import("window.zig").Window;
+const Surface = @import("surface.zig").Surface;
+const Tab = @import("tab.zig").Tab;
const Config = @import("config.zig").Config;
const log = std.log.scoped(.gtk_ghostty_command_palette);
@@ -146,34 +149,138 @@ pub const CommandPalette = extern struct {
return;
};
- const cfg = config.get();
-
// Clear existing binds
priv.source.removeAll();
+ const alloc = Application.default().allocator();
+ var commands: std.ArrayList(*Command) = .{};
+ defer {
+ for (commands.items) |cmd| cmd.unref();
+ commands.deinit(alloc);
+ }
+
+ self.collectJumpCommands(config, &commands) catch |err| {
+ log.warn("failed to collect jump commands: {}", .{err});
+ };
+
+ self.collectRegularCommands(config, &commands, alloc);
+
+ // Sort commands
+ std.mem.sort(*Command, commands.items, {}, struct {
+ fn lessThan(_: void, a: *Command, b: *Command) bool {
+ return compareCommands(a, b);
+ }
+ }.lessThan);
+
+ for (commands.items) |cmd| {
+ const cmd_ref = cmd.as(gobject.Object);
+ priv.source.append(cmd_ref);
+ }
+ }
+
+ /// Collect regular commands from configuration, filtering out unsupported actions.
+ fn collectRegularCommands(
+ self: *CommandPalette,
+ config: *Config,
+ commands: *std.ArrayList(*Command),
+ alloc: std.mem.Allocator,
+ ) void {
+ _ = self;
+ const cfg = config.get();
+
for (cfg.@"command-palette-entry".value.items) |command| {
// Filter out actions that are not implemented or don't make sense
// for GTK.
- switch (command.action) {
- .close_all_windows,
- .toggle_secure_input,
- .check_for_updates,
- .redo,
- .undo,
- .reset_window_size,
- .toggle_window_float_on_top,
- => continue,
+ if (!isActionSupportedOnGtk(command.action)) continue;
- else => {},
- }
+ const cmd = Command.new(config, command) catch |err| {
+ log.warn("failed to create command: {}", .{err});
+ continue;
+ };
+ errdefer cmd.unref();
- const cmd = Command.new(config, command);
- const cmd_ref = cmd.as(gobject.Object);
- priv.source.append(cmd_ref);
- cmd_ref.unref();
+ commands.append(alloc, cmd) catch |err| {
+ log.warn("failed to add command to list: {}", .{err});
+ continue;
+ };
}
}
+ /// Check if an action is supported on GTK.
+ fn isActionSupportedOnGtk(action: input.Binding.Action) bool {
+ return switch (action) {
+ .close_all_windows,
+ .toggle_secure_input,
+ .check_for_updates,
+ .redo,
+ .undo,
+ .reset_window_size,
+ .toggle_window_float_on_top,
+ => false,
+
+ else => true,
+ };
+ }
+
+ /// Collect jump commands for all surfaces across all windows.
+ fn collectJumpCommands(
+ self: *CommandPalette,
+ config: *Config,
+ commands: *std.ArrayList(*Command),
+ ) !void {
+ _ = self;
+ const app = Application.default();
+ const alloc = app.allocator();
+
+ // Get all surfaces from the core app
+ const core_app = app.core();
+ for (core_app.surfaces.items) |apprt_surface| {
+ const surface = apprt_surface.gobj();
+ const cmd = Command.newJump(config, surface);
+ errdefer cmd.unref();
+ try commands.append(alloc, cmd);
+ }
+ }
+
+ /// Compare two commands for sorting.
+ /// Sorts alphabetically by title (case-insensitive), with colon normalization
+ /// so "Foo:" sorts before "Foo Bar:". Uses sort_key as tie-breaker.
+ fn compareCommands(a: *Command, b: *Command) bool {
+ const a_title = a.propGetTitle() orelse return false;
+ const b_title = b.propGetTitle() orelse return true;
+
+ // Compare case-insensitively with colon normalization
+ for (0..@min(a_title.len, b_title.len)) |i| {
+ // Get characters, replacing ':' with '\t'
+ const a_char = if (a_title[i] == ':') '\t' else a_title[i];
+ const b_char = if (b_title[i] == ':') '\t' else b_title[i];
+
+ const a_lower = std.ascii.toLower(a_char);
+ const b_lower = std.ascii.toLower(b_char);
+
+ if (a_lower != b_lower) {
+ return a_lower < b_lower;
+ }
+ }
+
+ // If one title is a prefix of the other, shorter one comes first
+ if (a_title.len != b_title.len) {
+ return a_title.len < b_title.len;
+ }
+
+ // Titles are equal - use sort_key as tie-breaker if both are jump commands
+ const a_sort_key = switch (a.private().data) {
+ .regular => return false,
+ .jump => |*ja| ja.sort_key,
+ };
+ const b_sort_key = switch (b.private().data) {
+ .regular => return false,
+ .jump => |*jb| jb.sort_key,
+ };
+
+ return a_sort_key < b_sort_key;
+ }
+
fn close(self: *CommandPalette) void {
const priv = self.private();
_ = priv.dialog.close();
@@ -234,6 +341,16 @@ pub const CommandPalette = extern struct {
self.close();
const cmd = gobject.ext.cast(Command, object_ orelse return) orelse return;
+
+ // Handle jump commands differently
+ if (cmd.isJump()) {
+ const surface = cmd.getJumpSurface() orelse return;
+ defer surface.unref();
+ surface.present();
+ return;
+ }
+
+ // Regular command - emit trigger signal
const action = cmd.getAction() orelse return;
// Signal that an an action has been selected. Signals are synchronous
@@ -413,31 +530,63 @@ const Command = extern struct {
};
pub const Private = struct {
- /// The configuration we should use to get keybindings.
config: ?*Config = null,
-
- /// Arena used to manage our allocations.
arena: ArenaAllocator,
-
- /// The command.
- command: ?input.Command = null,
-
- /// Cache the formatted action.
- action: ?[:0]const u8 = null,
-
- /// Cache the formatted action_key.
- action_key: ?[:0]const u8 = null,
+ data: CommandData,
pub var offset: c_int = 0;
+
+ pub const CommandData = union(enum) {
+ regular: RegularData,
+ jump: JumpData,
+ };
+
+ pub const RegularData = struct {
+ command: input.Command,
+ action: ?[:0]const u8 = null,
+ action_key: ?[:0]const u8 = null,
+ };
+
+ pub const JumpData = struct {
+ surface: WeakRef(Surface) = .empty,
+ title: ?[:0]const u8 = null,
+ description: ?[:0]const u8 = null,
+ sort_key: usize,
+ };
};
- pub fn new(config: *Config, command: input.Command) *Self {
+ pub fn new(config: *Config, command: input.Command) Allocator.Error!*Self {
+ const self = gobject.ext.newInstance(Self, .{
+ .config = config,
+ });
+ errdefer self.unref();
+
+ const priv = self.private();
+ const cloned = try command.clone(priv.arena.allocator());
+
+ priv.data = .{
+ .regular = .{
+ .command = cloned,
+ },
+ };
+
+ return self;
+ }
+
+ /// Create a new jump command that focuses a specific surface.
+ pub fn newJump(config: *Config, surface: *Surface) *Self {
const self = gobject.ext.newInstance(Self, .{
.config = config,
});
const priv = self.private();
- priv.command = command.clone(priv.arena.allocator()) catch null;
+ priv.data = .{
+ .jump = .{
+ // TODO: Replace with surface id whenever Ghostty adds one
+ .sort_key = @intFromPtr(surface),
+ },
+ };
+ priv.data.jump.surface.set(surface);
return self;
}
@@ -459,6 +608,13 @@ const Command = extern struct {
priv.config = null;
}
+ switch (priv.data) {
+ .regular => {},
+ .jump => |*j| {
+ j.surface.set(null);
+ },
+ }
+
gobject.Object.virtual_methods.dispose.call(
Class.parent,
self.as(Parent),
@@ -481,52 +637,99 @@ const Command = extern struct {
fn propGetActionKey(self: *Self) ?[:0]const u8 {
const priv = self.private();
- if (priv.action_key) |action_key| return action_key;
+ const regular = switch (priv.data) {
+ .regular => |*r| r,
+ .jump => return null,
+ };
- const command = priv.command orelse return null;
+ if (regular.action_key) |action_key| return action_key;
- priv.action_key = std.fmt.allocPrintSentinel(
+ regular.action_key = std.fmt.allocPrintSentinel(
priv.arena.allocator(),
"{f}",
- .{command.action},
+ .{regular.command.action},
0,
) catch null;
- return priv.action_key;
+ return regular.action_key;
}
fn propGetAction(self: *Self) ?[:0]const u8 {
const priv = self.private();
- if (priv.action) |action| return action;
+ const regular = switch (priv.data) {
+ .regular => |*r| r,
+ .jump => return null,
+ };
- const command = priv.command orelse return null;
+ if (regular.action) |action| return action;
const cfg = if (priv.config) |config| config.get() else return null;
const keybinds = cfg.keybind.set;
const alloc = priv.arena.allocator();
- priv.action = action: {
+ regular.action = action: {
var buf: [64]u8 = undefined;
- const trigger = keybinds.getTrigger(command.action) orelse break :action null;
+ const trigger = keybinds.getTrigger(regular.command.action) orelse break :action null;
const accel = (key.accelFromTrigger(&buf, trigger) catch break :action null) orelse break :action null;
break :action alloc.dupeZ(u8, accel) catch return null;
};
- return priv.action;
+ return regular.action;
}
fn propGetTitle(self: *Self) ?[:0]const u8 {
const priv = self.private();
- const command = priv.command orelse return null;
- return command.title;
+
+ switch (priv.data) {
+ .regular => |*r| return r.command.title,
+ .jump => |*j| {
+ if (j.title) |title| return title;
+
+ const surface = j.surface.get() orelse return null;
+ defer surface.unref();
+
+ const alloc = priv.arena.allocator();
+ const surface_title = surface.getTitle() orelse "Untitled";
+
+ j.title = std.fmt.allocPrintSentinel(
+ alloc,
+ "Focus: {s}",
+ .{surface_title},
+ 0,
+ ) catch null;
+
+ return j.title;
+ },
+ }
}
fn propGetDescription(self: *Self) ?[:0]const u8 {
const priv = self.private();
- const command = priv.command orelse return null;
- return command.description;
+
+ switch (priv.data) {
+ .regular => |*r| return r.command.description,
+ .jump => |*j| {
+ if (j.description) |desc| return desc;
+
+ const surface = j.surface.get() orelse return null;
+ defer surface.unref();
+
+ const alloc = priv.arena.allocator();
+
+ const title = surface.getTitle() orelse "Untitled";
+ const pwd = surface.getPwd();
+
+ if (pwd) |p| {
+ if (std.mem.indexOf(u8, title, p) == null) {
+ j.description = alloc.dupeZ(u8, p) catch null;
+ }
+ }
+
+ return j.description;
+ },
+ }
}
//---------------------------------------------------------------
@@ -536,8 +739,26 @@ const Command = extern struct {
/// allocated data that will be freed when this object is.
pub fn getAction(self: *Self) ?input.Binding.Action {
const priv = self.private();
- const command = priv.command orelse return null;
- return command.action;
+ return switch (priv.data) {
+ .regular => |*r| r.command.action,
+ .jump => null,
+ };
+ }
+
+ /// Check if this is a jump command.
+ pub fn isJump(self: *Self) bool {
+ const priv = self.private();
+ return priv.data == .jump;
+ }
+
+ /// Get the jump surface. Returns a strong reference that the caller
+ /// must unref when done, or null if the surface has been destroyed.
+ pub fn getJumpSurface(self: *Self) ?*Surface {
+ const priv = self.private();
+ return switch (priv.data) {
+ .regular => null,
+ .jump => |*j| j.surface.get(),
+ };
}
//---------------------------------------------------------------
diff --git a/src/apprt/gtk/class/global_shortcuts.zig b/src/apprt/gtk/class/global_shortcuts.zig
index 57652916a..cf0f31a6e 100644
--- a/src/apprt/gtk/class/global_shortcuts.zig
+++ b/src/apprt/gtk/class/global_shortcuts.zig
@@ -169,13 +169,17 @@ pub const GlobalShortcuts = extern struct {
var trigger_buf: [1024]u8 = undefined;
var it = config.keybind.set.bindings.iterator();
while (it.next()) |entry| {
- const leaf = switch (entry.value_ptr.*) {
- // Global shortcuts can't have leaders
+ const leaf: Binding.Set.GenericLeaf = switch (entry.value_ptr.*) {
.leader => continue,
- .leaf => |leaf| leaf,
+ inline .leaf, .leaf_chained => |leaf| leaf.generic(),
};
if (!leaf.flags.global) continue;
+ // We only allow global keybinds that map to exactly a single
+ // action for now. TODO: remove this restriction
+ const actions = leaf.actionsSlice();
+ if (actions.len != 1) continue;
+
const trigger = if (key.xdgShortcutFromTrigger(
&trigger_buf,
entry.key_ptr.*,
@@ -197,7 +201,7 @@ pub const GlobalShortcuts = extern struct {
try priv.map.put(
alloc,
try alloc.dupeZ(u8, trigger),
- leaf.action,
+ actions[0],
);
}
diff --git a/src/apprt/gtk/class/imgui_widget.zig b/src/apprt/gtk/class/imgui_widget.zig
index ef1ca05c9..79e85fad2 100644
--- a/src/apprt/gtk/class/imgui_widget.zig
+++ b/src/apprt/gtk/class/imgui_widget.zig
@@ -1,7 +1,7 @@
const std = @import("std");
const assert = @import("../../../quirks.zig").inlineAssert;
-const cimgui = @import("cimgui");
+const cimgui = @import("dcimgui");
const gl = @import("opengl");
const adw = @import("adw");
const gdk = @import("gdk");
@@ -126,7 +126,7 @@ pub const ImguiWidget = extern struct {
log.warn("Dear ImGui context not initialized", .{});
return error.ContextNotInitialized;
};
- cimgui.c.igSetCurrentContext(ig_context);
+ cimgui.c.ImGui_SetCurrentContext(ig_context);
}
/// Initialize the frame. Expects that the context is already current.
@@ -137,7 +137,7 @@ pub const ImguiWidget = extern struct {
const priv = self.private();
- const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO();
+ const io: *cimgui.c.ImGuiIO = cimgui.c.ImGui_GetIO();
// Determine our delta time
const now = std.time.Instant.now() catch unreachable;
@@ -163,7 +163,7 @@ pub const ImguiWidget = extern struct {
self.setCurrentContext() catch return false;
- const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO();
+ const io: *cimgui.c.ImGuiIO = cimgui.c.ImGui_GetIO();
const mods = key.translateMods(gtk_mods);
cimgui.c.ImGuiIO_AddKeyEvent(io, cimgui.c.ImGuiKey_LeftShift, mods.shift);
@@ -219,14 +219,14 @@ pub const ImguiWidget = extern struct {
return;
}
- priv.ig_context = cimgui.c.igCreateContext(null) orelse {
+ priv.ig_context = cimgui.c.ImGui_CreateContext(null) orelse {
log.warn("unable to initialize Dear ImGui context", .{});
return;
};
self.setCurrentContext() catch return;
// Setup some basic config
- const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO();
+ const io: *cimgui.c.ImGuiIO = cimgui.c.ImGui_GetIO();
io.BackendPlatformName = "ghostty_gtk";
// Realize means that our OpenGL context is ready, so we can now
@@ -247,7 +247,7 @@ pub const ImguiWidget = extern struct {
/// Handle a request to resize the GLArea
fn glAreaResize(area: *gtk.GLArea, width: c_int, height: c_int, self: *Self) callconv(.c) void {
self.setCurrentContext() catch return;
- const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO();
+ const io: *cimgui.c.ImGuiIO = cimgui.c.ImGui_GetIO();
const scale_factor = area.as(gtk.Widget).getScaleFactor();
// Our display size is always unscaled. We'll do the scaling in the
@@ -255,12 +255,14 @@ pub const ImguiWidget = extern struct {
io.DisplaySize = .{ .x = @floatFromInt(width), .y = @floatFromInt(height) };
io.DisplayFramebufferScale = .{ .x = 1, .y = 1 };
- // Setup a new style and scale it appropriately.
- const style = cimgui.c.ImGuiStyle_ImGuiStyle();
- defer cimgui.c.ImGuiStyle_destroy(style);
- cimgui.c.ImGuiStyle_ScaleAllSizes(style, @floatFromInt(scale_factor));
- const active_style = cimgui.c.igGetStyle();
- active_style.* = style.*;
+ // Setup a new style and scale it appropriately. We must use the
+ // ImGuiStyle constructor to get proper default values (e.g.,
+ // CurveTessellationTol) rather than zero-initialized values.
+ var style: cimgui.c.ImGuiStyle = undefined;
+ cimgui.ext.ImGuiStyle_ImGuiStyle(&style);
+ cimgui.c.ImGuiStyle_ScaleAllSizes(&style, @floatFromInt(scale_factor));
+ const active_style = cimgui.c.ImGui_GetStyle();
+ active_style.* = style;
}
/// Handle a request to render the contents of our GLArea
@@ -273,33 +275,33 @@ pub const ImguiWidget = extern struct {
for (0..2) |_| {
cimgui.ImGui_ImplOpenGL3_NewFrame();
self.newFrame();
- cimgui.c.igNewFrame();
+ cimgui.c.ImGui_NewFrame();
// Call the virtual method to draw the UI.
self.render();
// Render
- cimgui.c.igRender();
+ cimgui.c.ImGui_Render();
}
// OpenGL final render
gl.clearColor(0x28 / 0xFF, 0x2C / 0xFF, 0x34 / 0xFF, 1.0);
gl.clear(gl.c.GL_COLOR_BUFFER_BIT);
- cimgui.ImGui_ImplOpenGL3_RenderDrawData(cimgui.c.igGetDrawData());
+ cimgui.ImGui_ImplOpenGL3_RenderDrawData(cimgui.c.ImGui_GetDrawData());
return @intFromBool(true);
}
fn ecFocusEnter(_: *gtk.EventControllerFocus, self: *Self) callconv(.c) void {
self.setCurrentContext() catch return;
- const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO();
+ const io: *cimgui.c.ImGuiIO = cimgui.c.ImGui_GetIO();
cimgui.c.ImGuiIO_AddFocusEvent(io, true);
self.queueRender();
}
fn ecFocusLeave(_: *gtk.EventControllerFocus, self: *Self) callconv(.c) void {
self.setCurrentContext() catch return;
- const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO();
+ const io: *cimgui.c.ImGuiIO = cimgui.c.ImGui_GetIO();
cimgui.c.ImGuiIO_AddFocusEvent(io, false);
self.queueRender();
}
@@ -345,7 +347,7 @@ pub const ImguiWidget = extern struct {
) callconv(.c) void {
self.queueRender();
self.setCurrentContext() catch return;
- const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO();
+ const io: *cimgui.c.ImGuiIO = cimgui.c.ImGui_GetIO();
const gdk_button = gesture.as(gtk.GestureSingle).getCurrentButton();
if (translateMouseButton(gdk_button)) |button| {
cimgui.c.ImGuiIO_AddMouseButtonEvent(io, button, true);
@@ -361,7 +363,7 @@ pub const ImguiWidget = extern struct {
) callconv(.c) void {
self.queueRender();
self.setCurrentContext() catch return;
- const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO();
+ const io: *cimgui.c.ImGuiIO = cimgui.c.ImGui_GetIO();
const gdk_button = gesture.as(gtk.GestureSingle).getCurrentButton();
if (translateMouseButton(gdk_button)) |button| {
cimgui.c.ImGuiIO_AddMouseButtonEvent(io, button, false);
@@ -376,7 +378,7 @@ pub const ImguiWidget = extern struct {
) callconv(.c) void {
self.queueRender();
self.setCurrentContext() catch return;
- const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO();
+ const io: *cimgui.c.ImGuiIO = cimgui.c.ImGui_GetIO();
const scale_factor = self.getScaleFactor();
cimgui.c.ImGuiIO_AddMousePosEvent(
io,
@@ -393,7 +395,7 @@ pub const ImguiWidget = extern struct {
) callconv(.c) c_int {
self.queueRender();
self.setCurrentContext() catch return @intFromBool(false);
- const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO();
+ const io: *cimgui.c.ImGuiIO = cimgui.c.ImGui_GetIO();
cimgui.c.ImGuiIO_AddMouseWheelEvent(
io,
@floatCast(x),
@@ -409,7 +411,7 @@ pub const ImguiWidget = extern struct {
) callconv(.c) void {
self.queueRender();
self.setCurrentContext() catch return;
- const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO();
+ const io: *cimgui.c.ImGuiIO = cimgui.c.ImGui_GetIO();
cimgui.c.ImGuiIO_AddInputCharactersUTF8(io, bytes);
}
diff --git a/src/apprt/gtk/class/key_state_overlay.zig b/src/apprt/gtk/class/key_state_overlay.zig
new file mode 100644
index 000000000..7aca8f01d
--- /dev/null
+++ b/src/apprt/gtk/class/key_state_overlay.zig
@@ -0,0 +1,342 @@
+const std = @import("std");
+const adw = @import("adw");
+const glib = @import("glib");
+const gobject = @import("gobject");
+const gtk = @import("gtk");
+
+const ext = @import("../ext.zig");
+const gresource = @import("../build/gresource.zig");
+const Application = @import("application.zig").Application;
+const Common = @import("../class.zig").Common;
+
+const log = std.log.scoped(.gtk_ghostty_key_state_overlay);
+
+/// An overlay that displays the current key table stack and pending key sequence.
+/// This helps users understand what key bindings are active and what keys they've
+/// pressed in a multi-key sequence.
+pub const KeyStateOverlay = extern struct {
+ const Self = @This();
+ parent_instance: Parent,
+ pub const Parent = adw.Bin;
+ pub const getGObjectType = gobject.ext.defineClass(Self, .{
+ .name = "GhosttyKeyStateOverlay",
+ .instanceInit = &init,
+ .classInit = &Class.init,
+ .parent_class = &Class.parent,
+ .private = .{ .Type = Private, .offset = &Private.offset },
+ });
+
+ pub const properties = struct {
+ pub const tables = struct {
+ pub const name = "tables";
+ const impl = gobject.ext.defineProperty(
+ name,
+ Self,
+ ?*ext.StringList,
+ .{
+ .accessor = gobject.ext.typedAccessor(
+ Self,
+ ?*ext.StringList,
+ .{
+ .getter = getTables,
+ .getter_transfer = .none,
+ .setter = setTables,
+ .setter_transfer = .full,
+ },
+ ),
+ },
+ );
+ };
+
+ pub const @"has-tables" = struct {
+ pub const name = "has-tables";
+ const impl = gobject.ext.defineProperty(
+ name,
+ Self,
+ bool,
+ .{
+ .default = false,
+ .accessor = gobject.ext.typedAccessor(
+ Self,
+ bool,
+ .{ .getter = getHasTables },
+ ),
+ },
+ );
+ };
+
+ pub const sequence = struct {
+ pub const name = "sequence";
+ const impl = gobject.ext.defineProperty(
+ name,
+ Self,
+ ?*ext.StringList,
+ .{
+ .accessor = gobject.ext.typedAccessor(
+ Self,
+ ?*ext.StringList,
+ .{
+ .getter = getSequence,
+ .getter_transfer = .none,
+ .setter = setSequence,
+ .setter_transfer = .full,
+ },
+ ),
+ },
+ );
+ };
+
+ pub const @"has-sequence" = struct {
+ pub const name = "has-sequence";
+ const impl = gobject.ext.defineProperty(
+ name,
+ Self,
+ bool,
+ .{
+ .default = false,
+ .accessor = gobject.ext.typedAccessor(
+ Self,
+ bool,
+ .{ .getter = getHasSequence },
+ ),
+ },
+ );
+ };
+
+ pub const @"valign-target" = struct {
+ pub const name = "valign-target";
+ const impl = gobject.ext.defineProperty(
+ name,
+ Self,
+ gtk.Align,
+ .{
+ .default = .end,
+ .accessor = C.privateShallowFieldAccessor("valign_target"),
+ },
+ );
+ };
+ };
+
+ const Private = struct {
+ /// The key table stack.
+ tables: ?*ext.StringList = null,
+
+ /// The key sequence.
+ sequence: ?*ext.StringList = null,
+
+ /// Target vertical alignment for the overlay.
+ valign_target: gtk.Align = .end,
+
+ pub var offset: c_int = 0;
+ };
+
+ fn init(self: *Self, _: *Class) callconv(.c) void {
+ gtk.Widget.initTemplate(self.as(gtk.Widget));
+ }
+
+ fn getTables(self: *Self) ?*ext.StringList {
+ return self.private().tables;
+ }
+
+ fn getSequence(self: *Self) ?*ext.StringList {
+ return self.private().sequence;
+ }
+
+ fn setTables(self: *Self, value: ?*ext.StringList) void {
+ const priv = self.private();
+ if (priv.tables) |old| {
+ old.destroy();
+ priv.tables = null;
+ }
+ if (value) |v| {
+ priv.tables = v;
+ }
+
+ self.as(gobject.Object).notifyByPspec(properties.tables.impl.param_spec);
+ self.as(gobject.Object).notifyByPspec(properties.@"has-tables".impl.param_spec);
+ }
+
+ fn setSequence(self: *Self, value: ?*ext.StringList) void {
+ const priv = self.private();
+ if (priv.sequence) |old| {
+ old.destroy();
+ priv.sequence = null;
+ }
+ if (value) |v| {
+ priv.sequence = v;
+ }
+
+ self.as(gobject.Object).notifyByPspec(properties.sequence.impl.param_spec);
+ self.as(gobject.Object).notifyByPspec(properties.@"has-sequence".impl.param_spec);
+ }
+
+ fn getHasTables(self: *Self) bool {
+ const v = self.private().tables orelse return false;
+ return v.strings.len > 0;
+ }
+
+ fn getHasSequence(self: *Self) bool {
+ const v = self.private().sequence orelse return false;
+ return v.strings.len > 0;
+ }
+
+ fn closureShowChevron(
+ _: *Self,
+ has_tables: bool,
+ has_sequence: bool,
+ ) callconv(.c) c_int {
+ return if (has_tables and has_sequence) 1 else 0;
+ }
+
+ fn closureHasState(
+ _: *Self,
+ has_tables: bool,
+ has_sequence: bool,
+ ) callconv(.c) c_int {
+ return if (has_tables or has_sequence) 1 else 0;
+ }
+
+ fn closureTablesText(
+ _: *Self,
+ tables: ?*ext.StringList,
+ ) callconv(.c) ?[*:0]const u8 {
+ const list = tables orelse return null;
+ if (list.strings.len == 0) return null;
+
+ var buf: std.Io.Writer.Allocating = .init(Application.default().allocator());
+ defer buf.deinit();
+
+ for (list.strings, 0..) |s, i| {
+ if (i > 0) buf.writer.writeAll(" > ") catch return null;
+ buf.writer.writeAll(s) catch return null;
+ }
+
+ return glib.ext.dupeZ(u8, buf.written());
+ }
+
+ fn closureSequenceText(
+ _: *Self,
+ sequence: ?*ext.StringList,
+ ) callconv(.c) ?[*:0]const u8 {
+ const list = sequence orelse return null;
+ if (list.strings.len == 0) return null;
+
+ var buf: std.Io.Writer.Allocating = .init(Application.default().allocator());
+ defer buf.deinit();
+
+ for (list.strings, 0..) |s, i| {
+ if (i > 0) buf.writer.writeAll(" ") catch return null;
+ buf.writer.writeAll(s) catch return null;
+ }
+
+ return glib.ext.dupeZ(u8, buf.written());
+ }
+
+ //---------------------------------------------------------------
+ // Template callbacks
+
+ fn onDragEnd(
+ _: *gtk.GestureDrag,
+ _: f64,
+ offset_y: f64,
+ self: *Self,
+ ) callconv(.c) void {
+ // Key state overlay only moves between top-center and bottom-center.
+ // Horizontal alignment is always center.
+ const priv = self.private();
+ const widget = self.as(gtk.Widget);
+ const parent = widget.getParent() orelse return;
+
+ const parent_height: f64 = @floatFromInt(parent.getAllocatedHeight());
+ const self_height: f64 = @floatFromInt(widget.getAllocatedHeight());
+
+ const self_y: f64 = if (priv.valign_target == .start) 0 else parent_height - self_height;
+ const new_y = self_y + offset_y + (self_height / 2);
+
+ const new_valign: gtk.Align = if (new_y > parent_height / 2) .end else .start;
+
+ if (new_valign != priv.valign_target) {
+ priv.valign_target = new_valign;
+ self.as(gobject.Object).notifyByPspec(properties.@"valign-target".impl.param_spec);
+ self.as(gtk.Widget).queueResize();
+ }
+ }
+
+ //---------------------------------------------------------------
+ // Virtual methods
+
+ fn dispose(self: *Self) callconv(.c) void {
+ gtk.Widget.disposeTemplate(
+ self.as(gtk.Widget),
+ getGObjectType(),
+ );
+
+ gobject.Object.virtual_methods.dispose.call(
+ Class.parent,
+ self.as(Parent),
+ );
+ }
+
+ fn finalize(self: *Self) callconv(.c) void {
+ const priv = self.private();
+
+ if (priv.tables) |v| {
+ v.destroy();
+ }
+ if (priv.sequence) |v| {
+ v.destroy();
+ }
+
+ gobject.Object.virtual_methods.finalize.call(
+ Class.parent,
+ self.as(Parent),
+ );
+ }
+
+ const C = Common(Self, Private);
+ pub const as = C.as;
+ pub const ref = C.ref;
+ pub const unref = C.unref;
+ const private = C.private;
+
+ pub const Class = extern struct {
+ parent_class: Parent.Class,
+ var parent: *Parent.Class = undefined;
+ pub const Instance = Self;
+
+ fn init(class: *Class) callconv(.c) void {
+ gtk.Widget.Class.setTemplateFromResource(
+ class.as(gtk.Widget.Class),
+ comptime gresource.blueprint(.{
+ .major = 1,
+ .minor = 2,
+ .name = "key-state-overlay",
+ }),
+ );
+
+ // Template Callbacks
+ class.bindTemplateCallback("on_drag_end", &onDragEnd);
+ class.bindTemplateCallback("show_chevron", &closureShowChevron);
+ class.bindTemplateCallback("has_state", &closureHasState);
+ class.bindTemplateCallback("tables_text", &closureTablesText);
+ class.bindTemplateCallback("sequence_text", &closureSequenceText);
+
+ // Properties
+ gobject.ext.registerProperties(class, &.{
+ properties.tables.impl,
+ properties.@"has-tables".impl,
+ properties.sequence.impl,
+ properties.@"has-sequence".impl,
+ properties.@"valign-target".impl,
+ });
+
+ // Virtual methods
+ gobject.Object.virtual_methods.dispose.implement(class, &dispose);
+ gobject.Object.virtual_methods.finalize.implement(class, &finalize);
+ }
+
+ pub const as = C.Class.as;
+ pub const bindTemplateChildPrivate = C.Class.bindTemplateChildPrivate;
+ pub const bindTemplateCallback = C.Class.bindTemplateCallback;
+ };
+};
diff --git a/src/apprt/gtk/class/search_overlay.zig b/src/apprt/gtk/class/search_overlay.zig
index 4936cd967..a81115462 100644
--- a/src/apprt/gtk/class/search_overlay.zig
+++ b/src/apprt/gtk/class/search_overlay.zig
@@ -250,6 +250,13 @@ pub const SearchOverlay = extern struct {
priv.active = active;
}
+ // Set contents of search
+ pub fn setSearchContents(self: *Self, content: [:0]const u8) void {
+ const priv = self.private();
+ priv.search_entry.as(gtk.Editable).setText(content);
+ signals.@"search-changed".impl.emit(self, null, .{content}, null);
+ }
+
/// Set the total number of search matches.
pub fn setSearchTotal(self: *Self, total: ?usize) void {
const priv = self.private();
diff --git a/src/apprt/gtk/class/split_tree.zig b/src/apprt/gtk/class/split_tree.zig
index 46b3268d9..8d859adca 100644
--- a/src/apprt/gtk/class/split_tree.zig
+++ b/src/apprt/gtk/class/split_tree.zig
@@ -219,7 +219,7 @@ pub const SplitTree = extern struct {
// Inherit properly if we were asked to.
if (parent_) |p| {
if (p.core()) |core| {
- surface.setParent(core);
+ surface.setParent(core, .split);
}
}
diff --git a/src/apprt/gtk/class/surface.zig b/src/apprt/gtk/class/surface.zig
index 93d1beeb2..5c3bf18b6 100644
--- a/src/apprt/gtk/class/surface.zig
+++ b/src/apprt/gtk/class/surface.zig
@@ -19,6 +19,7 @@ const terminal = @import("../../../terminal/main.zig");
const CoreSurface = @import("../../../Surface.zig");
const gresource = @import("../build/gresource.zig");
const ext = @import("../ext.zig");
+const gsettings = @import("../gsettings.zig");
const gtk_key = @import("../key.zig");
const ApprtSurface = @import("../Surface.zig");
const Common = @import("../class.zig").Common;
@@ -26,6 +27,7 @@ const Application = @import("application.zig").Application;
const Config = @import("config.zig").Config;
const ResizeOverlay = @import("resize_overlay.zig").ResizeOverlay;
const SearchOverlay = @import("search_overlay.zig").SearchOverlay;
+const KeyStateOverlay = @import("key_state_overlay.zig").KeyStateOverlay;
const ChildExited = @import("surface_child_exited.zig").SurfaceChildExited;
const ClipboardConfirmationDialog = @import("clipboard_confirmation_dialog.zig").ClipboardConfirmationDialog;
const TitleDialog = @import("surface_title_dialog.zig").SurfaceTitleDialog;
@@ -360,6 +362,44 @@ pub const Surface = extern struct {
},
);
};
+
+ pub const @"key-sequence" = struct {
+ pub const name = "key-sequence";
+ const impl = gobject.ext.defineProperty(
+ name,
+ Self,
+ ?*ext.StringList,
+ .{
+ .accessor = gobject.ext.typedAccessor(
+ Self,
+ ?*ext.StringList,
+ .{
+ .getter = getKeySequence,
+ .getter_transfer = .full,
+ },
+ ),
+ },
+ );
+ };
+
+ pub const @"key-table" = struct {
+ pub const name = "key-table";
+ const impl = gobject.ext.defineProperty(
+ name,
+ Self,
+ ?*ext.StringList,
+ .{
+ .accessor = gobject.ext.typedAccessor(
+ Self,
+ ?*ext.StringList,
+ .{
+ .getter = getKeyTable,
+ .getter_transfer = .full,
+ },
+ ),
+ },
+ );
+ };
};
pub const signals = struct {
@@ -553,6 +593,9 @@ pub const Surface = extern struct {
/// The search overlay
search_overlay: *SearchOverlay,
+ /// The key state overlay
+ key_state_overlay: *KeyStateOverlay,
+
/// The apprt Surface.
rt_surface: ApprtSurface = undefined,
@@ -617,6 +660,10 @@ pub const Surface = extern struct {
vscroll_policy: gtk.ScrollablePolicy = .natural,
vadj_signal_group: ?*gobject.SignalGroup = null,
+ // Key state tracking for key sequences and tables
+ key_sequence: std.ArrayListUnmanaged([:0]const u8) = .empty,
+ key_tables: std.ArrayListUnmanaged([:0]const u8) = .empty,
+
// Template binds
child_exited_overlay: *ChildExited,
context_menu: *gtk.PopoverMenu,
@@ -625,6 +672,12 @@ pub const Surface = extern struct {
error_page: *adw.StatusPage,
terminal_page: *gtk.Overlay,
+ /// The context for this surface (window, tab, or split)
+ context: apprt.surface.NewSurfaceContext = .window,
+
+ /// Whether primary paste (middle-click paste) is enabled.
+ gtk_enable_primary_paste: bool = true,
+
pub var offset: c_int = 0;
};
@@ -650,6 +703,7 @@ pub const Surface = extern struct {
pub fn setParent(
self: *Self,
parent: *CoreSurface,
+ context: apprt.surface.NewSurfaceContext,
) void {
const priv = self.private();
@@ -660,6 +714,9 @@ pub const Surface = extern struct {
return;
}
+ // Store the context so initSurface can use it
+ priv.context = context;
+
// Setup our font size
const font_size_ptr = glib.ext.create(font.face.DesiredSize);
errdefer glib.ext.destroy(font_size_ptr);
@@ -670,10 +727,8 @@ pub const Surface = extern struct {
// Remainder needs a config. If there is no config we just assume
// we aren't inheriting any of these values.
if (priv.config) |config_obj| {
- const config = config_obj.get();
-
- // Setup our pwd if configured to inherit
- if (config.@"window-inherit-working-directory") {
+ // Setup our cwd if configured to inherit
+ if (apprt.surface.shouldInheritWorkingDirectory(context, config_obj.get())) {
if (parent.rt_surface.surface.getPwd()) |pwd| {
priv.pwd = glib.ext.dupeZ(u8, pwd);
self.as(gobject.Object).notifyByPspec(properties.pwd.impl.param_spec);
@@ -778,6 +833,74 @@ pub const Surface = extern struct {
if (priv.inspector) |v| v.queueRender();
}
+ /// Handle a key sequence action from the apprt.
+ pub fn keySequenceAction(
+ self: *Self,
+ value: apprt.action.KeySequence,
+ ) Allocator.Error!void {
+ const priv = self.private();
+ const alloc = Application.default().allocator();
+
+ self.as(gobject.Object).freezeNotify();
+ defer self.as(gobject.Object).thawNotify();
+ self.as(gobject.Object).notifyByPspec(properties.@"key-sequence".impl.param_spec);
+
+ switch (value) {
+ .trigger => |trigger| {
+ // Convert the trigger to a human-readable label
+ var buf: std.Io.Writer.Allocating = .init(alloc);
+ defer buf.deinit();
+ if (gtk_key.labelFromTrigger(&buf.writer, trigger)) |success| {
+ if (!success) return;
+ } else |_| return error.OutOfMemory;
+
+ // Make space
+ try priv.key_sequence.ensureUnusedCapacity(alloc, 1);
+
+ // Copy and append
+ const duped = try buf.toOwnedSliceSentinel(0);
+ errdefer alloc.free(duped);
+ priv.key_sequence.appendAssumeCapacity(duped);
+ },
+ .end => {
+ // Free all the stored strings and clear
+ for (priv.key_sequence.items) |s| alloc.free(s);
+ priv.key_sequence.clearAndFree(alloc);
+ },
+ }
+ }
+
+ /// Handle a key table action from the apprt.
+ pub fn keyTableAction(
+ self: *Self,
+ value: apprt.action.KeyTable,
+ ) Allocator.Error!void {
+ const priv = self.private();
+ const alloc = Application.default().allocator();
+
+ self.as(gobject.Object).freezeNotify();
+ defer self.as(gobject.Object).thawNotify();
+ self.as(gobject.Object).notifyByPspec(properties.@"key-table".impl.param_spec);
+
+ switch (value) {
+ .activate => |name| {
+ // Duplicate the name string and push onto stack
+ const duped = try alloc.dupeZ(u8, name);
+ errdefer alloc.free(duped);
+ try priv.key_tables.append(alloc, duped);
+ },
+ .deactivate => {
+ // Pop and free the top table
+ if (priv.key_tables.pop()) |s| alloc.free(s);
+ },
+ .deactivate_all => {
+ // Free all tables and clear
+ for (priv.key_tables.items) |s| alloc.free(s);
+ priv.key_tables.clearAndFree(alloc);
+ },
+ }
+ }
+
pub fn showOnScreenKeyboard(self: *Self, event: ?*gdk.Event) bool {
const priv = self.private();
return priv.im_context.as(gtk.IMContext).activateOsk(event) != 0;
@@ -1392,19 +1515,17 @@ pub const Surface = extern struct {
const xft_dpi_scale = xft_scale: {
// gtk-xft-dpi is font DPI multiplied by 1024. See
// https://docs.gtk.org/gtk4/property.Settings.gtk-xft-dpi.html
- const settings = gtk.Settings.getDefault() orelse break :xft_scale 1.0;
- var value = std.mem.zeroes(gobject.Value);
- defer value.unset();
- _ = value.init(gobject.ext.typeFor(c_int));
- settings.as(gobject.Object).getProperty("gtk-xft-dpi", &value);
- const gtk_xft_dpi = value.getInt();
+ const gtk_xft_dpi = gsettings.get(.@"gtk-xft-dpi") orelse {
+ log.warn("gtk-xft-dpi was not set, using default value", .{});
+ break :xft_scale 1.0;
+ };
// Use a value of 1.0 for the XFT DPI scale if the setting is <= 0
// See:
// https://gitlab.gnome.org/GNOME/libadwaita/-/commit/a7738a4d269bfdf4d8d5429ca73ccdd9b2450421
// https://gitlab.gnome.org/GNOME/libadwaita/-/commit/9759d3fd81129608dd78116001928f2aed974ead
if (gtk_xft_dpi <= 0) {
- log.warn("gtk-xft-dpi was not set, using default value", .{});
+ log.warn("gtk-xft-dpi has invalid value ({}), using default", .{gtk_xft_dpi});
break :xft_scale 1.0;
}
@@ -1552,8 +1673,8 @@ pub const Surface = extern struct {
self: *Self,
clipboard_type: apprt.Clipboard,
state: apprt.ClipboardRequest,
- ) !void {
- try Clipboard.request(
+ ) !bool {
+ return try Clipboard.request(
self,
clipboard_type,
state,
@@ -1648,6 +1769,9 @@ pub const Surface = extern struct {
priv.im_composing = false;
priv.im_len = 0;
+ // Read GTK primary paste setting
+ priv.gtk_enable_primary_paste = gsettings.get(.@"gtk-enable-primary-paste") orelse true;
+
// Set up to handle items being dropped on our surface. Files can be dropped
// from Nautilus and strings can be dropped from many programs. The order
// of these types matter.
@@ -1787,6 +1911,14 @@ pub const Surface = extern struct {
glib.free(@ptrCast(@constCast(v)));
priv.title_override = null;
}
+
+ // Clean up key sequence and key table state
+ const alloc = Application.default().allocator();
+ for (priv.key_sequence.items) |s| alloc.free(s);
+ priv.key_sequence.deinit(alloc);
+ for (priv.key_tables.items) |s| alloc.free(s);
+ priv.key_tables.deinit(alloc);
+
self.clearCgroup();
gobject.Object.virtual_methods.finalize.call(
@@ -1873,6 +2005,20 @@ pub const Surface = extern struct {
self.as(gobject.Object).notifyByPspec(properties.@"default-size".impl.param_spec);
}
+ /// Get the key sequence list. Full transfer.
+ fn getKeySequence(self: *Self) ?*ext.StringList {
+ const priv = self.private();
+ const alloc = Application.default().allocator();
+ return ext.StringList.create(alloc, priv.key_sequence.items) catch null;
+ }
+
+ /// Get the key table list. Full transfer.
+ fn getKeyTable(self: *Self) ?*ext.StringList {
+ const priv = self.private();
+ const alloc = Application.default().allocator();
+ return ext.StringList.create(alloc, priv.key_tables.items) catch null;
+ }
+
/// Return the min size, if set.
pub fn getMinSize(self: *Self) ?*Size {
const priv = self.private();
@@ -1950,7 +2096,7 @@ pub const Surface = extern struct {
self.as(gobject.Object).notifyByPspec(properties.@"error".impl.param_spec);
}
- pub fn setSearchActive(self: *Self, active: bool) void {
+ pub fn setSearchActive(self: *Self, active: bool, needle: [:0]const u8) void {
const priv = self.private();
var value = gobject.ext.Value.newFrom(active);
defer value.unset();
@@ -1960,6 +2106,10 @@ pub const Surface = extern struct {
&value,
);
+ if (!std.mem.eql(u8, needle, "")) {
+ priv.search_overlay.setSearchContents(needle);
+ }
+
if (active) {
priv.search_overlay.grabFocus();
}
@@ -2540,6 +2690,11 @@ pub const Surface = extern struct {
// Report the event
const button = translateMouseButton(gesture.as(gtk.GestureSingle).getCurrentButton());
+
+ if (button == .middle and !priv.gtk_enable_primary_paste) {
+ return;
+ }
+
const consumed = consumed: {
const gtk_mods = event.getModifierState();
const mods = gtk_key.translateMods(gtk_mods);
@@ -2591,6 +2746,10 @@ pub const Surface = extern struct {
const gtk_mods = event.getModifierState();
const button = translateMouseButton(gesture.as(gtk.GestureSingle).getCurrentButton());
+ if (button == .middle and !priv.gtk_enable_primary_paste) {
+ return;
+ }
+
const mods = gtk_key.translateMods(gtk_mods);
const consumed = surface.mouseButtonCallback(
.release,
@@ -3070,6 +3229,7 @@ pub const Surface = extern struct {
var config = try apprt.surface.newConfig(
app.core(),
priv.config.?.get(),
+ priv.context,
);
defer config.deinit();
@@ -3236,6 +3396,7 @@ pub const Surface = extern struct {
fn init(class: *Class) callconv(.c) void {
gobject.ext.ensureType(ResizeOverlay);
gobject.ext.ensureType(SearchOverlay);
+ gobject.ext.ensureType(KeyStateOverlay);
gobject.ext.ensureType(ChildExited);
gtk.Widget.Class.setTemplateFromResource(
class.as(gtk.Widget.Class),
@@ -3256,6 +3417,7 @@ pub const Surface = extern struct {
class.bindTemplateChildPrivate("progress_bar_overlay", .{});
class.bindTemplateChildPrivate("resize_overlay", .{});
class.bindTemplateChildPrivate("search_overlay", .{});
+ class.bindTemplateChildPrivate("key_state_overlay", .{});
class.bindTemplateChildPrivate("terminal_page", .{});
class.bindTemplateChildPrivate("drop_target", .{});
class.bindTemplateChildPrivate("im_context", .{});
@@ -3307,6 +3469,8 @@ pub const Surface = extern struct {
properties.@"error".impl,
properties.@"font-size-request".impl,
properties.focused.impl,
+ properties.@"key-sequence".impl,
+ properties.@"key-table".impl,
properties.@"min-size".impl,
properties.@"mouse-shape".impl,
properties.@"mouse-hidden".impl,
@@ -3483,16 +3647,30 @@ const Clipboard = struct {
/// Request data from the clipboard (read the clipboard). This
/// completes asynchronously and will call the `completeClipboardRequest`
/// core surface API when done.
+ ///
+ /// Returns true if the request was started, false if the clipboard
+ /// doesn't contain text (allowing performable keybinds to pass through).
pub fn request(
self: *Surface,
clipboard_type: apprt.Clipboard,
state: apprt.ClipboardRequest,
- ) Allocator.Error!void {
+ ) Allocator.Error!bool {
// Get our requested clipboard
const clipboard = get(
self.private().gl_area.as(gtk.Widget),
clipboard_type,
- ) orelse return;
+ ) orelse return false;
+
+ // For paste requests, check if clipboard has text format available.
+ // This is a synchronous check that allows performable keybinds to
+ // pass through when the clipboard contains non-text content (e.g., images).
+ if (state == .paste) {
+ const formats = clipboard.getFormats();
+ if (formats.containGtype(gobject.ext.types.string) == 0) {
+ log.debug("clipboard has no text format, not starting paste request", .{});
+ return false;
+ }
+ }
// Allocate our userdata
const alloc = Application.default().allocator();
@@ -3512,6 +3690,8 @@ const Clipboard = struct {
clipboardReadText,
ud,
);
+
+ return true;
}
/// Paste explicit text directly into the surface, regardless of the
diff --git a/src/apprt/gtk/class/tab.zig b/src/apprt/gtk/class/tab.zig
index fb3b8b0ef..ae05cd1ad 100644
--- a/src/apprt/gtk/class/tab.zig
+++ b/src/apprt/gtk/class/tab.zig
@@ -161,8 +161,12 @@ pub const Tab = extern struct {
/// ever created for a tab. If a surface was already created this does
/// nothing.
pub fn setParent(self: *Self, parent: *CoreSurface) void {
+ self.setParentWithContext(parent, .tab);
+ }
+
+ pub fn setParentWithContext(self: *Self, parent: *CoreSurface, context: apprt.surface.NewSurfaceContext) void {
if (self.getActiveSurface()) |surface| {
- surface.setParent(parent);
+ surface.setParent(parent, context);
}
}
diff --git a/src/apprt/gtk/class/window.zig b/src/apprt/gtk/class/window.zig
index 77fd2eea5..4a16580ef 100644
--- a/src/apprt/gtk/class/window.zig
+++ b/src/apprt/gtk/class/window.zig
@@ -361,10 +361,14 @@ pub const Window = extern struct {
/// at the position dictated by the `window-new-tab-position` config.
/// The new tab will be selected.
pub fn newTab(self: *Self, parent_: ?*CoreSurface) void {
- _ = self.newTabPage(parent_);
+ _ = self.newTabPage(parent_, .tab);
}
- fn newTabPage(self: *Self, parent_: ?*CoreSurface) *adw.TabPage {
+ pub fn newTabForWindow(self: *Self, parent_: ?*CoreSurface) void {
+ _ = self.newTabPage(parent_, .window);
+ }
+
+ fn newTabPage(self: *Self, parent_: ?*CoreSurface, context: apprt.surface.NewSurfaceContext) *adw.TabPage {
const priv = self.private();
const tab_view = priv.tab_view;
@@ -372,7 +376,9 @@ pub const Window = extern struct {
const tab = gobject.ext.newInstance(Tab, .{
.config = priv.config,
});
- if (parent_) |p| tab.setParent(p);
+ if (parent_) |p| {
+ tab.setParentWithContext(p, context);
+ }
// Get the position that we should insert the new tab at.
const config = if (priv.config) |v| v.get() else {
@@ -804,6 +810,11 @@ pub const Window = extern struct {
return self.private().config;
}
+ /// Get the tab view for this window.
+ pub fn getTabView(self: *Self) *adw.TabView {
+ return self.private().tab_view;
+ }
+
/// Get the current window decoration value for this window.
pub fn getWindowDecoration(self: *Self) configpkg.WindowDecoration {
const priv = self.private();
@@ -1231,7 +1242,7 @@ pub const Window = extern struct {
_: *adw.TabOverview,
self: *Self,
) callconv(.c) *adw.TabPage {
- return self.newTabPage(if (self.getActiveSurface()) |v| v.core() else null);
+ return self.newTabPage(if (self.getActiveSurface()) |v| v.core() else null, .tab);
}
fn tabOverviewOpen(
diff --git a/src/apprt/gtk/css/style.css b/src/apprt/gtk/css/style.css
index 938d23ad8..f5491b7de 100644
--- a/src/apprt/gtk/css/style.css
+++ b/src/apprt/gtk/css/style.css
@@ -46,6 +46,18 @@ label.url-overlay.right {
outline-width: 1px;
}
+/*
+ * GhosttySurface key state overlay
+ */
+.key-state-overlay {
+ padding: 6px 10px;
+ margin: 8px;
+ border-radius: 8px;
+ outline-style: solid;
+ outline-color: #555555;
+ outline-width: 1px;
+}
+
/*
* GhosttySurface resize overlay
*/
diff --git a/src/apprt/gtk/ext.zig b/src/apprt/gtk/ext.zig
index 9b1eeecc6..df9ab4ea2 100644
--- a/src/apprt/gtk/ext.zig
+++ b/src/apprt/gtk/ext.zig
@@ -12,6 +12,8 @@ const gobject = @import("gobject");
const gtk = @import("gtk");
pub const actions = @import("ext/actions.zig");
+const slice = @import("ext/slice.zig");
+pub const StringList = slice.StringList;
/// Wrapper around `gobject.boxedCopy` to copy a boxed type `T`.
pub fn boxedCopy(comptime T: type, ptr: *const T) *T {
@@ -64,4 +66,5 @@ pub fn gValueHolds(value_: ?*const gobject.Value, g_type: gobject.Type) bool {
test {
_ = actions;
+ _ = slice;
}
diff --git a/src/apprt/gtk/ext/slice.zig b/src/apprt/gtk/ext/slice.zig
new file mode 100644
index 000000000..49ad63d85
--- /dev/null
+++ b/src/apprt/gtk/ext/slice.zig
@@ -0,0 +1,111 @@
+const std = @import("std");
+const Allocator = std.mem.Allocator;
+const ArenaAllocator = std.heap.ArenaAllocator;
+const glib = @import("glib");
+const gobject = @import("gobject");
+
+/// A boxed type that holds a list of string slices.
+pub const StringList = struct {
+ arena: ArenaAllocator,
+ strings: []const [:0]const u8,
+
+ pub fn create(
+ alloc: Allocator,
+ strings: []const [:0]const u8,
+ ) Allocator.Error!*StringList {
+ var arena: ArenaAllocator = .init(alloc);
+ errdefer arena.deinit();
+ const arena_alloc = arena.allocator();
+ var stored = try arena_alloc.alloc([:0]const u8, strings.len);
+ for (strings, 0..) |s, i| stored[i] = try arena_alloc.dupeZ(u8, s);
+
+ const ptr = try alloc.create(StringList);
+ errdefer alloc.destroy(ptr);
+ ptr.* = .{ .arena = arena, .strings = stored };
+
+ return ptr;
+ }
+
+ pub fn deinit(self: *StringList) void {
+ self.arena.deinit();
+ }
+
+ pub fn destroy(self: *StringList) void {
+ const alloc = self.arena.child_allocator;
+ self.deinit();
+ alloc.destroy(self);
+ }
+
+ /// Returns the general-purpose allocator used by this StringList.
+ pub fn allocator(self: *const StringList) Allocator {
+ return self.arena.child_allocator;
+ }
+
+ pub const getGObjectType = gobject.ext.defineBoxed(
+ StringList,
+ .{
+ .name = "GhosttyStringList",
+ .funcs = .{
+ .copy = &struct {
+ fn copy(self: *StringList) callconv(.c) *StringList {
+ return StringList.create(
+ self.arena.child_allocator,
+ self.strings,
+ ) catch @panic("OOM");
+ }
+ }.copy,
+ .free = &struct {
+ fn free(self: *StringList) callconv(.c) void {
+ self.destroy();
+ }
+ }.free,
+ },
+ },
+ );
+};
+
+test "StringList create and destroy" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ const input: []const [:0]const u8 = &.{ "hello", "world" };
+ const list = try StringList.create(alloc, input);
+ defer list.destroy();
+
+ try testing.expectEqual(@as(usize, 2), list.strings.len);
+ try testing.expectEqualStrings("hello", list.strings[0]);
+ try testing.expectEqualStrings("world", list.strings[1]);
+}
+
+test "StringList create empty list" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ const input: []const [:0]const u8 = &.{};
+ const list = try StringList.create(alloc, input);
+ defer list.destroy();
+
+ try testing.expectEqual(@as(usize, 0), list.strings.len);
+}
+
+test "StringList boxedCopy and boxedFree" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ const input: []const [:0]const u8 = &.{ "foo", "bar", "baz" };
+ const original = try StringList.create(alloc, input);
+ defer original.destroy();
+
+ const copied: *StringList = @ptrCast(@alignCast(gobject.boxedCopy(
+ StringList.getGObjectType(),
+ original,
+ )));
+ defer gobject.boxedFree(StringList.getGObjectType(), copied);
+
+ try testing.expectEqual(@as(usize, 3), copied.strings.len);
+ try testing.expectEqualStrings("foo", copied.strings[0]);
+ try testing.expectEqualStrings("bar", copied.strings[1]);
+ try testing.expectEqualStrings("baz", copied.strings[2]);
+
+ try testing.expect(original.strings.ptr != copied.strings.ptr);
+}
diff --git a/src/apprt/gtk/gsettings.zig b/src/apprt/gtk/gsettings.zig
new file mode 100644
index 000000000..8cf7f12d2
--- /dev/null
+++ b/src/apprt/gtk/gsettings.zig
@@ -0,0 +1,101 @@
+const std = @import("std");
+const gtk = @import("gtk");
+const gobject = @import("gobject");
+
+/// GTK Settings keys with well-defined types.
+pub const Key = enum {
+ @"gtk-enable-primary-paste",
+ @"gtk-xft-dpi",
+ @"gtk-font-name",
+
+ fn Type(comptime self: Key) type {
+ return switch (self) {
+ .@"gtk-enable-primary-paste" => bool,
+ .@"gtk-xft-dpi" => c_int,
+ .@"gtk-font-name" => []const u8,
+ };
+ }
+
+ fn GValueType(comptime self: Key) type {
+ return switch (self.Type()) {
+ bool => c_int,
+ c_int => c_int,
+ []const u8 => ?[*:0]const u8,
+ else => @compileError("Unsupported type for GTK settings"),
+ };
+ }
+
+ /// Returns true if this setting type requires memory allocation.
+ /// Types that do not need allocation must be explicitly marked.
+ fn requiresAllocation(comptime self: Key) bool {
+ const T = self.Type();
+ return switch (T) {
+ bool, c_int => false,
+ else => true,
+ };
+ }
+};
+
+/// Reads a GTK setting for non-allocating types.
+/// Automatically uses XDG Desktop Portal in Flatpak environments.
+/// Returns null if the setting is unavailable.
+pub fn get(comptime key: Key) ?key.Type() {
+ if (comptime key.requiresAllocation()) {
+ @compileError("Allocating types require an allocator; use getAlloc() instead");
+ }
+ const settings = gtk.Settings.getDefault() orelse return null;
+ return getImpl(settings, null, key) catch unreachable;
+}
+
+/// Reads a GTK setting, allocating memory if necessary.
+/// Automatically uses XDG Desktop Portal in Flatpak environments.
+/// Caller must free returned memory with the provided allocator.
+/// Returns null if the setting is unavailable.
+pub fn getAlloc(allocator: std.mem.Allocator, comptime key: Key) !?key.Type() {
+ const settings = gtk.Settings.getDefault() orelse return null;
+ return getImpl(settings, allocator, key);
+}
+
+fn getImpl(settings: *gtk.Settings, allocator: ?std.mem.Allocator, comptime key: Key) !?key.Type() {
+ const GValType = key.GValueType();
+ var value = gobject.ext.Value.new(GValType);
+ defer value.unset();
+
+ settings.as(gobject.Object).getProperty(@tagName(key).ptr, &value);
+
+ return switch (key.Type()) {
+ bool => value.getInt() != 0,
+ c_int => value.getInt(),
+ []const u8 => blk: {
+ const alloc = allocator.?;
+ const ptr = value.getString() orelse break :blk null;
+ const str = std.mem.span(ptr);
+ break :blk try alloc.dupe(u8, str);
+ },
+ else => @compileError("Unsupported type for GTK settings"),
+ };
+}
+
+test "Key.Type returns correct types" {
+ try std.testing.expectEqual(bool, Key.@"gtk-enable-primary-paste".Type());
+ try std.testing.expectEqual(c_int, Key.@"gtk-xft-dpi".Type());
+ try std.testing.expectEqual([]const u8, Key.@"gtk-font-name".Type());
+}
+
+test "Key.requiresAllocation identifies allocating types" {
+ try std.testing.expectEqual(false, Key.@"gtk-enable-primary-paste".requiresAllocation());
+ try std.testing.expectEqual(false, Key.@"gtk-xft-dpi".requiresAllocation());
+ try std.testing.expectEqual(true, Key.@"gtk-font-name".requiresAllocation());
+}
+
+test "Key.GValueType returns correct GObject types" {
+ try std.testing.expectEqual(c_int, Key.@"gtk-enable-primary-paste".GValueType());
+ try std.testing.expectEqual(c_int, Key.@"gtk-xft-dpi".GValueType());
+ try std.testing.expectEqual(?[*:0]const u8, Key.@"gtk-font-name".GValueType());
+}
+
+test "@tagName returns correct GTK property names" {
+ try std.testing.expectEqualStrings("gtk-enable-primary-paste", @tagName(Key.@"gtk-enable-primary-paste"));
+ try std.testing.expectEqualStrings("gtk-xft-dpi", @tagName(Key.@"gtk-xft-dpi"));
+ try std.testing.expectEqualStrings("gtk-font-name", @tagName(Key.@"gtk-font-name"));
+}
diff --git a/src/apprt/gtk/key.zig b/src/apprt/gtk/key.zig
index 19bdc8315..5f717e14a 100644
--- a/src/apprt/gtk/key.zig
+++ b/src/apprt/gtk/key.zig
@@ -74,6 +74,8 @@ fn writeTriggerKey(
try writer.print("{u}", .{cp});
}
},
+
+ .catch_all => return false,
}
return true;
@@ -231,6 +233,70 @@ pub fn keyvalFromKey(key: input.Key) ?c_uint {
}
}
+/// Converts a trigger to a human-readable label for display in UI.
+///
+/// Uses GTK accelerator-style formatting (e.g., "Ctrl+Shift+A").
+/// Returns false if the trigger cannot be formatted (e.g., catch_all).
+pub fn labelFromTrigger(
+ writer: *std.Io.Writer,
+ trigger: input.Binding.Trigger,
+) std.Io.Writer.Error!bool {
+ // Modifiers first, using human-readable format
+ if (trigger.mods.super) try writer.writeAll("Super+");
+ if (trigger.mods.ctrl) try writer.writeAll("Ctrl+");
+ if (trigger.mods.alt) try writer.writeAll("Alt+");
+ if (trigger.mods.shift) try writer.writeAll("Shift+");
+
+ // Write the key
+ return writeTriggerKeyLabel(writer, trigger);
+}
+
+/// Writes the key portion of a trigger in human-readable format.
+fn writeTriggerKeyLabel(
+ writer: *std.Io.Writer,
+ trigger: input.Binding.Trigger,
+) error{WriteFailed}!bool {
+ switch (trigger.key) {
+ .physical => |k| {
+ const keyval = keyvalFromKey(k) orelse return false;
+ const name = gdk.keyvalName(keyval) orelse return false;
+ // Capitalize the first letter for nicer display
+ const span = std.mem.span(name);
+ if (span.len > 0) {
+ if (span[0] >= 'a' and span[0] <= 'z') {
+ try writer.writeByte(span[0] - 'a' + 'A');
+ if (span.len > 1) try writer.writeAll(span[1..]);
+ } else {
+ try writer.writeAll(span);
+ }
+ }
+ },
+
+ .unicode => |cp| {
+ // Try to get a nice name from GDK first
+ if (gdk.keyvalName(cp)) |name| {
+ const span = std.mem.span(name);
+ if (span.len > 0) {
+ // Capitalize the first letter for nicer display
+ if (span[0] >= 'a' and span[0] <= 'z') {
+ try writer.writeByte(span[0] - 'a' + 'A');
+ if (span.len > 1) try writer.writeAll(span[1..]);
+ } else {
+ try writer.writeAll(span);
+ }
+ }
+ } else {
+ // Fall back to printing the character
+ try writer.print("{u}", .{cp});
+ }
+ },
+
+ .catch_all => return false,
+ }
+
+ return true;
+}
+
test "accelFromTrigger" {
const testing = std.testing;
var buf: [256]u8 = undefined;
@@ -261,6 +327,64 @@ test "xdgShortcutFromTrigger" {
})).?);
}
+test "labelFromTrigger" {
+ const testing = std.testing;
+
+ // Simple unicode key with modifier
+ {
+ var buf: std.Io.Writer.Allocating = .init(testing.allocator);
+ defer buf.deinit();
+ try testing.expect(try labelFromTrigger(&buf.writer, .{
+ .mods = .{ .super = true },
+ .key = .{ .unicode = 'q' },
+ }));
+ try testing.expectEqualStrings("Super+Q", buf.written());
+ }
+
+ // Multiple modifiers
+ {
+ var buf: std.Io.Writer.Allocating = .init(testing.allocator);
+ defer buf.deinit();
+ try testing.expect(try labelFromTrigger(&buf.writer, .{
+ .mods = .{ .ctrl = true, .alt = true, .super = true, .shift = true },
+ .key = .{ .unicode = 92 },
+ }));
+ try testing.expectEqualStrings("Super+Ctrl+Alt+Shift+Backslash", buf.written());
+ }
+
+ // Physical key
+ {
+ var buf: std.Io.Writer.Allocating = .init(testing.allocator);
+ defer buf.deinit();
+ try testing.expect(try labelFromTrigger(&buf.writer, .{
+ .mods = .{ .ctrl = true },
+ .key = .{ .physical = .key_a },
+ }));
+ try testing.expectEqualStrings("Ctrl+A", buf.written());
+ }
+
+ // No modifiers
+ {
+ var buf: std.Io.Writer.Allocating = .init(testing.allocator);
+ defer buf.deinit();
+ try testing.expect(try labelFromTrigger(&buf.writer, .{
+ .mods = .{},
+ .key = .{ .physical = .escape },
+ }));
+ try testing.expectEqualStrings("Escape", buf.written());
+ }
+
+ // catch_all returns false
+ {
+ var buf: std.Io.Writer.Allocating = .init(testing.allocator);
+ defer buf.deinit();
+ try testing.expect(!try labelFromTrigger(&buf.writer, .{
+ .mods = .{},
+ .key = .catch_all,
+ }));
+ }
+}
+
/// A raw entry in the keymap. Our keymap contains mappings between
/// GDK keys and our own key enum.
const RawEntry = struct { c_uint, input.Key };
diff --git a/src/apprt/gtk/ui/1.2/key-state-overlay.blp b/src/apprt/gtk/ui/1.2/key-state-overlay.blp
new file mode 100644
index 000000000..c8654bfbb
--- /dev/null
+++ b/src/apprt/gtk/ui/1.2/key-state-overlay.blp
@@ -0,0 +1,58 @@
+using Gtk 4.0;
+using Adw 1;
+
+template $GhosttyKeyStateOverlay: Adw.Bin {
+ visible: bind $has_state(template.has-tables, template.has-sequence) as ;
+ valign-target: end;
+ halign: center;
+ valign: bind template.valign-target;
+
+ GestureDrag {
+ button: 1;
+ propagation-phase: capture;
+ drag-end => $on_drag_end();
+ }
+
+ Adw.Bin {
+ Box container {
+ styles [
+ "background",
+ "key-state-overlay",
+ ]
+
+ orientation: horizontal;
+ spacing: 6;
+
+ Image {
+ icon-name: "input-keyboard-symbolic";
+ pixel-size: 16;
+ }
+
+ Label tables_label {
+ visible: bind template.has-tables;
+ label: bind $tables_text(template.tables) as ;
+ xalign: 0.0;
+ }
+
+ Label chevron_label {
+ visible: bind $show_chevron(template.has-tables, template.has-sequence) as ;
+ label: "โบ";
+
+ styles [
+ "dim-label",
+ ]
+ }
+
+ Label sequence_label {
+ visible: bind template.has-sequence;
+ label: bind $sequence_text(template.sequence) as ;
+ xalign: 0.0;
+ }
+
+ Spinner pending_spinner {
+ visible: bind template.has-sequence;
+ spinning: bind template.has-sequence;
+ }
+ }
+ }
+}
diff --git a/src/apprt/gtk/ui/1.2/surface.blp b/src/apprt/gtk/ui/1.2/surface.blp
index 4ebfeabfb..a594ba98f 100644
--- a/src/apprt/gtk/ui/1.2/surface.blp
+++ b/src/apprt/gtk/ui/1.2/surface.blp
@@ -155,6 +155,12 @@ Overlay terminal_page {
previous-match => $search_previous_match();
}
+ [overlay]
+ $GhosttyKeyStateOverlay key_state_overlay {
+ tables: bind template.key-table;
+ sequence: bind template.key-sequence;
+ }
+
[overlay]
// Apply unfocused-split-fill and unfocused-split-opacity to current surface
// this is only applied when a tab has more than one surface
diff --git a/src/apprt/surface.zig b/src/apprt/surface.zig
index 45a847493..5c25281c8 100644
--- a/src/apprt/surface.zig
+++ b/src/apprt/surface.zig
@@ -63,11 +63,6 @@ pub const Message = union(enum) {
/// Health status change for the renderer.
renderer_health: renderer.Health,
- /// Report the color scheme. The bool parameter is whether to force or not.
- /// If force is true, the color scheme should be reported even if mode
- /// 2031 is not set.
- report_color_scheme: bool,
-
/// Tell the surface to present itself to the user. This may require raising
/// a window and switching tabs.
present_surface: void,
@@ -159,12 +154,28 @@ pub const Mailbox = struct {
}
};
+/// Context for new surface creation to determine inheritance behavior
+pub const NewSurfaceContext = enum(c_int) {
+ window = 0,
+ tab = 1,
+ split = 2,
+};
+
+pub fn shouldInheritWorkingDirectory(context: NewSurfaceContext, config: *const Config) bool {
+ return switch (context) {
+ .window => config.@"window-inherit-working-directory",
+ .tab => config.@"tab-inherit-working-directory",
+ .split => config.@"split-inherit-working-directory",
+ };
+}
+
/// Returns a new config for a surface for the given app that should be
/// used for any new surfaces. The resulting config should be deinitialized
/// after the surface is initialized.
pub fn newConfig(
app: *const App,
config: *const Config,
+ context: NewSurfaceContext,
) Allocator.Error!Config {
// Create a shallow clone
var copy = config.shallowClone(app.alloc);
@@ -175,7 +186,7 @@ pub fn newConfig(
// Get our previously focused surface for some inherited values.
const prev = app.focusedSurface();
if (prev) |p| {
- if (config.@"window-inherit-working-directory") {
+ if (shouldInheritWorkingDirectory(context, config)) {
if (try p.pwd(alloc)) |pwd| {
copy.@"working-directory" = pwd;
}
diff --git a/src/benchmark/GraphemeBreak.zig b/src/benchmark/GraphemeBreak.zig
index 328d63a75..8278c5c2f 100644
--- a/src/benchmark/GraphemeBreak.zig
+++ b/src/benchmark/GraphemeBreak.zig
@@ -10,6 +10,7 @@ const Benchmark = @import("Benchmark.zig");
const options = @import("options.zig");
const UTF8Decoder = @import("../terminal/UTF8Decoder.zig");
const unicode = @import("../unicode/main.zig");
+const uucode = @import("uucode");
const log = std.log.scoped(.@"terminal-stream-bench");
@@ -118,7 +119,7 @@ fn stepTable(ptr: *anyopaque) Benchmark.Error!void {
var r = &f_reader.interface;
var d: UTF8Decoder = .{};
- var state: unicode.GraphemeBreakState = .{};
+ var state: uucode.grapheme.BreakState = .default;
var cp1: u21 = 0;
var buf: [4096]u8 align(std.atomic.cache_line) = undefined;
while (true) {
diff --git a/src/benchmark/OscParser.zig b/src/benchmark/OscParser.zig
index 6243aba7d..d4b416de8 100644
--- a/src/benchmark/OscParser.zig
+++ b/src/benchmark/OscParser.zig
@@ -100,8 +100,8 @@ fn step(ptr: *anyopaque) Benchmark.Error!void {
error.ReadFailed => return error.BenchmarkFailed,
};
- for (osc_buf[0..len]) |c| self.parser.next(c);
- _ = self.parser.end(std.ascii.control_code.bel);
+ for (osc_buf[0..len]) |c| @call(.always_inline, Parser.next, .{ &self.parser, c });
+ std.mem.doNotOptimizeAway(self.parser.end(std.ascii.control_code.bel));
self.parser.reset();
}
}
diff --git a/src/build/GhosttyZig.zig b/src/build/GhosttyZig.zig
index a8d2726bc..e63120e74 100644
--- a/src/build/GhosttyZig.zig
+++ b/src/build/GhosttyZig.zig
@@ -73,6 +73,9 @@ fn initVt(
// We always need unicode tables
deps.unicode_tables.addModuleImport(vt);
+ // We need uucode for grapheme break support
+ deps.addUucode(b, vt, cfg.target, cfg.optimize);
+
// If SIMD is enabled, add all our SIMD dependencies.
if (cfg.simd) {
try SharedDeps.addSimd(b, vt, null);
diff --git a/src/build/SharedDeps.zig b/src/build/SharedDeps.zig
index 5e2cd40b9..0ca43e78d 100644
--- a/src/build/SharedDeps.zig
+++ b/src/build/SharedDeps.zig
@@ -135,29 +135,28 @@ pub fn add(
// Every exe needs the terminal options
self.config.terminalOptions().add(b, step.root_module);
- // Freetype
+ // Freetype. We always include this even if our font backend doesn't
+ // use it because Dear Imgui uses Freetype.
_ = b.systemIntegrationOption("freetype", .{}); // Shows it in help
- if (self.config.font_backend.hasFreetype()) {
- if (b.lazyDependency("freetype", .{
- .target = target,
- .optimize = optimize,
- .@"enable-libpng" = true,
- })) |freetype_dep| {
- step.root_module.addImport(
- "freetype",
- freetype_dep.module("freetype"),
- );
+ if (b.lazyDependency("freetype", .{
+ .target = target,
+ .optimize = optimize,
+ .@"enable-libpng" = true,
+ })) |freetype_dep| {
+ step.root_module.addImport(
+ "freetype",
+ freetype_dep.module("freetype"),
+ );
- if (b.systemIntegrationOption("freetype", .{})) {
- step.linkSystemLibrary2("bzip2", dynamic_link_opts);
- step.linkSystemLibrary2("freetype2", dynamic_link_opts);
- } else {
- step.linkLibrary(freetype_dep.artifact("freetype"));
- try static_libs.append(
- b.allocator,
- freetype_dep.artifact("freetype").getEmittedBin(),
- );
- }
+ if (b.systemIntegrationOption("freetype", .{})) {
+ step.linkSystemLibrary2("bzip2", dynamic_link_opts);
+ step.linkSystemLibrary2("freetype2", dynamic_link_opts);
+ } else {
+ step.linkLibrary(freetype_dep.artifact("freetype"));
+ try static_libs.append(
+ b.allocator,
+ freetype_dep.artifact("freetype").getEmittedBin(),
+ );
}
}
@@ -413,14 +412,7 @@ pub fn add(
})) |dep| {
step.root_module.addImport("z2d", dep.module("z2d"));
}
- if (b.lazyDependency("uucode", .{
- .target = target,
- .optimize = optimize,
- .tables_path = self.uucode_tables,
- .build_config_path = b.path("src/build/uucode_config.zig"),
- })) |dep| {
- step.root_module.addImport("uucode", dep.module("uucode"));
- }
+ self.addUucode(b, step.root_module, target, optimize);
if (b.lazyDependency("zf", .{
.target = target,
.optimize = optimize,
@@ -479,15 +471,19 @@ pub fn add(
}
// cimgui
- if (b.lazyDependency("cimgui", .{
+ if (b.lazyDependency("dcimgui", .{
.target = target,
.optimize = optimize,
- })) |cimgui_dep| {
- step.root_module.addImport("cimgui", cimgui_dep.module("cimgui"));
- step.linkLibrary(cimgui_dep.artifact("cimgui"));
+ .freetype = true,
+ .@"backend-metal" = target.result.os.tag.isDarwin(),
+ .@"backend-osx" = target.result.os.tag == .macos,
+ .@"backend-opengl3" = target.result.os.tag != .macos,
+ })) |dep| {
+ step.root_module.addImport("dcimgui", dep.module("dcimgui"));
+ step.linkLibrary(dep.artifact("dcimgui"));
try static_libs.append(
b.allocator,
- cimgui_dep.artifact("cimgui").getEmittedBin(),
+ dep.artifact("dcimgui").getEmittedBin(),
);
}
@@ -875,6 +871,23 @@ pub fn gtkNgDistResources(
};
}
+pub fn addUucode(
+ self: *const SharedDeps,
+ b: *std.Build,
+ module: *std.Build.Module,
+ target: std.Build.ResolvedTarget,
+ optimize: std.builtin.OptimizeMode,
+) void {
+ if (b.lazyDependency("uucode", .{
+ .target = target,
+ .optimize = optimize,
+ .tables_path = self.uucode_tables,
+ .build_config_path = b.path("src/build/uucode_config.zig"),
+ })) |dep| {
+ module.addImport("uucode", dep.module("uucode"));
+ }
+}
+
// For dynamic linking, we prefer dynamic linking and to search by
// mode first. Mode first will search all paths for a dynamic library
// before falling back to static.
diff --git a/src/build/uucode_config.zig b/src/build/uucode_config.zig
index 2fadbdb78..594a05366 100644
--- a/src/build/uucode_config.zig
+++ b/src/build/uucode_config.zig
@@ -4,6 +4,7 @@ const config = @import("config.zig");
const config_x = @import("config.x.zig");
const d = config.default;
const wcwidth = config_x.wcwidth;
+const grapheme_break_no_control = config_x.grapheme_break_no_control;
const Allocator = std.mem.Allocator;
@@ -85,10 +86,15 @@ pub const tables = [_]config.Table{
},
.{
.name = "buildtime",
- .extensions = &.{ wcwidth, width, is_symbol },
+ .extensions = &.{
+ wcwidth,
+ grapheme_break_no_control,
+ width,
+ is_symbol,
+ },
.fields = &.{
width.field("width"),
- d.field("grapheme_break"),
+ grapheme_break_no_control.field("grapheme_break_no_control"),
is_symbol.field("is_symbol"),
d.field("is_emoji_vs_base"),
},
diff --git a/src/cli/list_keybinds.zig b/src/cli/list_keybinds.zig
index a8899a4f5..61050d0cb 100644
--- a/src/cli/list_keybinds.zig
+++ b/src/cli/list_keybinds.zig
@@ -95,18 +95,35 @@ const TriggerNode = struct {
};
const ChordBinding = struct {
+ table_name: ?[]const u8 = null,
triggers: std.SinglyLinkedList,
- action: Binding.Action,
+ actions: []const Binding.Action,
// Order keybinds based on various properties
- // 1. Longest chord sequence
- // 2. Most active modifiers
- // 3. Alphabetically by active modifiers
- // 4. Trigger key order
+ // 1. Default bindings before table bindings (tables grouped at end)
+ // 2. Longest chord sequence
+ // 3. Most active modifiers
+ // 4. Alphabetically by active modifiers
+ // 5. Trigger key order
+ // 6. Within tables, sort by table name
// These properties propagate through chorded keypresses
//
// Adapted from Binding.lessThan
pub fn lessThan(_: void, lhs: ChordBinding, rhs: ChordBinding) bool {
+ const lhs_has_table = lhs.table_name != null;
+ const rhs_has_table = rhs.table_name != null;
+
+ if (lhs_has_table != rhs_has_table) {
+ return !lhs_has_table;
+ }
+
+ if (lhs_has_table) {
+ const table_cmp = std.mem.order(u8, lhs.table_name.?, rhs.table_name.?);
+ if (table_cmp != .eq) {
+ return table_cmp == .lt;
+ }
+ }
+
const lhs_len = lhs.triggers.len();
const rhs_len = rhs.triggers.len();
@@ -166,16 +183,19 @@ const ChordBinding = struct {
var r_trigger = rhs.triggers.first;
while (l_trigger != null and r_trigger != null) {
+ // We want catch_all to sort last.
const lhs_key: c_int = blk: {
switch (TriggerNode.get(l_trigger.?).data.key) {
.physical => |key| break :blk @intFromEnum(key),
.unicode => |key| break :blk @intCast(key),
+ .catch_all => break :blk std.math.maxInt(c_int),
}
};
const rhs_key: c_int = blk: {
switch (TriggerNode.get(r_trigger.?).data.key) {
.physical => |key| break :blk @intFromEnum(key),
.unicode => |key| break :blk @intCast(key),
+ .catch_all => break :blk std.math.maxInt(c_int),
}
};
@@ -228,10 +248,30 @@ fn prettyPrint(alloc: Allocator, keybinds: Config.Keybinds) !u8 {
const win = vx.window();
- // Generate a list of bindings, recursively traversing chorded keybindings
+ // Collect default bindings, recursively flattening chords
var iter = keybinds.set.bindings.iterator();
- const bindings, const widest_chord = try iterateBindings(alloc, &iter, &win);
+ const default_bindings, var widest_chord = try iterateBindings(alloc, &iter, &win);
+ var bindings_list: std.ArrayList(ChordBinding) = .empty;
+ try bindings_list.appendSlice(alloc, default_bindings);
+
+ // Collect key table bindings
+ var widest_table_prefix: u16 = 0;
+ var table_iter = keybinds.tables.iterator();
+ while (table_iter.next()) |table_entry| {
+ const table_name = table_entry.key_ptr.*;
+ var binding_iter = table_entry.value_ptr.bindings.iterator();
+ const table_bindings, const table_width = try iterateBindings(alloc, &binding_iter, &win);
+ for (table_bindings) |*b| {
+ b.table_name = table_name;
+ }
+
+ try bindings_list.appendSlice(alloc, table_bindings);
+ widest_chord = @max(widest_chord, table_width);
+ widest_table_prefix = @max(widest_table_prefix, @as(u16, @intCast(win.gwidth(table_name) + win.gwidth("/"))));
+ }
+
+ const bindings = bindings_list.items;
std.mem.sort(ChordBinding, bindings, {}, ChordBinding.lessThan);
// Set up styles for each modifier
@@ -239,12 +279,22 @@ fn prettyPrint(alloc: Allocator, keybinds: Config.Keybinds) !u8 {
const ctrl_style: vaxis.Style = .{ .fg = .{ .index = 2 } };
const alt_style: vaxis.Style = .{ .fg = .{ .index = 3 } };
const shift_style: vaxis.Style = .{ .fg = .{ .index = 4 } };
+ const table_style: vaxis.Style = .{ .fg = .{ .index = 8 } };
// Print the list
for (bindings) |bind| {
win.clear();
var result: vaxis.Window.PrintResult = .{ .col = 0, .row = 0, .overflow = false };
+
+ if (bind.table_name) |name| {
+ result = win.printSegment(
+ .{ .text = name, .style = table_style },
+ .{ .col_offset = result.col },
+ );
+ result = win.printSegment(.{ .text = "/", .style = table_style }, .{ .col_offset = result.col });
+ }
+
var maybe_trigger = bind.triggers.first;
while (maybe_trigger) |node| : (maybe_trigger = node.next) {
const trigger: *TriggerNode = .get(node);
@@ -268,6 +318,7 @@ fn prettyPrint(alloc: Allocator, keybinds: Config.Keybinds) !u8 {
const key = switch (trigger.data.key) {
.physical => |k| try std.fmt.allocPrint(alloc, "{t}", .{k}),
.unicode => |c| try std.fmt.allocPrint(alloc, "{u}", .{c}),
+ .catch_all => "catch_all",
};
result = win.printSegment(.{ .text = key }, .{ .col_offset = result.col });
@@ -277,16 +328,32 @@ fn prettyPrint(alloc: Allocator, keybinds: Config.Keybinds) !u8 {
}
}
- const action = try std.fmt.allocPrint(alloc, "{f}", .{bind.action});
- // If our action has an argument, we print the argument in a different color
- if (std.mem.indexOfScalar(u8, action, ':')) |idx| {
- _ = win.print(&.{
- .{ .text = action[0..idx] },
- .{ .text = action[idx .. idx + 1], .style = .{ .dim = true } },
- .{ .text = action[idx + 1 ..], .style = .{ .fg = .{ .index = 5 } } },
- }, .{ .col_offset = widest_chord + 3 });
- } else {
- _ = win.printSegment(.{ .text = action }, .{ .col_offset = widest_chord + 3 });
+ var action_col: u16 = widest_table_prefix + widest_chord + 3;
+ for (bind.actions, 0..) |act, i| {
+ if (i > 0) {
+ const chain_result = win.printSegment(
+ .{ .text = ", ", .style = .{ .dim = true } },
+ .{ .col_offset = action_col },
+ );
+ action_col = chain_result.col;
+ }
+
+ const action = try std.fmt.allocPrint(alloc, "{f}", .{act});
+ // If our action has an argument, we print the argument in a different color
+ if (std.mem.indexOfScalar(u8, action, ':')) |idx| {
+ const print_result = win.print(&.{
+ .{ .text = action[0..idx] },
+ .{ .text = action[idx .. idx + 1], .style = .{ .dim = true } },
+ .{ .text = action[idx + 1 ..], .style = .{ .fg = .{ .index = 5 } } },
+ }, .{ .col_offset = action_col });
+ action_col = print_result.col;
+ } else {
+ const print_result = win.printSegment(
+ .{ .text = action },
+ .{ .col_offset = action_col },
+ );
+ action_col = print_result.col;
+ }
}
try vx.prettyPrint(writer);
}
@@ -314,6 +381,7 @@ fn iterateBindings(
switch (t.key) {
.physical => |k| try buf.writer.print("{t}", .{k}),
.unicode => |c| try buf.writer.print("{u}", .{c}),
+ .catch_all => try buf.writer.print("catch_all", .{}),
}
break :blk win.gwidth(buf.written());
@@ -321,7 +389,6 @@ fn iterateBindings(
switch (bind.value_ptr.*) {
.leader => |leader| {
-
// Recursively iterate on the set of bindings for this leader key
var n_iter = leader.bindings.iterator();
const sub_bindings, const max_width = try iterateBindings(alloc, &n_iter, win);
@@ -342,10 +409,23 @@ fn iterateBindings(
const node = try alloc.create(TriggerNode);
node.* = .{ .data = bind.key_ptr.* };
+ const actions = try alloc.alloc(Binding.Action, 1);
+ actions[0] = leaf.action;
+
widest_chord = @max(widest_chord, width);
try bindings.append(alloc, .{
.triggers = .{ .first = &node.node },
- .action = leaf.action,
+ .actions = actions,
+ });
+ },
+ .leaf_chained => |leaf| {
+ const node = try alloc.create(TriggerNode);
+ node.* = .{ .data = bind.key_ptr.* };
+
+ widest_chord = @max(widest_chord, width);
+ try bindings.append(alloc, .{
+ .triggers = .{ .first = &node.node },
+ .actions = leaf.actions.items,
});
},
}
diff --git a/src/cli/ssh-cache/DiskCache.zig b/src/cli/ssh-cache/DiskCache.zig
index 62620ecb0..6214d0429 100644
--- a/src/cli/ssh-cache/DiskCache.zig
+++ b/src/cli/ssh-cache/DiskCache.zig
@@ -9,7 +9,6 @@ const assert = @import("../../quirks.zig").inlineAssert;
const Allocator = std.mem.Allocator;
const internal_os = @import("../../os/main.zig");
const xdg = internal_os.xdg;
-const TempDir = internal_os.TempDir;
const Entry = @import("Entry.zig");
// 512KB - sufficient for approximately 10k entries
@@ -125,7 +124,7 @@ pub fn add(
break :update .updated;
};
- try self.writeCacheFile(alloc, entries, null);
+ try self.writeCacheFile(entries, null);
return result;
}
@@ -166,7 +165,7 @@ pub fn remove(
alloc.free(kv.value.terminfo_version);
}
- try self.writeCacheFile(alloc, entries, null);
+ try self.writeCacheFile(entries, null);
}
/// Check if a hostname exists in the cache.
@@ -209,32 +208,30 @@ fn fixupPermissions(file: std.fs.File) (std.fs.File.StatError || std.fs.File.Chm
fn writeCacheFile(
self: DiskCache,
- alloc: Allocator,
entries: std.StringHashMap(Entry),
expire_days: ?u32,
) !void {
- var td: TempDir = try .init();
- defer td.deinit();
+ const cache_dir = std.fs.path.dirname(self.path) orelse return error.InvalidCachePath;
+ const cache_basename = std.fs.path.basename(self.path);
- const tmp_file = try td.dir.createFile("ssh-cache", .{ .mode = 0o600 });
- defer tmp_file.close();
- const tmp_path = try td.dir.realpathAlloc(alloc, "ssh-cache");
- defer alloc.free(tmp_path);
+ var dir = try std.fs.cwd().openDir(cache_dir, .{});
+ defer dir.close();
var buf: [1024]u8 = undefined;
- var writer = tmp_file.writer(&buf);
+ var atomic_file = try dir.atomicFile(cache_basename, .{
+ .mode = 0o600,
+ .write_buffer = &buf,
+ });
+ defer atomic_file.deinit();
+
var iter = entries.iterator();
while (iter.next()) |kv| {
// Only write non-expired entries
if (kv.value_ptr.isExpired(expire_days)) continue;
- try kv.value_ptr.format(&writer.interface);
+ try kv.value_ptr.format(&atomic_file.file_writer.interface);
}
- // Don't forget to flush!!
- try writer.interface.flush();
-
- // Atomic replace
- try std.fs.renameAbsolute(tmp_path, self.path);
+ try atomic_file.finish();
}
/// List all entries in the cache.
@@ -382,16 +379,16 @@ test "disk cache clear" {
const alloc = testing.allocator;
// Create our path
- var td: TempDir = try .init();
- defer td.deinit();
+ var tmp = testing.tmpDir(.{});
+ defer tmp.cleanup();
var buf: [4096]u8 = undefined;
{
- var file = try td.dir.createFile("cache", .{});
+ var file = try tmp.dir.createFile("cache", .{});
defer file.close();
var file_writer = file.writer(&buf);
try file_writer.interface.writeAll("HELLO!");
}
- const path = try td.dir.realpathAlloc(alloc, "cache");
+ const path = try tmp.dir.realpathAlloc(alloc, "cache");
defer alloc.free(path);
// Setup our cache
@@ -401,7 +398,7 @@ test "disk cache clear" {
// Verify the file is gone
try testing.expectError(
error.FileNotFound,
- td.dir.openFile("cache", .{}),
+ tmp.dir.openFile("cache", .{}),
);
}
@@ -410,18 +407,18 @@ test "disk cache operations" {
const alloc = testing.allocator;
// Create our path
- var td: TempDir = try .init();
- defer td.deinit();
+ var tmp = testing.tmpDir(.{});
+ defer tmp.cleanup();
var buf: [4096]u8 = undefined;
{
- var file = try td.dir.createFile("cache", .{});
+ var file = try tmp.dir.createFile("cache", .{});
defer file.close();
var file_writer = file.writer(&buf);
const writer = &file_writer.interface;
try writer.writeAll("HELLO!");
try writer.flush();
}
- const path = try td.dir.realpathAlloc(alloc, "cache");
+ const path = try tmp.dir.realpathAlloc(alloc, "cache");
defer alloc.free(path);
// Setup our cache
@@ -453,6 +450,32 @@ test "disk cache operations" {
);
}
+test "disk cache cleans up temp files" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var tmp = testing.tmpDir(.{ .iterate = true });
+ defer tmp.cleanup();
+
+ const tmp_path = try tmp.dir.realpathAlloc(alloc, ".");
+ defer alloc.free(tmp_path);
+ const cache_path = try std.fs.path.join(alloc, &.{ tmp_path, "cache" });
+ defer alloc.free(cache_path);
+
+ const cache: DiskCache = .{ .path = cache_path };
+ try testing.expectEqual(AddResult.added, try cache.add(alloc, "example.com"));
+ try testing.expectEqual(AddResult.added, try cache.add(alloc, "example.org"));
+
+ // Verify only the cache file exists and no temp files left behind
+ var count: usize = 0;
+ var iter = tmp.dir.iterate();
+ while (try iter.next()) |entry| {
+ count += 1;
+ try testing.expectEqualStrings("cache", entry.name);
+ }
+ try testing.expectEqual(1, count);
+}
+
test isValidHost {
const testing = std.testing;
diff --git a/src/config/CApi.zig b/src/config/CApi.zig
index a970a8d33..4ea9ea63f 100644
--- a/src/config/CApi.zig
+++ b/src/config/CApi.zig
@@ -65,6 +65,15 @@ export fn ghostty_config_load_default_files(self: *Config) void {
};
}
+/// Load the configuration from a specific file path.
+/// The path must be null-terminated.
+export fn ghostty_config_load_file(self: *Config, path: [*:0]const u8) void {
+ const path_slice = std.mem.span(path);
+ self.loadFile(state.alloc, path_slice) catch |err| {
+ log.err("error loading config from file path={s} err={}", .{ path_slice, err });
+ };
+}
+
/// Load the configuration from the user-specified configuration
/// file locations in the previously loaded configuration. This will
/// recursively continue to load up to a built-in limit.
diff --git a/src/config/Config.zig b/src/config/Config.zig
index 1aad62d7d..8ca64efe9 100644
--- a/src/config/Config.zig
+++ b/src/config/Config.zig
@@ -17,6 +17,7 @@ const assert = @import("../quirks.zig").inlineAssert;
const Allocator = std.mem.Allocator;
const ArenaAllocator = std.heap.ArenaAllocator;
const global_state = &@import("../global.zig").state;
+const deepEqual = @import("../datastruct/comparison.zig").deepEqual;
const fontpkg = @import("../font/main.zig");
const inputpkg = @import("../input.zig");
const internal_os = @import("../os/main.zig");
@@ -37,6 +38,7 @@ const RepeatableStringMap = @import("RepeatableStringMap.zig");
pub const Path = @import("path.zig").Path;
pub const RepeatablePath = @import("path.zig").RepeatablePath;
const ClipboardCodepointMap = @import("ClipboardCodepointMap.zig");
+const KeyRemapSet = @import("../input/key_mods.zig").RemapSet;
// We do this instead of importing all of terminal/main.zig to
// limit the dependency graph. This is important because some things
@@ -710,6 +712,32 @@ foreground: Color = .{ .r = 0xFF, .g = 0xFF, .b = 0xFF },
/// on the same selection.
@"selection-clear-on-copy": bool = false,
+/// Characters that mark word boundaries during text selection operations such
+/// as double-clicking. When selecting a word, the selection will stop at any
+/// of these characters.
+///
+/// This is similar to the `WORDCHARS` environment variable in zsh, except this
+/// specifies the boundary characters rather than the word characters. The
+/// default includes common delimiters and punctuation that typically separate
+/// words in code and prose.
+///
+/// Each character in this string becomes a word boundary. Multi-byte UTF-8
+/// characters are supported, but only single codepoints can be specified.
+/// Multi-codepoint sequences (e.g. emoji) are not supported.
+///
+/// The null character (U+0000) is always treated as a boundary and does not
+/// need to be included in this configuration.
+///
+/// Default: ` \t'"โ`|:;,()[]{}<>$`
+///
+/// To add or remove specific characters, you can set this to a custom value.
+/// For example, to treat semicolons as part of words:
+///
+/// selection-word-chars = " \t'\"โ`|:,()[]{}<>$"
+///
+/// Available since: 1.3.0
+@"selection-word-chars": SelectionWordChars = .{},
+
/// The minimum contrast ratio between the foreground and background colors.
/// The contrast ratio is a value between 1 and 21. A value of 1 allows for no
/// contrast (e.g. black on black). This value is the contrast ratio as defined
@@ -1477,6 +1505,13 @@ class: ?[:0]const u8 = null,
/// so if you specify both `a` and `KeyA`, the physical key will always be used
/// regardless of what order they are configured.
///
+/// The special key `catch_all` can be used to match any key that is not
+/// otherwise bound. This can be combined with modifiers, for example
+/// `ctrl+catch_all` will match any key pressed with `ctrl` that is not
+/// otherwise bound. When looking up a binding, Ghostty first tries to match
+/// `catch_all` with modifiers. If no match is found and the event has
+/// modifiers, it falls back to `catch_all` without modifiers.
+///
/// Valid modifiers are `shift`, `ctrl` (alias: `control`), `alt` (alias: `opt`,
/// `option`), and `super` (alias: `cmd`, `command`). You may use the modifier
/// or the alias. When debugging keybinds, the non-aliased modifier will always
@@ -1514,6 +1549,11 @@ class: ?[:0]const u8 = null,
/// specifically output that key (e.g. `ctrl+a>ctrl+a=text:foo`) or
/// press an unbound key which will send both keys to the program.
///
+/// * If an unbound key is pressed during a sequence and a `catch_all`
+/// binding exists that would `ignore` the input, the entire sequence
+/// is dropped and nothing happens. Otherwise, the entire sequence is
+/// encoded and sent to the running program as if no keybind existed.
+///
/// * If a prefix in a sequence is previously bound, the sequence will
/// override the previous binding. For example, if `ctrl+a` is bound to
/// `new_window` and `ctrl+a>n` is bound to `new_tab`, pressing `ctrl+a`
@@ -1659,8 +1699,138 @@ class: ?[:0]const u8 = null,
///
/// - Notably, global shortcuts have not been implemented on wlroots-based
/// compositors like Sway (see [upstream issue](https://github.com/emersion/xdg-desktop-portal-wlr/issues/240)).
+///
+/// ## Chained Actions
+///
+/// A keybind can have multiple actions by using the `chain` keyword for
+/// subsequent actions. When a keybind is activated, all chained actions are
+/// executed in order. The syntax is:
+///
+/// ```ini
+/// keybind = ctrl+a=new_window
+/// keybind = chain=goto_split:left
+/// ```
+///
+/// This binds `ctrl+a` to first open a new window, then move focus to the
+/// left split. Each `chain` entry appends an action to the most recently
+/// defined keybind. You can chain as many actions as you want:
+///
+/// ```ini
+/// keybind = ctrl+a=new_window
+/// keybind = chain=goto_split:left
+/// keybind = chain=toggle_fullscreen
+/// ```
+///
+/// Chained actions cannot have prefixes like `global:` or `unconsumed:`.
+/// The flags from the original keybind apply to the entire chain.
+///
+/// Chained actions work with key sequences as well. For example:
+///
+/// ```ini
+/// keybind = ctrl+a>n=new_window
+/// keybind = chain=goto_split:left
+/// ````
+///
+/// Chains with key sequences apply to the most recent binding in the
+/// sequence.
+///
+/// Chained keybinds are available since Ghostty 1.3.0.
+///
+/// ## Key Tables
+///
+/// You may also create a named set of keybindings known as a "key table."
+/// A key table must be explicitly activated for the bindings to become
+/// available. This can be used to implement features such as a
+/// "copy mode", "vim mode", etc. Generically, this can implement modal
+/// keyboard input.
+///
+/// Key tables are defined using the syntax `/`. The
+/// `` value is everything documented above for keybinds. The
+/// `` value is the name of the key table. Table names can contain
+/// anything except `/`, `=`, `+`, and `>`. The characters `+` and `>` are
+/// reserved for keybind syntax (modifier combinations and key sequences).
+/// For example `foo/ctrl+a=new_window` defines a binding within a table
+/// named `foo`.
+///
+/// Tables are activated and deactivated using the binding actions
+/// `activate_key_table:` and `deactivate_key_table`. Other table
+/// related binding actions also exist; see the documentation for a full list.
+/// These are the primary way to interact with key tables.
+///
+/// Binding lookup proceeds from the innermost table outward, so keybinds in
+/// the default table remain available unless explicitly unbound in an inner
+/// table.
+///
+/// A key table has some special syntax and handling:
+///
+/// * `/` (with no binding) defines and clears a table, resetting all
+/// of its keybinds and settings.
+///
+/// * You cannot activate a table that is already the innermost table; such
+/// attempts are ignored. However, the same table can appear multiple times
+/// in the stack as long as it is not innermost (e.g., `A -> B -> A -> B`
+/// is valid, but `A -> B -> B` is not).
+///
+/// * A table can be activated in one-shot mode using
+/// `activate_key_table_once:`. A one-shot table is automatically
+/// deactivated when any non-catch-all binding is invoked.
+///
+/// * Key sequences work within tables: `foo/ctrl+a>ctrl+b=new_window`.
+/// If an invalid key is pressed, the sequence ends but the table remains
+/// active.
+///
+/// * Prefixes like `global:` work within tables:
+/// `foo/global:ctrl+a=new_window`.
+///
+/// Key tables are available since Ghostty 1.3.0.
keybind: Keybinds = .{},
+/// Remap modifier keys within Ghostty. This allows you to swap or reassign
+/// modifier keys at the application level without affecting system-wide
+/// settings.
+///
+/// The format is `from=to` where both `from` and `to` are modifier key names.
+/// You can use generic names like `ctrl`, `alt`, `shift`, `super` (macOS:
+/// `cmd`/`command`) or sided names like `left_ctrl`, `right_alt`, etc.
+///
+/// This will NOT change keyboard layout or key encodings outside of Ghostty.
+/// For example, on macOS, `option+a` may still produce `รฅ` even if `option` is
+/// remapped to `ctrl`. Desktop environments usually handle key layout long
+/// before Ghostty receives the key events.
+///
+/// Example:
+///
+/// key-remap = ctrl=super
+/// key-remap = left_control=right_alt
+///
+/// Important notes:
+///
+/// * This is a one-way remap. If you remap `ctrl=super`, then the physical
+/// Ctrl key acts as Super, but the Super key remains Super.
+///
+/// * Remaps are not transitive. If you remap `ctrl=super` and `alt=ctrl`,
+/// pressing Alt will produce Ctrl, NOT Super.
+///
+/// * This affects both keybind matching and terminal input encoding.
+/// This does NOT impact keyboard layout or how keys are interpreted
+/// prior to Ghostty receiving them. For example, `option+a` on macOS
+/// may still produce `รฅ` even if `option` is remapped to `ctrl`.
+///
+/// * Generic modifiers (e.g. `ctrl`) match both left and right physical keys.
+/// Use sided names (e.g. `left_ctrl`) to remap only one side.
+///
+/// There are other edge case scenarios that may not behave as expected
+/// but are working as intended the way this feature is designed:
+///
+/// * On macOS, bindings in the main menu will trigger before any remapping
+/// is done. This is because macOS itself handles menu activation and
+/// this happens before Ghostty receives the key event. To workaround
+/// this, you should unbind the menu items and rebind them using your
+/// desired modifier.
+///
+/// This configuration can be repeated to specify multiple remaps.
+@"key-remap": KeyRemapSet = .empty,
+
/// Horizontal window padding. This applies padding between the terminal cells
/// and the left and right window borders. The value is in points, meaning that
/// it will be scaled appropriately for screen DPI.
@@ -1749,11 +1919,21 @@ keybind: Keybinds = .{},
/// This setting is only supported currently on macOS.
@"window-vsync": bool = true,
-/// If true, new windows and tabs will inherit the working directory of the
+/// If true, new windows will inherit the working directory of the
/// previously focused window. If no window was previously focused, the default
/// working directory will be used (the `working-directory` option).
@"window-inherit-working-directory": bool = true,
+/// If true, new tabs will inherit the working directory of the
+/// previously focused tab. If no tab was previously focused, the default
+/// working directory will be used (the `working-directory` option).
+@"tab-inherit-working-directory": bool = true,
+
+/// If true, new split panes will inherit the working directory of the
+/// previously focused split. If no split was previously focused, the default
+/// working directory will be used (the `working-directory` option).
+@"split-inherit-working-directory": bool = true,
+
/// If true, new windows and tabs will inherit the font size of the previously
/// focused window. If no window was previously focused, the default font size
/// will be used. If this is false, the default font size specified in the
@@ -2513,7 +2693,7 @@ keybind: Keybinds = .{},
///
/// * `detect` - Detect the shell based on the filename.
///
-/// * `bash`, `elvish`, `fish`, `zsh` - Use this specific shell injection scheme.
+/// * `bash`, `elvish`, `fish`, `nushell`, `zsh` - Use this specific shell injection scheme.
///
/// The default value is `detect`.
@"shell-integration": ShellIntegration = .detect,
@@ -2687,6 +2867,40 @@ keybind: Keybinds = .{},
/// the same time as the `iTime` uniform, allowing you to compute the
/// time since the change by subtracting this from `iTime`.
///
+/// * `float iTimeFocus` - Timestamp when the surface last gained iFocus.
+///
+/// When the surface gains focus, this is set to the current value of
+/// `iTime`, similar to how `iTimeCursorChange` works. This allows you
+/// to compute the time since focus was gained or lost by calculating
+/// `iTime - iTimeFocus`. Use this to create animations that restart
+/// when the terminal regains focus.
+///
+/// * `int iFocus` - Current focus state of the surface.
+///
+/// Set to 1.0 when the surface is focused, 0.0 when unfocused. This
+/// allows shaders to detect unfocused state and avoid animation artifacts
+/// from large time deltas caused by infrequent "deceptive frames"
+/// (e.g., modifier key presses, link hover events in unfocused split panes).
+/// Check `iFocus > 0` to determine if the surface is currently focused.
+///
+/// * `vec3 iPalette[256]` - The 256-color terminal palette.
+///
+/// RGB values for all 256 colors in the terminal palette, normalized
+/// to [0.0, 1.0]. Index 0-15 are the ANSI colors, 16-231 are the 6x6x6
+/// color cube, and 232-255 are the grayscale colors.
+///
+/// * `vec3 iBackgroundColor` - Terminal background color (RGB).
+///
+/// * `vec3 iForegroundColor` - Terminal foreground color (RGB).
+///
+/// * `vec3 iCursorColor` - Terminal cursor color (RGB).
+///
+/// * `vec3 iCursorText` - Terminal cursor text color (RGB).
+///
+/// * `vec3 iSelectionBackgroundColor` - Selection background color (RGB).
+///
+/// * `vec3 iSelectionForegroundColor` - Selection foreground color (RGB).
+///
/// If the shader fails to compile, the shader will be ignored. Any errors
/// related to shader compilation will not show up as configuration errors
/// and only show up in the log, since shader compilation happens after
@@ -2765,7 +2979,7 @@ keybind: Keybinds = .{},
/// Display a border around the alerted surface until the terminal is
/// re-focused or interacted with (such as on keyboard input).
///
-/// GTK only.
+/// Available since: 1.2.0 on GTK, 1.2.1 on macOS
///
/// Example: `audio`, `no-audio`, `system`, `no-system`
///
@@ -3264,7 +3478,7 @@ else
/// more subtle border.
@"gtk-toolbar-style": GtkToolbarStyle = .raised,
-/// The style of the GTK titlbar. Available values are `native` and `tabs`.
+/// The style of the GTK titlebar. Available values are `native` and `tabs`.
///
/// The `native` titlebar style is a traditional titlebar with a title, a few
/// buttons and window controls. A separate tab bar will show up below the
@@ -3951,7 +4165,7 @@ pub fn changeConditionalState(
// Conditional set contains the keys that this config uses. So we
// only continue if we use this key.
- if (self._conditional_set.contains(key) and !equalField(
+ if (self._conditional_set.contains(key) and !deepEqual(
@TypeOf(@field(self._conditional_state, field.name)),
@field(self._conditional_state, field.name),
@field(new, field.name),
@@ -4314,6 +4528,9 @@ pub fn finalize(self: *Config) !void {
}
self.@"faint-opacity" = std.math.clamp(self.@"faint-opacity", 0.0, 1.0);
+
+ // Finalize key remapping set for efficient lookups
+ self.@"key-remap".finalize();
}
/// Callback for src/cli/args.zig to allow us to handle special cases
@@ -4654,7 +4871,7 @@ pub fn changed(self: *const Config, new: *const Config, comptime key: Key) bool
const old_value = @field(self, field.name);
const new_value = @field(new, field.name);
- return !equalField(field.type, old_value, new_value);
+ return !deepEqual(field.type, old_value, new_value);
}
/// This yields a key for every changed field between old and new.
@@ -4682,91 +4899,6 @@ pub const ChangeIterator = struct {
}
};
-/// A config-specific helper to determine if two values of the same
-/// type are equal. This isn't the same as std.mem.eql or std.testing.equals
-/// because we expect structs to implement their own equality.
-///
-/// This also doesn't support ALL Zig types, because we only add to it
-/// as we need types for the config.
-fn equalField(comptime T: type, old: T, new: T) bool {
- // Do known named types first
- switch (T) {
- inline []const u8,
- [:0]const u8,
- => return std.mem.eql(u8, old, new),
-
- []const [:0]const u8,
- => {
- if (old.len != new.len) return false;
- for (old, new) |a, b| {
- if (!std.mem.eql(u8, a, b)) return false;
- }
-
- return true;
- },
-
- else => {},
- }
-
- // Back into types of types
- switch (@typeInfo(T)) {
- .void => return true,
-
- inline .bool,
- .int,
- .float,
- .@"enum",
- => return old == new,
-
- .optional => |info| {
- if (old == null and new == null) return true;
- if (old == null or new == null) return false;
- return equalField(info.child, old.?, new.?);
- },
-
- .@"struct" => |info| {
- if (@hasDecl(T, "equal")) return old.equal(new);
-
- // If a struct doesn't declare an "equal" function, we fall back
- // to a recursive field-by-field compare.
- inline for (info.fields) |field_info| {
- if (!equalField(
- field_info.type,
- @field(old, field_info.name),
- @field(new, field_info.name),
- )) return false;
- }
- return true;
- },
-
- .@"union" => |info| {
- if (@hasDecl(T, "equal")) return old.equal(new);
-
- const tag_type = info.tag_type.?;
- const old_tag = std.meta.activeTag(old);
- const new_tag = std.meta.activeTag(new);
- if (old_tag != new_tag) return false;
-
- inline for (info.fields) |field_info| {
- if (@field(tag_type, field_info.name) == old_tag) {
- return equalField(
- field_info.type,
- @field(old, field_info.name),
- @field(new, field_info.name),
- );
- }
- }
-
- unreachable;
- },
-
- else => {
- @compileLog(T);
- @compileError("unsupported field type");
- },
- }
-}
-
/// This runs a heuristic to determine if we are likely running
/// Ghostty in a CLI environment. We need this to change some behaviors.
/// We should keep the set of behaviors that depend on this as small
@@ -5675,6 +5807,113 @@ pub const RepeatableString = struct {
}
};
+/// SelectionWordChars stores the parsed codepoints for word boundary
+/// characters used during text selection. The string is parsed once
+/// during configuration and stored as u21 codepoints for efficient
+/// lookup during selection operations.
+pub const SelectionWordChars = struct {
+ const Self = @This();
+
+ /// Default boundary characters: ` \t'"โ`|:;,()[]{}<>$`
+ const default_codepoints = [_]u21{
+ 0, // null
+ ' ', // space
+ '\t', // tab
+ '\'', // single quote
+ '"', // double quote
+ 'โ', // U+2502 box drawing
+ '`', // backtick
+ '|', // pipe
+ ':', // colon
+ ';', // semicolon
+ ',', // comma
+ '(', // left paren
+ ')', // right paren
+ '[', // left bracket
+ ']', // right bracket
+ '{', // left brace
+ '}', // right brace
+ '<', // less than
+ '>', // greater than
+ '$', // dollar
+ };
+
+ /// The parsed codepoints. Always includes null (U+0000) at index 0.
+ codepoints: []const u21 = &default_codepoints,
+
+ pub fn parseCLI(self: *Self, alloc: Allocator, input: ?[]const u8) !void {
+ const value = input orelse return error.ValueRequired;
+
+ // Parse UTF-8 string into codepoints
+ var list: std.ArrayList(u21) = .empty;
+ defer list.deinit(alloc);
+
+ // Always include null as first boundary
+ try list.append(alloc, 0);
+
+ // Parse the UTF-8 string
+ const utf8_view = std.unicode.Utf8View.init(value) catch {
+ // Invalid UTF-8, just use null boundary
+ self.codepoints = try list.toOwnedSlice(alloc);
+ return;
+ };
+
+ var utf8_it = utf8_view.iterator();
+ while (utf8_it.nextCodepoint()) |codepoint| {
+ try list.append(alloc, codepoint);
+ }
+
+ self.codepoints = try list.toOwnedSlice(alloc);
+ }
+
+ /// Deep copy of the struct. Required by Config.
+ pub fn clone(self: *const Self, alloc: Allocator) Allocator.Error!Self {
+ const copy = try alloc.dupe(u21, self.codepoints);
+ return .{ .codepoints = copy };
+ }
+
+ /// Compare if two values are equal. Required by Config.
+ pub fn equal(self: Self, other: Self) bool {
+ return std.mem.eql(u21, self.codepoints, other.codepoints);
+ }
+
+ /// Used by Formatter
+ pub fn formatEntry(self: Self, formatter: formatterpkg.EntryFormatter) !void {
+ // Convert codepoints back to UTF-8 string for display
+ var buf: [4096]u8 = undefined;
+ var pos: usize = 0;
+
+ // Skip the null character at index 0
+ for (self.codepoints[1..]) |codepoint| {
+ var utf8_buf: [4]u8 = undefined;
+ const len = std.unicode.utf8Encode(codepoint, &utf8_buf) catch continue;
+ if (pos + len > buf.len) break;
+ @memcpy(buf[pos..][0..len], utf8_buf[0..len]);
+ pos += len;
+ }
+
+ try formatter.formatEntry([]const u8, buf[0..pos]);
+ }
+
+ test "parseCLI" {
+ const testing = std.testing;
+ var arena = ArenaAllocator.init(testing.allocator);
+ defer arena.deinit();
+ const alloc = arena.allocator();
+
+ var chars: Self = .{};
+ try chars.parseCLI(alloc, " \t;,");
+
+ // Should have null + 4 characters
+ try testing.expectEqual(@as(usize, 5), chars.codepoints.len);
+ try testing.expectEqual(@as(u21, 0), chars.codepoints[0]);
+ try testing.expectEqual(@as(u21, ' '), chars.codepoints[1]);
+ try testing.expectEqual(@as(u21, '\t'), chars.codepoints[2]);
+ try testing.expectEqual(@as(u21, ';'), chars.codepoints[3]);
+ try testing.expectEqual(@as(u21, ','), chars.codepoints[4]);
+ }
+};
+
/// FontVariation is a repeatable configuration value that sets a single
/// font variation value. Font variations are configurations for what
/// are often called "variable fonts." The font files usually end in
@@ -5805,12 +6044,17 @@ pub const RepeatableFontVariation = struct {
pub const Keybinds = struct {
set: inputpkg.Binding.Set = .{},
+ /// Defined key tables. The default key table is always the root "set",
+ /// which allows all table names to be available without reservation.
+ tables: std.StringArrayHashMapUnmanaged(inputpkg.Binding.Set) = .empty,
+
pub fn init(self: *Keybinds, alloc: Allocator) !void {
// We don't clear the memory because it's in the arena and unlikely
// to be free-able anyways (since arenas can only clear the last
// allocated value). This isn't a memory leak because the arena
// will be freed when the config is freed.
self.set = .{};
+ self.tables = .empty;
// keybinds for opening and reloading config
try self.set.put(
@@ -5881,7 +6125,7 @@ pub const Keybinds = struct {
// set the expected keybind for the menu.
try self.set.put(
alloc,
- .{ .key = .{ .physical = .equal }, .mods = inputpkg.ctrlOrSuper(.{}) },
+ .{ .key = .{ .unicode = '=' }, .mods = inputpkg.ctrlOrSuper(.{}) },
.{ .increase_font_size = 1 },
);
try self.set.put(
@@ -6049,13 +6293,13 @@ pub const Keybinds = struct {
);
try self.set.putFlags(
alloc,
- .{ .key = .{ .physical = .bracket_left }, .mods = .{ .ctrl = true, .super = true } },
+ .{ .key = .{ .unicode = '[' }, .mods = .{ .ctrl = true, .super = true } },
.{ .goto_split = .previous },
.{ .performable = true },
);
try self.set.putFlags(
alloc,
- .{ .key = .{ .physical = .bracket_right }, .mods = .{ .ctrl = true, .super = true } },
+ .{ .key = .{ .unicode = ']' }, .mods = .{ .ctrl = true, .super = true } },
.{ .goto_split = .next },
.{ .performable = true },
);
@@ -6329,6 +6573,12 @@ pub const Keybinds = struct {
.{ .key = .{ .physical = .page_down }, .mods = .{ .super = true } },
.{ .scroll_page_down = {} },
);
+ try self.set.putFlags(
+ alloc,
+ .{ .key = .{ .unicode = 'j' }, .mods = .{ .super = true } },
+ .{ .scroll_to_selection = {} },
+ .{ .performable = true },
+ );
// Semantic prompts
try self.set.put(
@@ -6375,12 +6625,12 @@ pub const Keybinds = struct {
);
try self.set.put(
alloc,
- .{ .key = .{ .physical = .bracket_left }, .mods = .{ .super = true, .shift = true } },
+ .{ .key = .{ .unicode = '[' }, .mods = .{ .super = true, .shift = true } },
.{ .previous_tab = {} },
);
try self.set.put(
alloc,
- .{ .key = .{ .physical = .bracket_right }, .mods = .{ .super = true, .shift = true } },
+ .{ .key = .{ .unicode = ']' }, .mods = .{ .super = true, .shift = true } },
.{ .next_tab = {} },
);
try self.set.put(
@@ -6395,12 +6645,12 @@ pub const Keybinds = struct {
);
try self.set.put(
alloc,
- .{ .key = .{ .physical = .bracket_left }, .mods = .{ .super = true } },
+ .{ .key = .{ .unicode = '[' }, .mods = .{ .super = true } },
.{ .goto_split = .previous },
);
try self.set.put(
alloc,
- .{ .key = .{ .physical = .bracket_right }, .mods = .{ .super = true } },
+ .{ .key = .{ .unicode = ']' }, .mods = .{ .super = true } },
.{ .goto_split = .next },
);
try self.set.put(
@@ -6445,7 +6695,7 @@ pub const Keybinds = struct {
);
try self.set.put(
alloc,
- .{ .key = .{ .physical = .equal }, .mods = .{ .super = true, .ctrl = true } },
+ .{ .key = .{ .unicode = '=' }, .mods = .{ .super = true, .ctrl = true } },
.{ .equalize_splits = {} },
);
@@ -6468,6 +6718,12 @@ pub const Keybinds = struct {
.start_search,
.{ .performable = true },
);
+ try self.set.putFlags(
+ alloc,
+ .{ .key = .{ .unicode = 'e' }, .mods = .{ .super = true } },
+ .search_selection,
+ .{ .performable = true },
+ );
try self.set.putFlags(
alloc,
.{ .key = .{ .unicode = 'f' }, .mods = .{ .super = true, .shift = true } },
@@ -6580,21 +6836,86 @@ pub const Keybinds = struct {
// will be freed when the config is freed.
log.info("config has 'keybind = clear', all keybinds cleared", .{});
self.set = .{};
+ self.tables = .empty;
return;
}
- // Let our much better tested binding package handle parsing and storage.
+ // Check for table syntax: "name/" or "name/binding"
+ // We look for '/' only before the first '=' to avoid matching
+ // action arguments like "foo=text:/hello".
+ const eq_idx = std.mem.indexOfScalar(u8, value, '=') orelse value.len;
+ if (std.mem.indexOfScalar(u8, value[0..eq_idx], '/')) |slash_idx| table: {
+ const table_name = value[0..slash_idx];
+
+ // Length zero is valid, so you can set `/=action` for the slash key
+ if (table_name.len == 0) break :table;
+
+ // Ignore '+', '>' because they can be part of sequences and
+ // triggers. This lets things like `ctrl+/=action` work.
+ if (std.mem.indexOfAny(
+ u8,
+ table_name,
+ "+>",
+ ) != null) break :table;
+
+ const binding = value[slash_idx + 1 ..];
+
+ // Get or create the table
+ const gop = try self.tables.getOrPut(alloc, table_name);
+ if (!gop.found_existing) {
+ // We need to copy our table name into the arena
+ // for valid lookups later.
+ gop.key_ptr.* = try alloc.dupe(u8, table_name);
+ gop.value_ptr.* = .{};
+ }
+
+ // If there's no binding after the slash, this is a table
+ // definition/clear command
+ if (binding.len == 0) {
+ log.debug("config has 'keybind = {s}/', table cleared", .{table_name});
+ gop.value_ptr.* = .{};
+ return;
+ }
+
+ // Parse and add the binding to the table
+ try gop.value_ptr.parseAndPut(alloc, binding);
+ return;
+ }
+
+ // Parse into default set
try self.set.parseAndPut(alloc, value);
}
/// Deep copy of the struct. Required by Config.
pub fn clone(self: *const Keybinds, alloc: Allocator) Allocator.Error!Keybinds {
- return .{ .set = try self.set.clone(alloc) };
+ var tables: std.StringArrayHashMapUnmanaged(inputpkg.Binding.Set) = .empty;
+ try tables.ensureTotalCapacity(alloc, @intCast(self.tables.count()));
+ var it = self.tables.iterator();
+ while (it.next()) |entry| {
+ const key = try alloc.dupe(u8, entry.key_ptr.*);
+ tables.putAssumeCapacity(key, try entry.value_ptr.clone(alloc));
+ }
+
+ return .{
+ .set = try self.set.clone(alloc),
+ .tables = tables,
+ };
}
/// Compare if two of our value are requal. Required by Config.
pub fn equal(self: Keybinds, other: Keybinds) bool {
- return equalSet(&self.set, &other.set);
+ if (!equalSet(&self.set, &other.set)) return false;
+
+ // Compare tables
+ if (self.tables.count() != other.tables.count()) return false;
+
+ var it = self.tables.iterator();
+ while (it.next()) |entry| {
+ const other_set = other.tables.get(entry.key_ptr.*) orelse return false;
+ if (!equalSet(entry.value_ptr, &other_set)) return false;
+ }
+
+ return true;
}
fn equalSet(
@@ -6631,12 +6952,27 @@ pub const Keybinds = struct {
const self_leaf = self_entry.value_ptr.*.leaf;
const other_leaf = other_entry.value_ptr.*.leaf;
- if (!equalField(
+ if (!deepEqual(
inputpkg.Binding.Set.Leaf,
self_leaf,
other_leaf,
)) return false;
},
+
+ .leaf_chained => {
+ const self_chain = self_entry.value_ptr.*.leaf_chained;
+ const other_chain = other_entry.value_ptr.*.leaf_chained;
+
+ if (self_chain.flags != other_chain.flags) return false;
+ if (self_chain.actions.items.len != other_chain.actions.items.len) return false;
+ for (self_chain.actions.items, other_chain.actions.items) |a1, a2| {
+ if (!deepEqual(
+ inputpkg.Binding.Action,
+ a1,
+ a2,
+ )) return false;
+ }
+ },
}
}
@@ -6645,12 +6981,14 @@ pub const Keybinds = struct {
/// Like formatEntry but has an option to include docs.
pub fn formatEntryDocs(self: Keybinds, formatter: formatterpkg.EntryFormatter, docs: bool) !void {
- if (self.set.bindings.size == 0) {
+ if (self.set.bindings.count() == 0 and self.tables.count() == 0) {
try formatter.formatEntry(void, {});
return;
}
var buf: [1024]u8 = undefined;
+
+ // Format root set bindings
var iter = self.set.bindings.iterator();
while (iter.next()) |next| {
const k = next.key_ptr.*;
@@ -6677,6 +7015,23 @@ pub const Keybinds = struct {
writer.print("{f}", .{k}) catch return error.OutOfMemory;
try v.formatEntries(&writer, formatter);
}
+
+ // Format table bindings
+ var table_iter = self.tables.iterator();
+ while (table_iter.next()) |table_entry| {
+ const table_name = table_entry.key_ptr.*;
+ const table_set = table_entry.value_ptr.*;
+
+ var binding_iter = table_set.bindings.iterator();
+ while (binding_iter.next()) |next| {
+ const k = next.key_ptr.*;
+ const v = next.value_ptr.*;
+
+ var writer: std.Io.Writer = .fixed(&buf);
+ writer.print("{s}/{f}", .{ table_name, k }) catch return error.OutOfMemory;
+ try v.formatEntries(&writer, formatter);
+ }
+ }
}
/// Used by Formatter
@@ -6728,8 +7083,8 @@ pub const Keybinds = struct {
// Note they turn into translated keys because they match
// their ASCII mapping.
const want =
- \\keybind = ctrl+z>2=goto_tab:2
\\keybind = ctrl+z>1=goto_tab:1
+ \\keybind = ctrl+z>2=goto_tab:2
\\
;
try std.testing.expectEqualStrings(want, buf.written());
@@ -6753,14 +7108,390 @@ pub const Keybinds = struct {
// NB: This does not currently retain the order of the keybinds.
const want =
- \\a = ctrl+a>ctrl+c>t=new_tab
- \\a = ctrl+a>ctrl+b>w=close_window
\\a = ctrl+a>ctrl+b>n=new_window
+ \\a = ctrl+a>ctrl+b>w=close_window
+ \\a = ctrl+a>ctrl+c>t=new_tab
\\a = ctrl+b>ctrl+d>a=previous_tab
\\
;
try std.testing.expectEqualStrings(want, buf.written());
}
+
+ test "parseCLI table definition" {
+ const testing = std.testing;
+ var arena = ArenaAllocator.init(testing.allocator);
+ defer arena.deinit();
+ const alloc = arena.allocator();
+
+ var keybinds: Keybinds = .{};
+
+ // Define a table by adding a binding to it
+ try keybinds.parseCLI(alloc, "foo/shift+a=copy_to_clipboard");
+ try testing.expectEqual(1, keybinds.tables.count());
+ try testing.expect(keybinds.tables.contains("foo"));
+
+ const table = keybinds.tables.get("foo").?;
+ try testing.expectEqual(1, table.bindings.count());
+ }
+
+ test "parseCLI table clear" {
+ const testing = std.testing;
+ var arena = ArenaAllocator.init(testing.allocator);
+ defer arena.deinit();
+ const alloc = arena.allocator();
+
+ var keybinds: Keybinds = .{};
+
+ // Add a binding to a table
+ try keybinds.parseCLI(alloc, "foo/shift+a=copy_to_clipboard");
+ try testing.expectEqual(1, keybinds.tables.get("foo").?.bindings.count());
+
+ // Clear the table with "foo/"
+ try keybinds.parseCLI(alloc, "foo/");
+ try testing.expectEqual(0, keybinds.tables.get("foo").?.bindings.count());
+ }
+
+ test "parseCLI table multiple bindings" {
+ const testing = std.testing;
+ var arena = ArenaAllocator.init(testing.allocator);
+ defer arena.deinit();
+ const alloc = arena.allocator();
+
+ var keybinds: Keybinds = .{};
+
+ try keybinds.parseCLI(alloc, "foo/shift+a=copy_to_clipboard");
+ try keybinds.parseCLI(alloc, "foo/shift+b=paste_from_clipboard");
+ try keybinds.parseCLI(alloc, "bar/ctrl+c=close_window");
+
+ try testing.expectEqual(2, keybinds.tables.count());
+ try testing.expectEqual(2, keybinds.tables.get("foo").?.bindings.count());
+ try testing.expectEqual(1, keybinds.tables.get("bar").?.bindings.count());
+ }
+
+ test "parseCLI table does not affect root set" {
+ const testing = std.testing;
+ var arena = ArenaAllocator.init(testing.allocator);
+ defer arena.deinit();
+ const alloc = arena.allocator();
+
+ var keybinds: Keybinds = .{};
+
+ try keybinds.parseCLI(alloc, "shift+a=copy_to_clipboard");
+ try keybinds.parseCLI(alloc, "foo/shift+b=paste_from_clipboard");
+
+ // Root set should have the first binding
+ try testing.expectEqual(1, keybinds.set.bindings.count());
+ // Table should have the second binding
+ try testing.expectEqual(1, keybinds.tables.get("foo").?.bindings.count());
+ }
+
+ test "parseCLI table empty name is invalid" {
+ const testing = std.testing;
+ var arena = ArenaAllocator.init(testing.allocator);
+ defer arena.deinit();
+ const alloc = arena.allocator();
+
+ var keybinds: Keybinds = .{};
+ try testing.expectError(error.InvalidFormat, keybinds.parseCLI(alloc, "/shift+a=copy_to_clipboard"));
+ }
+
+ test "parseCLI table with key sequence" {
+ const testing = std.testing;
+ var arena = ArenaAllocator.init(testing.allocator);
+ defer arena.deinit();
+ const alloc = arena.allocator();
+
+ var keybinds: Keybinds = .{};
+
+ // Key sequences should work within tables
+ try keybinds.parseCLI(alloc, "foo/ctrl+a>ctrl+b=new_window");
+
+ const table = keybinds.tables.get("foo").?;
+ try testing.expectEqual(1, table.bindings.count());
+ }
+
+ test "parseCLI slash in action argument is not a table" {
+ const testing = std.testing;
+ var arena = ArenaAllocator.init(testing.allocator);
+ defer arena.deinit();
+ const alloc = arena.allocator();
+
+ var keybinds: Keybinds = .{};
+
+ // A slash after the = should not be interpreted as a table delimiter
+ try keybinds.parseCLI(alloc, "ctrl+a=text:/hello");
+
+ // Should be in root set, not a table
+ try testing.expectEqual(1, keybinds.set.bindings.count());
+ try testing.expectEqual(0, keybinds.tables.count());
+ }
+
+ test "parseCLI slash as key with modifier is not a table" {
+ const testing = std.testing;
+ var arena = ArenaAllocator.init(testing.allocator);
+ defer arena.deinit();
+ const alloc = arena.allocator();
+
+ var keybinds: Keybinds = .{};
+
+ // ctrl+/ should be parsed as a keybind with '/' as the key, not a table
+ try keybinds.parseCLI(alloc, "ctrl+/=text:foo");
+
+ // Should be in root set, not a table
+ try testing.expectEqual(1, keybinds.set.bindings.count());
+ try testing.expectEqual(0, keybinds.tables.count());
+ }
+
+ test "parseCLI shift+slash as key is not a table" {
+ const testing = std.testing;
+ var arena = ArenaAllocator.init(testing.allocator);
+ defer arena.deinit();
+ const alloc = arena.allocator();
+
+ var keybinds: Keybinds = .{};
+
+ // shift+/ should be parsed as a keybind, not a table
+ try keybinds.parseCLI(alloc, "shift+/=ignore");
+
+ // Should be in root set, not a table
+ try testing.expectEqual(1, keybinds.set.bindings.count());
+ try testing.expectEqual(0, keybinds.tables.count());
+ }
+
+ test "parseCLI bare slash as key is not a table" {
+ const testing = std.testing;
+ var arena = ArenaAllocator.init(testing.allocator);
+ defer arena.deinit();
+ const alloc = arena.allocator();
+
+ var keybinds: Keybinds = .{};
+
+ // Bare / as a key should work (empty table name is rejected)
+ try keybinds.parseCLI(alloc, "/=text:foo");
+
+ // Should be in root set, not a table
+ try testing.expectEqual(1, keybinds.set.bindings.count());
+ try testing.expectEqual(0, keybinds.tables.count());
+ }
+
+ test "parseCLI slash in key sequence is not a table" {
+ const testing = std.testing;
+ var arena = ArenaAllocator.init(testing.allocator);
+ defer arena.deinit();
+ const alloc = arena.allocator();
+
+ var keybinds: Keybinds = .{};
+
+ // Key sequence ending with / should work
+ try keybinds.parseCLI(alloc, "ctrl+a>ctrl+/=new_window");
+
+ // Should be in root set, not a table
+ try testing.expectEqual(1, keybinds.set.bindings.count());
+ try testing.expectEqual(0, keybinds.tables.count());
+ }
+
+ test "parseCLI table with slash in binding" {
+ const testing = std.testing;
+ var arena = ArenaAllocator.init(testing.allocator);
+ defer arena.deinit();
+ const alloc = arena.allocator();
+
+ var keybinds: Keybinds = .{};
+
+ // Table with a binding that uses / as the key
+ try keybinds.parseCLI(alloc, "mytable//=text:foo");
+
+ // Should be in the table
+ try testing.expectEqual(0, keybinds.set.bindings.count());
+ try testing.expectEqual(1, keybinds.tables.count());
+ try testing.expect(keybinds.tables.contains("mytable"));
+ try testing.expectEqual(1, keybinds.tables.get("mytable").?.bindings.count());
+ }
+
+ test "parseCLI table with sequence containing slash" {
+ const testing = std.testing;
+ var arena = ArenaAllocator.init(testing.allocator);
+ defer arena.deinit();
+ const alloc = arena.allocator();
+
+ var keybinds: Keybinds = .{};
+
+ // Table with a key sequence that ends with /
+ try keybinds.parseCLI(alloc, "mytable/a>/=new_window");
+
+ // Should be in the table
+ try testing.expectEqual(0, keybinds.set.bindings.count());
+ try testing.expectEqual(1, keybinds.tables.count());
+ try testing.expect(keybinds.tables.contains("mytable"));
+ }
+
+ test "clone with tables" {
+ const testing = std.testing;
+ var arena = ArenaAllocator.init(testing.allocator);
+ defer arena.deinit();
+ const alloc = arena.allocator();
+
+ var keybinds: Keybinds = .{};
+ try keybinds.parseCLI(alloc, "shift+a=copy_to_clipboard");
+ try keybinds.parseCLI(alloc, "foo/shift+b=paste_from_clipboard");
+ try keybinds.parseCLI(alloc, "bar/ctrl+c=close_window");
+
+ const cloned = try keybinds.clone(alloc);
+
+ // Verify the clone has the same structure
+ try testing.expectEqual(keybinds.set.bindings.count(), cloned.set.bindings.count());
+ try testing.expectEqual(keybinds.tables.count(), cloned.tables.count());
+ try testing.expectEqual(
+ keybinds.tables.get("foo").?.bindings.count(),
+ cloned.tables.get("foo").?.bindings.count(),
+ );
+ try testing.expectEqual(
+ keybinds.tables.get("bar").?.bindings.count(),
+ cloned.tables.get("bar").?.bindings.count(),
+ );
+ }
+
+ test "equal with tables" {
+ const testing = std.testing;
+ var arena = ArenaAllocator.init(testing.allocator);
+ defer arena.deinit();
+ const alloc = arena.allocator();
+
+ var keybinds1: Keybinds = .{};
+ try keybinds1.parseCLI(alloc, "foo/shift+a=copy_to_clipboard");
+
+ var keybinds2: Keybinds = .{};
+ try keybinds2.parseCLI(alloc, "foo/shift+a=copy_to_clipboard");
+
+ try testing.expect(keybinds1.equal(keybinds2));
+ }
+
+ test "equal with tables different table count" {
+ const testing = std.testing;
+ var arena = ArenaAllocator.init(testing.allocator);
+ defer arena.deinit();
+ const alloc = arena.allocator();
+
+ var keybinds1: Keybinds = .{};
+ try keybinds1.parseCLI(alloc, "foo/shift+a=copy_to_clipboard");
+
+ var keybinds2: Keybinds = .{};
+ try keybinds2.parseCLI(alloc, "foo/shift+a=copy_to_clipboard");
+ try keybinds2.parseCLI(alloc, "bar/shift+b=paste_from_clipboard");
+
+ try testing.expect(!keybinds1.equal(keybinds2));
+ }
+
+ test "equal with tables different table names" {
+ const testing = std.testing;
+ var arena = ArenaAllocator.init(testing.allocator);
+ defer arena.deinit();
+ const alloc = arena.allocator();
+
+ var keybinds1: Keybinds = .{};
+ try keybinds1.parseCLI(alloc, "foo/shift+a=copy_to_clipboard");
+
+ var keybinds2: Keybinds = .{};
+ try keybinds2.parseCLI(alloc, "bar/shift+a=copy_to_clipboard");
+
+ try testing.expect(!keybinds1.equal(keybinds2));
+ }
+
+ test "equal with tables different bindings" {
+ const testing = std.testing;
+ var arena = ArenaAllocator.init(testing.allocator);
+ defer arena.deinit();
+ const alloc = arena.allocator();
+
+ var keybinds1: Keybinds = .{};
+ try keybinds1.parseCLI(alloc, "foo/shift+a=copy_to_clipboard");
+
+ var keybinds2: Keybinds = .{};
+ try keybinds2.parseCLI(alloc, "foo/shift+b=paste_from_clipboard");
+
+ try testing.expect(!keybinds1.equal(keybinds2));
+ }
+
+ test "formatEntry with tables" {
+ const testing = std.testing;
+ var buf: std.Io.Writer.Allocating = .init(testing.allocator);
+ defer buf.deinit();
+
+ var arena = ArenaAllocator.init(testing.allocator);
+ defer arena.deinit();
+ const alloc = arena.allocator();
+
+ var keybinds: Keybinds = .{};
+ try keybinds.parseCLI(alloc, "foo/shift+a=csi:hello");
+ try keybinds.formatEntry(formatterpkg.entryFormatter("keybind", &buf.writer));
+
+ try testing.expectEqualStrings("keybind = foo/shift+a=csi:hello\n", buf.written());
+ }
+
+ test "formatEntry with tables and root set" {
+ const testing = std.testing;
+ var buf: std.Io.Writer.Allocating = .init(testing.allocator);
+ defer buf.deinit();
+
+ var arena = ArenaAllocator.init(testing.allocator);
+ defer arena.deinit();
+ const alloc = arena.allocator();
+
+ var keybinds: Keybinds = .{};
+ try keybinds.parseCLI(alloc, "shift+b=csi:world");
+ try keybinds.parseCLI(alloc, "foo/shift+a=csi:hello");
+ try keybinds.formatEntry(formatterpkg.entryFormatter("keybind", &buf.writer));
+
+ const output = buf.written();
+ try testing.expect(std.mem.indexOf(u8, output, "keybind = shift+b=csi:world\n") != null);
+ try testing.expect(std.mem.indexOf(u8, output, "keybind = foo/shift+a=csi:hello\n") != null);
+ }
+
+ test "parseCLI clear clears tables" {
+ const testing = std.testing;
+ var arena = ArenaAllocator.init(testing.allocator);
+ defer arena.deinit();
+ const alloc = arena.allocator();
+
+ var keybinds: Keybinds = .{};
+
+ // Add bindings to root set and tables
+ try keybinds.parseCLI(alloc, "shift+a=copy_to_clipboard");
+ try keybinds.parseCLI(alloc, "foo/shift+b=paste_from_clipboard");
+ try keybinds.parseCLI(alloc, "bar/ctrl+c=close_window");
+
+ try testing.expectEqual(1, keybinds.set.bindings.count());
+ try testing.expectEqual(2, keybinds.tables.count());
+
+ // Clear all keybinds
+ try keybinds.parseCLI(alloc, "clear");
+
+ // Both root set and tables should be cleared
+ try testing.expectEqual(0, keybinds.set.bindings.count());
+ try testing.expectEqual(0, keybinds.tables.count());
+ }
+
+ test "parseCLI reset clears tables" {
+ const testing = std.testing;
+ var arena = ArenaAllocator.init(testing.allocator);
+ defer arena.deinit();
+ const alloc = arena.allocator();
+
+ var keybinds: Keybinds = .{};
+
+ // Add bindings to tables
+ try keybinds.parseCLI(alloc, "foo/shift+a=copy_to_clipboard");
+ try keybinds.parseCLI(alloc, "bar/shift+b=paste_from_clipboard");
+
+ try testing.expectEqual(2, keybinds.tables.count());
+
+ // Reset to defaults (empty value)
+ try keybinds.parseCLI(alloc, "");
+
+ // Tables should be cleared, root set has defaults
+ try testing.expectEqual(0, keybinds.tables.count());
+ try testing.expect(keybinds.set.bindings.count() > 0);
+ }
};
/// See "font-codepoint-map" for documentation.
@@ -7447,6 +8178,7 @@ pub const ShellIntegration = enum {
bash,
elvish,
fish,
+ nushell,
zsh,
};
@@ -7465,15 +8197,37 @@ pub const SplitPreserveZoom = packed struct {
};
pub const RepeatableCommand = struct {
- value: std.ArrayListUnmanaged(inputpkg.Command) = .empty,
+ const Self = @This();
- pub fn init(self: *RepeatableCommand, alloc: Allocator) !void {
+ value: std.ArrayListUnmanaged(inputpkg.Command) = .empty,
+ value_c: std.ArrayListUnmanaged(inputpkg.Command.C) = .empty,
+
+ /// ghostty_config_command_list_s
+ pub const C = extern struct {
+ commands: [*]inputpkg.Command.C,
+ len: usize,
+ };
+
+ pub fn cval(self: *const Self) C {
+ return .{
+ .commands = self.value_c.items.ptr,
+ .len = self.value_c.items.len,
+ };
+ }
+
+ pub fn init(self: *Self, alloc: Allocator) !void {
self.value = .empty;
+ self.value_c = .empty;
+ errdefer {
+ self.value.deinit(alloc);
+ self.value_c.deinit(alloc);
+ }
try self.value.appendSlice(alloc, inputpkg.command.defaults);
+ try self.value_c.appendSlice(alloc, inputpkg.command.defaultsC);
}
pub fn parseCLI(
- self: *RepeatableCommand,
+ self: *Self,
alloc: Allocator,
input_: ?[]const u8,
) !void {
@@ -7481,26 +8235,36 @@ pub const RepeatableCommand = struct {
const input = input_ orelse "";
if (input.len == 0) {
self.value.clearRetainingCapacity();
+ self.value_c.clearRetainingCapacity();
return;
}
+ // Reserve space in our lists
+ try self.value.ensureUnusedCapacity(alloc, 1);
+ try self.value_c.ensureUnusedCapacity(alloc, 1);
+
const cmd = try cli.args.parseAutoStruct(
inputpkg.Command,
alloc,
input,
null,
);
- try self.value.append(alloc, cmd);
+ const cmd_c = try cmd.cval(alloc);
+ self.value.appendAssumeCapacity(cmd);
+ self.value_c.appendAssumeCapacity(cmd_c);
}
/// Deep copy of the struct. Required by Config.
- pub fn clone(self: *const RepeatableCommand, alloc: Allocator) Allocator.Error!RepeatableCommand {
+ pub fn clone(self: *const Self, alloc: Allocator) Allocator.Error!Self {
const value = try self.value.clone(alloc);
for (value.items) |*item| {
item.* = try item.clone(alloc);
}
- return .{ .value = value };
+ return .{
+ .value = value,
+ .value_c = try self.value_c.clone(alloc),
+ };
}
/// Compare if two of our value are equal. Required by Config.
@@ -7656,6 +8420,50 @@ pub const RepeatableCommand = struct {
try testing.expectEqualStrings("kurwa", item.action.text);
}
}
+
+ test "RepeatableCommand cval" {
+ const testing = std.testing;
+
+ var arena = ArenaAllocator.init(testing.allocator);
+ defer arena.deinit();
+ const alloc = arena.allocator();
+
+ var list: RepeatableCommand = .{};
+ try list.parseCLI(alloc, "title:Foo,action:ignore");
+ try list.parseCLI(alloc, "title:Bar,description:bobr,action:text:ale bydle");
+
+ try testing.expectEqual(@as(usize, 2), list.value.items.len);
+ try testing.expectEqual(@as(usize, 2), list.value_c.items.len);
+
+ const cv = list.cval();
+ try testing.expectEqual(@as(usize, 2), cv.len);
+
+ // First entry
+ try testing.expectEqualStrings("Foo", std.mem.sliceTo(cv.commands[0].title, 0));
+ try testing.expectEqualStrings("ignore", std.mem.sliceTo(cv.commands[0].action_key, 0));
+ try testing.expectEqualStrings("ignore", std.mem.sliceTo(cv.commands[0].action, 0));
+
+ // Second entry
+ try testing.expectEqualStrings("Bar", std.mem.sliceTo(cv.commands[1].title, 0));
+ try testing.expectEqualStrings("bobr", std.mem.sliceTo(cv.commands[1].description, 0));
+ try testing.expectEqualStrings("text", std.mem.sliceTo(cv.commands[1].action_key, 0));
+ try testing.expectEqualStrings("text:ale bydle", std.mem.sliceTo(cv.commands[1].action, 0));
+ }
+
+ test "RepeatableCommand cval cleared" {
+ const testing = std.testing;
+
+ var arena = ArenaAllocator.init(testing.allocator);
+ defer arena.deinit();
+ const alloc = arena.allocator();
+
+ var list: RepeatableCommand = .{};
+ try list.parseCLI(alloc, "title:Foo,action:ignore");
+ try testing.expectEqual(@as(usize, 1), list.cval().len);
+
+ try list.parseCLI(alloc, "");
+ try testing.expectEqual(@as(usize, 0), list.cval().len);
+ }
};
/// OSC 4, 10, 11, and 12 default color reporting format.
diff --git a/src/config/theme.zig b/src/config/theme.zig
index 7ba6e5885..8776fb1bf 100644
--- a/src/config/theme.zig
+++ b/src/config/theme.zig
@@ -221,7 +221,7 @@ pub fn open(
// Unlikely scenario: the theme doesn't exist. In this case, we reset
// our iterator, reiterate over in order to build a better error message.
- // This does double allocate some memory but for errors I think thats
+ // This does double allocate some memory but for errors I think that's
// fine.
it.reset();
while (try it.next()) |loc| {
diff --git a/src/datastruct/circ_buf.zig b/src/datastruct/circ_buf.zig
index 0caa9e85d..3e373cb94 100644
--- a/src/datastruct/circ_buf.zig
+++ b/src/datastruct/circ_buf.zig
@@ -217,6 +217,13 @@ pub fn CircBuf(comptime T: type, comptime default: T) type {
pub fn deleteOldest(self: *Self, n: usize) void {
assert(n <= self.storage.len);
+ // Special case n == 0 otherwise we will accidentally break
+ // our circular buffer.
+ if (n == 0) {
+ @branchHint(.cold);
+ return;
+ }
+
// Clear the values back to default
const slices = self.getPtrSlice(0, n);
inline for (slices) |slice| @memset(slice, default);
@@ -233,6 +240,12 @@ pub fn CircBuf(comptime T: type, comptime default: T) type {
/// the end of our buffer. This never "rotates" the buffer because
/// the offset can only be within the size of the buffer.
pub fn getPtrSlice(self: *Self, offset: usize, slice_len: usize) [2][]T {
+ // Special case the empty slice fast-path.
+ if (slice_len == 0) {
+ @branchHint(.cold);
+ return .{ &.{}, &.{} };
+ }
+
// Note: this assertion is very important, it hints the compiler
// which generates ~10% faster code than without it.
assert(offset + slice_len <= self.capacity());
@@ -779,3 +792,75 @@ test "CircBuf resize shrink" {
try testing.expectEqual(@as(u8, 3), slices[0][2]);
}
}
+
+test "CircBuf append empty slice" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ const Buf = CircBuf(u8, 0);
+ var buf = try Buf.init(alloc, 5);
+ defer buf.deinit(alloc);
+
+ // Appending an empty slice to empty buffer should be a no-op
+ buf.appendSliceAssumeCapacity("");
+ try testing.expectEqual(@as(usize, 0), buf.len());
+ try testing.expect(!buf.full);
+
+ // Buffer should still work normally after appending empty slice
+ buf.appendSliceAssumeCapacity("hi");
+ try testing.expectEqual(@as(usize, 2), buf.len());
+
+ // Appending an empty slice to non-empty buffer should also be a no-op
+ buf.appendSliceAssumeCapacity("");
+ try testing.expectEqual(@as(usize, 2), buf.len());
+}
+
+test "CircBuf getPtrSlice zero length" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ const Buf = CircBuf(u8, 0);
+ var buf = try Buf.init(alloc, 5);
+ defer buf.deinit(alloc);
+
+ // getPtrSlice with zero length on empty buffer should return empty slices
+ const slices = buf.getPtrSlice(0, 0);
+ try testing.expectEqual(@as(usize, 0), slices[0].len);
+ try testing.expectEqual(@as(usize, 0), slices[1].len);
+ try testing.expectEqual(@as(usize, 0), buf.len());
+
+ // Fill buffer partially
+ buf.appendSliceAssumeCapacity("abc");
+ try testing.expectEqual(@as(usize, 3), buf.len());
+
+ // getPtrSlice with zero length on non-empty buffer should also work
+ const slices2 = buf.getPtrSlice(0, 0);
+ try testing.expectEqual(@as(usize, 0), slices2[0].len);
+ try testing.expectEqual(@as(usize, 0), slices2[1].len);
+ try testing.expectEqual(@as(usize, 3), buf.len());
+}
+
+test "CircBuf deleteOldest zero" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ const Buf = CircBuf(u8, 0);
+ var buf = try Buf.init(alloc, 5);
+ defer buf.deinit(alloc);
+
+ // deleteOldest(0) on empty buffer should be a no-op
+ buf.deleteOldest(0);
+ try testing.expectEqual(@as(usize, 0), buf.len());
+
+ // Fill buffer
+ buf.appendSliceAssumeCapacity("hello");
+ try testing.expectEqual(@as(usize, 5), buf.len());
+
+ // deleteOldest(0) on non-empty buffer should be a no-op
+ buf.deleteOldest(0);
+ try testing.expectEqual(@as(usize, 5), buf.len());
+
+ // Verify data is unchanged
+ var it = buf.iterator(.forward);
+ try testing.expectEqual(@as(u8, 'h'), it.next().?.*);
+}
diff --git a/src/datastruct/comparison.zig b/src/datastruct/comparison.zig
index 4427c143c..6a862bb9c 100644
--- a/src/datastruct/comparison.zig
+++ b/src/datastruct/comparison.zig
@@ -1,11 +1,102 @@
// The contents of this file is largely based on testing.zig from the Zig 0.15.1
// stdlib, distributed under the MIT license, copyright (c) Zig contributors
const std = @import("std");
+const testing = std.testing;
+
+/// A deep equality comparison function that works for most types. We
+/// add types as necessary. It defers to `equal` decls on types that support
+/// decls.
+pub fn deepEqual(comptime T: type, old: T, new: T) bool {
+ // Do known named types first
+ switch (T) {
+ inline []const u8,
+ [:0]const u8,
+ => return std.mem.eql(u8, old, new),
+
+ []const [:0]const u8,
+ => {
+ if (old.len != new.len) return false;
+ for (old, new) |a, b| {
+ if (!std.mem.eql(u8, a, b)) return false;
+ }
+
+ return true;
+ },
+
+ else => {},
+ }
+
+ // Back into types of types
+ switch (@typeInfo(T)) {
+ .void => return true,
+
+ inline .bool,
+ .int,
+ .float,
+ .@"enum",
+ => return old == new,
+
+ .optional => |info| {
+ if (old == null and new == null) return true;
+ if (old == null or new == null) return false;
+ return deepEqual(info.child, old.?, new.?);
+ },
+
+ .array => |info| for (old, new) |old_elem, new_elem| {
+ if (!deepEqual(
+ info.child,
+ old_elem,
+ new_elem,
+ )) return false;
+ } else return true,
+
+ .@"struct" => |info| {
+ if (@hasDecl(T, "equal")) return old.equal(new);
+
+ // If a struct doesn't declare an "equal" function, we fall back
+ // to a recursive field-by-field compare.
+ inline for (info.fields) |field_info| {
+ if (!deepEqual(
+ field_info.type,
+ @field(old, field_info.name),
+ @field(new, field_info.name),
+ )) return false;
+ }
+ return true;
+ },
+
+ .@"union" => |info| {
+ if (@hasDecl(T, "equal")) return old.equal(new);
+
+ const tag_type = info.tag_type.?;
+ const old_tag = std.meta.activeTag(old);
+ const new_tag = std.meta.activeTag(new);
+ if (old_tag != new_tag) return false;
+
+ inline for (info.fields) |field_info| {
+ if (@field(tag_type, field_info.name) == old_tag) {
+ return deepEqual(
+ field_info.type,
+ @field(old, field_info.name),
+ @field(new, field_info.name),
+ );
+ }
+ }
+
+ unreachable;
+ },
+
+ else => {
+ @compileLog(T);
+ @compileError("unsupported field type");
+ },
+ }
+}
/// Generic, recursive equality testing utility using approximate comparison for
/// floats and equality for everything else
///
-/// Based on `std.testing.expectEqual` and `std.testing.expectEqualSlices`.
+/// Based on `testing.expectEqual` and `testing.expectEqualSlices`.
///
/// The relative tolerance is currently hardcoded to `sqrt(eps(float_type))`.
pub inline fn expectApproxEqual(expected: anytype, actual: anytype) !void {
@@ -59,7 +150,7 @@ fn expectApproxEqualInner(comptime T: type, expected: T, actual: T) !void {
if (union_info.tag_type == null) {
// untagged unions can only be compared bitwise,
// so expectEqual is all we need
- std.testing.expectEqual(expected, actual) catch {
+ testing.expectEqual(expected, actual) catch {
return error.TestExpectedApproxEqual;
};
}
@@ -69,7 +160,7 @@ fn expectApproxEqualInner(comptime T: type, expected: T, actual: T) !void {
const expectedTag = @as(Tag, expected);
const actualTag = @as(Tag, actual);
- std.testing.expectEqual(expectedTag, actualTag) catch {
+ testing.expectEqual(expectedTag, actualTag) catch {
return error.TestExpectedApproxEqual;
};
@@ -84,23 +175,23 @@ fn expectApproxEqualInner(comptime T: type, expected: T, actual: T) !void {
};
// we only reach this point if there's at least one null or error,
// in which case expectEqual is all we need
- std.testing.expectEqual(expected, actual) catch {
+ testing.expectEqual(expected, actual) catch {
return error.TestExpectedApproxEqual;
};
},
// fall back to expectEqual for everything else
- else => std.testing.expectEqual(expected, actual) catch {
+ else => testing.expectEqual(expected, actual) catch {
return error.TestExpectedApproxEqual;
},
}
}
-/// Copy of std.testing.print (not public)
+/// Copy of testing.print (not public)
fn print(comptime fmt: []const u8, args: anytype) void {
if (@inComptime()) {
@compileError(std.fmt.comptimePrint(fmt, args));
- } else if (std.testing.backend_can_print) {
+ } else if (testing.backend_can_print) {
std.debug.print(fmt, args);
}
}
@@ -145,3 +236,195 @@ test "expectApproxEqual struct" {
try expectApproxEqual(a, b);
}
+
+test "deepEqual void" {
+ try testing.expect(deepEqual(void, {}, {}));
+}
+
+test "deepEqual bool" {
+ try testing.expect(deepEqual(bool, true, true));
+ try testing.expect(deepEqual(bool, false, false));
+ try testing.expect(!deepEqual(bool, true, false));
+ try testing.expect(!deepEqual(bool, false, true));
+}
+
+test "deepEqual int" {
+ try testing.expect(deepEqual(i32, 42, 42));
+ try testing.expect(deepEqual(i32, -100, -100));
+ try testing.expect(!deepEqual(i32, 42, 43));
+ try testing.expect(deepEqual(u64, 0, 0));
+ try testing.expect(!deepEqual(u64, 0, 1));
+}
+
+test "deepEqual float" {
+ try testing.expect(deepEqual(f32, 1.0, 1.0));
+ try testing.expect(!deepEqual(f32, 1.0, 1.1));
+ try testing.expect(deepEqual(f64, 3.14159, 3.14159));
+ try testing.expect(!deepEqual(f64, 3.14159, 3.14158));
+}
+
+test "deepEqual enum" {
+ const Color = enum { red, green, blue };
+ try testing.expect(deepEqual(Color, .red, .red));
+ try testing.expect(deepEqual(Color, .blue, .blue));
+ try testing.expect(!deepEqual(Color, .red, .green));
+ try testing.expect(!deepEqual(Color, .green, .blue));
+}
+
+test "deepEqual []const u8" {
+ try testing.expect(deepEqual([]const u8, "hello", "hello"));
+ try testing.expect(deepEqual([]const u8, "", ""));
+ try testing.expect(!deepEqual([]const u8, "hello", "world"));
+ try testing.expect(!deepEqual([]const u8, "hello", "hell"));
+ try testing.expect(!deepEqual([]const u8, "hello", "hello!"));
+}
+
+test "deepEqual [:0]const u8" {
+ try testing.expect(deepEqual([:0]const u8, "foo", "foo"));
+ try testing.expect(!deepEqual([:0]const u8, "foo", "bar"));
+ try testing.expect(!deepEqual([:0]const u8, "foo", "fo"));
+}
+
+test "deepEqual []const [:0]const u8" {
+ const a: []const [:0]const u8 = &.{ "one", "two", "three" };
+ const b: []const [:0]const u8 = &.{ "one", "two", "three" };
+ const c: []const [:0]const u8 = &.{ "one", "two" };
+ const d: []const [:0]const u8 = &.{ "one", "two", "four" };
+ const e: []const [:0]const u8 = &.{};
+
+ try testing.expect(deepEqual([]const [:0]const u8, a, b));
+ try testing.expect(!deepEqual([]const [:0]const u8, a, c));
+ try testing.expect(!deepEqual([]const [:0]const u8, a, d));
+ try testing.expect(deepEqual([]const [:0]const u8, e, e));
+ try testing.expect(!deepEqual([]const [:0]const u8, a, e));
+}
+
+test "deepEqual optional" {
+ try testing.expect(deepEqual(?i32, null, null));
+ try testing.expect(deepEqual(?i32, 42, 42));
+ try testing.expect(!deepEqual(?i32, null, 42));
+ try testing.expect(!deepEqual(?i32, 42, null));
+ try testing.expect(!deepEqual(?i32, 42, 43));
+}
+
+test "deepEqual optional nested" {
+ const Nested = struct { x: i32, y: i32 };
+ try testing.expect(deepEqual(?Nested, null, null));
+ try testing.expect(deepEqual(?Nested, .{ .x = 1, .y = 2 }, .{ .x = 1, .y = 2 }));
+ try testing.expect(!deepEqual(?Nested, .{ .x = 1, .y = 2 }, .{ .x = 1, .y = 3 }));
+ try testing.expect(!deepEqual(?Nested, .{ .x = 1, .y = 2 }, null));
+}
+
+test "deepEqual array" {
+ try testing.expect(deepEqual([3]i32, .{ 1, 2, 3 }, .{ 1, 2, 3 }));
+ try testing.expect(!deepEqual([3]i32, .{ 1, 2, 3 }, .{ 1, 2, 4 }));
+ try testing.expect(!deepEqual([3]i32, .{ 1, 2, 3 }, .{ 0, 2, 3 }));
+ try testing.expect(deepEqual([0]i32, .{}, .{}));
+}
+
+test "deepEqual nested array" {
+ const a = [2][2]i32{ .{ 1, 2 }, .{ 3, 4 } };
+ const b = [2][2]i32{ .{ 1, 2 }, .{ 3, 4 } };
+ const c = [2][2]i32{ .{ 1, 2 }, .{ 3, 5 } };
+
+ try testing.expect(deepEqual([2][2]i32, a, b));
+ try testing.expect(!deepEqual([2][2]i32, a, c));
+}
+
+test "deepEqual struct" {
+ const Point = struct { x: i32, y: i32 };
+ try testing.expect(deepEqual(Point, .{ .x = 10, .y = 20 }, .{ .x = 10, .y = 20 }));
+ try testing.expect(!deepEqual(Point, .{ .x = 10, .y = 20 }, .{ .x = 10, .y = 21 }));
+ try testing.expect(!deepEqual(Point, .{ .x = 10, .y = 20 }, .{ .x = 11, .y = 20 }));
+}
+
+test "deepEqual struct nested" {
+ const Inner = struct { value: i32 };
+ const Outer = struct { a: Inner, b: Inner };
+
+ const x = Outer{ .a = .{ .value = 1 }, .b = .{ .value = 2 } };
+ const y = Outer{ .a = .{ .value = 1 }, .b = .{ .value = 2 } };
+ const z = Outer{ .a = .{ .value = 1 }, .b = .{ .value = 3 } };
+
+ try testing.expect(deepEqual(Outer, x, y));
+ try testing.expect(!deepEqual(Outer, x, z));
+}
+
+test "deepEqual struct with equal decl" {
+ const Custom = struct {
+ value: i32,
+
+ pub fn equal(self: @This(), other: @This()) bool {
+ return @mod(self.value, 10) == @mod(other.value, 10);
+ }
+ };
+
+ try testing.expect(deepEqual(Custom, .{ .value = 5 }, .{ .value = 15 }));
+ try testing.expect(deepEqual(Custom, .{ .value = 100 }, .{ .value = 0 }));
+ try testing.expect(!deepEqual(Custom, .{ .value = 5 }, .{ .value = 6 }));
+}
+
+test "deepEqual union" {
+ const Value = union(enum) {
+ int: i32,
+ float: f32,
+ none,
+ };
+
+ try testing.expect(deepEqual(Value, .{ .int = 42 }, .{ .int = 42 }));
+ try testing.expect(!deepEqual(Value, .{ .int = 42 }, .{ .int = 43 }));
+ try testing.expect(!deepEqual(Value, .{ .int = 42 }, .{ .float = 42.0 }));
+ try testing.expect(deepEqual(Value, .none, .none));
+ try testing.expect(!deepEqual(Value, .none, .{ .int = 0 }));
+}
+
+test "deepEqual union with equal decl" {
+ const Value = union(enum) {
+ num: i32,
+ str: []const u8,
+
+ pub fn equal(self: @This(), other: @This()) bool {
+ return switch (self) {
+ .num => |n| switch (other) {
+ .num => |m| @mod(n, 10) == @mod(m, 10),
+ else => false,
+ },
+ .str => |s| switch (other) {
+ .str => |t| s.len == t.len,
+ else => false,
+ },
+ };
+ }
+ };
+
+ try testing.expect(deepEqual(Value, .{ .num = 5 }, .{ .num = 25 }));
+ try testing.expect(!deepEqual(Value, .{ .num = 5 }, .{ .num = 6 }));
+ try testing.expect(deepEqual(Value, .{ .str = "abc" }, .{ .str = "xyz" }));
+ try testing.expect(!deepEqual(Value, .{ .str = "abc" }, .{ .str = "ab" }));
+}
+
+test "deepEqual array of structs" {
+ const Item = struct { id: i32, name: []const u8 };
+ const a = [2]Item{ .{ .id = 1, .name = "one" }, .{ .id = 2, .name = "two" } };
+ const b = [2]Item{ .{ .id = 1, .name = "one" }, .{ .id = 2, .name = "two" } };
+ const c = [2]Item{ .{ .id = 1, .name = "one" }, .{ .id = 2, .name = "TWO" } };
+
+ try testing.expect(deepEqual([2]Item, a, b));
+ try testing.expect(!deepEqual([2]Item, a, c));
+}
+
+test "deepEqual struct with optional field" {
+ const Config = struct { name: []const u8, port: ?u16 };
+
+ try testing.expect(deepEqual(Config, .{ .name = "app", .port = 8080 }, .{ .name = "app", .port = 8080 }));
+ try testing.expect(deepEqual(Config, .{ .name = "app", .port = null }, .{ .name = "app", .port = null }));
+ try testing.expect(!deepEqual(Config, .{ .name = "app", .port = 8080 }, .{ .name = "app", .port = null }));
+ try testing.expect(!deepEqual(Config, .{ .name = "app", .port = 8080 }, .{ .name = "app", .port = 8081 }));
+}
+
+test "deepEqual struct with array field" {
+ const Data = struct { values: [3]i32 };
+
+ try testing.expect(deepEqual(Data, .{ .values = .{ 1, 2, 3 } }, .{ .values = .{ 1, 2, 3 } }));
+ try testing.expect(!deepEqual(Data, .{ .values = .{ 1, 2, 3 } }, .{ .values = .{ 1, 2, 4 } }));
+}
diff --git a/src/datastruct/main.zig b/src/datastruct/main.zig
index 64a29269e..bfee23427 100644
--- a/src/datastruct/main.zig
+++ b/src/datastruct/main.zig
@@ -19,4 +19,6 @@ pub const SplitTree = split_tree.SplitTree;
test {
@import("std").testing.refAllDecls(@This());
+
+ _ = @import("comparison.zig");
}
diff --git a/src/extra/bash.zig b/src/extra/bash.zig
index ee9a7895c..0cea3e317 100644
--- a/src/extra/bash.zig
+++ b/src/extra/bash.zig
@@ -158,9 +158,6 @@ fn writeBashCompletions(writer: *std.Io.Writer) !void {
);
for (@typeInfo(Action).@"enum".fields) |field| {
- if (std.mem.eql(u8, "help", field.name)) continue;
- if (std.mem.eql(u8, "version", field.name)) continue;
-
const options = @field(Action, field.name).options();
// assumes options will never be created with only <_name> members
if (@typeInfo(options).@"struct".fields.len == 0) continue;
@@ -194,9 +191,6 @@ fn writeBashCompletions(writer: *std.Io.Writer) !void {
);
for (@typeInfo(Action).@"enum".fields) |field| {
- if (std.mem.eql(u8, "help", field.name)) continue;
- if (std.mem.eql(u8, "version", field.name)) continue;
-
const options = @field(Action, field.name).options();
if (@typeInfo(options).@"struct".fields.len == 0) continue;
@@ -272,9 +266,6 @@ fn writeBashCompletions(writer: *std.Io.Writer) !void {
);
for (@typeInfo(Action).@"enum".fields) |field| {
- if (std.mem.eql(u8, "help", field.name)) continue;
- if (std.mem.eql(u8, "version", field.name)) continue;
-
try writer.writeAll(pad1 ++ "topLevel+=\" +" ++ field.name ++ "\"\n");
}
@@ -296,7 +287,7 @@ fn writeBashCompletions(writer: *std.Io.Writer) !void {
\\ else prev="${COMP_WORDS[COMP_CWORD-1]}"
\\ fi
\\
- \\ # current completion is double quoted add a space so the curor progresses
+ \\ # current completion is double quoted add a space so the cursor progresses
\\ if [[ "$2" == \"*\" ]]; then
\\ COMPREPLY=( "$cur " );
\\ return;
diff --git a/src/extra/fish.zig b/src/extra/fish.zig
index 12343c62f..73fa9a706 100644
--- a/src/extra/fish.zig
+++ b/src/extra/fish.zig
@@ -28,8 +28,6 @@ fn writeCompletions(writer: *std.Io.Writer) !void {
try writer.writeAll("set -l commands \"");
var count: usize = 0;
for (@typeInfo(Action).@"enum".fields) |field| {
- if (std.mem.eql(u8, "help", field.name)) continue;
- if (std.mem.eql(u8, "version", field.name)) continue;
if (count > 0) try writer.writeAll(" ");
try writer.writeAll("+");
try writer.writeAll(field.name);
@@ -98,8 +96,6 @@ fn writeCompletions(writer: *std.Io.Writer) !void {
try writer.writeAll("complete -c ghostty -n \"string match -q -- '+*' (commandline -pt)\" -f -a \"");
var count: usize = 0;
for (@typeInfo(Action).@"enum".fields) |field| {
- if (std.mem.eql(u8, "help", field.name)) continue;
- if (std.mem.eql(u8, "version", field.name)) continue;
if (count > 0) try writer.writeAll(" ");
try writer.writeAll("+");
try writer.writeAll(field.name);
diff --git a/src/extra/vim.zig b/src/extra/vim.zig
index 9140b83f8..062ccd2b6 100644
--- a/src/extra/vim.zig
+++ b/src/extra/vim.zig
@@ -10,7 +10,7 @@ pub const ftdetect =
\\"
\\" THIS FILE IS AUTO-GENERATED
\\
- \\au BufRead,BufNewFile */ghostty/config,*/ghostty/themes/*,*.ghostty setf ghostty
+ \\au BufRead,BufNewFile */ghostty/config,*/*.ghostty/config,*/ghostty/themes/*,*.ghostty setf ghostty
\\
;
pub const ftplugin =
diff --git a/src/extra/zsh.zig b/src/extra/zsh.zig
index 2fad4234a..376db807f 100644
--- a/src/extra/zsh.zig
+++ b/src/extra/zsh.zig
@@ -139,9 +139,6 @@ fn writeZshCompletions(writer: *std.Io.Writer) !void {
var count: usize = 0;
const padding = " ";
for (@typeInfo(Action).@"enum".fields) |field| {
- if (std.mem.eql(u8, "help", field.name)) continue;
- if (std.mem.eql(u8, "version", field.name)) continue;
-
try writer.writeAll(padding ++ "'+");
try writer.writeAll(field.name);
try writer.writeAll("'\n");
@@ -168,9 +165,6 @@ fn writeZshCompletions(writer: *std.Io.Writer) !void {
{
const padding = " ";
for (@typeInfo(Action).@"enum".fields) |field| {
- if (std.mem.eql(u8, "help", field.name)) continue;
- if (std.mem.eql(u8, "version", field.name)) continue;
-
const options = @field(Action, field.name).options();
// assumes options will never be created with only <_name> members
if (@typeInfo(options).@"struct".fields.len == 0) continue;
diff --git a/src/font/Atlas.zig b/src/font/Atlas.zig
index 0648c0edf..7dcff8416 100644
--- a/src/font/Atlas.zig
+++ b/src/font/Atlas.zig
@@ -562,7 +562,7 @@ test "exact fit" {
try testing.expectError(Error.AtlasFull, atlas.reserve(alloc, 1, 1));
}
-test "doesnt fit" {
+test "doesn't fit" {
const alloc = testing.allocator;
var atlas = try init(alloc, 32, .grayscale);
defer atlas.deinit(alloc);
diff --git a/src/font/Collection.zig b/src/font/Collection.zig
index 412098f10..5d7bfa519 100644
--- a/src/font/Collection.zig
+++ b/src/font/Collection.zig
@@ -196,8 +196,18 @@ pub fn getFace(self: *Collection, index: Index) !*Face {
return try self.getFaceFromEntry(try self.getEntry(index));
}
+pub const EntryError = error{
+ /// Index represents a special font (built-in) and these don't
+ /// have an associated face. This should be caught upstream and use
+ /// alternate logic.
+ SpecialHasNoFace,
+
+ /// Invalid index.
+ IndexOutOfBounds,
+};
+
/// Get the unaliased entry from an index
-pub fn getEntry(self: *Collection, index: Index) !*Entry {
+pub fn getEntry(self: *Collection, index: Index) EntryError!*Entry {
if (index.special() != null) return error.SpecialHasNoFace;
const list = self.faces.getPtr(index.style);
if (index.idx >= list.len) return error.IndexOutOfBounds;
diff --git a/src/font/embedded.zig b/src/font/embedded.zig
index 1e496075d..8e0ae33b8 100644
--- a/src/font/embedded.zig
+++ b/src/font/embedded.zig
@@ -47,3 +47,9 @@ pub const monaspace_neon = @embedFile("res/MonaspaceNeon-Regular.otf");
/// Terminus TTF is a scalable font with bitmap glyphs at various sizes.
pub const terminus_ttf = @embedFile("res/TerminusTTF-Regular.ttf");
+
+/// Spleen is a monospaced bitmap font available in multiple formats.
+/// Used for testing bitmap font support across different file formats.
+pub const spleen_bdf = @embedFile("res/spleen-8x16.bdf");
+pub const spleen_pcf = @embedFile("res/spleen-8x16.pcf");
+pub const spleen_otb = @embedFile("res/spleen-8x16.otb");
diff --git a/src/font/face/freetype.zig b/src/font/face/freetype.zig
index a6ef52c39..827753254 100644
--- a/src/font/face/freetype.zig
+++ b/src/font/face/freetype.zig
@@ -1284,3 +1284,153 @@ test "bitmap glyph" {
}
}
}
+
+// Expected pixel pattern for Spleen 8x16 'A' (glyph index from char 'A')
+// Derived from BDF BITMAP data: 00,00,7C,C6,C6,C6,FE,C6,C6,C6,C6,C6,00,00,00,00
+const spleen_A =
+ \\........
+ \\........
+ \\.#####..
+ \\##...##.
+ \\##...##.
+ \\##...##.
+ \\#######.
+ \\##...##.
+ \\##...##.
+ \\##...##.
+ \\##...##.
+ \\##...##.
+ \\........
+ \\........
+ \\........
+ \\........
+;
+// Including the newline
+const spleen_A_pitch = 9;
+// Test parameters for bitmap font tests
+const spleen_test_point_size = 12;
+const spleen_test_dpi = 96;
+
+test "bitmap glyph BDF" {
+ const alloc = testing.allocator;
+ const testFont = font.embedded.spleen_bdf;
+
+ var lib = try Library.init(alloc);
+ defer lib.deinit();
+
+ var atlas = try font.Atlas.init(alloc, 512, .grayscale);
+ defer atlas.deinit(alloc);
+
+ // Spleen 8x16 is a pure bitmap font at 16px height
+ var ft_font = try Face.init(lib, testFont, .{ .size = .{
+ .points = spleen_test_point_size,
+ .xdpi = spleen_test_dpi,
+ .ydpi = spleen_test_dpi,
+ } });
+ defer ft_font.deinit();
+
+ // Get glyph index for 'A'
+ const glyph_index = ft_font.glyphIndex('A') orelse return error.GlyphNotFound;
+
+ const glyph = try ft_font.renderGlyph(
+ alloc,
+ &atlas,
+ glyph_index,
+ .{ .grid_metrics = font.Metrics.calc(ft_font.getMetrics()) },
+ );
+
+ // Verify dimensions match Spleen 8x16
+ try testing.expectEqual(8, glyph.width);
+ try testing.expectEqual(16, glyph.height);
+
+ // Verify pixel-perfect rendering
+ for (0..glyph.height) |y| {
+ for (0..glyph.width) |x| {
+ const pixel = spleen_A[y * spleen_A_pitch + x];
+ try testing.expectEqual(
+ @as(u8, if (pixel == '#') 255 else 0),
+ atlas.data[(glyph.atlas_y + y) * atlas.size + (glyph.atlas_x + x)],
+ );
+ }
+ }
+}
+
+test "bitmap glyph PCF" {
+ const alloc = testing.allocator;
+ const testFont = font.embedded.spleen_pcf;
+
+ var lib = try Library.init(alloc);
+ defer lib.deinit();
+
+ var atlas = try font.Atlas.init(alloc, 512, .grayscale);
+ defer atlas.deinit(alloc);
+
+ var ft_font = try Face.init(lib, testFont, .{ .size = .{
+ .points = spleen_test_point_size,
+ .xdpi = spleen_test_dpi,
+ .ydpi = spleen_test_dpi,
+ } });
+ defer ft_font.deinit();
+
+ const glyph_index = ft_font.glyphIndex('A') orelse return error.GlyphNotFound;
+
+ const glyph = try ft_font.renderGlyph(
+ alloc,
+ &atlas,
+ glyph_index,
+ .{ .grid_metrics = font.Metrics.calc(ft_font.getMetrics()) },
+ );
+
+ try testing.expectEqual(8, glyph.width);
+ try testing.expectEqual(16, glyph.height);
+
+ for (0..glyph.height) |y| {
+ for (0..glyph.width) |x| {
+ const pixel = spleen_A[y * spleen_A_pitch + x];
+ try testing.expectEqual(
+ @as(u8, if (pixel == '#') 255 else 0),
+ atlas.data[(glyph.atlas_y + y) * atlas.size + (glyph.atlas_x + x)],
+ );
+ }
+ }
+}
+
+test "bitmap glyph OTB" {
+ const alloc = testing.allocator;
+ const testFont = font.embedded.spleen_otb;
+
+ var lib = try Library.init(alloc);
+ defer lib.deinit();
+
+ var atlas = try font.Atlas.init(alloc, 512, .grayscale);
+ defer atlas.deinit(alloc);
+
+ var ft_font = try Face.init(lib, testFont, .{ .size = .{
+ .points = spleen_test_point_size,
+ .xdpi = spleen_test_dpi,
+ .ydpi = spleen_test_dpi,
+ } });
+ defer ft_font.deinit();
+
+ const glyph_index = ft_font.glyphIndex('A') orelse return error.GlyphNotFound;
+
+ const glyph = try ft_font.renderGlyph(
+ alloc,
+ &atlas,
+ glyph_index,
+ .{ .grid_metrics = font.Metrics.calc(ft_font.getMetrics()) },
+ );
+
+ try testing.expectEqual(8, glyph.width);
+ try testing.expectEqual(16, glyph.height);
+
+ for (0..glyph.height) |y| {
+ for (0..glyph.width) |x| {
+ const pixel = spleen_A[y * spleen_A_pitch + x];
+ try testing.expectEqual(
+ @as(u8, if (pixel == '#') 255 else 0),
+ atlas.data[(glyph.atlas_y + y) * atlas.size + (glyph.atlas_x + x)],
+ );
+ }
+ }
+}
diff --git a/src/font/res/BSD-2-Clause.txt b/src/font/res/BSD-2-Clause.txt
new file mode 100644
index 000000000..4387948e8
--- /dev/null
+++ b/src/font/res/BSD-2-Clause.txt
@@ -0,0 +1,24 @@
+Copyright (c) 2018-2024, Frederic Cambus
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS
+BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
diff --git a/src/font/res/README.md b/src/font/res/README.md
index 5ad4b274f..b4d77a783 100644
--- a/src/font/res/README.md
+++ b/src/font/res/README.md
@@ -1,6 +1,6 @@
# Fonts and Licenses
-This project uses several fonts which fall under the SIL Open Font License (OFL-1.1) and MIT License:
+This project uses several fonts which fall under the SIL Open Font License (OFL-1.1), MIT License, and BSD 2-Clause License:
- Code New Roman (OFL-1.1)
- [ยฉ 2014 Sam Radian. All Rights Reserved.](https://github.com/chrissimpkins/codeface/blob/master/fonts/code-new-roman/license.txt)
@@ -28,8 +28,12 @@ This project uses several fonts which fall under the SIL Open Font License (OFL-
- Terminus TTF (OFL-1.1)
- [Copyright (c) 2010-2020 Dimitar Toshkov Zhekov with Reserved Font Name "Terminus Font"](https://sourceforge.net/projects/terminus-font/)
- [Copyright (c) 2011-2023 Tilman Blumenbach with Reserved Font Name "Terminus (TTF)"](https://files.ax86.net/terminus-ttf/)
+- Spleen (BSD 2-Clause)
+ - [Copyright (c) 2018-2024, Frederic Cambus](https://github.com/fcambus/spleen)
A full copy of the OFL license can be found at [OFL.txt](./OFL.txt).
An accompanying FAQ is also available at .
A full copy of the MIT license can be found at [MIT.txt](./MIT.txt).
+
+A full copy of the BSD 2-Clause license can be found at [BSD-2-Clause.txt](./BSD-2-Clause.txt).
diff --git a/src/font/res/spleen-8x16.bdf b/src/font/res/spleen-8x16.bdf
new file mode 100644
index 000000000..5c7c2684b
--- /dev/null
+++ b/src/font/res/spleen-8x16.bdf
@@ -0,0 +1,22328 @@
+STARTFONT 2.1
+COMMENT /*
+COMMENT * Spleen 8x16 2.1.0
+COMMENT * Copyright (c) 2018-2024, Frederic Cambus
+COMMENT * https://www.cambus.net/
+COMMENT *
+COMMENT * Created: 2018-08-11
+COMMENT * Last Updated: 2024-01-27
+COMMENT *
+COMMENT * Spleen is released under the BSD 2-Clause license.
+COMMENT * See LICENSE file for details.
+COMMENT *
+COMMENT * SPDX-License-Identifier: BSD-2-Clause
+COMMENT */
+FONT -misc-spleen-medium-r-normal--16-160-72-72-C-80-ISO10646-1
+SIZE 16 72 72
+FONTBOUNDINGBOX 8 16 0 -4
+STARTPROPERTIES 20
+FAMILY_NAME "Spleen"
+WEIGHT_NAME "Medium"
+FONT_VERSION "2.1.0"
+FOUNDRY "misc"
+SLANT "R"
+SETWIDTH_NAME "Normal"
+PIXEL_SIZE 16
+POINT_SIZE 160
+RESOLUTION_X 72
+RESOLUTION_Y 72
+SPACING "C"
+AVERAGE_WIDTH 80
+CHARSET_REGISTRY "ISO10646"
+CHARSET_ENCODING "1"
+MIN_SPACE 8
+FONT_ASCENT 12
+FONT_DESCENT 4
+COPYRIGHT "Copyright (c) 2018-2024, Frederic Cambus"
+DEFAULT_CHAR 32
+_GBDFED_INFO "Edited with gbdfed 1.6."
+ENDPROPERTIES
+CHARS 969
+STARTCHAR SPACE
+ENCODING 32
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR EXCLAMATION MARK
+ENCODING 33
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+18
+18
+18
+18
+18
+18
+18
+00
+18
+18
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR QUOTATION MARK
+ENCODING 34
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+66
+66
+66
+66
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR NUMBER SIGN
+ENCODING 35
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+6C
+6C
+FE
+6C
+6C
+6C
+6C
+FE
+6C
+6C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR DOLLAR SIGN
+ENCODING 36
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+10
+7E
+D0
+D0
+D0
+7C
+16
+16
+16
+16
+FC
+10
+00
+00
+00
+ENDCHAR
+STARTCHAR PERCENT SIGN
+ENCODING 37
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+06
+66
+6C
+0C
+18
+18
+30
+36
+66
+60
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR AMPERSAND
+ENCODING 38
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+38
+6C
+6C
+6C
+38
+70
+DA
+CC
+CC
+7A
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR APOSTROPHE
+ENCODING 39
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+18
+18
+18
+18
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LEFT PARENTHESIS
+ENCODING 40
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+0E
+18
+30
+30
+60
+60
+60
+60
+30
+30
+18
+0E
+00
+00
+00
+ENDCHAR
+STARTCHAR RIGHT PARENTHESIS
+ENCODING 41
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+70
+18
+0C
+0C
+06
+06
+06
+06
+0C
+0C
+18
+70
+00
+00
+00
+ENDCHAR
+STARTCHAR ASTERISK
+ENCODING 42
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+66
+3C
+18
+FF
+18
+3C
+66
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR PLUS SIGN
+ENCODING 43
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+18
+18
+7E
+18
+18
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR COMMA
+ENCODING 44
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+18
+18
+30
+00
+00
+00
+ENDCHAR
+STARTCHAR HYPHEN-MINUS
+ENCODING 45
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+7E
+00
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR FULL STOP
+ENCODING 46
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+18
+18
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR SOLIDUS
+ENCODING 47
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+06
+06
+0C
+0C
+18
+18
+30
+30
+60
+60
+C0
+C0
+00
+00
+00
+ENDCHAR
+STARTCHAR DIGIT ZERO
+ENCODING 48
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+7C
+C6
+C6
+CE
+DE
+F6
+E6
+C6
+C6
+7C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR DIGIT ONE
+ENCODING 49
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+18
+38
+78
+58
+18
+18
+18
+18
+18
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR DIGIT TWO
+ENCODING 50
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+7C
+C6
+06
+06
+0C
+18
+30
+60
+C6
+FE
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR DIGIT THREE
+ENCODING 51
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+7C
+C6
+06
+06
+3C
+06
+06
+06
+C6
+7C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR DIGIT FOUR
+ENCODING 52
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+C0
+C0
+CC
+CC
+CC
+CC
+FE
+0C
+0C
+0C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR DIGIT FIVE
+ENCODING 53
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+FE
+C6
+C0
+C0
+FC
+06
+06
+06
+C6
+7C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR DIGIT SIX
+ENCODING 54
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+7C
+C6
+C0
+C0
+FC
+C6
+C6
+C6
+C6
+7C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR DIGIT SEVEN
+ENCODING 55
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+FE
+C6
+06
+06
+0C
+18
+30
+30
+30
+30
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR DIGIT EIGHT
+ENCODING 56
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+7C
+C6
+C6
+C6
+7C
+C6
+C6
+C6
+C6
+7C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR DIGIT NINE
+ENCODING 57
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+7C
+C6
+C6
+C6
+C6
+7E
+06
+06
+C6
+7C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR COLON
+ENCODING 58
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+18
+18
+00
+00
+00
+18
+18
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR SEMICOLON
+ENCODING 59
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+18
+18
+00
+00
+00
+18
+18
+30
+00
+00
+00
+ENDCHAR
+STARTCHAR LESS-THAN SIGN
+ENCODING 60
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+06
+0C
+18
+30
+60
+60
+30
+18
+0C
+06
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR EQUALS SIGN
+ENCODING 61
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+7E
+00
+00
+7E
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR GREATER-THAN SIGN
+ENCODING 62
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+60
+30
+18
+0C
+06
+06
+0C
+18
+30
+60
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR QUESTION MARK
+ENCODING 63
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+7C
+C6
+06
+0C
+18
+30
+30
+00
+30
+30
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR COMMERCIAL AT
+ENCODING 64
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+7C
+C2
+DA
+DA
+DA
+DA
+DE
+C0
+7C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER A
+ENCODING 65
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+7C
+C6
+C6
+C6
+FE
+C6
+C6
+C6
+C6
+C6
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER B
+ENCODING 66
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+FC
+C6
+C6
+C6
+FC
+C6
+C6
+C6
+C6
+FC
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER C
+ENCODING 67
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+7E
+C0
+C0
+C0
+C0
+C0
+C0
+C0
+C0
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER D
+ENCODING 68
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+FC
+C6
+C6
+C6
+C6
+C6
+C6
+C6
+C6
+FC
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER E
+ENCODING 69
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+7E
+C0
+C0
+C0
+F8
+C0
+C0
+C0
+C0
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER F
+ENCODING 70
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+7E
+C0
+C0
+C0
+F8
+C0
+C0
+C0
+C0
+C0
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER G
+ENCODING 71
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+7E
+C0
+C0
+C0
+DE
+C6
+C6
+C6
+C6
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER H
+ENCODING 72
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+C6
+C6
+C6
+C6
+FE
+C6
+C6
+C6
+C6
+C6
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER I
+ENCODING 73
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+7E
+18
+18
+18
+18
+18
+18
+18
+18
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER J
+ENCODING 74
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+7E
+18
+18
+18
+18
+18
+18
+18
+18
+F0
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER K
+ENCODING 75
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+C6
+C6
+C6
+CC
+F8
+CC
+C6
+C6
+C6
+C6
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER L
+ENCODING 76
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+C0
+C0
+C0
+C0
+C0
+C0
+C0
+C0
+C0
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER M
+ENCODING 77
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+C6
+EE
+FE
+D6
+C6
+C6
+C6
+C6
+C6
+C6
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER N
+ENCODING 78
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+C6
+C6
+E6
+E6
+D6
+D6
+CE
+CE
+C6
+C6
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER O
+ENCODING 79
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+7C
+C6
+C6
+C6
+C6
+C6
+C6
+C6
+C6
+7C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER P
+ENCODING 80
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+FC
+C6
+C6
+C6
+FC
+C0
+C0
+C0
+C0
+C0
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER Q
+ENCODING 81
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+7C
+C6
+C6
+C6
+C6
+C6
+C6
+D6
+D6
+7C
+18
+0C
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER R
+ENCODING 82
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+FC
+C6
+C6
+C6
+FC
+C6
+C6
+C6
+C6
+C6
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER S
+ENCODING 83
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+7E
+C0
+C0
+C0
+7C
+06
+06
+06
+06
+FC
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER T
+ENCODING 84
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+FF
+18
+18
+18
+18
+18
+18
+18
+18
+18
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER U
+ENCODING 85
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+C6
+C6
+C6
+C6
+C6
+C6
+C6
+C6
+C6
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER V
+ENCODING 86
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+C6
+C6
+C6
+C6
+C6
+C6
+C6
+6C
+38
+10
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER W
+ENCODING 87
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+C6
+C6
+C6
+C6
+C6
+C6
+D6
+FE
+EE
+C6
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER X
+ENCODING 88
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+C6
+C6
+C6
+6C
+38
+6C
+C6
+C6
+C6
+C6
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER Y
+ENCODING 89
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+C6
+C6
+C6
+C6
+7E
+06
+06
+06
+06
+FC
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER Z
+ENCODING 90
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+FE
+06
+06
+0C
+18
+30
+60
+C0
+C0
+FE
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LEFT SQUARE BRACKET
+ENCODING 91
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+3E
+30
+30
+30
+30
+30
+30
+30
+30
+30
+30
+3E
+00
+00
+00
+ENDCHAR
+STARTCHAR REVERSE SOLIDUS
+ENCODING 92
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+C0
+C0
+60
+60
+30
+30
+18
+18
+0C
+0C
+06
+06
+00
+00
+00
+ENDCHAR
+STARTCHAR RIGHT SQUARE BRACKET
+ENCODING 93
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+7C
+0C
+0C
+0C
+0C
+0C
+0C
+0C
+0C
+0C
+0C
+7C
+00
+00
+00
+ENDCHAR
+STARTCHAR CIRCUMFLEX ACCENT
+ENCODING 94
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+10
+38
+6C
+C6
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LOW LINE
+ENCODING 95
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+FE
+00
+ENDCHAR
+STARTCHAR GRAVE ACCENT
+ENCODING 96
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+30
+18
+0C
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER A
+ENCODING 97
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+7C
+06
+7E
+C6
+C6
+C6
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER B
+ENCODING 98
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+C0
+C0
+C0
+FC
+C6
+C6
+C6
+C6
+C6
+FC
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER C
+ENCODING 99
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+7E
+C0
+C0
+C0
+C0
+C0
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER D
+ENCODING 100
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+06
+06
+06
+7E
+C6
+C6
+C6
+C6
+C6
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER E
+ENCODING 101
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+7E
+C6
+C6
+FE
+C0
+C0
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER F
+ENCODING 102
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+1E
+30
+30
+30
+7C
+30
+30
+30
+30
+30
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER G
+ENCODING 103
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+7E
+C6
+C6
+C6
+C6
+C6
+7C
+06
+06
+FC
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER H
+ENCODING 104
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+C0
+C0
+C0
+FC
+C6
+C6
+C6
+C6
+C6
+C6
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER I
+ENCODING 105
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+18
+18
+00
+38
+18
+18
+18
+18
+18
+1C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER J
+ENCODING 106
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+18
+18
+00
+18
+18
+18
+18
+18
+18
+18
+18
+18
+70
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER K
+ENCODING 107
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+C0
+C0
+C0
+CC
+D8
+F0
+F0
+D8
+CC
+C6
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER L
+ENCODING 108
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+30
+30
+30
+30
+30
+30
+30
+30
+30
+1C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER M
+ENCODING 109
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+EC
+D6
+D6
+D6
+D6
+C6
+C6
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER N
+ENCODING 110
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+FC
+C6
+C6
+C6
+C6
+C6
+C6
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER O
+ENCODING 111
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+7C
+C6
+C6
+C6
+C6
+C6
+7C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER P
+ENCODING 112
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+FC
+C6
+C6
+C6
+C6
+C6
+FC
+C0
+C0
+C0
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER Q
+ENCODING 113
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+7E
+C6
+C6
+C6
+C6
+C6
+7E
+06
+06
+06
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER R
+ENCODING 114
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+7E
+C6
+C0
+C0
+C0
+C0
+C0
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER S
+ENCODING 115
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+7E
+C0
+C0
+7C
+06
+06
+FC
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER T
+ENCODING 116
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+30
+30
+30
+7C
+30
+30
+30
+30
+30
+1E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER U
+ENCODING 117
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+C6
+C6
+C6
+C6
+C6
+C6
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER V
+ENCODING 118
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+C6
+C6
+C6
+C6
+6C
+38
+10
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER W
+ENCODING 119
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+C6
+C6
+D6
+D6
+D6
+D6
+6E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER X
+ENCODING 120
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+C6
+6C
+38
+38
+6C
+C6
+C6
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER Y
+ENCODING 121
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+C6
+C6
+C6
+C6
+C6
+C6
+7E
+06
+06
+FC
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER Z
+ENCODING 122
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+FE
+06
+0C
+18
+30
+60
+FE
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LEFT CURLY BRACKET
+ENCODING 123
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+0E
+18
+18
+18
+18
+70
+70
+18
+18
+18
+18
+0E
+00
+00
+00
+ENDCHAR
+STARTCHAR VERTICAL LINE
+ENCODING 124
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+18
+18
+18
+18
+18
+18
+18
+18
+18
+18
+18
+18
+00
+00
+00
+ENDCHAR
+STARTCHAR RIGHT CURLY BRACKET
+ENCODING 125
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+70
+18
+18
+18
+18
+0E
+0E
+18
+18
+18
+18
+70
+00
+00
+00
+ENDCHAR
+STARTCHAR TILDE
+ENCODING 126
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+32
+7E
+4C
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR
+ENCODING 127
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR NO-BREAK SPACE
+ENCODING 160
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR INVERTED EXCLAMATION MARK
+ENCODING 161
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+18
+18
+00
+18
+18
+18
+18
+18
+18
+18
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR CENT SIGN
+ENCODING 162
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+08
+7E
+C8
+C8
+C8
+C8
+C8
+7E
+08
+00
+00
+00
+ENDCHAR
+STARTCHAR POUND SIGN
+ENCODING 163
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+38
+6C
+60
+60
+60
+F8
+60
+60
+C0
+FE
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR CURRENCY SIGN
+ENCODING 164
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+66
+3C
+66
+66
+66
+3C
+66
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR YEN SIGN
+ENCODING 165
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+C3
+C3
+66
+3C
+18
+3C
+18
+3C
+18
+18
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BROKEN BAR
+ENCODING 166
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+18
+18
+18
+18
+18
+00
+00
+18
+18
+18
+18
+18
+00
+00
+ENDCHAR
+STARTCHAR SECTION SIGN
+ENCODING 167
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+3C
+66
+60
+30
+3C
+66
+66
+66
+66
+3C
+0C
+06
+66
+3C
+00
+ENDCHAR
+STARTCHAR DIAERESIS
+ENCODING 168
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+6C
+6C
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR COPYRIGHT SIGN
+ENCODING 169
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+7C
+82
+9A
+A2
+A2
+A2
+9A
+82
+7C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR FEMININE ORDINAL INDICATOR
+ENCODING 170
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+38
+0C
+3C
+4C
+3C
+00
+7C
+00
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+ENCODING 171
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+33
+66
+CC
+66
+33
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR NOT SIGN
+ENCODING 172
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+FE
+06
+06
+06
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR SOFT HYPHEN
+ENCODING 173
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+00
+3C
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR REGISTERED SIGN
+ENCODING 174
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+7C
+82
+BA
+AA
+B2
+AA
+AA
+82
+7C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR MACRON
+ENCODING 175
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+7C
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR DEGREE SIGN
+ENCODING 176
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+38
+6C
+6C
+38
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR PLUS-MINUS SIGN
+ENCODING 177
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+18
+18
+7E
+18
+18
+00
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR SUPERSCRIPT TWO
+ENCODING 178
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+38
+4C
+0C
+38
+60
+7C
+00
+00
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR SUPERSCRIPT THREE
+ENCODING 179
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+1C
+26
+0C
+06
+26
+1C
+00
+00
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR ACUTE ACCENT
+ENCODING 180
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+0C
+18
+30
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR MICRO SIGN
+ENCODING 181
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+CC
+CC
+CC
+CC
+CC
+CC
+F6
+C0
+C0
+C0
+00
+ENDCHAR
+STARTCHAR PILCROW SIGN
+ENCODING 182
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+7E
+D6
+D6
+D6
+76
+16
+16
+16
+16
+16
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR MIDDLE DOT
+ENCODING 183
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+18
+18
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR CEDILLA
+ENCODING 184
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+18
+18
+30
+00
+ENDCHAR
+STARTCHAR SUPERSCRIPT ONE
+ENCODING 185
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+18
+38
+18
+18
+18
+3C
+00
+00
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR MASCULINE ORDINAL INDICATOR
+ENCODING 186
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+38
+6C
+6C
+38
+00
+00
+7C
+00
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+ENCODING 187
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+CC
+66
+33
+66
+CC
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR VULGAR FRACTION ONE QUARTER
+ENCODING 188
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+40
+C0
+40
+42
+46
+EC
+18
+30
+70
+D4
+94
+1E
+04
+04
+00
+ENDCHAR
+STARTCHAR VULGAR FRACTION ONE HALF
+ENCODING 189
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+40
+C0
+40
+42
+46
+EC
+18
+30
+6C
+D2
+82
+0C
+10
+1E
+00
+ENDCHAR
+STARTCHAR VULGAR FRACTION THREE QUARTERS
+ENCODING 190
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+60
+90
+20
+12
+96
+6C
+18
+30
+70
+D4
+94
+1E
+04
+04
+00
+ENDCHAR
+STARTCHAR INVERTED QUESTION MARK
+ENCODING 191
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+18
+18
+00
+18
+18
+30
+60
+C0
+C6
+7C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER A WITH GRAVE
+ENCODING 192
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+30
+18
+00
+7C
+C6
+C6
+C6
+FE
+C6
+C6
+C6
+C6
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER A WITH ACUTE
+ENCODING 193
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+18
+30
+00
+7C
+C6
+C6
+C6
+FE
+C6
+C6
+C6
+C6
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER A WITH CIRCUMFLEX
+ENCODING 194
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+38
+6C
+00
+7C
+C6
+C6
+C6
+FE
+C6
+C6
+C6
+C6
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER A WITH TILDE
+ENCODING 195
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+32
+4C
+00
+7C
+C6
+C6
+C6
+FE
+C6
+C6
+C6
+C6
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER A WITH DIAERESIS
+ENCODING 196
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+6C
+6C
+00
+7C
+C6
+C6
+C6
+FE
+C6
+C6
+C6
+C6
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER A WITH RING ABOVE
+ENCODING 197
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+38
+6C
+38
+7C
+C6
+C6
+C6
+FE
+C6
+C6
+C6
+C6
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER AE
+ENCODING 198
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+7E
+D8
+D8
+D8
+FE
+D8
+D8
+D8
+D8
+DE
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER C WITH CEDILLA
+ENCODING 199
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+7E
+C0
+C0
+C0
+C0
+C0
+C0
+C0
+C0
+7E
+18
+18
+30
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER E WITH GRAVE
+ENCODING 200
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+30
+18
+00
+7E
+C0
+C0
+C0
+F8
+C0
+C0
+C0
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER E WITH ACUTE
+ENCODING 201
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+18
+30
+00
+7E
+C0
+C0
+C0
+F8
+C0
+C0
+C0
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER E WITH CIRCUMFLEX
+ENCODING 202
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+38
+6C
+00
+7E
+C0
+C0
+C0
+F8
+C0
+C0
+C0
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER E WITH DIAERESIS
+ENCODING 203
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+6C
+6C
+00
+7E
+C0
+C0
+C0
+F8
+C0
+C0
+C0
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER I WITH GRAVE
+ENCODING 204
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+30
+18
+00
+7E
+18
+18
+18
+18
+18
+18
+18
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER I WITH ACUTE
+ENCODING 205
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+0C
+18
+00
+7E
+18
+18
+18
+18
+18
+18
+18
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER I WITH CIRCUMFLEX
+ENCODING 206
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+38
+6C
+00
+7E
+18
+18
+18
+18
+18
+18
+18
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER I WITH DIAERESIS
+ENCODING 207
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+66
+66
+00
+7E
+18
+18
+18
+18
+18
+18
+18
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER ETH
+ENCODING 208
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+7C
+66
+66
+66
+F6
+66
+66
+66
+66
+7C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER N WITH TILDE
+ENCODING 209
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+32
+4C
+00
+C6
+E6
+E6
+D6
+D6
+CE
+CE
+C6
+C6
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER O WITH GRAVE
+ENCODING 210
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+30
+18
+00
+7C
+C6
+C6
+C6
+C6
+C6
+C6
+C6
+7C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER O WITH ACUTE
+ENCODING 211
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+18
+30
+00
+7C
+C6
+C6
+C6
+C6
+C6
+C6
+C6
+7C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER O WITH CIRCUMFLEX
+ENCODING 212
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+38
+6C
+00
+7C
+C6
+C6
+C6
+C6
+C6
+C6
+C6
+7C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER O WITH TILDE
+ENCODING 213
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+32
+4C
+00
+7C
+C6
+C6
+C6
+C6
+C6
+C6
+C6
+7C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER O WITH DIAERESIS
+ENCODING 214
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+6C
+6C
+00
+7C
+C6
+C6
+C6
+C6
+C6
+C6
+C6
+7C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR MULTIPLICATION SIGN
+ENCODING 215
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+C6
+6C
+38
+38
+6C
+C6
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER O WITH STROKE
+ENCODING 216
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+02
+7C
+C6
+CE
+CE
+D6
+D6
+E6
+E6
+C6
+7C
+80
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER U WITH GRAVE
+ENCODING 217
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+30
+18
+00
+C6
+C6
+C6
+C6
+C6
+C6
+C6
+C6
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER U WITH ACUTE
+ENCODING 218
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+18
+30
+00
+C6
+C6
+C6
+C6
+C6
+C6
+C6
+C6
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER U WITH CIRCUMFLEX
+ENCODING 219
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+38
+6C
+00
+C6
+C6
+C6
+C6
+C6
+C6
+C6
+C6
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER U WITH DIAERESIS
+ENCODING 220
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+6C
+6C
+00
+C6
+C6
+C6
+C6
+C6
+C6
+C6
+C6
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER Y WITH ACUTE
+ENCODING 221
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+18
+30
+00
+C6
+C6
+C6
+C6
+7E
+06
+06
+06
+FC
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER THORN
+ENCODING 222
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+C0
+C0
+FC
+C6
+C6
+C6
+C6
+C6
+FC
+C0
+C0
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER SHARP S
+ENCODING 223
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+78
+CC
+CC
+CC
+D8
+CC
+C6
+C6
+D6
+DC
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER A WITH GRAVE
+ENCODING 224
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+60
+30
+18
+00
+7C
+06
+7E
+C6
+C6
+C6
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER A WITH ACUTE
+ENCODING 225
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+0C
+18
+30
+00
+7C
+06
+7E
+C6
+C6
+C6
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER A WITH CIRCUMFLEX
+ENCODING 226
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+10
+38
+6C
+00
+7C
+06
+7E
+C6
+C6
+C6
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER A WITH TILDE
+ENCODING 227
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+32
+7E
+4C
+00
+7C
+06
+7E
+C6
+C6
+C6
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER A WITH DIAERESIS
+ENCODING 228
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+6C
+6C
+00
+7C
+06
+7E
+C6
+C6
+C6
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER A WITH RING ABOVE
+ENCODING 229
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+38
+6C
+38
+00
+7C
+06
+7E
+C6
+C6
+C6
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER AE
+ENCODING 230
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+6E
+16
+16
+7E
+D0
+D0
+6E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER C WITH CEDILLA
+ENCODING 231
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+7E
+C0
+C0
+C0
+C0
+C0
+7E
+18
+18
+30
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER E WITH GRAVE
+ENCODING 232
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+60
+30
+18
+00
+7E
+C6
+C6
+FE
+C0
+C0
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER E WITH ACUTE
+ENCODING 233
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+0C
+18
+30
+00
+7E
+C6
+C6
+FE
+C0
+C0
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER E WITH CIRCUMFLEX
+ENCODING 234
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+10
+38
+6C
+00
+7E
+C6
+C6
+FE
+C0
+C0
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER E WITH DIAERESIS
+ENCODING 235
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+6C
+6C
+00
+7E
+C6
+C6
+FE
+C0
+C0
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER I WITH GRAVE
+ENCODING 236
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+30
+18
+0C
+00
+38
+18
+18
+18
+18
+18
+1C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER I WITH ACUTE
+ENCODING 237
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+0C
+18
+30
+00
+38
+18
+18
+18
+18
+18
+1C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER I WITH CIRCUMFLEX
+ENCODING 238
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+18
+3C
+66
+00
+38
+18
+18
+18
+18
+18
+1C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER I WITH DIAERESIS
+ENCODING 239
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+66
+66
+00
+38
+18
+18
+18
+18
+18
+1C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER ETH
+ENCODING 240
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+D8
+70
+D8
+0C
+7C
+C6
+C6
+C6
+C6
+C6
+7C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER N WITH TILDE
+ENCODING 241
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+32
+7E
+4C
+00
+FC
+C6
+C6
+C6
+C6
+C6
+C6
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER O WITH GRAVE
+ENCODING 242
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+60
+30
+18
+00
+7C
+C6
+C6
+C6
+C6
+C6
+7C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER O WITH ACUTE
+ENCODING 243
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+0C
+18
+30
+00
+7C
+C6
+C6
+C6
+C6
+C6
+7C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER O WITH CIRCUMFLEX
+ENCODING 244
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+10
+38
+6C
+00
+7C
+C6
+C6
+C6
+C6
+C6
+7C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER O WITH TILDE
+ENCODING 245
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+32
+7E
+4C
+00
+7C
+C6
+C6
+C6
+C6
+C6
+7C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER O WITH DIAERESIS
+ENCODING 246
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+6C
+6C
+00
+7C
+C6
+C6
+C6
+C6
+C6
+7C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR DIVISION SIGN
+ENCODING 247
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+18
+18
+00
+7E
+00
+18
+18
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER O WITH STROKE
+ENCODING 248
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+02
+7C
+C6
+CE
+D6
+E6
+C6
+7C
+80
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER U WITH GRAVE
+ENCODING 249
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+60
+30
+18
+00
+C6
+C6
+C6
+C6
+C6
+C6
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER U WITH ACUTE
+ENCODING 250
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+0C
+18
+30
+00
+C6
+C6
+C6
+C6
+C6
+C6
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER U WITH CIRCUMFLEX
+ENCODING 251
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+10
+38
+6C
+00
+C6
+C6
+C6
+C6
+C6
+C6
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER U WITH DIAERESIS
+ENCODING 252
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+6C
+6C
+00
+C6
+C6
+C6
+C6
+C6
+C6
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER Y WITH ACUTE
+ENCODING 253
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+0C
+18
+30
+00
+C6
+C6
+C6
+C6
+C6
+C6
+7E
+06
+06
+FC
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER THORN
+ENCODING 254
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+C0
+C0
+C0
+FC
+C6
+C6
+C6
+C6
+C6
+FC
+C0
+C0
+C0
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER Y WITH DIAERESIS
+ENCODING 255
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+6C
+6C
+00
+C6
+C6
+C6
+C6
+C6
+C6
+7E
+06
+06
+FC
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER A WITH MACRON
+ENCODING 256
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+7C
+00
+7C
+C6
+C6
+C6
+FE
+C6
+C6
+C6
+C6
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER A WITH MACRON
+ENCODING 257
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+7C
+00
+7C
+06
+7E
+C6
+C6
+C6
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER A WITH BREVE
+ENCODING 258
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+6C
+38
+00
+7C
+C6
+C6
+C6
+FE
+C6
+C6
+C6
+C6
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER A WITH BREVE
+ENCODING 259
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+6C
+6C
+38
+00
+7C
+06
+7E
+C6
+C6
+C6
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER A WITH OGONEK
+ENCODING 260
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+7C
+C6
+C6
+C6
+FE
+C6
+C6
+C6
+C6
+C6
+0C
+08
+06
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER A WITH OGONEK
+ENCODING 261
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+7C
+06
+7E
+C6
+C6
+C6
+7E
+0C
+08
+06
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER C WITH ACUTE
+ENCODING 262
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+18
+30
+00
+7E
+C0
+C0
+C0
+C0
+C0
+C0
+C0
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER C WITH ACUTE
+ENCODING 263
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+0C
+18
+30
+00
+7E
+C0
+C0
+C0
+C0
+C0
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER C WITH CIRCUMFLEX
+ENCODING 264
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+38
+6C
+00
+7E
+C0
+C0
+C0
+C0
+C0
+C0
+C0
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER C WITH CIRCUMFLEX
+ENCODING 265
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+10
+38
+6C
+00
+7E
+C0
+C0
+C0
+C0
+C0
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER C WITH DOT ABOVE
+ENCODING 266
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+18
+18
+00
+7E
+C0
+C0
+C0
+C0
+C0
+C0
+C0
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER C WITH DOT ABOVE
+ENCODING 267
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+18
+18
+00
+7E
+C0
+C0
+C0
+C0
+C0
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER C WITH CARON
+ENCODING 268
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+6C
+38
+10
+7E
+C0
+C0
+C0
+C0
+C0
+C0
+C0
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER C WITH CARON
+ENCODING 269
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+6C
+38
+10
+00
+7E
+C0
+C0
+C0
+C0
+C0
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER D WITH CARON
+ENCODING 270
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+6C
+38
+10
+FC
+C6
+C6
+C6
+C6
+C6
+C6
+C6
+FC
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER D WITH CARON
+ENCODING 271
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+6C
+38
+16
+06
+06
+7E
+C6
+C6
+C6
+C6
+C6
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER D WITH STROKE
+ENCODING 272
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+7C
+66
+66
+66
+F6
+66
+66
+66
+66
+7C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER D WITH STROKE
+ENCODING 273
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+06
+1F
+06
+7E
+C6
+C6
+C6
+C6
+C6
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER E WITH MACRON
+ENCODING 274
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+7C
+00
+7E
+C0
+C0
+C0
+F8
+C0
+C0
+C0
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER E WITH MACRON
+ENCODING 275
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+7C
+00
+7E
+C6
+C6
+FE
+C0
+C0
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER E WITH BREVE
+ENCODING 276
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+6C
+38
+00
+7E
+C0
+C0
+C0
+F8
+C0
+C0
+C0
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER E WITH BREVE
+ENCODING 277
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+6C
+6C
+38
+00
+7E
+C6
+C6
+FE
+C0
+C0
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER E WITH DOT ABOVE
+ENCODING 278
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+18
+18
+00
+7E
+C0
+C0
+C0
+F8
+C0
+C0
+C0
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER E WITH DOT ABOVE
+ENCODING 279
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+18
+18
+00
+7E
+C6
+C6
+FE
+C0
+C0
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER E WITH OGONEK
+ENCODING 280
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+7E
+C0
+C0
+C0
+F8
+C0
+C0
+C0
+C0
+7E
+0C
+08
+06
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER E WITH OGONEK
+ENCODING 281
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+7E
+C6
+C6
+FE
+C0
+C0
+7E
+0C
+08
+06
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER E WITH CARON
+ENCODING 282
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+6C
+38
+10
+7E
+C0
+C0
+C0
+F8
+C0
+C0
+C0
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER E WITH CARON
+ENCODING 283
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+6C
+38
+10
+00
+7E
+C6
+C6
+FE
+C0
+C0
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER G WITH CIRCUMFLEX
+ENCODING 284
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+38
+6C
+00
+7E
+C0
+C0
+C0
+DE
+C6
+C6
+C6
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER G WITH CIRCUMFLEX
+ENCODING 285
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+10
+38
+6C
+00
+7E
+C6
+C6
+C6
+C6
+C6
+7C
+06
+06
+FC
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER G WITH BREVE
+ENCODING 286
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+6C
+38
+00
+7E
+C0
+C0
+C0
+DE
+C6
+C6
+C6
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER G WITH BREVE
+ENCODING 287
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+6C
+6C
+38
+00
+7E
+C6
+C6
+C6
+C6
+C6
+7C
+06
+06
+FC
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER G WITH DOT ABOVE
+ENCODING 288
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+18
+18
+00
+7E
+C0
+C0
+C0
+DE
+C6
+C6
+C6
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER G WITH DOT ABOVE
+ENCODING 289
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+18
+18
+00
+7E
+C6
+C6
+C6
+C6
+C6
+7C
+06
+06
+FC
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER G WITH CEDILLA
+ENCODING 290
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+7E
+C0
+C0
+C0
+DE
+C6
+C6
+C6
+C6
+7E
+18
+18
+30
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER G WITH CEDILLA
+ENCODING 291
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+18
+18
+30
+00
+7E
+C6
+C6
+C6
+C6
+C6
+7C
+06
+06
+FC
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER H WITH CIRCUMFLEX
+ENCODING 292
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+38
+6C
+00
+C6
+C6
+C6
+C6
+FE
+C6
+C6
+C6
+C6
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER H WITH CIRCUMFLEX
+ENCODING 293
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+38
+6C
+00
+C0
+C0
+C0
+FC
+C6
+C6
+C6
+C6
+C6
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER H WITH STROKE
+ENCODING 294
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+C6
+FF
+C6
+C6
+FE
+C6
+C6
+C6
+C6
+C6
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER H WITH STROKE
+ENCODING 295
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+C0
+F0
+C0
+FC
+C6
+C6
+C6
+C6
+C6
+C6
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER I WITH TILDE
+ENCODING 296
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+32
+4C
+00
+7E
+18
+18
+18
+18
+18
+18
+18
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER I WITH TILDE
+ENCODING 297
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+32
+7E
+4C
+00
+38
+18
+18
+18
+18
+18
+1C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER I WITH MACRON
+ENCODING 298
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+7E
+00
+7E
+18
+18
+18
+18
+18
+18
+18
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER I WITH MACRON
+ENCODING 299
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+7E
+00
+38
+18
+18
+18
+18
+18
+1C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER I WITH BREVE
+ENCODING 300
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+66
+3C
+00
+7E
+18
+18
+18
+18
+18
+18
+18
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER I WITH BREVE
+ENCODING 301
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+66
+66
+3C
+00
+38
+18
+18
+18
+18
+18
+1C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER I WITH OGONEK
+ENCODING 302
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+7E
+18
+18
+18
+18
+18
+18
+18
+18
+7E
+0C
+08
+06
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER I WITH OGONEK
+ENCODING 303
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+18
+18
+00
+38
+18
+18
+18
+18
+18
+1C
+0C
+08
+06
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER I WITH DOT ABOVE
+ENCODING 304
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+18
+18
+00
+7E
+18
+18
+18
+18
+18
+18
+18
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER DOTLESS I
+ENCODING 305
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+38
+18
+18
+18
+18
+18
+1C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LIGATURE IJ
+ENCODING 306
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+F7
+66
+66
+66
+66
+66
+66
+66
+66
+EC
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LIGATURE IJ
+ENCODING 307
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+66
+66
+00
+E6
+66
+66
+66
+66
+66
+76
+06
+06
+1C
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER J WITH CIRCUMFLEX
+ENCODING 308
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+38
+6C
+00
+7E
+18
+18
+18
+18
+18
+18
+18
+F0
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER J WITH CIRCUMFLEX
+ENCODING 309
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+18
+3C
+66
+00
+18
+18
+18
+18
+18
+18
+18
+18
+18
+70
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER K WITH CEDILLA
+ENCODING 310
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+C6
+C6
+C6
+CC
+F8
+CC
+C6
+C6
+C6
+C6
+18
+18
+30
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER K WITH CEDILLA
+ENCODING 311
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+C0
+C0
+C0
+CC
+D8
+F0
+F0
+D8
+CC
+C6
+18
+18
+30
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER KRA
+ENCODING 312
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+C6
+CC
+D8
+F0
+D8
+CC
+C6
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER L WITH ACUTE
+ENCODING 313
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+18
+30
+00
+C0
+C0
+C0
+C0
+C0
+C0
+C0
+C0
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER L WITH ACUTE
+ENCODING 314
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+18
+30
+00
+30
+30
+30
+30
+30
+30
+30
+30
+1C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER L WITH CEDILLA
+ENCODING 315
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+C0
+C0
+C0
+C0
+C0
+C0
+C0
+C0
+C0
+7E
+18
+18
+30
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER L WITH CEDILLA
+ENCODING 316
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+30
+30
+30
+30
+30
+30
+30
+30
+30
+1C
+18
+18
+30
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER L WITH CARON
+ENCODING 317
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+6C
+38
+10
+C0
+C0
+C0
+C0
+C0
+C0
+C0
+C0
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER L WITH CARON
+ENCODING 318
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+6C
+38
+10
+30
+30
+30
+30
+30
+30
+30
+30
+1C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER L WITH MIDDLE DOT
+ENCODING 319
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+C0
+C0
+C0
+C0
+CC
+CC
+C0
+C0
+C0
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER L WITH MIDDLE DOT
+ENCODING 320
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+30
+30
+30
+30
+36
+36
+30
+30
+30
+1C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER L WITH STROKE
+ENCODING 321
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+60
+60
+68
+78
+70
+E0
+E0
+60
+60
+3E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER L WITH STROKE
+ENCODING 322
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+30
+30
+34
+3C
+38
+70
+70
+30
+30
+1C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER N WITH ACUTE
+ENCODING 323
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+18
+30
+00
+C6
+E6
+E6
+D6
+D6
+CE
+CE
+C6
+C6
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER N WITH ACUTE
+ENCODING 324
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+0C
+18
+30
+00
+FC
+C6
+C6
+C6
+C6
+C6
+C6
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER N WITH CEDILLA
+ENCODING 325
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+C6
+C6
+E6
+E6
+D6
+D6
+CE
+CE
+C6
+C6
+18
+18
+30
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER N WITH CEDILLA
+ENCODING 326
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+FC
+C6
+C6
+C6
+C6
+C6
+C6
+18
+18
+30
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER N WITH CARON
+ENCODING 327
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+6C
+38
+10
+C6
+E6
+E6
+D6
+D6
+CE
+CE
+C6
+C6
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER N WITH CARON
+ENCODING 328
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+6C
+38
+10
+00
+FC
+C6
+C6
+C6
+C6
+C6
+C6
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER N PRECEDED BY APOSTROPHE
+ENCODING 329
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+60
+60
+C0
+00
+FC
+C6
+C6
+C6
+C6
+C6
+C6
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER ENG
+ENCODING 330
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+C6
+C6
+E6
+E6
+D6
+D6
+CE
+CE
+C6
+C6
+06
+06
+0C
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER ENG
+ENCODING 331
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+FC
+C6
+C6
+C6
+C6
+C6
+C6
+06
+06
+0C
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER O WITH MACRON
+ENCODING 332
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+7C
+00
+7C
+C6
+C6
+C6
+C6
+C6
+C6
+C6
+7C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER O WITH MACRON
+ENCODING 333
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+7C
+00
+7C
+C6
+C6
+C6
+C6
+C6
+7C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER O WITH BREVE
+ENCODING 334
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+6C
+38
+00
+7C
+C6
+C6
+C6
+C6
+C6
+C6
+C6
+7C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER O WITH BREVE
+ENCODING 335
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+6C
+6C
+38
+00
+7C
+C6
+C6
+C6
+C6
+C6
+7C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER O WITH DOUBLE ACUTE
+ENCODING 336
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+66
+CC
+00
+7C
+C6
+C6
+C6
+C6
+C6
+C6
+C6
+7C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER O WITH DOUBLE ACUTE
+ENCODING 337
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+36
+6C
+D8
+00
+7C
+C6
+C6
+C6
+C6
+C6
+7C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LIGATURE OE
+ENCODING 338
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+7E
+D8
+D8
+D8
+DE
+D8
+D8
+D8
+D8
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LIGATURE OE
+ENCODING 339
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+6E
+D6
+D6
+DE
+D0
+D0
+6E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER R WITH ACUTE
+ENCODING 340
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+18
+30
+00
+FC
+C6
+C6
+C6
+FC
+C6
+C6
+C6
+C6
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER R WITH ACUTE
+ENCODING 341
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+0C
+18
+30
+00
+7E
+C6
+C0
+C0
+C0
+C0
+C0
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER R WITH CEDILLA
+ENCODING 342
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+FC
+C6
+C6
+C6
+FC
+C6
+C6
+C6
+C6
+C6
+18
+18
+30
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER R WITH CEDILLA
+ENCODING 343
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+7E
+C6
+C0
+C0
+C0
+C0
+C0
+18
+18
+30
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER R WITH CARON
+ENCODING 344
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+6C
+38
+10
+FC
+C6
+C6
+C6
+FC
+C6
+C6
+C6
+C6
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER R WITH CARON
+ENCODING 345
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+6C
+38
+10
+00
+7E
+C6
+C0
+C0
+C0
+C0
+C0
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER S WITH ACUTE
+ENCODING 346
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+18
+30
+00
+7E
+C0
+C0
+C0
+7C
+06
+06
+06
+FC
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER S WITH ACUTE
+ENCODING 347
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+0C
+18
+30
+00
+7E
+C0
+C0
+7C
+06
+06
+FC
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER S WITH CIRCUMFLEX
+ENCODING 348
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+38
+6C
+00
+7E
+C0
+C0
+C0
+7C
+06
+06
+06
+FC
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER S WITH CIRCUMFLEX
+ENCODING 349
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+10
+38
+6C
+00
+7E
+C0
+C0
+7C
+06
+06
+FC
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER S WITH CEDILLA
+ENCODING 350
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+7E
+C0
+C0
+C0
+7C
+06
+06
+06
+06
+FC
+18
+18
+30
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER S WITH CEDILLA
+ENCODING 351
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+7E
+C0
+C0
+7C
+06
+06
+FC
+18
+18
+30
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER S WITH CARON
+ENCODING 352
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+6C
+38
+10
+7E
+C0
+C0
+C0
+7C
+06
+06
+06
+FC
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER S WITH CARON
+ENCODING 353
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+6C
+38
+10
+00
+7E
+C0
+C0
+7C
+06
+06
+FC
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER T WITH CEDILLA
+ENCODING 354
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+FF
+18
+18
+18
+18
+18
+18
+18
+18
+18
+0C
+0C
+18
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER T WITH CEDILLA
+ENCODING 355
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+30
+30
+30
+7C
+30
+30
+30
+30
+30
+1E
+0C
+0C
+18
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER T WITH CARON
+ENCODING 356
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+6C
+38
+10
+FF
+18
+18
+18
+18
+18
+18
+18
+18
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER T WITH CARON
+ENCODING 357
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+6C
+38
+10
+30
+30
+7C
+30
+30
+30
+30
+30
+1E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER T WITH STROKE
+ENCODING 358
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+FF
+18
+18
+18
+18
+7E
+18
+18
+18
+18
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER T WITH STROKE
+ENCODING 359
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+30
+30
+30
+7C
+30
+7C
+30
+30
+30
+1E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER U WITH TILDE
+ENCODING 360
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+32
+4C
+00
+C6
+C6
+C6
+C6
+C6
+C6
+C6
+C6
+7C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER U WITH TILDE
+ENCODING 361
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+32
+7E
+4C
+00
+C6
+C6
+C6
+C6
+C6
+C6
+7C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER U WITH MACRON
+ENCODING 362
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+7C
+00
+C6
+C6
+C6
+C6
+C6
+C6
+C6
+C6
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER U WITH MACRON
+ENCODING 363
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+7C
+00
+C6
+C6
+C6
+C6
+C6
+C6
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER U WITH BREVE
+ENCODING 364
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+6C
+38
+00
+C6
+C6
+C6
+C6
+C6
+C6
+C6
+C6
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER U WITH BREVE
+ENCODING 365
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+6C
+6C
+38
+00
+C6
+C6
+C6
+C6
+C6
+C6
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER U WITH RING ABOVE
+ENCODING 366
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+38
+6C
+38
+C6
+C6
+C6
+C6
+C6
+C6
+C6
+C6
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER U WITH RING ABOVE
+ENCODING 367
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+38
+6C
+38
+00
+C6
+C6
+C6
+C6
+C6
+C6
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER U WITH DOUBLE ACUTE
+ENCODING 368
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+66
+CC
+00
+C6
+C6
+C6
+C6
+C6
+C6
+C6
+C6
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER U WITH DOUBLE ACUTE
+ENCODING 369
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+36
+6C
+D8
+00
+C6
+C6
+C6
+C6
+C6
+C6
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER U WITH OGONEK
+ENCODING 370
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+C6
+C6
+C6
+C6
+C6
+C6
+C6
+C6
+C6
+7E
+0C
+08
+06
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER U WITH OGONEK
+ENCODING 371
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+C6
+C6
+C6
+C6
+C6
+C6
+7E
+0C
+08
+06
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER W WITH CIRCUMFLEX
+ENCODING 372
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+38
+6C
+00
+C6
+C6
+C6
+C6
+C6
+D6
+FE
+EE
+C6
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER W WITH CIRCUMFLEX
+ENCODING 373
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+10
+38
+6C
+00
+C6
+C6
+D6
+D6
+D6
+D6
+6E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER Y WITH CIRCUMFLEX
+ENCODING 374
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+38
+6C
+00
+C6
+C6
+C6
+C6
+7E
+06
+06
+06
+FC
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER Y WITH CIRCUMFLEX
+ENCODING 375
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+10
+38
+6C
+00
+C6
+C6
+C6
+C6
+C6
+C6
+7E
+06
+06
+FC
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER Y WITH DIAERESIS
+ENCODING 376
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+6C
+6C
+00
+C6
+C6
+C6
+C6
+7E
+06
+06
+06
+FC
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER Z WITH ACUTE
+ENCODING 377
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+18
+30
+00
+FE
+06
+0C
+18
+30
+60
+C0
+C0
+FE
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER Z WITH ACUTE
+ENCODING 378
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+0C
+18
+30
+00
+FE
+06
+0C
+18
+30
+60
+FE
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER Z WITH DOT ABOVE
+ENCODING 379
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+18
+18
+00
+FE
+06
+0C
+18
+30
+60
+C0
+C0
+FE
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER Z WITH DOT ABOVE
+ENCODING 380
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+18
+18
+00
+FE
+06
+0C
+18
+30
+60
+FE
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER Z WITH CARON
+ENCODING 381
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+6C
+38
+10
+FE
+06
+0C
+18
+30
+60
+C0
+C0
+FE
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER Z WITH CARON
+ENCODING 382
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+6C
+38
+10
+00
+FE
+06
+0C
+18
+30
+60
+FE
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER LONG S
+ENCODING 383
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+1E
+30
+30
+70
+30
+30
+30
+30
+30
+30
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER F WITH HOOK
+ENCODING 402
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+0E
+1B
+1B
+18
+18
+18
+7E
+18
+18
+18
+D8
+D8
+70
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER A WITH CARON
+ENCODING 461
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+6C
+38
+10
+7C
+C6
+C6
+C6
+FE
+C6
+C6
+C6
+C6
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER A WITH CARON
+ENCODING 462
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+6C
+38
+10
+00
+7C
+06
+7E
+C6
+C6
+C6
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER I WITH CARON
+ENCODING 463
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+6C
+38
+10
+7E
+18
+18
+18
+18
+18
+18
+18
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER I WITH CARON
+ENCODING 464
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+6C
+38
+10
+00
+38
+18
+18
+18
+18
+18
+1C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER O WITH CARON
+ENCODING 465
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+6C
+38
+10
+7C
+C6
+C6
+C6
+C6
+C6
+C6
+C6
+7C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER O WITH CARON
+ENCODING 466
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+6C
+38
+10
+00
+7C
+C6
+C6
+C6
+C6
+C6
+7C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER U WITH CARON
+ENCODING 467
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+6C
+38
+10
+C6
+C6
+C6
+C6
+C6
+C6
+C6
+C6
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER U WITH CARON
+ENCODING 468
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+6C
+38
+10
+00
+C6
+C6
+C6
+C6
+C6
+C6
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER AE WITH MACRON
+ENCODING 482
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+7C
+00
+7E
+D8
+D8
+D8
+FE
+D8
+D8
+D8
+DE
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER AE WITH MACRON
+ENCODING 483
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+7C
+00
+6E
+16
+16
+7E
+D0
+D0
+6E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER G WITH CARON
+ENCODING 486
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+6C
+38
+10
+7E
+C0
+C0
+C0
+DE
+C6
+C6
+C6
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER G WITH CARON
+ENCODING 487
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+6C
+38
+10
+00
+7E
+C6
+C6
+C6
+C6
+C6
+7C
+06
+06
+FC
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER K WITH CARON
+ENCODING 488
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+6C
+38
+10
+C6
+C6
+C6
+CC
+F8
+CC
+C6
+C6
+C6
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER K WITH CARON
+ENCODING 489
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+6C
+38
+10
+C0
+C0
+CC
+D8
+F0
+F0
+D8
+CC
+C6
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER O WITH OGONEK
+ENCODING 490
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+7C
+C6
+C6
+C6
+C6
+C6
+C6
+C6
+C6
+7C
+18
+10
+0C
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER O WITH OGONEK
+ENCODING 491
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+7C
+C6
+C6
+C6
+C6
+C6
+7C
+18
+10
+0C
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER O WITH OGONEK AND MACRON
+ENCODING 492
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+7C
+00
+7C
+C6
+C6
+C6
+C6
+C6
+C6
+C6
+7C
+18
+10
+0C
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER O WITH OGONEK AND MACRON
+ENCODING 493
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+7C
+00
+7C
+C6
+C6
+C6
+C6
+C6
+7C
+18
+10
+0C
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER J WITH CARON
+ENCODING 496
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+6C
+38
+10
+00
+18
+18
+18
+18
+18
+18
+18
+18
+18
+70
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER G WITH ACUTE
+ENCODING 500
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+18
+30
+00
+7E
+C0
+C0
+C0
+DE
+C6
+C6
+C6
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER G WITH ACUTE
+ENCODING 501
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+0C
+18
+30
+00
+7E
+C6
+C6
+C6
+C6
+C6
+7C
+06
+06
+FC
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER AE WITH ACUTE
+ENCODING 508
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+18
+30
+00
+7E
+D8
+D8
+D8
+FE
+D8
+D8
+D8
+DE
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER AE WITH ACUTE
+ENCODING 509
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+0C
+18
+30
+00
+6E
+16
+16
+7E
+D0
+D0
+6E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER O WITH STROKE AND ACUTE
+ENCODING 510
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+18
+30
+02
+7C
+CE
+CE
+D6
+D6
+E6
+E6
+C6
+7C
+80
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER O WITH STROKE AND ACUTE
+ENCODING 511
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+0C
+18
+30
+02
+7C
+C6
+CE
+D6
+E6
+C6
+7C
+80
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER A WITH DOUBLE GRAVE
+ENCODING 512
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+CC
+66
+00
+7C
+C6
+C6
+C6
+FE
+C6
+C6
+C6
+C6
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER A WITH DOUBLE GRAVE
+ENCODING 513
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+D8
+6C
+36
+00
+7C
+06
+7E
+C6
+C6
+C6
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER A WITH INVERTED BREVE
+ENCODING 514
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+38
+6C
+00
+7C
+C6
+C6
+C6
+FE
+C6
+C6
+C6
+C6
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER A WITH INVERTED BREVE
+ENCODING 515
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+38
+6C
+6C
+00
+7C
+06
+7E
+C6
+C6
+C6
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER E WITH DOUBLE GRAVE
+ENCODING 516
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+CC
+66
+00
+7E
+C0
+C0
+C0
+F8
+C0
+C0
+C0
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER E WITH DOUBLE GRAVE
+ENCODING 517
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+D8
+6C
+36
+00
+7E
+C6
+C6
+FE
+C0
+C0
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER E WITH INVERTED BREVE
+ENCODING 518
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+38
+6C
+00
+7E
+C0
+C0
+C0
+F8
+C0
+C0
+C0
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER E WITH INVERTED BREVE
+ENCODING 519
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+38
+6C
+6C
+00
+7E
+C6
+C6
+FE
+C0
+C0
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER I WITH DOUBLE GRAVE
+ENCODING 520
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+CC
+66
+00
+7E
+18
+18
+18
+18
+18
+18
+18
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER I WITH DOUBLE GRAVE
+ENCODING 521
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+D8
+6C
+36
+00
+38
+18
+18
+18
+18
+18
+1C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER I WITH INVERTED BREVE
+ENCODING 522
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+3C
+66
+00
+7E
+18
+18
+18
+18
+18
+18
+18
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER I WITH INVERTED BREVE
+ENCODING 523
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+3C
+66
+66
+00
+38
+18
+18
+18
+18
+18
+1C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER O WITH DOUBLE GRAVE
+ENCODING 524
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+CC
+66
+00
+7C
+C6
+C6
+C6
+C6
+C6
+C6
+C6
+7C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER O WITH DOUBLE GRAVE
+ENCODING 525
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+D8
+6C
+36
+00
+7C
+C6
+C6
+C6
+C6
+C6
+7C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER O WITH INVERTED BREVE
+ENCODING 526
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+38
+6C
+00
+7C
+C6
+C6
+C6
+C6
+C6
+C6
+C6
+7C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER O WITH INVERTED BREVE
+ENCODING 527
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+38
+6C
+6C
+00
+7C
+C6
+C6
+C6
+C6
+C6
+7C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER R WITH DOUBLE GRAVE
+ENCODING 528
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+CC
+66
+00
+FC
+C6
+C6
+C6
+FC
+C6
+C6
+C6
+C6
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER R WITH DOUBLE GRAVE
+ENCODING 529
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+D8
+6C
+36
+00
+7E
+C6
+C0
+C0
+C0
+C0
+C0
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER R WITH INVERTED BREVE
+ENCODING 530
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+38
+6C
+00
+FC
+C6
+C6
+C6
+FC
+C6
+C6
+C6
+C6
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER R WITH INVERTED BREVE
+ENCODING 531
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+38
+6C
+6C
+00
+7E
+C6
+C0
+C0
+C0
+C0
+C0
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER U WITH DOUBLE GRAVE
+ENCODING 532
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+CC
+66
+00
+C6
+C6
+C6
+C6
+C6
+C6
+C6
+C6
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER U WITH DOUBLE GRAVE
+ENCODING 533
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+D8
+6C
+36
+00
+C6
+C6
+C6
+C6
+C6
+C6
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER U WITH INVERTED BREVE
+ENCODING 534
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+38
+6C
+00
+C6
+C6
+C6
+C6
+C6
+C6
+C6
+C6
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER U WITH INVERTED BREVE
+ENCODING 535
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+38
+6C
+6C
+00
+C6
+C6
+C6
+C6
+C6
+C6
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER H WITH CARON
+ENCODING 542
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+6C
+38
+10
+C6
+C6
+C6
+C6
+FE
+C6
+C6
+C6
+C6
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER H WITH CARON
+ENCODING 543
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+6C
+38
+10
+C0
+C0
+C0
+FC
+C6
+C6
+C6
+C6
+C6
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER A WITH DOT ABOVE
+ENCODING 550
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+18
+18
+00
+7C
+C6
+C6
+C6
+FE
+C6
+C6
+C6
+C6
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER A WITH DOT ABOVE
+ENCODING 551
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+18
+18
+00
+7C
+06
+7E
+C6
+C6
+C6
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER E WITH CEDILLA
+ENCODING 552
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+7E
+C0
+C0
+C0
+F8
+C0
+C0
+C0
+C0
+7E
+18
+18
+30
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER E WITH CEDILLA
+ENCODING 553
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+7E
+C6
+C6
+FE
+C0
+C0
+7E
+18
+18
+30
+00
+ENDCHAR
+STARTCHAR LATIN CAPITAL LETTER O WITH DOT ABOVE
+ENCODING 558
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+18
+18
+00
+7C
+C6
+C6
+C6
+C6
+C6
+C6
+C6
+7C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LATIN SMALL LETTER O WITH DOT ABOVE
+ENCODING 559
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+18
+18
+00
+7C
+C6
+C6
+C6
+C6
+C6
+7C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BREVE
+ENCODING 728
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+6C
+38
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR DOT ABOVE
+ENCODING 729
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+18
+18
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR OGONEK
+ENCODING 731
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+0C
+08
+06
+00
+ENDCHAR
+STARTCHAR SMALL TILDE
+ENCODING 732
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+32
+4C
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR DOUBLE ACUTE ACCENT
+ENCODING 733
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+66
+CC
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR COMBINING BREVE
+ENCODING 774
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+6C
+38
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR COMBINING DIAERESIS
+ENCODING 776
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+6C
+6C
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR GREEK CAPITAL LETTER GAMMA
+ENCODING 915
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+FE
+C6
+C0
+C0
+C0
+C0
+C0
+C0
+C0
+C0
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR GREEK CAPITAL LETTER THETA
+ENCODING 920
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+38
+6C
+C6
+C6
+FE
+C6
+C6
+C6
+6C
+38
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR GREEK CAPITAL LETTER SIGMA
+ENCODING 931
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+FE
+C6
+60
+30
+18
+18
+30
+60
+C6
+FE
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR GREEK CAPITAL LETTER PHI
+ENCODING 934
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+10
+7C
+D6
+D6
+D6
+D6
+D6
+D6
+7C
+10
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR GREEK CAPITAL LETTER OMEGA
+ENCODING 937
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+38
+6C
+C6
+C6
+C6
+C6
+C6
+6C
+6C
+EE
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR GREEK SMALL LETTER ALPHA
+ENCODING 945
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+76
+DC
+D8
+D8
+D8
+DC
+76
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR GREEK SMALL LETTER DELTA
+ENCODING 948
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+1E
+30
+18
+0C
+3C
+66
+66
+66
+66
+3C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR GREEK SMALL LETTER EPSILON
+ENCODING 949
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+7C
+C6
+C0
+70
+C0
+C6
+7C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR GREEK SMALL LETTER PI
+ENCODING 960
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+FE
+6C
+6C
+6C
+6C
+6C
+6C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR GREEK SMALL LETTER SIGMA
+ENCODING 963
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+7E
+D8
+D8
+D8
+D8
+D8
+70
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR GREEK SMALL LETTER TAU
+ENCODING 964
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+7E
+18
+18
+18
+18
+18
+0C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR GREEK SMALL LETTER PHI
+ENCODING 966
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+4C
+D6
+D6
+D6
+D6
+D6
+7C
+10
+10
+00
+00
+ENDCHAR
+STARTCHAR CYRILLIC CAPITAL LETTER IO
+ENCODING 1025
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+6C
+6C
+00
+7E
+C0
+C0
+C0
+F8
+C0
+C0
+C0
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I
+ENCODING 1030
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+7E
+18
+18
+18
+18
+18
+18
+18
+18
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR CYRILLIC CAPITAL LETTER YI
+ENCODING 1031
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+66
+66
+00
+7E
+18
+18
+18
+18
+18
+18
+18
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR CYRILLIC CAPITAL LETTER SHORT U
+ENCODING 1038
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+6C
+38
+00
+C6
+C6
+C6
+C6
+7E
+06
+06
+06
+FC
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR CYRILLIC CAPITAL LETTER A
+ENCODING 1040
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+7C
+C6
+C6
+C6
+FE
+C6
+C6
+C6
+C6
+C6
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR CYRILLIC CAPITAL LETTER BE
+ENCODING 1041
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+FC
+C0
+C0
+C0
+FC
+C6
+C6
+C6
+C6
+FC
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR CYRILLIC CAPITAL LETTER VE
+ENCODING 1042
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+FC
+C6
+C6
+C6
+FC
+C6
+C6
+C6
+C6
+FC
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR CYRILLIC CAPITAL LETTER GHE
+ENCODING 1043
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+7E
+C0
+C0
+C0
+C0
+C0
+C0
+C0
+C0
+C0
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR CYRILLIC CAPITAL LETTER DE
+ENCODING 1044
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+1C
+3C
+6C
+6C
+6C
+6C
+6C
+6C
+FE
+C6
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR CYRILLIC CAPITAL LETTER IE
+ENCODING 1045
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+7E
+C0
+C0
+C0
+F8
+C0
+C0
+C0
+C0
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR CYRILLIC CAPITAL LETTER ZHE
+ENCODING 1046
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+D6
+D6
+D6
+7C
+38
+7C
+D6
+D6
+D6
+D6
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR CYRILLIC CAPITAL LETTER ZE
+ENCODING 1047
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+FC
+06
+06
+06
+3C
+06
+06
+06
+06
+FC
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR CYRILLIC CAPITAL LETTER I
+ENCODING 1048
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+C6
+C6
+CE
+CE
+D6
+D6
+E6
+E6
+C6
+C6
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR CYRILLIC CAPITAL LETTER SHORT I
+ENCODING 1049
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+6C
+38
+00
+C6
+CE
+CE
+D6
+D6
+E6
+E6
+C6
+C6
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR CYRILLIC CAPITAL LETTER KA
+ENCODING 1050
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+C6
+C6
+C6
+CC
+F8
+CC
+C6
+C6
+C6
+C6
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR CYRILLIC CAPITAL LETTER EL
+ENCODING 1051
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+7E
+C6
+C6
+C6
+C6
+C6
+C6
+C6
+C6
+C6
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR CYRILLIC CAPITAL LETTER EM
+ENCODING 1052
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+C6
+EE
+FE
+D6
+C6
+C6
+C6
+C6
+C6
+C6
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR CYRILLIC CAPITAL LETTER EN
+ENCODING 1053
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+C6
+C6
+C6
+C6
+FE
+C6
+C6
+C6
+C6
+C6
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR CYRILLIC CAPITAL LETTER O
+ENCODING 1054
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+7C
+C6
+C6
+C6
+C6
+C6
+C6
+C6
+C6
+7C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR CYRILLIC CAPITAL LETTER PE
+ENCODING 1055
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+FE
+C6
+C6
+C6
+C6
+C6
+C6
+C6
+C6
+C6
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR CYRILLIC CAPITAL LETTER ER
+ENCODING 1056
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+FC
+C6
+C6
+C6
+FC
+C0
+C0
+C0
+C0
+C0
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR CYRILLIC CAPITAL LETTER ES
+ENCODING 1057
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+7E
+C0
+C0
+C0
+C0
+C0
+C0
+C0
+C0
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR CYRILLIC CAPITAL LETTER TE
+ENCODING 1058
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+FF
+18
+18
+18
+18
+18
+18
+18
+18
+18
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR CYRILLIC CAPITAL LETTER U
+ENCODING 1059
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+C6
+C6
+C6
+C6
+7E
+06
+06
+06
+06
+FC
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR CYRILLIC CAPITAL LETTER EF
+ENCODING 1060
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+10
+7C
+D6
+D6
+D6
+D6
+D6
+D6
+7C
+10
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR CYRILLIC CAPITAL LETTER HA
+ENCODING 1061
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+C6
+C6
+C6
+6C
+38
+6C
+C6
+C6
+C6
+C6
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR CYRILLIC CAPITAL LETTER TSE
+ENCODING 1062
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+C6
+C6
+C6
+C6
+C6
+C6
+C6
+C6
+C6
+7F
+03
+03
+06
+00
+ENDCHAR
+STARTCHAR CYRILLIC CAPITAL LETTER CHE
+ENCODING 1063
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+C6
+C6
+C6
+C6
+7E
+06
+06
+06
+06
+06
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR CYRILLIC CAPITAL LETTER SHA
+ENCODING 1064
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+D6
+D6
+D6
+D6
+D6
+D6
+D6
+D6
+D6
+7C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR CYRILLIC CAPITAL LETTER SHCHA
+ENCODING 1065
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+D6
+D6
+D6
+D6
+D6
+D6
+D6
+D6
+D6
+7F
+03
+03
+06
+00
+ENDCHAR
+STARTCHAR CYRILLIC CAPITAL LETTER HARD SIGN
+ENCODING 1066
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+F0
+F0
+30
+30
+3C
+36
+36
+36
+36
+3C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR CYRILLIC CAPITAL LETTER YERU
+ENCODING 1067
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+C6
+C6
+C6
+C6
+F6
+DE
+DE
+DE
+DE
+F6
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR CYRILLIC CAPITAL LETTER SOFT SIGN
+ENCODING 1068
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+30
+30
+30
+30
+3C
+36
+36
+36
+36
+3C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR CYRILLIC CAPITAL LETTER E
+ENCODING 1069
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+FC
+06
+06
+06
+3E
+06
+06
+06
+06
+FC
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR CYRILLIC CAPITAL LETTER YU
+ENCODING 1070
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+CC
+D6
+D6
+D6
+F6
+F6
+D6
+D6
+D6
+CC
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR CYRILLIC CAPITAL LETTER YA
+ENCODING 1071
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+7E
+C6
+C6
+C6
+7E
+C6
+C6
+C6
+C6
+C6
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR CYRILLIC SMALL LETTER A
+ENCODING 1072
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+7C
+06
+7E
+C6
+C6
+C6
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR CYRILLIC SMALL LETTER BE
+ENCODING 1073
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+7C
+C0
+FC
+C6
+C6
+C6
+7C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR CYRILLIC SMALL LETTER VE
+ENCODING 1074
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+70
+D8
+D8
+D8
+FC
+C6
+C6
+C6
+C6
+7C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR CYRILLIC SMALL LETTER GHE
+ENCODING 1075
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+FC
+06
+06
+7C
+C0
+C0
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR CYRILLIC SMALL LETTER DE
+ENCODING 1076
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+7E
+C6
+C6
+C6
+C6
+C6
+7E
+06
+06
+FC
+00
+ENDCHAR
+STARTCHAR CYRILLIC SMALL LETTER IE
+ENCODING 1077
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+7E
+C6
+C6
+FE
+C0
+C0
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR CYRILLIC SMALL LETTER ZHE
+ENCODING 1078
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+D6
+7C
+38
+38
+7C
+D6
+D6
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR CYRILLIC SMALL LETTER ZE
+ENCODING 1079
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+FC
+06
+06
+3C
+06
+06
+FC
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR CYRILLIC SMALL LETTER I
+ENCODING 1080
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+C6
+C6
+C6
+C6
+C6
+C6
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR CYRILLIC SMALL LETTER SHORT I
+ENCODING 1081
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+6C
+6C
+38
+00
+C6
+C6
+C6
+C6
+C6
+C6
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR CYRILLIC SMALL LETTER KA
+ENCODING 1082
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+CC
+CC
+D8
+F0
+F0
+D8
+CC
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR CYRILLIC SMALL LETTER EL
+ENCODING 1083
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+7E
+C6
+C6
+C6
+C6
+C6
+C6
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR CYRILLIC SMALL LETTER EM
+ENCODING 1084
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+C6
+EE
+FE
+D6
+C6
+C6
+C6
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR CYRILLIC SMALL LETTER EN
+ENCODING 1085
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+C6
+C6
+C6
+FE
+C6
+C6
+C6
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR CYRILLIC SMALL LETTER O
+ENCODING 1086
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+7C
+C6
+C6
+C6
+C6
+C6
+7C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR CYRILLIC SMALL LETTER PE
+ENCODING 1087
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+FC
+C6
+C6
+C6
+C6
+C6
+C6
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR CYRILLIC SMALL LETTER ER
+ENCODING 1088
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+FC
+C6
+C6
+C6
+C6
+C6
+FC
+C0
+C0
+C0
+00
+ENDCHAR
+STARTCHAR CYRILLIC SMALL LETTER ES
+ENCODING 1089
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+7E
+C0
+C0
+C0
+C0
+C0
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR CYRILLIC SMALL LETTER TE
+ENCODING 1090
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+EC
+D6
+D6
+D6
+D6
+C6
+C6
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR CYRILLIC SMALL LETTER U
+ENCODING 1091
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+C6
+C6
+C6
+C6
+C6
+C6
+7E
+06
+06
+FC
+00
+ENDCHAR
+STARTCHAR CYRILLIC SMALL LETTER EF
+ENCODING 1092
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+7C
+D6
+D6
+D6
+D6
+D6
+7C
+10
+10
+00
+00
+ENDCHAR
+STARTCHAR CYRILLIC SMALL LETTER HA
+ENCODING 1093
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+C6
+6C
+38
+38
+6C
+C6
+C6
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR CYRILLIC SMALL LETTER TSE
+ENCODING 1094
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+C6
+C6
+C6
+C6
+C6
+C6
+7F
+03
+03
+06
+00
+ENDCHAR
+STARTCHAR CYRILLIC SMALL LETTER CHE
+ENCODING 1095
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+C6
+C6
+C6
+7E
+06
+06
+06
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR CYRILLIC SMALL LETTER SHA
+ENCODING 1096
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+C6
+C6
+D6
+D6
+D6
+D6
+6E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR CYRILLIC SMALL LETTER SHCHA
+ENCODING 1097
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+C6
+C6
+D6
+D6
+D6
+D6
+6F
+03
+03
+06
+00
+ENDCHAR
+STARTCHAR CYRILLIC SMALL LETTER HARD SIGN
+ENCODING 1098
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+F0
+30
+3C
+36
+36
+36
+3C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR CYRILLIC SMALL LETTER YERU
+ENCODING 1099
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+C6
+C6
+F6
+DE
+DE
+DE
+F6
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR CYRILLIC SMALL LETTER SOFT SIGN
+ENCODING 1100
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+30
+30
+3C
+36
+36
+36
+3C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR CYRILLIC SMALL LETTER E
+ENCODING 1101
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+FC
+06
+06
+3E
+06
+06
+FC
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR CYRILLIC SMALL LETTER YU
+ENCODING 1102
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+CC
+D6
+D6
+F6
+D6
+D6
+CC
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR CYRILLIC SMALL LETTER YA
+ENCODING 1103
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+7E
+C6
+C6
+7E
+C6
+C6
+C6
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR CYRILLIC SMALL LETTER IO
+ENCODING 1105
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+6C
+6C
+00
+7E
+C6
+C6
+FE
+C0
+C0
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I
+ENCODING 1110
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+18
+18
+00
+38
+18
+18
+18
+18
+18
+1C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR CYRILLIC SMALL LETTER YI
+ENCODING 1111
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+66
+66
+00
+38
+18
+18
+18
+18
+18
+1C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR CYRILLIC SMALL LETTER SHORT U
+ENCODING 1118
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+6C
+6C
+38
+00
+C6
+C6
+C6
+C6
+C6
+C6
+7E
+06
+06
+FC
+00
+ENDCHAR
+STARTCHAR CYRILLIC CAPITAL LETTER GHE WITH UPTURN
+ENCODING 1168
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+06
+06
+7C
+C0
+C0
+C0
+C0
+C0
+C0
+C0
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR CYRILLIC SMALL LETTER GHE WITH UPTURN
+ENCODING 1169
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+06
+06
+7C
+C0
+C0
+C0
+C0
+C0
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR DOUBLE VERTICAL LINE
+ENCODING 8214
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+66
+66
+66
+66
+66
+66
+66
+66
+66
+66
+66
+66
+00
+00
+00
+ENDCHAR
+STARTCHAR LEFT SINGLE QUOTATION MARK
+ENCODING 8216
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+18
+18
+18
+18
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR RIGHT SINGLE QUOTATION MARK
+ENCODING 8217
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+18
+18
+18
+18
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LEFT DOUBLE QUOTATION MARK
+ENCODING 8220
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+66
+66
+66
+66
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR RIGHT DOUBLE QUOTATION MARK
+ENCODING 8221
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+66
+66
+66
+66
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BULLET
+ENCODING 8226
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+18
+3C
+3C
+18
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR HORIZONTAL ELLIPSIS
+ENCODING 8230
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+DB
+DB
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR SINGLE LEFT-POINTING ANGLE QUOTATION MARK
+ENCODING 8249
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+0C
+18
+30
+18
+0C
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
+ENCODING 8250
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+30
+18
+0C
+18
+30
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR DOUBLE EXCLAMATION MARK
+ENCODING 8252
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+66
+66
+66
+66
+66
+66
+66
+00
+66
+66
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR SUPERSCRIPT LATIN SMALL LETTER N
+ENCODING 8319
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+78
+6C
+6C
+6C
+6C
+6C
+00
+00
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR PESETA SIGN
+ENCODING 8359
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+F8
+CC
+CC
+F8
+C0
+CC
+DE
+CC
+CC
+CC
+C6
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR EURO SIGN
+ENCODING 8364
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+1C
+36
+60
+F8
+60
+F8
+60
+36
+1C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LEFTWARDS ARROW
+ENCODING 8592
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+20
+40
+FE
+40
+20
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR UPWARDS ARROW
+ENCODING 8593
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+18
+3C
+5A
+18
+18
+18
+18
+18
+18
+18
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR RIGHTWARDS ARROW
+ENCODING 8594
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+08
+04
+FE
+04
+08
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR DOWNWARDS ARROW
+ENCODING 8595
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+18
+18
+18
+18
+18
+18
+18
+5A
+3C
+18
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LEFT RIGHT ARROW
+ENCODING 8596
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+28
+44
+FE
+44
+28
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR UP DOWN ARROW
+ENCODING 8597
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+18
+3C
+5A
+18
+18
+18
+18
+5A
+3C
+18
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR UP DOWN ARROW WITH BASE
+ENCODING 8616
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+18
+3C
+5A
+18
+18
+18
+18
+5A
+3C
+18
+7E
+00
+00
+00
+ENDCHAR
+STARTCHAR BULLET OPERATOR
+ENCODING 8729
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+38
+38
+38
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR SQUARE ROOT
+ENCODING 8730
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+0F
+0C
+0C
+0C
+0C
+0C
+CC
+6C
+3C
+1C
+0C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR INFINITY
+ENCODING 8734
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+7E
+DB
+DB
+DB
+7E
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR INTERSECTION
+ENCODING 8745
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+7C
+C6
+C6
+C6
+C6
+C6
+C6
+C6
+C6
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR UNION
+ENCODING 8746
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+C6
+C6
+C6
+C6
+C6
+C6
+C6
+C6
+7C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR ALMOST EQUAL TO
+ENCODING 8776
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+32
+4C
+00
+32
+4C
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR IDENTICAL TO
+ENCODING 8801
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+7E
+00
+00
+7E
+00
+00
+7E
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LESS-THAN OR EQUAL TO
+ENCODING 8804
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+0C
+18
+30
+18
+0C
+00
+3C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR GREATER-THAN OR EQUAL TO
+ENCODING 8805
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+30
+18
+0C
+18
+30
+00
+3C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR HOUSE
+ENCODING 8962
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+10
+38
+6C
+C6
+C6
+C6
+FE
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR REVERSED NOT SIGN
+ENCODING 8976
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+FE
+C0
+C0
+C0
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR TURNED NOT SIGN
+ENCODING 8985
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+C0
+C0
+C0
+FE
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR TOP HALF INTEGRAL
+ENCODING 8992
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+0E
+1B
+1B
+18
+18
+18
+18
+18
+18
+18
+18
+18
+18
+18
+ENDCHAR
+STARTCHAR BOTTOM HALF INTEGRAL
+ENCODING 8993
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+18
+18
+18
+18
+18
+18
+18
+18
+18
+D8
+D8
+70
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BOX DRAWINGS LIGHT HORIZONTAL
+ENCODING 9472
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+FF
+00
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BOX DRAWINGS HEAVY HORIZONTAL
+ENCODING 9473
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+FF
+FF
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BOX DRAWINGS LIGHT VERTICAL
+ENCODING 9474
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+18
+18
+18
+18
+18
+18
+18
+18
+18
+18
+18
+18
+18
+18
+18
+18
+ENDCHAR
+STARTCHAR BOX DRAWINGS HEAVY VERTICAL
+ENCODING 9475
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+ENDCHAR
+STARTCHAR BOX DRAWINGS LIGHT TRIPLE DASH HORIZONTAL
+ENCODING 9476
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+DB
+00
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BOX DRAWINGS HEAVY TRIPLE DASH HORIZONTAL
+ENCODING 9477
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+DB
+DB
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BOX DRAWINGS LIGHT TRIPLE DASH VERTICAL
+ENCODING 9478
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+18
+18
+18
+00
+00
+18
+18
+18
+00
+00
+18
+18
+18
+00
+00
+ENDCHAR
+STARTCHAR BOX DRAWINGS HEAVY TRIPLE DASH VERTICAL
+ENCODING 9479
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+1C
+1C
+1C
+00
+00
+1C
+1C
+1C
+00
+00
+1C
+1C
+1C
+00
+00
+ENDCHAR
+STARTCHAR BOX DRAWINGS LIGHT QUADRUPLE DASH HORIZONTAL
+ENCODING 9480
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+AA
+00
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BOX DRAWINGS HEAVY QUADRUPLE DASH HORIZONTAL
+ENCODING 9481
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+AA
+AA
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BOX DRAWINGS LIGHT QUADRUPLE DASH VERTICAL
+ENCODING 9482
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+18
+18
+00
+00
+18
+18
+00
+00
+18
+18
+00
+00
+18
+18
+00
+ENDCHAR
+STARTCHAR BOX DRAWINGS HEAVY QUADRUPLE DASH VERTICAL
+ENCODING 9483
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+1C
+1C
+00
+00
+1C
+1C
+00
+00
+1C
+1C
+00
+00
+1C
+1C
+00
+ENDCHAR
+STARTCHAR BOX DRAWINGS LIGHT DOWN AND RIGHT
+ENCODING 9484
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+1F
+18
+18
+18
+18
+18
+18
+18
+18
+ENDCHAR
+STARTCHAR BOX DRAWINGS DOWN LIGHT AND RIGHT HEAVY
+ENCODING 9485
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+1F
+1F
+18
+18
+18
+18
+18
+18
+18
+ENDCHAR
+STARTCHAR BOX DRAWINGS DOWN HEAVY AND RIGHT LIGHT
+ENCODING 9486
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+1F
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+ENDCHAR
+STARTCHAR BOX DRAWINGS HEAVY DOWN AND RIGHT
+ENCODING 9487
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+1F
+1F
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+ENDCHAR
+STARTCHAR BOX DRAWINGS LIGHT DOWN AND LEFT
+ENCODING 9488
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+F8
+18
+18
+18
+18
+18
+18
+18
+18
+ENDCHAR
+STARTCHAR BOX DRAWINGS DOWN LIGHT AND LEFT HEAVY
+ENCODING 9489
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+F8
+F8
+18
+18
+18
+18
+18
+18
+18
+ENDCHAR
+STARTCHAR BOX DRAWINGS DOWN HEAVY AND LEFT LIGHT
+ENCODING 9490
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+FC
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+ENDCHAR
+STARTCHAR BOX DRAWINGS HEAVY DOWN AND LEFT
+ENCODING 9491
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+FC
+FC
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+ENDCHAR
+STARTCHAR BOX DRAWINGS LIGHT UP AND RIGHT
+ENCODING 9492
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+18
+18
+18
+18
+18
+18
+18
+1F
+00
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BOX DRAWINGS UP LIGHT AND RIGHT HEAVY
+ENCODING 9493
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+18
+18
+18
+18
+18
+18
+18
+1F
+1F
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BOX DRAWINGS UP HEAVY AND RIGHT LIGHT
+ENCODING 9494
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+1F
+00
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BOX DRAWINGS HEAVY UP AND RIGHT
+ENCODING 9495
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+1F
+1F
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BOX DRAWINGS LIGHT UP AND LEFT
+ENCODING 9496
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+18
+18
+18
+18
+18
+18
+18
+F8
+00
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BOX DRAWINGS UP LIGHT AND LEFT HEAVY
+ENCODING 9497
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+18
+18
+18
+18
+18
+18
+18
+F8
+F8
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BOX DRAWINGS UP HEAVY AND LEFT LIGHT
+ENCODING 9498
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+FC
+00
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BOX DRAWINGS HEAVY UP AND LEFT
+ENCODING 9499
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+FC
+FC
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BOX DRAWINGS LIGHT VERTICAL AND RIGHT
+ENCODING 9500
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+18
+18
+18
+18
+18
+18
+18
+1F
+18
+18
+18
+18
+18
+18
+18
+18
+ENDCHAR
+STARTCHAR BOX DRAWINGS VERTICAL LIGHT AND RIGHT HEAVY
+ENCODING 9501
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+18
+18
+18
+18
+18
+18
+18
+1F
+1F
+18
+18
+18
+18
+18
+18
+18
+ENDCHAR
+STARTCHAR BOX DRAWINGS UP HEAVY AND RIGHT DOWN LIGHT
+ENCODING 9502
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+1F
+18
+18
+18
+18
+18
+18
+18
+18
+ENDCHAR
+STARTCHAR BOX DRAWINGS DOWN HEAVY AND RIGHT UP LIGHT
+ENCODING 9503
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+18
+18
+18
+18
+18
+18
+18
+1F
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+ENDCHAR
+STARTCHAR BOX DRAWINGS VERTICAL HEAVY AND RIGHT LIGHT
+ENCODING 9504
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+1F
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+ENDCHAR
+STARTCHAR BOX DRAWINGS DOWN LIGHT AND RIGHT UP HEAVY
+ENCODING 9505
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+1F
+1F
+18
+18
+18
+18
+18
+18
+18
+ENDCHAR
+STARTCHAR BOX DRAWINGS UP LIGHT AND RIGHT DOWN HEAVY
+ENCODING 9506
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+18
+18
+18
+18
+18
+18
+18
+1F
+1F
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+ENDCHAR
+STARTCHAR BOX DRAWINGS HEAVY VERTICAL AND RIGHT
+ENCODING 9507
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+1F
+1F
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+ENDCHAR
+STARTCHAR BOX DRAWINGS LIGHT VERTICAL AND LEFT
+ENCODING 9508
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+18
+18
+18
+18
+18
+18
+18
+F8
+18
+18
+18
+18
+18
+18
+18
+18
+ENDCHAR
+STARTCHAR BOX DRAWINGS VERTICAL LIGHT AND LEFT HEAVY
+ENCODING 9509
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+18
+18
+18
+18
+18
+18
+18
+F8
+F8
+18
+18
+18
+18
+18
+18
+18
+ENDCHAR
+STARTCHAR BOX DRAWINGS UP HEAVY AND LEFT DOWN LIGHT
+ENCODING 9510
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+FC
+18
+18
+18
+18
+18
+18
+18
+18
+ENDCHAR
+STARTCHAR BOX DRAWINGS DOWN HEAVY AND LEFT UP LIGHT
+ENCODING 9511
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+18
+18
+18
+18
+18
+18
+18
+FC
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+ENDCHAR
+STARTCHAR BOX DRAWINGS VERTICAL HEAVY AND LEFT LIGHT
+ENCODING 9512
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+FC
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+ENDCHAR
+STARTCHAR BOX DRAWINGS DOWN LIGHT AND LEFT UP HEAVY
+ENCODING 9513
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+FC
+FC
+18
+18
+18
+18
+18
+18
+18
+ENDCHAR
+STARTCHAR BOX DRAWINGS UP LIGHT AND LEFT DOWN HEAVY
+ENCODING 9514
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+18
+18
+18
+18
+18
+18
+18
+FC
+FC
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+ENDCHAR
+STARTCHAR BOX DRAWINGS HEAVY VERTICAL AND LEFT
+ENCODING 9515
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+FC
+FC
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+ENDCHAR
+STARTCHAR BOX DRAWINGS LIGHT DOWN AND HORIZONTAL
+ENCODING 9516
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+FF
+18
+18
+18
+18
+18
+18
+18
+18
+ENDCHAR
+STARTCHAR BOX DRAWINGS LEFT HEAVY AND RIGHT DOWN LIGHT
+ENCODING 9517
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+F8
+FF
+18
+18
+18
+18
+18
+18
+18
+ENDCHAR
+STARTCHAR BOX DRAWINGS RIGHT HEAVY AND LEFT DOWN LIGHT
+ENCODING 9518
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+1F
+FF
+18
+18
+18
+18
+18
+18
+18
+ENDCHAR
+STARTCHAR BOX DRAWINGS DOWN LIGHT AND HORIZONTAL HEAVY
+ENCODING 9519
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+FF
+FF
+18
+18
+18
+18
+18
+18
+18
+ENDCHAR
+STARTCHAR BOX DRAWINGS DOWN HEAVY AND HORIZONTAL LIGHT
+ENCODING 9520
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+FF
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+ENDCHAR
+STARTCHAR BOX DRAWINGS RIGHT LIGHT AND LEFT DOWN HEAVY
+ENCODING 9521
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+FF
+FC
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+ENDCHAR
+STARTCHAR BOX DRAWINGS LEFT LIGHT AND RIGHT DOWN HEAVY
+ENCODING 9522
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+FF
+1F
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+ENDCHAR
+STARTCHAR BOX DRAWINGS HEAVY DOWN AND HORIZONTAL
+ENCODING 9523
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+FF
+FF
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+ENDCHAR
+STARTCHAR BOX DRAWINGS LIGHT UP AND HORIZONTAL
+ENCODING 9524
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+18
+18
+18
+18
+18
+18
+18
+FF
+00
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BOX DRAWINGS LEFT HEAVY AND RIGHT UP LIGHT
+ENCODING 9525
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+18
+18
+18
+18
+18
+18
+18
+FF
+F8
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BOX DRAWINGS RIGHT HEAVY AND LEFT UP LIGHT
+ENCODING 9526
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+18
+18
+18
+18
+18
+18
+18
+FF
+1F
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BOX DRAWINGS UP LIGHT AND HORIZONTAL HEAVY
+ENCODING 9527
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+18
+18
+18
+18
+18
+18
+18
+FF
+FF
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BOX DRAWINGS UP HEAVY AND HORIZONTAL LIGHT
+ENCODING 9528
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+FF
+00
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BOX DRAWINGS RIGHT LIGHT AND LEFT UP HEAVY
+ENCODING 9529
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+FF
+FC
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BOX DRAWINGS LEFT LIGHT AND RIGHT UP HEAVY
+ENCODING 9530
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+FF
+1F
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BOX DRAWINGS HEAVY UP AND HORIZONTAL
+ENCODING 9531
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+FF
+FF
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL
+ENCODING 9532
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+18
+18
+18
+18
+18
+18
+18
+FF
+18
+18
+18
+18
+18
+18
+18
+18
+ENDCHAR
+STARTCHAR BOX DRAWINGS LEFT HEAVY AND RIGHT VERTICAL LIGHT
+ENCODING 9533
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+18
+18
+18
+18
+18
+18
+18
+FF
+F8
+18
+18
+18
+18
+18
+18
+18
+ENDCHAR
+STARTCHAR BOX DRAWINGS RIGHT HEAVY AND LEFT VERTICAL LIGHT
+ENCODING 9534
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+18
+18
+18
+18
+18
+18
+18
+FF
+1F
+18
+18
+18
+18
+18
+18
+18
+ENDCHAR
+STARTCHAR BOX DRAWINGS VERTICAL LIGHT AND HORIZONTAL HEAVY
+ENCODING 9535
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+18
+18
+18
+18
+18
+18
+18
+FF
+FF
+18
+18
+18
+18
+18
+18
+18
+ENDCHAR
+STARTCHAR BOX DRAWINGS UP HEAVY AND DOWN HORIZONTAL LIGHT
+ENCODING 9536
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+FF
+18
+18
+18
+18
+18
+18
+18
+18
+ENDCHAR
+STARTCHAR BOX DRAWINGS DOWN HEAVY AND UP HORIZONTAL LIGHT
+ENCODING 9537
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+18
+18
+18
+18
+18
+18
+18
+FF
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+ENDCHAR
+STARTCHAR BOX DRAWINGS VERTICAL HEAVY AND HORIZONTAL LIGHT
+ENCODING 9538
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+FF
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+ENDCHAR
+STARTCHAR BOX DRAWINGS LEFT UP HEAVY AND RIGHT DOWN LIGHT
+ENCODING 9539
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+FF
+F8
+18
+18
+18
+18
+18
+18
+18
+ENDCHAR
+STARTCHAR BOX DRAWINGS RIGHT UP HEAVY AND LEFT DOWN LIGHT
+ENCODING 9540
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+FF
+1F
+18
+18
+18
+18
+18
+18
+18
+ENDCHAR
+STARTCHAR BOX DRAWINGS LEFT DOWN HEAVY AND RIGHT UP LIGHT
+ENCODING 9541
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+18
+18
+18
+18
+18
+18
+18
+FF
+FC
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+ENDCHAR
+STARTCHAR BOX DRAWINGS RIGHT DOWN HEAVY AND LEFT UP LIGHT
+ENCODING 9542
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+18
+18
+18
+18
+18
+18
+18
+FF
+1F
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+ENDCHAR
+STARTCHAR BOX DRAWINGS DOWN LIGHT AND UP HORIZONTAL HEAVY
+ENCODING 9543
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+FF
+FF
+18
+18
+18
+18
+18
+18
+18
+ENDCHAR
+STARTCHAR BOX DRAWINGS UP LIGHT AND DOWN HORIZONTAL HEAVY
+ENCODING 9544
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+18
+18
+18
+18
+18
+18
+18
+FF
+FF
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+ENDCHAR
+STARTCHAR BOX DRAWINGS RIGHT LIGHT AND LEFT VERTICAL HEAVY
+ENCODING 9545
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+FF
+FC
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+ENDCHAR
+STARTCHAR BOX DRAWINGS LEFT LIGHT AND RIGHT VERTICAL HEAVY
+ENCODING 9546
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+FF
+1F
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+ENDCHAR
+STARTCHAR BOX DRAWINGS HEAVY VERTICAL AND HORIZONTAL
+ENCODING 9547
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+FF
+FF
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+ENDCHAR
+STARTCHAR BOX DRAWINGS LIGHT DOUBLE DASH HORIZONTAL
+ENCODING 9548
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+EE
+00
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BOX DRAWINGS HEAVY DOUBLE DASH HORIZONTAL
+ENCODING 9549
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+EE
+EE
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BOX DRAWINGS LIGHT DOUBLE DASH VERTICAL
+ENCODING 9550
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+18
+18
+18
+18
+18
+18
+00
+00
+18
+18
+18
+18
+18
+18
+00
+ENDCHAR
+STARTCHAR BOX DRAWINGS HEAVY DOUBLE DASH VERTICAL
+ENCODING 9551
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+1C
+1C
+1C
+1C
+1C
+1C
+00
+00
+1C
+1C
+1C
+1C
+1C
+1C
+00
+ENDCHAR
+STARTCHAR BOX DRAWINGS DOUBLE HORIZONTAL
+ENCODING 9552
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+FF
+00
+FF
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BOX DRAWINGS DOUBLE VERTICAL
+ENCODING 9553
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+36
+36
+36
+36
+36
+36
+36
+36
+36
+36
+36
+36
+36
+36
+36
+36
+ENDCHAR
+STARTCHAR BOX DRAWINGS DOWN SINGLE AND RIGHT DOUBLE
+ENCODING 9554
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+1F
+18
+1F
+18
+18
+18
+18
+18
+18
+18
+ENDCHAR
+STARTCHAR BOX DRAWINGS DOWN DOUBLE AND RIGHT SINGLE
+ENCODING 9555
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+3F
+36
+36
+36
+36
+36
+36
+36
+36
+ENDCHAR
+STARTCHAR BOX DRAWINGS DOUBLE DOWN AND RIGHT
+ENCODING 9556
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+3F
+30
+37
+36
+36
+36
+36
+36
+36
+36
+ENDCHAR
+STARTCHAR BOX DRAWINGS DOWN SINGLE AND LEFT DOUBLE
+ENCODING 9557
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+F8
+18
+F8
+18
+18
+18
+18
+18
+18
+18
+ENDCHAR
+STARTCHAR BOX DRAWINGS DOWN DOUBLE AND LEFT SINGLE
+ENCODING 9558
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+FE
+36
+36
+36
+36
+36
+36
+36
+36
+ENDCHAR
+STARTCHAR BOX DRAWINGS DOUBLE DOWN AND LEFT
+ENCODING 9559
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+FE
+06
+F6
+36
+36
+36
+36
+36
+36
+36
+ENDCHAR
+STARTCHAR BOX DRAWINGS UP SINGLE AND RIGHT DOUBLE
+ENCODING 9560
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+18
+18
+18
+18
+18
+18
+1F
+18
+1F
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BOX DRAWINGS UP DOUBLE AND RIGHT SINGLE
+ENCODING 9561
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+36
+36
+36
+36
+36
+36
+36
+3F
+00
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BOX DRAWINGS DOUBLE UP AND RIGHT
+ENCODING 9562
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+36
+36
+36
+36
+36
+36
+37
+30
+3F
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BOX DRAWINGS UP SINGLE AND LEFT DOUBLE
+ENCODING 9563
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+18
+18
+18
+18
+18
+18
+F8
+18
+F8
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BOX DRAWINGS UP DOUBLE AND LEFT SINGLE
+ENCODING 9564
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+36
+36
+36
+36
+36
+36
+36
+FE
+00
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BOX DRAWINGS DOUBLE UP AND LEFT
+ENCODING 9565
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+36
+36
+36
+36
+36
+36
+F6
+06
+FE
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BOX DRAWINGS VERTICAL SINGLE AND RIGHT DOUBLE
+ENCODING 9566
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+18
+18
+18
+18
+18
+18
+1F
+18
+1F
+18
+18
+18
+18
+18
+18
+18
+ENDCHAR
+STARTCHAR BOX DRAWINGS VERTICAL DOUBLE AND RIGHT SINGLE
+ENCODING 9567
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+36
+36
+36
+36
+36
+36
+36
+37
+36
+36
+36
+36
+36
+36
+36
+36
+ENDCHAR
+STARTCHAR BOX DRAWINGS DOUBLE VERTICAL AND RIGHT
+ENCODING 9568
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+36
+36
+36
+36
+36
+36
+37
+30
+37
+36
+36
+36
+36
+36
+36
+36
+ENDCHAR
+STARTCHAR BOX DRAWINGS VERTICAL SINGLE AND LEFT DOUBLE
+ENCODING 9569
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+18
+18
+18
+18
+18
+18
+F8
+18
+F8
+18
+18
+18
+18
+18
+18
+18
+ENDCHAR
+STARTCHAR BOX DRAWINGS VERTICAL DOUBLE AND LEFT SINGLE
+ENCODING 9570
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+36
+36
+36
+36
+36
+36
+36
+F6
+36
+36
+36
+36
+36
+36
+36
+36
+ENDCHAR
+STARTCHAR BOX DRAWINGS DOUBLE VERTICAL AND LEFT
+ENCODING 9571
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+36
+36
+36
+36
+36
+36
+F6
+06
+F6
+36
+36
+36
+36
+36
+36
+36
+ENDCHAR
+STARTCHAR BOX DRAWINGS DOWN SINGLE AND HORIZONTAL DOUBLE
+ENCODING 9572
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+FF
+00
+FF
+18
+18
+18
+18
+18
+18
+18
+ENDCHAR
+STARTCHAR BOX DRAWINGS DOWN DOUBLE AND HORIZONTAL SINGLE
+ENCODING 9573
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+FF
+36
+36
+36
+36
+36
+36
+36
+36
+ENDCHAR
+STARTCHAR BOX DRAWINGS DOUBLE DOWN AND HORIZONTAL
+ENCODING 9574
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+FF
+00
+F7
+36
+36
+36
+36
+36
+36
+36
+ENDCHAR
+STARTCHAR BOX DRAWINGS UP SINGLE AND HORIZONTAL DOUBLE
+ENCODING 9575
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+18
+18
+18
+18
+18
+18
+FF
+00
+FF
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BOX DRAWINGS UP DOUBLE AND HORIZONTAL SINGLE
+ENCODING 9576
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+36
+36
+36
+36
+36
+36
+36
+FF
+00
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BOX DRAWINGS DOUBLE UP AND HORIZONTAL
+ENCODING 9577
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+36
+36
+36
+36
+36
+36
+F7
+00
+FF
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BOX DRAWINGS VERTICAL SINGLE AND HORIZONTAL DOUBLE
+ENCODING 9578
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+18
+18
+18
+18
+18
+18
+FF
+18
+FF
+18
+18
+18
+18
+18
+18
+18
+ENDCHAR
+STARTCHAR BOX DRAWINGS VERTICAL DOUBLE AND HORIZONTAL SINGLE
+ENCODING 9579
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+36
+36
+36
+36
+36
+36
+36
+FF
+36
+36
+36
+36
+36
+36
+36
+36
+ENDCHAR
+STARTCHAR BOX DRAWINGS DOUBLE VERTICAL AND HORIZONTAL
+ENCODING 9580
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+36
+36
+36
+36
+36
+36
+F7
+00
+F7
+36
+36
+36
+36
+36
+36
+36
+ENDCHAR
+STARTCHAR BOX DRAWINGS LIGHT ARC DOWN AND RIGHT
+ENCODING 9581
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+0F
+18
+18
+18
+18
+18
+18
+18
+18
+ENDCHAR
+STARTCHAR BOX DRAWINGS LIGHT ARC DOWN AND LEFT
+ENCODING 9582
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+F0
+18
+18
+18
+18
+18
+18
+18
+18
+ENDCHAR
+STARTCHAR BOX DRAWINGS LIGHT ARC UP AND LEFT
+ENCODING 9583
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+18
+18
+18
+18
+18
+18
+18
+F0
+00
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BOX DRAWINGS LIGHT ARC UP AND RIGHT
+ENCODING 9584
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+18
+18
+18
+18
+18
+18
+18
+0F
+00
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BOX DRAWINGS LIGHT DIAGONAL UPPER RIGHT TO LOWER LEFT
+ENCODING 9585
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+01
+03
+03
+06
+06
+0C
+0C
+18
+18
+30
+30
+60
+60
+C0
+C0
+80
+ENDCHAR
+STARTCHAR BOX DRAWINGS LIGHT DIAGONAL UPPER LEFT TO LOWER RIGHT
+ENCODING 9586
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+80
+C0
+C0
+60
+60
+30
+30
+18
+18
+0C
+0C
+06
+06
+03
+03
+01
+ENDCHAR
+STARTCHAR BOX DRAWINGS LIGHT DIAGONAL CROSS
+ENCODING 9587
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+81
+C3
+C3
+66
+66
+3C
+3C
+18
+18
+3C
+3C
+66
+66
+C3
+C3
+81
+ENDCHAR
+STARTCHAR BOX DRAWINGS LIGHT LEFT
+ENCODING 9588
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+F0
+00
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BOX DRAWINGS LIGHT UP
+ENCODING 9589
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+18
+18
+18
+18
+18
+18
+18
+18
+00
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BOX DRAWINGS LIGHT RIGHT
+ENCODING 9590
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+0F
+00
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BOX DRAWINGS LIGHT DOWN
+ENCODING 9591
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+00
+18
+18
+18
+18
+18
+18
+18
+18
+ENDCHAR
+STARTCHAR BOX DRAWINGS HEAVY LEFT
+ENCODING 9592
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+F0
+F0
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BOX DRAWINGS HEAVY UP
+ENCODING 9593
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+00
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BOX DRAWINGS HEAVY RIGHT
+ENCODING 9594
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+0F
+0F
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BOX DRAWINGS HEAVY DOWN
+ENCODING 9595
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+00
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+ENDCHAR
+STARTCHAR BOX DRAWINGS LIGHT LEFT AND HEAVY RIGHT
+ENCODING 9596
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+FF
+0F
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BOX DRAWINGS LIGHT UP AND HEAVY DOWN
+ENCODING 9597
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+18
+18
+18
+18
+18
+18
+18
+18
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+ENDCHAR
+STARTCHAR BOX DRAWINGS HEAVY LEFT AND LIGHT RIGHT
+ENCODING 9598
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+FF
+F0
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BOX DRAWINGS HEAVY UP AND LIGHT DOWN
+ENCODING 9599
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+1C
+18
+18
+18
+18
+18
+18
+18
+18
+ENDCHAR
+STARTCHAR UPPER HALF BLOCK
+ENCODING 9600
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+FF
+FF
+FF
+FF
+FF
+FF
+FF
+FF
+00
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LOWER ONE EIGHTH BLOCK
+ENCODING 9601
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+FF
+FF
+ENDCHAR
+STARTCHAR LOWER ONE QUARTER BLOCK
+ENCODING 9602
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+FF
+FF
+FF
+FF
+ENDCHAR
+STARTCHAR LOWER THREE EIGHTHS BLOCK
+ENCODING 9603
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+FF
+FF
+FF
+FF
+FF
+FF
+ENDCHAR
+STARTCHAR LOWER HALF BLOCK
+ENCODING 9604
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+00
+FF
+FF
+FF
+FF
+FF
+FF
+FF
+FF
+ENDCHAR
+STARTCHAR LOWER FIVE EIGHTHS BLOCK
+ENCODING 9605
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+FF
+FF
+FF
+FF
+FF
+FF
+FF
+FF
+FF
+FF
+ENDCHAR
+STARTCHAR LOWER THREE QUARTERS BLOCK
+ENCODING 9606
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+FF
+FF
+FF
+FF
+FF
+FF
+FF
+FF
+FF
+FF
+FF
+FF
+ENDCHAR
+STARTCHAR LOWER SEVEN EIGHTHS BLOCK
+ENCODING 9607
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+FF
+FF
+FF
+FF
+FF
+FF
+FF
+FF
+FF
+FF
+FF
+FF
+FF
+FF
+ENDCHAR
+STARTCHAR FULL BLOCK
+ENCODING 9608
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+FF
+FF
+FF
+FF
+FF
+FF
+FF
+FF
+FF
+FF
+FF
+FF
+FF
+FF
+FF
+FF
+ENDCHAR
+STARTCHAR LEFT SEVEN EIGHTHS BLOCK
+ENCODING 9609
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+FE
+FE
+FE
+FE
+FE
+FE
+FE
+FE
+FE
+FE
+FE
+FE
+FE
+FE
+FE
+FE
+ENDCHAR
+STARTCHAR LEFT THREE QUARTERS BLOCK
+ENCODING 9610
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+FC
+FC
+FC
+FC
+FC
+FC
+FC
+FC
+FC
+FC
+FC
+FC
+FC
+FC
+FC
+FC
+ENDCHAR
+STARTCHAR LEFT FIVE EIGHTHS BLOCK
+ENCODING 9611
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+F8
+F8
+F8
+F8
+F8
+F8
+F8
+F8
+F8
+F8
+F8
+F8
+F8
+F8
+F8
+F8
+ENDCHAR
+STARTCHAR LEFT HALF BLOCK
+ENCODING 9612
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+F0
+F0
+F0
+F0
+F0
+F0
+F0
+F0
+F0
+F0
+F0
+F0
+F0
+F0
+F0
+F0
+ENDCHAR
+STARTCHAR LEFT THREE EIGHTHS BLOCK
+ENCODING 9613
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+E0
+E0
+E0
+E0
+E0
+E0
+E0
+E0
+E0
+E0
+E0
+E0
+E0
+E0
+E0
+E0
+ENDCHAR
+STARTCHAR LEFT ONE QUARTER BLOCK
+ENCODING 9614
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+C0
+C0
+C0
+C0
+C0
+C0
+C0
+C0
+C0
+C0
+C0
+C0
+C0
+C0
+C0
+C0
+ENDCHAR
+STARTCHAR LEFT ONE EIGHTH BLOCK
+ENCODING 9615
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+80
+80
+80
+80
+80
+80
+80
+80
+80
+80
+80
+80
+80
+80
+80
+80
+ENDCHAR
+STARTCHAR RIGHT HALF BLOCK
+ENCODING 9616
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+0F
+0F
+0F
+0F
+0F
+0F
+0F
+0F
+0F
+0F
+0F
+0F
+0F
+0F
+0F
+0F
+ENDCHAR
+STARTCHAR LIGHT SHADE
+ENCODING 9617
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+11
+44
+11
+44
+11
+44
+11
+44
+11
+44
+11
+44
+11
+44
+11
+44
+ENDCHAR
+STARTCHAR MEDIUM SHADE
+ENCODING 9618
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+55
+AA
+55
+AA
+55
+AA
+55
+AA
+55
+AA
+55
+AA
+55
+AA
+55
+AA
+ENDCHAR
+STARTCHAR DARK SHADE
+ENCODING 9619
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+DD
+77
+DD
+77
+DD
+77
+DD
+77
+DD
+77
+DD
+77
+DD
+77
+DD
+77
+ENDCHAR
+STARTCHAR UPPER ONE EIGHTH BLOCK
+ENCODING 9620
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+FF
+FF
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR RIGHT ONE EIGHTH BLOCK
+ENCODING 9621
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+01
+01
+01
+01
+01
+01
+01
+01
+01
+01
+01
+01
+01
+01
+01
+01
+ENDCHAR
+STARTCHAR QUADRANT LOWER LEFT
+ENCODING 9622
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+00
+F0
+F0
+F0
+F0
+F0
+F0
+F0
+F0
+ENDCHAR
+STARTCHAR QUADRANT LOWER RIGHT
+ENCODING 9623
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+00
+0F
+0F
+0F
+0F
+0F
+0F
+0F
+0F
+ENDCHAR
+STARTCHAR QUADRANT UPPER LEFT
+ENCODING 9624
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+F0
+F0
+F0
+F0
+F0
+F0
+F0
+F0
+00
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR QUADRANT UPPER LEFT AND LOWER LEFT AND LOWER RIGHT
+ENCODING 9625
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+F0
+F0
+F0
+F0
+F0
+F0
+F0
+F0
+FF
+FF
+FF
+FF
+FF
+FF
+FF
+FF
+ENDCHAR
+STARTCHAR QUADRANT UPPER LEFT AND LOWER RIGHT
+ENCODING 9626
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+F0
+F0
+F0
+F0
+F0
+F0
+F0
+F0
+0F
+0F
+0F
+0F
+0F
+0F
+0F
+0F
+ENDCHAR
+STARTCHAR QUADRANT UPPER LEFT AND UPPER RIGHT AND LOWER LEFT
+ENCODING 9627
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+FF
+FF
+FF
+FF
+FF
+FF
+FF
+FF
+F0
+F0
+F0
+F0
+F0
+F0
+F0
+F0
+ENDCHAR
+STARTCHAR QUADRANT UPPER LEFT AND UPPER RIGHT AND LOWER RIGHT
+ENCODING 9628
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+FF
+FF
+FF
+FF
+FF
+FF
+FF
+FF
+0F
+0F
+0F
+0F
+0F
+0F
+0F
+0F
+ENDCHAR
+STARTCHAR QUADRANT UPPER RIGHT
+ENCODING 9629
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+0F
+0F
+0F
+0F
+0F
+0F
+0F
+0F
+00
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR QUADRANT UPPER RIGHT AND LOWER LEFT
+ENCODING 9630
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+0F
+0F
+0F
+0F
+0F
+0F
+0F
+0F
+F0
+F0
+F0
+F0
+F0
+F0
+F0
+F0
+ENDCHAR
+STARTCHAR QUADRANT UPPER RIGHT AND LOWER LEFT AND LOWER RIGHT
+ENCODING 9631
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+0F
+0F
+0F
+0F
+0F
+0F
+0F
+0F
+FF
+FF
+FF
+FF
+FF
+FF
+FF
+FF
+ENDCHAR
+STARTCHAR BLACK SQUARE
+ENCODING 9632
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+7C
+7C
+7C
+7C
+7C
+7C
+7C
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BLACK RECTANGLE
+ENCODING 9644
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+00
+FE
+FE
+FE
+FE
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BLACK UP-POINTING TRIANGLE
+ENCODING 9650
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+10
+38
+38
+7C
+7C
+FE
+FE
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BLACK DOWN-POINTING TRIANGLE
+ENCODING 9660
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+FE
+FE
+7C
+7C
+38
+38
+10
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BLACK DIAMOND
+ENCODING 9670
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+10
+38
+7C
+FE
+7C
+38
+10
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LOZENGE
+ENCODING 9674
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+10
+38
+6C
+C6
+6C
+38
+10
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR WHITE CIRCLE
+ENCODING 9675
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+3C
+66
+66
+66
+66
+3C
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BLACK CIRCLE
+ENCODING 9679
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+3C
+7E
+7E
+7E
+7E
+3C
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR INVERSE BULLET
+ENCODING 9688
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+FF
+FF
+FF
+FF
+FF
+FF
+E7
+C3
+C3
+E7
+FF
+FF
+FF
+FF
+FF
+FF
+ENDCHAR
+STARTCHAR INVERSE WHITE CIRCLE
+ENCODING 9689
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+FF
+FF
+FF
+FF
+FF
+C3
+99
+99
+99
+99
+C3
+FF
+FF
+FF
+FF
+FF
+ENDCHAR
+STARTCHAR BLACK LOWER RIGHT TRIANGLE
+ENCODING 9698
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+00
+80
+C0
+E0
+F0
+F8
+FC
+FE
+FF
+ENDCHAR
+STARTCHAR BLACK LOWER LEFT TRIANGLE
+ENCODING 9699
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+00
+01
+03
+07
+0F
+1F
+3F
+7F
+FF
+ENDCHAR
+STARTCHAR BLACK UPPER LEFT TRIANGLE
+ENCODING 9700
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+FF
+FE
+FC
+F8
+F0
+E0
+C0
+80
+00
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BLACK UPPER RIGHT TRIANGLE
+ENCODING 9701
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+FF
+7F
+3F
+1F
+0F
+07
+03
+01
+00
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR TRIGRAM FOR HEAVEN
+ENCODING 9776
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+7E
+00
+00
+7E
+00
+00
+7E
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR TRIGRAM FOR LAKE
+ENCODING 9777
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+66
+00
+00
+7E
+00
+00
+7E
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR TRIGRAM FOR FIRE
+ENCODING 9778
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+7E
+00
+00
+66
+00
+00
+7E
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR TRIGRAM FOR THUNDER
+ENCODING 9779
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+66
+00
+00
+66
+00
+00
+7E
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR TRIGRAM FOR WIND
+ENCODING 9780
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+7E
+00
+00
+7E
+00
+00
+66
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR TRIGRAM FOR WATER
+ENCODING 9781
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+66
+00
+00
+7E
+00
+00
+66
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR TRIGRAM FOR MOUNTAIN
+ENCODING 9782
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+7E
+00
+00
+66
+00
+00
+66
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR TRIGRAM FOR EARTH
+ENCODING 9783
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+66
+00
+00
+66
+00
+00
+66
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR WHITE SMILING FACE
+ENCODING 9786
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+7E
+81
+A5
+81
+81
+A5
+99
+81
+81
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BLACK SMILING FACE
+ENCODING 9787
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+7E
+FF
+DB
+FF
+FF
+DB
+E7
+FF
+FF
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR WHITE SUN WITH RAYS
+ENCODING 9788
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+18
+18
+DB
+3C
+E7
+3C
+DB
+18
+18
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR FEMALE SIGN
+ENCODING 9792
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+3C
+66
+66
+66
+66
+3C
+18
+3C
+18
+18
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR MALE SIGN
+ENCODING 9794
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+0F
+07
+0D
+19
+3C
+66
+66
+66
+66
+3C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BLACK SPADE SUIT
+ENCODING 9824
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+18
+3C
+7E
+FF
+FF
+7E
+18
+18
+3C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BLACK CLUB SUIT
+ENCODING 9827
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+18
+3C
+3C
+E7
+E7
+E7
+18
+18
+3C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BLACK HEART SUIT
+ENCODING 9829
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+6C
+FE
+FE
+FE
+7C
+38
+10
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BLACK DIAMOND SUIT
+ENCODING 9830
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+10
+38
+7C
+FE
+7C
+38
+10
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR EIGHTH NOTE
+ENCODING 9834
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+7E
+66
+7E
+60
+60
+60
+60
+60
+C0
+C0
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BEAMED EIGHTH NOTES
+ENCODING 9835
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+7E
+66
+7E
+66
+66
+66
+66
+66
+6E
+CC
+C0
+00
+00
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN BLANK
+ENCODING 10240
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-1
+ENCODING 10241
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+60
+60
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-2
+ENCODING 10242
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+60
+60
+00
+00
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-12
+ENCODING 10243
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+60
+60
+00
+00
+60
+60
+00
+00
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-3
+ENCODING 10244
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+00
+00
+60
+60
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-13
+ENCODING 10245
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+60
+60
+00
+00
+00
+00
+00
+00
+60
+60
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-23
+ENCODING 10246
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+60
+60
+00
+00
+60
+60
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-123
+ENCODING 10247
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+60
+60
+00
+00
+60
+60
+00
+00
+60
+60
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-4
+ENCODING 10248
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+06
+06
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-14
+ENCODING 10249
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+66
+66
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-24
+ENCODING 10250
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+06
+06
+00
+00
+60
+60
+00
+00
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-124
+ENCODING 10251
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+66
+66
+00
+00
+60
+60
+00
+00
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-34
+ENCODING 10252
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+06
+06
+00
+00
+00
+00
+00
+00
+60
+60
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-134
+ENCODING 10253
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+66
+66
+00
+00
+00
+00
+00
+00
+60
+60
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-234
+ENCODING 10254
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+06
+06
+00
+00
+60
+60
+00
+00
+60
+60
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-1234
+ENCODING 10255
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+66
+66
+00
+00
+60
+60
+00
+00
+60
+60
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-5
+ENCODING 10256
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+06
+06
+00
+00
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-15
+ENCODING 10257
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+60
+60
+00
+00
+06
+06
+00
+00
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-25
+ENCODING 10258
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+66
+66
+00
+00
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-125
+ENCODING 10259
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+60
+60
+00
+00
+66
+66
+00
+00
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-35
+ENCODING 10260
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+06
+06
+00
+00
+60
+60
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-135
+ENCODING 10261
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+60
+60
+00
+00
+06
+06
+00
+00
+60
+60
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-235
+ENCODING 10262
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+66
+66
+00
+00
+60
+60
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-1235
+ENCODING 10263
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+60
+60
+00
+00
+66
+66
+00
+00
+60
+60
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-45
+ENCODING 10264
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+06
+06
+00
+00
+06
+06
+00
+00
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-145
+ENCODING 10265
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+66
+66
+00
+00
+06
+06
+00
+00
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-245
+ENCODING 10266
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+06
+06
+00
+00
+66
+66
+00
+00
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-1245
+ENCODING 10267
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+66
+66
+00
+00
+66
+66
+00
+00
+00
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-345
+ENCODING 10268
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+06
+06
+00
+00
+06
+06
+00
+00
+60
+60
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-1345
+ENCODING 10269
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+66
+66
+00
+00
+06
+06
+00
+00
+60
+60
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-2345
+ENCODING 10270
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+06
+06
+00
+00
+66
+66
+00
+00
+60
+60
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-12345
+ENCODING 10271
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+66
+66
+00
+00
+66
+66
+00
+00
+60
+60
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-6
+ENCODING 10272
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+00
+00
+06
+06
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-16
+ENCODING 10273
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+60
+60
+00
+00
+00
+00
+00
+00
+06
+06
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-26
+ENCODING 10274
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+60
+60
+00
+00
+06
+06
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-126
+ENCODING 10275
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+60
+60
+00
+00
+60
+60
+00
+00
+06
+06
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-36
+ENCODING 10276
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+00
+00
+66
+66
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-136
+ENCODING 10277
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+60
+60
+00
+00
+00
+00
+00
+00
+66
+66
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-236
+ENCODING 10278
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+60
+60
+00
+00
+66
+66
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-1236
+ENCODING 10279
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+60
+60
+00
+00
+60
+60
+00
+00
+66
+66
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-46
+ENCODING 10280
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+06
+06
+00
+00
+00
+00
+00
+00
+06
+06
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-146
+ENCODING 10281
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+66
+66
+00
+00
+00
+00
+00
+00
+06
+06
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-246
+ENCODING 10282
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+06
+06
+00
+00
+60
+60
+00
+00
+06
+06
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-1246
+ENCODING 10283
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+66
+66
+00
+00
+60
+60
+00
+00
+06
+06
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-346
+ENCODING 10284
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+06
+06
+00
+00
+00
+00
+00
+00
+66
+66
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-1346
+ENCODING 10285
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+66
+66
+00
+00
+00
+00
+00
+00
+66
+66
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-2346
+ENCODING 10286
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+06
+06
+00
+00
+60
+60
+00
+00
+66
+66
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-12346
+ENCODING 10287
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+66
+66
+00
+00
+60
+60
+00
+00
+66
+66
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-56
+ENCODING 10288
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+06
+06
+00
+00
+06
+06
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-156
+ENCODING 10289
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+60
+60
+00
+00
+06
+06
+00
+00
+06
+06
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-256
+ENCODING 10290
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+66
+66
+00
+00
+06
+06
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-1256
+ENCODING 10291
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+60
+60
+00
+00
+66
+66
+00
+00
+06
+06
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-356
+ENCODING 10292
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+06
+06
+00
+00
+66
+66
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-1356
+ENCODING 10293
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+60
+60
+00
+00
+06
+06
+00
+00
+66
+66
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-2356
+ENCODING 10294
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+66
+66
+00
+00
+66
+66
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-12356
+ENCODING 10295
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+60
+60
+00
+00
+66
+66
+00
+00
+66
+66
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-456
+ENCODING 10296
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+06
+06
+00
+00
+06
+06
+00
+00
+06
+06
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-1456
+ENCODING 10297
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+66
+66
+00
+00
+06
+06
+00
+00
+06
+06
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-2456
+ENCODING 10298
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+06
+06
+00
+00
+66
+66
+00
+00
+06
+06
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-12456
+ENCODING 10299
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+66
+66
+00
+00
+66
+66
+00
+00
+06
+06
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-3456
+ENCODING 10300
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+06
+06
+00
+00
+06
+06
+00
+00
+66
+66
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-13456
+ENCODING 10301
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+66
+66
+00
+00
+06
+06
+00
+00
+66
+66
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-23456
+ENCODING 10302
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+06
+06
+00
+00
+66
+66
+00
+00
+66
+66
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-123456
+ENCODING 10303
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+66
+66
+00
+00
+66
+66
+00
+00
+66
+66
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-7
+ENCODING 10304
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+60
+60
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-17
+ENCODING 10305
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+60
+60
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+60
+60
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-27
+ENCODING 10306
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+60
+60
+00
+00
+00
+00
+00
+00
+60
+60
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-127
+ENCODING 10307
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+60
+60
+00
+00
+60
+60
+00
+00
+00
+00
+00
+00
+60
+60
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-37
+ENCODING 10308
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+00
+00
+60
+60
+00
+00
+60
+60
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-137
+ENCODING 10309
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+60
+60
+00
+00
+00
+00
+00
+00
+60
+60
+00
+00
+60
+60
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-237
+ENCODING 10310
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+60
+60
+00
+00
+60
+60
+00
+00
+60
+60
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-1237
+ENCODING 10311
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+60
+60
+00
+00
+60
+60
+00
+00
+60
+60
+00
+00
+60
+60
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-47
+ENCODING 10312
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+06
+06
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+60
+60
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-147
+ENCODING 10313
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+66
+66
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+60
+60
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-247
+ENCODING 10314
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+06
+06
+00
+00
+60
+60
+00
+00
+00
+00
+00
+00
+60
+60
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-1247
+ENCODING 10315
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+66
+66
+00
+00
+60
+60
+00
+00
+00
+00
+00
+00
+60
+60
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-347
+ENCODING 10316
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+06
+06
+00
+00
+00
+00
+00
+00
+60
+60
+00
+00
+60
+60
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-1347
+ENCODING 10317
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+66
+66
+00
+00
+00
+00
+00
+00
+60
+60
+00
+00
+60
+60
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-2347
+ENCODING 10318
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+06
+06
+00
+00
+60
+60
+00
+00
+60
+60
+00
+00
+60
+60
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-12347
+ENCODING 10319
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+66
+66
+00
+00
+60
+60
+00
+00
+60
+60
+00
+00
+60
+60
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-57
+ENCODING 10320
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+06
+06
+00
+00
+00
+00
+00
+00
+60
+60
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-157
+ENCODING 10321
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+60
+60
+00
+00
+06
+06
+00
+00
+00
+00
+00
+00
+60
+60
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-257
+ENCODING 10322
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+66
+66
+00
+00
+00
+00
+00
+00
+60
+60
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-1257
+ENCODING 10323
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+60
+60
+00
+00
+66
+66
+00
+00
+00
+00
+00
+00
+60
+60
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-357
+ENCODING 10324
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+06
+06
+00
+00
+60
+60
+00
+00
+60
+60
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-1357
+ENCODING 10325
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+60
+60
+00
+00
+06
+06
+00
+00
+60
+60
+00
+00
+60
+60
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-2357
+ENCODING 10326
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+66
+66
+00
+00
+60
+60
+00
+00
+60
+60
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-12357
+ENCODING 10327
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+60
+60
+00
+00
+66
+66
+00
+00
+60
+60
+00
+00
+60
+60
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-457
+ENCODING 10328
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+06
+06
+00
+00
+06
+06
+00
+00
+00
+00
+00
+00
+60
+60
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-1457
+ENCODING 10329
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+66
+66
+00
+00
+06
+06
+00
+00
+00
+00
+00
+00
+60
+60
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-2457
+ENCODING 10330
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+06
+06
+00
+00
+66
+66
+00
+00
+00
+00
+00
+00
+60
+60
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-12457
+ENCODING 10331
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+66
+66
+00
+00
+66
+66
+00
+00
+00
+00
+00
+00
+60
+60
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-3457
+ENCODING 10332
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+06
+06
+00
+00
+06
+06
+00
+00
+60
+60
+00
+00
+60
+60
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-13457
+ENCODING 10333
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+66
+66
+00
+00
+06
+06
+00
+00
+60
+60
+00
+00
+60
+60
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-23457
+ENCODING 10334
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+06
+06
+00
+00
+66
+66
+00
+00
+60
+60
+00
+00
+60
+60
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-123457
+ENCODING 10335
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+66
+66
+00
+00
+66
+66
+00
+00
+60
+60
+00
+00
+60
+60
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-67
+ENCODING 10336
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+00
+00
+06
+06
+00
+00
+60
+60
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-167
+ENCODING 10337
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+60
+60
+00
+00
+00
+00
+00
+00
+06
+06
+00
+00
+60
+60
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-267
+ENCODING 10338
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+60
+60
+00
+00
+06
+06
+00
+00
+60
+60
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-1267
+ENCODING 10339
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+60
+60
+00
+00
+60
+60
+00
+00
+06
+06
+00
+00
+60
+60
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-367
+ENCODING 10340
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+00
+00
+66
+66
+00
+00
+60
+60
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-1367
+ENCODING 10341
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+60
+60
+00
+00
+00
+00
+00
+00
+66
+66
+00
+00
+60
+60
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-2367
+ENCODING 10342
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+60
+60
+00
+00
+66
+66
+00
+00
+60
+60
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-12367
+ENCODING 10343
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+60
+60
+00
+00
+60
+60
+00
+00
+66
+66
+00
+00
+60
+60
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-467
+ENCODING 10344
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+06
+06
+00
+00
+00
+00
+00
+00
+06
+06
+00
+00
+60
+60
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-1467
+ENCODING 10345
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+66
+66
+00
+00
+00
+00
+00
+00
+06
+06
+00
+00
+60
+60
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-2467
+ENCODING 10346
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+06
+06
+00
+00
+60
+60
+00
+00
+06
+06
+00
+00
+60
+60
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-12467
+ENCODING 10347
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+66
+66
+00
+00
+60
+60
+00
+00
+06
+06
+00
+00
+60
+60
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-3467
+ENCODING 10348
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+06
+06
+00
+00
+00
+00
+00
+00
+66
+66
+00
+00
+60
+60
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-13467
+ENCODING 10349
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+66
+66
+00
+00
+00
+00
+00
+00
+66
+66
+00
+00
+60
+60
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-23467
+ENCODING 10350
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+06
+06
+00
+00
+60
+60
+00
+00
+66
+66
+00
+00
+60
+60
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-123467
+ENCODING 10351
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+66
+66
+00
+00
+60
+60
+00
+00
+66
+66
+00
+00
+60
+60
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-567
+ENCODING 10352
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+06
+06
+00
+00
+06
+06
+00
+00
+60
+60
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-1567
+ENCODING 10353
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+60
+60
+00
+00
+06
+06
+00
+00
+06
+06
+00
+00
+60
+60
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-2567
+ENCODING 10354
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+66
+66
+00
+00
+06
+06
+00
+00
+60
+60
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-12567
+ENCODING 10355
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+60
+60
+00
+00
+66
+66
+00
+00
+06
+06
+00
+00
+60
+60
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-3567
+ENCODING 10356
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+06
+06
+00
+00
+66
+66
+00
+00
+60
+60
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-13567
+ENCODING 10357
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+60
+60
+00
+00
+06
+06
+00
+00
+66
+66
+00
+00
+60
+60
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-23567
+ENCODING 10358
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+66
+66
+00
+00
+66
+66
+00
+00
+60
+60
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-123567
+ENCODING 10359
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+60
+60
+00
+00
+66
+66
+00
+00
+66
+66
+00
+00
+60
+60
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-4567
+ENCODING 10360
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+06
+06
+00
+00
+06
+06
+00
+00
+06
+06
+00
+00
+60
+60
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-14567
+ENCODING 10361
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+66
+66
+00
+00
+06
+06
+00
+00
+06
+06
+00
+00
+60
+60
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-24567
+ENCODING 10362
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+06
+06
+00
+00
+66
+66
+00
+00
+06
+06
+00
+00
+60
+60
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-124567
+ENCODING 10363
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+66
+66
+00
+00
+66
+66
+00
+00
+06
+06
+00
+00
+60
+60
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-34567
+ENCODING 10364
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+06
+06
+00
+00
+06
+06
+00
+00
+66
+66
+00
+00
+60
+60
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-134567
+ENCODING 10365
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+66
+66
+00
+00
+06
+06
+00
+00
+66
+66
+00
+00
+60
+60
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-234567
+ENCODING 10366
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+06
+06
+00
+00
+66
+66
+00
+00
+66
+66
+00
+00
+60
+60
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-1234567
+ENCODING 10367
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+66
+66
+00
+00
+66
+66
+00
+00
+66
+66
+00
+00
+60
+60
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-8
+ENCODING 10368
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+06
+06
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-18
+ENCODING 10369
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+60
+60
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+06
+06
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-28
+ENCODING 10370
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+60
+60
+00
+00
+00
+00
+00
+00
+06
+06
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-128
+ENCODING 10371
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+60
+60
+00
+00
+60
+60
+00
+00
+00
+00
+00
+00
+06
+06
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-38
+ENCODING 10372
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+00
+00
+60
+60
+00
+00
+06
+06
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-138
+ENCODING 10373
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+60
+60
+00
+00
+00
+00
+00
+00
+60
+60
+00
+00
+06
+06
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-238
+ENCODING 10374
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+60
+60
+00
+00
+60
+60
+00
+00
+06
+06
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-1238
+ENCODING 10375
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+60
+60
+00
+00
+60
+60
+00
+00
+60
+60
+00
+00
+06
+06
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-48
+ENCODING 10376
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+06
+06
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+06
+06
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-148
+ENCODING 10377
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+66
+66
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+06
+06
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-248
+ENCODING 10378
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+06
+06
+00
+00
+60
+60
+00
+00
+00
+00
+00
+00
+06
+06
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-1248
+ENCODING 10379
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+66
+66
+00
+00
+60
+60
+00
+00
+00
+00
+00
+00
+06
+06
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-348
+ENCODING 10380
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+06
+06
+00
+00
+00
+00
+00
+00
+60
+60
+00
+00
+06
+06
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-1348
+ENCODING 10381
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+66
+66
+00
+00
+00
+00
+00
+00
+60
+60
+00
+00
+06
+06
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-2348
+ENCODING 10382
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+06
+06
+00
+00
+60
+60
+00
+00
+60
+60
+00
+00
+06
+06
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-12348
+ENCODING 10383
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+66
+66
+00
+00
+60
+60
+00
+00
+60
+60
+00
+00
+06
+06
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-58
+ENCODING 10384
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+06
+06
+00
+00
+00
+00
+00
+00
+06
+06
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-158
+ENCODING 10385
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+60
+60
+00
+00
+06
+06
+00
+00
+00
+00
+00
+00
+06
+06
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-258
+ENCODING 10386
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+66
+66
+00
+00
+00
+00
+00
+00
+06
+06
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-1258
+ENCODING 10387
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+60
+60
+00
+00
+66
+66
+00
+00
+00
+00
+00
+00
+06
+06
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-358
+ENCODING 10388
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+06
+06
+00
+00
+60
+60
+00
+00
+06
+06
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-1358
+ENCODING 10389
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+60
+60
+00
+00
+06
+06
+00
+00
+60
+60
+00
+00
+06
+06
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-2358
+ENCODING 10390
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+66
+66
+00
+00
+60
+60
+00
+00
+06
+06
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-12358
+ENCODING 10391
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+60
+60
+00
+00
+66
+66
+00
+00
+60
+60
+00
+00
+06
+06
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-458
+ENCODING 10392
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+06
+06
+00
+00
+06
+06
+00
+00
+00
+00
+00
+00
+06
+06
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-1458
+ENCODING 10393
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+66
+66
+00
+00
+06
+06
+00
+00
+00
+00
+00
+00
+06
+06
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-2458
+ENCODING 10394
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+06
+06
+00
+00
+66
+66
+00
+00
+00
+00
+00
+00
+06
+06
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-12458
+ENCODING 10395
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+66
+66
+00
+00
+66
+66
+00
+00
+00
+00
+00
+00
+06
+06
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-3458
+ENCODING 10396
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+06
+06
+00
+00
+06
+06
+00
+00
+60
+60
+00
+00
+06
+06
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-13458
+ENCODING 10397
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+66
+66
+00
+00
+06
+06
+00
+00
+60
+60
+00
+00
+06
+06
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-23458
+ENCODING 10398
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+06
+06
+00
+00
+66
+66
+00
+00
+60
+60
+00
+00
+06
+06
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-123458
+ENCODING 10399
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+66
+66
+00
+00
+66
+66
+00
+00
+60
+60
+00
+00
+06
+06
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-68
+ENCODING 10400
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+00
+00
+06
+06
+00
+00
+06
+06
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-168
+ENCODING 10401
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+60
+60
+00
+00
+00
+00
+00
+00
+06
+06
+00
+00
+06
+06
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-268
+ENCODING 10402
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+60
+60
+00
+00
+06
+06
+00
+00
+06
+06
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-1268
+ENCODING 10403
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+60
+60
+00
+00
+60
+60
+00
+00
+06
+06
+00
+00
+06
+06
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-368
+ENCODING 10404
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+00
+00
+66
+66
+00
+00
+06
+06
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-1368
+ENCODING 10405
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+60
+60
+00
+00
+00
+00
+00
+00
+66
+66
+00
+00
+06
+06
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-2368
+ENCODING 10406
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+60
+60
+00
+00
+66
+66
+00
+00
+06
+06
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-12368
+ENCODING 10407
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+60
+60
+00
+00
+60
+60
+00
+00
+66
+66
+00
+00
+06
+06
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-468
+ENCODING 10408
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+06
+06
+00
+00
+00
+00
+00
+00
+06
+06
+00
+00
+06
+06
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-1468
+ENCODING 10409
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+66
+66
+00
+00
+00
+00
+00
+00
+06
+06
+00
+00
+06
+06
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-2468
+ENCODING 10410
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+06
+06
+00
+00
+60
+60
+00
+00
+06
+06
+00
+00
+06
+06
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-12468
+ENCODING 10411
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+66
+66
+00
+00
+60
+60
+00
+00
+06
+06
+00
+00
+06
+06
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-3468
+ENCODING 10412
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+06
+06
+00
+00
+00
+00
+00
+00
+66
+66
+00
+00
+06
+06
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-13468
+ENCODING 10413
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+66
+66
+00
+00
+00
+00
+00
+00
+66
+66
+00
+00
+06
+06
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-23468
+ENCODING 10414
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+06
+06
+00
+00
+60
+60
+00
+00
+66
+66
+00
+00
+06
+06
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-123468
+ENCODING 10415
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+66
+66
+00
+00
+60
+60
+00
+00
+66
+66
+00
+00
+06
+06
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-568
+ENCODING 10416
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+06
+06
+00
+00
+06
+06
+00
+00
+06
+06
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-1568
+ENCODING 10417
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+60
+60
+00
+00
+06
+06
+00
+00
+06
+06
+00
+00
+06
+06
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-2568
+ENCODING 10418
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+66
+66
+00
+00
+06
+06
+00
+00
+06
+06
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-12568
+ENCODING 10419
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+60
+60
+00
+00
+66
+66
+00
+00
+06
+06
+00
+00
+06
+06
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-3568
+ENCODING 10420
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+06
+06
+00
+00
+66
+66
+00
+00
+06
+06
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-13568
+ENCODING 10421
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+60
+60
+00
+00
+06
+06
+00
+00
+66
+66
+00
+00
+06
+06
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-23568
+ENCODING 10422
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+66
+66
+00
+00
+66
+66
+00
+00
+06
+06
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-123568
+ENCODING 10423
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+60
+60
+00
+00
+66
+66
+00
+00
+66
+66
+00
+00
+06
+06
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-4568
+ENCODING 10424
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+06
+06
+00
+00
+06
+06
+00
+00
+06
+06
+00
+00
+06
+06
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-14568
+ENCODING 10425
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+66
+66
+00
+00
+06
+06
+00
+00
+06
+06
+00
+00
+06
+06
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-24568
+ENCODING 10426
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+06
+06
+00
+00
+66
+66
+00
+00
+06
+06
+00
+00
+06
+06
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-124568
+ENCODING 10427
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+66
+66
+00
+00
+66
+66
+00
+00
+06
+06
+00
+00
+06
+06
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-34568
+ENCODING 10428
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+06
+06
+00
+00
+06
+06
+00
+00
+66
+66
+00
+00
+06
+06
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-134568
+ENCODING 10429
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+66
+66
+00
+00
+06
+06
+00
+00
+66
+66
+00
+00
+06
+06
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-234568
+ENCODING 10430
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+06
+06
+00
+00
+66
+66
+00
+00
+66
+66
+00
+00
+06
+06
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-1234568
+ENCODING 10431
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+66
+66
+00
+00
+66
+66
+00
+00
+66
+66
+00
+00
+06
+06
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-78
+ENCODING 10432
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+66
+66
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-178
+ENCODING 10433
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+60
+60
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+66
+66
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-278
+ENCODING 10434
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+60
+60
+00
+00
+00
+00
+00
+00
+66
+66
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-1278
+ENCODING 10435
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+60
+60
+00
+00
+60
+60
+00
+00
+00
+00
+00
+00
+66
+66
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-378
+ENCODING 10436
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+00
+00
+60
+60
+00
+00
+66
+66
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-1378
+ENCODING 10437
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+60
+60
+00
+00
+00
+00
+00
+00
+60
+60
+00
+00
+66
+66
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-2378
+ENCODING 10438
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+60
+60
+00
+00
+60
+60
+00
+00
+66
+66
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-12378
+ENCODING 10439
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+60
+60
+00
+00
+60
+60
+00
+00
+60
+60
+00
+00
+66
+66
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-478
+ENCODING 10440
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+06
+06
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+66
+66
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-1478
+ENCODING 10441
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+66
+66
+00
+00
+00
+00
+00
+00
+00
+00
+00
+00
+66
+66
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-2478
+ENCODING 10442
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+06
+06
+00
+00
+60
+60
+00
+00
+00
+00
+00
+00
+66
+66
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-12478
+ENCODING 10443
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+66
+66
+00
+00
+60
+60
+00
+00
+00
+00
+00
+00
+66
+66
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-3478
+ENCODING 10444
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+06
+06
+00
+00
+00
+00
+00
+00
+60
+60
+00
+00
+66
+66
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-13478
+ENCODING 10445
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+66
+66
+00
+00
+00
+00
+00
+00
+60
+60
+00
+00
+66
+66
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-23478
+ENCODING 10446
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+06
+06
+00
+00
+60
+60
+00
+00
+60
+60
+00
+00
+66
+66
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-123478
+ENCODING 10447
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+66
+66
+00
+00
+60
+60
+00
+00
+60
+60
+00
+00
+66
+66
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-578
+ENCODING 10448
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+06
+06
+00
+00
+00
+00
+00
+00
+66
+66
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-1578
+ENCODING 10449
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+60
+60
+00
+00
+06
+06
+00
+00
+00
+00
+00
+00
+66
+66
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-2578
+ENCODING 10450
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+66
+66
+00
+00
+00
+00
+00
+00
+66
+66
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-12578
+ENCODING 10451
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+60
+60
+00
+00
+66
+66
+00
+00
+00
+00
+00
+00
+66
+66
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-3578
+ENCODING 10452
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+06
+06
+00
+00
+60
+60
+00
+00
+66
+66
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-13578
+ENCODING 10453
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+60
+60
+00
+00
+06
+06
+00
+00
+60
+60
+00
+00
+66
+66
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-23578
+ENCODING 10454
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+66
+66
+00
+00
+60
+60
+00
+00
+66
+66
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-123578
+ENCODING 10455
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+60
+60
+00
+00
+66
+66
+00
+00
+60
+60
+00
+00
+66
+66
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-4578
+ENCODING 10456
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+06
+06
+00
+00
+06
+06
+00
+00
+00
+00
+00
+00
+66
+66
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-14578
+ENCODING 10457
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+66
+66
+00
+00
+06
+06
+00
+00
+00
+00
+00
+00
+66
+66
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-24578
+ENCODING 10458
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+06
+06
+00
+00
+66
+66
+00
+00
+00
+00
+00
+00
+66
+66
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-124578
+ENCODING 10459
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+66
+66
+00
+00
+66
+66
+00
+00
+00
+00
+00
+00
+66
+66
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-34578
+ENCODING 10460
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+06
+06
+00
+00
+06
+06
+00
+00
+60
+60
+00
+00
+66
+66
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-134578
+ENCODING 10461
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+66
+66
+00
+00
+06
+06
+00
+00
+60
+60
+00
+00
+66
+66
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-234578
+ENCODING 10462
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+06
+06
+00
+00
+66
+66
+00
+00
+60
+60
+00
+00
+66
+66
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-1234578
+ENCODING 10463
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+66
+66
+00
+00
+66
+66
+00
+00
+60
+60
+00
+00
+66
+66
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-678
+ENCODING 10464
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+00
+00
+06
+06
+00
+00
+66
+66
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-1678
+ENCODING 10465
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+60
+60
+00
+00
+00
+00
+00
+00
+06
+06
+00
+00
+66
+66
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-2678
+ENCODING 10466
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+60
+60
+00
+00
+06
+06
+00
+00
+66
+66
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-12678
+ENCODING 10467
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+60
+60
+00
+00
+60
+60
+00
+00
+06
+06
+00
+00
+66
+66
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-3678
+ENCODING 10468
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+00
+00
+00
+00
+66
+66
+00
+00
+66
+66
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-13678
+ENCODING 10469
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+60
+60
+00
+00
+00
+00
+00
+00
+66
+66
+00
+00
+66
+66
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-23678
+ENCODING 10470
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+60
+60
+00
+00
+66
+66
+00
+00
+66
+66
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-123678
+ENCODING 10471
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+60
+60
+00
+00
+60
+60
+00
+00
+66
+66
+00
+00
+66
+66
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-4678
+ENCODING 10472
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+06
+06
+00
+00
+00
+00
+00
+00
+06
+06
+00
+00
+66
+66
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-14678
+ENCODING 10473
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+66
+66
+00
+00
+00
+00
+00
+00
+06
+06
+00
+00
+66
+66
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-24678
+ENCODING 10474
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+06
+06
+00
+00
+60
+60
+00
+00
+06
+06
+00
+00
+66
+66
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-124678
+ENCODING 10475
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+66
+66
+00
+00
+60
+60
+00
+00
+06
+06
+00
+00
+66
+66
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-34678
+ENCODING 10476
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+06
+06
+00
+00
+00
+00
+00
+00
+66
+66
+00
+00
+66
+66
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-134678
+ENCODING 10477
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+66
+66
+00
+00
+00
+00
+00
+00
+66
+66
+00
+00
+66
+66
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-234678
+ENCODING 10478
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+06
+06
+00
+00
+60
+60
+00
+00
+66
+66
+00
+00
+66
+66
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-1234678
+ENCODING 10479
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+66
+66
+00
+00
+60
+60
+00
+00
+66
+66
+00
+00
+66
+66
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-5678
+ENCODING 10480
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+06
+06
+00
+00
+06
+06
+00
+00
+66
+66
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-15678
+ENCODING 10481
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+60
+60
+00
+00
+06
+06
+00
+00
+06
+06
+00
+00
+66
+66
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-25678
+ENCODING 10482
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+66
+66
+00
+00
+06
+06
+00
+00
+66
+66
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-125678
+ENCODING 10483
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+60
+60
+00
+00
+66
+66
+00
+00
+06
+06
+00
+00
+66
+66
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-35678
+ENCODING 10484
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+06
+06
+00
+00
+66
+66
+00
+00
+66
+66
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-135678
+ENCODING 10485
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+60
+60
+00
+00
+06
+06
+00
+00
+66
+66
+00
+00
+66
+66
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-235678
+ENCODING 10486
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+66
+66
+00
+00
+66
+66
+00
+00
+66
+66
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-1235678
+ENCODING 10487
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+60
+60
+00
+00
+66
+66
+00
+00
+66
+66
+00
+00
+66
+66
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-45678
+ENCODING 10488
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+06
+06
+00
+00
+06
+06
+00
+00
+06
+06
+00
+00
+66
+66
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-145678
+ENCODING 10489
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+66
+66
+00
+00
+06
+06
+00
+00
+06
+06
+00
+00
+66
+66
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-245678
+ENCODING 10490
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+06
+06
+00
+00
+66
+66
+00
+00
+06
+06
+00
+00
+66
+66
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-1245678
+ENCODING 10491
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+66
+66
+00
+00
+66
+66
+00
+00
+06
+06
+00
+00
+66
+66
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-345678
+ENCODING 10492
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+06
+06
+00
+00
+06
+06
+00
+00
+66
+66
+00
+00
+66
+66
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-1345678
+ENCODING 10493
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+66
+66
+00
+00
+06
+06
+00
+00
+66
+66
+00
+00
+66
+66
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-2345678
+ENCODING 10494
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+06
+06
+00
+00
+66
+66
+00
+00
+66
+66
+00
+00
+66
+66
+00
+ENDCHAR
+STARTCHAR BRAILLE PATTERN DOTS-12345678
+ENCODING 10495
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+66
+66
+00
+00
+66
+66
+00
+00
+66
+66
+00
+00
+66
+66
+00
+ENDCHAR
+STARTCHAR UPWARDS BLACK ARROW
+ENCODING 11014
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+18
+3C
+7E
+FF
+3C
+3C
+3C
+3C
+3C
+3C
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR DOWNWARDS BLACK ARROW
+ENCODING 11015
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+3C
+3C
+3C
+3C
+3C
+3C
+FF
+7E
+3C
+18
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LEFTWARDS TRIANGLE-HEADED ARROW
+ENCODING 11104
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+20
+60
+FE
+60
+20
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR UPWARDS TRIANGLE-HEADED ARROW
+ENCODING 11105
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+18
+3C
+7E
+18
+18
+18
+18
+18
+18
+18
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR RIGHTWARDS TRIANGLE-HEADED ARROW
+ENCODING 11106
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+08
+0C
+FE
+0C
+08
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR DOWNWARDS TRIANGLE-HEADED ARROW
+ENCODING 11107
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+18
+18
+18
+18
+18
+18
+18
+7E
+3C
+18
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR LEFT RIGHT TRIANGLE-HEADED ARROW
+ENCODING 11108
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+00
+00
+00
+28
+6C
+FE
+6C
+28
+00
+00
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR UP DOWN TRIANGLE-HEADED ARROW
+ENCODING 11109
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+18
+3C
+7E
+18
+18
+18
+18
+7E
+3C
+18
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR char57504
+ENCODING 57504
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+C0
+CC
+DE
+CC
+CC
+CC
+98
+30
+60
+C0
+C0
+C0
+00
+00
+ENDCHAR
+STARTCHAR char57505
+ENCODING 57505
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+40
+40
+40
+40
+40
+38
+00
+12
+1A
+1A
+16
+16
+12
+00
+00
+ENDCHAR
+STARTCHAR char57506
+ENCODING 57506
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+00
+00
+18
+24
+24
+24
+7E
+7E
+66
+66
+7E
+7E
+00
+00
+00
+00
+ENDCHAR
+STARTCHAR char57520
+ENCODING 57520
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+80
+C0
+E0
+F0
+F8
+FC
+FE
+FF
+FF
+FE
+FC
+F8
+F0
+E0
+C0
+80
+ENDCHAR
+STARTCHAR char57521
+ENCODING 57521
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+80
+C0
+60
+30
+18
+0C
+06
+03
+03
+06
+0C
+18
+30
+60
+C0
+80
+ENDCHAR
+STARTCHAR char57522
+ENCODING 57522
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+01
+03
+07
+0F
+1F
+3F
+7F
+FF
+FF
+7F
+3F
+1F
+0F
+07
+03
+01
+ENDCHAR
+STARTCHAR char57523
+ENCODING 57523
+SWIDTH 500 0
+DWIDTH 8 0
+BBX 8 16 0 -4
+BITMAP
+01
+03
+06
+0C
+18
+30
+60
+C0
+C0
+60
+30
+18
+0C
+06
+03
+01
+ENDCHAR
+ENDFONT
diff --git a/src/font/res/spleen-8x16.otb b/src/font/res/spleen-8x16.otb
new file mode 100644
index 000000000..229b7ab02
Binary files /dev/null and b/src/font/res/spleen-8x16.otb differ
diff --git a/src/font/res/spleen-8x16.pcf b/src/font/res/spleen-8x16.pcf
new file mode 100644
index 000000000..1c4fbd3f7
Binary files /dev/null and b/src/font/res/spleen-8x16.pcf differ
diff --git a/src/font/shaper/coretext.zig b/src/font/shaper/coretext.zig
index 6b01d79aa..ab3c6aaab 100644
--- a/src/font/shaper/coretext.zig
+++ b/src/font/shaper/coretext.zig
@@ -6,6 +6,7 @@ const macos = @import("macos");
const font = @import("../main.zig");
const os = @import("../../os/main.zig");
const terminal = @import("../../terminal/main.zig");
+const unicode = @import("../../unicode/main.zig");
const Feature = font.shape.Feature;
const FeatureList = font.shape.FeatureList;
const default_features = font.shape.default_features;
@@ -52,7 +53,7 @@ pub const Shaper = struct {
/// Cached attributes dict for creating CTTypesetter objects.
/// The values in this never change so we can avoid overhead
- /// by just creating it once and saving it for re-use.
+ /// by just creating it once and saving it for reuse.
typesetter_attr_dict: *macos.foundation.Dictionary,
/// List where we cache fonts, so we don't have to remake them for
@@ -97,21 +98,15 @@ pub const Shaper = struct {
self.unichars.deinit(alloc);
}
- fn reset(self: *RunState) !void {
+ fn reset(self: *RunState) void {
self.codepoints.clearRetainingCapacity();
self.unichars.clearRetainingCapacity();
}
};
- const RunOffset = struct {
- x: f64 = 0,
- y: f64 = 0,
- };
-
- const CellOffset = struct {
+ const Offset = struct {
cluster: u32 = 0,
x: f64 = 0,
- y: f64 = 0,
};
/// Create a CoreFoundation Dictionary suitable for
@@ -388,15 +383,16 @@ pub const Shaper = struct {
const line = typesetter.createLine(.{ .location = 0, .length = 0 });
self.cf_release_pool.appendAssumeCapacity(line);
- // This keeps track of the current offsets within a run.
- var run_offset: RunOffset = .{};
+ // This keeps track of the current x offset (sum of advance.width) and
+ // the furthest cluster we've seen so far (max).
+ var run_offset: Offset = .{};
- // This keeps track of the current offsets within a cell.
- var cell_offset: CellOffset = .{};
+ // This keeps track of the cell starting x and cluster.
+ var cell_offset: Offset = .{};
// For debugging positions, turn this on:
- //var start_index: usize = 0;
- //var end_index: usize = 0;
+ //var run_offset_y: f64 = 0.0;
+ //var cell_offset_y: f64 = 0.0;
// Clear our cell buf and make sure we have enough room for the whole
// line of glyphs, so that we can just assume capacity when appending
@@ -416,8 +412,8 @@ pub const Shaper = struct {
// other so we can iterate over them and just append to our
// cell buffer.
const runs = line.getGlyphRuns();
- for (0..runs.getCount()) |i| {
- const ctrun = runs.getValueAtIndex(macos.text.Run, i);
+ for (0..runs.getCount()) |run_i| {
+ const ctrun = runs.getValueAtIndex(macos.text.Run, run_i);
const status = ctrun.getStatus();
if (status.non_monotonic or status.right_to_left) non_ltr = true;
@@ -440,49 +436,87 @@ pub const Shaper = struct {
// Our cluster is also our cell X position. If the cluster changes
// then we need to reset our current cell offsets.
const cluster = state.codepoints.items[index].cluster;
- if (cell_offset.cluster != cluster) pad: {
- // We previously asserted this but for rtl text this is
- // not true. So we check for this and break out. In the
- // future we probably need to reverse pad for rtl but
- // I don't have a solid test case for this yet so let's
- // wait for that.
- if (cell_offset.cluster > cluster) break :pad;
+ if (cell_offset.cluster != cluster) {
+ // We previously asserted that the new cluster is greater
+ // than cell_offset.cluster, but this isn't always true.
+ // See e.g. the "shape Chakma vowel sign with ligature
+ // (vowel sign renders first)" test.
- cell_offset = .{
- .cluster = cluster,
- .x = run_offset.x,
- .y = run_offset.y,
+ const is_after_glyph_from_current_or_next_clusters =
+ cluster <= run_offset.cluster;
+
+ const is_first_codepoint_in_cluster = blk: {
+ var i = index;
+ while (i > 0) {
+ i -= 1;
+ const codepoint = state.codepoints.items[i];
+
+ // Skip surrogate pair padding
+ if (codepoint.codepoint == 0) continue;
+ break :blk codepoint.cluster != cluster;
+ } else break :blk true;
};
- // For debugging positions, turn this on:
- // start_index = index;
- // end_index = index;
- //} else {
- // if (index < start_index) {
- // start_index = index;
- // }
- // if (index > end_index) {
- // end_index = index;
- // }
+ // We need to reset the `cell_offset` at the start of a new
+ // cluster, but we do that conditionally if the codepoint
+ // `is_first_codepoint_in_cluster` and the cluster is not
+ // `is_after_glyph_from_current_or_next_clusters`, which is
+ // a heuristic to detect ligatures and avoid positioning
+ // glyphs that mark ligatures incorrectly. The idea is that
+ // if the first codepoint in a cluster doesn't appear in
+ // the stream, it's very likely that it combined with
+ // codepoints from a previous cluster into a ligature.
+ // Then, the subsequent codepoints are very likely marking
+ // glyphs that are placed relative to that ligature, so if
+ // we were to reset the `cell_offset` to align it with the
+ // grid, the positions would be off. The
+ // `!is_after_glyph_from_current_or_next_clusters` check is
+ // needed in case these marking glyphs come from a later
+ // cluster but are rendered first (see the Chakma and
+ // Bengali tests). In that case when we get to the
+ // codepoint that `is_first_codepoint_in_cluster`, but in a
+ // cluster that
+ // `is_after_glyph_from_current_or_next_clusters`, we don't
+ // want to reset to the grid and cause the positions to be
+ // off. (Note that we could go back and align the cells to
+ // the grid starting from the one from the cluster that
+ // rendered out of order, but that is more complicated so
+ // we don't do that for now. Also, it's TBD if there are
+ // exceptions to this heuristic for detecting ligatures,
+ // but using the logging below seems to show it works
+ // well.)
+ if (is_first_codepoint_in_cluster and
+ !is_after_glyph_from_current_or_next_clusters)
+ {
+ cell_offset = .{
+ .cluster = cluster,
+ .x = run_offset.x,
+ };
+
+ // For debugging positions, turn this on:
+ //cell_offset_y = run_offset_y;
+ }
}
// For debugging positions, turn this on:
- //try self.debugPositions(alloc, run_offset, cell_offset, position, start_index, end_index, index);
+ //try self.debugPositions(alloc, run_offset, run_offset_y, cell_offset, cell_offset_y, position, index);
const x_offset = position.x - cell_offset.x;
- const y_offset = position.y - cell_offset.y;
self.cell_buf.appendAssumeCapacity(.{
- .x = @intCast(cluster),
+ .x = @intCast(cell_offset.cluster),
.x_offset = @intFromFloat(@round(x_offset)),
- .y_offset = @intFromFloat(@round(y_offset)),
+ .y_offset = @intFromFloat(@round(position.y)),
.glyph_index = glyph,
});
// Add our advances to keep track of our run offsets.
// Advances apply to the NEXT cell.
run_offset.x += advance.width;
- run_offset.y += advance.height;
+ run_offset.cluster = @max(run_offset.cluster, cluster);
+
+ // For debugging positions, turn this on:
+ //run_offset_y += advance.height;
}
}
@@ -610,8 +644,8 @@ pub const Shaper = struct {
pub const RunIteratorHook = struct {
shaper: *Shaper,
- pub fn prepare(self: *RunIteratorHook) !void {
- try self.shaper.run_state.reset();
+ pub fn prepare(self: *RunIteratorHook) void {
+ self.shaper.run_state.reset();
// log.warn("----------- run reset -------------", .{});
}
@@ -647,7 +681,7 @@ pub const Shaper = struct {
});
}
- pub fn finalize(self: RunIteratorHook) !void {
+ pub fn finalize(self: RunIteratorHook) void {
_ = self;
}
};
@@ -655,55 +689,145 @@ pub const Shaper = struct {
fn debugPositions(
self: *Shaper,
alloc: Allocator,
- run_offset: RunOffset,
- cell_offset: CellOffset,
+ run_offset: Offset,
+ run_offset_y: f64,
+ cell_offset: Offset,
+ cell_offset_y: f64,
position: macos.graphics.Point,
- start_index: usize,
- end_index: usize,
index: usize,
) !void {
const state = &self.run_state;
const x_offset = position.x - cell_offset.x;
- const y_offset = position.y - cell_offset.y;
const advance_x_offset = run_offset.x - cell_offset.x;
- const advance_y_offset = run_offset.y - cell_offset.y;
+ const advance_y_offset = run_offset_y - cell_offset_y;
const x_offset_diff = x_offset - advance_x_offset;
- const y_offset_diff = y_offset - advance_y_offset;
+ const y_offset_diff = position.y - advance_y_offset;
+ const positions_differ = @abs(x_offset_diff) > 0.0001 or @abs(y_offset_diff) > 0.0001;
+ const old_offset_y = position.y - cell_offset_y;
+ const position_y_differs = @abs(cell_offset_y) > 0.0001;
+ const cluster = state.codepoints.items[index].cluster;
+ const cluster_differs = cluster != cell_offset.cluster;
- if (@abs(x_offset_diff) > 0.0001 or @abs(y_offset_diff) > 0.0001) {
+ // To debug every loop, flip this to true:
+ const extra_debugging = false;
+
+ const is_previous_codepoint_prepend = if (cluster_differs or
+ extra_debugging)
+ blk: {
+ var i = index;
+ while (i > 0) {
+ i -= 1;
+ const codepoint = state.codepoints.items[i];
+
+ // Skip surrogate pair padding
+ if (codepoint.codepoint == 0) continue;
+
+ break :blk unicode.table.get(@intCast(codepoint.codepoint)).grapheme_boundary_class == .prepend;
+ }
+ break :blk false;
+ } else false;
+
+ const formatted_cps = if (positions_differ or
+ position_y_differs or
+ cluster_differs or
+ extra_debugging)
+ blk: {
var allocating = std.Io.Writer.Allocating.init(alloc);
const writer = &allocating.writer;
- const codepoints = state.codepoints.items[start_index .. end_index + 1];
- for (codepoints) |cp| {
- if (cp.codepoint == 0) continue; // Skip surrogate pair padding
- try writer.print("\\u{{{x}}}", .{cp.codepoint});
+ const codepoints = state.codepoints.items;
+ var last_cluster: ?u32 = null;
+ for (codepoints, 0..) |cp, i| {
+ if ((@as(i32, @intCast(cp.cluster)) >= @as(i32, @intCast(cell_offset.cluster)) - 1 and
+ cp.cluster <= cluster + 1) and
+ cp.codepoint != 0 // Skip surrogate pair padding
+ ) {
+ if (last_cluster) |last| {
+ if (cp.cluster != last) {
+ try writer.writeAll(" ");
+ }
+ }
+ if (i == index) {
+ try writer.writeAll("โธ");
+ }
+ // Using Python syntax for easier debugging
+ if (cp.codepoint > 0xFFFF) {
+ try writer.print("\\U{x:0>8}", .{cp.codepoint});
+ } else {
+ try writer.print("\\u{x:0>4}", .{cp.codepoint});
+ }
+ last_cluster = cp.cluster;
+ }
}
try writer.writeAll(" โ ");
for (codepoints) |cp| {
- if (cp.codepoint == 0) continue; // Skip surrogate pair padding
- try writer.print("{u}", .{@as(u21, @intCast(cp.codepoint))});
+ if ((@as(i32, @intCast(cp.cluster)) >= @as(i32, @intCast(cell_offset.cluster)) - 1 and
+ cp.cluster <= cluster + 1) and
+ cp.codepoint != 0 // Skip surrogate pair padding
+ ) {
+ try writer.print("{u}", .{@as(u21, @intCast(cp.codepoint))});
+ }
}
- const formatted_cps = try allocating.toOwnedSlice();
+ break :blk try allocating.toOwnedSlice();
+ } else "";
- // Note that the codepoints from `start_index .. end_index + 1`
- // might not include all the codepoints being shaped. Sometimes a
- // codepoint gets represented in a glyph with a later codepoint
- // such that the index for the former codepoint is skipped and just
- // the index for the latter codepoint is used. Additionally, this
- // gets called as we iterate through the glyphs, so it won't
- // include the codepoints that come later that might be affecting
- // positions for the current glyph. Usually though, for that case
- // the positions of the later glyphs will also be affected and show
- // up in the logs.
- log.warn("position differs from advance: cluster={d} pos=({d:.2},{d:.2}) adv=({d:.2},{d:.2}) diff=({d:.2},{d:.2}) current cp={x}, cps={s}", .{
+ if (extra_debugging) {
+ log.warn("extra debugging of positions index={d} cell_offset.cluster={d} cluster={d} run_offset.cluster={d} diff={d} pos=({d:.2},{d:.2}) run_offset=({d:.2},{d:.2}) cell_offset=({d:.2},{d:.2}) is_prev_prepend={} cps = {s}", .{
+ index,
cell_offset.cluster,
+ cluster,
+ run_offset.cluster,
+ @as(isize, @intCast(cluster)) - @as(isize, @intCast(cell_offset.cluster)),
x_offset,
- y_offset,
+ position.y,
+ run_offset.x,
+ run_offset_y,
+ cell_offset.x,
+ cell_offset_y,
+ is_previous_codepoint_prepend,
+ formatted_cps,
+ });
+ }
+
+ if (positions_differ) {
+ log.warn("position differs from advance: cluster={d} pos=({d:.2},{d:.2}) adv=({d:.2},{d:.2}) diff=({d:.2},{d:.2}) cps = {s}", .{
+ cluster,
+ x_offset,
+ position.y,
advance_x_offset,
advance_y_offset,
x_offset_diff,
y_offset_diff,
- state.codepoints.items[index].codepoint,
+ formatted_cps,
+ });
+ }
+
+ if (position_y_differs) {
+ log.warn("position.y differs from old offset.y: cluster={d} pos=({d:.2},{d:.2}) run_offset=({d:.2},{d:.2}) cell_offset=({d:.2},{d:.2}) old offset.y={d:.2} cps = {s}", .{
+ cluster,
+ x_offset,
+ position.y,
+ run_offset.x,
+ run_offset_y,
+ cell_offset.x,
+ cell_offset_y,
+ old_offset_y,
+ formatted_cps,
+ });
+ }
+
+ if (cluster_differs) {
+ log.warn("cell_offset.cluster differs from cluster (potential ligature detected) cell_offset.cluster={d} cluster={d} run_offset.cluster={d} diff={d} pos=({d:.2},{d:.2}) run_offset=({d:.2},{d:.2}) cell_offset=({d:.2},{d:.2}) is_prev_prepend={} cps = {s}", .{
+ cell_offset.cluster,
+ cluster,
+ run_offset.cluster,
+ @as(isize, @intCast(cluster)) - @as(isize, @intCast(cell_offset.cluster)),
+ x_offset,
+ position.y,
+ run_offset.x,
+ run_offset_y,
+ cell_offset.x,
+ cell_offset_y,
+ is_previous_codepoint_prepend,
formatted_cps,
});
}
@@ -1453,11 +1577,13 @@ test "shape Devanagari string" {
try testing.expect(run != null);
const cells = try shaper.shape(run.?);
+ // To understand the `x`/`cluster` assertions here, run with the "For
+ // debugging positions" code turned on and `extra_debugging` set to true.
try testing.expectEqual(@as(usize, 8), cells.len);
try testing.expectEqual(@as(u16, 0), cells[0].x);
try testing.expectEqual(@as(u16, 1), cells[1].x);
try testing.expectEqual(@as(u16, 2), cells[2].x);
- try testing.expectEqual(@as(u16, 3), cells[3].x);
+ try testing.expectEqual(@as(u16, 4), cells[3].x);
try testing.expectEqual(@as(u16, 4), cells[4].x);
try testing.expectEqual(@as(u16, 5), cells[5].x);
try testing.expectEqual(@as(u16, 5), cells[6].x);
@@ -1522,6 +1648,269 @@ test "shape Tai Tham vowels (position differs from advance)" {
try testing.expectEqual(@as(usize, 1), count);
}
+test "shape Tai Tham letters (position.y differs from advance)" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ // We need a font that supports Tai Tham for this to work, if we can't find
+ // Noto Sans Tai Tham, which is a system font on macOS, we just skip the
+ // test.
+ var testdata = testShaperWithDiscoveredFont(
+ alloc,
+ "Noto Sans Tai Tham",
+ ) catch return error.SkipZigTest;
+ defer testdata.deinit();
+
+ var buf: [32]u8 = undefined;
+ var buf_idx: usize = 0;
+
+ // First grapheme cluster:
+ buf_idx += try std.unicode.utf8Encode(0x1a49, buf[buf_idx..]); // HA
+ buf_idx += try std.unicode.utf8Encode(0x1a60, buf[buf_idx..]); // SAKOT
+ // Second grapheme cluster, combining with the first in a ligature:
+ buf_idx += try std.unicode.utf8Encode(0x1a3f, buf[buf_idx..]); // YA
+ buf_idx += try std.unicode.utf8Encode(0x1a69, buf[buf_idx..]); // U
+
+ // Make a screen with some data
+ var t = try terminal.Terminal.init(alloc, .{ .cols = 30, .rows = 3 });
+ defer t.deinit(alloc);
+
+ // Enable grapheme clustering
+ t.modes.set(.grapheme_cluster, true);
+
+ var s = t.vtStream();
+ defer s.deinit();
+ try s.nextSlice(buf[0..buf_idx]);
+
+ var state: terminal.RenderState = .empty;
+ defer state.deinit(alloc);
+ try state.update(alloc, &t);
+
+ // Get our run iterator
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+
+ const cells = try shaper.shape(run);
+ try testing.expectEqual(@as(usize, 3), cells.len);
+ try testing.expectEqual(@as(u16, 0), cells[0].x);
+ try testing.expectEqual(@as(u16, 0), cells[1].x);
+ try testing.expectEqual(@as(u16, 0), cells[2].x); // U from second grapheme
+
+ // The U glyph renders at a y below zero
+ try testing.expectEqual(@as(i16, -3), cells[2].y_offset);
+ }
+ try testing.expectEqual(@as(usize, 1), count);
+}
+
+test "shape Javanese ligatures" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ // We need a font that supports Javanese for this to work, if we can't find
+ // Noto Sans Javanese Regular, which is a system font on macOS, we just
+ // skip the test.
+ var testdata = testShaperWithDiscoveredFont(
+ alloc,
+ "Noto Sans Javanese",
+ ) catch return error.SkipZigTest;
+ defer testdata.deinit();
+
+ var buf: [32]u8 = undefined;
+ var buf_idx: usize = 0;
+
+ // First grapheme cluster:
+ buf_idx += try std.unicode.utf8Encode(0xa9a4, buf[buf_idx..]); // NA
+ buf_idx += try std.unicode.utf8Encode(0xa9c0, buf[buf_idx..]); // PANGKON
+ // Second grapheme cluster, combining with the first in a ligature:
+ buf_idx += try std.unicode.utf8Encode(0xa9b2, buf[buf_idx..]); // HA
+ buf_idx += try std.unicode.utf8Encode(0xa9b8, buf[buf_idx..]); // Vowel sign SUKU
+
+ // Make a screen with some data
+ var t = try terminal.Terminal.init(alloc, .{ .cols = 30, .rows = 3 });
+ defer t.deinit(alloc);
+
+ // Enable grapheme clustering
+ t.modes.set(.grapheme_cluster, true);
+
+ var s = t.vtStream();
+ defer s.deinit();
+ try s.nextSlice(buf[0..buf_idx]);
+
+ var state: terminal.RenderState = .empty;
+ defer state.deinit(alloc);
+ try state.update(alloc, &t);
+
+ // Get our run iterator
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+
+ const cells = try shaper.shape(run);
+ const cell_width = run.grid.metrics.cell_width;
+ try testing.expectEqual(@as(usize, 3), cells.len);
+ try testing.expectEqual(@as(u16, 0), cells[0].x);
+ try testing.expectEqual(@as(u16, 0), cells[1].x);
+ try testing.expectEqual(@as(u16, 0), cells[2].x);
+
+ // The vowel sign SUKU renders with correct x_offset
+ try testing.expect(cells[2].x_offset > 3 * cell_width);
+ }
+ try testing.expectEqual(@as(usize, 1), count);
+}
+
+test "shape Chakma vowel sign with ligature (vowel sign renders first)" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ // We need a font that supports Chakma for this to work, if we can't find
+ // Noto Sans Chakma Regular, which is a system font on macOS, we just skip
+ // the test.
+ var testdata = testShaperWithDiscoveredFont(
+ alloc,
+ "Noto Sans Chakma",
+ ) catch return error.SkipZigTest;
+ defer testdata.deinit();
+
+ var buf: [32]u8 = undefined;
+ var buf_idx: usize = 0;
+
+ // First grapheme cluster:
+ buf_idx += try std.unicode.utf8Encode(0x1111d, buf[buf_idx..]); // BAA
+ // Second grapheme cluster:
+ buf_idx += try std.unicode.utf8Encode(0x11116, buf[buf_idx..]); // TAA
+ buf_idx += try std.unicode.utf8Encode(0x11133, buf[buf_idx..]); // Virama
+ // Third grapheme cluster, combining with the second in a ligature:
+ buf_idx += try std.unicode.utf8Encode(0x11120, buf[buf_idx..]); // YYAA
+ buf_idx += try std.unicode.utf8Encode(0x1112c, buf[buf_idx..]); // Vowel Sign U
+
+ // Make a screen with some data
+ var t = try terminal.Terminal.init(alloc, .{ .cols = 30, .rows = 3 });
+ defer t.deinit(alloc);
+
+ // Enable grapheme clustering
+ t.modes.set(.grapheme_cluster, true);
+
+ var s = t.vtStream();
+ defer s.deinit();
+ try s.nextSlice(buf[0..buf_idx]);
+
+ var state: terminal.RenderState = .empty;
+ defer state.deinit(alloc);
+ try state.update(alloc, &t);
+
+ // Get our run iterator
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+
+ const cells = try shaper.shape(run);
+ try testing.expectEqual(@as(usize, 4), cells.len);
+ try testing.expectEqual(@as(u16, 0), cells[0].x);
+ // See the giant "We need to reset the `cell_offset`" comment, but here
+ // we should technically have the rest of these be `x` of 1, but that
+ // would require going back in the stream to adjust past cells, and
+ // we don't take on that complexity.
+ try testing.expectEqual(@as(u16, 0), cells[1].x);
+ try testing.expectEqual(@as(u16, 0), cells[2].x);
+ try testing.expectEqual(@as(u16, 0), cells[3].x);
+
+ // The vowel sign U renders before the TAA:
+ try testing.expect(cells[1].x_offset < cells[2].x_offset);
+ }
+ try testing.expectEqual(@as(usize, 1), count);
+}
+
+test "shape Bengali ligatures with out of order vowels" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ // We need a font that supports Bengali for this to work, if we can't find
+ // Arial Unicode MS, which is a system font on macOS, we just skip the
+ // test.
+ var testdata = testShaperWithDiscoveredFont(
+ alloc,
+ "Arial Unicode MS",
+ ) catch return error.SkipZigTest;
+ defer testdata.deinit();
+
+ var buf: [32]u8 = undefined;
+ var buf_idx: usize = 0;
+
+ // First grapheme cluster:
+ buf_idx += try std.unicode.utf8Encode(0x09b0, buf[buf_idx..]); // RA
+ buf_idx += try std.unicode.utf8Encode(0x09be, buf[buf_idx..]); // Vowel sign AA
+ // Second grapheme cluster:
+ buf_idx += try std.unicode.utf8Encode(0x09b7, buf[buf_idx..]); // SSA
+ buf_idx += try std.unicode.utf8Encode(0x09cd, buf[buf_idx..]); // Virama
+ // Third grapheme cluster, combining with the second in a ligature:
+ buf_idx += try std.unicode.utf8Encode(0x099f, buf[buf_idx..]); // TTA
+ buf_idx += try std.unicode.utf8Encode(0x09cd, buf[buf_idx..]); // Virama
+ // Fourth grapheme cluster, combining with the previous two in a ligature:
+ buf_idx += try std.unicode.utf8Encode(0x09b0, buf[buf_idx..]); // RA
+ buf_idx += try std.unicode.utf8Encode(0x09c7, buf[buf_idx..]); // Vowel sign E
+
+ // Make a screen with some data
+ var t = try terminal.Terminal.init(alloc, .{ .cols = 30, .rows = 3 });
+ defer t.deinit(alloc);
+
+ // Enable grapheme clustering
+ t.modes.set(.grapheme_cluster, true);
+
+ var s = t.vtStream();
+ defer s.deinit();
+ try s.nextSlice(buf[0..buf_idx]);
+
+ var state: terminal.RenderState = .empty;
+ defer state.deinit(alloc);
+ try state.update(alloc, &t);
+
+ // Get our run iterator
+ var shaper = &testdata.shaper;
+ var it = shaper.runIterator(.{
+ .grid = testdata.grid,
+ .cells = state.row_data.get(0).cells.slice(),
+ });
+ var count: usize = 0;
+ while (try it.next(alloc)) |run| {
+ count += 1;
+
+ const cells = try shaper.shape(run);
+ try testing.expectEqual(@as(usize, 8), cells.len);
+ try testing.expectEqual(@as(u16, 0), cells[0].x);
+ try testing.expectEqual(@as(u16, 0), cells[1].x);
+ // See the giant "We need to reset the `cell_offset`" comment, but here
+ // we should technically have the rest of these be `x` of 1, but that
+ // would require going back in the stream to adjust past cells, and
+ // we don't take on that complexity.
+ try testing.expectEqual(@as(u16, 0), cells[2].x);
+ try testing.expectEqual(@as(u16, 0), cells[3].x);
+ try testing.expectEqual(@as(u16, 0), cells[4].x);
+ try testing.expectEqual(@as(u16, 0), cells[5].x);
+ try testing.expectEqual(@as(u16, 0), cells[6].x);
+ try testing.expectEqual(@as(u16, 0), cells[7].x);
+
+ // The vowel sign E renders before the SSA:
+ try testing.expect(cells[2].x_offset < cells[3].x_offset);
+ }
+ try testing.expectEqual(@as(usize, 1), count);
+}
+
test "shape box glyphs" {
const testing = std.testing;
const alloc = testing.allocator;
@@ -2259,7 +2648,7 @@ fn testShaperWithDiscoveredFont(alloc: Allocator, font_req: [:0]const u8) !TestS
.monospace = false,
});
defer disco_it.deinit();
- var face: font.DeferredFace = (try disco_it.next()).?;
+ var face: font.DeferredFace = (try disco_it.next()) orelse return error.FontNotFound;
errdefer face.deinit();
_ = try c.add(
alloc,
diff --git a/src/font/shaper/harfbuzz.zig b/src/font/shaper/harfbuzz.zig
index e4a9301e8..6dcc0f94e 100644
--- a/src/font/shaper/harfbuzz.zig
+++ b/src/font/shaper/harfbuzz.zig
@@ -175,7 +175,7 @@ pub const Shaper = struct {
pub const RunIteratorHook = struct {
shaper: *Shaper,
- pub fn prepare(self: RunIteratorHook) !void {
+ pub fn prepare(self: RunIteratorHook) void {
// Reset the buffer for our current run
self.shaper.hb_buf.reset();
self.shaper.hb_buf.setContentType(.unicode);
@@ -191,7 +191,7 @@ pub const Shaper = struct {
self.shaper.hb_buf.add(cp, cluster);
}
- pub fn finalize(self: RunIteratorHook) !void {
+ pub fn finalize(self: RunIteratorHook) void {
self.shaper.hb_buf.guessSegmentProperties();
}
};
diff --git a/src/font/shaper/run.zig b/src/font/shaper/run.zig
index 85c5c410b..45c5d38ca 100644
--- a/src/font/shaper/run.zig
+++ b/src/font/shaper/run.zig
@@ -73,7 +73,7 @@ pub const RunIterator = struct {
var current_font: font.Collection.Index = .{};
// Allow the hook to prepare
- try self.hooks.prepare();
+ self.hooks.prepare();
// Initialize our hash for this run.
var hasher = Hasher.init(0);
@@ -283,7 +283,7 @@ pub const RunIterator = struct {
}
// Finalize our buffer
- try self.hooks.finalize();
+ self.hooks.finalize();
// Add our length to the hash as an additional mechanism to avoid collisions
autoHash(&hasher, j - self.i);
diff --git a/src/font/shaper/web_canvas.zig b/src/font/shaper/web_canvas.zig
index c8334ec9d..590c1d2a3 100644
--- a/src/font/shaper/web_canvas.zig
+++ b/src/font/shaper/web_canvas.zig
@@ -4,6 +4,7 @@ const Allocator = std.mem.Allocator;
const font = @import("../main.zig");
const terminal = @import("../../terminal/main.zig");
const unicode = @import("../../unicode/main.zig");
+const uucode = @import("uucode");
const log = std.log.scoped(.font_shaper);
@@ -111,7 +112,7 @@ pub const Shaper = struct {
// font ligatures. However, we do support grapheme clustering.
// This means we can render things like skin tone emoji but
// we can't render things like single glyph "=>".
- var break_state: unicode.GraphemeBreakState = .{};
+ var break_state: uucode.grapheme.BreakState = .default;
var cp1: u21 = @intCast(codepoints[0]);
var start: usize = 0;
diff --git a/src/font/sprite/Face.zig b/src/font/sprite/Face.zig
index 94bfa2f0b..596a92044 100644
--- a/src/font/sprite/Face.zig
+++ b/src/font/sprite/Face.zig
@@ -405,7 +405,7 @@ fn testDrawRanges(
const padding_x = width / 4;
const padding_y = height / 4;
- // Canvas to draw glyphs on, we'll re-use this for all glyphs.
+ // Canvas to draw glyphs on, we'll reuse this for all glyphs.
var canvas = try font.sprite.Canvas.init(
alloc,
width,
diff --git a/src/font/sprite/canvas.zig b/src/font/sprite/canvas.zig
index 19d27eb45..7904f20a5 100644
--- a/src/font/sprite/canvas.zig
+++ b/src/font/sprite/canvas.zig
@@ -360,7 +360,7 @@ pub const Canvas = struct {
pub fn strokePath(
self: *Canvas,
path: z2d.Path,
- opts: z2d.painter.StrokeOpts,
+ opts: z2d.painter.StrokeOptions,
color: Color,
) z2d.painter.StrokeError!void {
try z2d.painter.stroke(
@@ -380,7 +380,7 @@ pub const Canvas = struct {
pub fn innerStrokePath(
self: *Canvas,
path: z2d.Path,
- opts: z2d.painter.StrokeOpts,
+ opts: z2d.painter.StrokeOptions,
color: Color,
) (z2d.painter.StrokeError || z2d.painter.FillError)!void {
// On one surface we fill the shape, this will be a mask we
@@ -459,7 +459,7 @@ pub const Canvas = struct {
pub fn fillPath(
self: *Canvas,
path: z2d.Path,
- opts: z2d.painter.FillOpts,
+ opts: z2d.painter.FillOptions,
color: Color,
) z2d.painter.FillError!void {
try z2d.painter.fill(
diff --git a/src/input.zig b/src/input.zig
index be84a60d6..bad3ac1f3 100644
--- a/src/input.zig
+++ b/src/input.zig
@@ -4,6 +4,7 @@ const builtin = @import("builtin");
const config = @import("input/config.zig");
const mouse = @import("input/mouse.zig");
const key = @import("input/key.zig");
+const key_mods = @import("input/key_mods.zig");
const keyboard = @import("input/keyboard.zig");
pub const command = @import("input/command.zig");
@@ -21,8 +22,9 @@ pub const Link = @import("input/Link.zig");
pub const Key = key.Key;
pub const KeyboardLayout = keyboard.Layout;
pub const KeyEvent = key.KeyEvent;
+pub const KeyRemapSet = key_mods.RemapSet;
pub const InspectorMode = Binding.Action.InspectorMode;
-pub const Mods = key.Mods;
+pub const Mods = key_mods.Mods;
pub const MouseButton = mouse.Button;
pub const MouseButtonState = mouse.ButtonState;
pub const MousePressureStage = mouse.PressureStage;
diff --git a/src/input/Binding.zig b/src/input/Binding.zig
index 9f3ad8a2a..57414d764 100644
--- a/src/input/Binding.zig
+++ b/src/input/Binding.zig
@@ -8,7 +8,9 @@ const assert = @import("../quirks.zig").inlineAssert;
const build_config = @import("../build_config.zig");
const uucode = @import("uucode");
const EntryFormatter = @import("../config/formatter.zig").EntryFormatter;
+const deepEqual = @import("../datastruct/comparison.zig").deepEqual;
const key = @import("key.zig");
+const key_mods = @import("key_mods.zig");
const KeyEvent = key.KeyEvent;
/// The trigger that needs to be performed to execute the action.
@@ -44,6 +46,27 @@ pub const Flags = packed struct {
/// performed. If the action can't be performed then the binding acts as
/// if it doesn't exist.
performable: bool = false,
+
+ /// C type
+ pub const C = u8;
+
+ /// Converts this to a C-compatible value.
+ ///
+ /// Sync with ghostty.h for enums.
+ pub fn cval(self: Flags) C {
+ const Backing = @typeInfo(Flags).@"struct".backing_integer.?;
+ return @as(Backing, @bitCast(self));
+ }
+
+ test "cval" {
+ const testing = std.testing;
+ try testing.expectEqual(@as(u8, 0b0001), (Flags{}).cval());
+ try testing.expectEqual(@as(u8, 0b0000), (Flags{ .consumed = false }).cval());
+ try testing.expectEqual(@as(u8, 0b0011), (Flags{ .all = true }).cval());
+ try testing.expectEqual(@as(u8, 0b0101), (Flags{ .global = true }).cval());
+ try testing.expectEqual(@as(u8, 0b1001), (Flags{ .performable = true }).cval());
+ try testing.expectEqual(@as(u8, 0b1111), (Flags{ .consumed = true, .all = true, .global = true, .performable = true }).cval());
+ }
};
/// Full binding parser. The binding parser is implemented as an iterator
@@ -52,6 +75,7 @@ pub const Parser = struct {
trigger_it: SequenceIterator,
action: Action,
flags: Flags = .{},
+ chain: bool,
pub const Elem = union(enum) {
/// A leader trigger in a sequence.
@@ -59,6 +83,12 @@ pub const Parser = struct {
/// The final trigger and action in a sequence.
binding: Binding,
+
+ /// A chained action `chain=` that should be appended
+ /// to the previous binding. Note that any action is parsed, including
+ /// invalid actions for chains such as `unbind`. We expect downstream
+ /// consumers to validate that the action is valid for chaining.
+ chain: Action,
};
pub fn init(raw_input: []const u8) Error!Parser {
@@ -95,12 +125,23 @@ pub const Parser = struct {
return Error.InvalidFormat;
};
+ // Detect chains. Chains must not have flags.
+ const chain = std.mem.eql(u8, input[0..eql_idx], "chain");
+ if (chain and start_idx > 0) return Error.InvalidFormat;
+
// Sequence iterator goes up to the equal, action is after. We can
// parse the action now.
return .{
- .trigger_it = .{ .input = input[0..eql_idx] },
+ .trigger_it = .{
+ // This is kind of hacky but we put a dummy trigger
+ // for chained inputs. The `next` will never yield this
+ // because we have chain set. When we find a nicer way to
+ // do this we can remove it, the e2e is tested.
+ .input = if (chain) "a" else input[0..eql_idx],
+ },
.action = try .parse(input[eql_idx + 1 ..]),
.flags = flags,
+ .chain = chain,
};
}
@@ -156,6 +197,9 @@ pub const Parser = struct {
return .{ .leader = trigger };
}
+ // If we're a chain then return it as-is.
+ if (self.chain) return .{ .chain = self.action };
+
// Out of triggers, yield the final action.
return .{ .binding = .{
.trigger = trigger,
@@ -198,12 +242,19 @@ const SequenceIterator = struct {
/// Parse a single, non-sequenced binding. To support sequences you must
/// use parse. This is a convenience function for single bindings aimed
/// primarily at tests.
-fn parseSingle(raw_input: []const u8) (Error || error{UnexpectedSequence})!Binding {
+///
+/// This doesn't support `chain` either, since chaining requires some
+/// stateful concept of a prior binding.
+fn parseSingle(raw_input: []const u8) (Error || error{
+ UnexpectedChain,
+ UnexpectedSequence,
+})!Binding {
var p = try Parser.init(raw_input);
const elem = (try p.next()) orelse return Error.InvalidFormat;
return switch (elem) {
.leader => error.UnexpectedSequence,
.binding => elem.binding,
+ .chain => error.UnexpectedChain,
};
}
@@ -340,6 +391,11 @@ pub const Action = union(enum) {
/// If a previous search is active, it is replaced.
search: []const u8,
+ /// Start a search for the current text selection. If there is no
+ /// selection, this does nothing. If a search is already active, this
+ /// changes the search terms.
+ search_selection,
+
/// Navigate the search results. If there is no active search, this
/// is not performed.
navigate_search: NavigateSearch,
@@ -799,6 +855,51 @@ pub const Action = union(enum) {
/// be undone or redone.
redo,
+ /// End the currently active key sequence, if any, and flush the
+ /// keys up to this point to the terminal, excluding the key that
+ /// triggered this action.
+ ///
+ /// For example: `ctrl+w>escape=end_key_sequence` would encode
+ /// `ctrl+w` to the terminal and exit the key sequence.
+ ///
+ /// Normally, an invalid sequence will reset the key sequence and
+ /// flush all data including the invalid key. This action allows
+ /// you to flush only the prior keys, which is useful when you want
+ /// to bind something like a control key (`ctrl+w`) but not send
+ /// additional inputs.
+ end_key_sequence,
+
+ /// Activate a named key table (see `keybind` configuration documentation).
+ /// The named key table will remain active until `deactivate_key_table`
+ /// is called. If you want a one-shot key table activation, use the
+ /// `activate_key_table_once` action instead.
+ ///
+ /// If the named key table does not exist, this action has no effect
+ /// and performable will report false.
+ ///
+ /// If the named key table is already the currently active key table,
+ /// this action has no effect and performable will report false.
+ activate_key_table: []const u8,
+
+ /// Same as activate_key_table, but the key table will only be active
+ /// until the first valid keybinding from that table is used (including
+ /// any defined `catch_all` bindings).
+ ///
+ /// The "once" check is only done if this is the currently active
+ /// key table. If another key table is activated later, then this
+ /// table will remain active until it pops back out to being the
+ /// active key table.
+ activate_key_table_once: []const u8,
+
+ /// Deactivate the currently active key table, if any. The next most
+ /// recently activated key table (if any) will become active again.
+ /// If no key table is active, this action has no effect.
+ deactivate_key_table,
+
+ /// Deactivate all active key tables. If no active key table exists,
+ /// this will report performable as false.
+ deactivate_all_key_tables,
+
/// Quit Ghostty.
quit,
@@ -1211,6 +1312,7 @@ pub const Action = union(enum) {
.cursor_key,
.search,
.navigate_search,
+ .search_selection,
.start_search,
.end_search,
.reset,
@@ -1253,6 +1355,11 @@ pub const Action = union(enum) {
.toggle_background_opacity,
.show_on_screen_keyboard,
.reset_window_size,
+ .activate_key_table,
+ .activate_key_table_once,
+ .deactivate_key_table,
+ .deactivate_all_key_tables,
+ .end_key_sequence,
.crash,
=> .surface,
@@ -1481,6 +1588,24 @@ pub const Action = union(enum) {
},
}
}
+
+ /// Compares two actions for equality.
+ pub fn equal(self: Action, other: Action) bool {
+ if (std.meta.activeTag(self) != std.meta.activeTag(other)) return false;
+ return switch (self) {
+ inline else => |field_self, tag| {
+ const field_other = @field(other, @tagName(tag));
+ return deepEqual(
+ @TypeOf(field_self),
+ field_self,
+ field_other,
+ );
+ },
+ };
+ }
+
+ /// For the Set.Context
+ const bindingSetEqual = equal;
};
/// Trigger is the associated key state that can trigger an action.
@@ -1505,6 +1630,10 @@ pub const Trigger = struct {
/// codepoint. This is useful for binding to keys that don't have a
/// registered keycode with Ghostty.
unicode: u21,
+
+ /// A catch-all key that matches any key press that is otherwise
+ /// unbound.
+ catch_all,
};
/// The extern struct used for triggers in the C API.
@@ -1516,6 +1645,7 @@ pub const Trigger = struct {
pub const Tag = enum(c_int) {
physical,
unicode,
+ catch_all,
};
pub const Key = extern union {
@@ -1551,18 +1681,12 @@ pub const Trigger = struct {
}
// Alias modifiers
- const alias_mods = .{
- .{ "cmd", "super" },
- .{ "command", "super" },
- .{ "opt", "alt" },
- .{ "option", "alt" },
- .{ "control", "ctrl" },
- };
- inline for (alias_mods) |pair| {
+ inline for (key_mods.alias) |pair| {
if (std.mem.eql(u8, part, pair[0])) {
// Repeat not allowed
- if (@field(result.mods, pair[1])) return Error.InvalidFormat;
- @field(result.mods, pair[1]) = true;
+ const field = @tagName(pair[1]);
+ if (@field(result.mods, field)) return Error.InvalidFormat;
+ @field(result.mods, field) = true;
continue :loop;
}
}
@@ -1611,6 +1735,13 @@ pub const Trigger = struct {
continue :loop;
}
+ // Check for catch_all. We do this near the end since its unlikely
+ // in most cases that we're setting a catch-all key.
+ if (std.mem.eql(u8, part, "catch_all")) {
+ result.key = .catch_all;
+ continue :loop;
+ }
+
// If we're still unset then we look for backwards compatible
// keys with Ghostty 1.1.x. We do this last so its least likely
// to impact performance for modern users.
@@ -1751,7 +1882,7 @@ pub const Trigger = struct {
pub fn isKeyUnset(self: Trigger) bool {
return switch (self.key) {
.physical => |v| v == .unidentified,
- else => false,
+ .unicode, .catch_all => false,
};
}
@@ -1771,6 +1902,7 @@ pub const Trigger = struct {
hasher,
foldedCodepoint(cp),
),
+ .catch_all => {},
}
std.hash.autoHash(hasher, self.mods.binding());
}
@@ -1794,6 +1926,39 @@ pub const Trigger = struct {
return array;
}
+ /// Returns true if two triggers are equal.
+ pub fn equal(self: Trigger, other: Trigger) bool {
+ if (self.mods != other.mods) return false;
+ const self_tag = std.meta.activeTag(self.key);
+ const other_tag = std.meta.activeTag(other.key);
+ if (self_tag != other_tag) return false;
+ return switch (self.key) {
+ .physical => |v| v == other.key.physical,
+ .unicode => |v| v == other.key.unicode,
+ .catch_all => true,
+ };
+ }
+
+ /// Returns true if two triggers are equal using folded codepoints.
+ pub fn foldedEqual(self: Trigger, other: Trigger) bool {
+ if (self.mods != other.mods) return false;
+ const self_tag = std.meta.activeTag(self.key);
+ const other_tag = std.meta.activeTag(other.key);
+ if (self_tag != other_tag) return false;
+ return switch (self.key) {
+ .physical => |v| v == other.key.physical,
+ .unicode => |v| deepEqual(
+ [3]u21,
+ foldedCodepoint(v),
+ foldedCodepoint(other.key.unicode),
+ ),
+ .catch_all => true,
+ };
+ }
+
+ /// For the Set.Context
+ const bindingSetEqual = foldedEqual;
+
/// Convert the trigger to a C API compatible trigger.
pub fn cval(self: Trigger) C {
return .{
@@ -1801,6 +1966,9 @@ pub const Trigger = struct {
.key = switch (self.key) {
.physical => |v| .{ .physical = v },
.unicode => |v| .{ .unicode = @intCast(v) },
+ // catch_all has no associated value so its an error
+ // for a C consumer to look at it.
+ .catch_all => undefined,
},
.mods = self.mods,
};
@@ -1821,6 +1989,7 @@ pub const Trigger = struct {
switch (self.key) {
.physical => |k| try writer.print("{t}", .{k}),
.unicode => |c| try writer.print("{u}", .{c}),
+ .catch_all => try writer.writeAll("catch_all"),
}
}
};
@@ -1829,18 +1998,18 @@ pub const Trigger = struct {
/// The use case is that this will be called on EVERY key input to look
/// for an associated action so it must be fast.
pub const Set = struct {
- const HashMap = std.HashMapUnmanaged(
+ const HashMap = std.ArrayHashMapUnmanaged(
Trigger,
Value,
Context(Trigger),
- std.hash_map.default_max_load_percentage,
+ true,
);
- const ReverseMap = std.HashMapUnmanaged(
+ const ReverseMap = std.ArrayHashMapUnmanaged(
Action,
Trigger,
Context(Action),
- std.hash_map.default_max_load_percentage,
+ true,
);
/// The set of bindings.
@@ -1863,6 +2032,23 @@ pub const Set = struct {
/// integration with GUI toolkits.
reverse: ReverseMap = .{},
+ /// The chain parent is the information necessary to attach a chained
+ /// action to the proper location in our mapping. It tracks both the
+ /// entry in the hashmap and the set it belongs to, which is needed
+ /// to properly update reverse mappings when converting a leaf to
+ /// a chained action.
+ chain_parent: ?ChainParent = null,
+
+ /// Information about a chain parent entry, including which set it
+ /// belongs to. This is needed because reverse mappings are only
+ /// maintained in the root set, but the chain parent entry may be
+ /// in a nested set (for leader key sequences).
+ const ChainParent = struct {
+ key_ptr: *Trigger,
+ value_ptr: *Value,
+ set: *Set,
+ };
+
/// The entry type for the forward mapping of trigger to action.
pub const Value = union(enum) {
/// This key is a leader key in a sequence. You must follow the given
@@ -1873,6 +2059,9 @@ pub const Set = struct {
/// to take along with the flags that may define binding behavior.
leaf: Leaf,
+ /// A set of actions to take in response to a trigger.
+ leaf_chained: LeafChained,
+
/// Implements the formatter for the fmt package. This encodes the
/// action back into the format used by parse.
pub fn format(
@@ -1907,7 +2096,7 @@ pub const Set = struct {
///
/// `buffer_stream` is a FixedBufferStream used for temporary storage
/// that is shared between calls to nested levels of the set.
- /// For example, 'a>b>c=x' and 'a>b>d=y' will re-use the 'a>b' written
+ /// For example, 'a>b>c=x' and 'a>b>d=y' will reuse the 'a>b' written
/// to the buffer before flushing it to the formatter with 'c=x' and 'd=y'.
pub fn formatEntries(
self: Value,
@@ -1938,6 +2127,20 @@ pub const Set = struct {
buffer.print("={f}", .{leaf.action}) catch return error.OutOfMemory;
try formatter.formatEntry([]const u8, buffer.buffer[0..buffer.end]);
},
+
+ .leaf_chained => |leaf| {
+ const pos = buffer.end;
+ for (leaf.actions.items, 0..) |action, i| {
+ if (i == 0) {
+ buffer.print("={f}", .{action}) catch return error.OutOfMemory;
+ } else {
+ buffer.end = 0;
+ buffer.print("chain={f}", .{action}) catch return error.OutOfMemory;
+ }
+ try formatter.formatEntry([]const u8, buffer.buffer[0..buffer.end]);
+ buffer.end = pos;
+ }
+ },
}
}
};
@@ -1964,6 +2167,62 @@ pub const Set = struct {
std.hash.autoHash(&hasher, self.flags);
return hasher.final();
}
+
+ pub fn generic(self: *const Leaf) GenericLeaf {
+ return .{
+ .flags = self.flags,
+ .actions = .{ .single = .{self.action} },
+ };
+ }
+ };
+
+ /// Leaf node of a set that triggers multiple actions in sequence.
+ pub const LeafChained = struct {
+ actions: std.ArrayList(Action),
+ flags: Flags,
+
+ pub fn clone(
+ self: LeafChained,
+ alloc: Allocator,
+ ) Allocator.Error!LeafChained {
+ var cloned_actions = try self.actions.clone(alloc);
+ errdefer cloned_actions.deinit(alloc);
+ for (cloned_actions.items) |*action| {
+ action.* = try action.clone(alloc);
+ }
+ return .{
+ .actions = cloned_actions,
+ .flags = self.flags,
+ };
+ }
+
+ pub fn deinit(self: *LeafChained, alloc: Allocator) void {
+ self.actions.deinit(alloc);
+ }
+
+ pub fn generic(self: *const LeafChained) GenericLeaf {
+ return .{
+ .flags = self.flags,
+ .actions = .{ .many = self.actions.items },
+ };
+ }
+ };
+
+ /// A generic leaf node that can be used to unify the handling of
+ /// leaf and leaf_chained.
+ pub const GenericLeaf = struct {
+ flags: Flags,
+ actions: union(enum) {
+ single: [1]Action,
+ many: []const Action,
+ },
+
+ pub fn actionsSlice(self: *const GenericLeaf) []const Action {
+ return switch (self.actions) {
+ .single => |*arr| arr,
+ .many => |slice| slice,
+ };
+ }
};
/// A full key-value entry for the set.
@@ -1977,6 +2236,9 @@ pub const Set = struct {
s.deinit(alloc);
alloc.destroy(s);
},
+
+ .leaf_chained => |*l| l.deinit(alloc),
+
.leaf => {},
};
@@ -2006,26 +2268,65 @@ pub const Set = struct {
// We use recursion so that we can utilize the stack as our state
// for cleanup.
- self.parseAndPutRecurse(alloc, &it) catch |err| switch (err) {
- // If this gets sent up to the root then we've unbound
- // all the way up and this put was a success.
- error.SequenceUnbind => {},
+ const updated_set_ = self.parseAndPutRecurse(
+ self,
+ alloc,
+ &it,
+ ) catch |err| err: {
+ switch (err) {
+ // If this gets sent up to the root then we've unbound
+ // all the way up and this put was a success.
+ error.SequenceUnbind => break :err null,
- // Unrecoverable
- error.OutOfMemory => return error.OutOfMemory,
+ // If our parser input was too short then the format
+ // is invalid because we handle all valid cases.
+ error.UnexpectedEndOfInput => return error.InvalidFormat,
+
+ // If we had a chain without a parent then the format is wrong.
+ error.NoChainParent => return error.InvalidFormat,
+
+ // If we had an invalid action for a chain (e.g. unbind).
+ error.InvalidChainAction => return error.InvalidFormat,
+
+ // Unrecoverable
+ error.OutOfMemory => return error.OutOfMemory,
+ }
+
+ // Errors must never fall through.
+ unreachable;
};
+
+ // If we have an updated set (a binding was added) then we store
+ // it for our chain parent. If we didn't update a set then we clear
+ // our chain parent since chaining is no longer valid until a
+ // valid binding is saved.
+ if (updated_set_) |updated_set| {
+ // A successful addition must have recorded a chain parent.
+ assert(updated_set.chain_parent != null);
+ if (updated_set != self) self.chain_parent = updated_set.chain_parent;
+ assert(self.chain_parent != null);
+ } else {
+ self.chain_parent = null;
+ }
}
const ParseAndPutRecurseError = Allocator.Error || error{
SequenceUnbind,
+ NoChainParent,
+ UnexpectedEndOfInput,
+ InvalidChainAction,
};
+ /// Returns the set that was ultimately updated if a binding was
+ /// added. Unbind does not return a set since nothing was added.
fn parseAndPutRecurse(
+ root: *Set,
set: *Set,
alloc: Allocator,
it: *Parser,
- ) ParseAndPutRecurseError!void {
- const elem = (it.next() catch unreachable) orelse return;
+ ) ParseAndPutRecurseError!?*Set {
+ const elem = (it.next() catch unreachable) orelse
+ return error.UnexpectedEndOfInput;
switch (elem) {
.leader => |t| {
// If we have a leader, we need to upsert a set for it.
@@ -2037,7 +2338,7 @@ pub const Set = struct {
if (old) |entry| switch (entry) {
// We have an existing leader for this key already
// so recurse into this set.
- .leader => |s| return parseAndPutRecurse(
+ .leader => |s| return root.parseAndPutRecurse(
s,
alloc,
it,
@@ -2048,12 +2349,16 @@ pub const Set = struct {
error.SequenceUnbind => if (s.bindings.count() == 0) {
set.remove(alloc, t);
return error.SequenceUnbind;
- },
+ } else null,
- error.OutOfMemory => return error.OutOfMemory,
+ error.NoChainParent,
+ error.UnexpectedEndOfInput,
+ error.InvalidChainAction,
+ error.OutOfMemory,
+ => err,
},
- .leaf => {
+ .leaf, .leaf_chained => {
// Remove the existing action. Fallthrough as if
// we don't have a leader.
set.remove(alloc, t);
@@ -2070,7 +2375,7 @@ pub const Set = struct {
try set.bindings.put(alloc, t, .{ .leader = next });
// Recurse
- parseAndPutRecurse(next, alloc, it) catch |err| switch (err) {
+ return root.parseAndPutRecurse(next, alloc, it) catch |err| switch (err) {
// If our action was to unbind, we restore the old
// action if we have it.
error.SequenceUnbind => {
@@ -2083,10 +2388,35 @@ pub const Set = struct {
leaf.action,
leaf.flags,
) catch {},
+
+ .leaf_chained => |leaf| chain: {
+ // Rebuild our chain
+ set.putFlags(
+ alloc,
+ t,
+ leaf.actions.items[0],
+ leaf.flags,
+ ) catch break :chain;
+ for (leaf.actions.items[1..]) |action| {
+ set.appendChain(
+ alloc,
+ action,
+ ) catch {
+ set.remove(alloc, t);
+ break :chain;
+ };
+ }
+ },
};
+
+ return null;
},
- error.OutOfMemory => return error.OutOfMemory,
+ error.NoChainParent,
+ error.UnexpectedEndOfInput,
+ error.InvalidChainAction,
+ error.OutOfMemory,
+ => return err,
};
},
@@ -2096,12 +2426,24 @@ pub const Set = struct {
return error.SequenceUnbind;
},
- else => try set.putFlags(
- alloc,
- b.trigger,
- b.action,
- b.flags,
- ),
+ else => {
+ try set.putFlags(
+ alloc,
+ b.trigger,
+ b.action,
+ b.flags,
+ );
+ return set;
+ },
+ },
+
+ .chain => |action| {
+ // Chains can only happen on the root.
+ assert(set == root);
+ // Unbind is not valid for chains.
+ if (action == .unbind) return error.InvalidChainAction;
+ try set.appendChain(alloc, action);
+ return set;
},
}
}
@@ -2133,7 +2475,22 @@ pub const Set = struct {
// See the reverse map docs for more information.
const track_reverse: bool = !flags.performable;
+ // No matter what our chained parent becomes invalid because
+ // getOrPut invalidates pointers.
+ self.chain_parent = null;
+
const gop = try self.bindings.getOrPut(alloc, t);
+ self.chain_parent = .{
+ .key_ptr = gop.key_ptr,
+ .value_ptr = gop.value_ptr,
+ .set = self,
+ };
+ errdefer {
+ // If we have any errors we can't trust our values here. And
+ // we can't restore the old values because they're also invalidated
+ // by getOrPut so we just disable chaining.
+ self.chain_parent = null;
+ }
if (gop.found_existing) switch (gop.value_ptr.*) {
// If we have a leader we need to clean up the memory
@@ -2146,24 +2503,93 @@ pub const Set = struct {
// update the reverse mapping to remove the old action.
.leaf => if (track_reverse) {
const t_hash = t.hash();
- var it = self.reverse.iterator();
- while (it.next()) |reverse_entry| it: {
- if (t_hash == reverse_entry.value_ptr.hash()) {
- self.reverse.removeByPtr(reverse_entry.key_ptr);
- break :it;
+ for (0.., self.reverse.values()) |i, *value| {
+ if (t_hash == value.hash()) {
+ self.reverse.swapRemoveAt(i);
+ break;
}
}
},
+
+ // Chained leaves aren't in the reverse mapping so we just
+ // clear it out.
+ .leaf_chained => |*l| {
+ l.deinit(alloc);
+ },
};
gop.value_ptr.* = .{ .leaf = .{
.action = action,
.flags = flags,
} };
- errdefer _ = self.bindings.remove(t);
+ errdefer _ = self.bindings.swapRemove(t);
if (track_reverse) try self.reverse.put(alloc, action, t);
errdefer if (track_reverse) self.reverse.remove(action);
+
+ // Invariant: after successful put, chain_parent must be valid and point
+ // to the entry we just added/updated.
+ assert(self.chain_parent != null);
+ assert(self.chain_parent.?.key_ptr == gop.key_ptr);
+ assert(self.chain_parent.?.value_ptr == gop.value_ptr);
+ assert(self.chain_parent.?.value_ptr.* == .leaf);
+ }
+
+ /// Append a chained action to the prior set action.
+ ///
+ /// It is an error if there is no valid prior chain parent.
+ pub fn appendChain(
+ self: *Set,
+ alloc: Allocator,
+ action: Action,
+ ) (Allocator.Error || error{NoChainParent})!void {
+ // Unbind is not a valid chain action; callers must check this.
+ assert(action != .unbind);
+
+ const parent = self.chain_parent orelse return error.NoChainParent;
+ switch (parent.value_ptr.*) {
+ // Leader can never be a chain parent. Verified through various
+ // assertions and unit tests.
+ .leader => unreachable,
+
+ // If it is already a chained action, we just append the
+ // action. Easy!
+ .leaf_chained => |*leaf| try leaf.actions.append(
+ alloc,
+ action,
+ ),
+
+ // If it is a leaf, we need to convert it to a leaf_chained.
+ // We also need to be careful to remove any prior reverse
+ // mappings for this action since chained actions are not
+ // part of the reverse mapping.
+ .leaf => |leaf| {
+ // Setup our failable actions list first.
+ var actions: std.ArrayList(Action) = .empty;
+ try actions.ensureTotalCapacity(alloc, 2);
+ errdefer actions.deinit(alloc);
+ actions.appendAssumeCapacity(leaf.action);
+ actions.appendAssumeCapacity(action);
+
+ // Convert to leaf_chained first, before fixing up reverse
+ // mapping. This is important because fixupReverseForAction
+ // searches for other bindings with the same action, and we
+ // don't want to find this entry (which is now chained).
+ parent.value_ptr.* = .{ .leaf_chained = .{
+ .actions = actions,
+ .flags = leaf.flags,
+ } };
+
+ // Clean up our reverse mapping. Chained actions are not
+ // part of the reverse mapping, so we need to fix up the
+ // reverse map (possibly restoring another trigger for the
+ // same action).
+ parent.set.fixupReverseForAction(
+ leaf.action,
+ parent.key_ptr.*,
+ );
+ },
+ }
}
/// Get a binding for a given trigger.
@@ -2213,6 +2639,14 @@ pub const Set = struct {
if (self.get(trigger)) |v| return v;
}
+ // Fallback to catch_all with modifiers first, then without modifiers.
+ trigger.key = .catch_all;
+ if (self.get(trigger)) |v| return v;
+ if (!trigger.mods.empty()) {
+ trigger.mods = .{};
+ if (self.get(trigger)) |v| return v;
+ }
+
return null;
}
@@ -2222,8 +2656,13 @@ pub const Set = struct {
}
fn removeExact(self: *Set, alloc: Allocator, t: Trigger) void {
- const entry = self.bindings.get(t) orelse return;
- _ = self.bindings.remove(t);
+ // Removal always resets our chain parent. We could make this
+ // finer grained but the way it is documented is that chaining
+ // must happen directly after sets so this works.
+ self.chain_parent = null;
+
+ var entry = self.bindings.get(t) orelse return;
+ _ = self.bindings.swapRemove(t);
switch (entry) {
// For a leader removal, we need to deallocate our child set.
@@ -2235,32 +2674,67 @@ pub const Set = struct {
},
// For an action we need to fix up the reverse mapping.
- // Note: we'd LIKE to replace this with the most recent binding but
- // our hash map obviously has no concept of ordering so we have to
- // choose whatever. Maybe a switch to an array hash map here.
- .leaf => |leaf| {
- const action_hash = leaf.action.hash();
+ .leaf => |leaf| self.fixupReverseForAction(
+ leaf.action,
+ t,
+ ),
- var it = self.bindings.iterator();
- while (it.next()) |it_entry| {
- switch (it_entry.value_ptr.*) {
- .leader => {},
- .leaf => |leaf_search| {
- if (leaf_search.action.hash() == action_hash) {
- self.reverse.putAssumeCapacity(leaf.action, it_entry.key_ptr.*);
- break;
- }
- },
- }
- } else {
- // No other trigger points to this action so we remove
- // the reverse mapping completely.
- _ = self.reverse.remove(leaf.action);
- }
+ // Chained leaves are never in our reverse mapping so no
+ // cleanup is required.
+ .leaf_chained => |*l| {
+ l.deinit(alloc);
},
}
}
+ /// Fix up the reverse mapping after removing an action.
+ ///
+ /// When an action is removed from a binding (either by removal or by
+ /// converting to a chained action), we need to update the reverse mapping.
+ /// If another binding has the same action, we update the reverse mapping
+ /// to point to that binding. Otherwise, we remove the action from the
+ /// reverse mapping entirely.
+ ///
+ /// The `old` parameter is the trigger that was previously bound to this
+ /// action. It is used to check if the reverse mapping still points to
+ /// this trigger; if not, no fixup is needed since the reverse map already
+ /// points to a different trigger for this action.
+ ///
+ /// Note: we'd LIKE to replace this with the most recent binding but
+ /// our hash map obviously has no concept of ordering so we have to
+ /// choose whatever. Maybe a switch to an array hash map here.
+ fn fixupReverseForAction(
+ self: *Set,
+ action: Action,
+ old: Trigger,
+ ) void {
+ const entry = self.reverse.getEntry(action) orelse return;
+
+ // If our value is not the same as the old trigger, we can
+ // ignore it because our reverse mapping points somewhere else.
+ if (!entry.value_ptr.equal(old)) return;
+
+ // It is the same trigger, so let's now go through our bindings
+ // and try to find another trigger that maps to the same action.
+ const action_hash = action.hash();
+ var it = self.bindings.iterator();
+ while (it.next()) |it_entry| {
+ switch (it_entry.value_ptr.*) {
+ .leader, .leaf_chained => {},
+ .leaf => |leaf_search| {
+ if (leaf_search.action.hash() == action_hash) {
+ entry.value_ptr.* = it_entry.key_ptr.*;
+ return;
+ }
+ },
+ }
+ }
+
+ // No other trigger points to this action so we remove
+ // the reverse mapping completely.
+ _ = self.reverse.swapRemove(action);
+ }
+
/// Deep clone the set.
pub fn clone(self: *const Set, alloc: Allocator) !Set {
var result: Set = .{
@@ -2276,6 +2750,8 @@ pub const Set = struct {
// contain allocated strings).
.leaf => |*s| s.* = try s.clone(alloc),
+ .leaf_chained => |*s| s.* = try s.clone(alloc),
+
// Must be deep cloned.
.leader => |*s| {
const ptr = try alloc.create(Set);
@@ -2289,9 +2765,8 @@ pub const Set = struct {
// We need to clone the action keys in the reverse map since
// they may contain allocated values.
- {
- var it = result.reverse.keyIterator();
- while (it.next()) |action| action.* = try action.clone(alloc);
+ for (result.reverse.keys()) |*action| {
+ action.* = try action.clone(alloc);
}
return result;
@@ -2301,13 +2776,23 @@ pub const Set = struct {
/// gets the hash key and checks for equality.
fn Context(comptime KeyType: type) type {
return struct {
- pub fn hash(ctx: @This(), k: KeyType) u64 {
+ pub fn hash(ctx: @This(), k: KeyType) u32 {
_ = ctx;
- return k.hash();
+ // This seems crazy at first glance but this is also how
+ // the Zig standard library handles hashing for array
+ // hash maps!
+ return @truncate(k.hash());
}
- pub fn eql(ctx: @This(), a: KeyType, b: KeyType) bool {
- return ctx.hash(a) == ctx.hash(b);
+ pub fn eql(
+ ctx: @This(),
+ a: KeyType,
+ b: KeyType,
+ b_index: usize,
+ ) bool {
+ _ = ctx;
+ _ = b_index;
+ return a.bindingSetEqual(b);
}
};
}
@@ -2433,6 +2918,31 @@ test "parse: w3c key names" {
try testing.expectError(Error.InvalidFormat, parseSingle("Keya=ignore"));
}
+test "parse: catch_all" {
+ const testing = std.testing;
+
+ // Basic catch_all
+ try testing.expectEqual(
+ Binding{
+ .trigger = .{ .key = .catch_all },
+ .action = .{ .ignore = {} },
+ },
+ try parseSingle("catch_all=ignore"),
+ );
+
+ // catch_all with modifiers
+ try testing.expectEqual(
+ Binding{
+ .trigger = .{
+ .mods = .{ .ctrl = true },
+ .key = .catch_all,
+ },
+ .action = .{ .ignore = {} },
+ },
+ try parseSingle("ctrl+catch_all=ignore"),
+ );
+}
+
test "parse: plus sign" {
const testing = std.testing;
@@ -2647,6 +3157,66 @@ test "parse: all triggers" {
}
}
+test "Trigger: equal" {
+ const testing = std.testing;
+
+ // Equal physical keys
+ {
+ const t1: Trigger = .{ .key = .{ .physical = .arrow_up }, .mods = .{ .ctrl = true } };
+ const t2: Trigger = .{ .key = .{ .physical = .arrow_up }, .mods = .{ .ctrl = true } };
+ try testing.expect(t1.equal(t2));
+ }
+
+ // Different physical keys
+ {
+ const t1: Trigger = .{ .key = .{ .physical = .arrow_up }, .mods = .{ .ctrl = true } };
+ const t2: Trigger = .{ .key = .{ .physical = .arrow_down }, .mods = .{ .ctrl = true } };
+ try testing.expect(!t1.equal(t2));
+ }
+
+ // Different mods
+ {
+ const t1: Trigger = .{ .key = .{ .physical = .arrow_up }, .mods = .{ .ctrl = true } };
+ const t2: Trigger = .{ .key = .{ .physical = .arrow_up }, .mods = .{ .shift = true } };
+ try testing.expect(!t1.equal(t2));
+ }
+
+ // Equal unicode keys
+ {
+ const t1: Trigger = .{ .key = .{ .unicode = 'a' }, .mods = .{} };
+ const t2: Trigger = .{ .key = .{ .unicode = 'a' }, .mods = .{} };
+ try testing.expect(t1.equal(t2));
+ }
+
+ // Different unicode keys
+ {
+ const t1: Trigger = .{ .key = .{ .unicode = 'a' }, .mods = .{} };
+ const t2: Trigger = .{ .key = .{ .unicode = 'b' }, .mods = .{} };
+ try testing.expect(!t1.equal(t2));
+ }
+
+ // Different key types
+ {
+ const t1: Trigger = .{ .key = .{ .unicode = 'a' }, .mods = .{} };
+ const t2: Trigger = .{ .key = .{ .physical = .key_a }, .mods = .{} };
+ try testing.expect(!t1.equal(t2));
+ }
+
+ // catch_all
+ {
+ const t1: Trigger = .{ .key = .catch_all, .mods = .{} };
+ const t2: Trigger = .{ .key = .catch_all, .mods = .{} };
+ try testing.expect(t1.equal(t2));
+ }
+
+ // catch_all with different mods
+ {
+ const t1: Trigger = .{ .key = .catch_all, .mods = .{} };
+ const t2: Trigger = .{ .key = .catch_all, .mods = .{ .alt = true } };
+ try testing.expect(!t1.equal(t2));
+ }
+}
+
test "parse: modifier aliases" {
const testing = std.testing;
@@ -2802,6 +3372,29 @@ test "parse: action with a tuple" {
try testing.expectError(Error.InvalidFormat, parseSingle("a=resize_split:up,four"));
}
+test "parse: chain" {
+ const testing = std.testing;
+
+ // Valid
+ {
+ var p = try Parser.init("chain=new_tab");
+ try testing.expectEqual(Parser.Elem{
+ .chain = .new_tab,
+ }, try p.next());
+ try testing.expect(try p.next() == null);
+ }
+
+ // Chain can't have flags
+ try testing.expectError(error.InvalidFormat, Parser.init("global:chain=ignore"));
+
+ // Chain can't be part of a sequence
+ {
+ var p = try Parser.init("a>chain=ignore");
+ _ = try p.next();
+ try testing.expectError(error.InvalidFormat, p.next());
+ }
+}
+
test "sequence iterator" {
const testing = std.testing;
@@ -2893,6 +3486,15 @@ test "set: parseAndPut typical binding" {
const trigger = s.getTrigger(.{ .new_window = {} }).?;
try testing.expect(trigger.key.unicode == 'a');
}
+
+ // Sets up the chain parent properly
+ try testing.expect(s.chain_parent != null);
+ {
+ var buf: std.Io.Writer.Allocating = .init(alloc);
+ defer buf.deinit();
+ try s.chain_parent.?.key_ptr.format(&buf.writer);
+ try testing.expectEqualStrings("a", buf.written());
+ }
}
test "set: parseAndPut unconsumed binding" {
@@ -2917,6 +3519,15 @@ test "set: parseAndPut unconsumed binding" {
const trigger = s.getTrigger(.{ .new_window = {} }).?;
try testing.expect(trigger.key.unicode == 'a');
}
+
+ // Sets up the chain parent properly
+ try testing.expect(s.chain_parent != null);
+ {
+ var buf: std.Io.Writer.Allocating = .init(alloc);
+ defer buf.deinit();
+ try s.chain_parent.?.key_ptr.format(&buf.writer);
+ try testing.expectEqualStrings("a", buf.written());
+ }
}
test "set: parseAndPut removed binding" {
@@ -2935,6 +3546,264 @@ test "set: parseAndPut removed binding" {
try testing.expect(s.get(trigger) == null);
}
try testing.expect(s.getTrigger(.{ .new_window = {} }) == null);
+
+ // Sets up the chain parent properly
+ try testing.expect(s.chain_parent == null);
+}
+
+test "set: put sets chain_parent" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var s: Set = .{};
+ defer s.deinit(alloc);
+
+ try s.put(alloc, .{ .key = .{ .unicode = 'a' } }, .{ .new_window = {} });
+
+ // chain_parent should be set
+ try testing.expect(s.chain_parent != null);
+ {
+ var buf: std.Io.Writer.Allocating = .init(alloc);
+ defer buf.deinit();
+ try s.chain_parent.?.key_ptr.format(&buf.writer);
+ try testing.expectEqualStrings("a", buf.written());
+ }
+
+ // chain_parent value should be a leaf
+ try testing.expect(s.chain_parent.?.value_ptr.* == .leaf);
+}
+
+test "set: putFlags sets chain_parent" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var s: Set = .{};
+ defer s.deinit(alloc);
+
+ try s.putFlags(
+ alloc,
+ .{ .key = .{ .unicode = 'a' } },
+ .{ .new_window = {} },
+ .{ .consumed = false },
+ );
+
+ // chain_parent should be set
+ try testing.expect(s.chain_parent != null);
+ {
+ var buf: std.Io.Writer.Allocating = .init(alloc);
+ defer buf.deinit();
+ try s.chain_parent.?.key_ptr.format(&buf.writer);
+ try testing.expectEqualStrings("a", buf.written());
+ }
+
+ // chain_parent value should be a leaf with correct flags
+ try testing.expect(s.chain_parent.?.value_ptr.* == .leaf);
+ try testing.expect(!s.chain_parent.?.value_ptr.*.leaf.flags.consumed);
+}
+
+test "set: sequence sets chain_parent to final leaf" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var s: Set = .{};
+ defer s.deinit(alloc);
+
+ try s.parseAndPut(alloc, "a>b=new_window");
+
+ // chain_parent should be set and point to 'b' (the final leaf)
+ try testing.expect(s.chain_parent != null);
+ {
+ var buf: std.Io.Writer.Allocating = .init(alloc);
+ defer buf.deinit();
+ try s.chain_parent.?.key_ptr.format(&buf.writer);
+ try testing.expectEqualStrings("b", buf.written());
+ }
+
+ // chain_parent value should be a leaf
+ try testing.expect(s.chain_parent.?.value_ptr.* == .leaf);
+ try testing.expect(s.chain_parent.?.value_ptr.*.leaf.action == .new_window);
+}
+
+test "set: multiple leaves under leader updates chain_parent" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var s: Set = .{};
+ defer s.deinit(alloc);
+
+ try s.parseAndPut(alloc, "a>b=new_window");
+
+ // After first binding, chain_parent should be 'b'
+ try testing.expect(s.chain_parent != null);
+ {
+ var buf: std.Io.Writer.Allocating = .init(alloc);
+ defer buf.deinit();
+ try s.chain_parent.?.key_ptr.format(&buf.writer);
+ try testing.expectEqualStrings("b", buf.written());
+ }
+
+ try s.parseAndPut(alloc, "a>c=new_tab");
+
+ // After second binding, chain_parent should be updated to 'c'
+ try testing.expect(s.chain_parent != null);
+ {
+ var buf: std.Io.Writer.Allocating = .init(alloc);
+ defer buf.deinit();
+ try s.chain_parent.?.key_ptr.format(&buf.writer);
+ try testing.expectEqualStrings("c", buf.written());
+ }
+ try testing.expect(s.chain_parent.?.value_ptr.*.leaf.action == .new_tab);
+}
+
+test "set: sequence unbind clears chain_parent" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var s: Set = .{};
+ defer s.deinit(alloc);
+
+ try s.parseAndPut(alloc, "a>b=new_window");
+ try testing.expect(s.chain_parent != null);
+
+ try s.parseAndPut(alloc, "a>b=unbind");
+
+ // After unbind, chain_parent should be cleared
+ try testing.expect(s.chain_parent == null);
+}
+
+test "set: sequence unbind with remaining leaves clears chain_parent" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var s: Set = .{};
+ defer s.deinit(alloc);
+
+ try s.parseAndPut(alloc, "a>b=new_window");
+ try s.parseAndPut(alloc, "a>c=new_tab");
+ try s.parseAndPut(alloc, "a>b=unbind");
+
+ // After unbind, chain_parent should be cleared even though 'c' remains
+ try testing.expect(s.chain_parent == null);
+
+ // But 'c' should still exist
+ const a_entry = s.get(.{ .key = .{ .unicode = 'a' } }).?;
+ try testing.expect(a_entry.value_ptr.* == .leader);
+ const inner_set = a_entry.value_ptr.*.leader;
+ try testing.expect(inner_set.get(.{ .key = .{ .unicode = 'c' } }) != null);
+}
+
+test "set: direct remove clears chain_parent" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var s: Set = .{};
+ defer s.deinit(alloc);
+
+ try s.put(alloc, .{ .key = .{ .unicode = 'a' } }, .{ .new_window = {} });
+ try testing.expect(s.chain_parent != null);
+
+ s.remove(alloc, .{ .key = .{ .unicode = 'a' } });
+
+ // After removal, chain_parent should be cleared
+ try testing.expect(s.chain_parent == null);
+}
+
+test "set: invalid format preserves chain_parent" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var s: Set = .{};
+ defer s.deinit(alloc);
+
+ try s.parseAndPut(alloc, "a=new_window");
+ const before_key = s.chain_parent.?.key_ptr;
+ const before_value = s.chain_parent.?.value_ptr;
+
+ // Try an invalid parse - should fail
+ try testing.expectError(error.InvalidAction, s.parseAndPut(alloc, "a=invalid_action_xyz"));
+
+ // chain_parent should be unchanged
+ try testing.expect(s.chain_parent != null);
+ try testing.expect(s.chain_parent.?.key_ptr == before_key);
+ try testing.expect(s.chain_parent.?.value_ptr == before_value);
+}
+
+test "set: clone produces null chain_parent" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var s: Set = .{};
+ defer s.deinit(alloc);
+
+ try s.parseAndPut(alloc, "a=new_window");
+ try testing.expect(s.chain_parent != null);
+
+ var cloned = try s.clone(alloc);
+ defer cloned.deinit(alloc);
+
+ // Clone should have null chain_parent
+ try testing.expect(cloned.chain_parent == null);
+
+ // But should have the binding
+ try testing.expect(cloned.get(.{ .key = .{ .unicode = 'a' } }) != null);
+}
+
+test "set: clone with leaf_chained" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var s: Set = .{};
+ defer s.deinit(alloc);
+
+ // Create a chained binding using parseAndPut with chain=
+ try s.parseAndPut(alloc, "a=new_window");
+ try s.parseAndPut(alloc, "chain=new_tab");
+
+ // Verify we have a leaf_chained
+ const entry = s.get(.{ .key = .{ .unicode = 'a' } }).?;
+ try testing.expect(entry.value_ptr.* == .leaf_chained);
+ try testing.expectEqual(@as(usize, 2), entry.value_ptr.leaf_chained.actions.items.len);
+
+ // Clone the set
+ var cloned = try s.clone(alloc);
+ defer cloned.deinit(alloc);
+
+ // Verify the cloned set has the leaf_chained with same actions
+ const cloned_entry = cloned.get(.{ .key = .{ .unicode = 'a' } }).?;
+ try testing.expect(cloned_entry.value_ptr.* == .leaf_chained);
+ try testing.expectEqual(@as(usize, 2), cloned_entry.value_ptr.leaf_chained.actions.items.len);
+ try testing.expect(cloned_entry.value_ptr.leaf_chained.actions.items[0] == .new_window);
+ try testing.expect(cloned_entry.value_ptr.leaf_chained.actions.items[1] == .new_tab);
+}
+
+test "set: clone with leaf_chained containing allocated data" {
+ const testing = std.testing;
+ var arena = std.heap.ArenaAllocator.init(testing.allocator);
+ defer arena.deinit();
+ const alloc = arena.allocator();
+
+ var s: Set = .{};
+
+ // Create a chained binding with text actions (which have allocated strings)
+ try s.parseAndPut(alloc, "a=text:hello");
+ try s.parseAndPut(alloc, "chain=text:world");
+
+ // Verify we have a leaf_chained
+ const entry = s.get(.{ .key = .{ .unicode = 'a' } }).?;
+ try testing.expect(entry.value_ptr.* == .leaf_chained);
+
+ // Clone the set
+ const cloned = try s.clone(alloc);
+
+ // Verify the cloned set has independent copies of the text
+ const cloned_entry = cloned.get(.{ .key = .{ .unicode = 'a' } }).?;
+ try testing.expect(cloned_entry.value_ptr.* == .leaf_chained);
+ try testing.expectEqualStrings("hello", cloned_entry.value_ptr.leaf_chained.actions.items[0].text);
+ try testing.expectEqualStrings("world", cloned_entry.value_ptr.leaf_chained.actions.items[1].text);
+
+ // Verify the pointers are different (truly cloned, not shared)
+ try testing.expect(entry.value_ptr.leaf_chained.actions.items[0].text.ptr !=
+ cloned_entry.value_ptr.leaf_chained.actions.items[0].text.ptr);
}
test "set: parseAndPut sequence" {
@@ -3218,6 +4087,143 @@ test "set: consumed state" {
try testing.expect(s.get(.{ .key = .{ .unicode = 'a' } }).?.value_ptr.*.leaf.flags.consumed);
}
+test "set: parseAndPut chain" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var s: Set = .{};
+ defer s.deinit(alloc);
+
+ try s.parseAndPut(alloc, "a=new_window");
+ try s.parseAndPut(alloc, "chain=new_tab");
+
+ // Creates forward mapping as leaf_chained
+ {
+ const entry = s.get(.{ .key = .{ .unicode = 'a' } }).?.value_ptr.*;
+ try testing.expect(entry == .leaf_chained);
+ const chained = entry.leaf_chained;
+ try testing.expectEqual(@as(usize, 2), chained.actions.items.len);
+ try testing.expect(chained.actions.items[0] == .new_window);
+ try testing.expect(chained.actions.items[1] == .new_tab);
+ }
+
+ // Does not create reverse mapping, because reverse mappings are only for
+ // non-chained actions.
+ {
+ try testing.expect(s.getTrigger(.{ .new_window = {} }) == null);
+ }
+}
+
+test "set: parseAndPut chain without parent is error" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var s: Set = .{};
+ defer s.deinit(alloc);
+
+ // Chain without a prior binding should fail
+ try testing.expectError(error.InvalidFormat, s.parseAndPut(alloc, "chain=new_tab"));
+}
+
+test "set: parseAndPut chain multiple times" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var s: Set = .{};
+ defer s.deinit(alloc);
+
+ try s.parseAndPut(alloc, "a=new_window");
+ try s.parseAndPut(alloc, "chain=new_tab");
+ try s.parseAndPut(alloc, "chain=close_surface");
+
+ // Should have 3 actions chained
+ {
+ const entry = s.get(.{ .key = .{ .unicode = 'a' } }).?.value_ptr.*;
+ try testing.expect(entry == .leaf_chained);
+ const chained = entry.leaf_chained;
+ try testing.expectEqual(@as(usize, 3), chained.actions.items.len);
+ try testing.expect(chained.actions.items[0] == .new_window);
+ try testing.expect(chained.actions.items[1] == .new_tab);
+ try testing.expect(chained.actions.items[2] == .close_surface);
+ }
+}
+
+test "set: parseAndPut chain preserves flags" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var s: Set = .{};
+ defer s.deinit(alloc);
+
+ try s.parseAndPut(alloc, "unconsumed:a=new_window");
+ try s.parseAndPut(alloc, "chain=new_tab");
+
+ // Should preserve unconsumed flag
+ {
+ const entry = s.get(.{ .key = .{ .unicode = 'a' } }).?.value_ptr.*;
+ try testing.expect(entry == .leaf_chained);
+ const chained = entry.leaf_chained;
+ try testing.expect(!chained.flags.consumed);
+ try testing.expectEqual(@as(usize, 2), chained.actions.items.len);
+ }
+}
+
+test "set: parseAndPut chain after unbind is error" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var s: Set = .{};
+ defer s.deinit(alloc);
+
+ try s.parseAndPut(alloc, "a=new_window");
+ try s.parseAndPut(alloc, "a=unbind");
+
+ // Chain after unbind should fail because chain_parent is cleared
+ try testing.expectError(error.InvalidFormat, s.parseAndPut(alloc, "chain=new_tab"));
+}
+
+test "set: parseAndPut chain on sequence" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var s: Set = .{};
+ defer s.deinit(alloc);
+
+ try s.parseAndPut(alloc, "a>b=new_window");
+ try s.parseAndPut(alloc, "chain=new_tab");
+
+ // Navigate to the inner set
+ const a_entry = s.get(.{ .key = .{ .unicode = 'a' } }).?.value_ptr.*;
+ try testing.expect(a_entry == .leader);
+ const inner_set = a_entry.leader;
+
+ // Check the chained binding
+ const b_entry = inner_set.get(.{ .key = .{ .unicode = 'b' } }).?.value_ptr.*;
+ try testing.expect(b_entry == .leaf_chained);
+ const chained = b_entry.leaf_chained;
+ try testing.expectEqual(@as(usize, 2), chained.actions.items.len);
+ try testing.expect(chained.actions.items[0] == .new_window);
+ try testing.expect(chained.actions.items[1] == .new_tab);
+}
+
+test "set: parseAndPut chain with unbind is error" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var s: Set = .{};
+ defer s.deinit(alloc);
+
+ try s.parseAndPut(alloc, "a=new_window");
+
+ // chain=unbind is not valid
+ try testing.expectError(error.InvalidFormat, s.parseAndPut(alloc, "chain=unbind"));
+
+ // Original binding should still exist
+ const entry = s.get(.{ .key = .{ .unicode = 'a' } }).?.value_ptr.*;
+ try testing.expect(entry == .leaf);
+ try testing.expect(entry.leaf.action == .new_window);
+}
+
test "set: getEvent physical" {
const testing = std.testing;
const alloc = testing.allocator;
@@ -3329,6 +4335,83 @@ test "set: getEvent codepoint case folding" {
}
}
+test "set: getEvent catch_all fallback" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var s: Set = .{};
+ defer s.deinit(alloc);
+
+ try s.parseAndPut(alloc, "catch_all=ignore");
+
+ // Matches unbound key without modifiers
+ {
+ const action = s.getEvent(.{
+ .key = .key_a,
+ .mods = .{},
+ }).?.value_ptr.*.leaf;
+ try testing.expect(action.action == .ignore);
+ }
+
+ // Matches unbound key with modifiers (falls back to catch_all without mods)
+ {
+ const action = s.getEvent(.{
+ .key = .key_a,
+ .mods = .{ .ctrl = true },
+ }).?.value_ptr.*.leaf;
+ try testing.expect(action.action == .ignore);
+ }
+
+ // Specific binding takes precedence over catch_all
+ try s.parseAndPut(alloc, "ctrl+b=new_window");
+ {
+ const action = s.getEvent(.{
+ .key = .key_b,
+ .mods = .{ .ctrl = true },
+ .unshifted_codepoint = 'b',
+ }).?.value_ptr.*.leaf;
+ try testing.expect(action.action == .new_window);
+ }
+}
+
+test "set: getEvent catch_all with modifiers" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var s: Set = .{};
+ defer s.deinit(alloc);
+
+ try s.parseAndPut(alloc, "ctrl+catch_all=close_surface");
+ try s.parseAndPut(alloc, "catch_all=ignore");
+
+ // Key with ctrl matches catch_all with ctrl modifier
+ {
+ const action = s.getEvent(.{
+ .key = .key_a,
+ .mods = .{ .ctrl = true },
+ }).?.value_ptr.*.leaf;
+ try testing.expect(action.action == .close_surface);
+ }
+
+ // Key without mods matches catch_all without mods
+ {
+ const action = s.getEvent(.{
+ .key = .key_a,
+ .mods = .{},
+ }).?.value_ptr.*.leaf;
+ try testing.expect(action.action == .ignore);
+ }
+
+ // Key with different mods falls back to catch_all without mods
+ {
+ const action = s.getEvent(.{
+ .key = .key_a,
+ .mods = .{ .alt = true },
+ }).?.value_ptr.*.leaf;
+ try testing.expect(action.action == .ignore);
+ }
+}
+
test "Action: clone" {
const testing = std.testing;
var arena = std.heap.ArenaAllocator.init(testing.allocator);
@@ -3476,3 +4559,258 @@ test "action: format" {
try a.format(&buf.writer);
try testing.expectEqualStrings("text:\\xf0\\x9f\\x91\\xbb", buf.written());
}
+
+test "set: appendChain with no parent returns error" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var s: Set = .{};
+ defer s.deinit(alloc);
+
+ try testing.expectError(error.NoChainParent, s.appendChain(alloc, .{ .new_tab = {} }));
+}
+
+test "set: appendChain after put converts to leaf_chained" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var s: Set = .{};
+ defer s.deinit(alloc);
+
+ try s.put(alloc, .{ .key = .{ .unicode = 'a' } }, .{ .new_window = {} });
+
+ // First appendChain converts leaf to leaf_chained and appends the new action
+ try s.appendChain(alloc, .{ .new_tab = {} });
+
+ const entry = s.get(.{ .key = .{ .unicode = 'a' } }).?;
+ try testing.expect(entry.value_ptr.* == .leaf_chained);
+
+ const chained = entry.value_ptr.*.leaf_chained;
+ try testing.expectEqual(@as(usize, 2), chained.actions.items.len);
+ try testing.expect(chained.actions.items[0] == .new_window);
+ try testing.expect(chained.actions.items[1] == .new_tab);
+}
+
+test "set: appendChain after putFlags preserves flags" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var s: Set = .{};
+ defer s.deinit(alloc);
+
+ try s.putFlags(
+ alloc,
+ .{ .key = .{ .unicode = 'a' } },
+ .{ .new_window = {} },
+ .{ .consumed = false },
+ );
+ try s.appendChain(alloc, .{ .new_tab = {} });
+
+ const entry = s.get(.{ .key = .{ .unicode = 'a' } }).?;
+ try testing.expect(entry.value_ptr.* == .leaf_chained);
+
+ const chained = entry.value_ptr.*.leaf_chained;
+ try testing.expect(!chained.flags.consumed);
+ try testing.expectEqual(@as(usize, 2), chained.actions.items.len);
+ try testing.expect(chained.actions.items[0] == .new_window);
+ try testing.expect(chained.actions.items[1] == .new_tab);
+}
+
+test "set: appendChain multiple times" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var s: Set = .{};
+ defer s.deinit(alloc);
+
+ try s.put(alloc, .{ .key = .{ .unicode = 'a' } }, .{ .new_window = {} });
+ try s.appendChain(alloc, .{ .new_tab = {} });
+ try s.appendChain(alloc, .{ .close_surface = {} });
+
+ const entry = s.get(.{ .key = .{ .unicode = 'a' } }).?;
+ try testing.expect(entry.value_ptr.* == .leaf_chained);
+
+ const chained = entry.value_ptr.*.leaf_chained;
+ try testing.expectEqual(@as(usize, 3), chained.actions.items.len);
+ try testing.expect(chained.actions.items[0] == .new_window);
+ try testing.expect(chained.actions.items[1] == .new_tab);
+ try testing.expect(chained.actions.items[2] == .close_surface);
+}
+
+test "set: appendChain removes reverse mapping" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var s: Set = .{};
+ defer s.deinit(alloc);
+
+ try s.put(alloc, .{ .key = .{ .unicode = 'a' } }, .{ .new_window = {} });
+
+ // Verify reverse mapping exists before chaining
+ try testing.expect(s.getTrigger(.{ .new_window = {} }) != null);
+
+ // Chaining should remove the reverse mapping
+ try s.appendChain(alloc, .{ .new_tab = {} });
+
+ // Reverse mapping should be gone since chained actions are not in reverse map
+ try testing.expect(s.getTrigger(.{ .new_window = {} }) == null);
+}
+
+test "set: appendChain with performable does not affect reverse mapping" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var s: Set = .{};
+ defer s.deinit(alloc);
+
+ // Add a non-performable binding first
+ try s.put(alloc, .{ .key = .{ .unicode = 'b' } }, .{ .new_window = {} });
+ try testing.expect(s.getTrigger(.{ .new_window = {} }) != null);
+
+ // Add a performable binding (not in reverse map) and chain it
+ try s.putFlags(
+ alloc,
+ .{ .key = .{ .unicode = 'a' } },
+ .{ .close_surface = {} },
+ .{ .performable = true },
+ );
+
+ // close_surface was performable, so not in reverse map
+ try testing.expect(s.getTrigger(.{ .close_surface = {} }) == null);
+
+ // Chaining the performable binding should not crash or affect anything
+ try s.appendChain(alloc, .{ .new_tab = {} });
+
+ // The non-performable new_window binding should still be in reverse map
+ try testing.expect(s.getTrigger(.{ .new_window = {} }) != null);
+}
+
+test "set: appendChain restores next valid reverse mapping" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var s: Set = .{};
+ defer s.deinit(alloc);
+
+ // Add two bindings for the same action
+ try s.put(alloc, .{ .key = .{ .unicode = 'a' } }, .{ .new_window = {} });
+ try s.put(alloc, .{ .key = .{ .unicode = 'b' } }, .{ .new_window = {} });
+
+ // Reverse mapping should point to 'b' (most recent)
+ {
+ const trigger = s.getTrigger(.{ .new_window = {} }).?;
+ try testing.expect(trigger.key.unicode == 'b');
+ }
+
+ // Chain an action to 'b', which should restore 'a' in the reverse map
+ try s.appendChain(alloc, .{ .new_tab = {} });
+
+ // Now reverse mapping should point to 'a'
+ {
+ const trigger = s.getTrigger(.{ .new_window = {} }).?;
+ try testing.expect(trigger.key.unicode == 'a');
+ }
+}
+
+test "set: formatEntries leaf_chained" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+ const formatterpkg = @import("../config/formatter.zig");
+
+ var s: Set = .{};
+ defer s.deinit(alloc);
+
+ // Create a chained binding
+ try s.parseAndPut(alloc, "a=new_window");
+ try s.parseAndPut(alloc, "chain=new_tab");
+
+ // Verify it's a leaf_chained
+ const entry = s.get(.{ .key = .{ .unicode = 'a' } }).?;
+ try testing.expect(entry.value_ptr.* == .leaf_chained);
+
+ // Format the entries
+ var output: std.Io.Writer.Allocating = .init(alloc);
+ defer output.deinit();
+
+ var buf: [1024]u8 = undefined;
+ var writer: std.Io.Writer = .fixed(&buf);
+
+ // Write the trigger first (as formatEntry in Config.zig does)
+ try entry.key_ptr.format(&writer);
+ try entry.value_ptr.formatEntries(&writer, formatterpkg.entryFormatter("keybind", &output.writer));
+
+ const expected =
+ \\keybind = a=new_window
+ \\keybind = chain=new_tab
+ \\
+ ;
+ try testing.expectEqualStrings(expected, output.written());
+}
+
+test "set: formatEntries leaf_chained multiple chains" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+ const formatterpkg = @import("../config/formatter.zig");
+
+ var s: Set = .{};
+ defer s.deinit(alloc);
+
+ // Create a chained binding with 3 actions
+ try s.parseAndPut(alloc, "ctrl+a=new_window");
+ try s.parseAndPut(alloc, "chain=new_tab");
+ try s.parseAndPut(alloc, "chain=close_surface");
+
+ // Verify it's a leaf_chained with 3 actions
+ const entry = s.get(.{ .key = .{ .unicode = 'a' }, .mods = .{ .ctrl = true } }).?;
+ try testing.expect(entry.value_ptr.* == .leaf_chained);
+ try testing.expectEqual(@as(usize, 3), entry.value_ptr.leaf_chained.actions.items.len);
+
+ // Format the entries
+ var output: std.Io.Writer.Allocating = .init(alloc);
+ defer output.deinit();
+
+ var buf: [1024]u8 = undefined;
+ var writer: std.Io.Writer = .fixed(&buf);
+
+ try entry.key_ptr.format(&writer);
+ try entry.value_ptr.formatEntries(&writer, formatterpkg.entryFormatter("keybind", &output.writer));
+
+ const expected =
+ \\keybind = ctrl+a=new_window
+ \\keybind = chain=new_tab
+ \\keybind = chain=close_surface
+ \\
+ ;
+ try testing.expectEqualStrings(expected, output.written());
+}
+
+test "set: formatEntries leaf_chained with text action" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+ const formatterpkg = @import("../config/formatter.zig");
+
+ var s: Set = .{};
+ defer s.deinit(alloc);
+
+ // Create a chained binding with text actions
+ try s.parseAndPut(alloc, "a=text:hello");
+ try s.parseAndPut(alloc, "chain=text:world");
+
+ // Format the entries
+ var output: std.Io.Writer.Allocating = .init(alloc);
+ defer output.deinit();
+
+ var buf: [1024]u8 = undefined;
+ var writer: std.Io.Writer = .fixed(&buf);
+
+ const entry = s.get(.{ .key = .{ .unicode = 'a' } }).?;
+ try entry.key_ptr.format(&writer);
+ try entry.value_ptr.formatEntries(&writer, formatterpkg.entryFormatter("keybind", &output.writer));
+
+ const expected =
+ \\keybind = a=text:hello
+ \\keybind = chain=text:world
+ \\
+ ;
+ try testing.expectEqualStrings(expected, output.written());
+}
diff --git a/src/input/command.zig b/src/input/command.zig
index 6ac4312a9..d6d2b0247 100644
--- a/src/input/command.zig
+++ b/src/input/command.zig
@@ -43,7 +43,7 @@ pub const Command = struct {
return true;
}
- /// Convert this command to a C struct.
+ /// Convert this command to a C struct at comptime.
pub fn comptimeCval(self: Command) C {
assert(@inComptime());
@@ -55,6 +55,27 @@ pub const Command = struct {
};
}
+ /// Convert this command to a C struct at runtime.
+ ///
+ /// This shares memory with the original command.
+ ///
+ /// The action string is allocated using the provided allocator. You can
+ /// free the slice directly if you need to but we recommend an arena
+ /// for this.
+ pub fn cval(self: Command, alloc: Allocator) Allocator.Error!C {
+ var buf: std.Io.Writer.Allocating = .init(alloc);
+ defer buf.deinit();
+ self.action.format(&buf.writer) catch return error.OutOfMemory;
+ const action = try buf.toOwnedSliceSentinel(0);
+
+ return .{
+ .action_key = @tagName(self.action),
+ .action = action.ptr,
+ .title = self.title,
+ .description = self.description,
+ };
+ }
+
/// Implements a comparison function for std.mem.sortUnstable
/// and similar functions. The sorting is defined by Ghostty
/// to be what we prefer. If a caller wants some other sorting,
@@ -168,6 +189,12 @@ fn actionCommands(action: Action.Key) []const Command {
.description = "Start a search if one isn't already active.",
}},
+ .search_selection => comptime &.{.{
+ .action = .search_selection,
+ .title = "Search Selection",
+ .description = "Start a search for the current text selection.",
+ }},
+
.end_search => comptime &.{.{
.action = .end_search,
.title = "End Search",
@@ -671,6 +698,11 @@ fn actionCommands(action: Action.Key) []const Command {
.write_scrollback_file,
.goto_tab,
.resize_split,
+ .activate_key_table,
+ .activate_key_table_once,
+ .deactivate_key_table,
+ .deactivate_all_key_tables,
+ .end_key_sequence,
.crash,
=> comptime &.{},
diff --git a/src/input/key.zig b/src/input/key.zig
index 54c7491ae..a929a0323 100644
--- a/src/input/key.zig
+++ b/src/input/key.zig
@@ -1,9 +1,11 @@
const std = @import("std");
const builtin = @import("builtin");
const Allocator = std.mem.Allocator;
-const cimgui = @import("cimgui");
+const cimgui = @import("dcimgui");
const OptionAsAlt = @import("config.zig").OptionAsAlt;
+pub const Mods = @import("key_mods.zig").Mods;
+
/// A generic key input event. This is the information that is necessary
/// regardless of apprt in order to generate the proper terminal
/// control sequences for a given key press.
@@ -76,161 +78,6 @@ pub const KeyEvent = struct {
}
};
-/// A bitmask for all key modifiers.
-///
-/// IMPORTANT: Any changes here update include/ghostty.h
-pub const Mods = packed struct(Mods.Backing) {
- pub const Backing = u16;
-
- shift: bool = false,
- ctrl: bool = false,
- alt: bool = false,
- super: bool = false,
- caps_lock: bool = false,
- num_lock: bool = false,
- sides: side = .{},
- _padding: u6 = 0,
-
- /// Tracks the side that is active for any given modifier. Note
- /// that this doesn't confirm a modifier is pressed; you must check
- /// the bool for that in addition to this.
- ///
- /// Not all platforms support this, check apprt for more info.
- pub const side = packed struct(u4) {
- shift: Side = .left,
- ctrl: Side = .left,
- alt: Side = .left,
- super: Side = .left,
- };
-
- pub const Side = enum(u1) { left, right };
-
- /// Integer value of this struct.
- pub fn int(self: Mods) Backing {
- return @bitCast(self);
- }
-
- /// Returns true if no modifiers are set.
- pub fn empty(self: Mods) bool {
- return self.int() == 0;
- }
-
- /// Returns true if two mods are equal.
- pub fn equal(self: Mods, other: Mods) bool {
- return self.int() == other.int();
- }
-
- /// Return mods that are only relevant for bindings.
- pub fn binding(self: Mods) Mods {
- return .{
- .shift = self.shift,
- .ctrl = self.ctrl,
- .alt = self.alt,
- .super = self.super,
- };
- }
-
- /// Perform `self &~ other` to remove the other mods from self.
- pub fn unset(self: Mods, other: Mods) Mods {
- return @bitCast(self.int() & ~other.int());
- }
-
- /// Returns the mods without locks set.
- pub fn withoutLocks(self: Mods) Mods {
- var copy = self;
- copy.caps_lock = false;
- copy.num_lock = false;
- return copy;
- }
-
- /// Return the mods to use for key translation. This handles settings
- /// like macos-option-as-alt. The translation mods should be used for
- /// translation but never sent back in for the key callback.
- pub fn translation(self: Mods, option_as_alt: OptionAsAlt) Mods {
- var result = self;
-
- // macos-option-as-alt for darwin
- if (comptime builtin.target.os.tag.isDarwin()) alt: {
- // Alt has to be set only on the correct side
- switch (option_as_alt) {
- .false => break :alt,
- .true => {},
- .left => if (self.sides.alt == .right) break :alt,
- .right => if (self.sides.alt == .left) break :alt,
- }
-
- // Unset alt
- result.alt = false;
- }
-
- return result;
- }
-
- /// Checks to see if super is on (MacOS) or ctrl.
- pub fn ctrlOrSuper(self: Mods) bool {
- if (comptime builtin.target.os.tag.isDarwin()) {
- return self.super;
- }
- return self.ctrl;
- }
-
- // For our own understanding
- test {
- const testing = std.testing;
- try testing.expectEqual(@as(Backing, @bitCast(Mods{})), @as(Backing, 0b0));
- try testing.expectEqual(
- @as(Backing, @bitCast(Mods{ .shift = true })),
- @as(Backing, 0b0000_0001),
- );
- }
-
- test "translation macos-option-as-alt" {
- if (comptime !builtin.target.os.tag.isDarwin()) return error.SkipZigTest;
-
- const testing = std.testing;
-
- // Unset
- {
- const mods: Mods = .{};
- const result = mods.translation(.true);
- try testing.expectEqual(result, mods);
- }
-
- // Set
- {
- const mods: Mods = .{ .alt = true };
- const result = mods.translation(.true);
- try testing.expectEqual(Mods{}, result);
- }
-
- // Set but disabled
- {
- const mods: Mods = .{ .alt = true };
- const result = mods.translation(.false);
- try testing.expectEqual(result, mods);
- }
-
- // Set wrong side
- {
- const mods: Mods = .{ .alt = true, .sides = .{ .alt = .right } };
- const result = mods.translation(.left);
- try testing.expectEqual(result, mods);
- }
- {
- const mods: Mods = .{ .alt = true, .sides = .{ .alt = .left } };
- const result = mods.translation(.right);
- try testing.expectEqual(result, mods);
- }
-
- // Set with other mods
- {
- const mods: Mods = .{ .alt = true, .shift = true };
- const result = mods.translation(.true);
- try testing.expectEqual(Mods{ .shift = true }, result);
- }
- }
-};
-
/// The action associated with an input event. This is backed by a c_int
/// so that we can use the enum as-is for our embedding API.
///
@@ -696,7 +543,7 @@ pub const Key = enum(c_int) {
}
/// Returns the cimgui key constant for this key.
- pub fn imguiKey(self: Key) ?c_uint {
+ pub fn imguiKey(self: Key) ?c_int {
return switch (self) {
.key_a => cimgui.c.ImGuiKey_A,
.key_b => cimgui.c.ImGuiKey_B,
diff --git a/src/input/key_encode.zig b/src/input/key_encode.zig
index 736df58a0..3716c226e 100644
--- a/src/input/key_encode.zig
+++ b/src/input/key_encode.zig
@@ -153,7 +153,7 @@ fn kitty(
// IME confirmation still sends an enter key so if we have enter
// and UTF8 text we just send it directly since we assume that is
- // whats happening. See legacy()'s similar logic for more details
+ // what's happening. See legacy()'s similar logic for more details
// on how to verify this.
if (event.utf8.len > 0) utf8: {
switch (event.key) {
diff --git a/src/input/key_mods.zig b/src/input/key_mods.zig
new file mode 100644
index 000000000..35e1c1038
--- /dev/null
+++ b/src/input/key_mods.zig
@@ -0,0 +1,914 @@
+const std = @import("std");
+const assert = @import("../quirks.zig").inlineAssert;
+const Allocator = std.mem.Allocator;
+const builtin = @import("builtin");
+const OptionAsAlt = @import("config.zig").OptionAsAlt;
+
+/// Aliases for modifier names.
+pub const alias: []const struct { []const u8, Mod } = &.{
+ .{ "cmd", .super },
+ .{ "command", .super },
+ .{ "opt", .alt },
+ .{ "option", .alt },
+ .{ "control", .ctrl },
+};
+
+/// Single modifier
+pub const Mod = enum {
+ shift,
+ ctrl,
+ alt,
+ super,
+
+ pub const Side = enum(u1) { left, right };
+};
+
+/// A bitmask for all key modifiers.
+///
+/// IMPORTANT: Any changes here update include/ghostty.h
+pub const Mods = packed struct(Mods.Backing) {
+ pub const Backing = u16;
+
+ shift: bool = false,
+ ctrl: bool = false,
+ alt: bool = false,
+ super: bool = false,
+ caps_lock: bool = false,
+ num_lock: bool = false,
+ sides: Side = .{},
+ _padding: u6 = 0,
+
+ /// The standard modifier keys only. Does not include the lock keys,
+ /// only standard bindable keys.
+ pub const Keys = packed struct(u4) {
+ shift: bool = false,
+ ctrl: bool = false,
+ alt: bool = false,
+ super: bool = false,
+
+ pub const Backing = @typeInfo(Keys).@"struct".backing_integer.?;
+
+ pub inline fn int(self: Keys) Keys.Backing {
+ return @bitCast(self);
+ }
+ };
+
+ /// Tracks the side that is active for any given modifier. Note
+ /// that this doesn't confirm a modifier is pressed; you must check
+ /// the bool for that in addition to this.
+ ///
+ /// Not all platforms support this, check apprt for more info.
+ pub const Side = packed struct(u4) {
+ shift: Mod.Side = .left,
+ ctrl: Mod.Side = .left,
+ alt: Mod.Side = .left,
+ super: Mod.Side = .left,
+
+ pub const Backing = @typeInfo(Side).@"struct".backing_integer.?;
+ };
+
+ /// The mask that has all the side bits set.
+ pub const side_mask: Mods = .{
+ .sides = .{
+ .shift = .right,
+ .ctrl = .right,
+ .alt = .right,
+ .super = .right,
+ },
+ };
+
+ /// Integer value of this struct.
+ pub fn int(self: Mods) Backing {
+ return @bitCast(self);
+ }
+
+ /// Returns true if no modifiers are set.
+ pub fn empty(self: Mods) bool {
+ return self.int() == 0;
+ }
+
+ /// Returns true if two mods are equal.
+ pub fn equal(self: Mods, other: Mods) bool {
+ return self.int() == other.int();
+ }
+
+ /// Returns only the keys.
+ ///
+ /// In the future I want to remove `binding` for this. I didn't want
+ /// to do that all in one PR where I added this because its a bigger
+ /// change.
+ pub fn keys(self: Mods) Keys {
+ const backing: Keys.Backing = @truncate(self.int());
+ return @bitCast(backing);
+ }
+
+ /// Return mods that are only relevant for bindings.
+ pub fn binding(self: Mods) Mods {
+ return .{
+ .shift = self.shift,
+ .ctrl = self.ctrl,
+ .alt = self.alt,
+ .super = self.super,
+ };
+ }
+
+ /// Perform `self &~ other` to remove the other mods from self.
+ pub fn unset(self: Mods, other: Mods) Mods {
+ return @bitCast(self.int() & ~other.int());
+ }
+
+ /// Returns the mods without locks set.
+ pub fn withoutLocks(self: Mods) Mods {
+ var copy = self;
+ copy.caps_lock = false;
+ copy.num_lock = false;
+ return copy;
+ }
+
+ /// Return the mods to use for key translation. This handles settings
+ /// like macos-option-as-alt. The translation mods should be used for
+ /// translation but never sent back in for the key callback.
+ pub fn translation(self: Mods, option_as_alt: OptionAsAlt) Mods {
+ var result = self;
+
+ // macos-option-as-alt for darwin
+ if (comptime builtin.target.os.tag.isDarwin()) alt: {
+ // Alt has to be set only on the correct side
+ switch (option_as_alt) {
+ .false => break :alt,
+ .true => {},
+ .left => if (self.sides.alt == .right) break :alt,
+ .right => if (self.sides.alt == .left) break :alt,
+ }
+
+ // Unset alt
+ result.alt = false;
+ }
+
+ return result;
+ }
+
+ /// Checks to see if super is on (MacOS) or ctrl.
+ pub fn ctrlOrSuper(self: Mods) bool {
+ if (comptime builtin.target.os.tag.isDarwin()) {
+ return self.super;
+ }
+ return self.ctrl;
+ }
+
+ // For our own understanding
+ test {
+ const testing = std.testing;
+ try testing.expectEqual(@as(Backing, @bitCast(Mods{})), @as(Backing, 0b0));
+ try testing.expectEqual(
+ @as(Backing, @bitCast(Mods{ .shift = true })),
+ @as(Backing, 0b0000_0001),
+ );
+ }
+
+ test "translation macos-option-as-alt" {
+ if (comptime !builtin.target.os.tag.isDarwin()) return error.SkipZigTest;
+
+ const testing = std.testing;
+
+ // Unset
+ {
+ const mods: Mods = .{};
+ const result = mods.translation(.true);
+ try testing.expectEqual(result, mods);
+ }
+
+ // Set
+ {
+ const mods: Mods = .{ .alt = true };
+ const result = mods.translation(.true);
+ try testing.expectEqual(Mods{}, result);
+ }
+
+ // Set but disabled
+ {
+ const mods: Mods = .{ .alt = true };
+ const result = mods.translation(.false);
+ try testing.expectEqual(result, mods);
+ }
+
+ // Set wrong side
+ {
+ const mods: Mods = .{ .alt = true, .sides = .{ .alt = .right } };
+ const result = mods.translation(.left);
+ try testing.expectEqual(result, mods);
+ }
+ {
+ const mods: Mods = .{ .alt = true, .sides = .{ .alt = .left } };
+ const result = mods.translation(.right);
+ try testing.expectEqual(result, mods);
+ }
+
+ // Set with other mods
+ {
+ const mods: Mods = .{ .alt = true, .shift = true };
+ const result = mods.translation(.true);
+ try testing.expectEqual(Mods{ .shift = true }, result);
+ }
+ }
+};
+
+/// Modifier remapping. See `key-remap` in Config.zig for detailed docs.
+pub const RemapSet = struct {
+ /// Available mappings.
+ map: std.AutoArrayHashMapUnmanaged(Mods, Mods),
+
+ /// The mask of remapped modifiers that can be used to quickly
+ /// check if some input mods need remapping.
+ mask: Mask,
+
+ pub const empty: RemapSet = .{
+ .map = .{},
+ .mask = .{},
+ };
+
+ pub const ParseError = Allocator.Error || error{
+ MissingAssignment,
+ InvalidMod,
+ };
+
+ /// Parse from CLI input. Required by Config.
+ pub fn parseCLI(self: *RemapSet, alloc: Allocator, input: ?[]const u8) !void {
+ const value = input orelse "";
+
+ // Empty value resets the set
+ if (value.len == 0) {
+ self.map.clearRetainingCapacity();
+ self.mask = .{};
+ return;
+ }
+
+ self.parse(alloc, value) catch |err| switch (err) {
+ error.OutOfMemory => return error.OutOfMemory,
+ error.MissingAssignment, error.InvalidMod => return error.InvalidValue,
+ };
+ }
+
+ /// Parse a modifier remap and add it to the set.
+ pub fn parse(
+ self: *RemapSet,
+ alloc: Allocator,
+ input: []const u8,
+ ) ParseError!void {
+ // Find the assignment point ('=')
+ const eql_idx = std.mem.indexOfScalar(
+ u8,
+ input,
+ '=',
+ ) orelse return error.MissingAssignment;
+
+ // The to side defaults to "left" if no explicit side is given.
+ // This is because this is the default unsided value provided by
+ // the apprts in the current Mods layout.
+ const to: Mods = to: {
+ const raw = try parseMod(input[eql_idx + 1 ..]);
+ break :to initMods(raw[0], raw[1] orelse .left);
+ };
+
+ // The from side, if sided, is easy and we put it directly into
+ // the map.
+ const from_raw = try parseMod(input[0..eql_idx]);
+ if (from_raw[1]) |from_side| {
+ const from: Mods = initMods(from_raw[0], from_side);
+ try self.map.put(
+ alloc,
+ from,
+ to,
+ );
+ errdefer comptime unreachable;
+ self.mask.update(from);
+ return;
+ }
+
+ // We need to do some combinatorial explosion here for unsided
+ // from in order to assign all possible sides.
+ const from_left = initMods(from_raw[0], .left);
+ const from_right = initMods(from_raw[0], .right);
+ try self.map.put(
+ alloc,
+ from_left,
+ to,
+ );
+ errdefer _ = self.map.swapRemove(from_left);
+ try self.map.put(
+ alloc,
+ from_right,
+ to,
+ );
+ errdefer _ = self.map.swapRemove(from_right);
+
+ errdefer comptime unreachable;
+ self.mask.update(from_left);
+ self.mask.update(from_right);
+ }
+
+ pub fn deinit(self: *RemapSet, alloc: Allocator) void {
+ self.map.deinit(alloc);
+ }
+
+ /// Must be called prior to any remappings so that the mapping
+ /// is sorted properly. Otherwise, you will get invalid results.
+ pub fn finalize(self: *RemapSet) void {
+ const Context = struct {
+ keys: []const Mods,
+
+ pub fn lessThan(
+ ctx: @This(),
+ a_index: usize,
+ b_index: usize,
+ ) bool {
+ _ = b_index;
+
+ // Mods with any right sides prioritize
+ const side_mask = comptime Mods.side_mask.int();
+ const a = ctx.keys[a_index];
+ return a.int() & side_mask != 0;
+ }
+ };
+
+ self.map.sort(Context{ .keys = self.map.keys() });
+ }
+
+ /// Deep copy of the struct. Required by Config.
+ pub fn clone(self: *const RemapSet, alloc: Allocator) Allocator.Error!RemapSet {
+ return .{
+ .map = try self.map.clone(alloc),
+ .mask = self.mask,
+ };
+ }
+
+ /// Compare if two RemapSets are equal. Required by Config.
+ pub fn equal(self: RemapSet, other: RemapSet) bool {
+ if (self.map.count() != other.map.count()) return false;
+
+ var it = self.map.iterator();
+ while (it.next()) |entry| {
+ const other_value = other.map.get(entry.key_ptr.*) orelse return false;
+ if (!entry.value_ptr.equal(other_value)) return false;
+ }
+
+ return true;
+ }
+
+ /// Used by Formatter. Required by Config.
+ pub fn formatEntry(self: RemapSet, formatter: anytype) !void {
+ if (self.map.count() == 0) {
+ try formatter.formatEntry(void, {});
+ return;
+ }
+
+ var it = self.map.iterator();
+ while (it.next()) |entry| {
+ const from = entry.key_ptr.*;
+ const to = entry.value_ptr.*;
+
+ var buf: [64]u8 = undefined;
+ var fbs = std.io.fixedBufferStream(&buf);
+ const writer = fbs.writer();
+ formatMod(writer, from) catch return error.OutOfMemory;
+ writer.writeByte('=') catch return error.OutOfMemory;
+ formatMod(writer, to) catch return error.OutOfMemory;
+ try formatter.formatEntry([]const u8, fbs.getWritten());
+ }
+ }
+
+ fn formatMod(writer: anytype, mods: Mods) !void {
+ // Check which mod is set and format it with optional side prefix
+ inline for (.{ "shift", "ctrl", "alt", "super" }) |name| {
+ if (@field(mods, name)) {
+ const side = @field(mods.sides, name);
+ if (side == .right) {
+ try writer.writeAll("right_");
+ } else {
+ // Only write left_ if we need to distinguish
+ // For now, always write left_ if it's a sided mapping
+ try writer.writeAll("left_");
+ }
+ try writer.writeAll(name);
+ return;
+ }
+ }
+ }
+
+ /// Parses a single mode in a single remapping string. E.g.
+ /// `ctrl` or `left_shift`.
+ fn parseMod(input: []const u8) error{InvalidMod}!struct { Mod, ?Mod.Side } {
+ const side_str, const mod_str = if (std.mem.indexOfScalar(
+ u8,
+ input,
+ '_',
+ )) |idx| .{
+ input[0..idx],
+ input[idx + 1 ..],
+ } else .{
+ "",
+ input,
+ };
+
+ const mod: Mod = if (std.meta.stringToEnum(
+ Mod,
+ mod_str,
+ )) |mod| mod else mod: {
+ inline for (alias) |pair| {
+ if (std.mem.eql(u8, mod_str, pair[0])) {
+ break :mod pair[1];
+ }
+ }
+
+ return error.InvalidMod;
+ };
+
+ return .{
+ mod,
+ if (side_str.len > 0) std.meta.stringToEnum(
+ Mod.Side,
+ side_str,
+ ) orelse return error.InvalidMod else null,
+ };
+ }
+
+ fn initMods(mod: Mod, side: Mod.Side) Mods {
+ switch (mod) {
+ inline else => |tag| {
+ var mods: Mods = .{};
+ @field(mods, @tagName(tag)) = true;
+ @field(mods.sides, @tagName(tag)) = side;
+ return mods;
+ },
+ }
+ }
+
+ /// Returns true if the given mods need remapping.
+ pub fn isRemapped(self: *const RemapSet, mods: Mods) bool {
+ return self.mask.match(mods);
+ }
+
+ /// Apply a remap to the given mods.
+ pub fn apply(self: *const RemapSet, mods: Mods) Mods {
+ if (!self.isRemapped(mods)) return mods;
+
+ const mods_binding: Mods.Keys.Backing = @truncate(mods.int());
+ const mods_sides: Mods.Side.Backing = @bitCast(mods.sides);
+
+ var it = self.map.iterator();
+ while (it.next()) |entry| {
+ const from = entry.key_ptr.*;
+ const from_binding: Mods.Keys.Backing = @truncate(from.int());
+ if (mods_binding & from_binding != from_binding) continue;
+ const from_sides: Mods.Side.Backing = @bitCast(from.sides);
+ if ((mods_sides ^ from_sides) & from_binding != 0) continue;
+
+ var mods_int = mods.int();
+ mods_int &= ~from.int();
+ mods_int |= entry.value_ptr.int();
+ return @bitCast(mods_int);
+ }
+
+ unreachable;
+ }
+
+ /// Tracks which modifier keys and sides have remappings registered.
+ /// Used as a fast pre-check before doing expensive map lookups.
+ ///
+ /// The mask uses separate tracking for left and right sides because
+ /// remappings can be side-specific (e.g., only remap left_ctrl).
+ ///
+ /// Note: `left_sides` uses inverted logic where 1 means "left is remapped"
+ /// even though `Mod.Side.left = 0`. This allows efficient bitwise matching
+ /// since we can AND directly with the side bits.
+ pub const Mask = packed struct(u12) {
+ /// Which modifier keys (shift/ctrl/alt/super) have any remapping.
+ keys: Mods.Keys = .{},
+ /// Which modifiers have left-side remappings (inverted: 1 = left remapped).
+ left_sides: Mods.Side = .{},
+ /// Which modifiers have right-side remappings (1 = right remapped).
+ right_sides: Mods.Side = .{},
+
+ /// Adds a modifier to the mask, marking it as having a remapping.
+ pub fn update(self: *Mask, mods: Mods) void {
+ const keys_int: Mods.Keys.Backing = mods.keys().int();
+
+ // OR the new keys into our existing keys mask.
+ // Example: keys=0b0000, new ctrl โ keys=0b0010
+ self.keys = @bitCast(self.keys.int() | keys_int);
+
+ // Both Keys and Side are u4 with matching bit positions.
+ // This lets us use keys_int to select which side bits to update.
+ const sides: Mods.Side.Backing = @bitCast(mods.sides);
+ const left_int: Mods.Side.Backing = @bitCast(self.left_sides);
+ const right_int: Mods.Side.Backing = @bitCast(self.right_sides);
+
+ // Update left_sides: set bit if this key is active AND side is left.
+ // Since Side.left=0, we invert sides (~sides) so left becomes 1.
+ // keys_int masks to only affect the modifier being added.
+ // Example: left_ctrl โ keys_int=0b0010, ~sides=0b1111 (left=0 inverted)
+ // result: left_int | (0b0010 & 0b1111) = left_int | 0b0010
+ self.left_sides = @bitCast(left_int | (keys_int & ~sides));
+
+ // Update right_sides: set bit if this key is active AND side is right.
+ // Since Side.right=1, we use sides directly.
+ // Example: right_ctrl โ keys_int=0b0010, sides=0b0010 (right=1)
+ // result: right_int | (0b0010 & 0b0010) = right_int | 0b0010
+ self.right_sides = @bitCast(right_int | (keys_int & sides));
+ }
+
+ /// Returns true if the given mods match any remapping in this mask.
+ /// This is a fast check to avoid expensive map lookups when no
+ /// remapping could possibly apply.
+ ///
+ /// Checks both that the modifier key is remapped AND that the
+ /// specific side (left/right) being pressed has a remapping.
+ pub fn match(self: *const Mask, mods: Mods) bool {
+ // Find which pressed keys have remappings registered.
+ // Example: pressed={ctrl,alt}, mask={ctrl} โ active=0b0010 (just ctrl)
+ const active = mods.keys().int() & self.keys.int();
+ if (active == 0) return false;
+
+ // Check if the pressed side matches a remapped side.
+ // For left (sides bit = 0): check against left_int (where 1 = left remapped)
+ // ~sides inverts so left becomes 1, then AND with left_int
+ // For right (sides bit = 1): check against right_int directly
+ //
+ // Example: pressing left_ctrl (sides.ctrl=0, left_int.ctrl=1)
+ // ~sides = 0b1111, left_int = 0b0010
+ // (~sides & left_int) = 0b0010 โ matches
+ //
+ // Example: pressing right_ctrl but only left_ctrl is remapped
+ // sides = 0b0010, left_int = 0b0010, right_int = 0b0000
+ // (~0b0010 & 0b0010) | (0b0010 & 0b0000) = 0b0000 โ no match
+ const sides: Mods.Side.Backing = @bitCast(mods.sides);
+ const left_int: Mods.Side.Backing = @bitCast(self.left_sides);
+ const right_int: Mods.Side.Backing = @bitCast(self.right_sides);
+ const side_match = (~sides & left_int) | (sides & right_int);
+
+ // Final check: is any active (pressed + remapped) key also side-matched?
+ return (active & side_match) != 0;
+ }
+ };
+};
+
+test "RemapSet: unsided remap creates both left and right mappings" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var set: RemapSet = .empty;
+ defer set.deinit(alloc);
+ try set.parse(alloc, "ctrl=super");
+ set.finalize();
+ try testing.expectEqual(
+ Mods{
+ .super = true,
+ .sides = .{ .super = .left },
+ },
+ set.apply(.{
+ .ctrl = true,
+ .sides = .{ .ctrl = .left },
+ }),
+ );
+ try testing.expectEqual(
+ Mods{
+ .super = true,
+ .sides = .{ .super = .left },
+ },
+ set.apply(.{
+ .ctrl = true,
+ .sides = .{ .ctrl = .right },
+ }),
+ );
+}
+
+test "RemapSet: sided from only maps that side" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var set: RemapSet = .empty;
+ defer set.deinit(alloc);
+
+ try set.parse(alloc, "left_alt=ctrl");
+ set.finalize();
+
+ const left_alt: Mods = .{ .alt = true, .sides = .{ .alt = .left } };
+ const left_ctrl: Mods = .{ .ctrl = true, .sides = .{ .ctrl = .left } };
+ try testing.expectEqual(left_ctrl, set.apply(left_alt));
+
+ const right_alt: Mods = .{ .alt = true, .sides = .{ .alt = .right } };
+ try testing.expectEqual(right_alt, set.apply(right_alt));
+}
+
+test "RemapSet: sided to" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var set: RemapSet = .empty;
+ defer set.deinit(alloc);
+
+ try set.parse(alloc, "ctrl=right_super");
+ set.finalize();
+
+ const left_ctrl: Mods = .{ .ctrl = true, .sides = .{ .ctrl = .left } };
+ const right_super: Mods = .{ .super = true, .sides = .{ .super = .right } };
+ try testing.expectEqual(right_super, set.apply(left_ctrl));
+}
+
+test "RemapSet: both sides specified" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var set: RemapSet = .empty;
+ defer set.deinit(alloc);
+
+ try set.parse(alloc, "left_shift=right_ctrl");
+ set.finalize();
+
+ const left_shift: Mods = .{ .shift = true, .sides = .{ .shift = .left } };
+ const right_ctrl: Mods = .{ .ctrl = true, .sides = .{ .ctrl = .right } };
+ try testing.expectEqual(right_ctrl, set.apply(left_shift));
+}
+
+test "RemapSet: multiple parses accumulate" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var set: RemapSet = .empty;
+ defer set.deinit(alloc);
+
+ try set.parse(alloc, "left_ctrl=super");
+ try set.parse(alloc, "left_alt=ctrl");
+ set.finalize();
+
+ const left_ctrl: Mods = .{ .ctrl = true, .sides = .{ .ctrl = .left } };
+ const left_super: Mods = .{ .super = true, .sides = .{ .super = .left } };
+ try testing.expectEqual(left_super, set.apply(left_ctrl));
+
+ const left_alt: Mods = .{ .alt = true, .sides = .{ .alt = .left } };
+ const left_ctrl_result: Mods = .{ .ctrl = true, .sides = .{ .ctrl = .left } };
+ try testing.expectEqual(left_ctrl_result, set.apply(left_alt));
+}
+
+test "RemapSet: error on missing assignment" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var set: RemapSet = .empty;
+ defer set.deinit(alloc);
+
+ try testing.expectError(error.MissingAssignment, set.parse(alloc, "ctrl"));
+ try testing.expectError(error.MissingAssignment, set.parse(alloc, ""));
+}
+
+test "RemapSet: error on invalid modifier" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var set: RemapSet = .empty;
+ defer set.deinit(alloc);
+
+ try testing.expectError(error.InvalidMod, set.parse(alloc, "invalid=ctrl"));
+ try testing.expectError(error.InvalidMod, set.parse(alloc, "ctrl=invalid"));
+ try testing.expectError(error.InvalidMod, set.parse(alloc, "middle_ctrl=super"));
+}
+
+test "RemapSet: isRemapped checks mask" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var set: RemapSet = .empty;
+ defer set.deinit(alloc);
+
+ try set.parse(alloc, "ctrl=super");
+ set.finalize();
+
+ try testing.expect(set.isRemapped(.{ .ctrl = true }));
+ try testing.expect(!set.isRemapped(.{ .alt = true }));
+ try testing.expect(!set.isRemapped(.{ .shift = true }));
+}
+
+test "RemapSet: clone creates independent copy" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var set: RemapSet = .empty;
+ defer set.deinit(alloc);
+
+ try set.parse(alloc, "ctrl=super");
+ set.finalize();
+
+ var cloned = try set.clone(alloc);
+ defer cloned.deinit(alloc);
+
+ try testing.expect(set.equal(cloned));
+ try testing.expect(cloned.isRemapped(.{ .ctrl = true }));
+}
+
+test "RemapSet: equal compares correctly" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var set1: RemapSet = .empty;
+ defer set1.deinit(alloc);
+
+ var set2: RemapSet = .empty;
+ defer set2.deinit(alloc);
+
+ try testing.expect(set1.equal(set2));
+
+ try set1.parse(alloc, "ctrl=super");
+ try testing.expect(!set1.equal(set2));
+
+ try set2.parse(alloc, "ctrl=super");
+ try testing.expect(set1.equal(set2));
+
+ try set1.parse(alloc, "alt=shift");
+ try testing.expect(!set1.equal(set2));
+}
+
+test "RemapSet: parseCLI basic" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var set: RemapSet = .empty;
+ defer set.deinit(alloc);
+
+ try set.parseCLI(alloc, "ctrl=super");
+ try testing.expectEqual(@as(usize, 2), set.map.count());
+}
+
+test "RemapSet: parseCLI empty clears" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var set: RemapSet = .empty;
+ defer set.deinit(alloc);
+
+ try set.parseCLI(alloc, "ctrl=super");
+ try testing.expectEqual(@as(usize, 2), set.map.count());
+
+ try set.parseCLI(alloc, "");
+ try testing.expectEqual(@as(usize, 0), set.map.count());
+}
+
+test "RemapSet: parseCLI invalid" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var set: RemapSet = .empty;
+ defer set.deinit(alloc);
+
+ try testing.expectError(error.InvalidValue, set.parseCLI(alloc, "foo=bar"));
+ try testing.expectError(error.InvalidValue, set.parseCLI(alloc, "ctrl"));
+}
+
+test "RemapSet: parse aliased modifiers" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var set: RemapSet = .empty;
+ defer set.deinit(alloc);
+
+ try set.parse(alloc, "cmd=ctrl");
+ set.finalize();
+
+ const left_super: Mods = .{ .super = true, .sides = .{ .super = .left } };
+ const left_ctrl: Mods = .{ .ctrl = true, .sides = .{ .ctrl = .left } };
+ try testing.expectEqual(left_ctrl, set.apply(left_super));
+}
+
+test "RemapSet: parse aliased modifiers command" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var set: RemapSet = .empty;
+ defer set.deinit(alloc);
+
+ try set.parse(alloc, "command=alt");
+ set.finalize();
+
+ const left_super: Mods = .{ .super = true, .sides = .{ .super = .left } };
+ const left_alt: Mods = .{ .alt = true, .sides = .{ .alt = .left } };
+ try testing.expectEqual(left_alt, set.apply(left_super));
+}
+
+test "RemapSet: parse aliased modifiers opt and option" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var set: RemapSet = .empty;
+ defer set.deinit(alloc);
+
+ try set.parse(alloc, "opt=super");
+ set.finalize();
+
+ const left_alt: Mods = .{ .alt = true, .sides = .{ .alt = .left } };
+ const left_super: Mods = .{ .super = true, .sides = .{ .super = .left } };
+ try testing.expectEqual(left_super, set.apply(left_alt));
+
+ set.deinit(alloc);
+ set = .empty;
+
+ try set.parse(alloc, "option=shift");
+ set.finalize();
+
+ const left_shift: Mods = .{ .shift = true, .sides = .{ .shift = .left } };
+ try testing.expectEqual(left_shift, set.apply(left_alt));
+}
+
+test "RemapSet: parse aliased modifiers control" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var set: RemapSet = .empty;
+ defer set.deinit(alloc);
+
+ try set.parse(alloc, "control=super");
+ set.finalize();
+
+ const left_ctrl: Mods = .{ .ctrl = true, .sides = .{ .ctrl = .left } };
+ const left_super: Mods = .{ .super = true, .sides = .{ .super = .left } };
+ try testing.expectEqual(left_super, set.apply(left_ctrl));
+}
+
+test "RemapSet: parse aliased modifiers on target side" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var set: RemapSet = .empty;
+ defer set.deinit(alloc);
+
+ try set.parse(alloc, "alt=cmd");
+ set.finalize();
+
+ const left_alt: Mods = .{ .alt = true, .sides = .{ .alt = .left } };
+ const left_super: Mods = .{ .super = true, .sides = .{ .super = .left } };
+ try testing.expectEqual(left_super, set.apply(left_alt));
+}
+
+test "RemapSet: formatEntry empty" {
+ const testing = std.testing;
+ const formatterpkg = @import("../config/formatter.zig");
+
+ var buf: std.Io.Writer.Allocating = .init(testing.allocator);
+ defer buf.deinit();
+
+ const set: RemapSet = .empty;
+ try set.formatEntry(formatterpkg.entryFormatter("key-remap", &buf.writer));
+ try testing.expectEqualSlices(u8, "key-remap = \n", buf.written());
+}
+
+test "RemapSet: formatEntry single sided" {
+ const testing = std.testing;
+ const formatterpkg = @import("../config/formatter.zig");
+
+ var buf: std.Io.Writer.Allocating = .init(testing.allocator);
+ defer buf.deinit();
+
+ var set: RemapSet = .empty;
+ defer set.deinit(testing.allocator);
+
+ try set.parse(testing.allocator, "left_ctrl=super");
+ set.finalize();
+
+ try set.formatEntry(formatterpkg.entryFormatter("key-remap", &buf.writer));
+ try testing.expectEqualSlices(u8, "key-remap = left_ctrl=left_super\n", buf.written());
+}
+
+test "RemapSet: formatEntry unsided creates two entries" {
+ const testing = std.testing;
+ const formatterpkg = @import("../config/formatter.zig");
+
+ var buf: std.Io.Writer.Allocating = .init(testing.allocator);
+ defer buf.deinit();
+
+ var set: RemapSet = .empty;
+ defer set.deinit(testing.allocator);
+
+ try set.parse(testing.allocator, "ctrl=super");
+ set.finalize();
+
+ try set.formatEntry(formatterpkg.entryFormatter("key-remap", &buf.writer));
+ // Unsided creates both left and right mappings
+ const written = buf.written();
+ try testing.expect(std.mem.indexOf(u8, written, "left_ctrl=left_super") != null);
+ try testing.expect(std.mem.indexOf(u8, written, "right_ctrl=left_super") != null);
+}
+
+test "RemapSet: formatEntry right sided" {
+ const testing = std.testing;
+ const formatterpkg = @import("../config/formatter.zig");
+
+ var buf: std.Io.Writer.Allocating = .init(testing.allocator);
+ defer buf.deinit();
+
+ var set: RemapSet = .empty;
+ defer set.deinit(testing.allocator);
+
+ try set.parse(testing.allocator, "left_alt=right_ctrl");
+ set.finalize();
+
+ try set.formatEntry(formatterpkg.entryFormatter("key-remap", &buf.writer));
+ try testing.expectEqualSlices(u8, "key-remap = left_alt=right_ctrl\n", buf.written());
+}
diff --git a/src/input/mouse.zig b/src/input/mouse.zig
index 2be2b9a26..bdf967ed2 100644
--- a/src/input/mouse.zig
+++ b/src/input/mouse.zig
@@ -10,7 +10,7 @@ pub const ButtonState = enum(c_int) {
press,
};
-/// Possible mouse buttons. We only track up to 11 because thats the maximum
+/// Possible mouse buttons. We only track up to 11 because that's the maximum
/// button input that terminal mouse tracking handles without becoming
/// ambiguous.
///
diff --git a/src/inspector/Inspector.zig b/src/inspector/Inspector.zig
index 86a7b473c..156e2cb18 100644
--- a/src/inspector/Inspector.zig
+++ b/src/inspector/Inspector.zig
@@ -7,7 +7,7 @@ const std = @import("std");
const assert = @import("../quirks.zig").inlineAssert;
const Allocator = std.mem.Allocator;
const builtin = @import("builtin");
-const cimgui = @import("cimgui");
+const cimgui = @import("dcimgui");
const Surface = @import("../Surface.zig");
const font = @import("../font/main.zig");
const input = @import("../input.zig");
@@ -126,7 +126,7 @@ const CellInspect = union(enum) {
/// Setup the ImGui state. This requires an ImGui context to be set.
pub fn setup() void {
- const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO();
+ const io: *cimgui.c.ImGuiIO = cimgui.c.ImGui_GetIO();
// Enable docking, which we use heavily for the UI.
io.ConfigFlags |= cimgui.c.ImGuiConfigFlags_DockingEnable;
@@ -144,15 +144,15 @@ pub fn setup() void {
// This is currently hardcoded to a 2x content scale.
const font_size = 16 * 2;
- const font_config: *cimgui.c.ImFontConfig = cimgui.c.ImFontConfig_ImFontConfig();
- defer cimgui.c.ImFontConfig_destroy(font_config);
+ var font_config: cimgui.c.ImFontConfig = undefined;
+ cimgui.ext.ImFontConfig_ImFontConfig(&font_config);
font_config.FontDataOwnedByAtlas = false;
_ = cimgui.c.ImFontAtlas_AddFontFromMemoryTTF(
io.Fonts,
- @ptrCast(@constCast(font.embedded.regular)),
- font.embedded.regular.len,
+ @ptrCast(@constCast(font.embedded.regular.ptr)),
+ @intCast(font.embedded.regular.len),
font_size,
- font_config,
+ &font_config,
null,
);
}
@@ -221,11 +221,7 @@ pub fn recordPtyRead(self: *Inspector, data: []const u8) !void {
/// Render the frame.
pub fn render(self: *Inspector) void {
- const dock_id = cimgui.c.igDockSpaceOverViewport(
- cimgui.c.igGetMainViewport(),
- cimgui.c.ImGuiDockNodeFlags_None,
- null,
- );
+ const dock_id = cimgui.c.ImGui_DockSpaceOverViewport();
// Render all of our data. We hold the mutex for this duration. This is
// expensive but this is an initial implementation until it doesn't work
@@ -245,7 +241,7 @@ pub fn render(self: *Inspector) void {
// widgets and such.
if (builtin.mode == .Debug) {
var show: bool = true;
- cimgui.c.igShowDemoWindow(&show);
+ cimgui.c.ImGui_ShowDemoWindow(&show);
}
// On first render we set up the layout. We can actually do this at
@@ -261,7 +257,7 @@ fn setupLayout(self: *Inspector, dock_id_main: cimgui.c.ImGuiID) void {
_ = self;
// Our initial focus
- cimgui.c.igSetWindowFocus_Str(window_screen);
+ cimgui.c.ImGui_SetWindowFocusStr(window_screen);
// Setup our initial layout.
const dock_id: struct {
@@ -270,7 +266,7 @@ fn setupLayout(self: *Inspector, dock_id_main: cimgui.c.ImGuiID) void {
} = dock_id: {
var dock_id_left: cimgui.c.ImGuiID = undefined;
var dock_id_right: cimgui.c.ImGuiID = undefined;
- _ = cimgui.c.igDockBuilderSplitNode(
+ _ = cimgui.ImGui_DockBuilderSplitNode(
dock_id_main,
cimgui.c.ImGuiDir_Left,
0.7,
@@ -284,20 +280,20 @@ fn setupLayout(self: *Inspector, dock_id_main: cimgui.c.ImGuiID) void {
};
};
- cimgui.c.igDockBuilderDockWindow(window_cell, dock_id.left);
- cimgui.c.igDockBuilderDockWindow(window_modes, dock_id.left);
- cimgui.c.igDockBuilderDockWindow(window_keyboard, dock_id.left);
- cimgui.c.igDockBuilderDockWindow(window_termio, dock_id.left);
- cimgui.c.igDockBuilderDockWindow(window_screen, dock_id.left);
- cimgui.c.igDockBuilderDockWindow(window_imgui_demo, dock_id.left);
- cimgui.c.igDockBuilderDockWindow(window_size, dock_id.right);
- cimgui.c.igDockBuilderFinish(dock_id_main);
+ cimgui.ImGui_DockBuilderDockWindow(window_cell, dock_id.left);
+ cimgui.ImGui_DockBuilderDockWindow(window_modes, dock_id.left);
+ cimgui.ImGui_DockBuilderDockWindow(window_keyboard, dock_id.left);
+ cimgui.ImGui_DockBuilderDockWindow(window_termio, dock_id.left);
+ cimgui.ImGui_DockBuilderDockWindow(window_screen, dock_id.left);
+ cimgui.ImGui_DockBuilderDockWindow(window_imgui_demo, dock_id.left);
+ cimgui.ImGui_DockBuilderDockWindow(window_size, dock_id.right);
+ cimgui.ImGui_DockBuilderFinish(dock_id_main);
}
fn renderScreenWindow(self: *Inspector) void {
// Start our window. If we're collapsed we do nothing.
- defer cimgui.c.igEnd();
- if (!cimgui.c.igBegin(
+ defer cimgui.c.ImGui_End();
+ if (!cimgui.c.ImGui_Begin(
window_screen,
null,
cimgui.c.ImGuiWindowFlags_NoFocusOnAppearing,
@@ -307,76 +303,70 @@ fn renderScreenWindow(self: *Inspector) void {
const screen: *terminal.Screen = t.screens.active;
{
- _ = cimgui.c.igBeginTable(
+ _ = cimgui.c.ImGui_BeginTable(
"table_screen",
2,
cimgui.c.ImGuiTableFlags_None,
- .{ .x = 0, .y = 0 },
- 0,
);
- defer cimgui.c.igEndTable();
+ defer cimgui.c.ImGui_EndTable();
{
- cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
+ cimgui.c.ImGui_TableNextRow();
{
- _ = cimgui.c.igTableSetColumnIndex(0);
- cimgui.c.igText("Active Screen");
+ _ = cimgui.c.ImGui_TableSetColumnIndex(0);
+ cimgui.c.ImGui_Text("Active Screen");
}
{
- _ = cimgui.c.igTableSetColumnIndex(1);
- cimgui.c.igText("%s", @tagName(t.screens.active_key).ptr);
+ _ = cimgui.c.ImGui_TableSetColumnIndex(1);
+ cimgui.c.ImGui_Text("%s", @tagName(t.screens.active_key).ptr);
}
}
}
- if (cimgui.c.igCollapsingHeader_TreeNodeFlags(
+ if (cimgui.c.ImGui_CollapsingHeader(
"Cursor",
cimgui.c.ImGuiTreeNodeFlags_DefaultOpen,
)) {
{
- _ = cimgui.c.igBeginTable(
+ _ = cimgui.c.ImGui_BeginTable(
"table_cursor",
2,
cimgui.c.ImGuiTableFlags_None,
- .{ .x = 0, .y = 0 },
- 0,
);
- defer cimgui.c.igEndTable();
+ defer cimgui.c.ImGui_EndTable();
inspector.cursor.renderInTable(
self.surface.renderer_state.terminal,
&screen.cursor,
);
} // table
- cimgui.c.igTextDisabled("(Any styles not shown are not currently set)");
+ cimgui.c.ImGui_TextDisabled("(Any styles not shown are not currently set)");
} // cursor
- if (cimgui.c.igCollapsingHeader_TreeNodeFlags(
+ if (cimgui.c.ImGui_CollapsingHeader(
"Keyboard",
cimgui.c.ImGuiTreeNodeFlags_DefaultOpen,
)) {
{
- _ = cimgui.c.igBeginTable(
+ _ = cimgui.c.ImGui_BeginTable(
"table_keyboard",
2,
cimgui.c.ImGuiTableFlags_None,
- .{ .x = 0, .y = 0 },
- 0,
);
- defer cimgui.c.igEndTable();
+ defer cimgui.c.ImGui_EndTable();
const kitty_flags = screen.kitty_keyboard.current();
{
- cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
+ cimgui.c.ImGui_TableNextRow();
{
- _ = cimgui.c.igTableSetColumnIndex(0);
- cimgui.c.igText("Mode");
+ _ = cimgui.c.ImGui_TableSetColumnIndex(0);
+ cimgui.c.ImGui_Text("Mode");
}
{
- _ = cimgui.c.igTableSetColumnIndex(1);
+ _ = cimgui.c.ImGui_TableSetColumnIndex(1);
const mode = if (kitty_flags.int() != 0) "kitty" else "legacy";
- cimgui.c.igText("%s", mode.ptr);
+ cimgui.c.ImGui_Text("%s", mode.ptr);
}
}
@@ -386,15 +376,15 @@ fn renderScreenWindow(self: *Inspector) void {
{
const value = @field(kitty_flags, field.name);
- cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
+ cimgui.c.ImGui_TableNextRow();
{
- _ = cimgui.c.igTableSetColumnIndex(0);
+ _ = cimgui.c.ImGui_TableSetColumnIndex(0);
const name = std.fmt.comptimePrint("{s}", .{field.name});
- cimgui.c.igText("%s", name.ptr);
+ cimgui.c.ImGui_Text("%s", name.ptr);
}
{
- _ = cimgui.c.igTableSetColumnIndex(1);
- cimgui.c.igText(
+ _ = cimgui.c.ImGui_TableSetColumnIndex(1);
+ cimgui.c.ImGui_Text(
"%s",
if (value) "true".ptr else "false".ptr,
);
@@ -403,14 +393,14 @@ fn renderScreenWindow(self: *Inspector) void {
}
} else {
{
- cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
+ cimgui.c.ImGui_TableNextRow();
{
- _ = cimgui.c.igTableSetColumnIndex(0);
- cimgui.c.igText("Xterm modify keys");
+ _ = cimgui.c.ImGui_TableSetColumnIndex(0);
+ cimgui.c.ImGui_Text("Xterm modify keys");
}
{
- _ = cimgui.c.igTableSetColumnIndex(1);
- cimgui.c.igText(
+ _ = cimgui.c.ImGui_TableSetColumnIndex(1);
+ cimgui.c.ImGui_Text(
"%s",
if (t.flags.modify_other_keys_2) "true".ptr else "false".ptr,
);
@@ -420,143 +410,139 @@ fn renderScreenWindow(self: *Inspector) void {
} // table
} // keyboard
- if (cimgui.c.igCollapsingHeader_TreeNodeFlags(
+ if (cimgui.c.ImGui_CollapsingHeader(
"Kitty Graphics",
cimgui.c.ImGuiTreeNodeFlags_DefaultOpen,
)) kitty_gfx: {
if (!screen.kitty_images.enabled()) {
- cimgui.c.igTextDisabled("(Kitty graphics are disabled)");
+ cimgui.c.ImGui_TextDisabled("(Kitty graphics are disabled)");
break :kitty_gfx;
}
{
- _ = cimgui.c.igBeginTable(
+ _ = cimgui.c.ImGui_BeginTable(
"##kitty_graphics",
2,
cimgui.c.ImGuiTableFlags_None,
- .{ .x = 0, .y = 0 },
- 0,
);
- defer cimgui.c.igEndTable();
+ defer cimgui.c.ImGui_EndTable();
const kitty_images = &screen.kitty_images;
{
- cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
+ cimgui.c.ImGui_TableNextRow();
{
- _ = cimgui.c.igTableSetColumnIndex(0);
- cimgui.c.igText("Memory Usage");
+ _ = cimgui.c.ImGui_TableSetColumnIndex(0);
+ cimgui.c.ImGui_Text("Memory Usage");
}
{
- _ = cimgui.c.igTableSetColumnIndex(1);
- cimgui.c.igText("%d bytes (%d KiB)", kitty_images.total_bytes, units.toKibiBytes(kitty_images.total_bytes));
+ _ = cimgui.c.ImGui_TableSetColumnIndex(1);
+ cimgui.c.ImGui_Text("%d bytes (%d KiB)", kitty_images.total_bytes, units.toKibiBytes(kitty_images.total_bytes));
}
}
{
- cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
+ cimgui.c.ImGui_TableNextRow();
{
- _ = cimgui.c.igTableSetColumnIndex(0);
- cimgui.c.igText("Memory Limit");
+ _ = cimgui.c.ImGui_TableSetColumnIndex(0);
+ cimgui.c.ImGui_Text("Memory Limit");
}
{
- _ = cimgui.c.igTableSetColumnIndex(1);
- cimgui.c.igText("%d bytes (%d KiB)", kitty_images.total_limit, units.toKibiBytes(kitty_images.total_limit));
+ _ = cimgui.c.ImGui_TableSetColumnIndex(1);
+ cimgui.c.ImGui_Text("%d bytes (%d KiB)", kitty_images.total_limit, units.toKibiBytes(kitty_images.total_limit));
}
}
{
- cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
+ cimgui.c.ImGui_TableNextRow();
{
- _ = cimgui.c.igTableSetColumnIndex(0);
- cimgui.c.igText("Image Count");
+ _ = cimgui.c.ImGui_TableSetColumnIndex(0);
+ cimgui.c.ImGui_Text("Image Count");
}
{
- _ = cimgui.c.igTableSetColumnIndex(1);
- cimgui.c.igText("%d", kitty_images.images.count());
+ _ = cimgui.c.ImGui_TableSetColumnIndex(1);
+ cimgui.c.ImGui_Text("%d", kitty_images.images.count());
}
}
{
- cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
+ cimgui.c.ImGui_TableNextRow();
{
- _ = cimgui.c.igTableSetColumnIndex(0);
- cimgui.c.igText("Placement Count");
+ _ = cimgui.c.ImGui_TableSetColumnIndex(0);
+ cimgui.c.ImGui_Text("Placement Count");
}
{
- _ = cimgui.c.igTableSetColumnIndex(1);
- cimgui.c.igText("%d", kitty_images.placements.count());
+ _ = cimgui.c.ImGui_TableSetColumnIndex(1);
+ cimgui.c.ImGui_Text("%d", kitty_images.placements.count());
}
}
{
- cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
+ cimgui.c.ImGui_TableNextRow();
{
- _ = cimgui.c.igTableSetColumnIndex(0);
- cimgui.c.igText("Image Loading");
+ _ = cimgui.c.ImGui_TableSetColumnIndex(0);
+ cimgui.c.ImGui_Text("Image Loading");
}
{
- _ = cimgui.c.igTableSetColumnIndex(1);
- cimgui.c.igText("%s", if (kitty_images.loading != null) "true".ptr else "false".ptr);
+ _ = cimgui.c.ImGui_TableSetColumnIndex(1);
+ cimgui.c.ImGui_Text("%s", if (kitty_images.loading != null) "true".ptr else "false".ptr);
}
}
} // table
} // kitty graphics
- if (cimgui.c.igCollapsingHeader_TreeNodeFlags(
+ if (cimgui.c.ImGui_CollapsingHeader(
"Internal Terminal State",
cimgui.c.ImGuiTreeNodeFlags_DefaultOpen,
)) {
const pages = &screen.pages;
{
- _ = cimgui.c.igBeginTable(
+ _ = cimgui.c.ImGui_BeginTable(
"##terminal_state",
2,
cimgui.c.ImGuiTableFlags_None,
- .{ .x = 0, .y = 0 },
- 0,
);
- defer cimgui.c.igEndTable();
+ defer cimgui.c.ImGui_EndTable();
{
- cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
+ cimgui.c.ImGui_TableNextRow();
{
- _ = cimgui.c.igTableSetColumnIndex(0);
- cimgui.c.igText("Memory Usage");
+ _ = cimgui.c.ImGui_TableSetColumnIndex(0);
+ cimgui.c.ImGui_Text("Memory Usage");
}
{
- _ = cimgui.c.igTableSetColumnIndex(1);
- cimgui.c.igText("%d bytes (%d KiB)", pages.page_size, units.toKibiBytes(pages.page_size));
+ _ = cimgui.c.ImGui_TableSetColumnIndex(1);
+ cimgui.c.ImGui_Text("%d bytes (%d KiB)", pages.page_size, units.toKibiBytes(pages.page_size));
}
}
{
- cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
+ cimgui.c.ImGui_TableNextRow();
{
- _ = cimgui.c.igTableSetColumnIndex(0);
- cimgui.c.igText("Memory Limit");
+ _ = cimgui.c.ImGui_TableSetColumnIndex(0);
+ cimgui.c.ImGui_Text("Memory Limit");
}
{
- _ = cimgui.c.igTableSetColumnIndex(1);
- cimgui.c.igText("%d bytes (%d KiB)", pages.maxSize(), units.toKibiBytes(pages.maxSize()));
+ _ = cimgui.c.ImGui_TableSetColumnIndex(1);
+ cimgui.c.ImGui_Text("%d bytes (%d KiB)", pages.maxSize(), units.toKibiBytes(pages.maxSize()));
}
}
{
- cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
+ cimgui.c.ImGui_TableNextRow();
{
- _ = cimgui.c.igTableSetColumnIndex(0);
- cimgui.c.igText("Viewport Location");
+ _ = cimgui.c.ImGui_TableSetColumnIndex(0);
+ cimgui.c.ImGui_Text("Viewport Location");
}
{
- _ = cimgui.c.igTableSetColumnIndex(1);
- cimgui.c.igText("%s", @tagName(pages.viewport).ptr);
+ _ = cimgui.c.ImGui_TableSetColumnIndex(1);
+ cimgui.c.ImGui_Text("%s", @tagName(pages.viewport).ptr);
}
}
} // table
//
- if (cimgui.c.igCollapsingHeader_TreeNodeFlags(
+ if (cimgui.c.ImGui_CollapsingHeader(
"Active Page",
cimgui.c.ImGuiTreeNodeFlags_DefaultOpen,
)) {
@@ -569,28 +555,26 @@ fn renderScreenWindow(self: *Inspector) void {
/// users to toggle them on and off.
fn renderModesWindow(self: *Inspector) void {
// Start our window. If we're collapsed we do nothing.
- defer cimgui.c.igEnd();
- if (!cimgui.c.igBegin(
+ defer cimgui.c.ImGui_End();
+ if (!cimgui.c.ImGui_Begin(
window_modes,
null,
cimgui.c.ImGuiWindowFlags_NoFocusOnAppearing,
)) return;
- _ = cimgui.c.igBeginTable(
+ _ = cimgui.c.ImGui_BeginTable(
"table_modes",
3,
cimgui.c.ImGuiTableFlags_SizingFixedFit |
cimgui.c.ImGuiTableFlags_RowBg,
- .{ .x = 0, .y = 0 },
- 0,
);
- defer cimgui.c.igEndTable();
+ defer cimgui.c.ImGui_EndTable();
{
- _ = cimgui.c.igTableSetupColumn("", cimgui.c.ImGuiTableColumnFlags_NoResize, 0, 0);
- _ = cimgui.c.igTableSetupColumn("Number", cimgui.c.ImGuiTableColumnFlags_PreferSortAscending, 0, 0);
- _ = cimgui.c.igTableSetupColumn("Name", cimgui.c.ImGuiTableColumnFlags_WidthStretch, 0, 0);
- cimgui.c.igTableHeadersRow();
+ cimgui.c.ImGui_TableSetupColumn("", cimgui.c.ImGuiTableColumnFlags_NoResize);
+ cimgui.c.ImGui_TableSetupColumn("Number", cimgui.c.ImGuiTableColumnFlags_PreferSortAscending);
+ cimgui.c.ImGui_TableSetupColumn("Name", cimgui.c.ImGuiTableColumnFlags_WidthStretch);
+ cimgui.c.ImGui_TableHeadersRow();
}
const t = self.surface.renderer_state.terminal;
@@ -598,59 +582,57 @@ fn renderModesWindow(self: *Inspector) void {
@setEvalBranchQuota(6000);
const tag: terminal.modes.ModeTag = @bitCast(@as(terminal.modes.ModeTag.Backing, field.value));
- cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
+ cimgui.c.ImGui_TableNextRow();
{
- _ = cimgui.c.igTableSetColumnIndex(0);
+ _ = cimgui.c.ImGui_TableSetColumnIndex(0);
var value: bool = t.modes.get(@field(terminal.Mode, field.name));
- _ = cimgui.c.igCheckbox("", &value);
+ _ = cimgui.c.ImGui_Checkbox("", &value);
}
{
- _ = cimgui.c.igTableSetColumnIndex(1);
- cimgui.c.igText(
+ _ = cimgui.c.ImGui_TableSetColumnIndex(1);
+ cimgui.c.ImGui_Text(
"%s%d",
if (tag.ansi) "" else "?",
@as(u32, @intCast(tag.value)),
);
}
{
- _ = cimgui.c.igTableSetColumnIndex(2);
+ _ = cimgui.c.ImGui_TableSetColumnIndex(2);
const name = std.fmt.comptimePrint("{s}", .{field.name});
- cimgui.c.igText("%s", name.ptr);
+ cimgui.c.ImGui_Text("%s", name.ptr);
}
}
}
fn renderSizeWindow(self: *Inspector) void {
// Start our window. If we're collapsed we do nothing.
- defer cimgui.c.igEnd();
- if (!cimgui.c.igBegin(
+ defer cimgui.c.ImGui_End();
+ if (!cimgui.c.ImGui_Begin(
window_size,
null,
cimgui.c.ImGuiWindowFlags_NoFocusOnAppearing,
)) return;
- cimgui.c.igSeparatorText("Dimensions");
+ cimgui.c.ImGui_SeparatorText("Dimensions");
{
- _ = cimgui.c.igBeginTable(
+ _ = cimgui.c.ImGui_BeginTable(
"table_size",
2,
cimgui.c.ImGuiTableFlags_None,
- .{ .x = 0, .y = 0 },
- 0,
);
- defer cimgui.c.igEndTable();
+ defer cimgui.c.ImGui_EndTable();
// Screen Size
{
- cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
+ cimgui.c.ImGui_TableNextRow();
{
- _ = cimgui.c.igTableSetColumnIndex(0);
- cimgui.c.igText("Screen Size");
+ _ = cimgui.c.ImGui_TableSetColumnIndex(0);
+ cimgui.c.ImGui_Text("Screen Size");
}
{
- _ = cimgui.c.igTableSetColumnIndex(1);
- cimgui.c.igText(
+ _ = cimgui.c.ImGui_TableSetColumnIndex(1);
+ cimgui.c.ImGui_Text(
"%dpx x %dpx",
self.surface.size.screen.width,
self.surface.size.screen.height,
@@ -660,15 +642,15 @@ fn renderSizeWindow(self: *Inspector) void {
// Grid Size
{
- cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
+ cimgui.c.ImGui_TableNextRow();
{
- _ = cimgui.c.igTableSetColumnIndex(0);
- cimgui.c.igText("Grid Size");
+ _ = cimgui.c.ImGui_TableSetColumnIndex(0);
+ cimgui.c.ImGui_Text("Grid Size");
}
{
- _ = cimgui.c.igTableSetColumnIndex(1);
+ _ = cimgui.c.ImGui_TableSetColumnIndex(1);
const grid_size = self.surface.size.grid();
- cimgui.c.igText(
+ cimgui.c.ImGui_Text(
"%dc x %dr",
grid_size.columns,
grid_size.rows,
@@ -678,14 +660,14 @@ fn renderSizeWindow(self: *Inspector) void {
// Cell Size
{
- cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
+ cimgui.c.ImGui_TableNextRow();
{
- _ = cimgui.c.igTableSetColumnIndex(0);
- cimgui.c.igText("Cell Size");
+ _ = cimgui.c.ImGui_TableSetColumnIndex(0);
+ cimgui.c.ImGui_Text("Cell Size");
}
{
- _ = cimgui.c.igTableSetColumnIndex(1);
- cimgui.c.igText(
+ _ = cimgui.c.ImGui_TableSetColumnIndex(1);
+ cimgui.c.ImGui_Text(
"%dpx x %dpx",
self.surface.size.cell.width,
self.surface.size.cell.height,
@@ -695,14 +677,14 @@ fn renderSizeWindow(self: *Inspector) void {
// Padding
{
- cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
+ cimgui.c.ImGui_TableNextRow();
{
- _ = cimgui.c.igTableSetColumnIndex(0);
- cimgui.c.igText("Window Padding");
+ _ = cimgui.c.ImGui_TableSetColumnIndex(0);
+ cimgui.c.ImGui_Text("Window Padding");
}
{
- _ = cimgui.c.igTableSetColumnIndex(1);
- cimgui.c.igText(
+ _ = cimgui.c.ImGui_TableSetColumnIndex(1);
+ cimgui.c.ImGui_Text(
"T=%d B=%d L=%d R=%d px",
self.surface.size.padding.top,
self.surface.size.padding.bottom,
@@ -713,27 +695,25 @@ fn renderSizeWindow(self: *Inspector) void {
}
}
- cimgui.c.igSeparatorText("Font");
+ cimgui.c.ImGui_SeparatorText("Font");
{
- _ = cimgui.c.igBeginTable(
+ _ = cimgui.c.ImGui_BeginTable(
"table_font",
2,
cimgui.c.ImGuiTableFlags_None,
- .{ .x = 0, .y = 0 },
- 0,
);
- defer cimgui.c.igEndTable();
+ defer cimgui.c.ImGui_EndTable();
{
- cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
+ cimgui.c.ImGui_TableNextRow();
{
- _ = cimgui.c.igTableSetColumnIndex(0);
- cimgui.c.igText("Size (Points)");
+ _ = cimgui.c.ImGui_TableSetColumnIndex(0);
+ cimgui.c.ImGui_Text("Size (Points)");
}
{
- _ = cimgui.c.igTableSetColumnIndex(1);
- cimgui.c.igText(
+ _ = cimgui.c.ImGui_TableSetColumnIndex(1);
+ cimgui.c.ImGui_Text(
"%.2f pt",
self.surface.font_size.points,
);
@@ -741,14 +721,14 @@ fn renderSizeWindow(self: *Inspector) void {
}
{
- cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
+ cimgui.c.ImGui_TableNextRow();
{
- _ = cimgui.c.igTableSetColumnIndex(0);
- cimgui.c.igText("Size (Pixels)");
+ _ = cimgui.c.ImGui_TableSetColumnIndex(0);
+ cimgui.c.ImGui_Text("Size (Pixels)");
}
{
- _ = cimgui.c.igTableSetColumnIndex(1);
- cimgui.c.igText(
+ _ = cimgui.c.ImGui_TableSetColumnIndex(1);
+ cimgui.c.ImGui_Text(
"%.2f px",
self.surface.font_size.pixels(),
);
@@ -756,17 +736,15 @@ fn renderSizeWindow(self: *Inspector) void {
}
}
- cimgui.c.igSeparatorText("Mouse");
+ cimgui.c.ImGui_SeparatorText("Mouse");
{
- _ = cimgui.c.igBeginTable(
+ _ = cimgui.c.ImGui_BeginTable(
"table_mouse",
2,
cimgui.c.ImGuiTableFlags_None,
- .{ .x = 0, .y = 0 },
- 0,
);
- defer cimgui.c.igEndTable();
+ defer cimgui.c.ImGui_EndTable();
const mouse = &self.surface.mouse;
const t = self.surface.renderer_state.terminal;
@@ -781,14 +759,14 @@ fn renderSizeWindow(self: *Inspector) void {
break :pt pt.coord();
};
- cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
+ cimgui.c.ImGui_TableNextRow();
{
- _ = cimgui.c.igTableSetColumnIndex(0);
- cimgui.c.igText("Hover Grid");
+ _ = cimgui.c.ImGui_TableSetColumnIndex(0);
+ cimgui.c.ImGui_Text("Hover Grid");
}
{
- _ = cimgui.c.igTableSetColumnIndex(1);
- cimgui.c.igText(
+ _ = cimgui.c.ImGui_TableSetColumnIndex(1);
+ cimgui.c.ImGui_Text(
"row=%d, col=%d",
hover_point.y,
hover_point.x,
@@ -804,14 +782,14 @@ fn renderSizeWindow(self: *Inspector) void {
},
}).convert(.terminal, self.surface.size).terminal;
- cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
+ cimgui.c.ImGui_TableNextRow();
{
- _ = cimgui.c.igTableSetColumnIndex(0);
- cimgui.c.igText("Hover Point");
+ _ = cimgui.c.ImGui_TableSetColumnIndex(0);
+ cimgui.c.ImGui_Text("Hover Point");
}
{
- _ = cimgui.c.igTableSetColumnIndex(1);
- cimgui.c.igText(
+ _ = cimgui.c.ImGui_TableSetColumnIndex(1);
+ cimgui.c.ImGui_Text(
"(%dpx, %dpx)",
@as(i64, @intFromFloat(coord.x)),
@as(i64, @intFromFloat(coord.y)),
@@ -824,23 +802,23 @@ fn renderSizeWindow(self: *Inspector) void {
} else false;
click: {
- cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
+ cimgui.c.ImGui_TableNextRow();
{
- _ = cimgui.c.igTableSetColumnIndex(0);
- cimgui.c.igText("Click State");
+ _ = cimgui.c.ImGui_TableSetColumnIndex(0);
+ cimgui.c.ImGui_Text("Click State");
}
{
- _ = cimgui.c.igTableSetColumnIndex(1);
+ _ = cimgui.c.ImGui_TableSetColumnIndex(1);
if (!any_click) {
- cimgui.c.igText("none");
+ cimgui.c.ImGui_Text("none");
break :click;
}
for (mouse.click_state, 0..) |state, i| {
if (state != .press) continue;
const button: input.MouseButton = @enumFromInt(i);
- cimgui.c.igSameLine(0, 0);
- cimgui.c.igText("%s", (switch (button) {
+ cimgui.c.ImGui_SameLine();
+ cimgui.c.ImGui_Text("%s", (switch (button) {
.unknown => "?",
.left => "L",
.middle => "M",
@@ -868,14 +846,14 @@ fn renderSizeWindow(self: *Inspector) void {
break :pt pt.coord();
};
- cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
+ cimgui.c.ImGui_TableNextRow();
{
- _ = cimgui.c.igTableSetColumnIndex(0);
- cimgui.c.igText("Click Grid");
+ _ = cimgui.c.ImGui_TableSetColumnIndex(0);
+ cimgui.c.ImGui_Text("Click Grid");
}
{
- _ = cimgui.c.igTableSetColumnIndex(1);
- cimgui.c.igText(
+ _ = cimgui.c.ImGui_TableSetColumnIndex(1);
+ cimgui.c.ImGui_Text(
"row=%d, col=%d",
left_click_point.y,
left_click_point.x,
@@ -884,14 +862,14 @@ fn renderSizeWindow(self: *Inspector) void {
}
{
- cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
+ cimgui.c.ImGui_TableNextRow();
{
- _ = cimgui.c.igTableSetColumnIndex(0);
- cimgui.c.igText("Click Point");
+ _ = cimgui.c.ImGui_TableSetColumnIndex(0);
+ cimgui.c.ImGui_Text("Click Point");
}
{
- _ = cimgui.c.igTableSetColumnIndex(1);
- cimgui.c.igText(
+ _ = cimgui.c.ImGui_TableSetColumnIndex(1);
+ cimgui.c.ImGui_Text(
"(%dpx, %dpx)",
@as(u32, @intFromFloat(mouse.left_click_xpos)),
@as(u32, @intFromFloat(mouse.left_click_ypos)),
@@ -903,8 +881,8 @@ fn renderSizeWindow(self: *Inspector) void {
fn renderCellWindow(self: *Inspector) void {
// Start our window. If we're collapsed we do nothing.
- defer cimgui.c.igEnd();
- if (!cimgui.c.igBegin(
+ defer cimgui.c.ImGui_End();
+ if (!cimgui.c.ImGui_Begin(
window_cell,
null,
cimgui.c.ImGuiWindowFlags_NoFocusOnAppearing,
@@ -913,45 +891,45 @@ fn renderCellWindow(self: *Inspector) void {
// Our popup for the picker
const popup_picker = "Cell Picker";
- if (cimgui.c.igButton("Picker", .{ .x = 0, .y = 0 })) {
+ if (cimgui.c.ImGui_Button("Picker")) {
// Request a cell
self.cell.request();
- cimgui.c.igOpenPopup_Str(
+ cimgui.c.ImGui_OpenPopup(
popup_picker,
cimgui.c.ImGuiPopupFlags_None,
);
}
- if (cimgui.c.igBeginPopupModal(
+ if (cimgui.c.ImGui_BeginPopupModal(
popup_picker,
null,
cimgui.c.ImGuiWindowFlags_AlwaysAutoResize,
)) popup: {
- defer cimgui.c.igEndPopup();
+ defer cimgui.c.ImGui_EndPopup();
// Once we select a cell, close this popup.
if (self.cell == .selected) {
- cimgui.c.igCloseCurrentPopup();
+ cimgui.c.ImGui_CloseCurrentPopup();
break :popup;
}
- cimgui.c.igText(
+ cimgui.c.ImGui_Text(
"Click on a cell in the terminal to inspect it.\n" ++
"The click will be intercepted by the picker, \n" ++
"so it won't be sent to the terminal.",
);
- cimgui.c.igSeparator();
+ cimgui.c.ImGui_Separator();
- if (cimgui.c.igButton("Cancel", .{ .x = 0, .y = 0 })) {
- cimgui.c.igCloseCurrentPopup();
+ if (cimgui.c.ImGui_Button("Cancel")) {
+ cimgui.c.ImGui_CloseCurrentPopup();
}
} // cell pick popup
- cimgui.c.igSeparator();
+ cimgui.c.ImGui_Separator();
if (self.cell != .selected) {
- cimgui.c.igText("No cell selected.");
+ cimgui.c.ImGui_Text("No cell selected.");
return;
}
@@ -965,8 +943,8 @@ fn renderCellWindow(self: *Inspector) void {
fn renderKeyboardWindow(self: *Inspector) void {
// Start our window. If we're collapsed we do nothing.
- defer cimgui.c.igEnd();
- if (!cimgui.c.igBegin(
+ defer cimgui.c.ImGui_End();
+ if (!cimgui.c.ImGui_Begin(
window_keyboard,
null,
cimgui.c.ImGuiWindowFlags_NoFocusOnAppearing,
@@ -974,47 +952,44 @@ fn renderKeyboardWindow(self: *Inspector) void {
list: {
if (self.key_events.empty()) {
- cimgui.c.igText("No recorded key events. Press a key with the " ++
+ cimgui.c.ImGui_Text("No recorded key events. Press a key with the " ++
"terminal focused to record it.");
break :list;
}
- if (cimgui.c.igButton("Clear", .{ .x = 0, .y = 0 })) {
+ if (cimgui.c.ImGui_Button("Clear")) {
var it = self.key_events.iterator(.forward);
while (it.next()) |v| v.deinit(self.surface.alloc);
self.key_events.clear();
self.vt_stream.handler.current_seq = 1;
}
- cimgui.c.igSeparator();
+ cimgui.c.ImGui_Separator();
- _ = cimgui.c.igBeginTable(
+ _ = cimgui.c.ImGui_BeginTable(
"table_key_events",
1,
//cimgui.c.ImGuiTableFlags_ScrollY |
cimgui.c.ImGuiTableFlags_RowBg |
cimgui.c.ImGuiTableFlags_Borders,
- .{ .x = 0, .y = 0 },
- 0,
);
- defer cimgui.c.igEndTable();
+ defer cimgui.c.ImGui_EndTable();
var it = self.key_events.iterator(.reverse);
while (it.next()) |ev| {
// Need to push an ID so that our selectable is unique.
- cimgui.c.igPushID_Ptr(ev);
- defer cimgui.c.igPopID();
+ cimgui.c.ImGui_PushIDPtr(ev);
+ defer cimgui.c.ImGui_PopID();
- cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
- _ = cimgui.c.igTableSetColumnIndex(0);
+ cimgui.c.ImGui_TableNextRow();
+ _ = cimgui.c.ImGui_TableSetColumnIndex(0);
var buf: [1024]u8 = undefined;
const label = ev.label(&buf) catch "Key Event";
- _ = cimgui.c.igSelectable_BoolPtr(
+ _ = cimgui.c.ImGui_SelectableBoolPtr(
label.ptr,
&ev.imgui_state.selected,
cimgui.c.ImGuiSelectableFlags_None,
- .{ .x = 0, .y = 0 },
);
if (!ev.imgui_state.selected) continue;
@@ -1034,7 +1009,7 @@ fn getKeyAction(self: *Inspector) KeyAction {
};
inline for (keys) |k| {
- if (cimgui.c.igIsKeyPressed_Bool(k.key, false)) {
+ if (cimgui.c.ImGui_IsKeyPressed(k.key)) {
return k.action;
}
}
@@ -1043,8 +1018,8 @@ fn getKeyAction(self: *Inspector) KeyAction {
fn renderTermioWindow(self: *Inspector) void {
// Start our window. If we're collapsed we do nothing.
- defer cimgui.c.igEnd();
- if (!cimgui.c.igBegin(
+ defer cimgui.c.ImGui_End();
+ if (!cimgui.c.ImGui_Begin(
window_termio,
null,
cimgui.c.ImGuiWindowFlags_NoFocusOnAppearing,
@@ -1057,21 +1032,21 @@ fn renderTermioWindow(self: *Inspector) void {
"Pause##pause_play"
else
"Resume##pause_play";
- if (cimgui.c.igButton(pause_play.ptr, .{ .x = 0, .y = 0 })) {
+ if (cimgui.c.ImGui_Button(pause_play.ptr)) {
self.vt_stream.handler.active = !self.vt_stream.handler.active;
}
- cimgui.c.igSameLine(0, cimgui.c.igGetStyle().*.ItemInnerSpacing.x);
- if (cimgui.c.igButton("Filter", .{ .x = 0, .y = 0 })) {
- cimgui.c.igOpenPopup_Str(
+ cimgui.c.ImGui_SameLineEx(0, cimgui.c.ImGui_GetStyle().*.ItemInnerSpacing.x);
+ if (cimgui.c.ImGui_Button("Filter")) {
+ cimgui.c.ImGui_OpenPopup(
popup_filter,
cimgui.c.ImGuiPopupFlags_None,
);
}
if (!self.vt_events.empty()) {
- cimgui.c.igSameLine(0, cimgui.c.igGetStyle().*.ItemInnerSpacing.x);
- if (cimgui.c.igButton("Clear", .{ .x = 0, .y = 0 })) {
+ cimgui.c.ImGui_SameLineEx(0, cimgui.c.ImGui_GetStyle().*.ItemInnerSpacing.x);
+ if (cimgui.c.ImGui_Button("Clear")) {
var it = self.vt_events.iterator(.forward);
while (it.next()) |v| v.deinit(self.surface.alloc);
self.vt_events.clear();
@@ -1081,44 +1056,36 @@ fn renderTermioWindow(self: *Inspector) void {
}
}
- cimgui.c.igSeparator();
+ cimgui.c.ImGui_Separator();
if (self.vt_events.empty()) {
- cimgui.c.igText("Waiting for events...");
+ cimgui.c.ImGui_Text("Waiting for events...");
break :list;
}
- _ = cimgui.c.igBeginTable(
+ _ = cimgui.c.ImGui_BeginTable(
"table_vt_events",
3,
cimgui.c.ImGuiTableFlags_RowBg |
cimgui.c.ImGuiTableFlags_Borders,
- .{ .x = 0, .y = 0 },
- 0,
);
- defer cimgui.c.igEndTable();
+ defer cimgui.c.ImGui_EndTable();
- cimgui.c.igTableSetupColumn(
+ cimgui.c.ImGui_TableSetupColumn(
"Seq",
cimgui.c.ImGuiTableColumnFlags_WidthFixed,
- 0,
- 0,
);
- cimgui.c.igTableSetupColumn(
+ cimgui.c.ImGui_TableSetupColumn(
"Kind",
cimgui.c.ImGuiTableColumnFlags_WidthFixed,
- 0,
- 0,
);
- cimgui.c.igTableSetupColumn(
+ cimgui.c.ImGui_TableSetupColumn(
"Description",
cimgui.c.ImGuiTableColumnFlags_WidthStretch,
- 0,
- 0,
);
// Handle keyboard navigation when window is focused
- if (cimgui.c.igIsWindowFocused(cimgui.c.ImGuiFocusedFlags_RootAndChildWindows)) {
+ if (cimgui.c.ImGui_IsWindowFocused(cimgui.c.ImGuiFocusedFlags_RootAndChildWindows)) {
const key_pressed = self.getKeyAction();
switch (key_pressed) {
@@ -1174,11 +1141,11 @@ fn renderTermioWindow(self: *Inspector) void {
var it = self.vt_events.iterator(.reverse);
while (it.next()) |ev| {
// Need to push an ID so that our selectable is unique.
- cimgui.c.igPushID_Ptr(ev);
- defer cimgui.c.igPopID();
+ cimgui.c.ImGui_PushIDPtr(ev);
+ defer cimgui.c.ImGui_PopID();
- cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
- _ = cimgui.c.igTableNextColumn();
+ cimgui.c.ImGui_TableNextRow();
+ _ = cimgui.c.ImGui_TableNextColumn();
// Store the previous selection state to detect changes
const was_selected = ev.imgui_selected;
@@ -1189,11 +1156,10 @@ fn renderTermioWindow(self: *Inspector) void {
}
// Handle selectable widget
- if (cimgui.c.igSelectable_BoolPtr(
+ if (cimgui.c.ImGui_SelectableBoolPtr(
"##select",
&ev.imgui_selected,
cimgui.c.ImGuiSelectableFlags_SpanAllColumns,
- .{ .x = 0, .y = 0 },
)) {
// If selection state changed, update keyboard navigation state
if (ev.imgui_selected != was_selected) {
@@ -1205,40 +1171,38 @@ fn renderTermioWindow(self: *Inspector) void {
}
}
- cimgui.c.igSameLine(0, 0);
- cimgui.c.igText("%d", ev.seq);
- _ = cimgui.c.igTableNextColumn();
- cimgui.c.igText("%s", @tagName(ev.kind).ptr);
- _ = cimgui.c.igTableNextColumn();
- cimgui.c.igText("%s", ev.str.ptr);
+ cimgui.c.ImGui_SameLine();
+ cimgui.c.ImGui_Text("%d", ev.seq);
+ _ = cimgui.c.ImGui_TableNextColumn();
+ cimgui.c.ImGui_Text("%s", @tagName(ev.kind).ptr);
+ _ = cimgui.c.ImGui_TableNextColumn();
+ cimgui.c.ImGui_Text("%s", ev.str.ptr);
// If the event is selected, we render info about it. For now
- // we put this in the last column because thats the widest and
+ // we put this in the last column because that's the widest and
// imgui has no way to make a column span.
if (ev.imgui_selected) {
{
- _ = cimgui.c.igBeginTable(
+ _ = cimgui.c.ImGui_BeginTable(
"details",
2,
cimgui.c.ImGuiTableFlags_None,
- .{ .x = 0, .y = 0 },
- 0,
);
- defer cimgui.c.igEndTable();
+ defer cimgui.c.ImGui_EndTable();
inspector.cursor.renderInTable(
self.surface.renderer_state.terminal,
&ev.cursor,
);
{
- cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
+ cimgui.c.ImGui_TableNextRow();
{
- _ = cimgui.c.igTableSetColumnIndex(0);
- cimgui.c.igText("Scroll Region");
+ _ = cimgui.c.ImGui_TableSetColumnIndex(0);
+ cimgui.c.ImGui_Text("Scroll Region");
}
{
- _ = cimgui.c.igTableSetColumnIndex(1);
- cimgui.c.igText(
+ _ = cimgui.c.ImGui_TableSetColumnIndex(1);
+ cimgui.c.ImGui_Text(
"T=%d B=%d L=%d R=%d",
ev.scrolling_region.top,
ev.scrolling_region.bottom,
@@ -1253,51 +1217,49 @@ fn renderTermioWindow(self: *Inspector) void {
var buf: [256]u8 = undefined;
const key = std.fmt.bufPrintZ(&buf, "{s}", .{entry.key_ptr.*}) catch
"";
- cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
- _ = cimgui.c.igTableNextColumn();
- cimgui.c.igText("%s", key.ptr);
- _ = cimgui.c.igTableNextColumn();
- cimgui.c.igText("%s", entry.value_ptr.ptr);
+ cimgui.c.ImGui_TableNextRow();
+ _ = cimgui.c.ImGui_TableNextColumn();
+ cimgui.c.ImGui_Text("%s", key.ptr);
+ _ = cimgui.c.ImGui_TableNextColumn();
+ cimgui.c.ImGui_Text("%s", entry.value_ptr.ptr);
}
}
// If this is the selected event and scrolling is needed, scroll to it
if (self.need_scroll_to_selected and self.is_keyboard_selection) {
- cimgui.c.igSetScrollHereY(0.5);
+ cimgui.c.ImGui_SetScrollHereY(0.5);
self.need_scroll_to_selected = false;
}
}
}
} // table
- if (cimgui.c.igBeginPopupModal(
+ if (cimgui.c.ImGui_BeginPopupModal(
popup_filter,
null,
cimgui.c.ImGuiWindowFlags_AlwaysAutoResize,
)) {
- defer cimgui.c.igEndPopup();
+ defer cimgui.c.ImGui_EndPopup();
- cimgui.c.igText("Changed filter settings will only affect future events.");
+ cimgui.c.ImGui_Text("Changed filter settings will only affect future events.");
- cimgui.c.igSeparator();
+ cimgui.c.ImGui_Separator();
{
- _ = cimgui.c.igBeginTable(
+ _ = cimgui.c.ImGui_BeginTable(
"table_filter_kind",
3,
cimgui.c.ImGuiTableFlags_None,
- .{ .x = 0, .y = 0 },
- 0,
);
- defer cimgui.c.igEndTable();
+ defer cimgui.c.ImGui_EndTable();
inline for (@typeInfo(terminal.Parser.Action.Tag).@"enum".fields) |field| {
const tag = @field(terminal.Parser.Action.Tag, field.name);
if (tag == .apc_put or tag == .dcs_put) continue;
- _ = cimgui.c.igTableNextColumn();
+ _ = cimgui.c.ImGui_TableNextColumn();
var value = !self.vt_stream.handler.filter_exclude.contains(tag);
- if (cimgui.c.igCheckbox(@tagName(tag).ptr, &value)) {
+ if (cimgui.c.ImGui_Checkbox(@tagName(tag).ptr, &value)) {
if (value) {
self.vt_stream.handler.filter_exclude.remove(tag);
} else {
@@ -1307,22 +1269,22 @@ fn renderTermioWindow(self: *Inspector) void {
}
} // Filter kind table
- cimgui.c.igSeparator();
+ cimgui.c.ImGui_Separator();
- cimgui.c.igText(
+ cimgui.c.ImGui_Text(
"Filter by string. Empty displays all, \"abc\" finds lines\n" ++
"containing \"abc\", \"abc,xyz\" finds lines containing \"abc\"\n" ++
"or \"xyz\", \"-abc\" excludes lines containing \"abc\".",
);
_ = cimgui.c.ImGuiTextFilter_Draw(
- self.vt_stream.handler.filter_text,
+ &self.vt_stream.handler.filter_text,
"##filter_text",
0,
);
- cimgui.c.igSeparator();
- if (cimgui.c.igButton("Close", .{ .x = 0, .y = 0 })) {
- cimgui.c.igCloseCurrentPopup();
+ cimgui.c.ImGui_Separator();
+ if (cimgui.c.ImGui_Button("Close")) {
+ cimgui.c.ImGui_CloseCurrentPopup();
}
} // filter popup
}
diff --git a/src/inspector/cell.zig b/src/inspector/cell.zig
index 2f72556bd..540e044fd 100644
--- a/src/inspector/cell.zig
+++ b/src/inspector/cell.zig
@@ -1,7 +1,7 @@
const std = @import("std");
const assert = @import("../quirks.zig").inlineAssert;
const Allocator = std.mem.Allocator;
-const cimgui = @import("cimgui");
+const cimgui = @import("dcimgui");
const terminal = @import("../terminal/main.zig");
/// A cell being inspected. This duplicates much of the data in
@@ -55,24 +55,22 @@ pub const Cell = struct {
y: usize,
) void {
// We have a selected cell, show information about it.
- _ = cimgui.c.igBeginTable(
+ _ = cimgui.c.ImGui_BeginTable(
"table_cursor",
2,
cimgui.c.ImGuiTableFlags_None,
- .{ .x = 0, .y = 0 },
- 0,
);
- defer cimgui.c.igEndTable();
+ defer cimgui.c.ImGui_EndTable();
{
- cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
+ cimgui.c.ImGui_TableNextRow();
{
- _ = cimgui.c.igTableSetColumnIndex(0);
- cimgui.c.igText("Grid Position");
+ _ = cimgui.c.ImGui_TableSetColumnIndex(0);
+ cimgui.c.ImGui_Text("Grid Position");
}
{
- _ = cimgui.c.igTableSetColumnIndex(1);
- cimgui.c.igText("row=%d col=%d", y, x);
+ _ = cimgui.c.ImGui_TableSetColumnIndex(1);
+ cimgui.c.ImGui_Text("row=%d col=%d", y, x);
}
}
@@ -82,18 +80,18 @@ pub const Cell = struct {
// the single glyph in an image view so it looks _identical_ to the
// terminal.
codepoint: {
- cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
+ cimgui.c.ImGui_TableNextRow();
{
- _ = cimgui.c.igTableSetColumnIndex(0);
- cimgui.c.igText("Codepoints");
+ _ = cimgui.c.ImGui_TableSetColumnIndex(0);
+ cimgui.c.ImGui_Text("Codepoints");
}
{
- _ = cimgui.c.igTableSetColumnIndex(1);
- if (cimgui.c.igBeginListBox("##codepoints", .{ .x = 0, .y = 0 })) {
- defer cimgui.c.igEndListBox();
+ _ = cimgui.c.ImGui_TableSetColumnIndex(1);
+ if (cimgui.c.ImGui_BeginListBox("##codepoints", .{ .x = 0, .y = 0 })) {
+ defer cimgui.c.ImGui_EndListBox();
if (self.codepoint == 0) {
- _ = cimgui.c.igSelectable_Bool("(empty)", false, 0, .{});
+ _ = cimgui.c.ImGui_SelectableEx("(empty)", false, 0, .{});
break :codepoint;
}
@@ -102,42 +100,42 @@ pub const Cell = struct {
{
const key = std.fmt.bufPrintZ(&buf, "U+{X}", .{self.codepoint}) catch
"";
- _ = cimgui.c.igSelectable_Bool(key.ptr, false, 0, .{});
+ _ = cimgui.c.ImGui_SelectableEx(key.ptr, false, 0, .{});
}
// All extras
for (self.cps) |cp| {
const key = std.fmt.bufPrintZ(&buf, "U+{X}", .{cp}) catch
"";
- _ = cimgui.c.igSelectable_Bool(key.ptr, false, 0, .{});
+ _ = cimgui.c.ImGui_SelectableEx(key.ptr, false, 0, .{});
}
}
}
}
// Character width property
- cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
- _ = cimgui.c.igTableSetColumnIndex(0);
- cimgui.c.igText("Width Property");
- _ = cimgui.c.igTableSetColumnIndex(1);
- cimgui.c.igText(@tagName(self.wide));
+ cimgui.c.ImGui_TableNextRow();
+ _ = cimgui.c.ImGui_TableSetColumnIndex(0);
+ cimgui.c.ImGui_Text("Width Property");
+ _ = cimgui.c.ImGui_TableSetColumnIndex(1);
+ cimgui.c.ImGui_Text(@tagName(self.wide));
// If we have a color then we show the color
- cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
- _ = cimgui.c.igTableSetColumnIndex(0);
- cimgui.c.igText("Foreground Color");
- _ = cimgui.c.igTableSetColumnIndex(1);
+ cimgui.c.ImGui_TableNextRow();
+ _ = cimgui.c.ImGui_TableSetColumnIndex(0);
+ cimgui.c.ImGui_Text("Foreground Color");
+ _ = cimgui.c.ImGui_TableSetColumnIndex(1);
switch (self.style.fg_color) {
- .none => cimgui.c.igText("default"),
+ .none => cimgui.c.ImGui_Text("default"),
.palette => |idx| {
const rgb = t.colors.palette.current[idx];
- cimgui.c.igValue_Int("Palette", idx);
+ cimgui.c.ImGui_Text("Palette %d", idx);
var color: [3]f32 = .{
@as(f32, @floatFromInt(rgb.r)) / 255,
@as(f32, @floatFromInt(rgb.g)) / 255,
@as(f32, @floatFromInt(rgb.b)) / 255,
};
- _ = cimgui.c.igColorEdit3(
+ _ = cimgui.c.ImGui_ColorEdit3(
"color_fg",
&color,
cimgui.c.ImGuiColorEditFlags_DisplayHex |
@@ -152,7 +150,7 @@ pub const Cell = struct {
@as(f32, @floatFromInt(rgb.g)) / 255,
@as(f32, @floatFromInt(rgb.b)) / 255,
};
- _ = cimgui.c.igColorEdit3(
+ _ = cimgui.c.ImGui_ColorEdit3(
"color_fg",
&color,
cimgui.c.ImGuiColorEditFlags_DisplayHex |
@@ -162,21 +160,21 @@ pub const Cell = struct {
},
}
- cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
- _ = cimgui.c.igTableSetColumnIndex(0);
- cimgui.c.igText("Background Color");
- _ = cimgui.c.igTableSetColumnIndex(1);
+ cimgui.c.ImGui_TableNextRow();
+ _ = cimgui.c.ImGui_TableSetColumnIndex(0);
+ cimgui.c.ImGui_Text("Background Color");
+ _ = cimgui.c.ImGui_TableSetColumnIndex(1);
switch (self.style.bg_color) {
- .none => cimgui.c.igText("default"),
+ .none => cimgui.c.ImGui_Text("default"),
.palette => |idx| {
const rgb = t.colors.palette.current[idx];
- cimgui.c.igValue_Int("Palette", idx);
+ cimgui.c.ImGui_Text("Palette %d", idx);
var color: [3]f32 = .{
@as(f32, @floatFromInt(rgb.r)) / 255,
@as(f32, @floatFromInt(rgb.g)) / 255,
@as(f32, @floatFromInt(rgb.b)) / 255,
};
- _ = cimgui.c.igColorEdit3(
+ _ = cimgui.c.ImGui_ColorEdit3(
"color_bg",
&color,
cimgui.c.ImGuiColorEditFlags_DisplayHex |
@@ -191,7 +189,7 @@ pub const Cell = struct {
@as(f32, @floatFromInt(rgb.g)) / 255,
@as(f32, @floatFromInt(rgb.b)) / 255,
};
- _ = cimgui.c.igColorEdit3(
+ _ = cimgui.c.ImGui_ColorEdit3(
"color_bg",
&color,
cimgui.c.ImGuiColorEditFlags_DisplayHex |
@@ -209,17 +207,17 @@ pub const Cell = struct {
inline for (styles) |style| style: {
if (!@field(self.style.flags, style)) break :style;
- cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
+ cimgui.c.ImGui_TableNextRow();
{
- _ = cimgui.c.igTableSetColumnIndex(0);
- cimgui.c.igText(style.ptr);
+ _ = cimgui.c.ImGui_TableSetColumnIndex(0);
+ cimgui.c.ImGui_Text(style.ptr);
}
{
- _ = cimgui.c.igTableSetColumnIndex(1);
- cimgui.c.igText("true");
+ _ = cimgui.c.ImGui_TableSetColumnIndex(1);
+ cimgui.c.ImGui_Text("true");
}
}
- cimgui.c.igTextDisabled("(Any styles not shown are not currently set)");
+ cimgui.c.ImGui_TextDisabled("(Any styles not shown are not currently set)");
}
};
diff --git a/src/inspector/cursor.zig b/src/inspector/cursor.zig
index 756898252..4f8bfb2e0 100644
--- a/src/inspector/cursor.zig
+++ b/src/inspector/cursor.zig
@@ -1,4 +1,4 @@
-const cimgui = @import("cimgui");
+const cimgui = @import("dcimgui");
const terminal = @import("../terminal/main.zig");
/// Render cursor information with a table already open.
@@ -7,57 +7,57 @@ pub fn renderInTable(
cursor: *const terminal.Screen.Cursor,
) void {
{
- cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
+ cimgui.c.ImGui_TableNextRow();
{
- _ = cimgui.c.igTableSetColumnIndex(0);
- cimgui.c.igText("Position (x, y)");
+ _ = cimgui.c.ImGui_TableSetColumnIndex(0);
+ cimgui.c.ImGui_Text("Position (x, y)");
}
{
- _ = cimgui.c.igTableSetColumnIndex(1);
- cimgui.c.igText("(%d, %d)", cursor.x, cursor.y);
+ _ = cimgui.c.ImGui_TableSetColumnIndex(1);
+ cimgui.c.ImGui_Text("(%d, %d)", cursor.x, cursor.y);
}
}
{
- cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
+ cimgui.c.ImGui_TableNextRow();
{
- _ = cimgui.c.igTableSetColumnIndex(0);
- cimgui.c.igText("Style");
+ _ = cimgui.c.ImGui_TableSetColumnIndex(0);
+ cimgui.c.ImGui_Text("Style");
}
{
- _ = cimgui.c.igTableSetColumnIndex(1);
- cimgui.c.igText("%s", @tagName(cursor.cursor_style).ptr);
+ _ = cimgui.c.ImGui_TableSetColumnIndex(1);
+ cimgui.c.ImGui_Text("%s", @tagName(cursor.cursor_style).ptr);
}
}
if (cursor.pending_wrap) {
- cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
+ cimgui.c.ImGui_TableNextRow();
{
- _ = cimgui.c.igTableSetColumnIndex(0);
- cimgui.c.igText("Pending Wrap");
+ _ = cimgui.c.ImGui_TableSetColumnIndex(0);
+ cimgui.c.ImGui_Text("Pending Wrap");
}
{
- _ = cimgui.c.igTableSetColumnIndex(1);
- cimgui.c.igText("%s", if (cursor.pending_wrap) "true".ptr else "false".ptr);
+ _ = cimgui.c.ImGui_TableSetColumnIndex(1);
+ cimgui.c.ImGui_Text("%s", if (cursor.pending_wrap) "true".ptr else "false".ptr);
}
}
// If we have a color then we show the color
- cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
- _ = cimgui.c.igTableSetColumnIndex(0);
- cimgui.c.igText("Foreground Color");
- _ = cimgui.c.igTableSetColumnIndex(1);
+ cimgui.c.ImGui_TableNextRow();
+ _ = cimgui.c.ImGui_TableSetColumnIndex(0);
+ cimgui.c.ImGui_Text("Foreground Color");
+ _ = cimgui.c.ImGui_TableSetColumnIndex(1);
switch (cursor.style.fg_color) {
- .none => cimgui.c.igText("default"),
+ .none => cimgui.c.ImGui_Text("default"),
.palette => |idx| {
const rgb = t.colors.palette.current[idx];
- cimgui.c.igValue_Int("Palette", idx);
+ cimgui.c.ImGui_Text("Palette %d", idx);
var color: [3]f32 = .{
@as(f32, @floatFromInt(rgb.r)) / 255,
@as(f32, @floatFromInt(rgb.g)) / 255,
@as(f32, @floatFromInt(rgb.b)) / 255,
};
- _ = cimgui.c.igColorEdit3(
+ _ = cimgui.c.ImGui_ColorEdit3(
"color_fg",
&color,
cimgui.c.ImGuiColorEditFlags_DisplayHex |
@@ -72,7 +72,7 @@ pub fn renderInTable(
@as(f32, @floatFromInt(rgb.g)) / 255,
@as(f32, @floatFromInt(rgb.b)) / 255,
};
- _ = cimgui.c.igColorEdit3(
+ _ = cimgui.c.ImGui_ColorEdit3(
"color_fg",
&color,
cimgui.c.ImGuiColorEditFlags_DisplayHex |
@@ -82,21 +82,21 @@ pub fn renderInTable(
},
}
- cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
- _ = cimgui.c.igTableSetColumnIndex(0);
- cimgui.c.igText("Background Color");
- _ = cimgui.c.igTableSetColumnIndex(1);
+ cimgui.c.ImGui_TableNextRow();
+ _ = cimgui.c.ImGui_TableSetColumnIndex(0);
+ cimgui.c.ImGui_Text("Background Color");
+ _ = cimgui.c.ImGui_TableSetColumnIndex(1);
switch (cursor.style.bg_color) {
- .none => cimgui.c.igText("default"),
+ .none => cimgui.c.ImGui_Text("default"),
.palette => |idx| {
const rgb = t.colors.palette.current[idx];
- cimgui.c.igValue_Int("Palette", idx);
+ cimgui.c.ImGui_Text("Palette %d", idx);
var color: [3]f32 = .{
@as(f32, @floatFromInt(rgb.r)) / 255,
@as(f32, @floatFromInt(rgb.g)) / 255,
@as(f32, @floatFromInt(rgb.b)) / 255,
};
- _ = cimgui.c.igColorEdit3(
+ _ = cimgui.c.ImGui_ColorEdit3(
"color_bg",
&color,
cimgui.c.ImGuiColorEditFlags_DisplayHex |
@@ -111,7 +111,7 @@ pub fn renderInTable(
@as(f32, @floatFromInt(rgb.g)) / 255,
@as(f32, @floatFromInt(rgb.b)) / 255,
};
- _ = cimgui.c.igColorEdit3(
+ _ = cimgui.c.ImGui_ColorEdit3(
"color_bg",
&color,
cimgui.c.ImGuiColorEditFlags_DisplayHex |
@@ -129,14 +129,14 @@ pub fn renderInTable(
inline for (styles) |style| style: {
if (!@field(cursor.style.flags, style)) break :style;
- cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
+ cimgui.c.ImGui_TableNextRow();
{
- _ = cimgui.c.igTableSetColumnIndex(0);
- cimgui.c.igText(style.ptr);
+ _ = cimgui.c.ImGui_TableSetColumnIndex(0);
+ cimgui.c.ImGui_Text(style.ptr);
}
{
- _ = cimgui.c.igTableSetColumnIndex(1);
- cimgui.c.igText("true");
+ _ = cimgui.c.ImGui_TableSetColumnIndex(1);
+ cimgui.c.ImGui_Text("true");
}
}
}
diff --git a/src/inspector/key.zig b/src/inspector/key.zig
index dbccb47a8..12d91a107 100644
--- a/src/inspector/key.zig
+++ b/src/inspector/key.zig
@@ -2,7 +2,7 @@ const std = @import("std");
const Allocator = std.mem.Allocator;
const input = @import("../input.zig");
const CircBuf = @import("../datastruct/main.zig").CircBuf;
-const cimgui = @import("cimgui");
+const cimgui = @import("dcimgui");
/// Circular buffer of key events.
pub const EventRing = CircBuf(Event, undefined);
@@ -13,7 +13,8 @@ pub const Event = struct {
event: input.KeyEvent,
/// The binding that was triggered as a result of this event.
- binding: ?input.Binding.Action = null,
+ /// Multiple bindings are possible if they are chained.
+ binding: []const input.Binding.Action = &.{},
/// The data sent to the pty as a result of this keyboard event.
/// This is allocated using the inspector allocator.
@@ -32,6 +33,7 @@ pub const Event = struct {
}
pub fn deinit(self: *const Event, alloc: Allocator) void {
+ alloc.free(self.binding);
if (self.event.utf8.len > 0) alloc.free(self.event.utf8);
if (self.pty.len > 0) alloc.free(self.pty);
}
@@ -70,82 +72,96 @@ pub const Event = struct {
/// Render this event in the inspector GUI.
pub fn render(self: *const Event) void {
- _ = cimgui.c.igBeginTable(
+ _ = cimgui.c.ImGui_BeginTable(
"##event",
2,
cimgui.c.ImGuiTableFlags_None,
- .{ .x = 0, .y = 0 },
- 0,
);
- defer cimgui.c.igEndTable();
+ defer cimgui.c.ImGui_EndTable();
- if (self.binding) |binding| {
- cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
- _ = cimgui.c.igTableSetColumnIndex(0);
- cimgui.c.igText("Triggered Binding");
- _ = cimgui.c.igTableSetColumnIndex(1);
- cimgui.c.igText("%s", @tagName(binding).ptr);
+ if (self.binding.len > 0) {
+ cimgui.c.ImGui_TableNextRow();
+ _ = cimgui.c.ImGui_TableSetColumnIndex(0);
+ cimgui.c.ImGui_Text("Triggered Binding");
+ _ = cimgui.c.ImGui_TableSetColumnIndex(1);
+
+ const height: f32 = height: {
+ const item_count: f32 = @floatFromInt(@min(self.binding.len, 5));
+ const padding = cimgui.c.ImGui_GetStyle().*.FramePadding.y * 2;
+ break :height cimgui.c.ImGui_GetTextLineHeightWithSpacing() * item_count + padding;
+ };
+ if (cimgui.c.ImGui_BeginListBox("##bindings", .{ .x = 0, .y = height })) {
+ defer cimgui.c.ImGui_EndListBox();
+ for (self.binding) |action| {
+ _ = cimgui.c.ImGui_SelectableEx(
+ @tagName(action).ptr,
+ false,
+ cimgui.c.ImGuiSelectableFlags_None,
+ .{ .x = 0, .y = 0 },
+ );
+ }
+ }
}
pty: {
- cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
- _ = cimgui.c.igTableSetColumnIndex(0);
- cimgui.c.igText("Encoding to Pty");
- _ = cimgui.c.igTableSetColumnIndex(1);
+ cimgui.c.ImGui_TableNextRow();
+ _ = cimgui.c.ImGui_TableSetColumnIndex(0);
+ cimgui.c.ImGui_Text("Encoding to Pty");
+ _ = cimgui.c.ImGui_TableSetColumnIndex(1);
if (self.pty.len == 0) {
- cimgui.c.igTextDisabled("(no data)");
+ cimgui.c.ImGui_TextDisabled("(no data)");
break :pty;
}
self.renderPty() catch {
- cimgui.c.igTextDisabled("(error rendering pty data)");
+ cimgui.c.ImGui_TextDisabled("(error rendering pty data)");
break :pty;
};
}
{
- cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
- _ = cimgui.c.igTableSetColumnIndex(0);
- cimgui.c.igText("Action");
- _ = cimgui.c.igTableSetColumnIndex(1);
- cimgui.c.igText("%s", @tagName(self.event.action).ptr);
+ cimgui.c.ImGui_TableNextRow();
+ _ = cimgui.c.ImGui_TableSetColumnIndex(0);
+ cimgui.c.ImGui_Text("Action");
+ _ = cimgui.c.ImGui_TableSetColumnIndex(1);
+ cimgui.c.ImGui_Text("%s", @tagName(self.event.action).ptr);
}
{
- cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
- _ = cimgui.c.igTableSetColumnIndex(0);
- cimgui.c.igText("Key");
- _ = cimgui.c.igTableSetColumnIndex(1);
- cimgui.c.igText("%s", @tagName(self.event.key).ptr);
+ cimgui.c.ImGui_TableNextRow();
+ _ = cimgui.c.ImGui_TableSetColumnIndex(0);
+ cimgui.c.ImGui_Text("Key");
+ _ = cimgui.c.ImGui_TableSetColumnIndex(1);
+ cimgui.c.ImGui_Text("%s", @tagName(self.event.key).ptr);
}
if (!self.event.mods.empty()) {
- cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
- _ = cimgui.c.igTableSetColumnIndex(0);
- cimgui.c.igText("Mods");
- _ = cimgui.c.igTableSetColumnIndex(1);
- if (self.event.mods.shift) cimgui.c.igText("shift ");
- if (self.event.mods.ctrl) cimgui.c.igText("ctrl ");
- if (self.event.mods.alt) cimgui.c.igText("alt ");
- if (self.event.mods.super) cimgui.c.igText("super ");
+ cimgui.c.ImGui_TableNextRow();
+ _ = cimgui.c.ImGui_TableSetColumnIndex(0);
+ cimgui.c.ImGui_Text("Mods");
+ _ = cimgui.c.ImGui_TableSetColumnIndex(1);
+ if (self.event.mods.shift) cimgui.c.ImGui_Text("shift ");
+ if (self.event.mods.ctrl) cimgui.c.ImGui_Text("ctrl ");
+ if (self.event.mods.alt) cimgui.c.ImGui_Text("alt ");
+ if (self.event.mods.super) cimgui.c.ImGui_Text("super ");
}
if (self.event.composing) {
- cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
- _ = cimgui.c.igTableSetColumnIndex(0);
- cimgui.c.igText("Composing");
- _ = cimgui.c.igTableSetColumnIndex(1);
- cimgui.c.igText("true");
+ cimgui.c.ImGui_TableNextRow();
+ _ = cimgui.c.ImGui_TableSetColumnIndex(0);
+ cimgui.c.ImGui_Text("Composing");
+ _ = cimgui.c.ImGui_TableSetColumnIndex(1);
+ cimgui.c.ImGui_Text("true");
}
utf8: {
- cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
- _ = cimgui.c.igTableSetColumnIndex(0);
- cimgui.c.igText("UTF-8");
- _ = cimgui.c.igTableSetColumnIndex(1);
+ cimgui.c.ImGui_TableNextRow();
+ _ = cimgui.c.ImGui_TableSetColumnIndex(0);
+ cimgui.c.ImGui_Text("UTF-8");
+ _ = cimgui.c.ImGui_TableSetColumnIndex(1);
if (self.event.utf8.len == 0) {
- cimgui.c.igTextDisabled("(empty)");
+ cimgui.c.ImGui_TextDisabled("(empty)");
break :utf8;
}
self.renderUtf8(self.event.utf8) catch {
- cimgui.c.igTextDisabled("(error rendering utf-8)");
+ cimgui.c.ImGui_TextDisabled("(error rendering utf-8)");
break :utf8;
};
}
@@ -169,13 +185,11 @@ pub const Event = struct {
try writer.writeByte(0);
// Render as a textbox
- _ = cimgui.c.igInputText(
+ _ = cimgui.c.ImGui_InputText(
"##utf8",
&buf,
buf_stream.getWritten().len - 1,
cimgui.c.ImGuiInputTextFlags_ReadOnly,
- null,
- null,
);
}
@@ -205,13 +219,11 @@ pub const Event = struct {
try writer.writeByte(0);
// Render as a textbox
- _ = cimgui.c.igInputText(
+ _ = cimgui.c.ImGui_InputText(
"##pty",
&buf,
buf_stream.getWritten().len - 1,
cimgui.c.ImGuiInputTextFlags_ReadOnly,
- null,
- null,
);
}
};
diff --git a/src/inspector/page.zig b/src/inspector/page.zig
index 7da469e21..fd9d3bfb4 100644
--- a/src/inspector/page.zig
+++ b/src/inspector/page.zig
@@ -1,167 +1,161 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
-const cimgui = @import("cimgui");
+const cimgui = @import("dcimgui");
const terminal = @import("../terminal/main.zig");
const units = @import("units.zig");
pub fn render(page: *const terminal.Page) void {
- cimgui.c.igPushID_Ptr(page);
- defer cimgui.c.igPopID();
+ cimgui.c.ImGui_PushIDPtr(page);
+ defer cimgui.c.ImGui_PopID();
- _ = cimgui.c.igBeginTable(
+ _ = cimgui.c.ImGui_BeginTable(
"##page_state",
2,
cimgui.c.ImGuiTableFlags_None,
- .{ .x = 0, .y = 0 },
- 0,
);
- defer cimgui.c.igEndTable();
+ defer cimgui.c.ImGui_EndTable();
{
- cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
+ cimgui.c.ImGui_TableNextRow();
{
- _ = cimgui.c.igTableSetColumnIndex(0);
- cimgui.c.igText("Memory Size");
+ _ = cimgui.c.ImGui_TableSetColumnIndex(0);
+ cimgui.c.ImGui_Text("Memory Size");
}
{
- _ = cimgui.c.igTableSetColumnIndex(1);
- cimgui.c.igText("%d bytes (%d KiB)", page.memory.len, units.toKibiBytes(page.memory.len));
- cimgui.c.igText("%d VM pages", page.memory.len / std.heap.page_size_min);
+ _ = cimgui.c.ImGui_TableSetColumnIndex(1);
+ cimgui.c.ImGui_Text("%d bytes (%d KiB)", page.memory.len, units.toKibiBytes(page.memory.len));
+ cimgui.c.ImGui_Text("%d VM pages", page.memory.len / std.heap.page_size_min);
}
}
{
- cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
+ cimgui.c.ImGui_TableNextRow();
{
- _ = cimgui.c.igTableSetColumnIndex(0);
- cimgui.c.igText("Unique Styles");
+ _ = cimgui.c.ImGui_TableSetColumnIndex(0);
+ cimgui.c.ImGui_Text("Unique Styles");
}
{
- _ = cimgui.c.igTableSetColumnIndex(1);
- cimgui.c.igText("%d", page.styles.count());
+ _ = cimgui.c.ImGui_TableSetColumnIndex(1);
+ cimgui.c.ImGui_Text("%d", page.styles.count());
}
}
{
- cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
+ cimgui.c.ImGui_TableNextRow();
{
- _ = cimgui.c.igTableSetColumnIndex(0);
- cimgui.c.igText("Grapheme Entries");
+ _ = cimgui.c.ImGui_TableSetColumnIndex(0);
+ cimgui.c.ImGui_Text("Grapheme Entries");
}
{
- _ = cimgui.c.igTableSetColumnIndex(1);
- cimgui.c.igText("%d", page.graphemeCount());
+ _ = cimgui.c.ImGui_TableSetColumnIndex(1);
+ cimgui.c.ImGui_Text("%d", page.graphemeCount());
}
}
{
- cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
+ cimgui.c.ImGui_TableNextRow();
{
- _ = cimgui.c.igTableSetColumnIndex(0);
- cimgui.c.igText("Capacity");
+ _ = cimgui.c.ImGui_TableSetColumnIndex(0);
+ cimgui.c.ImGui_Text("Capacity");
}
{
- _ = cimgui.c.igTableSetColumnIndex(1);
- _ = cimgui.c.igBeginTable(
+ _ = cimgui.c.ImGui_TableSetColumnIndex(1);
+ _ = cimgui.c.ImGui_BeginTable(
"##capacity",
2,
cimgui.c.ImGuiTableFlags_None,
- .{ .x = 0, .y = 0 },
- 0,
);
- defer cimgui.c.igEndTable();
+ defer cimgui.c.ImGui_EndTable();
const cap = page.capacity;
{
- cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
+ cimgui.c.ImGui_TableNextRow();
{
- _ = cimgui.c.igTableSetColumnIndex(0);
- cimgui.c.igText("Columns");
+ _ = cimgui.c.ImGui_TableSetColumnIndex(0);
+ cimgui.c.ImGui_Text("Columns");
}
{
- _ = cimgui.c.igTableSetColumnIndex(1);
- cimgui.c.igText("%d", @as(u32, @intCast(cap.cols)));
+ _ = cimgui.c.ImGui_TableSetColumnIndex(1);
+ cimgui.c.ImGui_Text("%d", @as(u32, @intCast(cap.cols)));
}
}
{
- cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
+ cimgui.c.ImGui_TableNextRow();
{
- _ = cimgui.c.igTableSetColumnIndex(0);
- cimgui.c.igText("Rows");
+ _ = cimgui.c.ImGui_TableSetColumnIndex(0);
+ cimgui.c.ImGui_Text("Rows");
}
{
- _ = cimgui.c.igTableSetColumnIndex(1);
- cimgui.c.igText("%d", @as(u32, @intCast(cap.rows)));
+ _ = cimgui.c.ImGui_TableSetColumnIndex(1);
+ cimgui.c.ImGui_Text("%d", @as(u32, @intCast(cap.rows)));
}
}
{
- cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
+ cimgui.c.ImGui_TableNextRow();
{
- _ = cimgui.c.igTableSetColumnIndex(0);
- cimgui.c.igText("Unique Styles");
+ _ = cimgui.c.ImGui_TableSetColumnIndex(0);
+ cimgui.c.ImGui_Text("Unique Styles");
}
{
- _ = cimgui.c.igTableSetColumnIndex(1);
- cimgui.c.igText("%d", @as(u32, @intCast(cap.styles)));
+ _ = cimgui.c.ImGui_TableSetColumnIndex(1);
+ cimgui.c.ImGui_Text("%d", @as(u32, @intCast(cap.styles)));
}
}
{
- cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
+ cimgui.c.ImGui_TableNextRow();
{
- _ = cimgui.c.igTableSetColumnIndex(0);
- cimgui.c.igText("Grapheme Bytes");
+ _ = cimgui.c.ImGui_TableSetColumnIndex(0);
+ cimgui.c.ImGui_Text("Grapheme Bytes");
}
{
- _ = cimgui.c.igTableSetColumnIndex(1);
- cimgui.c.igText("%d", cap.grapheme_bytes);
+ _ = cimgui.c.ImGui_TableSetColumnIndex(1);
+ cimgui.c.ImGui_Text("%d", cap.grapheme_bytes);
}
}
}
}
{
- cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
+ cimgui.c.ImGui_TableNextRow();
{
- _ = cimgui.c.igTableSetColumnIndex(0);
- cimgui.c.igText("Size");
+ _ = cimgui.c.ImGui_TableSetColumnIndex(0);
+ cimgui.c.ImGui_Text("Size");
}
{
- _ = cimgui.c.igTableSetColumnIndex(1);
- _ = cimgui.c.igBeginTable(
+ _ = cimgui.c.ImGui_TableSetColumnIndex(1);
+ _ = cimgui.c.ImGui_BeginTable(
"##size",
2,
cimgui.c.ImGuiTableFlags_None,
- .{ .x = 0, .y = 0 },
- 0,
);
- defer cimgui.c.igEndTable();
+ defer cimgui.c.ImGui_EndTable();
const size = page.size;
{
- cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
+ cimgui.c.ImGui_TableNextRow();
{
- _ = cimgui.c.igTableSetColumnIndex(0);
- cimgui.c.igText("Columns");
+ _ = cimgui.c.ImGui_TableSetColumnIndex(0);
+ cimgui.c.ImGui_Text("Columns");
}
{
- _ = cimgui.c.igTableSetColumnIndex(1);
- cimgui.c.igText("%d", @as(u32, @intCast(size.cols)));
+ _ = cimgui.c.ImGui_TableSetColumnIndex(1);
+ cimgui.c.ImGui_Text("%d", @as(u32, @intCast(size.cols)));
}
}
{
- cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
+ cimgui.c.ImGui_TableNextRow();
{
- _ = cimgui.c.igTableSetColumnIndex(0);
- cimgui.c.igText("Rows");
+ _ = cimgui.c.ImGui_TableSetColumnIndex(0);
+ cimgui.c.ImGui_Text("Rows");
}
{
- _ = cimgui.c.igTableSetColumnIndex(1);
- cimgui.c.igText("%d", @as(u32, @intCast(size.rows)));
+ _ = cimgui.c.ImGui_TableSetColumnIndex(1);
+ cimgui.c.ImGui_Text("%d", @as(u32, @intCast(size.rows)));
}
}
}
diff --git a/src/inspector/termio.zig b/src/inspector/termio.zig
index 7e2b51ee1..934bb6e2d 100644
--- a/src/inspector/termio.zig
+++ b/src/inspector/termio.zig
@@ -1,6 +1,6 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
-const cimgui = @import("cimgui");
+const cimgui = @import("dcimgui");
const terminal = @import("../terminal/main.zig");
const CircBuf = @import("../datastruct/main.zig").CircBuf;
const Surface = @import("../Surface.zig");
@@ -83,7 +83,7 @@ pub const VTEvent = struct {
/// Returns true if the event passes the given filter.
pub fn passFilter(
self: *const VTEvent,
- filter: *cimgui.c.ImGuiTextFilter,
+ filter: *const cimgui.c.ImGuiTextFilter,
) bool {
// Check our main string
if (cimgui.c.ImGuiTextFilter_PassFilter(
@@ -286,18 +286,19 @@ pub const VTEvent = struct {
),
else => switch (Value) {
- u8, u16 => try md.put(
- key,
- try std.fmt.allocPrintSentinel(alloc, "{}", .{value}, 0),
- ),
-
[]const u8,
[:0]const u8,
=> try md.put(key, try alloc.dupeZ(u8, value)),
- else => |T| {
- @compileLog(T);
- @compileError("unsupported type, see log");
+ else => |T| switch (@typeInfo(T)) {
+ .int => try md.put(
+ key,
+ try std.fmt.allocPrintSentinel(alloc, "{}", .{value}, 0),
+ ),
+ else => {
+ @compileLog(T);
+ @compileError("unsupported type, see log");
+ },
},
},
}
@@ -318,19 +319,18 @@ pub const VTHandler = struct {
/// Exclude certain actions by tag.
filter_exclude: ActionTagSet = .initMany(&.{.print}),
- filter_text: *cimgui.c.ImGuiTextFilter,
+ filter_text: cimgui.c.ImGuiTextFilter = .{},
const ActionTagSet = std.EnumSet(terminal.Parser.Action.Tag);
pub fn init(surface: *Surface) VTHandler {
return .{
.surface = surface,
- .filter_text = cimgui.c.ImGuiTextFilter_ImGuiTextFilter(""),
};
}
pub fn deinit(self: *VTHandler) void {
- cimgui.c.ImGuiTextFilter_destroy(self.filter_text);
+ _ = self;
}
pub fn vt(
@@ -371,7 +371,7 @@ pub const VTHandler = struct {
errdefer ev.deinit(alloc);
// Check if the event passes the filter
- if (!ev.passFilter(self.filter_text)) {
+ if (!ev.passFilter(&self.filter_text)) {
ev.deinit(alloc);
return true;
}
diff --git a/src/os/mach.zig b/src/os/mach.zig
new file mode 100644
index 000000000..4477bd128
--- /dev/null
+++ b/src/os/mach.zig
@@ -0,0 +1,150 @@
+const std = @import("std");
+const assert = @import("../quirks.zig").inlineAssert;
+const mem = std.mem;
+const Allocator = std.mem.Allocator;
+
+/// macOS virtual memory tags for use with mach_vm_map/mach_vm_allocate.
+/// These identify memory regions in tools like vmmap and Instruments.
+pub const VMTag = enum(u8) {
+ application_specific_1 = 240,
+ application_specific_2 = 241,
+ application_specific_3 = 242,
+ application_specific_4 = 243,
+ application_specific_5 = 244,
+ application_specific_6 = 245,
+ application_specific_7 = 246,
+ application_specific_8 = 247,
+ application_specific_9 = 248,
+ application_specific_10 = 249,
+ application_specific_11 = 250,
+ application_specific_12 = 251,
+ application_specific_13 = 252,
+ application_specific_14 = 253,
+ application_specific_15 = 254,
+ application_specific_16 = 255,
+
+ // We ignore the rest because we never realistic set them.
+ _,
+
+ /// Converts the tag to the format expected by mach_vm_map/mach_vm_allocate.
+ /// Equivalent to C macro: VM_MAKE_TAG(tag)
+ pub fn make(self: VMTag) i32 {
+ return @bitCast(@as(u32, @intFromEnum(self)) << 24);
+ }
+};
+
+/// Creates a page allocator that tags all allocated memory with the given
+/// VMTag.
+pub fn taggedPageAllocator(tag: VMTag) Allocator {
+ return .{
+ // We smuggle the tag in as the context pointer.
+ .ptr = @ptrFromInt(@as(usize, @intFromEnum(tag))),
+ .vtable = &TaggedPageAllocator.vtable,
+ };
+}
+
+/// This is based heavily on the Zig 0.15.2 PageAllocator implementation,
+/// with only the posix implementation. Zig 0.15.2 is MIT licensed.
+const TaggedPageAllocator = struct {
+ pub const vtable: Allocator.VTable = .{
+ .alloc = alloc,
+ .resize = resize,
+ .remap = remap,
+ .free = free,
+ };
+
+ fn alloc(context: *anyopaque, n: usize, alignment: mem.Alignment, ra: usize) ?[*]u8 {
+ _ = ra;
+ assert(n > 0);
+ const tag: VMTag = @enumFromInt(@as(u8, @truncate(@intFromPtr(context))));
+ return map(n, alignment, tag);
+ }
+
+ fn resize(context: *anyopaque, memory: []u8, alignment: mem.Alignment, new_len: usize, return_address: usize) bool {
+ _ = context;
+ _ = alignment;
+ _ = return_address;
+ return realloc(memory, new_len, false) != null;
+ }
+
+ fn remap(context: *anyopaque, memory: []u8, alignment: mem.Alignment, new_len: usize, return_address: usize) ?[*]u8 {
+ _ = context;
+ _ = alignment;
+ _ = return_address;
+ return realloc(memory, new_len, true);
+ }
+
+ fn free(context: *anyopaque, memory: []u8, alignment: mem.Alignment, return_address: usize) void {
+ _ = context;
+ _ = alignment;
+ _ = return_address;
+ return unmap(@alignCast(memory));
+ }
+
+ pub fn map(n: usize, alignment: mem.Alignment, tag: VMTag) ?[*]u8 {
+ const page_size = std.heap.pageSize();
+ if (n >= std.math.maxInt(usize) - page_size) return null;
+ const alignment_bytes = alignment.toByteUnits();
+
+ const aligned_len = mem.alignForward(usize, n, page_size);
+ const max_drop_len = alignment_bytes - @min(alignment_bytes, page_size);
+ const overalloc_len = if (max_drop_len <= aligned_len - n)
+ aligned_len
+ else
+ mem.alignForward(usize, aligned_len + max_drop_len, page_size);
+ const hint = @atomicLoad(@TypeOf(std.heap.next_mmap_addr_hint), &std.heap.next_mmap_addr_hint, .unordered);
+ const slice = std.posix.mmap(
+ hint,
+ overalloc_len,
+ std.posix.PROT.READ | std.posix.PROT.WRITE,
+ .{ .TYPE = .PRIVATE, .ANONYMOUS = true },
+ tag.make(),
+ 0,
+ ) catch return null;
+ const result_ptr = mem.alignPointer(slice.ptr, alignment_bytes) orelse return null;
+ // Unmap the extra bytes that were only requested in order to guarantee
+ // that the range of memory we were provided had a proper alignment in it
+ // somewhere. The extra bytes could be at the beginning, or end, or both.
+ const drop_len = result_ptr - slice.ptr;
+ if (drop_len != 0) std.posix.munmap(slice[0..drop_len]);
+ const remaining_len = overalloc_len - drop_len;
+ if (remaining_len > aligned_len) std.posix.munmap(@alignCast(result_ptr[aligned_len..remaining_len]));
+ const new_hint: [*]align(std.heap.page_size_min) u8 = @alignCast(result_ptr + aligned_len);
+ _ = @cmpxchgStrong(@TypeOf(std.heap.next_mmap_addr_hint), &std.heap.next_mmap_addr_hint, hint, new_hint, .monotonic, .monotonic);
+ return result_ptr;
+ }
+
+ pub fn unmap(memory: []align(std.heap.page_size_min) u8) void {
+ const page_aligned_len = mem.alignForward(usize, memory.len, std.heap.pageSize());
+ std.posix.munmap(memory.ptr[0..page_aligned_len]);
+ }
+
+ pub fn realloc(uncasted_memory: []u8, new_len: usize, may_move: bool) ?[*]u8 {
+ const memory: []align(std.heap.page_size_min) u8 = @alignCast(uncasted_memory);
+ const page_size = std.heap.pageSize();
+ const new_size_aligned = mem.alignForward(usize, new_len, page_size);
+
+ const page_aligned_len = mem.alignForward(usize, memory.len, page_size);
+ if (new_size_aligned == page_aligned_len)
+ return memory.ptr;
+
+ if (std.posix.MREMAP != void) {
+ // TODO: if the next_mmap_addr_hint is within the remapped range, update it
+ const new_memory = std.posix.mremap(memory.ptr, memory.len, new_len, .{ .MAYMOVE = may_move }, null) catch return null;
+ return new_memory.ptr;
+ }
+
+ if (new_size_aligned < page_aligned_len) {
+ const ptr = memory.ptr + new_size_aligned;
+ // TODO: if the next_mmap_addr_hint is within the unmapped range, update it
+ std.posix.munmap(@alignCast(ptr[0 .. page_aligned_len - new_size_aligned]));
+ return memory.ptr;
+ }
+
+ return null;
+ }
+};
+
+test "VMTag.make" {
+ try std.testing.expectEqual(@as(i32, @bitCast(@as(u32, 240) << 24)), VMTag.application_specific_1.make());
+}
diff --git a/src/os/main.zig b/src/os/main.zig
index c105f6143..2aadabac5 100644
--- a/src/os/main.zig
+++ b/src/os/main.zig
@@ -23,6 +23,7 @@ pub const args = @import("args.zig");
pub const cgroup = @import("cgroup.zig");
pub const hostname = @import("hostname.zig");
pub const i18n = @import("i18n.zig");
+pub const mach = @import("mach.zig");
pub const path = @import("path.zig");
pub const passwd = @import("passwd.zig");
pub const xdg = @import("xdg.zig");
@@ -73,5 +74,8 @@ test {
if (comptime builtin.os.tag == .linux) {
_ = kernel_info;
+ } else if (comptime builtin.os.tag.isDarwin()) {
+ _ = mach;
+ _ = macos;
}
}
diff --git a/src/renderer/Metal.zig b/src/renderer/Metal.zig
index 168f54c2b..6c7432d21 100644
--- a/src/renderer/Metal.zig
+++ b/src/renderer/Metal.zig
@@ -55,6 +55,9 @@ blending: configpkg.Config.AlphaBlending,
/// the "shared" storage mode, instead we have to use the "managed" mode.
default_storage_mode: mtl.MTLResourceOptions.StorageMode,
+/// The maximum 2D texture width and height supported by the device.
+max_texture_size: u32,
+
/// We start an AutoreleasePool before `drawFrame` and end it afterwards.
autorelease_pool: ?*objc.AutoreleasePool = null,
@@ -72,8 +75,17 @@ pub fn init(alloc: Allocator, opts: rendererpkg.Options) !Metal {
const queue = device.msgSend(objc.Object, objc.sel("newCommandQueue"), .{});
errdefer queue.release();
- const default_storage_mode: mtl.MTLResourceOptions.StorageMode =
- if (device.getProperty(bool, "hasUnifiedMemory")) .shared else .managed;
+ // Grab metadata about the device.
+ const default_storage_mode: mtl.MTLResourceOptions.StorageMode = switch (comptime builtin.os.tag) {
+ // manage mode is not supported by iOS
+ .ios => .shared,
+ else => if (device.getProperty(bool, "hasUnifiedMemory")) .shared else .managed,
+ };
+ const max_texture_size = queryMaxTextureSize(device);
+ log.debug(
+ "device properties default_storage_mode={} max_texture_size={}",
+ .{ default_storage_mode, max_texture_size },
+ );
const ViewInfo = struct {
view: objc.Object,
@@ -114,7 +126,8 @@ pub fn init(alloc: Allocator, opts: rendererpkg.Options) !Metal {
},
.ios => {
- info.view.msgSend(void, objc.sel("addSublayer"), .{layer.layer.value});
+ const view_layer = objc.Object.fromId(info.view.getProperty(?*anyopaque, "layer"));
+ view_layer.msgSend(void, objc.sel("addSublayer:"), .{layer.layer.value});
},
else => @compileError("unsupported target for Metal"),
@@ -138,6 +151,7 @@ pub fn init(alloc: Allocator, opts: rendererpkg.Options) !Metal {
.queue = queue,
.blending = opts.config.blending,
.default_storage_mode = default_storage_mode,
+ .max_texture_size = max_texture_size,
};
}
@@ -202,9 +216,19 @@ pub fn initShaders(
pub fn surfaceSize(self: *const Metal) !struct { width: u32, height: u32 } {
const bounds = self.layer.layer.getProperty(graphics.Rect, "bounds");
const scale = self.layer.layer.getProperty(f64, "contentsScale");
+
+ // We need to clamp our runtime surface size to the maximum
+ // possible texture size since we can't create a screen buffer (texture)
+ // larger than that.
return .{
- .width = @intFromFloat(bounds.size.width * scale),
- .height = @intFromFloat(bounds.size.height * scale),
+ .width = @min(
+ @as(u32, @intFromFloat(bounds.size.width * scale)),
+ self.max_texture_size,
+ ),
+ .height = @min(
+ @as(u32, @intFromFloat(bounds.size.height * scale)),
+ self.max_texture_size,
+ ),
};
}
@@ -412,3 +436,23 @@ fn chooseDevice() error{NoMetalDevice}!objc.Object {
const device = chosen_device orelse return error.NoMetalDevice;
return device.retain();
}
+
+/// Determines the maximum 2D texture size supported by the device.
+/// We need to clamp our frame size to this if it's larger.
+fn queryMaxTextureSize(device: objc.Object) u32 {
+ // https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf
+
+ if (device.msgSend(
+ bool,
+ objc.sel("supportsFamily:"),
+ .{mtl.MTLGPUFamily.apple10},
+ )) return 32768;
+
+ if (device.msgSend(
+ bool,
+ objc.sel("supportsFamily:"),
+ .{mtl.MTLGPUFamily.apple3},
+ )) return 16384;
+
+ return 8192;
+}
diff --git a/src/renderer/cell.zig b/src/renderer/cell.zig
index 9e5802ea5..5ea5b7ab0 100644
--- a/src/renderer/cell.zig
+++ b/src/renderer/cell.zig
@@ -90,7 +90,6 @@ pub const Contents = struct {
const bg_cells = try alloc.alloc(shaderpkg.CellBg, cell_count);
errdefer alloc.free(bg_cells);
-
@memset(bg_cells, .{ 0, 0, 0, 0 });
// The foreground lists can hold 3 types of items:
@@ -106,32 +105,28 @@ pub const Contents = struct {
// We have size.rows + 2 lists because indexes 0 and size.rows - 1 are
// used for special lists containing the cursor cell which need to
// be first and last in the buffer, respectively.
- var fg_rows = try ArrayListCollection(shaderpkg.CellText).init(
+ var fg_rows: ArrayListCollection(shaderpkg.CellText) = try .init(
alloc,
size.rows + 2,
size.columns * 3,
);
errdefer fg_rows.deinit(alloc);
- alloc.free(self.bg_cells);
- self.fg_rows.deinit(alloc);
-
- self.bg_cells = bg_cells;
- self.fg_rows = fg_rows;
-
// We don't need 3*cols worth of cells for the cursor lists, so we can
// replace them with smaller lists. This is technically a tiny bit of
// extra work but resize is not a hot function so it's worth it to not
// waste the memory.
- self.fg_rows.lists[0].deinit(alloc);
- self.fg_rows.lists[0] = try std.ArrayListUnmanaged(
- shaderpkg.CellText,
- ).initCapacity(alloc, 1);
+ fg_rows.lists[0].deinit(alloc);
+ fg_rows.lists[0] = try .initCapacity(alloc, 1);
+ fg_rows.lists[size.rows + 1].deinit(alloc);
+ fg_rows.lists[size.rows + 1] = try .initCapacity(alloc, 1);
- self.fg_rows.lists[size.rows + 1].deinit(alloc);
- self.fg_rows.lists[size.rows + 1] = try std.ArrayListUnmanaged(
- shaderpkg.CellText,
- ).initCapacity(alloc, 1);
+ // Perform the swap, no going back from here.
+ errdefer comptime unreachable;
+ alloc.free(self.bg_cells);
+ self.fg_rows.deinit(alloc);
+ self.bg_cells = bg_cells;
+ self.fg_rows = fg_rows;
}
/// Reset the cell contents to an empty state without resizing.
diff --git a/src/renderer/generic.zig b/src/renderer/generic.zig
index 39eec7b43..e75171721 100644
--- a/src/renderer/generic.zig
+++ b/src/renderer/generic.zig
@@ -116,6 +116,10 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
/// True if the window is focused
focused: bool,
+ /// Flag to indicate that our focus state changed for custom
+ /// shaders to update their state.
+ custom_shader_focused_changed: bool = false,
+
/// The most recent scrollbar state. We use this as a cache to
/// determine if we need to notify the apprt that there was a
/// scrollbar change.
@@ -227,7 +231,6 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
search_match,
search_match_selected,
};
-
/// Swap chain which maintains multiple copies of the state needed to
/// render a frame, so that we can start building the next frame while
/// the previous frame is still being processed on the GPU.
@@ -746,6 +749,15 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
.current_cursor_color = @splat(0),
.previous_cursor_color = @splat(0),
.cursor_change_time = 0,
+ .time_focus = 0,
+ .focus = 1, // assume focused initially
+ .palette = @splat(@splat(0)),
+ .background_color = @splat(0),
+ .foreground_color = @splat(0),
+ .cursor_color = @splat(0),
+ .cursor_text = @splat(0),
+ .selection_background_color = @splat(0),
+ .selection_foreground_color = @splat(0),
},
.bg_image_buffer = undefined,
@@ -1008,8 +1020,13 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
///
/// Must be called on the render thread.
pub fn setFocus(self: *Self, focus: bool) !void {
+ assert(self.focused != focus);
+
self.focused = focus;
+ // Flag that we need to update our custom shaders
+ self.custom_shader_focused_changed = true;
+
// If we're not focused, then we want to stop the display link
// because it is a waste of resources and we can move to pure
// change-driven updates.
@@ -1100,7 +1117,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
self: *Self,
state: *renderer.State,
cursor_blink_visible: bool,
- ) !void {
+ ) Allocator.Error!void {
// We fully deinit and reset the terminal state every so often
// so that a particularly large terminal state doesn't cause
// the renderer to hold on to retained memory.
@@ -1176,7 +1193,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
if (state.terminal.screens.active.kitty_images.dirty or
self.image_virtual)
{
- try self.prepKittyGraphics(state.terminal);
+ self.prepKittyGraphics(state.terminal);
}
// Get our OSC8 links we're hovering if we have a mouse.
@@ -1265,26 +1282,34 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
}
}
- // Build our GPU cells
- try self.rebuildCells(
- critical.preedit,
- renderer.cursorStyle(&self.terminal_state, .{
- .preedit = critical.preedit != null,
- .focused = self.focused,
- .blink_visible = cursor_blink_visible,
- }),
- &critical.links,
- );
+ // From this point forward no more errors.
+ errdefer comptime unreachable;
- // Notify our shaper we're done for the frame. For some shapers,
- // such as CoreText, this triggers off-thread cleanup logic.
- self.font_shaper.endFrame();
+ // Reset our dirty state after updating.
+ defer self.terminal_state.dirty = .false;
- // Acquire the draw mutex because we're modifying state here.
+ // Acquire the draw mutex for all remaining state updates.
{
self.draw_mutex.lock();
defer self.draw_mutex.unlock();
+ // Build our GPU cells
+ self.rebuildCells(
+ critical.preedit,
+ renderer.cursorStyle(&self.terminal_state, .{
+ .preedit = critical.preedit != null,
+ .focused = self.focused,
+ .blink_visible = cursor_blink_visible,
+ }),
+ &critical.links,
+ ) catch |err| {
+ // This means we weren't able to allocate our buffer
+ // to update the cells. In this case, we continue with
+ // our old buffer (frozen contents) and log it.
+ comptime assert(@TypeOf(err) == error{OutOfMemory});
+ log.warn("error rebuilding GPU cells err={}", .{err});
+ };
+
// The scrollbar is only emitted during draws so we also
// check the scrollbar cache here and update if needed.
// This is pretty fast.
@@ -1311,7 +1336,14 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
else => {},
};
+
+ // Update custom shader uniforms that depend on terminal state.
+ self.updateCustomShaderUniformsFromState();
}
+
+ // Notify our shaper we're done for the frame. For some shapers,
+ // such as CoreText, this triggers off-thread cleanup logic.
+ self.font_shaper.endFrame();
}
/// Draw the frame to the screen.
@@ -1433,8 +1465,8 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
// Upload the background image to the GPU as necessary.
try self.uploadBackgroundImage();
- // Update custom shader uniforms if necessary.
- try self.updateCustomShaderUniforms();
+ // Update per-frame custom shader uniforms.
+ try self.updateCustomShaderUniformsForFrame();
// Setup our frame data
try frame.uniforms.sync(&.{self.uniforms});
@@ -1680,7 +1712,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
fn prepKittyGraphics(
self: *Self,
t: *terminal.Terminal,
- ) !void {
+ ) void {
self.draw_mutex.lock();
defer self.draw_mutex.unlock();
@@ -1744,16 +1776,32 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
continue;
};
- try self.prepKittyPlacement(t, top_y, bot_y, &image, p);
+ self.prepKittyPlacement(
+ t,
+ top_y,
+ bot_y,
+ &image,
+ p,
+ ) catch |err| {
+ // For errors we log and continue. We try to place
+ // other placements even if one fails.
+ log.warn("error preparing kitty placement err={}", .{err});
+ };
}
// If we have virtual placements then we need to scan for placeholders.
if (self.image_virtual) {
var v_it = terminal.kitty.graphics.unicode.placementIterator(top, bot);
- while (v_it.next()) |virtual_p| try self.prepKittyVirtualPlacement(
- t,
- &virtual_p,
- );
+ while (v_it.next()) |virtual_p| {
+ self.prepKittyVirtualPlacement(
+ t,
+ &virtual_p,
+ ) catch |err| {
+ // For errors we log and continue. We try to place
+ // other placements even if one fails.
+ log.warn("error preparing kitty placement err={}", .{err});
+ };
+ }
}
// Sort the placements by their Z value.
@@ -1801,7 +1849,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
self: *Self,
t: *terminal.Terminal,
p: *const terminal.kitty.graphics.unicode.Placement,
- ) !void {
+ ) PrepKittyImageError!void {
const storage = &t.screens.active.kitty_images;
const image = storage.imageById(p.image_id) orelse {
log.warn(
@@ -1862,7 +1910,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
bot_y: u32,
image: *const terminal.kitty.graphics.Image,
p: *const terminal.kitty.graphics.ImageStorage.Placement,
- ) !void {
+ ) PrepKittyImageError!void {
// Get the rect for the placement. If this placement doesn't have
// a rect then its virtual or something so skip it.
const rect = p.rect(image.*, t) orelse return;
@@ -1918,12 +1966,17 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
}
}
+ const PrepKittyImageError = error{
+ OutOfMemory,
+ ImageConversionError,
+ };
+
/// Prepare the provided image for upload to the GPU by copying its
/// data with our allocator and setting it to the pending state.
fn prepKittyImage(
self: *Self,
image: *const terminal.kitty.graphics.Image,
- ) !void {
+ ) PrepKittyImageError!void {
// If this image exists and its transmit time is the same we assume
// it is the identical image so we don't need to send it to the GPU.
const gop = try self.images.getOrPut(self.alloc, image.id);
@@ -1934,39 +1987,60 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
}
// Copy the data into the pending state.
- const data = try self.alloc.dupe(u8, image.data);
- errdefer self.alloc.free(data);
+ const data = if (self.alloc.dupe(
+ u8,
+ image.data,
+ )) |v| v else |_| {
+ if (!gop.found_existing) {
+ // If this is a new entry we can just remove it since it
+ // was never sent to the GPU.
+ _ = self.images.remove(image.id);
+ } else {
+ // If this was an existing entry, it is invalid and
+ // we must unload it.
+ gop.value_ptr.image.markForUnload();
+ }
+
+ return error.OutOfMemory;
+ };
+ // Note: we don't need to errdefer free the data because it is
+ // put into the map immediately below and our errdefer to
+ // handle our map state will fix this up.
// Store it in the map
- const pending: Image.Pending = .{
- .width = image.width,
- .height = image.height,
- .pixel_format = switch (image.format) {
- .gray => .gray,
- .gray_alpha => .gray_alpha,
- .rgb => .rgb,
- .rgba => .rgba,
- .png => unreachable, // should be decoded by now
+ const new_image: Image = .{
+ .pending = .{
+ .width = image.width,
+ .height = image.height,
+ .pixel_format = switch (image.format) {
+ .gray => .gray,
+ .gray_alpha => .gray_alpha,
+ .rgb => .rgb,
+ .rgba => .rgba,
+ .png => unreachable, // should be decoded by now
+ },
+ .data = data.ptr,
},
- .data = data.ptr,
};
-
- const new_image: Image = .{ .pending = pending };
-
if (!gop.found_existing) {
gop.value_ptr.* = .{
.image = new_image,
.transmit_time = undefined,
};
} else {
- try gop.value_ptr.image.markForReplace(
+ gop.value_ptr.image.markForReplace(
self.alloc,
new_image,
);
}
- try gop.value_ptr.image.prepForUpload(self.alloc);
+ // If any error happens, we unload the image and it is invalid.
+ errdefer gop.value_ptr.image.markForUnload();
+ gop.value_ptr.image.prepForUpload(self.alloc) catch |err| {
+ log.warn("error preparing kitty image for upload err={}", .{err});
+ return error.ImageConversionError;
+ };
gop.value_ptr.transmit_time = image.transmit_time;
}
@@ -2058,7 +2132,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
// If we have an existing background image, replace it.
// Otherwise, set this as our background image directly.
if (self.bg_image) |*img| {
- try img.markForReplace(self.alloc, image);
+ img.markForReplace(self.alloc, image);
} else {
self.bg_image = image;
}
@@ -2099,7 +2173,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
}
// We also need to reset the shaper cache so shaper info
- // from the previous font isn't re-used for the new font.
+ // from the previous font isn't reused for the new font.
const font_shaper_cache = font.ShaperCache.init();
self.font_shaper_cache.deinit(self.alloc);
self.font_shaper_cache = font_shaper_cache;
@@ -2248,13 +2322,98 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
self.bg_image_buffer_modified +%= 1;
}
- /// Update uniforms for the custom shaders, if necessary.
+ /// Update custom shader uniforms that depend on terminal state.
///
- /// This should be called exactly once per frame, inside `drawFrame`.
- fn updateCustomShaderUniforms(self: *Self) !void {
+ /// This should be called in `updateFrame` when terminal state changes.
+ fn updateCustomShaderUniformsFromState(self: *Self) void {
// We only need to do this if we have custom shaders.
if (!self.has_custom_shaders) return;
+ // Only update when terminal state is dirty.
+ if (self.terminal_state.dirty == .false) return;
+
+ const colors: *const terminal.RenderState.Colors = &self.terminal_state.colors;
+
+ // 256-color palette
+ for (colors.palette, 0..) |color, i| {
+ self.custom_shader_uniforms.palette[i] = .{
+ @as(f32, @floatFromInt(color.r)) / 255.0,
+ @as(f32, @floatFromInt(color.g)) / 255.0,
+ @as(f32, @floatFromInt(color.b)) / 255.0,
+ 1.0,
+ };
+ }
+
+ // Background color
+ self.custom_shader_uniforms.background_color = .{
+ @as(f32, @floatFromInt(colors.background.r)) / 255.0,
+ @as(f32, @floatFromInt(colors.background.g)) / 255.0,
+ @as(f32, @floatFromInt(colors.background.b)) / 255.0,
+ 1.0,
+ };
+
+ // Foreground color
+ self.custom_shader_uniforms.foreground_color = .{
+ @as(f32, @floatFromInt(colors.foreground.r)) / 255.0,
+ @as(f32, @floatFromInt(colors.foreground.g)) / 255.0,
+ @as(f32, @floatFromInt(colors.foreground.b)) / 255.0,
+ 1.0,
+ };
+
+ // Cursor color
+ if (colors.cursor) |cursor_color| {
+ self.custom_shader_uniforms.cursor_color = .{
+ @as(f32, @floatFromInt(cursor_color.r)) / 255.0,
+ @as(f32, @floatFromInt(cursor_color.g)) / 255.0,
+ @as(f32, @floatFromInt(cursor_color.b)) / 255.0,
+ 1.0,
+ };
+ }
+
+ // NOTE: the following could be optimized to follow a change in
+ // config for a slight optimization however this is only 12 bytes
+ // each being updated and likely isn't a cause for concern
+
+ // Cursor text color
+ if (self.config.cursor_text) |cursor_text| {
+ self.custom_shader_uniforms.cursor_text = .{
+ @as(f32, @floatFromInt(cursor_text.color.r)) / 255.0,
+ @as(f32, @floatFromInt(cursor_text.color.g)) / 255.0,
+ @as(f32, @floatFromInt(cursor_text.color.b)) / 255.0,
+ 1.0,
+ };
+ }
+
+ // Selection background color
+ if (self.config.selection_background) |selection_bg| {
+ self.custom_shader_uniforms.selection_background_color = .{
+ @as(f32, @floatFromInt(selection_bg.color.r)) / 255.0,
+ @as(f32, @floatFromInt(selection_bg.color.g)) / 255.0,
+ @as(f32, @floatFromInt(selection_bg.color.b)) / 255.0,
+ 1.0,
+ };
+ }
+
+ // Selection foreground color
+ if (self.config.selection_foreground) |selection_fg| {
+ self.custom_shader_uniforms.selection_foreground_color = .{
+ @as(f32, @floatFromInt(selection_fg.color.r)) / 255.0,
+ @as(f32, @floatFromInt(selection_fg.color.g)) / 255.0,
+ @as(f32, @floatFromInt(selection_fg.color.b)) / 255.0,
+ 1.0,
+ };
+ }
+ }
+
+ /// Update per-frame custom shader uniforms.
+ ///
+ /// This should be called exactly once per frame, inside `drawFrame`.
+ fn updateCustomShaderUniformsForFrame(self: *Self) !void {
+ // We only need to do this if we have custom shaders.
+ if (!self.has_custom_shaders) return;
+
+ const uniforms = &self.custom_shader_uniforms;
+
const now = try std.time.Instant.now();
defer self.last_frame_time = now;
const first_frame_time = self.first_frame_time orelse t: {
@@ -2264,23 +2423,23 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
const last_frame_time = self.last_frame_time orelse now;
const since_ns: f32 = @floatFromInt(now.since(first_frame_time));
- self.custom_shader_uniforms.time = since_ns / std.time.ns_per_s;
+ uniforms.time = since_ns / std.time.ns_per_s;
const delta_ns: f32 = @floatFromInt(now.since(last_frame_time));
- self.custom_shader_uniforms.time_delta = delta_ns / std.time.ns_per_s;
+ uniforms.time_delta = delta_ns / std.time.ns_per_s;
- self.custom_shader_uniforms.frame += 1;
+ uniforms.frame += 1;
const screen = self.size.screen;
const padding = self.size.padding;
const cell = self.size.cell;
- self.custom_shader_uniforms.resolution = .{
+ uniforms.resolution = .{
@floatFromInt(screen.width),
@floatFromInt(screen.height),
1,
};
- self.custom_shader_uniforms.channel_resolution[0] = .{
+ uniforms.channel_resolution[0] = .{
@floatFromInt(screen.width),
@floatFromInt(screen.height),
1,
@@ -2345,8 +2504,6 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
@as(f32, @floatFromInt(cursor.color[3])) / 255.0,
};
- const uniforms = &self.custom_shader_uniforms;
-
const cursor_changed: bool =
!std.meta.eql(new_cursor, uniforms.current_cursor) or
!std.meta.eql(cursor_color, uniforms.current_cursor_color);
@@ -2359,22 +2516,41 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
uniforms.cursor_change_time = uniforms.time;
}
}
+
+ // Update focus uniforms
+ uniforms.focus = @intFromBool(self.focused);
+
+ // If we need to update the time our focus state changed
+ // then update it to our current frame time. This may not be
+ // exactly correct since it is frame time, not exact focus
+ // time, but focus time on its own isn't exactly correct anyways
+ // since it comes async from a message.
+ if (self.custom_shader_focused_changed and self.focused) {
+ uniforms.time_focus = uniforms.time;
+ self.custom_shader_focused_changed = false;
+ }
}
+ const PreeditRange = struct {
+ y: terminal.size.CellCountInt,
+ x: [2]terminal.size.CellCountInt,
+ cp_offset: usize,
+ };
+
/// Convert the terminal state to GPU cells stored in CPU memory. These
/// are then synced to the GPU in the next frame. This only updates CPU
/// memory and doesn't touch the GPU.
+ ///
+ /// This requires the draw mutex.
+ ///
+ /// Dirty state on terminal state won't be reset by this.
fn rebuildCells(
self: *Self,
preedit: ?renderer.State.Preedit,
cursor_style_: ?renderer.CursorStyle,
links: *const terminal.RenderState.CellSet,
- ) !void {
+ ) Allocator.Error!void {
const state: *terminal.RenderState = &self.terminal_state;
- defer state.dirty = .false;
-
- self.draw_mutex.lock();
- defer self.draw_mutex.unlock();
// const start = try std.time.Instant.now();
// const start_micro = std.time.microTimestamp();
@@ -2386,11 +2562,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
// Determine our x/y range for preedit. We don't want to render anything
// here because we will render the preedit separately.
- const preedit_range: ?struct {
- y: terminal.size.CellCountInt,
- x: [2]terminal.size.CellCountInt,
- cp_offset: usize,
- } = if (preedit) |preedit_v| preedit: {
+ const preedit_range: ?PreeditRange = if (preedit) |preedit_v| preedit: {
// We base the preedit on the position of the cursor in the
// viewport. If the cursor isn't visible in the viewport we
// don't show it.
@@ -2445,6 +2617,10 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
}
}
+ // From this point on we never fail. We produce some kind of
+ // working terminal state, even if incorrect.
+ errdefer comptime unreachable;
+
// Get our row data from our state
const row_data = state.row_data.slice();
const row_raws = row_data.items(.raw);
@@ -2468,7 +2644,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
row_dirty[0..row_len],
row_selection[0..row_len],
row_highlights[0..row_len],
- ) |y_usize, row, *cells, *dirty, selection, highlights| {
+ ) |y_usize, row, *cells, *dirty, selection, *highlights| {
const y: terminal.size.CellCountInt = @intCast(y_usize);
if (!rebuild) {
@@ -2482,440 +2658,22 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
// Unmark the dirty state in our render state.
dirty.* = false;
- // If our viewport is wider than our cell contents buffer,
- // we still only process cells up to the width of the buffer.
- const cells_slice = cells.slice();
- const cells_len = @min(cells_slice.len, self.cells.size.columns);
- const cells_raw = cells_slice.items(.raw);
- const cells_style = cells_slice.items(.style);
-
- // On primary screen, we still apply vertical padding
- // extension under certain conditions we feel are safe.
- //
- // This helps make some scenarios look better while
- // avoiding scenarios we know do NOT look good.
- switch (self.config.padding_color) {
- // These already have the correct values set above.
- .background, .@"extend-always" => {},
-
- // Apply heuristics for padding extension.
- .extend => if (y == 0) {
- self.uniforms.padding_extend.up = !rowNeverExtendBg(
- row,
- cells_raw,
- cells_style,
- &state.colors.palette,
- state.colors.background,
- );
- } else if (y == self.cells.size.rows - 1) {
- self.uniforms.padding_extend.down = !rowNeverExtendBg(
- row,
- cells_raw,
- cells_style,
- &state.colors.palette,
- state.colors.background,
- );
- },
- }
-
- // Iterator of runs for shaping.
- var run_iter_opts: font.shape.RunOptions = .{
- .grid = self.font_grid,
- .cells = cells_slice,
- .selection = if (selection) |s| s else null,
-
- // We want to do font shaping as long as the cursor is
- // visible on this viewport.
- .cursor_x = cursor_x: {
- const vp = state.cursor.viewport orelse break :cursor_x null;
- if (vp.y != y) break :cursor_x null;
- break :cursor_x vp.x;
- },
+ self.rebuildRow(
+ y,
+ row,
+ cells,
+ preedit_range,
+ selection,
+ highlights,
+ links,
+ ) catch |err| {
+ // This should never happen except under exceptional
+ // scenarios. In this case, we don't want to corrupt
+ // our render state so just clear this row and keep
+ // trying to finish it out.
+ log.warn("error building row y={} err={}", .{ y, err });
+ self.cells.clear(y);
};
- run_iter_opts.applyBreakConfig(self.config.font_shaping_break);
- var run_iter = self.font_shaper.runIterator(run_iter_opts);
- var shaper_run: ?font.shape.TextRun = try run_iter.next(self.alloc);
- var shaper_cells: ?[]const font.shape.Cell = null;
- var shaper_cells_i: usize = 0;
-
- for (
- 0..,
- cells_raw[0..cells_len],
- cells_style[0..cells_len],
- ) |x, *cell, *managed_style| {
- // If this cell falls within our preedit range then we
- // skip this because preedits are setup separately.
- if (preedit_range) |range| preedit: {
- // We're not on the preedit line, no actions necessary.
- if (range.y != y) break :preedit;
- // We're before the preedit range, no actions necessary.
- if (x < range.x[0]) break :preedit;
- // We're in the preedit range, skip this cell.
- if (x <= range.x[1]) continue;
- // After exiting the preedit range we need to catch
- // the run position up because of the missed cells.
- // In all other cases, no action is necessary.
- if (x != range.x[1] + 1) break :preedit;
-
- // Step the run iterator until we find a run that ends
- // after the current cell, which will be the soonest run
- // that might contain glyphs for our cell.
- while (shaper_run) |run| {
- if (run.offset + run.cells > x) break;
- shaper_run = try run_iter.next(self.alloc);
- shaper_cells = null;
- shaper_cells_i = 0;
- }
-
- const run = shaper_run orelse break :preedit;
-
- // If we haven't shaped this run, do so now.
- shaper_cells = shaper_cells orelse
- // Try to read the cells from the shaping cache if we can.
- self.font_shaper_cache.get(run) orelse
- cache: {
- // Otherwise we have to shape them.
- const new_cells = try self.font_shaper.shape(run);
-
- // Try to cache them. If caching fails for any reason we
- // continue because it is just a performance optimization,
- // not a correctness issue.
- self.font_shaper_cache.put(
- self.alloc,
- run,
- new_cells,
- ) catch |err| {
- log.warn(
- "error caching font shaping results err={}",
- .{err},
- );
- };
-
- // The cells we get from direct shaping are always owned
- // by the shaper and valid until the next shaping call so
- // we can safely use them.
- break :cache new_cells;
- };
-
- // Advance our index until we reach or pass
- // our current x position in the shaper cells.
- const shaper_cells_unwrapped = shaper_cells.?;
- while (run.offset + shaper_cells_unwrapped[shaper_cells_i].x < x) {
- shaper_cells_i += 1;
- }
- }
-
- const wide = cell.wide;
- const style: terminal.Style = if (cell.hasStyling())
- managed_style.*
- else
- .{};
-
- // True if this cell is selected
- const selected: enum {
- false,
- selection,
- search,
- search_selected,
- } = selected: {
- // Order below matters for precedence.
-
- // Selection should take the highest precedence.
- const x_compare = if (wide == .spacer_tail)
- x -| 1
- else
- x;
- if (selection) |sel| {
- if (x_compare >= sel[0] and
- x_compare <= sel[1]) break :selected .selection;
- }
-
- // If we're highlighted, then we're selected. In the
- // future we want to use a different style for this
- // but this to get started.
- for (highlights.items) |hl| {
- if (x_compare >= hl.range[0] and
- x_compare <= hl.range[1])
- {
- const tag: HighlightTag = @enumFromInt(hl.tag);
- break :selected switch (tag) {
- .search_match => .search,
- .search_match_selected => .search_selected,
- };
- }
- }
-
- break :selected .false;
- };
-
- // The `_style` suffixed values are the colors based on
- // the cell style (SGR), before applying any additional
- // configuration, inversions, selections, etc.
- const bg_style = style.bg(
- cell,
- &state.colors.palette,
- );
- const fg_style = style.fg(.{
- .default = state.colors.foreground,
- .palette = &state.colors.palette,
- .bold = self.config.bold_color,
- });
-
- // The final background color for the cell.
- const bg = switch (selected) {
- // If we have an explicit selection background color
- // specified in the config, use that.
- //
- // If no configuration, then our selection background
- // is our foreground color.
- .selection => if (self.config.selection_background) |v| switch (v) {
- .color => |color| color.toTerminalRGB(),
- .@"cell-foreground" => if (style.flags.inverse) bg_style else fg_style,
- .@"cell-background" => if (style.flags.inverse) fg_style else bg_style,
- } else state.colors.foreground,
-
- .search => switch (self.config.search_background) {
- .color => |color| color.toTerminalRGB(),
- .@"cell-foreground" => if (style.flags.inverse) bg_style else fg_style,
- .@"cell-background" => if (style.flags.inverse) fg_style else bg_style,
- },
-
- .search_selected => switch (self.config.search_selected_background) {
- .color => |color| color.toTerminalRGB(),
- .@"cell-foreground" => if (style.flags.inverse) bg_style else fg_style,
- .@"cell-background" => if (style.flags.inverse) fg_style else bg_style,
- },
-
- // Not selected
- .false => if (style.flags.inverse != isCovering(cell.codepoint()))
- // Two cases cause us to invert (use the fg color as the bg)
- // - The "inverse" style flag.
- // - A "covering" glyph; we use fg for bg in that
- // case to help make sure that padding extension
- // works correctly.
- //
- // If one of these is true (but not the other)
- // then we use the fg style color for the bg.
- fg_style
- else
- // Otherwise they cancel out.
- bg_style,
- };
-
- const fg = fg: {
- // Our happy-path non-selection background color
- // is our style or our configured defaults.
- const final_bg = bg_style orelse state.colors.background;
-
- // Whether we need to use the bg color as our fg color:
- // - Cell is selected, inverted, and set to cell-foreground
- // - Cell is selected, not inverted, and set to cell-background
- // - Cell is inverted and not selected
- break :fg switch (selected) {
- .selection => if (self.config.selection_foreground) |v| switch (v) {
- .color => |color| color.toTerminalRGB(),
- .@"cell-foreground" => if (style.flags.inverse) final_bg else fg_style,
- .@"cell-background" => if (style.flags.inverse) fg_style else final_bg,
- } else state.colors.background,
-
- .search => switch (self.config.search_foreground) {
- .color => |color| color.toTerminalRGB(),
- .@"cell-foreground" => if (style.flags.inverse) final_bg else fg_style,
- .@"cell-background" => if (style.flags.inverse) fg_style else final_bg,
- },
-
- .search_selected => switch (self.config.search_selected_foreground) {
- .color => |color| color.toTerminalRGB(),
- .@"cell-foreground" => if (style.flags.inverse) final_bg else fg_style,
- .@"cell-background" => if (style.flags.inverse) fg_style else final_bg,
- },
-
- .false => if (style.flags.inverse)
- final_bg
- else
- fg_style,
- };
- };
-
- // Foreground alpha for this cell.
- const alpha: u8 = if (style.flags.faint) self.config.faint_opacity else 255;
-
- // Set the cell's background color.
- {
- const rgb = bg orelse state.colors.background;
-
- // Determine our background alpha. If we have transparency configured
- // then this is dynamic depending on some situations. This is all
- // in an attempt to make transparency look the best for various
- // situations. See inline comments.
- const bg_alpha: u8 = bg_alpha: {
- const default: u8 = 255;
-
- // Cells that are selected should be fully opaque.
- if (selected != .false) break :bg_alpha default;
-
- // Cells that are reversed should be fully opaque.
- if (style.flags.inverse) break :bg_alpha default;
-
- // If the user requested to have opacity on all cells, apply it.
- if (self.config.background_opacity_cells and bg_style != null) {
- var opacity: f64 = @floatFromInt(default);
- opacity *= self.config.background_opacity;
- break :bg_alpha @intFromFloat(opacity);
- }
-
- // Cells that have an explicit bg color should be fully opaque.
- if (bg_style != null) break :bg_alpha default;
-
- // Otherwise, we won't draw the bg for this cell,
- // we'll let the already-drawn background color
- // show through.
- break :bg_alpha 0;
- };
-
- self.cells.bgCell(y, x).* = .{
- rgb.r, rgb.g, rgb.b, bg_alpha,
- };
- }
-
- // If the invisible flag is set on this cell then we
- // don't need to render any foreground elements, so
- // we just skip all glyphs with this x coordinate.
- //
- // NOTE: This behavior matches xterm. Some other terminal
- // emulators, e.g. Alacritty, still render text decorations
- // and only make the text itself invisible. The decision
- // has been made here to match xterm's behavior for this.
- if (style.flags.invisible) {
- continue;
- }
-
- // Give links a single underline, unless they already have
- // an underline, in which case use a double underline to
- // distinguish them.
- const underline: terminal.Attribute.Underline = underline: {
- if (links.contains(.{
- .x = @intCast(x),
- .y = @intCast(y),
- })) {
- break :underline if (style.flags.underline == .single)
- .double
- else
- .single;
- }
- break :underline style.flags.underline;
- };
-
- // We draw underlines first so that they layer underneath text.
- // This improves readability when a colored underline is used
- // which intersects parts of the text (descenders).
- if (underline != .none) self.addUnderline(
- @intCast(x),
- @intCast(y),
- underline,
- style.underlineColor(&state.colors.palette) orelse fg,
- alpha,
- ) catch |err| {
- log.warn(
- "error adding underline to cell, will be invalid x={} y={}, err={}",
- .{ x, y, err },
- );
- };
-
- if (style.flags.overline) self.addOverline(@intCast(x), @intCast(y), fg, alpha) catch |err| {
- log.warn(
- "error adding overline to cell, will be invalid x={} y={}, err={}",
- .{ x, y, err },
- );
- };
-
- // If we're at or past the end of our shaper run then
- // we need to get the next run from the run iterator.
- if (shaper_cells != null and shaper_cells_i >= shaper_cells.?.len) {
- shaper_run = try run_iter.next(self.alloc);
- shaper_cells = null;
- shaper_cells_i = 0;
- }
-
- if (shaper_run) |run| glyphs: {
- // If we haven't shaped this run yet, do so.
- shaper_cells = shaper_cells orelse
- // Try to read the cells from the shaping cache if we can.
- self.font_shaper_cache.get(run) orelse
- cache: {
- // Otherwise we have to shape them.
- const new_cells = try self.font_shaper.shape(run);
-
- // Try to cache them. If caching fails for any reason we
- // continue because it is just a performance optimization,
- // not a correctness issue.
- self.font_shaper_cache.put(
- self.alloc,
- run,
- new_cells,
- ) catch |err| {
- log.warn(
- "error caching font shaping results err={}",
- .{err},
- );
- };
-
- // The cells we get from direct shaping are always owned
- // by the shaper and valid until the next shaping call so
- // we can safely use them.
- break :cache new_cells;
- };
-
- const shaped_cells = shaper_cells orelse break :glyphs;
-
- // If there are no shaper cells for this run, ignore it.
- // This can occur for runs of empty cells, and is fine.
- if (shaped_cells.len == 0) break :glyphs;
-
- // If we encounter a shaper cell to the left of the current
- // cell then we have some problems. This logic relies on x
- // position monotonically increasing.
- assert(run.offset + shaped_cells[shaper_cells_i].x >= x);
-
- // NOTE: An assumption is made here that a single cell will never
- // be present in more than one shaper run. If that assumption is
- // violated, this logic breaks.
-
- while (shaper_cells_i < shaped_cells.len and
- run.offset + shaped_cells[shaper_cells_i].x == x) : ({
- shaper_cells_i += 1;
- }) {
- self.addGlyph(
- @intCast(x),
- @intCast(y),
- state.cols,
- cells_raw,
- shaped_cells[shaper_cells_i],
- shaper_run.?,
- fg,
- alpha,
- ) catch |err| {
- log.warn(
- "error adding glyph to cell, will be invalid x={} y={}, err={}",
- .{ x, y, err },
- );
- };
- }
- }
-
- // Finally, draw a strikethrough if necessary.
- if (style.flags.strikethrough) self.addStrikethrough(
- @intCast(x),
- @intCast(y),
- fg,
- alpha,
- ) catch |err| {
- log.warn(
- "error adding strikethrough to cell, will be invalid x={} y={}, err={}",
- .{ x, y, err },
- );
- };
- }
}
// Setup our cursor rendering information.
@@ -3084,6 +2842,454 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
// });
}
+ fn rebuildRow(
+ self: *Self,
+ y: terminal.size.CellCountInt,
+ row: terminal.page.Row,
+ cells: *std.MultiArrayList(terminal.RenderState.Cell),
+ preedit_range: ?PreeditRange,
+ selection: ?[2]terminal.size.CellCountInt,
+ highlights: *const std.ArrayList(terminal.RenderState.Highlight),
+ links: *const terminal.RenderState.CellSet,
+ ) !void {
+ const state = &self.terminal_state;
+
+ // If our viewport is wider than our cell contents buffer,
+ // we still only process cells up to the width of the buffer.
+ const cells_slice = cells.slice();
+ const cells_len = @min(cells_slice.len, self.cells.size.columns);
+ const cells_raw = cells_slice.items(.raw);
+ const cells_style = cells_slice.items(.style);
+
+ // On primary screen, we still apply vertical padding
+ // extension under certain conditions we feel are safe.
+ //
+ // This helps make some scenarios look better while
+ // avoiding scenarios we know do NOT look good.
+ switch (self.config.padding_color) {
+ // These already have the correct values set above.
+ .background, .@"extend-always" => {},
+
+ // Apply heuristics for padding extension.
+ .extend => if (y == 0) {
+ self.uniforms.padding_extend.up = !rowNeverExtendBg(
+ row,
+ cells_raw,
+ cells_style,
+ &state.colors.palette,
+ state.colors.background,
+ );
+ } else if (y == self.cells.size.rows - 1) {
+ self.uniforms.padding_extend.down = !rowNeverExtendBg(
+ row,
+ cells_raw,
+ cells_style,
+ &state.colors.palette,
+ state.colors.background,
+ );
+ },
+ }
+
+ // Iterator of runs for shaping.
+ var run_iter_opts: font.shape.RunOptions = .{
+ .grid = self.font_grid,
+ .cells = cells_slice,
+ .selection = if (selection) |s| s else null,
+
+ // We want to do font shaping as long as the cursor is
+ // visible on this viewport.
+ .cursor_x = cursor_x: {
+ const vp = state.cursor.viewport orelse break :cursor_x null;
+ if (vp.y != y) break :cursor_x null;
+ break :cursor_x vp.x;
+ },
+ };
+ run_iter_opts.applyBreakConfig(self.config.font_shaping_break);
+ var run_iter = self.font_shaper.runIterator(run_iter_opts);
+ var shaper_run: ?font.shape.TextRun = try run_iter.next(self.alloc);
+ var shaper_cells: ?[]const font.shape.Cell = null;
+ var shaper_cells_i: usize = 0;
+
+ for (
+ 0..,
+ cells_raw[0..cells_len],
+ cells_style[0..cells_len],
+ ) |x, *cell, *managed_style| {
+ // If this cell falls within our preedit range then we
+ // skip this because preedits are setup separately.
+ if (preedit_range) |range| preedit: {
+ // We're not on the preedit line, no actions necessary.
+ if (range.y != y) break :preedit;
+ // We're before the preedit range, no actions necessary.
+ if (x < range.x[0]) break :preedit;
+ // We're in the preedit range, skip this cell.
+ if (x <= range.x[1]) continue;
+ // After exiting the preedit range we need to catch
+ // the run position up because of the missed cells.
+ // In all other cases, no action is necessary.
+ if (x != range.x[1] + 1) break :preedit;
+
+ // Step the run iterator until we find a run that ends
+ // after the current cell, which will be the soonest run
+ // that might contain glyphs for our cell.
+ while (shaper_run) |run| {
+ if (run.offset + run.cells > x) break;
+ shaper_run = try run_iter.next(self.alloc);
+ shaper_cells = null;
+ shaper_cells_i = 0;
+ }
+
+ const run = shaper_run orelse break :preedit;
+
+ // If we haven't shaped this run, do so now.
+ shaper_cells = shaper_cells orelse
+ // Try to read the cells from the shaping cache if we can.
+ self.font_shaper_cache.get(run) orelse
+ cache: {
+ // Otherwise we have to shape them.
+ const new_cells = try self.font_shaper.shape(run);
+
+ // Try to cache them. If caching fails for any reason we
+ // continue because it is just a performance optimization,
+ // not a correctness issue.
+ self.font_shaper_cache.put(
+ self.alloc,
+ run,
+ new_cells,
+ ) catch |err| {
+ log.warn(
+ "error caching font shaping results err={}",
+ .{err},
+ );
+ };
+
+ // The cells we get from direct shaping are always owned
+ // by the shaper and valid until the next shaping call so
+ // we can safely use them.
+ break :cache new_cells;
+ };
+
+ // Advance our index until we reach or pass
+ // our current x position in the shaper cells.
+ const shaper_cells_unwrapped = shaper_cells.?;
+ while (run.offset + shaper_cells_unwrapped[shaper_cells_i].x < x) {
+ shaper_cells_i += 1;
+ }
+ }
+
+ const wide = cell.wide;
+ const style: terminal.Style = if (cell.hasStyling())
+ managed_style.*
+ else
+ .{};
+
+ // True if this cell is selected
+ const selected: enum {
+ false,
+ selection,
+ search,
+ search_selected,
+ } = selected: {
+ // Order below matters for precedence.
+
+ // Selection should take the highest precedence.
+ const x_compare = if (wide == .spacer_tail)
+ x -| 1
+ else
+ x;
+ if (selection) |sel| {
+ if (x_compare >= sel[0] and
+ x_compare <= sel[1]) break :selected .selection;
+ }
+
+ // If we're highlighted, then we're selected. In the
+ // future we want to use a different style for this
+ // but this to get started.
+ for (highlights.items) |hl| {
+ if (x_compare >= hl.range[0] and
+ x_compare <= hl.range[1])
+ {
+ const tag: HighlightTag = @enumFromInt(hl.tag);
+ break :selected switch (tag) {
+ .search_match => .search,
+ .search_match_selected => .search_selected,
+ };
+ }
+ }
+
+ break :selected .false;
+ };
+
+ // The `_style` suffixed values are the colors based on
+ // the cell style (SGR), before applying any additional
+ // configuration, inversions, selections, etc.
+ const bg_style = style.bg(
+ cell,
+ &state.colors.palette,
+ );
+ const fg_style = style.fg(.{
+ .default = state.colors.foreground,
+ .palette = &state.colors.palette,
+ .bold = self.config.bold_color,
+ });
+
+ // The final background color for the cell.
+ const bg = switch (selected) {
+ // If we have an explicit selection background color
+ // specified in the config, use that.
+ //
+ // If no configuration, then our selection background
+ // is our foreground color.
+ .selection => if (self.config.selection_background) |v| switch (v) {
+ .color => |color| color.toTerminalRGB(),
+ .@"cell-foreground" => if (style.flags.inverse) bg_style else fg_style,
+ .@"cell-background" => if (style.flags.inverse) fg_style else bg_style,
+ } else state.colors.foreground,
+
+ .search => switch (self.config.search_background) {
+ .color => |color| color.toTerminalRGB(),
+ .@"cell-foreground" => if (style.flags.inverse) bg_style else fg_style,
+ .@"cell-background" => if (style.flags.inverse) fg_style else bg_style,
+ },
+
+ .search_selected => switch (self.config.search_selected_background) {
+ .color => |color| color.toTerminalRGB(),
+ .@"cell-foreground" => if (style.flags.inverse) bg_style else fg_style,
+ .@"cell-background" => if (style.flags.inverse) fg_style else bg_style,
+ },
+
+ // Not selected
+ .false => if (style.flags.inverse != isCovering(cell.codepoint()))
+ // Two cases cause us to invert (use the fg color as the bg)
+ // - The "inverse" style flag.
+ // - A "covering" glyph; we use fg for bg in that
+ // case to help make sure that padding extension
+ // works correctly.
+ //
+ // If one of these is true (but not the other)
+ // then we use the fg style color for the bg.
+ fg_style
+ else
+ // Otherwise they cancel out.
+ bg_style,
+ };
+
+ const fg = fg: {
+ // Our happy-path non-selection background color
+ // is our style or our configured defaults.
+ const final_bg = bg_style orelse state.colors.background;
+
+ // Whether we need to use the bg color as our fg color:
+ // - Cell is selected, inverted, and set to cell-foreground
+ // - Cell is selected, not inverted, and set to cell-background
+ // - Cell is inverted and not selected
+ break :fg switch (selected) {
+ .selection => if (self.config.selection_foreground) |v| switch (v) {
+ .color => |color| color.toTerminalRGB(),
+ .@"cell-foreground" => if (style.flags.inverse) final_bg else fg_style,
+ .@"cell-background" => if (style.flags.inverse) fg_style else final_bg,
+ } else state.colors.background,
+
+ .search => switch (self.config.search_foreground) {
+ .color => |color| color.toTerminalRGB(),
+ .@"cell-foreground" => if (style.flags.inverse) final_bg else fg_style,
+ .@"cell-background" => if (style.flags.inverse) fg_style else final_bg,
+ },
+
+ .search_selected => switch (self.config.search_selected_foreground) {
+ .color => |color| color.toTerminalRGB(),
+ .@"cell-foreground" => if (style.flags.inverse) final_bg else fg_style,
+ .@"cell-background" => if (style.flags.inverse) fg_style else final_bg,
+ },
+
+ .false => if (style.flags.inverse)
+ final_bg
+ else
+ fg_style,
+ };
+ };
+
+ // Foreground alpha for this cell.
+ const alpha: u8 = if (style.flags.faint) self.config.faint_opacity else 255;
+
+ // Set the cell's background color.
+ {
+ const rgb = bg orelse state.colors.background;
+
+ // Determine our background alpha. If we have transparency configured
+ // then this is dynamic depending on some situations. This is all
+ // in an attempt to make transparency look the best for various
+ // situations. See inline comments.
+ const bg_alpha: u8 = bg_alpha: {
+ const default: u8 = 255;
+
+ // Cells that are selected should be fully opaque.
+ if (selected != .false) break :bg_alpha default;
+
+ // Cells that are reversed should be fully opaque.
+ if (style.flags.inverse) break :bg_alpha default;
+
+ // If the user requested to have opacity on all cells, apply it.
+ if (self.config.background_opacity_cells and bg_style != null) {
+ var opacity: f64 = @floatFromInt(default);
+ opacity *= self.config.background_opacity;
+ break :bg_alpha @intFromFloat(opacity);
+ }
+
+ // Cells that have an explicit bg color should be fully opaque.
+ if (bg_style != null) break :bg_alpha default;
+
+ // Otherwise, we won't draw the bg for this cell,
+ // we'll let the already-drawn background color
+ // show through.
+ break :bg_alpha 0;
+ };
+
+ self.cells.bgCell(y, x).* = .{
+ rgb.r, rgb.g, rgb.b, bg_alpha,
+ };
+ }
+
+ // If the invisible flag is set on this cell then we
+ // don't need to render any foreground elements, so
+ // we just skip all glyphs with this x coordinate.
+ //
+ // NOTE: This behavior matches xterm. Some other terminal
+ // emulators, e.g. Alacritty, still render text decorations
+ // and only make the text itself invisible. The decision
+ // has been made here to match xterm's behavior for this.
+ if (style.flags.invisible) {
+ continue;
+ }
+
+ // Give links a single underline, unless they already have
+ // an underline, in which case use a double underline to
+ // distinguish them.
+ const underline: terminal.Attribute.Underline = underline: {
+ if (links.contains(.{
+ .x = @intCast(x),
+ .y = @intCast(y),
+ })) {
+ break :underline if (style.flags.underline == .single)
+ .double
+ else
+ .single;
+ }
+ break :underline style.flags.underline;
+ };
+
+ // We draw underlines first so that they layer underneath text.
+ // This improves readability when a colored underline is used
+ // which intersects parts of the text (descenders).
+ if (underline != .none) self.addUnderline(
+ @intCast(x),
+ @intCast(y),
+ underline,
+ style.underlineColor(&state.colors.palette) orelse fg,
+ alpha,
+ ) catch |err| {
+ log.warn(
+ "error adding underline to cell, will be invalid x={} y={}, err={}",
+ .{ x, y, err },
+ );
+ };
+
+ if (style.flags.overline) self.addOverline(@intCast(x), @intCast(y), fg, alpha) catch |err| {
+ log.warn(
+ "error adding overline to cell, will be invalid x={} y={}, err={}",
+ .{ x, y, err },
+ );
+ };
+
+ // If we're at or past the end of our shaper run then
+ // we need to get the next run from the run iterator.
+ if (shaper_cells != null and shaper_cells_i >= shaper_cells.?.len) {
+ shaper_run = try run_iter.next(self.alloc);
+ shaper_cells = null;
+ shaper_cells_i = 0;
+ }
+
+ if (shaper_run) |run| glyphs: {
+ // If we haven't shaped this run yet, do so.
+ shaper_cells = shaper_cells orelse
+ // Try to read the cells from the shaping cache if we can.
+ self.font_shaper_cache.get(run) orelse
+ cache: {
+ // Otherwise we have to shape them.
+ const new_cells = try self.font_shaper.shape(run);
+
+ // Try to cache them. If caching fails for any reason we
+ // continue because it is just a performance optimization,
+ // not a correctness issue.
+ self.font_shaper_cache.put(
+ self.alloc,
+ run,
+ new_cells,
+ ) catch |err| {
+ log.warn(
+ "error caching font shaping results err={}",
+ .{err},
+ );
+ };
+
+ // The cells we get from direct shaping are always owned
+ // by the shaper and valid until the next shaping call so
+ // we can safely use them.
+ break :cache new_cells;
+ };
+
+ const shaped_cells = shaper_cells orelse break :glyphs;
+
+ // If there are no shaper cells for this run, ignore it.
+ // This can occur for runs of empty cells, and is fine.
+ if (shaped_cells.len == 0) break :glyphs;
+
+ // If we encounter a shaper cell to the left of the current
+ // cell then we have some problems. This logic relies on x
+ // position monotonically increasing.
+ assert(run.offset + shaped_cells[shaper_cells_i].x >= x);
+
+ // NOTE: An assumption is made here that a single cell will never
+ // be present in more than one shaper run. If that assumption is
+ // violated, this logic breaks.
+
+ while (shaper_cells_i < shaped_cells.len and
+ run.offset + shaped_cells[shaper_cells_i].x == x) : ({
+ shaper_cells_i += 1;
+ }) {
+ self.addGlyph(
+ @intCast(x),
+ @intCast(y),
+ state.cols,
+ cells_raw,
+ shaped_cells[shaper_cells_i],
+ shaper_run.?,
+ fg,
+ alpha,
+ ) catch |err| {
+ log.warn(
+ "error adding glyph to cell, will be invalid x={} y={}, err={}",
+ .{ x, y, err },
+ );
+ };
+ }
+ }
+
+ // Finally, draw a strikethrough if necessary.
+ if (style.flags.strikethrough) self.addStrikethrough(
+ @intCast(x),
+ @intCast(y),
+ fg,
+ alpha,
+ ) catch |err| {
+ log.warn(
+ "error adding strikethrough to cell, will be invalid x={} y={}, err={}",
+ .{ x, y, err },
+ );
+ };
+ }
+ }
+
/// Add an underline decoration to the specified cell
fn addUnderline(
self: *Self,
@@ -3346,10 +3552,6 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
screen_bg: terminal.color.RGB,
screen_fg: terminal.color.RGB,
) !void {
- // Preedit is rendered inverted
- const bg = screen_fg;
- const fg = screen_bg;
-
// Render the glyph for our preedit text
const render_ = self.font_grid.renderCodepoint(
self.alloc,
@@ -3368,11 +3570,11 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
// Add our opaque background cell
self.cells.bgCell(coord.y, coord.x).* = .{
- bg.r, bg.g, bg.b, 255,
+ screen_bg.r, screen_bg.g, screen_bg.b, 255,
};
if (cp.wide and coord.x < self.cells.size.columns - 1) {
self.cells.bgCell(coord.y, coord.x + 1).* = .{
- bg.r, bg.g, bg.b, 255,
+ screen_bg.r, screen_bg.g, screen_bg.b, 255,
};
}
@@ -3380,7 +3582,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
try self.cells.add(self.alloc, .text, .{
.atlas = .grayscale,
.grid_pos = .{ @intCast(coord.x), @intCast(coord.y) },
- .color = .{ fg.r, fg.g, fg.b, 255 },
+ .color = .{ screen_fg.r, screen_fg.g, screen_fg.b, 255 },
.glyph_pos = .{ render.glyph.atlas_x, render.glyph.atlas_y },
.glyph_size = .{ render.glyph.width, render.glyph.height },
.bearings = .{
@@ -3388,6 +3590,12 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
@intCast(render.glyph.offset_y),
},
});
+
+ // Add underline
+ try self.addUnderline(@intCast(coord.x), @intCast(coord.y), .single, screen_fg, 255);
+ if (cp.wide and coord.x < self.cells.size.columns - 1) {
+ try self.addUnderline(@intCast(coord.x + 1), @intCast(coord.y), .single, screen_fg, 255);
+ }
}
/// Sync the atlas data to the given texture. This copies the bytes
diff --git a/src/renderer/image.zig b/src/renderer/image.zig
index 7089f5a8b..bf0f7b736 100644
--- a/src/renderer/image.zig
+++ b/src/renderer/image.zig
@@ -146,7 +146,7 @@ pub const Image = union(enum) {
/// Mark the current image to be replaced with a pending one. This will
/// attempt to update the existing texture if we have one, otherwise it
/// will act like a new upload.
- pub fn markForReplace(self: *Image, alloc: Allocator, img: Image) !void {
+ pub fn markForReplace(self: *Image, alloc: Allocator, img: Image) void {
assert(img.isPending());
// If we have pending data right now, free it.
@@ -216,9 +216,8 @@ pub const Image = union(enum) {
/// Prepare the pending image data for upload to the GPU.
/// This doesn't need GPU access so is safe to call any time.
- pub fn prepForUpload(self: *Image, alloc: Allocator) !void {
+ pub fn prepForUpload(self: *Image, alloc: Allocator) wuffs.Error!void {
assert(self.isPending());
-
try self.convert(alloc);
}
diff --git a/src/renderer/metal/api.zig b/src/renderer/metal/api.zig
index e1daa6848..a2d8a1356 100644
--- a/src/renderer/metal/api.zig
+++ b/src/renderer/metal/api.zig
@@ -391,6 +391,27 @@ pub const MTLRenderStage = enum(c_ulong) {
mesh = 16,
};
+/// https://developer.apple.com/documentation/metal/mtlgpufamily?language=objc
+pub const MTLGPUFamily = enum(c_long) {
+ apple1 = 1001,
+ apple2 = 1002,
+ apple3 = 1003,
+ apple4 = 1004,
+ apple5 = 1005,
+ apple6 = 1006,
+ apple7 = 1007,
+ apple8 = 1008,
+ apple9 = 1009,
+ apple10 = 1010,
+
+ common1 = 3001,
+ common2 = 3002,
+ common3 = 3003,
+
+ metal3 = 5001,
+ metal4 = 5002,
+};
+
pub const MTLClearColor = extern struct {
red: f64,
green: f64,
diff --git a/src/renderer/shaders/shadertoy_prefix.glsl b/src/renderer/shaders/shadertoy_prefix.glsl
index 6d9cf0f68..661bd233d 100644
--- a/src/renderer/shaders/shadertoy_prefix.glsl
+++ b/src/renderer/shaders/shadertoy_prefix.glsl
@@ -16,6 +16,15 @@ layout(binding = 1, std140) uniform Globals {
uniform vec4 iCurrentCursorColor;
uniform vec4 iPreviousCursorColor;
uniform float iTimeCursorChange;
+ uniform float iTimeFocus;
+ uniform int iFocus;
+ uniform vec3 iPalette[256];
+ uniform vec3 iBackgroundColor;
+ uniform vec3 iForegroundColor;
+ uniform vec3 iCursorColor;
+ uniform vec3 iCursorText;
+ uniform vec3 iSelectionForegroundColor;
+ uniform vec3 iSelectionBackgroundColor;
};
layout(binding = 0) uniform sampler2D iChannel0;
diff --git a/src/renderer/shaders/test_shadertoy_focus.glsl b/src/renderer/shaders/test_shadertoy_focus.glsl
new file mode 100644
index 000000000..9fc2304df
--- /dev/null
+++ b/src/renderer/shaders/test_shadertoy_focus.glsl
@@ -0,0 +1,41 @@
+// Test shader for iTimeFocus and iFocus
+// Shows border when focused, green fade that restarts on each focus gain
+void mainImage(out vec4 fragColor, in vec2 fragCoord) {
+ vec2 uv = fragCoord / iResolution.xy;
+
+ // Sample the terminal content
+ vec4 terminal = texture2D(iChannel0, uv);
+ vec3 color = terminal.rgb;
+
+ if (iFocus > 0) {
+ // FOCUSED: Add border and fading green overlay
+
+ // Calculate time since focus was gained
+ float timeSinceFocus = iTime - iTimeFocus;
+
+ // Green fade: starts at 1.0 (full green), fades to 0.0 over 3 seconds
+ float fadeOut = max(0.0, 1.0 - (timeSinceFocus / 3.0));
+
+ // Add green overlay that fades out
+ color = mix(color, vec3(0.0, 1.0, 0.0), fadeOut * 0.4);
+
+ // Add border (5 pixels)
+ float borderSize = 5.0;
+ vec2 pixelCoord = fragCoord;
+ bool isBorder = pixelCoord.x < borderSize ||
+ pixelCoord.x > iResolution.x - borderSize ||
+ pixelCoord.y < borderSize ||
+ pixelCoord.y > iResolution.y - borderSize;
+
+ if (isBorder) {
+ // Bright cyan border that pulses subtly
+ float pulse = sin(timeSinceFocus * 2.0) * 0.1 + 0.9;
+ color = vec3(0.0, 1.0, 1.0) * pulse;
+ }
+ } else {
+ // UNFOCUSED: Solid red overlay (no border)
+ color = mix(color, vec3(1.0, 0.0, 0.0), 0.3);
+ }
+
+ fragColor = vec4(color, 1.0);
+}
diff --git a/src/renderer/shadertoy.zig b/src/renderer/shadertoy.zig
index 0d096c0fc..7d0ad4b0a 100644
--- a/src/renderer/shadertoy.zig
+++ b/src/renderer/shadertoy.zig
@@ -25,6 +25,15 @@ pub const Uniforms = extern struct {
current_cursor_color: [4]f32 align(16),
previous_cursor_color: [4]f32 align(16),
cursor_change_time: f32 align(4),
+ time_focus: f32 align(4),
+ focus: i32 align(4),
+ palette: [256][4]f32 align(16),
+ background_color: [4]f32 align(16),
+ foreground_color: [4]f32 align(16),
+ cursor_color: [4]f32 align(16),
+ cursor_text: [4]f32 align(16),
+ selection_background_color: [4]f32 align(16),
+ selection_foreground_color: [4]f32 align(16),
};
/// The target to load shaders for.
@@ -412,3 +421,4 @@ test "shadertoy to glsl" {
const test_crt = @embedFile("shaders/test_shadertoy_crt.glsl");
const test_invalid = @embedFile("shaders/test_shadertoy_invalid.glsl");
+const test_focus = @embedFile("shaders/test_shadertoy_focus.glsl");
diff --git a/src/shell-integration/README.md b/src/shell-integration/README.md
index 9c422ef26..8809134d2 100644
--- a/src/shell-integration/README.md
+++ b/src/shell-integration/README.md
@@ -76,6 +76,24 @@ allowing us to automatically integrate with the shell. For details
on the Fish startup process, see the
[Fish documentation](https://fishshell.com/docs/current/language.html).
+### Nushell
+
+For [Nushell](https://www.nushell.sh/), Ghostty prepends to the
+`XDG_DATA_DIRS` directory, making the `ghostty` module available through
+Nushell's vendor autoload mechanism. Ghostty then automatically imports
+the module using the `-e "use ghostty *"` flag when starting Nushell.
+
+Nushell provides many shell features itself, such as `title` and `cursor`,
+so our integration focuses on Ghostty-specific features like `sudo`.
+
+The shell integration is automatically enabled when running Nushell in Ghostty,
+but you can also load it manually is shell integration is disabled:
+
+```nushell
+source $GHOSTTY_RESOURCES_DIR/shell-integration/nushell/vendor/autoload/ghostty.nu
+use ghostty *
+```
+
### Zsh
Automatic [Zsh](https://www.zsh.org/) integration works by temporarily setting
diff --git a/src/shell-integration/nushell/vendor/autoload/ghostty.nu b/src/shell-integration/nushell/vendor/autoload/ghostty.nu
new file mode 100644
index 000000000..467e3f529
--- /dev/null
+++ b/src/shell-integration/nushell/vendor/autoload/ghostty.nu
@@ -0,0 +1,35 @@
+# Ghostty shell integration
+export module ghostty {
+ def has_feature [feature: string] {
+ $feature in ($env.GHOSTTY_SHELL_FEATURES | default "" | split row ',')
+ }
+
+ # Wrap `sudo` to preserve Ghostty's TERMINFO environment variable
+ export def --wrapped sudo [
+ ...args # Arguments to pass to `sudo`
+ ] {
+ mut sudo_args = $args
+
+ if (has_feature "sudo") {
+ # Extract just the sudo options (before the command)
+ let sudo_options = ($args | take until {|arg|
+ not (($arg | str starts-with "-") or ($arg | str contains "="))
+ })
+
+ # Prepend TERMINFO preservation flag if not using sudoedit
+ if (not ("-e" in $sudo_options or "--edit" in $sudo_options)) {
+ $sudo_args = ($args | prepend "--preserve-env=TERMINFO")
+ }
+ }
+
+ ^sudo ...$sudo_args
+ }
+}
+
+# Clean up XDG_DATA_DIRS by removing GHOSTTY_SHELL_INTEGRATION_XDG_DIR
+if 'GHOSTTY_SHELL_INTEGRATION_XDG_DIR' in $env {
+ if 'XDG_DATA_DIRS' in $env {
+ $env.XDG_DATA_DIRS = ($env.XDG_DATA_DIRS | str replace $"($env.GHOSTTY_SHELL_INTEGRATION_XDG_DIR):" "")
+ }
+ hide-env GHOSTTY_SHELL_INTEGRATION_XDG_DIR
+}
diff --git a/src/shell-integration/zsh/ghostty-integration b/src/shell-integration/zsh/ghostty-integration
index febf3e59c..3fb3ec19b 100644
--- a/src/shell-integration/zsh/ghostty-integration
+++ b/src/shell-integration/zsh/ghostty-integration
@@ -201,11 +201,11 @@ _ghostty_deferred_init() {
_ghostty_report_pwd
if [[ "$GHOSTTY_SHELL_FEATURES" == *"title"* ]]; then
- # Enable terminal title changes.
+ # Enable terminal title changes, formatted for user-friendly display.
functions[_ghostty_precmd]+="
builtin print -rnu $_ghostty_fd \$'\\e]2;'\"\${(%):-%(4~|โฆ/%3~|%~)}\"\$'\\a'"
functions[_ghostty_preexec]+="
- builtin print -rnu $_ghostty_fd \$'\\e]2;'\"\${(V)1}\"\$'\\a'"
+ builtin print -rnu $_ghostty_fd \$'\\e]2;'\"\${1//[[:cntrl:]]}\"\$'\\a'"
fi
if [[ "$GHOSTTY_SHELL_FEATURES" == *"cursor"* ]]; then
diff --git a/src/terminal/PageList.zig b/src/terminal/PageList.zig
index 9e14e2a75..41f8d6533 100644
--- a/src/terminal/PageList.zig
+++ b/src/terminal/PageList.zig
@@ -4,6 +4,7 @@
const PageList = @This();
const std = @import("std");
+const builtin = @import("builtin");
const build_options = @import("terminal_options");
const Allocator = std.mem.Allocator;
const assert = @import("../quirks.zig").inlineAssert;
@@ -48,7 +49,12 @@ const Node = struct {
/// The memory pool we get page nodes from.
const NodePool = std.heap.MemoryPool(List.Node);
+/// The standard page capacity that we use as a starting point for
+/// all pages. This is chosen as a sane default that fits most terminal
+/// usage to support using our pool.
const std_capacity = pagepkg.std_capacity;
+
+/// The byte size required for a standard page.
const std_size = Page.layout(std_capacity).total_size;
/// The memory pool we use for page memory buffers. We use a separate pool
@@ -108,7 +114,6 @@ pub const MemoryPool = struct {
/// The memory pool we get page nodes, pages from.
pool: MemoryPool,
-pool_owned: bool,
/// The list of pages in the screen.
pages: List,
@@ -223,19 +228,30 @@ pub const Viewport = union(enum) {
/// But this gives us a nice fast heuristic for determining min/max size.
/// Therefore, if the page size is violated you should always also verify
/// that we have enough space for the active area.
-fn minMaxSize(cols: size.CellCountInt, rows: size.CellCountInt) !usize {
+fn minMaxSize(cols: size.CellCountInt, rows: size.CellCountInt) usize {
+ // Invariant required to ensure our divCeil below cannot overflow.
+ comptime {
+ const max_rows = std.math.maxInt(size.CellCountInt);
+ _ = std.math.divCeil(usize, max_rows, 1) catch unreachable;
+ }
+
// Get our capacity to fit our rows. If the cols are too big, it may
// force less rows than we want meaning we need more than one page to
// represent a viewport.
- const cap = try std_capacity.adjust(.{ .cols = cols });
+ const cap = initialCapacity(cols);
// Calculate the number of standard sized pages we need to represent
// an active area.
- const pages_exact = if (cap.rows >= rows) 1 else try std.math.divCeil(
+ const pages_exact = if (cap.rows >= rows) 1 else std.math.divCeil(
usize,
rows,
cap.rows,
- );
+ ) catch {
+ // Not possible:
+ // - initialCapacity guarantees at least 1 row
+ // - numerator/denominator can't overflow because of comptime check above
+ unreachable;
+ };
// We always need at least one page extra so that we
// can fit partial pages to spread our active area across two pages.
@@ -255,6 +271,65 @@ fn minMaxSize(cols: size.CellCountInt, rows: size.CellCountInt) !usize {
return PagePool.item_size * pages;
}
+/// Calculates the initial capacity for a new page for a given column
+/// count. This will attempt to fit within std_size at all times so we
+/// can use our memory pool, but if cols is too big, this will return a
+/// larger capacity.
+///
+/// The returned capacity is always guaranteed to layout properly (not
+/// overflow). We are able to support capacities up to the maximum int
+/// value of cols, so this will never overflow.
+fn initialCapacity(cols: size.CellCountInt) Capacity {
+ // This is an important invariant that ensures that this function
+ // can never return an error. We verify here that our standard capacity
+ // when increased to maximum possible columns can always support at
+ // least one row in memory.
+ //
+ // IF THIS EVER FAILS: We probably need to modify our logic below
+ // to reduce other elements of the capacity (styles, graphemes, etc.).
+ // But, instead, I recommend taking a step back and re-evaluating
+ // life choices.
+ comptime {
+ var cap = std_capacity;
+ cap.cols = std.math.maxInt(size.CellCountInt);
+ const layout = Page.layout(cap);
+ assert(layout.total_size <= size.max_page_size);
+ }
+
+ if (std_capacity.adjust(
+ .{ .cols = cols },
+ )) |cap| {
+ // If we can adjust our standard capacity, we fit within the
+ // standard size and we're good!
+ return cap;
+ } else |err| {
+ // Ensure our error set doesn't change.
+ comptime assert(@TypeOf(err) == error{OutOfMemory});
+ }
+
+ // This code path means that our standard capacity can't even
+ // accommodate our column count! The only solution is to increase
+ // our capacity and go non-standard.
+ var cap: Capacity = std_capacity;
+ cap.cols = cols;
+ return cap;
+}
+
+/// This is the page allocator we'll use for all our underlying
+/// VM page allocations.
+inline fn pageAllocator() Allocator {
+ // In tests we use our testing allocator so we can detect leaks.
+ if (builtin.is_test) return std.testing.allocator;
+
+ // On non-macOS we use our standard Zig page allocator.
+ if (!builtin.target.os.tag.isDarwin()) return std.heap.page_allocator;
+
+ // On macOS we want to tag our memory so we can assign it to our
+ // core terminal usage.
+ const mach = @import("../os/mach.zig");
+ return mach.taggedPageAllocator(.application_specific_1);
+}
+
/// Initialize the page. The top of the first page in the list is always the
/// top of the active area of the screen (important knowledge for quickly
/// setting up cursors in Screen).
@@ -280,7 +355,11 @@ pub fn init(
// The screen starts with a single page that is the entire viewport,
// and we'll split it thereafter if it gets too large and add more as
// necessary.
- var pool = try MemoryPool.init(alloc, std.heap.page_allocator, page_preheat);
+ var pool = try MemoryPool.init(
+ alloc,
+ pageAllocator(),
+ page_preheat,
+ );
errdefer pool.deinit();
var page_serial: u64 = 0;
const page_list, const page_size = try initPages(
@@ -291,7 +370,7 @@ pub fn init(
);
// Get our minimum max size, see doc comments for more details.
- const min_max_size = try minMaxSize(cols, rows);
+ const min_max_size = minMaxSize(cols, rows);
// We always track our viewport pin to ensure this is never an allocation
const viewport_pin = try pool.pins.create();
@@ -304,7 +383,6 @@ pub fn init(
.cols = cols,
.rows = rows,
.pool = pool,
- .pool_owned = true,
.pages = page_list,
.page_serial = page_serial,
.page_serial_min = 0,
@@ -326,17 +404,35 @@ fn initPages(
serial: *u64,
cols: size.CellCountInt,
rows: size.CellCountInt,
-) !struct { List, usize } {
+) Allocator.Error!struct { List, usize } {
var page_list: List = .{};
var page_size: usize = 0;
// Add pages as needed to create our initial viewport.
- const cap = try std_capacity.adjust(.{ .cols = cols });
+ const cap = initialCapacity(cols);
+ const layout = Page.layout(cap);
+ const pooled = layout.total_size <= std_size;
+ const page_alloc = pool.pages.arena.child_allocator;
+
+ // Guaranteed by comptime checks in initialCapacity but
+ // redundant here for safety.
+ assert(layout.total_size <= size.max_page_size);
+
var rem = rows;
while (rem > 0) {
const node = try pool.nodes.create();
- const page_buf = try pool.pages.create();
- // no errdefer because the pool deinit will clean these up
+ const page_buf = if (pooled)
+ try pool.pages.create()
+ else
+ try page_alloc.alignedAlloc(
+ u8,
+ .fromByteUnits(std.heap.page_size_min),
+ layout.total_size,
+ );
+ errdefer if (pooled)
+ pool.pages.destroy(page_buf)
+ else
+ page_alloc.free(page_buf);
// In runtime safety modes we have to memset because the Zig allocator
// interface will always memset to 0xAA for undefined. In non-safe modes
@@ -346,10 +442,7 @@ fn initPages(
// Initialize the first set of pages to contain our viewport so that
// the top of the first page is always the active area.
node.* = .{
- .data = .initBuf(
- .init(page_buf),
- Page.layout(cap),
- ),
+ .data = .initBuf(.init(page_buf), layout),
.serial = serial.*,
};
node.data.size.rows = @min(rem, node.data.capacity.rows);
@@ -392,9 +485,11 @@ pub inline fn pauseIntegrityChecks(self: *PageList, pause: bool) void {
}
const IntegrityError = error{
- TotalRowsMismatch,
- ViewportPinOffsetMismatch,
PageSerialInvalid,
+ TotalRowsMismatch,
+ TrackedPinInvalid,
+ ViewportPinOffsetMismatch,
+ ViewportPinInsufficientRows,
};
/// Verify the integrity of the PageList. This is expensive and should
@@ -435,9 +530,13 @@ fn verifyIntegrity(self: *const PageList) IntegrityError!void {
return IntegrityError.TotalRowsMismatch;
}
- // Verify that our viewport pin row offset is correct.
- if (self.viewport == .pin) pin: {
- const cached_offset = self.viewport_pin_row_offset orelse break :pin;
+ // Verify that all our tracked pins point to valid pages.
+ for (self.tracked_pins.keys()) |p| {
+ if (!self.pinIsValid(p.*)) return error.TrackedPinInvalid;
+ }
+
+ if (self.viewport == .pin) {
+ // Verify that our viewport pin row offset is correct.
const actual_offset: usize = offset: {
var offset: usize = 0;
var node = self.pages.last;
@@ -456,12 +555,24 @@ fn verifyIntegrity(self: *const PageList) IntegrityError!void {
return error.ViewportPinOffsetMismatch;
};
- if (cached_offset != actual_offset) {
+ if (self.viewport_pin_row_offset) |cached_offset| {
+ if (cached_offset != actual_offset) {
+ log.warn(
+ "PageList integrity violation: viewport pin offset mismatch cached={} actual={}",
+ .{ cached_offset, actual_offset },
+ );
+ return error.ViewportPinOffsetMismatch;
+ }
+ }
+
+ // Ensure our viewport has enough rows.
+ const rows = self.total_rows - actual_offset;
+ if (rows < self.rows) {
log.warn(
- "PageList integrity violation: viewport pin offset mismatch cached={} actual={}",
- .{ cached_offset, actual_offset },
+ "PageList integrity violation: viewport pin rows too small rows={} needed={}",
+ .{ rows, self.rows },
);
- return error.ViewportPinOffsetMismatch;
+ return error.ViewportPinInsufficientRows;
}
}
}
@@ -487,11 +598,7 @@ pub fn deinit(self: *PageList) void {
// Deallocate all the pages. We don't need to deallocate the list or
// nodes because they all reside in the pool.
- if (self.pool_owned) {
- self.pool.deinit();
- } else {
- self.pool.reset(.{ .retain_capacity = {} });
- }
+ self.pool.deinit();
}
/// Reset the PageList back to an empty state. This is similar to
@@ -507,10 +614,8 @@ pub fn reset(self: *PageList) void {
// We need enough pages/nodes to keep our active area. This should
// never fail since we by definition have allocated a page already
// that fits our size but I'm not confident to make that assertion.
- const cap = std_capacity.adjust(
- .{ .cols = self.cols },
- ) catch @panic("reset: std_capacity.adjust failed");
- assert(cap.rows > 0); // adjust should never return 0 rows
+ const cap = initialCapacity(self.cols);
+ assert(cap.rows > 0);
// The number of pages we need is the number of rows in the active
// area divided by the row capacity of a page.
@@ -609,14 +714,6 @@ pub const Clone = struct {
top: point.Point,
bot: ?point.Point = null,
- /// The allocator source for the clone operation. If this is alloc
- /// then the cloned pagelist will own and dealloc the memory on deinit.
- /// If this is pool then the caller owns the memory.
- memory: union(enum) {
- alloc: Allocator,
- pool: *MemoryPool,
- },
-
// If this is non-null then cloning will attempt to remap the tracked
// pins into the new cloned area and will keep track of the old to
// new mapping in this map. If this is null, the cloned pagelist will
@@ -638,40 +735,29 @@ pub const Clone = struct {
/// rows will be added to the bottom of the region to make up the difference.
pub fn clone(
self: *const PageList,
+ alloc: Allocator,
opts: Clone,
) !PageList {
var it = self.pageIterator(.right_down, opts.top, opts.bot);
- // Setup our own memory pool if we have to.
- var owned_pool: ?MemoryPool = switch (opts.memory) {
- .pool => null,
- .alloc => |alloc| alloc: {
- // First, count our pages so our preheat is exactly what we need.
- var it_copy = it;
- const page_count: usize = page_count: {
- var count: usize = 0;
- while (it_copy.next()) |_| count += 1;
- break :page_count count;
- };
-
- // Setup our pools
- break :alloc try .init(
- alloc,
- std.heap.page_allocator,
- page_count,
- );
- },
- };
- errdefer if (owned_pool) |*pool| pool.deinit();
-
- // Create our memory pool we use
- const pool: *MemoryPool = switch (opts.memory) {
- .pool => |v| v,
- .alloc => &owned_pool.?,
+ // First, count our pages so our preheat is exactly what we need.
+ var it_copy = it;
+ const page_count: usize = page_count: {
+ var count: usize = 0;
+ while (it_copy.next()) |_| count += 1;
+ break :page_count count;
};
- // Our viewport pin is always undefined since our viewport in a clones
- // goes back to the top
+ // Setup our pool
+ var pool: MemoryPool = try .init(
+ alloc,
+ pageAllocator(),
+ page_count,
+ );
+ errdefer pool.deinit();
+
+ // Create our viewport. In a clone, the viewport always goes
+ // to the top.
const viewport_pin = try pool.pins.create();
var tracked_pins: PinSet = .{};
errdefer tracked_pins.deinit(pool.alloc);
@@ -697,7 +783,7 @@ pub fn clone(
// Clone the page. We have to use createPageExt here because
// we don't know if the source page has a standard size.
const node = try createPageExt(
- pool,
+ &pool,
chunk.node.data.capacity,
&page_serial,
&page_size,
@@ -737,12 +823,12 @@ pub fn clone(
}
}
+ // Initialize our viewport pin to point to the first cloned page
+ // so it points to valid memory.
+ viewport_pin.* = .{ .node = page_list.first.? };
+
var result: PageList = .{
- .pool = pool.*,
- .pool_owned = switch (opts.memory) {
- .pool => false,
- .alloc => true,
- },
+ .pool = pool,
.pages = page_list,
.page_serial = page_serial,
.page_serial_min = 0,
@@ -803,7 +889,7 @@ pub const Resize = struct {
/// Resize
/// TODO: docs
-pub fn resize(self: *PageList, opts: Resize) !void {
+pub fn resize(self: *PageList, opts: Resize) Allocator.Error!void {
defer self.assertIntegrity();
if (comptime std.debug.runtime_safety) {
@@ -825,7 +911,7 @@ pub fn resize(self: *PageList, opts: Resize) !void {
// when increasing beyond our initial minimum max size or explicit max
// size to fit the active area.
const old_min_max_size = self.min_max_size;
- self.min_max_size = try minMaxSize(
+ self.min_max_size = minMaxSize(
opts.cols orelse self.cols,
opts.rows orelse self.rows,
);
@@ -856,6 +942,16 @@ pub fn resize(self: *PageList, opts: Resize) !void {
try self.resizeCols(cols, opts.cursor);
},
}
+
+ // Various resize operations can change our total row count such
+ // that our viewport pin is now in the active area and has insufficient
+ // space. We need to check for this case and fix it up.
+ switch (self.viewport) {
+ .pin => if (self.pinIsActive(self.viewport_pin.*)) {
+ self.viewport = .active;
+ },
+ .active, .top => {},
+ }
}
/// Resize the pagelist with reflow by adding or removing columns.
@@ -863,7 +959,7 @@ fn resizeCols(
self: *PageList,
cols: size.CellCountInt,
cursor: ?Resize.Cursor,
-) !void {
+) Allocator.Error!void {
assert(cols != self.cols);
// Update our cols. We have to do this early because grow() that we
@@ -911,32 +1007,71 @@ fn resizeCols(
} else null;
defer if (preserved_cursor) |c| self.untrackPin(c.tracked_pin);
- const first = self.pages.first.?;
- var it = self.rowIterator(.right_down, .{ .screen = .{} }, null);
+ // Create the first node that contains our reflow.
+ const first_rewritten_node = node: {
+ const page = &self.pages.first.?.data;
+ const cap = page.capacity.adjust(
+ .{ .cols = cols },
+ ) catch |err| err: {
+ comptime assert(@TypeOf(err) == error{OutOfMemory});
- const dst_node = try self.createPage(try first.data.capacity.adjust(.{ .cols = cols }));
- dst_node.data.size.rows = 1;
+ // We verify all maxed out page layouts work.
+ var cap = page.capacity;
+ cap.cols = cols;
+
+ // We're growing columns so we can only get less rows so use
+ // the lesser of our capacity and size so we minimize wasted
+ // rows.
+ cap.rows = @min(page.size.rows, cap.rows);
+ break :err cap;
+ };
+
+ const node = try self.createPage(cap);
+ node.data.size.rows = 1;
+ break :node node;
+ };
+
+ // We need to grab our rowIterator now before we rewrite our
+ // linked list below.
+ var it = self.rowIterator(
+ .right_down,
+ .{ .screen = .{} },
+ null,
+ );
+ errdefer {
+ // If an error occurs, we're in a pretty disastrous broken state,
+ // but we should still try to clean up our leaked memory. Free
+ // any of the remaining orphaned pages from before. If we reflowed
+ // successfully this will be null.
+ var node_: ?*Node = if (it.chunk) |chunk| chunk.node else null;
+ while (node_) |node| {
+ node_ = node.next;
+ self.destroyNode(node);
+ }
+ }
// Set our new page as the only page. This orphans the existing pages
// in the list, but that's fine since we're gonna delete them anyway.
- self.pages.first = dst_node;
- self.pages.last = dst_node;
+ self.pages.first = first_rewritten_node;
+ self.pages.last = first_rewritten_node;
// Reflow all our rows.
{
- var dst_cursor = ReflowCursor.init(dst_node);
+ var reflow_cursor: ReflowCursor = .init(first_rewritten_node);
while (it.next()) |row| {
- try dst_cursor.reflowRow(self, row);
+ try reflow_cursor.reflowRow(self, row);
- // Once we're done reflowing a page, destroy it.
+ // Once we're done reflowing a page, destroy it immediately.
+ // This frees memory and makes it more likely in memory
+ // constrained environments that the next reflow will work.
if (row.y == row.node.data.size.rows - 1) {
self.destroyNode(row.node);
}
}
// At the end of the reflow, setup our total row cache
- // log.warn("total old={} new={}", .{ self.total_rows, dst_cursor.total_rows });
- self.total_rows = dst_cursor.total_rows;
+ // log.warn("total old={} new={}", .{ self.total_rows, reflow_cursor.total_rows });
+ self.total_rows = reflow_cursor.total_rows;
}
// If our total rows is less than our active rows, we need to grow.
@@ -1029,20 +1164,15 @@ const ReflowCursor = struct {
self: *ReflowCursor,
list: *PageList,
row: Pin,
- ) !void {
+ ) Allocator.Error!void {
const src_page: *Page = &row.node.data;
const src_row = row.rowAndCell().row;
const src_y = row.y;
-
- // Inherit increased styles or grapheme bytes from
- // the src page we're reflowing from for new pages.
- const cap = try src_page.capacity.adjust(.{ .cols = self.page.size.cols });
-
const cells = src_row.cells.ptr(src_page.memory)[0..src_page.size.cols];
+ // Calculate the columns in this row. First up we trim non-semantic
+ // rightmost blanks.
var cols_len = src_page.size.cols;
-
- // If the row is wrapped, all empty cells are meaningful.
if (!src_row.wrap) {
while (cols_len > 0) {
if (!cells[cols_len - 1].isEmpty()) break;
@@ -1064,9 +1194,10 @@ const ReflowCursor = struct {
// If this pin is in the blanks on the right and past the end
// of the dst col width then we move it to the end of the dst
// col width instead.
- if (p.x >= cols_len) {
- p.x = @min(p.x, cap.cols - 1 - self.x);
- }
+ if (p.x >= cols_len) p.x = @min(
+ p.x,
+ self.page.size.cols - 1 - self.x,
+ );
// We increase our col len to at least include this pin.
// This ensures that blank rows with pins are processed,
@@ -1081,16 +1212,29 @@ const ReflowCursor = struct {
// If this blank row was a wrap continuation somehow
// then we won't need to write it since it should be
// a part of the previously written row.
- if (!src_row.wrap_continuation) {
- self.new_rows += 1;
- }
+ if (!src_row.wrap_continuation) self.new_rows += 1;
return;
}
+ // Inherit increased styles or grapheme bytes from the src page
+ // we're reflowing from for new pages.
+ const cap = src_page.capacity.adjust(
+ .{ .cols = self.page.size.cols },
+ ) catch |err| err: {
+ comptime assert(@TypeOf(err) == error{OutOfMemory});
+
+ var cap = src_page.capacity;
+ cap.cols = self.page.size.cols;
+ // We're already a non-standard page. We don't want to
+ // inherit a massive set of rows, so cap it at our std size.
+ cap.rows = @min(src_page.size.rows, std_capacity.rows);
+ break :err cap;
+ };
+
// Our row isn't blank, write any new rows we deferred.
while (self.new_rows > 0) {
- self.new_rows -= 1;
try self.cursorScrollOrNewPage(list, cap);
+ self.new_rows -= 1;
}
self.copyRowMetadata(src_row);
@@ -1118,8 +1262,93 @@ const ReflowCursor = struct {
}
}
- const cell = &cells[x];
- x += 1;
+ if (self.writeCell(
+ list,
+ &cells[x],
+ src_page,
+ )) |result| switch (result) {
+ // Wrote the cell, move to the next.
+ .success => x += 1,
+
+ // Wrote the cell but request to skip the next so skip it.
+ // This is used for things like spacers.
+ .skip_next => {
+ // Remap any tracked pins at the skipped position (x+1)
+ // since we won't process that cell in the loop.
+ const pin_keys = list.tracked_pins.keys();
+ for (pin_keys) |p| {
+ if (&p.node.data != src_page or
+ p.y != src_y or
+ p.x != x + 1) continue;
+
+ p.node = self.node;
+ p.x = self.x;
+ p.y = self.y;
+ }
+
+ x += 2;
+ },
+
+ // Didn't write the cell, repeat writing this same cell.
+ .repeat => {},
+ } else |err| switch (err) {
+ // System out of memory, we can't fix this.
+ error.OutOfMemory => return error.OutOfMemory,
+
+ // We reached the capacity of a single page and can't
+ // add any more of some type of managed memory. When this
+ // happens we split out the current row we're working on
+ // into a new page and continue from there.
+ error.OutOfSpace => if (self.y == 0) {
+ // If we're already on the first-row, we can't split
+ // any further, so we just ignore bad cells and take
+ // corrupted (but valid) cell contents.
+ log.warn("reflowRow OutOfSpace on first row, discarding cell managed memory", .{});
+ x += 1;
+ self.cursorForward();
+ } else {
+ // Move our last row to a new page.
+ try self.moveLastRowToNewPage(list, cap);
+
+ // Do NOT increment x so that we retry writing
+ // the same existing cell.
+ },
+ }
+ }
+
+ // If the source row isn't wrapped then we should scroll afterwards.
+ if (!src_row.wrap) {
+ self.new_rows += 1;
+ }
+ }
+
+ /// Write a cell. On error, this will not unwrite the cell but
+ /// the cell may be incomplete (but valid). For example, if the source
+ /// cell is styled and we failed to allocate space for styles, the
+ /// written cell may not be styled but it is valid.
+ ///
+ /// The key failure to recognize for callers is when we can't increase
+ /// capacity in our destination page. In this case, the caller may want
+ /// to split the page at this row, rewrite the row into a new page
+ /// and continue from there.
+ ///
+ /// But this function guarantees the terminal/page will be in a
+ /// coherent state even on error.
+ fn writeCell(
+ self: *ReflowCursor,
+ list: *PageList,
+ cell: *const pagepkg.Cell,
+ src_page: *const Page,
+ ) IncreaseCapacityError!enum {
+ success,
+ repeat,
+ skip_next,
+ } {
+ // Initialize self.page_cell with basic, unmanaged memory contents.
+ {
+ // This must not fail because we want to make sure we atomically
+ // setup our page cell to be valid.
+ errdefer comptime unreachable;
// Copy cell contents.
switch (cell.content_tag) {
@@ -1139,16 +1368,15 @@ const ReflowCursor = struct {
.wide = .spacer_head,
};
- // Decrement the source position so that when we
- // loop we'll process this source cell again,
- // since we can't copy it into a spacer head.
- x -= 1;
-
// Move to the next row (this sets pending wrap
// which will cause us to wrap on the next
// iteration).
self.cursorForward();
- continue;
+
+ // Decrement the source position so that when we
+ // loop we'll process this source cell again,
+ // since we can't copy it into a spacer head.
+ return .repeat;
} else {
self.page_cell.* = cell.*;
}
@@ -1159,9 +1387,9 @@ const ReflowCursor = struct {
self.page_cell.content.codepoint = 0;
self.page_cell.wide = .narrow;
self.cursorForward();
+
// Skip spacer tail so it doesn't cause a wrap.
- x += 1;
- continue;
+ return .skip_next;
},
.spacer_tail => if (self.page.size.cols > 1) {
@@ -1171,14 +1399,14 @@ const ReflowCursor = struct {
// characters are just destroyed and replaced
// with empty narrow cells, so we should just
// discard any spacer tails.
- continue;
+ return .success;
},
.spacer_head => {
// Spacer heads should be ignored. If we need a
// spacer head in our reflowed page, it is added
// when processing the wide cell it belongs to.
- continue;
+ return .success;
},
},
@@ -1189,7 +1417,7 @@ const ReflowCursor = struct {
// data associated with them so we can fast path them.
self.page_cell.* = cell.*;
self.cursorForward();
- continue;
+ return .success;
},
}
@@ -1200,185 +1428,279 @@ const ReflowCursor = struct {
self.page_cell.hyperlink = false;
self.page_cell.style_id = stylepkg.default_id;
- // std.log.warn("\nsrc_y={} src_x={} dst_y={} dst_x={} dst_cols={} cp={X} wide={} page_cell_wide={}", .{
- // src_y,
- // x,
- // self.y,
- // self.x,
- // self.page.size.cols,
- // cell.content.codepoint,
- // cell.wide,
- // self.page_cell.wide,
- // });
-
- // Copy grapheme data.
- if (cell.content_tag == .codepoint_grapheme) {
- // Copy the graphemes
- const cps = src_page.lookupGrapheme(cell).?;
-
- // If our page can't support an additional cell
- // with graphemes then we increase capacity.
- if (self.page.graphemeCount() >= self.page.graphemeCapacity()) {
- try self.adjustCapacity(list, .{
- .grapheme_bytes = cap.grapheme_bytes * 2,
- });
- }
-
- // Attempt to allocate the space that would be required
- // for these graphemes, and if it's not available, then
- // increase capacity.
- if (self.page.grapheme_alloc.alloc(
- u21,
- self.page.memory,
- cps.len,
- )) |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.
- try self.page.setGraphemes(self.page_row, self.page_cell, cps);
- }
-
- // Copy hyperlink data.
- if (cell.hyperlink) {
- const src_id = src_page.lookupHyperlink(cell).?;
- const src_link = src_page.hyperlink_set.get(src_page.memory, src_id);
-
- // If our page can't support an additional cell
- // with a hyperlink then we increase capacity.
- if (self.page.hyperlinkCount() >= self.page.hyperlinkCapacity()) {
- 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(
- self.page.memory,
- // We made sure there was enough capacity for this above.
- try src_link.dupe(src_page, self.page),
- src_id,
- .{ .page = self.page },
- ) catch |err| id: {
- // If the add failed then either the set needs to grow
- // or it needs to be rehashed. Either one of those can
- // 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 => .{},
- });
-
- // 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,
- try src_link.dupe(src_page, self.page),
- src_id,
- .{ .page = self.page },
- );
- } orelse src_id;
-
- // We expect this to succeed due to the
- // hyperlinkCapacity check we did before.
- try self.page.setHyperlink(
- self.page_row,
- self.page_cell,
- dst_id,
- );
- }
-
- // Copy style data.
- if (cell.hasStyling()) {
- const style = src_page.styles.get(
- src_page.memory,
- cell.style_id,
- ).*;
-
- const id = self.page.styles.addWithId(
- self.page.memory,
- style,
- cell.style_id,
- ) catch |err| id: {
- // If the add failed then either the set needs to grow
- // or it needs to be rehashed. Either one of those can
- // 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 => .{},
- });
-
- // We assume this one will succeed.
- break :id try self.page.styles.addWithId(
- self.page.memory,
- style,
- cell.style_id,
- );
- } orelse cell.style_id;
-
- self.page_row.styled = true;
-
- self.page_cell.style_id = id;
- }
-
if (comptime build_options.kitty_graphics) {
// Copy Kitty virtual placeholder status
if (cell.codepoint() == kitty.graphics.unicode.placeholder) {
self.page_row.kitty_virtual_placeholder = true;
}
}
-
- self.cursorForward();
}
- // If the source row isn't wrapped then we should scroll afterwards.
- if (!src_row.wrap) {
- self.new_rows += 1;
+ // std.log.warn("\nsrc_y={} src_x={} dst_y={} dst_x={} dst_cols={} cp={X} wide={} page_cell_wide={}", .{
+ // src_y,
+ // x,
+ // self.y,
+ // self.x,
+ // self.page.size.cols,
+ // cell.content.codepoint,
+ // cell.wide,
+ // self.page_cell.wide,
+ // });
+
+ // From this point on we're moving on to failable, managed memory.
+ // If we reach an error, we do the minimal cleanup necessary to
+ // not leave dangling memory but otherwise we gracefully degrade
+ // into some functional but not strictly correct cell.
+
+ // Copy grapheme data.
+ if (cell.content_tag == .codepoint_grapheme) {
+ // Copy the graphemes
+ const cps = src_page.lookupGrapheme(cell).?;
+
+ // If our page can't support an additional cell
+ // with graphemes then we increase capacity.
+ if (self.page.graphemeCount() >= self.page.graphemeCapacity()) {
+ try self.increaseCapacity(
+ list,
+ .grapheme_bytes,
+ );
+ }
+
+ // Attempt to allocate the space that would be required
+ // for these graphemes, and if it's not available, then
+ // increase capacity. Keep trying until we succeed.
+ while (true) {
+ if (self.page.grapheme_alloc.alloc(
+ u21,
+ self.page.memory,
+ cps.len,
+ )) |slice| {
+ self.page.grapheme_alloc.free(
+ self.page.memory,
+ slice,
+ );
+ break;
+ } else |_| {
+ // Grow our capacity until we can fit the extra bytes.
+ try self.increaseCapacity(list, .grapheme_bytes);
+ }
+ }
+
+ self.page.setGraphemes(
+ self.page_row,
+ self.page_cell,
+ cps,
+ ) catch |err| {
+ // This shouldn't fail since we made sure we have space
+ // above. There is no reasonable behavior we can take here
+ // so we have a warn level log. This is ALMOST non-recoverable,
+ // though we choose to recover by corrupting the cell
+ // to a non-grapheme codepoint.
+ log.err("setGraphemes failed after capacity increase err={}", .{err});
+ if (comptime std.debug.runtime_safety) {
+ // Force a crash with safe builds.
+ unreachable;
+ }
+
+ // Unsafe builds we throw away grapheme data!
+ self.page_cell.content_tag = .codepoint;
+ self.page_cell.content = .{ .codepoint = 0xFFFD };
+ };
}
+
+ // Copy hyperlink data.
+ if (cell.hyperlink) hyperlink: {
+ const src_id = src_page.lookupHyperlink(cell).?;
+ const src_link = src_page.hyperlink_set.get(src_page.memory, src_id);
+
+ // If our page can't support an additional cell
+ // with a hyperlink then we increase capacity.
+ if (self.page.hyperlinkCount() >= self.page.hyperlinkCapacity()) {
+ try self.increaseCapacity(list, .hyperlink_bytes);
+ }
+
+ // 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,
+ };
+ // Keep trying until we have enough capacity.
+ while (true) {
+ 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,
+ );
+ break;
+ } else |_| {
+ // Grow our capacity until we can fit the extra bytes.
+ try self.increaseCapacity(
+ list,
+ .string_bytes,
+ );
+ }
+ }
+
+ const dst_link = src_link.dupe(
+ src_page,
+ self.page,
+ ) catch |err| {
+ // This shouldn't fail since we did a capacity
+ // check above.
+ log.err("link dupe failed with capacity check err={}", .{err});
+ if (comptime std.debug.runtime_safety) {
+ // Force a crash with safe builds.
+ unreachable;
+ }
+
+ break :hyperlink;
+ };
+
+ const dst_id = self.page.hyperlink_set.addWithIdContext(
+ self.page.memory,
+ dst_link,
+ src_id,
+ .{ .page = self.page },
+ ) catch |err| id: {
+ // Always free our original link in case the increaseCap
+ // call fails so we aren't leaking memory.
+ dst_link.free(self.page);
+
+ // If the add failed then either the set needs to grow
+ // or it needs to be rehashed. Either one of those can
+ // be accomplished by increasing capacity, either with
+ // no actual change or with an increased hyperlink cap.
+ try self.increaseCapacity(list, switch (err) {
+ error.OutOfMemory => .hyperlink_bytes,
+ error.NeedsRehash => null,
+ });
+
+ // We need to recreate the link into the new page.
+ const dst_link2 = src_link.dupe(
+ src_page,
+ self.page,
+ ) catch |err2| {
+ // This shouldn't fail since we did a capacity
+ // check above.
+ log.err("link dupe failed with capacity check err={}", .{err2});
+ if (comptime std.debug.runtime_safety) {
+ // Force a crash with safe builds.
+ unreachable;
+ }
+
+ break :hyperlink;
+ };
+
+ // We assume this one will succeed. We dupe the link
+ // again, and don't have to worry about the other one
+ // because increasing the capacity naturally clears up
+ // any managed memory not associated with a cell yet.
+ break :id self.page.hyperlink_set.addWithIdContext(
+ self.page.memory,
+ dst_link2,
+ src_id,
+ .{ .page = self.page },
+ ) catch |err2| {
+ // This shouldn't happen since we increased capacity
+ // above so we handle it like the other similar
+ // cases and log it, crash in safe builds, and
+ // remove the hyperlink in unsafe builds.
+ log.err(
+ "addWithIdContext failed after capacity increase err={}",
+ .{err2},
+ );
+ if (comptime std.debug.runtime_safety) {
+ // Force a crash with safe builds.
+ unreachable;
+ }
+
+ dst_link2.free(self.page);
+ break :hyperlink;
+ };
+ } orelse src_id;
+
+ // We expect this to succeed due to the hyperlinkCapacity
+ // check we did before. If it doesn't succeed let's
+ // log it, crash (in safe builds), and clear our state.
+ self.page.setHyperlink(
+ self.page_row,
+ self.page_cell,
+ dst_id,
+ ) catch |err| {
+ log.err(
+ "setHyperlink failed after capacity increase err={}",
+ .{err},
+ );
+ if (comptime std.debug.runtime_safety) {
+ // Force a crash with safe builds.
+ unreachable;
+ }
+
+ // Unsafe builds we throw away hyperlink data!
+ self.page.hyperlink_set.release(self.page.memory, dst_id);
+ self.page_cell.hyperlink = false;
+ break :hyperlink;
+ };
+ }
+
+ // Copy style data.
+ if (cell.hasStyling()) style: {
+ const style = src_page.styles.get(
+ src_page.memory,
+ cell.style_id,
+ ).*;
+
+ const id = self.page.styles.addWithId(
+ self.page.memory,
+ style,
+ cell.style_id,
+ ) catch |err| id: {
+ // If the add failed then either the set needs to grow
+ // or it needs to be rehashed. Either one of those can
+ // be accomplished by increasing capacity, either with
+ // no actual change or with an increased style cap.
+ try self.increaseCapacity(list, switch (err) {
+ error.OutOfMemory => .styles,
+ error.NeedsRehash => null,
+ });
+
+ // We assume this one will succeed.
+ break :id self.page.styles.addWithId(
+ self.page.memory,
+ style,
+ cell.style_id,
+ ) catch |err2| {
+ // Should not fail since we just modified capacity
+ // above. Log it, crash in safe builds, clear style
+ // in unsafe builds.
+ log.err(
+ "addWithId failed after capacity increase err={}",
+ .{err2},
+ );
+ if (comptime std.debug.runtime_safety) {
+ // Force a crash with safe builds.
+ unreachable;
+ }
+
+ self.page_cell.style_id = stylepkg.default_id;
+ break :style;
+ };
+ } orelse cell.style_id;
+
+ self.page_row.styled = true;
+ self.page_cell.style_id = id;
+ }
+
+ self.cursorForward();
+ return .success;
}
/// Create a new page in the provided list with the provided
@@ -1397,7 +1719,7 @@ const ReflowCursor = struct {
self: *ReflowCursor,
list: *PageList,
cap: Capacity,
- ) !void {
+ ) Allocator.Error!void {
assert(self.y == self.page.size.rows - 1);
assert(!self.pending_wrap);
@@ -1406,16 +1728,50 @@ const ReflowCursor = struct {
const old_row = self.page_row;
const old_x = self.x;
+ // Our total row count never changes, because we're removing one
+ // row from the last page and moving it into a new page.
+ const old_total_rows = self.total_rows;
+ defer self.total_rows = old_total_rows;
+
try self.cursorNewPage(list, cap);
+ assert(self.node != old_node);
+ assert(self.y == 0);
+
+ // We have no cleanup for our old state from here on out. No failures!
+ errdefer comptime unreachable;
// Restore the x position of the cursor.
self.cursorAbsolute(old_x, 0);
- // We expect to have enough capacity to clone the row.
- try self.page.cloneRowFrom(old_page, self.page_row, old_row);
+ // Copy our old data. This should NOT fail because we have the
+ // capacity of the old page which already fits the data we requested.
+ self.page.cloneRowFrom(
+ old_page,
+ self.page_row,
+ old_row,
+ ) catch |err| {
+ log.err(
+ "error cloning single row for moveLastRowToNewPage err={}",
+ .{err},
+ );
+ @panic("unexpected copy row failure");
+ };
+
+ // Move any tracked pins from that last row into this new node.
+ {
+ const pin_keys = list.tracked_pins.keys();
+ for (pin_keys) |p| {
+ if (&p.node.data != old_page or
+ p.y != old_page.size.rows - 1) continue;
+
+ p.node = self.node;
+ p.y = self.y;
+ // p.x remains the same since we're copying the row as-is
+ }
+ }
// Clear the row from the old page and truncate it.
- old_page.clearCells(old_row, 0, self.page.size.cols);
+ old_page.clearCells(old_row, 0, old_page.size.cols);
old_page.size.rows -= 1;
// If that was the last row in that page
@@ -1426,27 +1782,31 @@ const ReflowCursor = struct {
}
}
- /// Adjust the capacity of the current page.
- fn adjustCapacity(
+ /// Increase the capacity of the current page.
+ fn increaseCapacity(
self: *ReflowCursor,
list: *PageList,
- adjustment: AdjustCapacity,
- ) !void {
+ adjustment: ?IncreaseCapacity,
+ ) IncreaseCapacityError!void {
const old_x = self.x;
const old_y = self.y;
const old_total_rows = self.total_rows;
- self.* = .init(node: {
+ const node = node: {
// Pause integrity checks because the total row count won't
// be correct during a reflow.
list.pauseIntegrityChecks(true);
defer list.pauseIntegrityChecks(false);
- break :node try list.adjustCapacity(
+ break :node try list.increaseCapacity(
self.node,
adjustment,
);
- });
+ };
+ // We must not fail after this, we've modified our self.node
+ // and we need to fix it up.
+ errdefer comptime unreachable;
+ self.* = .init(node);
self.cursorAbsolute(old_x, old_y);
self.total_rows = old_total_rows;
}
@@ -1497,17 +1857,17 @@ const ReflowCursor = struct {
self: *ReflowCursor,
list: *PageList,
cap: Capacity,
- ) !void {
+ ) Allocator.Error!void {
// Remember our new row count so we can restore it
// after reinitializing our cursor on the new page.
const new_rows = self.new_rows;
const node = try list.createPage(cap);
+ errdefer comptime unreachable;
node.data.size.rows = 1;
list.pages.insertAfter(self.node, node);
self.* = .init(node);
-
self.new_rows = new_rows;
}
@@ -1517,7 +1877,7 @@ const ReflowCursor = struct {
self: *ReflowCursor,
list: *PageList,
cap: Capacity,
- ) !void {
+ ) Allocator.Error!void {
// The functions below may overwrite self so we need to cache
// our total rows. We add one because no matter what when this
// returns we'll have one more row added.
@@ -1575,11 +1935,11 @@ const ReflowCursor = struct {
}
};
-fn resizeWithoutReflow(self: *PageList, opts: Resize) !void {
+fn resizeWithoutReflow(self: *PageList, opts: Resize) Allocator.Error!void {
// We only set the new min_max_size if we're not reflowing. If we are
// reflowing, then resize handles this for us.
const old_min_max_size = self.min_max_size;
- self.min_max_size = if (!opts.reflow) try minMaxSize(
+ self.min_max_size = if (!opts.reflow) minMaxSize(
opts.cols orelse self.cols,
opts.rows orelse self.rows,
) else old_min_max_size;
@@ -1717,7 +2077,7 @@ fn resizeWithoutReflow(self: *PageList, opts: Resize) !void {
// area, since that will lead to all sorts of problems.
switch (self.viewport) {
.pin => if (self.pinIsActive(self.viewport_pin.*)) {
- self.viewport = .{ .active = {} };
+ self.viewport = .active;
},
.active, .top => {},
}
@@ -1735,26 +2095,44 @@ fn resizeWithoutReflowGrowCols(
self: *PageList,
cols: size.CellCountInt,
chunk: PageIterator.Chunk,
-) !void {
+) Allocator.Error!void {
assert(cols > self.cols);
const page = &chunk.node.data;
- const cap = try page.capacity.adjust(.{ .cols = cols });
// Update our col count
const old_cols = self.cols;
- self.cols = cap.cols;
+ self.cols = cols;
errdefer self.cols = old_cols;
// Unlikely fast path: we have capacity in the page. This
// is only true if we resized to less cols earlier.
- if (page.capacity.cols >= cap.cols) {
- page.size.cols = cap.cols;
+ if (page.capacity.cols >= cols) {
+ page.size.cols = cols;
return;
}
// Likely slow path: we don't have capacity, so we need
// to allocate a page, and copy the old data into it.
+ // Try to fit our new column size into our existing page capacity.
+ // If that doesn't work then use a non-standard page with the
+ // given columns.
+ const cap = page.capacity.adjust(
+ .{ .cols = cols },
+ ) catch |err| err: {
+ comptime assert(@TypeOf(err) == error{OutOfMemory});
+
+ // We verify all maxed out page layouts don't overflow,
+ var cap = page.capacity;
+ cap.cols = cols;
+
+ // We're growing columns so we can only get less rows so use
+ // the lesser of our capacity and size so we minimize wasted
+ // rows.
+ cap.rows = @min(page.size.rows, cap.rows);
+ break :err cap;
+ };
+
// On error, we need to undo all the pages we've added.
const prev = chunk.node.prev;
errdefer {
@@ -1816,6 +2194,39 @@ fn resizeWithoutReflowGrowCols(
assert(copied == len);
assert(prev_page.size.rows <= prev_page.capacity.rows);
+
+ // Remap any tracked pins that pointed to rows we just copied to prev.
+ const pin_keys = self.tracked_pins.keys();
+ for (pin_keys) |p| {
+ if (p.node != chunk.node or p.y >= len) continue;
+ p.node = prev_node;
+ p.y += prev_page.size.rows - len;
+ }
+ }
+
+ // If we have an error, we clear the rows we just added to our prev page.
+ const prev_copied = copied;
+ errdefer if (prev_copied > 0) {
+ const prev_page = &prev.?.data;
+ const prev_size = prev_page.size.rows - prev_copied;
+ const prev_rows = prev_page.rows.ptr(prev_page.memory)[prev_size..prev_page.size.rows];
+ for (prev_rows) |*row| prev_page.clearCells(
+ row,
+ 0,
+ prev_page.size.cols,
+ );
+ prev_page.size.rows = prev_size;
+ };
+
+ // We delete any of the nodes we added.
+ errdefer {
+ var it = chunk.node.prev;
+ while (it) |node| {
+ if (node == prev) break;
+ it = node.prev;
+ self.pages.remove(node);
+ self.destroyNode(node);
+ }
}
// We need to loop because our col growth may force us
@@ -1830,19 +2241,33 @@ fn resizeWithoutReflowGrowCols(
// Perform the copy
const y_start = copied;
- const y_end = copied + len;
- const src_rows = page.rows.ptr(page.memory)[y_start..y_end];
+ const src_rows = page.rows.ptr(page.memory)[y_start .. copied + len];
const dst_rows = new_node.data.rows.ptr(new_node.data.memory)[0..len];
for (dst_rows, src_rows) |*dst_row, *src_row| {
new_node.data.size.rows += 1;
- errdefer new_node.data.size.rows -= 1;
- try new_node.data.cloneRowFrom(
+ if (new_node.data.cloneRowFrom(
page,
dst_row,
src_row,
- );
+ )) |_| {
+ copied += 1;
+ } else |err| {
+ // I don't THINK this should be possible, because while our
+ // row count may diminish due to the adjustment, our
+ // prior capacity should have been sufficient to hold all the
+ // managed memory.
+ log.warn(
+ "unexpected cloneRowFrom failure during resizeWithoutReflowGrowCols: {}",
+ .{err},
+ );
+
+ // We can actually safely handle this though by exiting
+ // this loop early and cutting our copy short.
+ new_node.data.size.rows -= 1;
+ break;
+ }
}
- copied = y_end;
+ const y_end = copied;
// Insert our new page
self.pages.insertBefore(chunk.node, new_node);
@@ -1859,6 +2284,10 @@ fn resizeWithoutReflowGrowCols(
}
assert(copied == page.size.rows);
+ // Our prior errdeferes are invalid after this point so ensure
+ // we don't have any more errors.
+ errdefer comptime unreachable;
+
// Remove the old page.
// Deallocate the old page.
self.pages.remove(chunk.node);
@@ -1978,6 +2407,12 @@ pub const Scroll = union(enum) {
pub fn scroll(self: *PageList, behavior: Scroll) void {
defer self.assertIntegrity();
+ // Special case no-scrollback mode to never allow scrolling.
+ if (self.explicit_max_size == 0) {
+ self.viewport = .active;
+ return;
+ }
+
switch (behavior) {
.active => self.viewport = .active,
.top => self.viewport = .top,
@@ -2250,6 +2685,166 @@ pub fn scrollClear(self: *PageList) !void {
for (0..non_empty) |_| _ = try self.grow();
}
+/// Compact a page to use the minimum required memory for the contents
+/// it stores. Returns the new node pointer if compaction occurred, or null
+/// if the page was already compact or compaction would not provide meaningful
+/// savings.
+///
+/// The current design of PageList at the time of writing this doesn't
+/// allow for smaller than `std_size` nodes so if the current node's backing
+/// page is standard size or smaller, no compaction will occur. In the
+/// future we should fix this up.
+///
+/// If this returns OOM, the PageList is left unchanged and no dangling
+/// memory references exist. It is safe to ignore the error and continue using
+/// the uncompacted page.
+pub fn compact(self: *PageList, node: *List.Node) Allocator.Error!?*List.Node {
+ defer self.assertIntegrity();
+ const page: *Page = &node.data;
+
+ // We should never have empty rows in our pagelist anyways...
+ assert(page.size.rows > 0);
+
+ // We never compact standard size or smaller pages because changing
+ // the capacity to something smaller won't save memory.
+ if (page.memory.len <= std_size) return null;
+
+ // Compute the minimum capacity required for this page's content
+ const req_cap = page.exactRowCapacity(0, page.size.rows);
+ const new_size = Page.layout(req_cap).total_size;
+ const old_size = page.memory.len;
+ if (new_size >= old_size) return null;
+
+ // Create the new smaller page
+ const new_node = try self.createPage(req_cap);
+ errdefer self.destroyNode(new_node);
+ const new_page: *Page = &new_node.data;
+ new_page.size = page.size;
+ new_page.dirty = page.dirty;
+ new_page.cloneFrom(
+ page,
+ 0,
+ page.size.rows,
+ ) catch |err| {
+ // cloneFrom should not fail when compacting since req_cap is
+ // computed to exactly fit the source content and our expectation
+ // of exactRowCapacity ensures it can fit all the requested
+ // data.
+ log.err("compact clone failed err={}", .{err});
+
+ // In this case, let's gracefully degrade by pretending we
+ // didn't need to compact.
+ self.destroyNode(new_node);
+ return null;
+ };
+
+ // Fix up all tracked pins to point to the new page
+ const pin_keys = self.tracked_pins.keys();
+ for (pin_keys) |p| {
+ if (p.node != node) continue;
+ p.node = new_node;
+ }
+
+ // Insert the new page and destroy the old one
+ self.pages.insertBefore(node, new_node);
+ self.pages.remove(node);
+ self.destroyNode(node);
+
+ new_page.assertIntegrity();
+ return new_node;
+}
+
+pub const SplitError = error{
+ // Allocator OOM
+ OutOfMemory,
+ // Page can't be split further because it is already a single row.
+ OutOfSpace,
+};
+
+/// Split the given node in the PageList at the given pin.
+///
+/// The row at the pin and after will be moved into a new page with
+/// the same capacity as the original page. Alternatively, you can "split
+/// above" by splitting the row following the desired split row.
+///
+/// Since the split happens below the pin, the pin remains valid.
+pub fn split(
+ self: *PageList,
+ p: Pin,
+) SplitError!void {
+ if (build_options.slow_runtime_safety) assert(self.pinIsValid(p));
+
+ // Ran into a bug that I can only explain via aliasing. If a tracked
+ // pin is passed in, its possible Zig will alias the memory and then
+ // when we modify it later it updates our p here. Copying the node
+ // fixes this.
+ const original_node = p.node;
+ const page: *Page = &original_node.data;
+
+ // A page that is already 1 row can't be split. In the future we can
+ // theoretically maybe split by soft-wrapping multiple pages but that
+ // seems crazy and the rest of our PageList can't handle heterogeneously
+ // sized pages today.
+ if (page.size.rows <= 1) return error.OutOfSpace;
+
+ // Splitting at row 0 is a no-op since there's nothing before the split point.
+ if (p.y == 0) return;
+
+ // At this point we're doing actual modification so make sure
+ // on the return that we're good.
+ defer self.assertIntegrity();
+
+ // Create a new node with the same capacity of managed memory.
+ const target = try self.createPage(page.capacity);
+ errdefer self.destroyNode(target);
+
+ // Determine how many rows we're copying
+ const y_start = p.y;
+ const y_end = page.size.rows;
+ target.data.size.rows = y_end - y_start;
+ assert(target.data.size.rows <= target.data.capacity.rows);
+
+ // Copy our old data. This should NOT fail because we have the
+ // capacity of the old page which already fits the data we requested.
+ target.data.cloneFrom(page, y_start, y_end) catch |err| {
+ log.err(
+ "error cloning rows for split err={}",
+ .{err},
+ );
+
+ // Rather than crash, we return an OutOfSpace to show that
+ // we couldn't split and let our callers gracefully handle it.
+ // Realistically though... this should not happen.
+ return error.OutOfSpace;
+ };
+
+ // From this point forward there is no going back. We have no
+ // error handling. It is possible but we haven't written it.
+ errdefer comptime unreachable;
+
+ // Move any tracked pins from the copied rows
+ for (self.tracked_pins.keys()) |tracked| {
+ if (&tracked.node.data != page or
+ tracked.y < p.y) continue;
+
+ tracked.node = target;
+ tracked.y -= p.y;
+ // p.x remains the same since we're copying the row as-is
+ }
+
+ // Clear our rows
+ for (page.rows.ptr(page.memory)[y_start..y_end]) |*row| {
+ page.clearCells(
+ row,
+ 0,
+ page.size.cols,
+ );
+ }
+ page.size.rows -= y_end - y_start;
+
+ self.pages.insertAfter(original_node, target);
+}
+
/// This represents the state necessary to render a scrollbar for this
/// PageList. It has the total size, the offset, and the size of the viewport.
pub const Scrollbar = struct {
@@ -2300,6 +2895,17 @@ pub const Scrollbar = struct {
/// is (arbitrary pins are expensive). The caller should take care to only
/// call this as needed and not too frequently.
pub fn scrollbar(self: *PageList) Scrollbar {
+ // If we have no scrollback, special case no scrollbar.
+ // We need to do this because the way PageList works is that
+ // it always has SOME extra space (due to the way we allocate by page).
+ // So even with no scrollback we have some growth. It is architecturally
+ // much simpler to just hide that for no-scrollback cases.
+ if (self.explicit_max_size == 0) return .{
+ .total = self.rows,
+ .offset = 0,
+ .len = self.rows,
+ };
+
return .{
.total = self.total_rows,
.offset = self.viewportRowOffset(),
@@ -2401,18 +3007,6 @@ pub fn maxSize(self: *const PageList) usize {
return @max(self.explicit_max_size, self.min_max_size);
}
-/// Returns true if we need to grow into our active area.
-inline fn growRequiredForActive(self: *const PageList) bool {
- var rows: usize = 0;
- var page = self.pages.last;
- while (page) |p| : (page = p.prev) {
- rows += p.data.size.rows;
- if (rows >= self.rows) return false;
- }
-
- return true;
-}
-
/// Grow the active area by exactly one row.
///
/// This may allocate, but also may not if our current page has more
@@ -2420,7 +3014,7 @@ inline fn growRequiredForActive(self: *const PageList) bool {
/// adhere to max_size.
///
/// This returns the newly allocated page node if there is one.
-pub fn grow(self: *PageList) !?*List.Node {
+pub fn grow(self: *PageList) Allocator.Error!?*List.Node {
defer self.assertIntegrity();
const last = self.pages.last.?;
@@ -2437,6 +3031,10 @@ pub fn grow(self: *PageList) !?*List.Node {
// Slower path: we have no space, we need to allocate a new page.
+ // Get the layout first so our failable work is done early.
+ // We'll need this for both paths.
+ const cap = initialCapacity(self.cols);
+
// If allocation would exceed our max size, we prune the first page.
// We don't need to reallocate because we can simply reuse that first
// page.
@@ -2449,22 +3047,21 @@ pub fn grow(self: *PageList) !?*List.Node {
self.pages.first != self.pages.last and
self.page_size + PagePool.item_size > self.maxSize())
prune: {
- // If we need to add more memory to ensure our active area is
- // satisfied then we do not prune.
- if (self.growRequiredForActive()) break :prune;
-
- const layout = Page.layout(try std_capacity.adjust(.{ .cols = self.cols }));
-
- // Get our first page and reset it to prepare for reuse.
const first = self.pages.popFirst().?;
assert(first != last);
- const buf = first.data.memory;
- @memset(buf, 0);
- // Decrease our total row count from the pruned page and then
- // add one for our new row.
+ // Decrease our total row count from the pruned page
self.total_rows -= first.data.size.rows;
- self.total_rows += 1;
+
+ // If our total row count is now less than our required
+ // rows then we can't prune. The "+ 1" is because we'll add one
+ // more row below.
+ if (self.total_rows + 1 < self.rows) {
+ self.pages.prepend(first);
+ assert(self.pages.first == first);
+ self.total_rows += first.data.size.rows;
+ break :prune;
+ }
// If we have a pin viewport cache then we need to update it.
if (self.viewport == .pin) viewport: {
@@ -2482,21 +3079,8 @@ pub fn grow(self: *PageList) !?*List.Node {
}
}
- // Initialize our new page and reinsert it as the last
- first.data = .initBuf(.init(buf), layout);
- first.data.size.rows = 1;
- self.pages.insertAfter(last, first);
-
- // We also need to reset the serial number. Since this is the only
- // place we ever reuse a serial number, we also can safely set
- // page_serial_min to be one more than the old serial because we
- // only ever prune the oldest pages.
- self.page_serial_min = first.serial + 1;
- first.serial = self.page_serial;
- self.page_serial += 1;
-
// Update any tracked pins that point to this page to point to the
- // new first page to the top-left.
+ // new first page to the top-left, and mark them as garbage.
const pin_keys = self.tracked_pins.keys();
for (pin_keys) |p| {
if (p.node != first) continue;
@@ -2507,6 +3091,31 @@ pub fn grow(self: *PageList) !?*List.Node {
}
self.viewport_pin.garbage = false;
+ // Non-standard pages can't be reused, just destroy them.
+ if (first.data.memory.len > std_size) {
+ self.destroyNode(first);
+ break :prune;
+ }
+
+ // Reset our memory
+ const buf = first.data.memory;
+ @memset(buf, 0);
+ assert(buf.len <= std_size);
+
+ // Initialize our new page and reinsert it as the last
+ first.data = .initBuf(.init(buf), Page.layout(cap));
+ first.data.size.rows = 1;
+ self.pages.insertAfter(last, first);
+ self.total_rows += 1;
+
+ // We also need to reset the serial number. Since this is the only
+ // place we ever reuse a serial number, we also can safely set
+ // page_serial_min to be one more than the old serial because we
+ // only ever prune the oldest pages.
+ self.page_serial_min = first.serial + 1;
+ first.serial = self.page_serial;
+ self.page_serial += 1;
+
// In this case we do NOT need to update page_size because
// we're reusing an existing page so nothing has changed.
@@ -2515,7 +3124,7 @@ pub fn grow(self: *PageList) !?*List.Node {
}
// We need to allocate a new memory buffer.
- const next_node = try self.createPage(try std_capacity.adjust(.{ .cols = self.cols }));
+ const next_node = try self.createPage(cap);
// we don't errdefer this because we've added it to the linked
// list and its fine to have dangling unused pages.
self.pages.append(next_node);
@@ -2531,75 +3140,82 @@ pub fn grow(self: *PageList) !?*List.Node {
return next_node;
}
-/// Adjust the capacity of the given page in the list.
-pub const AdjustCapacity = struct {
- /// Adjust the number of styles in the page. This may be
- /// rounded up if necessary to fit alignment requirements,
- /// but it will never be rounded down.
- styles: ?usize = null,
-
- /// Adjust the number of available grapheme bytes in the page.
- grapheme_bytes: ?usize = null,
-
- /// Adjust the number of available hyperlink bytes in the page.
- hyperlink_bytes: ?usize = null,
-
- /// Adjust the number of available string bytes in the page.
- string_bytes: ?usize = null,
+/// Possible dimensions to increase capacity for.
+pub const IncreaseCapacity = enum {
+ styles,
+ grapheme_bytes,
+ hyperlink_bytes,
+ string_bytes,
};
-pub const AdjustCapacityError = Allocator.Error || Page.CloneFromError;
+pub const IncreaseCapacityError = error{
+ // An actual system OOM trying to allocate memory.
+ OutOfMemory,
-/// Adjust the capacity of the given page in the list. This should
-/// be used in cases where OutOfMemory is returned by some operation
-/// i.e to increase style counts, grapheme counts, etc.
+ // The existing page is already at max capacity for the given
+ // adjustment. The caller must create a new page, remove data from
+ // the old page, etc. (up to the caller).
+ OutOfSpace,
+};
+
+/// Increase the capacity of the given page node in the given direction.
+/// This will always allocate a new node and remove the old node, so the
+/// existing node pointer will be invalid after this call. The newly created
+/// node on success is returned.
///
-/// Adjustment works by increasing the capacity of the desired
-/// dimension to a certain amount and increases the memory allocation
-/// requirement for the backing memory of the page. We currently
-/// never split pages or anything like that. Because increased allocation
-/// has to happen outside our memory pool, its generally much slower
-/// so pages should be sized to be large enough to handle all but
-/// exceptional cases.
+/// The increase amount is at the control of the PageList implementation,
+/// but is guaranteed to always increase by at least one unit in the
+/// given dimension. Practically, we'll always increase by much more
+/// (we currently double every time) but callers shouldn't depend on that.
+/// The only guarantee is some amount of growth.
///
-/// This can currently only INCREASE capacity size. It cannot
-/// decrease capacity size. This limitation is only because we haven't
-/// yet needed that use case. If we ever do, this can be added. Currently
-/// any requests to decrease will be ignored.
-pub fn adjustCapacity(
+/// Adjustment can be null if you want to recreate, reclone the page
+/// with the same capacity. This is a special case used for rehashing since
+/// the logic is otherwise the same. In this case, OutOfMemory is the
+/// only possible error.
+pub fn increaseCapacity(
self: *PageList,
node: *List.Node,
- adjustment: AdjustCapacity,
-) AdjustCapacityError!*List.Node {
+ adjustment: ?IncreaseCapacity,
+) IncreaseCapacityError!*List.Node {
defer self.assertIntegrity();
const page: *Page = &node.data;
- // We always start with the base capacity of the existing page. This
- // ensures we never shrink from what we need.
+ // Apply our adjustment
var cap = page.capacity;
+ if (adjustment) |v| switch (v) {
+ inline else => |tag| {
+ const field_name = @tagName(tag);
+ const Int = @FieldType(Capacity, field_name);
+ const old = @field(cap, field_name);
- // All ceilPowerOfTwo is unreachable because we're always same or less
- // bit width so maxInt is always possible.
- if (adjustment.styles) |v| {
- comptime assert(@bitSizeOf(@TypeOf(v)) <= @bitSizeOf(usize));
- const aligned = std.math.ceilPowerOfTwo(usize, v) catch unreachable;
- cap.styles = @max(cap.styles, aligned);
- }
- if (adjustment.grapheme_bytes) |v| {
- comptime assert(@bitSizeOf(@TypeOf(v)) <= @bitSizeOf(usize));
- const aligned = std.math.ceilPowerOfTwo(usize, v) catch unreachable;
- cap.grapheme_bytes = @max(cap.grapheme_bytes, aligned);
- }
- if (adjustment.hyperlink_bytes) |v| {
- comptime assert(@bitSizeOf(@TypeOf(v)) <= @bitSizeOf(usize));
- const aligned = std.math.ceilPowerOfTwo(usize, v) catch unreachable;
- cap.hyperlink_bytes = @max(cap.hyperlink_bytes, aligned);
- }
- if (adjustment.string_bytes) |v| {
- comptime assert(@bitSizeOf(@TypeOf(v)) <= @bitSizeOf(usize));
- const aligned = std.math.ceilPowerOfTwo(usize, v) catch unreachable;
- cap.string_bytes = @max(cap.string_bytes, aligned);
- }
+ // We use checked math to prevent overflow. If there is an
+ // overflow it means we're out of space in this dimension,
+ // since pages can take up to their maxInt capacity in any
+ // category.
+ const new = std.math.mul(
+ Int,
+ old,
+ 2,
+ ) catch |err| overflow: {
+ comptime assert(@TypeOf(err) == error{Overflow});
+ // Our final doubling would overflow since maxInt is
+ // 2^N - 1 for an unsignged int of N bits. So, if we overflow
+ // and we haven't used all the bits, use all the bits.
+ if (old < std.math.maxInt(Int)) break :overflow std.math.maxInt(Int);
+ return error.OutOfSpace;
+ };
+ @field(cap, field_name) = new;
+
+ // If our capacity exceeds the maximum page size, treat it
+ // as an OutOfSpace because things like page splitting will
+ // help.
+ const layout = Page.layout(cap);
+ if (layout.total_size > size.max_page_size) {
+ return error.OutOfSpace;
+ }
+ },
+ };
log.info("adjusting page capacity={}", .{cap});
@@ -2611,7 +3227,25 @@ pub fn adjustCapacity(
assert(new_page.capacity.cols >= page.capacity.cols);
new_page.size.rows = page.size.rows;
new_page.size.cols = page.size.cols;
- try new_page.cloneFrom(page, 0, page.size.rows);
+ new_page.cloneFrom(
+ page,
+ 0,
+ page.size.rows,
+ ) catch |err| {
+ // cloneFrom only errors if there isn't capacity for the data
+ // from the source page but we're only increasing capacity so
+ // this should never be possible. If it happens, we should crash
+ // because we're in no man's land and can't safely recover.
+ log.err("increaseCapacity clone failed err={}", .{err});
+ @panic("unexpected clone failure");
+ };
+
+ // Preserve page-level dirty flag (cloneFrom only copies row data)
+ new_page.dirty = page.dirty;
+
+ // Must not fail after this because the operations we do after this
+ // can't be recovered.
+ errdefer comptime unreachable;
// Fix up all our tracked pins to point to the new page.
const pin_keys = self.tracked_pins.keys();
@@ -2657,6 +3291,11 @@ inline fn createPageExt(
const pooled = layout.total_size <= std_size;
const page_alloc = pool.pages.arena.child_allocator;
+ // It would be better to encode this into the Zig error handling
+ // system but that is a big undertaking and we only have a few
+ // centralized call sites so it is handled on its own currently.
+ assert(layout.total_size <= size.max_page_size);
+
// Our page buffer comes from our standard memory pool if it
// is within our standard size since this is what the pool
// dispenses. Otherwise, we use the heap allocator to allocate.
@@ -3821,6 +4460,15 @@ pub const PageIterator = struct {
pub fn fullPage(self: Chunk) bool {
return self.start == 0 and self.end == self.node.data.size.rows;
}
+
+ /// Returns true if this chunk overlaps with the given other chunk
+ /// in any way.
+ pub fn overlaps(self: Chunk, other: Chunk) bool {
+ if (self.node != other.node) return false;
+ if (self.end <= other.start) return false;
+ if (self.start >= other.end) return false;
+ return true;
+ }
};
};
@@ -4499,6 +5147,38 @@ test "PageList init rows across two pages" {
}, s.scrollbar());
}
+test "PageList init more than max cols" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ // Initialize with more columns than we can fit in our standard
+ // capacity. This is going to force us to go to a non-standard page
+ // immediately.
+ var s = try init(
+ alloc,
+ std_capacity.maxCols().? + 1,
+ 80,
+ null,
+ );
+ defer s.deinit();
+ try testing.expect(s.viewport == .active);
+ try testing.expectEqual(@as(usize, s.rows), s.totalRows());
+
+ // We expect a single, non-standard page
+ try testing.expect(s.pages.first != null);
+ try testing.expect(s.pages.first.?.data.memory.len > std_size);
+
+ // Initial total rows should be our row count
+ try testing.expectEqual(s.rows, s.total_rows);
+
+ // Scrollbar should be where we expect it
+ try testing.expectEqual(Scrollbar{
+ .total = s.rows,
+ .offset = 0,
+ .len = s.rows,
+ }, s.scrollbar());
+}
+
test "PageList pointFromPin active no history" {
const testing = std.testing;
const alloc = testing.allocator;
@@ -4731,28 +5411,22 @@ test "PageList grow prune required with a single page" {
const testing = std.testing;
const alloc = testing.allocator;
- var s = try init(alloc, 80, 24, 0);
+ // Need scrollback > 0 to have a scrollbar to test
+ var s = try init(alloc, 80, 24, null);
defer s.deinit();
// This block is all test setup. There is nothing required about this
// behavior during a refactor. This is setting up a scenario that is
// possible to trigger a bug (#2280).
{
- // Adjust our capacity until our page is larger than the standard size.
+ // Increase our capacity until our page is larger than the standard size.
// This is important because it triggers a scenario where our calculated
// minSize() which is supposed to accommodate 2 pages is no longer true.
- var cap = std_capacity;
while (true) {
- cap.grapheme_bytes *= 2;
- const layout = Page.layout(cap);
+ const layout = Page.layout(s.pages.first.?.data.capacity);
if (layout.total_size > std_size) break;
+ _ = try s.increaseCapacity(s.pages.first.?, .grapheme_bytes);
}
-
- // Adjust to that capacity. After we should still have one page.
- _ = try s.adjustCapacity(
- s.pages.first.?,
- .{ .grapheme_bytes = cap.grapheme_bytes },
- );
try testing.expect(s.pages.first != null);
try testing.expect(s.pages.first == s.pages.last);
}
@@ -4779,6 +5453,47 @@ test "PageList grow prune required with a single page" {
}, s.scrollbar());
}
+test "PageList scrollbar with max_size 0 after grow" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var s = try init(alloc, 80, 24, 0);
+ defer s.deinit();
+
+ // Grow some rows (simulates normal terminal output)
+ try s.growRows(10);
+
+ const sb = s.scrollbar();
+
+ // With no scrollback (max_size = 0), total should equal rows
+ try testing.expectEqual(s.rows, sb.total);
+
+ // With no scrollback, offset should be 0 (nowhere to scroll back to)
+ try testing.expectEqual(@as(usize, 0), sb.offset);
+}
+
+test "PageList scroll with max_size 0 no history" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var s = try init(alloc, 80, 24, 0);
+ defer s.deinit();
+
+ try s.growRows(10);
+
+ // Remember initial viewport position
+ const pt_before = s.getCell(.{ .viewport = .{} }).?.screenPoint();
+
+ // Try to scroll backwards into "history" - should be no-op
+ s.scroll(.{ .delta_row = -5 });
+ try testing.expect(s.viewport == .active);
+
+ // Scroll to top - should also be no-op with no scrollback
+ s.scroll(.{ .top = {} });
+ const pt_after = s.getCell(.{ .viewport = .{} }).?.screenPoint();
+ try testing.expectEqual(pt_before, pt_after);
+}
+
test "PageList scroll top" {
const testing = std.testing;
const alloc = testing.allocator;
@@ -5754,8 +6469,8 @@ test "PageList grow prune scrollback" {
const testing = std.testing;
const alloc = testing.allocator;
- // Zero here forces minimum max size to effectively two pages.
- var s = try init(alloc, 80, 24, 0);
+ // Use std_size to limit scrollback so pruning is triggered.
+ var s = try init(alloc, 80, 24, std_size);
defer s.deinit();
// Grow to capacity
@@ -5823,8 +6538,8 @@ test "PageList grow prune scrollback with viewport pin not in pruned page" {
const testing = std.testing;
const alloc = testing.allocator;
- // Zero here forces minimum max size to effectively two pages.
- var s = try init(alloc, 80, 24, 0);
+ // Use std_size to limit scrollback so pruning is triggered.
+ var s = try init(alloc, 80, 24, std_size);
defer s.deinit();
// Grow to capacity of first page
@@ -6130,12 +6845,15 @@ test "PageList eraseRowBounded exhausts pages invalidates viewport offset cache"
}, s.scrollbar());
}
-test "PageList adjustCapacity to increase styles" {
+test "PageList increaseCapacity to increase styles" {
const testing = std.testing;
const alloc = testing.allocator;
var s = try init(alloc, 2, 2, 0);
defer s.deinit();
+
+ const original_styles_cap = s.pages.first.?.data.capacity.styles;
+
{
try testing.expect(s.pages.first == s.pages.last);
const page = &s.pages.first.?.data;
@@ -6153,14 +6871,19 @@ test "PageList adjustCapacity to increase styles" {
}
// Increase our styles
- _ = try s.adjustCapacity(
- s.pages.first.?,
- .{ .styles = std_capacity.styles * 2 },
- );
+ _ = try s.increaseCapacity(s.pages.first.?, .styles);
{
try testing.expect(s.pages.first == s.pages.last);
const page = &s.pages.first.?.data;
+
+ // Verify capacity doubled
+ try testing.expectEqual(
+ original_styles_cap * 2,
+ page.capacity.styles,
+ );
+
+ // Verify data preserved
for (0..s.rows) |y| {
for (0..s.cols) |x| {
const rac = page.getRowAndCell(x, y);
@@ -6173,17 +6896,19 @@ test "PageList adjustCapacity to increase styles" {
}
}
-test "PageList adjustCapacity to increase graphemes" {
+test "PageList increaseCapacity to increase graphemes" {
const testing = std.testing;
const alloc = testing.allocator;
var s = try init(alloc, 2, 2, 0);
defer s.deinit();
+
+ const original_cap = s.pages.first.?.data.capacity.grapheme_bytes;
+
{
try testing.expect(s.pages.first == s.pages.last);
const page = &s.pages.first.?.data;
- // Write all our data so we can assert its the same after
for (0..s.rows) |y| {
for (0..s.cols) |x| {
const rac = page.getRowAndCell(x, y);
@@ -6195,15 +6920,14 @@ test "PageList adjustCapacity to increase graphemes" {
}
}
- // Increase our graphemes
- _ = try s.adjustCapacity(
- s.pages.first.?,
- .{ .grapheme_bytes = std_capacity.grapheme_bytes * 2 },
- );
+ _ = try s.increaseCapacity(s.pages.first.?, .grapheme_bytes);
{
try testing.expect(s.pages.first == s.pages.last);
const page = &s.pages.first.?.data;
+
+ try testing.expectEqual(original_cap * 2, page.capacity.grapheme_bytes);
+
for (0..s.rows) |y| {
for (0..s.cols) |x| {
const rac = page.getRowAndCell(x, y);
@@ -6216,17 +6940,19 @@ test "PageList adjustCapacity to increase graphemes" {
}
}
-test "PageList adjustCapacity to increase hyperlinks" {
+test "PageList increaseCapacity to increase hyperlinks" {
const testing = std.testing;
const alloc = testing.allocator;
var s = try init(alloc, 2, 2, 0);
defer s.deinit();
+
+ const original_cap = s.pages.first.?.data.capacity.hyperlink_bytes;
+
{
try testing.expect(s.pages.first == s.pages.last);
const page = &s.pages.first.?.data;
- // Write all our data so we can assert its the same after
for (0..s.rows) |y| {
for (0..s.cols) |x| {
const rac = page.getRowAndCell(x, y);
@@ -6238,15 +6964,14 @@ test "PageList adjustCapacity to increase hyperlinks" {
}
}
- // Increase our graphemes
- _ = try s.adjustCapacity(
- s.pages.first.?,
- .{ .hyperlink_bytes = @max(std_capacity.hyperlink_bytes * 2, 2048) },
- );
+ _ = try s.increaseCapacity(s.pages.first.?, .hyperlink_bytes);
{
try testing.expect(s.pages.first == s.pages.last);
const page = &s.pages.first.?.data;
+
+ try testing.expectEqual(original_cap * 2, page.capacity.hyperlink_bytes);
+
for (0..s.rows) |y| {
for (0..s.cols) |x| {
const rac = page.getRowAndCell(x, y);
@@ -6259,39 +6984,193 @@ test "PageList adjustCapacity to increase hyperlinks" {
}
}
-test "PageList adjustCapacity after col shrink" {
+test "PageList increaseCapacity to increase string_bytes" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var s = try init(alloc, 2, 2, 0);
+ defer s.deinit();
+
+ const original_cap = s.pages.first.?.data.capacity.string_bytes;
+
+ {
+ try testing.expect(s.pages.first == s.pages.last);
+ const page = &s.pages.first.?.data;
+
+ for (0..s.rows) |y| {
+ for (0..s.cols) |x| {
+ const rac = page.getRowAndCell(x, y);
+ rac.cell.* = .{
+ .content_tag = .codepoint,
+ .content = .{ .codepoint = @intCast(x) },
+ };
+ }
+ }
+ }
+
+ _ = try s.increaseCapacity(s.pages.first.?, .string_bytes);
+
+ {
+ try testing.expect(s.pages.first == s.pages.last);
+ const page = &s.pages.first.?.data;
+
+ try testing.expectEqual(original_cap * 2, page.capacity.string_bytes);
+
+ for (0..s.rows) |y| {
+ for (0..s.cols) |x| {
+ const rac = page.getRowAndCell(x, y);
+ try testing.expectEqual(
+ @as(u21, @intCast(x)),
+ rac.cell.content.codepoint,
+ );
+ }
+ }
+ }
+}
+
+test "PageList increaseCapacity tracked pins" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var s = try init(alloc, 2, 2, 0);
+ defer s.deinit();
+
+ // Create a tracked pin on the first page
+ const tracked = try s.trackPin(s.pin(.{ .active = .{ .x = 1, .y = 1 } }).?);
+ defer s.untrackPin(tracked);
+
+ const old_node = s.pages.first.?;
+ try testing.expectEqual(old_node, tracked.node);
+
+ // Increase capacity
+ const new_node = try s.increaseCapacity(s.pages.first.?, .styles);
+
+ // Pin should now point to the new node
+ try testing.expectEqual(new_node, tracked.node);
+ try testing.expectEqual(@as(size.CellCountInt, 1), tracked.x);
+ try testing.expectEqual(@as(size.CellCountInt, 1), tracked.y);
+}
+
+test "PageList increaseCapacity returns OutOfSpace at max capacity" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var s = try init(alloc, 2, 2, 0);
+ defer s.deinit();
+
+ // Keep increasing styles capacity until we get OutOfSpace
+ const max_styles = std.math.maxInt(size.StyleCountInt);
+ while (true) {
+ _ = s.increaseCapacity(
+ s.pages.first.?,
+ .styles,
+ ) catch |err| {
+ // Before OutOfSpace, we should have reached maxInt
+ try testing.expectEqual(error.OutOfSpace, err);
+ try testing.expectEqual(max_styles, s.pages.first.?.data.capacity.styles);
+ break;
+ };
+ }
+}
+
+test "PageList increaseCapacity after col shrink" {
const testing = std.testing;
const alloc = testing.allocator;
var s = try init(alloc, 10, 2, 0);
defer s.deinit();
- // Shrink columns - this updates size.cols but not capacity.cols
+ // Shrink columns
try s.resize(.{ .cols = 5, .reflow = false });
try testing.expectEqual(5, s.cols);
{
const page = &s.pages.first.?.data;
- // capacity.cols is still 10, but size.cols should be 5
try testing.expectEqual(5, page.size.cols);
try testing.expect(page.capacity.cols >= 10);
}
- // Now adjust capacity (e.g., to increase styles)
- // This should preserve the current size.cols, not revert to capacity.cols
- _ = try s.adjustCapacity(
- s.pages.first.?,
- .{ .styles = std_capacity.styles * 2 },
- );
+ // Increase capacity
+ _ = try s.increaseCapacity(s.pages.first.?, .styles);
{
const page = &s.pages.first.?.data;
- // After adjustCapacity, size.cols should still be 5, not 10
+ // size.cols should still be 5, not reverted to capacity.cols
try testing.expectEqual(5, page.size.cols);
try testing.expectEqual(5, s.cols);
}
}
+test "PageList increaseCapacity multi-page" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var s = try init(alloc, 80, 24, null);
+ defer s.deinit();
+
+ // Grow to create a second page
+ const page1_node = s.pages.last.?;
+ page1_node.data.pauseIntegrityChecks(true);
+ for (0..page1_node.data.capacity.rows - page1_node.data.size.rows) |_| {
+ try testing.expect(try s.grow() == null);
+ }
+ page1_node.data.pauseIntegrityChecks(false);
+ try testing.expect(try s.grow() != null);
+
+ // Now we have two pages
+ try testing.expect(s.pages.first != s.pages.last);
+ const page2_node = s.pages.last.?;
+
+ const page1_styles_cap = s.pages.first.?.data.capacity.styles;
+ const page2_styles_cap = page2_node.data.capacity.styles;
+
+ // Increase capacity on the first page only
+ _ = try s.increaseCapacity(s.pages.first.?, .styles);
+
+ // First page capacity should be doubled
+ try testing.expectEqual(
+ page1_styles_cap * 2,
+ s.pages.first.?.data.capacity.styles,
+ );
+
+ // Second page should be unchanged
+ try testing.expectEqual(
+ page2_styles_cap,
+ s.pages.last.?.data.capacity.styles,
+ );
+}
+
+test "PageList increaseCapacity preserves dirty flag" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var s = try init(alloc, 2, 4, 0);
+ defer s.deinit();
+
+ // Set page dirty flag and mark some rows as dirty
+ const page = &s.pages.first.?.data;
+ page.dirty = true;
+
+ const rows = page.rows.ptr(page.memory);
+ rows[0].dirty = true;
+ rows[1].dirty = false;
+ rows[2].dirty = true;
+ rows[3].dirty = false;
+
+ // Increase capacity
+ const new_node = try s.increaseCapacity(s.pages.first.?, .styles);
+
+ // The page dirty flag should be preserved
+ try testing.expect(new_node.data.dirty);
+
+ // Row dirty flags should be preserved
+ const new_rows = new_node.data.rows.ptr(new_node.data.memory);
+ try testing.expect(new_rows[0].dirty);
+ try testing.expect(!new_rows[1].dirty);
+ try testing.expect(new_rows[2].dirty);
+ try testing.expect(!new_rows[3].dirty);
+}
+
test "PageList pageIterator single page" {
const testing = std.testing;
const alloc = testing.allocator;
@@ -7018,9 +7897,8 @@ test "PageList clone" {
defer s.deinit();
try testing.expectEqual(@as(usize, s.rows), s.totalRows());
- var s2 = try s.clone(.{
+ var s2 = try s.clone(alloc, .{
.top = .{ .screen = .{} },
- .memory = .{ .alloc = alloc },
});
defer s2.deinit();
try testing.expectEqual(@as(usize, s.rows), s2.totalRows());
@@ -7035,10 +7913,9 @@ test "PageList clone partial trimmed right" {
try testing.expectEqual(@as(usize, s.rows), s.totalRows());
try s.growRows(30);
- var s2 = try s.clone(.{
+ var s2 = try s.clone(alloc, .{
.top = .{ .screen = .{} },
.bot = .{ .screen = .{ .y = 39 } },
- .memory = .{ .alloc = alloc },
});
defer s2.deinit();
try testing.expectEqual(@as(usize, 40), s2.totalRows());
@@ -7053,9 +7930,8 @@ test "PageList clone partial trimmed left" {
try testing.expectEqual(@as(usize, s.rows), s.totalRows());
try s.growRows(30);
- var s2 = try s.clone(.{
+ var s2 = try s.clone(alloc, .{
.top = .{ .screen = .{ .y = 10 } },
- .memory = .{ .alloc = alloc },
});
defer s2.deinit();
try testing.expectEqual(@as(usize, 40), s2.totalRows());
@@ -7097,9 +7973,8 @@ test "PageList clone partial trimmed left reclaims styles" {
try testing.expectEqual(1, page.styles.count());
}
- var s2 = try s.clone(.{
+ var s2 = try s.clone(alloc, .{
.top = .{ .screen = .{ .y = 10 } },
- .memory = .{ .alloc = alloc },
});
defer s2.deinit();
try testing.expectEqual(@as(usize, 40), s2.totalRows());
@@ -7120,10 +7995,9 @@ test "PageList clone partial trimmed both" {
try testing.expectEqual(@as(usize, s.rows), s.totalRows());
try s.growRows(30);
- var s2 = try s.clone(.{
+ var s2 = try s.clone(alloc, .{
.top = .{ .screen = .{ .y = 10 } },
.bot = .{ .screen = .{ .y = 35 } },
- .memory = .{ .alloc = alloc },
});
defer s2.deinit();
try testing.expectEqual(@as(usize, 26), s2.totalRows());
@@ -7137,9 +8011,8 @@ test "PageList clone less than active" {
defer s.deinit();
try testing.expectEqual(@as(usize, s.rows), s.totalRows());
- var s2 = try s.clone(.{
+ var s2 = try s.clone(alloc, .{
.top = .{ .active = .{ .y = 5 } },
- .memory = .{ .alloc = alloc },
});
defer s2.deinit();
try testing.expectEqual(@as(usize, s.rows), s2.totalRows());
@@ -7159,9 +8032,8 @@ test "PageList clone remap tracked pin" {
var pin_remap = Clone.TrackedPinsRemap.init(alloc);
defer pin_remap.deinit();
- var s2 = try s.clone(.{
+ var s2 = try s.clone(alloc, .{
.top = .{ .active = .{ .y = 5 } },
- .memory = .{ .alloc = alloc },
.tracked_pins = &pin_remap,
});
defer s2.deinit();
@@ -7188,9 +8060,8 @@ test "PageList clone remap tracked pin not in cloned area" {
var pin_remap = Clone.TrackedPinsRemap.init(alloc);
defer pin_remap.deinit();
- var s2 = try s.clone(.{
+ var s2 = try s.clone(alloc, .{
.top = .{ .active = .{ .y = 5 } },
- .memory = .{ .alloc = alloc },
.tracked_pins = &pin_remap,
});
defer s2.deinit();
@@ -7212,9 +8083,8 @@ test "PageList clone full dirty" {
s.markDirty(.{ .active = .{ .x = 0, .y = 12 } });
s.markDirty(.{ .active = .{ .x = 0, .y = 23 } });
- var s2 = try s.clone(.{
+ var s2 = try s.clone(alloc, .{
.top = .{ .screen = .{} },
- .memory = .{ .alloc = alloc },
});
defer s2.deinit();
try testing.expectEqual(@as(usize, s.rows), s2.totalRows());
@@ -8275,7 +9145,7 @@ test "PageList resize reflow invalidates viewport offset cache" {
// Verify scrollbar cache was invalidated during reflow
try testing.expectEqual(Scrollbar{
.total = s.total_rows,
- .offset = 8,
+ .offset = 5,
.len = s.rows,
}, s.scrollbar());
}
@@ -10841,3 +11711,990 @@ test "PageList resize reflow grapheme map capacity exceeded" {
// Verify the resize succeeded
try testing.expectEqual(@as(usize, 2), s.cols);
}
+
+test "PageList resize grow cols with unwrap fixes viewport pin" {
+ // Regression test: after resize/reflow, the viewport pin can end up at a
+ // position where pin.y + rows > total_rows, causing getBottomRight to panic.
+
+ // The plan is to pin viewport in history, then grow columns to unwrap rows.
+ // The unwrap reduces total_rows, but the tracked pin moves to a position
+ // that no longer has enough rows below it for the viewport height.
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var s = try init(alloc, 2, 10, null);
+ defer s.deinit();
+
+ // Make sure we have some history, in this case we have 30 rows of history
+ try s.growRows(30);
+ try testing.expectEqual(@as(usize, 40), s.totalRows());
+
+ // Fill all rows with wrapped content (pairs that unwrap when cols increase)
+ var it = s.pageIterator(.right_down, .{ .screen = .{} }, null);
+ while (it.next()) |chunk| {
+ const page = &chunk.node.data;
+ for (chunk.start..chunk.end) |y| {
+ const rac = page.getRowAndCell(0, y);
+ if (y % 2 == 0) {
+ rac.row.wrap = true;
+ } else {
+ rac.row.wrap_continuation = true;
+ }
+ for (0..s.cols) |x| {
+ page.getRowAndCell(x, y).cell.* = .{
+ .content_tag = .codepoint,
+ .content = .{ .codepoint = 'A' },
+ };
+ }
+ }
+ }
+
+ // Pin viewport at row 28 (in history, 2 rows before active area at row 30).
+ // After unwrap: row 28 -> row 14, total_rows 40 -> 20, active starts at 10.
+ // Pin at 14 needs rows 14-23, but only 0-19 exist -> overflow.
+ s.scroll(.{ .pin = s.pin(.{ .screen = .{ .y = 28 } }).? });
+ try testing.expect(s.viewport == .pin);
+ try testing.expect(s.getBottomRight(.viewport) != null);
+
+ // Resize with reflow: unwraps rows, reducing total_rows
+ try s.resize(.{ .cols = 4, .reflow = true });
+ try testing.expectEqual(@as(usize, 4), s.cols);
+ try testing.expect(s.totalRows() < 40);
+
+ // Used to panic here, so test that we can get the bottom right.
+ const br_after = s.getBottomRight(.viewport);
+ try testing.expect(br_after != null);
+}
+
+test "PageList grow reuses non-standard page without leak" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ // Create a PageList with 3 * std_size max so we can fit multiple pages
+ // but will still trigger reuse.
+ var s = try init(alloc, 80, 24, 3 * std_size);
+ defer s.deinit();
+
+ // Increase the first page capacity to make it non-standard (larger than std_size).
+ while (s.pages.first.?.data.memory.len <= std_size) {
+ _ = try s.increaseCapacity(s.pages.first.?, .grapheme_bytes);
+ }
+
+ // The first page should now have non-standard memory size.
+ try testing.expect(s.pages.first.?.data.memory.len > std_size);
+
+ // First, fill up the first page's capacity
+ const first_page = s.pages.first.?;
+ while (first_page.data.size.rows < first_page.data.capacity.rows) {
+ _ = try s.grow();
+ }
+
+ // Now grow to create a second page
+ _ = try s.grow();
+ try testing.expect(s.pages.first != s.pages.last);
+
+ // Continue growing until we exceed max_size AND the last page is full
+ while (s.page_size + PagePool.item_size <= s.maxSize() or
+ s.pages.last.?.data.size.rows < s.pages.last.?.data.capacity.rows)
+ {
+ _ = try s.grow();
+ }
+
+ // The first page should still be non-standard
+ try testing.expect(s.pages.first.?.data.memory.len > std_size);
+
+ // Verify we have enough rows for active area (so prune path isn't skipped)
+ try testing.expect(s.totalRows() >= s.rows);
+
+ // Verify last page is full (so grow will need to allocate/reuse)
+ try testing.expect(s.pages.last.?.data.size.rows == s.pages.last.?.data.capacity.rows);
+
+ // Remember the first page memory pointer before the reuse attempt
+ const first_page_ptr = s.pages.first.?;
+ const first_page_mem_ptr = s.pages.first.?.data.memory.ptr;
+
+ // Create a tracked pin pointing to the non-standard first page
+ const tracked_pin = try s.trackPin(.{ .node = first_page_ptr, .x = 0, .y = 0 });
+ defer s.untrackPin(tracked_pin);
+
+ // Now grow one more time to trigger the reuse path. Since the first page
+ // is non-standard, it should be destroyed (not reused). The testing
+ // allocator will detect a leak if destroyNode doesn't properly free
+ // the non-standard memory.
+ _ = try s.grow();
+
+ // After grow, check if the first page is a different one
+ // (meaning the non-standard page was pruned, not reused at the end)
+ // The original first page should no longer be the first page
+ try testing.expect(s.pages.first.? != first_page_ptr);
+
+ // If the non-standard page was properly destroyed and not reused,
+ // the last page should not have the same memory pointer
+ try testing.expect(s.pages.last.?.data.memory.ptr != first_page_mem_ptr);
+
+ // The tracked pin should have been moved to the new first page and marked as garbage
+ try testing.expectEqual(s.pages.first.?, tracked_pin.node);
+ try testing.expectEqual(0, tracked_pin.x);
+ try testing.expectEqual(0, tracked_pin.y);
+ try testing.expect(tracked_pin.garbage);
+}
+
+test "PageList grow non-standard page prune protection" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ // This test specifically verifies the fix for the bug where pruning a
+ // non-standard page would cause totalRows() < self.rows.
+ //
+ // Bug trigger conditions (all must be true simultaneously):
+ // 1. first page is non-standard (memory.len > std_size)
+ // 2. page_size + PagePool.item_size > maxSize() (triggers prune consideration)
+ // 3. pages.first != pages.last (have multiple pages)
+ // 4. total_rows >= self.rows (have enough rows for active area)
+ // 5. total_rows - first.size.rows + 1 < self.rows (prune would lose too many)
+
+ // This is kind of magic and likely depends on std_size.
+ const rows_count = 600;
+ var s = try init(alloc, 80, rows_count, std_size);
+ defer s.deinit();
+
+ // Make the first page non-standard
+ while (s.pages.first.?.data.memory.len <= std_size) {
+ _ = try s.increaseCapacity(
+ s.pages.first.?,
+ .grapheme_bytes,
+ );
+ }
+ try testing.expect(s.pages.first.?.data.memory.len > std_size);
+
+ const first_page_node = s.pages.first.?;
+ const first_page_cap = first_page_node.data.capacity.rows;
+
+ // Fill first page to capacity
+ while (first_page_node.data.size.rows < first_page_cap) _ = try s.grow();
+
+ // Grow until we have a second page (first page fills up first)
+ var second_node: ?*List.Node = null;
+ while (s.pages.first == s.pages.last) second_node = try s.grow();
+ try testing.expect(s.pages.first != s.pages.last);
+
+ // Fill the second page to capacity so that the next grow() triggers prune
+ const last_node = s.pages.last.?;
+ const second_cap = last_node.data.capacity.rows;
+ while (last_node.data.size.rows < second_cap) _ = try s.grow();
+
+ // Now the last page is full. The next grow must either:
+ // 1. Prune the first page and reuse it, OR
+ // 2. Allocate a new page
+ const total = s.totalRows();
+ const would_remain = total - first_page_cap + 1;
+
+ // Verify the bug condition is present: pruning first page would leave < rows
+ try testing.expect(would_remain < s.rows);
+
+ // Verify prune path conditions are met
+ try testing.expect(s.pages.first != s.pages.last);
+ try testing.expect(s.page_size + PagePool.item_size > s.maxSize());
+ try testing.expect(s.totalRows() >= s.rows);
+
+ // Verify last page is at capacity (so grow must prune or allocate new)
+ try testing.expectEqual(second_cap, last_node.data.size.rows);
+
+ // The next grow should trigger prune consideration.
+ // Without the fix, this would destroy the non-standard first page,
+ // leaving only second_cap + 1 rows, which is < self.rows.
+ _ = try s.grow();
+
+ // Verify the invariant holds - the fix prevents the destructive prune
+ try testing.expect(s.totalRows() >= s.rows);
+}
+
+test "PageList resize (no reflow) more cols remaps pins in backfill path" {
+ // Regression test: when resizeWithoutReflowGrowCols copies rows to a previous
+ // page with spare capacity, tracked pins in those rows must be remapped.
+ // Without the fix, pins become dangling pointers when the original page is destroyed.
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ const cols: size.CellCountInt = 5;
+ const cap = try std_capacity.adjust(.{ .cols = cols });
+ var s = try init(alloc, cols, cap.rows, null);
+ defer s.deinit();
+
+ // Grow until we have two pages.
+ while (s.pages.first == s.pages.last) {
+ _ = try s.grow();
+ }
+ const first_page = s.pages.first.?;
+ const second_page = s.pages.last.?;
+ try testing.expect(first_page != second_page);
+
+ // Trim a history row so the first page has spare capacity.
+ // This triggers the backfill path in resizeWithoutReflowGrowCols.
+ s.eraseRows(.{ .history = .{} }, .{ .history = .{ .y = 0 } });
+ try testing.expect(first_page.data.size.rows < first_page.data.capacity.rows);
+
+ // Ensure the resize takes the slow path (new capacity > current capacity).
+ const new_cols: size.CellCountInt = cols + 1;
+ const adjusted = try second_page.data.capacity.adjust(.{ .cols = new_cols });
+ try testing.expect(second_page.data.capacity.cols < adjusted.cols);
+
+ // Track a pin in row 0 of the second page. This row will be copied
+ // to the first page during backfill and the pin must be remapped.
+ const tracked = try s.trackPin(.{ .node = second_page, .x = 0, .y = 0 });
+ defer s.untrackPin(tracked);
+
+ // Write a marker character to the tracked cell so we can verify
+ // the pin points to the correct cell after resize.
+ const marker: u21 = 'X';
+ tracked.rowAndCell().cell.* = .{
+ .content_tag = .codepoint,
+ .content = .{ .codepoint = marker },
+ };
+
+ try s.resize(.{ .cols = new_cols, .reflow = false });
+
+ // Verify the pin points to a valid node still in the page list.
+ var found = false;
+ var it = s.pages.first;
+ while (it) |node| : (it = node.next) {
+ if (node == tracked.node) {
+ found = true;
+ break;
+ }
+ }
+ try testing.expect(found);
+ try testing.expect(tracked.y < tracked.node.data.size.rows);
+
+ // Verify the pin still points to the cell with our marker content.
+ const cell = tracked.rowAndCell().cell;
+ try testing.expectEqual(.codepoint, cell.content_tag);
+ try testing.expectEqual(marker, cell.content.codepoint);
+}
+
+test "PageList compact std_size page returns null" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var s = try init(alloc, 80, 24, 0);
+ defer s.deinit();
+
+ // A freshly created page should be at std_size
+ const node = s.pages.first.?;
+ try testing.expect(node.data.memory.len <= std_size);
+
+ // compact should return null since there's nothing to compact
+ const result = try s.compact(node);
+ try testing.expectEqual(null, result);
+
+ // Page should still be the same
+ try testing.expectEqual(node, s.pages.first.?);
+}
+
+test "PageList compact oversized page" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var s = try init(alloc, 80, 24, null);
+ defer s.deinit();
+
+ // Grow until we have multiple pages
+ const page1_node = s.pages.first.?;
+ page1_node.data.pauseIntegrityChecks(true);
+ for (0..page1_node.data.capacity.rows - page1_node.data.size.rows) |_| {
+ _ = try s.grow();
+ }
+ page1_node.data.pauseIntegrityChecks(false);
+ _ = try s.grow();
+ try testing.expect(s.pages.first != s.pages.last);
+
+ var node = s.pages.first.?;
+
+ // Write content to verify it's preserved
+ {
+ const page = &node.data;
+ for (0..page.size.rows) |y| {
+ for (0..s.cols) |x| {
+ const rac = page.getRowAndCell(x, y);
+ rac.cell.* = .{
+ .content_tag = .codepoint,
+ .content = .{ .codepoint = @intCast(x + y * s.cols) },
+ };
+ }
+ }
+ }
+
+ // Create a tracked pin on this page
+ const tracked = try s.trackPin(.{ .node = node, .x = 5, .y = 10 });
+ defer s.untrackPin(tracked);
+
+ // Make the page oversized
+ while (node.data.memory.len <= std_size) {
+ node = try s.increaseCapacity(node, .grapheme_bytes);
+ }
+ try testing.expect(node.data.memory.len > std_size);
+ const oversized_len = node.data.memory.len;
+ const original_size = node.data.size;
+ const second_node = node.next.?;
+
+ // Set dirty flag after increaseCapacity
+ node.data.dirty = true;
+
+ // Compact the page
+ const new_node = try s.compact(node);
+ try testing.expect(new_node != null);
+
+ // Verify memory is smaller
+ try testing.expect(new_node.?.data.memory.len < oversized_len);
+
+ // Verify size preserved
+ try testing.expectEqual(original_size.rows, new_node.?.data.size.rows);
+ try testing.expectEqual(original_size.cols, new_node.?.data.size.cols);
+
+ // Verify dirty flag preserved
+ try testing.expect(new_node.?.data.dirty);
+
+ // Verify linked list integrity
+ try testing.expectEqual(new_node.?, s.pages.first.?);
+ try testing.expectEqual(null, new_node.?.prev);
+ try testing.expectEqual(second_node, new_node.?.next);
+ try testing.expectEqual(new_node.?, second_node.prev);
+
+ // Verify pin updated correctly
+ try testing.expectEqual(new_node.?, tracked.node);
+ try testing.expectEqual(@as(size.CellCountInt, 5), tracked.x);
+ try testing.expectEqual(@as(size.CellCountInt, 10), tracked.y);
+
+ // Verify content preserved
+ const page = &new_node.?.data;
+ for (0..page.size.rows) |y| {
+ for (0..s.cols) |x| {
+ const rac = page.getRowAndCell(x, y);
+ try testing.expectEqual(
+ @as(u21, @intCast(x + y * s.cols)),
+ rac.cell.content.codepoint,
+ );
+ }
+ }
+}
+
+test "PageList compact insufficient savings returns null" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var s = try init(alloc, 80, 24, 0);
+ defer s.deinit();
+
+ var node = s.pages.first.?;
+
+ // Make the page slightly oversized (just one increase)
+ // This might not provide enough savings to justify compaction
+ node = try s.increaseCapacity(node, .grapheme_bytes);
+
+ // If the page is still at or below std_size, compact returns null
+ if (node.data.memory.len <= std_size) {
+ const result = try s.compact(node);
+ try testing.expectEqual(null, result);
+ } else {
+ // If it did grow beyond std_size, verify that compaction
+ // works or returns null based on savings calculation
+ const result = try s.compact(node);
+ // Either it compacted or determined insufficient savings
+ if (result) |new_node| {
+ try testing.expect(new_node.data.memory.len < node.data.memory.len);
+ }
+ }
+}
+
+test "PageList split at middle row" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var s = try init(alloc, 10, 10, 0);
+ defer s.deinit();
+
+ const page = &s.pages.first.?.data;
+
+ // Write content to rows: row 0 gets codepoint 0, row 1 gets 1, etc.
+ for (0..page.size.rows) |y| {
+ const rac = page.getRowAndCell(0, y);
+ rac.cell.* = .{
+ .content_tag = .codepoint,
+ .content = .{ .codepoint = @intCast(y) },
+ };
+ }
+
+ // Split at row 5 (middle)
+ const split_pin: Pin = .{ .node = s.pages.first.?, .y = 5, .x = 0 };
+ try s.split(split_pin);
+
+ // Verify two pages exist
+ try testing.expect(s.pages.first != null);
+ try testing.expect(s.pages.first.?.next != null);
+
+ const first_page = &s.pages.first.?.data;
+ const second_page = &s.pages.first.?.next.?.data;
+
+ // First page should have rows 0-4 (5 rows)
+ try testing.expectEqual(@as(usize, 5), first_page.size.rows);
+ // Second page should have rows 5-9 (5 rows)
+ try testing.expectEqual(@as(usize, 5), second_page.size.rows);
+
+ // Verify content in first page is preserved (rows 0-4 have codepoints 0-4)
+ for (0..5) |y| {
+ const rac = first_page.getRowAndCell(0, y);
+ try testing.expectEqual(@as(u21, @intCast(y)), rac.cell.content.codepoint);
+ }
+
+ // Verify content in second page (original rows 5-9, now at y=0-4)
+ for (0..5) |y| {
+ const rac = second_page.getRowAndCell(0, y);
+ try testing.expectEqual(@as(u21, @intCast(y + 5)), rac.cell.content.codepoint);
+ }
+}
+
+test "PageList split at row 0 is no-op" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var s = try init(alloc, 10, 10, 0);
+ defer s.deinit();
+
+ const page = &s.pages.first.?.data;
+
+ // Write content to all rows
+ for (0..page.size.rows) |y| {
+ const rac = page.getRowAndCell(0, y);
+ rac.cell.* = .{
+ .content_tag = .codepoint,
+ .content = .{ .codepoint = @intCast(y) },
+ };
+ }
+
+ // Split at row 0 should be a no-op
+ const split_pin: Pin = .{ .node = s.pages.first.?, .y = 0, .x = 0 };
+ try s.split(split_pin);
+
+ // Verify only one page exists (no split occurred)
+ try testing.expect(s.pages.first != null);
+ try testing.expect(s.pages.first.?.next == null);
+
+ // Verify all content is still in the original page
+ try testing.expectEqual(@as(usize, 10), page.size.rows);
+ for (0..10) |y| {
+ const rac = page.getRowAndCell(0, y);
+ try testing.expectEqual(@as(u21, @intCast(y)), rac.cell.content.codepoint);
+ }
+}
+
+test "PageList split at last row" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var s = try init(alloc, 10, 10, 0);
+ defer s.deinit();
+
+ const page = &s.pages.first.?.data;
+
+ // Write content to all rows
+ for (0..page.size.rows) |y| {
+ const rac = page.getRowAndCell(0, y);
+ rac.cell.* = .{
+ .content_tag = .codepoint,
+ .content = .{ .codepoint = @intCast(y) },
+ };
+ }
+
+ // Split at last row (row 9)
+ const split_pin: Pin = .{ .node = s.pages.first.?, .y = 9, .x = 0 };
+ try s.split(split_pin);
+
+ // Verify two pages exist
+ try testing.expect(s.pages.first != null);
+ try testing.expect(s.pages.first.?.next != null);
+
+ const first_page = &s.pages.first.?.data;
+ const second_page = &s.pages.first.?.next.?.data;
+
+ // First page should have 9 rows
+ try testing.expectEqual(@as(usize, 9), first_page.size.rows);
+ // Second page should have 1 row
+ try testing.expectEqual(@as(usize, 1), second_page.size.rows);
+
+ // Verify content in second page (original row 9, now at y=0)
+ const rac = second_page.getRowAndCell(0, 0);
+ try testing.expectEqual(@as(u21, 9), rac.cell.content.codepoint);
+}
+
+test "PageList split single row page returns OutOfSpace" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ // Initialize with 1 row
+ var s = try init(alloc, 10, 1, 0);
+ defer s.deinit();
+
+ const split_pin: Pin = .{ .node = s.pages.first.?, .y = 0, .x = 0 };
+ const result = s.split(split_pin);
+
+ try testing.expectError(error.OutOfSpace, result);
+}
+
+test "PageList split moves tracked pins" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var s = try init(alloc, 10, 10, 0);
+ defer s.deinit();
+
+ // Track a pin at row 7
+ const tracked = try s.trackPin(.{ .node = s.pages.first.?, .y = 7, .x = 3 });
+ defer s.untrackPin(tracked);
+
+ // Split at row 5
+ const split_pin: Pin = .{ .node = s.pages.first.?, .y = 5, .x = 0 };
+ try s.split(split_pin);
+
+ // The tracked pin should now be in the second page
+ try testing.expect(tracked.node == s.pages.first.?.next.?);
+ // y should be adjusted: was 7, split at 5, so new y = 7 - 5 = 2
+ try testing.expectEqual(@as(usize, 2), tracked.y);
+ // x should remain unchanged
+ try testing.expectEqual(@as(usize, 3), tracked.x);
+}
+
+test "PageList split tracked pin before split point unchanged" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var s = try init(alloc, 10, 10, 0);
+ defer s.deinit();
+
+ const original_node = s.pages.first.?;
+
+ // Track a pin at row 2 (before the split point)
+ const tracked = try s.trackPin(.{ .node = original_node, .y = 2, .x = 5 });
+ defer s.untrackPin(tracked);
+
+ // Split at row 5
+ const split_pin: Pin = .{ .node = original_node, .y = 5, .x = 0 };
+ try s.split(split_pin);
+
+ // The tracked pin should remain in the original page
+ try testing.expect(tracked.node == s.pages.first.?);
+ // y and x should be unchanged
+ try testing.expectEqual(@as(usize, 2), tracked.y);
+ try testing.expectEqual(@as(usize, 5), tracked.x);
+}
+
+test "PageList split tracked pin at split point moves to new page" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var s = try init(alloc, 10, 10, 0);
+ defer s.deinit();
+
+ const original_node = s.pages.first.?;
+
+ // Track a pin at the exact split point (row 5)
+ const tracked = try s.trackPin(.{ .node = original_node, .y = 5, .x = 4 });
+ defer s.untrackPin(tracked);
+
+ // Split at row 5
+ const split_pin: Pin = .{ .node = original_node, .y = 5, .x = 0 };
+ try s.split(split_pin);
+
+ // The tracked pin should be in the new page
+ try testing.expect(tracked.node == s.pages.first.?.next.?);
+ // y should be 0 since it was at the split point: 5 - 5 = 0
+ try testing.expectEqual(@as(usize, 0), tracked.y);
+ // x should remain unchanged
+ try testing.expectEqual(@as(usize, 4), tracked.x);
+}
+
+test "PageList split multiple tracked pins across regions" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var s = try init(alloc, 10, 10, 0);
+ defer s.deinit();
+
+ const original_node = s.pages.first.?;
+
+ // Track multiple pins in different regions
+ const pin_before = try s.trackPin(.{ .node = original_node, .y = 1, .x = 0 });
+ defer s.untrackPin(pin_before);
+ const pin_at_split = try s.trackPin(.{ .node = original_node, .y = 5, .x = 2 });
+ defer s.untrackPin(pin_at_split);
+ const pin_after1 = try s.trackPin(.{ .node = original_node, .y = 7, .x = 3 });
+ defer s.untrackPin(pin_after1);
+ const pin_after2 = try s.trackPin(.{ .node = original_node, .y = 9, .x = 8 });
+ defer s.untrackPin(pin_after2);
+
+ // Split at row 5
+ const split_pin: Pin = .{ .node = original_node, .y = 5, .x = 0 };
+ try s.split(split_pin);
+
+ const first_page = s.pages.first.?;
+ const second_page = first_page.next.?;
+
+ // Pin before split point stays in original page
+ try testing.expect(pin_before.node == first_page);
+ try testing.expectEqual(@as(usize, 1), pin_before.y);
+ try testing.expectEqual(@as(usize, 0), pin_before.x);
+
+ // Pin at split point moves to new page with y=0
+ try testing.expect(pin_at_split.node == second_page);
+ try testing.expectEqual(@as(usize, 0), pin_at_split.y);
+ try testing.expectEqual(@as(usize, 2), pin_at_split.x);
+
+ // Pins after split point move to new page with adjusted y
+ try testing.expect(pin_after1.node == second_page);
+ try testing.expectEqual(@as(usize, 2), pin_after1.y); // 7 - 5 = 2
+ try testing.expectEqual(@as(usize, 3), pin_after1.x);
+
+ try testing.expect(pin_after2.node == second_page);
+ try testing.expectEqual(@as(usize, 4), pin_after2.y); // 9 - 5 = 4
+ try testing.expectEqual(@as(usize, 8), pin_after2.x);
+}
+
+test "PageList split tracked viewport_pin in split region moves correctly" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var s = try init(alloc, 10, 10, 0);
+ defer s.deinit();
+
+ const original_node = s.pages.first.?;
+
+ // Set viewport_pin to row 7 (after split point)
+ s.viewport_pin.node = original_node;
+ s.viewport_pin.y = 7;
+ s.viewport_pin.x = 6;
+
+ // Split at row 5
+ const split_pin: Pin = .{ .node = original_node, .y = 5, .x = 0 };
+ try s.split(split_pin);
+
+ // viewport_pin should be in the new page
+ try testing.expect(s.viewport_pin.node == s.pages.first.?.next.?);
+ // y should be adjusted: 7 - 5 = 2
+ try testing.expectEqual(@as(usize, 2), s.viewport_pin.y);
+ // x should remain unchanged
+ try testing.expectEqual(@as(usize, 6), s.viewport_pin.x);
+}
+
+test "PageList split middle page preserves linked list order" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ // Create a single page with 12 rows
+ var s = try init(alloc, 10, 12, 0);
+ defer s.deinit();
+
+ // Split at row 4 to create: page1 (rows 0-3), page2 (rows 4-11)
+ const first_node = s.pages.first.?;
+ const split_pin1: Pin = .{ .node = first_node, .y = 4, .x = 0 };
+ try s.split(split_pin1);
+
+ // Now we have 2 pages
+ const page1 = s.pages.first.?;
+ const page2 = s.pages.first.?.next.?;
+ try testing.expectEqual(@as(usize, 4), page1.data.size.rows);
+ try testing.expectEqual(@as(usize, 8), page2.data.size.rows);
+
+ // Split page2 at row 4 to create: page1 -> page2 (rows 0-3) -> page3 (rows 4-7)
+ const split_pin2: Pin = .{ .node = page2, .y = 4, .x = 0 };
+ try s.split(split_pin2);
+
+ // Now we have 3 pages
+ const first = s.pages.first.?;
+ const middle = first.next.?;
+ const last = middle.next.?;
+
+ // Verify linked list order: first -> middle -> last
+ try testing.expectEqual(page1, first);
+ try testing.expectEqual(page2, middle);
+ try testing.expectEqual(s.pages.last.?, last);
+
+ // Verify prev pointers
+ try testing.expect(first.prev == null);
+ try testing.expectEqual(first, middle.prev.?);
+ try testing.expectEqual(middle, last.prev.?);
+
+ // Verify next pointers
+ try testing.expectEqual(middle, first.next.?);
+ try testing.expectEqual(last, middle.next.?);
+ try testing.expect(last.next == null);
+
+ // Verify row counts
+ try testing.expectEqual(@as(usize, 4), first.data.size.rows);
+ try testing.expectEqual(@as(usize, 4), middle.data.size.rows);
+ try testing.expectEqual(@as(usize, 4), last.data.size.rows);
+}
+
+test "PageList split last page makes new page the last" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ // Create a single page with 10 rows
+ var s = try init(alloc, 10, 10, 0);
+ defer s.deinit();
+
+ // Split to create 2 pages first
+ const first_node = s.pages.first.?;
+ const split_pin1: Pin = .{ .node = first_node, .y = 5, .x = 0 };
+ try s.split(split_pin1);
+
+ // Now split the last page
+ const last_before_split = s.pages.last.?;
+ try testing.expectEqual(@as(usize, 5), last_before_split.data.size.rows);
+
+ const split_pin2: Pin = .{ .node = last_before_split, .y = 2, .x = 0 };
+ try s.split(split_pin2);
+
+ // The new page should be the new last
+ const new_last = s.pages.last.?;
+ try testing.expect(new_last != last_before_split);
+ try testing.expectEqual(last_before_split, new_last.prev.?);
+ try testing.expect(new_last.next == null);
+
+ // Verify row counts: original last has 2 rows, new last has 3 rows
+ try testing.expectEqual(@as(usize, 2), last_before_split.data.size.rows);
+ try testing.expectEqual(@as(usize, 3), new_last.data.size.rows);
+}
+
+test "PageList split first page keeps original as first" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ // Create 2 pages by splitting
+ var s = try init(alloc, 10, 10, 0);
+ defer s.deinit();
+
+ const original_first = s.pages.first.?;
+ const split_pin1: Pin = .{ .node = original_first, .y = 5, .x = 0 };
+ try s.split(split_pin1);
+
+ // Get second page (created by first split)
+ const second_page = s.pages.first.?.next.?;
+
+ // Now split the first page again
+ const split_pin2: Pin = .{ .node = s.pages.first.?, .y = 2, .x = 0 };
+ try s.split(split_pin2);
+
+ // Original first should still be first
+ try testing.expectEqual(original_first, s.pages.first.?);
+ try testing.expect(s.pages.first.?.prev == null);
+
+ // New page should be inserted between first and second
+ const inserted = s.pages.first.?.next.?;
+ try testing.expect(inserted != second_page);
+ try testing.expectEqual(second_page, inserted.next.?);
+
+ // Verify row counts: first has 2, inserted has 3, second has 5
+ try testing.expectEqual(@as(usize, 2), s.pages.first.?.data.size.rows);
+ try testing.expectEqual(@as(usize, 3), inserted.data.size.rows);
+ try testing.expectEqual(@as(usize, 5), second_page.data.size.rows);
+}
+
+test "PageList split preserves wrap flags" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var s = try init(alloc, 10, 10, 0);
+ defer s.deinit();
+
+ const page = &s.pages.first.?.data;
+
+ // Set wrap flags on rows that will be in the second page after split
+ // Row 5: wrap = true (this is the start of a wrapped line)
+ // Row 6: wrap_continuation = true (this continues the wrap)
+ // Row 7: wrap = true, wrap_continuation = true (wrapped and continues)
+ {
+ const rac5 = page.getRowAndCell(0, 5);
+ rac5.row.wrap = true;
+
+ const rac6 = page.getRowAndCell(0, 6);
+ rac6.row.wrap_continuation = true;
+
+ const rac7 = page.getRowAndCell(0, 7);
+ rac7.row.wrap = true;
+ rac7.row.wrap_continuation = true;
+ }
+
+ // Split at row 5
+ const split_pin: Pin = .{ .node = s.pages.first.?, .y = 5, .x = 0 };
+ try s.split(split_pin);
+
+ const second_page = &s.pages.first.?.next.?.data;
+
+ // Verify wrap flags are preserved in new page
+ // Original row 5 is now row 0 in second page
+ {
+ const rac0 = second_page.getRowAndCell(0, 0);
+ try testing.expect(rac0.row.wrap);
+ try testing.expect(!rac0.row.wrap_continuation);
+ }
+
+ // Original row 6 is now row 1 in second page
+ {
+ const rac1 = second_page.getRowAndCell(0, 1);
+ try testing.expect(!rac1.row.wrap);
+ try testing.expect(rac1.row.wrap_continuation);
+ }
+
+ // Original row 7 is now row 2 in second page
+ {
+ const rac2 = second_page.getRowAndCell(0, 2);
+ try testing.expect(rac2.row.wrap);
+ try testing.expect(rac2.row.wrap_continuation);
+ }
+}
+
+test "PageList split preserves styled cells" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var s = try init(alloc, 10, 10, 0);
+ defer s.deinit();
+
+ const page = &s.pages.first.?.data;
+
+ // Create a style and apply it to cells in rows 5-7 (which will be in the second page)
+ const style: stylepkg.Style = .{ .flags = .{ .bold = true } };
+ const style_id = try page.styles.add(page.memory, style);
+
+ for (5..8) |y| {
+ const rac = page.getRowAndCell(0, y);
+ rac.cell.* = .{
+ .content_tag = .codepoint,
+ .content = .{ .codepoint = 'S' },
+ .style_id = style_id,
+ };
+ rac.row.styled = true;
+ page.styles.use(page.memory, style_id);
+ }
+ // Release the extra ref from add
+ page.styles.release(page.memory, style_id);
+
+ // Split at row 5
+ const split_pin: Pin = .{ .node = s.pages.first.?, .y = 5, .x = 0 };
+ try s.split(split_pin);
+
+ const first_page = &s.pages.first.?.data;
+ const second_page = &s.pages.first.?.next.?.data;
+
+ // First page should have no styles (all styled rows moved to second page)
+ try testing.expectEqual(@as(usize, 0), first_page.styles.count());
+
+ // Second page should have exactly 1 style (the bold style, used by 3 cells)
+ try testing.expectEqual(@as(usize, 1), second_page.styles.count());
+
+ // Verify styled cells are preserved in new page
+ for (0..3) |y| {
+ const rac = second_page.getRowAndCell(0, y);
+ try testing.expectEqual(@as(u21, 'S'), rac.cell.content.codepoint);
+ try testing.expect(rac.cell.style_id != 0);
+
+ const got_style = second_page.styles.get(second_page.memory, rac.cell.style_id);
+ try testing.expect(got_style.flags.bold);
+ try testing.expect(rac.row.styled);
+ }
+}
+
+test "PageList split preserves grapheme clusters" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var s = try init(alloc, 10, 10, 0);
+ defer s.deinit();
+
+ const page = &s.pages.first.?.data;
+
+ // Add a grapheme cluster to row 6 (will be row 1 in second page after split at 5)
+ {
+ const rac = page.getRowAndCell(0, 6);
+ rac.cell.* = .{
+ .content_tag = .codepoint,
+ .content = .{ .codepoint = 0x1F468 }, // Man emoji
+ };
+ try page.setGraphemes(rac.row, rac.cell, &.{
+ 0x200D, // ZWJ
+ 0x1F469, // Woman emoji
+ });
+ }
+
+ // Split at row 5
+ const split_pin: Pin = .{ .node = s.pages.first.?, .y = 5, .x = 0 };
+ try s.split(split_pin);
+
+ const first_page = &s.pages.first.?.data;
+ const second_page = &s.pages.first.?.next.?.data;
+
+ // First page should have no graphemes (the grapheme row moved to second page)
+ try testing.expectEqual(@as(usize, 0), first_page.graphemeCount());
+
+ // Second page should have exactly 1 grapheme
+ try testing.expectEqual(@as(usize, 1), second_page.graphemeCount());
+
+ // Verify grapheme is preserved in new page (original row 6 is now row 1)
+ {
+ const rac = second_page.getRowAndCell(0, 1);
+ try testing.expectEqual(@as(u21, 0x1F468), rac.cell.content.codepoint);
+ try testing.expect(rac.row.grapheme);
+
+ const cps = second_page.lookupGrapheme(rac.cell).?;
+ try testing.expectEqual(@as(usize, 2), cps.len);
+ try testing.expectEqual(@as(u21, 0x200D), cps[0]);
+ try testing.expectEqual(@as(u21, 0x1F469), cps[1]);
+ }
+}
+
+test "PageList split preserves hyperlinks" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var s = try init(alloc, 10, 10, 0);
+ defer s.deinit();
+
+ const page = &s.pages.first.?.data;
+
+ // Add a hyperlink to row 7 (will be row 2 in second page after split at 5)
+ const hyperlink_id = try page.insertHyperlink(.{
+ .id = .{ .implicit = 0 },
+ .uri = "https://example.com",
+ });
+ {
+ const rac = page.getRowAndCell(0, 7);
+ rac.cell.* = .{
+ .content_tag = .codepoint,
+ .content = .{ .codepoint = 'L' },
+ };
+ try page.setHyperlink(rac.row, rac.cell, hyperlink_id);
+ }
+
+ // Split at row 5
+ const split_pin: Pin = .{ .node = s.pages.first.?, .y = 5, .x = 0 };
+ try s.split(split_pin);
+
+ const first_page = &s.pages.first.?.data;
+ const second_page = &s.pages.first.?.next.?.data;
+
+ // First page should have no hyperlinks (the hyperlink row moved to second page)
+ try testing.expectEqual(@as(usize, 0), first_page.hyperlink_set.count());
+
+ // Second page should have exactly 1 hyperlink
+ try testing.expectEqual(@as(usize, 1), second_page.hyperlink_set.count());
+
+ // Verify hyperlink is preserved in new page (original row 7 is now row 2)
+ {
+ const rac = second_page.getRowAndCell(0, 2);
+ try testing.expectEqual(@as(u21, 'L'), rac.cell.content.codepoint);
+ try testing.expect(rac.cell.hyperlink);
+
+ const link_id = second_page.lookupHyperlink(rac.cell).?;
+ const link = second_page.hyperlink_set.get(second_page.memory, link_id);
+ try testing.expectEqualStrings("https://example.com", link.uri.slice(second_page.memory));
+ }
+}
diff --git a/src/terminal/Parser.zig b/src/terminal/Parser.zig
index 980906e49..34a23787f 100644
--- a/src/terminal/Parser.zig
+++ b/src/terminal/Parser.zig
@@ -359,7 +359,7 @@ inline fn doAction(self: *Parser, action: TransitionAction, c: u8) ?Action {
break :param null;
},
.osc_put => osc_put: {
- self.osc_parser.next(c);
+ @call(.always_inline, osc.Parser.next, .{ &self.osc_parser, c });
break :osc_put null;
},
.csi_dispatch => csi_dispatch: {
diff --git a/src/terminal/Screen.zig b/src/terminal/Screen.zig
index ba2af2473..c8db19903 100644
--- a/src/terminal/Screen.zig
+++ b/src/terminal/Screen.zig
@@ -396,18 +396,6 @@ pub fn clone(
alloc: Allocator,
top: point.Point,
bot: ?point.Point,
-) !Screen {
- return try self.clonePool(alloc, null, top, bot);
-}
-
-/// Same as clone but you can specify a custom memory pool to use for
-/// the screen.
-pub fn clonePool(
- self: *const Screen,
- alloc: Allocator,
- pool: ?*PageList.MemoryPool,
- top: point.Point,
- bot: ?point.Point,
) !Screen {
// Create a tracked pin remapper for our selection and cursor. Note
// that we may want to expose this generally in the future but at the
@@ -415,14 +403,9 @@ pub fn clonePool(
var pin_remap = PageList.Clone.TrackedPinsRemap.init(alloc);
defer pin_remap.deinit();
- var pages = try self.pages.clone(.{
+ var pages = try self.pages.clone(alloc, .{
.top = top,
.bot = bot,
- .memory = if (pool) |p| .{
- .pool = p,
- } else .{
- .alloc = alloc,
- },
.tracked_pins = &pin_remap,
});
errdefer pages.deinit();
@@ -534,39 +517,38 @@ pub fn clonePool(
result.assertIntegrity();
return result;
}
-
-/// Adjust the capacity of a page within the pagelist of this screen.
-/// This handles some accounting if the page being modified is the
-/// cursor page.
-pub fn adjustCapacity(
+pub fn increaseCapacity(
self: *Screen,
node: *PageList.List.Node,
- adjustment: PageList.AdjustCapacity,
-) PageList.AdjustCapacityError!*PageList.List.Node {
+ adjustment: ?PageList.IncreaseCapacity,
+) PageList.IncreaseCapacityError!*PageList.List.Node {
// If the page being modified isn't our cursor page then
// this is a quick operation because we have no additional
- // accounting.
- if (node != self.cursor.page_pin.node) {
- return try self.pages.adjustCapacity(node, adjustment);
- }
+ // accounting. We have to do this check here BEFORE calling
+ // increaseCapacity because increaseCapacity will update all
+ // our tracked pins (including our cursor).
+ if (node != self.cursor.page_pin.node) return try self.pages.increaseCapacity(
+ node,
+ adjustment,
+ );
- // We're modifying the cursor page. When we adjust the
+ // We're modifying the cursor page. When we increase the
// capacity below it will be short the ref count on our
// current style and hyperlink, so we need to init those.
- const new_node = try self.pages.adjustCapacity(node, adjustment);
+ const new_node = try self.pages.increaseCapacity(node, adjustment);
const new_page: *Page = &new_node.data;
// Re-add the style, if the page somehow doesn't have enough
// memory to add it, we emit a warning and gracefully degrade
// to the default style for the cursor.
- if (self.cursor.style_id != 0) {
+ if (self.cursor.style_id != style.default_id) {
self.cursor.style_id = new_page.styles.add(
new_page.memory,
self.cursor.style,
) catch |err| id: {
// TODO: Should we increase the capacity further in this case?
log.warn(
- "(Screen.adjustCapacity) Failed to add cursor style back to page, err={}",
+ "(Screen.increaseCapacity) Failed to add cursor style back to page, err={}",
.{err},
);
@@ -588,7 +570,7 @@ pub fn adjustCapacity(
self.startHyperlinkOnce(link.*) catch |err| {
// TODO: Should we increase the capacity further in this case?
log.warn(
- "(Screen.adjustCapacity) Failed to add cursor hyperlink back to page, err={}",
+ "(Screen.increaseCapacity) Failed to add cursor hyperlink back to page, err={}",
.{err},
);
};
@@ -657,9 +639,8 @@ pub fn cursorUp(self: *Screen, n: size.CellCountInt) void {
defer self.assertIntegrity();
self.cursor.y -= n; // Must be set before cursorChangePin
- const page_pin = self.cursor.page_pin.up(n).?;
- self.cursorChangePin(page_pin);
- const page_rac = page_pin.rowAndCell();
+ self.cursorChangePin(self.cursor.page_pin.up(n).?);
+ const page_rac = self.cursor.page_pin.rowAndCell();
self.cursor.page_row = page_rac.row;
self.cursor.page_cell = page_rac.cell;
}
@@ -684,9 +665,8 @@ pub fn cursorDown(self: *Screen, n: size.CellCountInt) void {
// We move the offset into our page list to the next row and then
// get the pointers to the row/cell and set all the cursor state up.
- const page_pin = self.cursor.page_pin.down(n).?;
- self.cursorChangePin(page_pin);
- const page_rac = page_pin.rowAndCell();
+ self.cursorChangePin(self.cursor.page_pin.down(n).?);
+ const page_rac = self.cursor.page_pin.rowAndCell();
self.cursor.page_row = page_rac.row;
self.cursor.page_cell = page_rac.cell;
}
@@ -817,31 +797,37 @@ pub fn cursorDownScroll(self: *Screen) !void {
// allocate, prune scrollback, whatever.
_ = try self.pages.grow();
- // If our pin page change it means that the page that the pin
- // was on was pruned. In this case, grow() moves the pin to
- // the top-left of the new page. This effectively moves it by
- // one already, we just need to fix up the x value.
- const page_pin = if (old_pin.node == self.cursor.page_pin.node)
- self.cursor.page_pin.down(1).?
- else reuse: {
- var pin = self.cursor.page_pin.*;
- pin.x = self.cursor.x;
- break :reuse pin;
- };
+ self.cursorChangePin(new_pin: {
+ // We do this all in a block here because referencing this pin
+ // after cursorChangePin is unsafe, and we want to keep it out
+ // of scope.
- // These assertions help catch some pagelist math errors. Our
- // x/y should be unchanged after the grow.
- if (build_options.slow_runtime_safety) {
- const active = self.pages.pointFromPin(
- .active,
- page_pin,
- ).?.active;
- assert(active.x == self.cursor.x);
- assert(active.y == self.cursor.y);
- }
+ // If our pin page change it means that the page that the pin
+ // was on was pruned. In this case, grow() moves the pin to
+ // the top-left of the new page. This effectively moves it by
+ // one already, we just need to fix up the x value.
+ const page_pin = if (old_pin.node == self.cursor.page_pin.node)
+ self.cursor.page_pin.down(1).?
+ else reuse: {
+ var pin = self.cursor.page_pin.*;
+ pin.x = self.cursor.x;
+ break :reuse pin;
+ };
- self.cursorChangePin(page_pin);
- const page_rac = page_pin.rowAndCell();
+ // These assertions help catch some pagelist math errors. Our
+ // x/y should be unchanged after the grow.
+ if (build_options.slow_runtime_safety) {
+ const active = self.pages.pointFromPin(
+ .active,
+ page_pin,
+ ).?.active;
+ assert(active.x == self.cursor.x);
+ assert(active.y == self.cursor.y);
+ }
+
+ break :new_pin page_pin;
+ });
+ const page_rac = self.cursor.page_pin.rowAndCell();
self.cursor.page_row = page_rac.row;
self.cursor.page_cell = page_rac.cell;
@@ -851,7 +837,7 @@ pub fn cursorDownScroll(self: *Screen) !void {
// Clear the new row so it gets our bg color. We only do this
// if we have a bg color at all.
if (self.cursor.style.bg_color != .none) {
- const page: *Page = &page_pin.node.data;
+ const page: *Page = &self.cursor.page_pin.node.data;
self.clearCells(
page,
self.cursor.page_row,
@@ -1098,6 +1084,11 @@ pub fn cursorCopy(self: *Screen, other: Cursor, opts: struct {
/// page than the old AND we have a style or hyperlink set. In that case,
/// we must release our old one and insert the new one, since styles are
/// stored per-page.
+///
+/// Note that this can change the cursor pin AGAIN if the process of
+/// setting up our cursor forces a capacity adjustment of the underlying
+/// cursor page, so any references to the page pin should be re-read
+/// from `self.cursor.page_pin` after calling this.
inline fn cursorChangePin(self: *Screen, new: Pin) void {
// Moving the cursor affects text run splitting (ligatures) so
// we must mark the old and new page dirty. We do this as long
@@ -1114,14 +1105,19 @@ inline fn cursorChangePin(self: *Screen, new: Pin) void {
return;
}
- // If we have a old style then we need to release it from the old page.
+ // If we have an old style then we need to release it from the old page.
const old_style_: ?style.Style = if (self.cursor.style_id == style.default_id)
null
else
self.cursor.style;
if (old_style_ != null) {
+ // Release the style directly from the old page instead of going through
+ // manualStyleUpdate, because the cursor position may have already been
+ // updated but the pin has not, which would fail integrity checks.
+ const old_page: *Page = &self.cursor.page_pin.node.data;
+ old_page.styles.release(old_page.memory, self.cursor.style_id);
self.cursor.style = .{};
- self.manualStyleUpdate() catch unreachable; // Removing a style should never fail
+ self.cursor.style_id = style.default_id;
}
// If we have a hyperlink then we need to release it from the old page.
@@ -1793,9 +1789,25 @@ fn resizeInternal(
/// Set a style attribute for the current cursor.
///
-/// This can cause a page split if the current page cannot fit this style.
-/// This is the only scenario an error return is possible.
-pub fn setAttribute(self: *Screen, attr: sgr.Attribute) !void {
+/// If the style can't be set due to any internal errors (memory-related),
+/// then this will revert back to the existing style and return an error.
+pub fn setAttribute(
+ self: *Screen,
+ attr: sgr.Attribute,
+) PageList.IncreaseCapacityError!void {
+ // If we fail to set our style for any reason, we should revert
+ // back to the old style. If we fail to do that, we revert back to
+ // the default style.
+ const old_style = self.cursor.style;
+ errdefer {
+ self.cursor.style = old_style;
+ self.manualStyleUpdate() catch |err| {
+ log.warn("setAttribute error restoring old style after failure err={}", .{err});
+ self.cursor.style = .{};
+ self.manualStyleUpdate() catch unreachable;
+ };
+ }
+
switch (attr) {
.unset => {
self.cursor.style = .{};
@@ -1938,7 +1950,21 @@ pub fn setAttribute(self: *Screen, attr: sgr.Attribute) !void {
}
/// Call this whenever you manually change the cursor style.
-pub fn manualStyleUpdate(self: *Screen) !void {
+///
+/// This function can NOT fail if the cursor style is changing to the
+/// default style.
+///
+/// If this returns an error, the style change did not take effect and
+/// the cursor style is reverted back to the default. The only scenario
+/// this returns an error is if there is a physical memory allocation failure
+/// or if there is no possible way to increase style capacity to store
+/// the style.
+///
+/// This function WILL split pages as necessary to accommodate the new style.
+/// So if OutOfSpace is returned, it means that even after splitting the page
+/// there was still no room for the new style.
+pub fn manualStyleUpdate(self: *Screen) PageList.IncreaseCapacityError!void {
+ defer self.assertIntegrity();
var page: *Page = &self.cursor.page_pin.node.data;
// std.log.warn("active styles={}", .{page.styles.count()});
@@ -1957,6 +1983,9 @@ pub fn manualStyleUpdate(self: *Screen) !void {
// Clear the cursor style ID to prevent weird things from happening
// if the page capacity has to be adjusted which would end up calling
// manualStyleUpdate again.
+ //
+ // This also ensures that if anything fails below, we fall back to
+ // clearing our style.
self.cursor.style_id = style.default_id;
// After setting the style, we need to update our style map.
@@ -1968,30 +1997,115 @@ pub fn manualStyleUpdate(self: *Screen) !void {
page.memory,
self.cursor.style,
) catch |err| id: {
- // Our style map is full or needs to be rehashed,
- // so we allocate a new page, which will rehash,
- // and double the style capacity for it if it was
- // full.
- const node = try self.adjustCapacity(
+ // Our style map is full or needs to be rehashed, so we need to
+ // increase style capacity (or rehash).
+ const node = self.increaseCapacity(
self.cursor.page_pin.node,
switch (err) {
- error.OutOfMemory => .{ .styles = page.capacity.styles * 2 },
- error.NeedsRehash => .{},
+ error.OutOfMemory => .styles,
+ error.NeedsRehash => null,
},
- );
+ ) catch |increase_err| switch (increase_err) {
+ error.OutOfMemory => return error.OutOfMemory,
+ error.OutOfSpace => space: {
+ // Out of space, we need to split the page. Split wherever
+ // is using less capacity and hope that works. If it doesn't
+ // work, we tried.
+ try self.splitForCapacity(self.cursor.page_pin.*);
+ break :space self.cursor.page_pin.node;
+ },
+ };
page = &node.data;
- break :id try page.styles.add(
+ break :id page.styles.add(
page.memory,
self.cursor.style,
- );
+ ) catch |err2| switch (err2) {
+ error.OutOfMemory => {
+ // This shouldn't happen because increaseCapacity is
+ // guaranteed to increase our capacity by at least one and
+ // we only need one space, but again, I don't want to crash
+ // here so let's log loudly and reset.
+ log.err("style addition failed after capacity increase", .{});
+ return error.OutOfMemory;
+ },
+ error.NeedsRehash => {
+ // This should be impossible because we rehash above
+ // and rehashing should never result in a duplicate. But
+ // we don't want to simply hard crash so log it and
+ // clear our style.
+ log.err("style rehash resulted in needs rehash", .{});
+ return;
+ },
+ };
};
+ errdefer page.styles.release(page.memory, id);
+
self.cursor.style_id = id;
- self.assertIntegrity();
+}
+
+/// Split at the given pin so that the pinned row moves to the page
+/// with less used capacity after the split.
+///
+/// The primary use case for this is to handle IncreaseCapacityError
+/// OutOfSpace conditions where we need to split the page in order
+/// to make room for more managed memory.
+///
+/// If the caller cares about where the pin moves to, they should
+/// setup a tracked pin before calling this and then check that.
+/// In many calling cases, the input pin is tracked (e.g. the cursor
+/// pin).
+///
+/// If this returns OOM then its a system OOM. If this returns OutOfSpace
+/// then it means the page can't be split further.
+fn splitForCapacity(
+ self: *Screen,
+ pin: Pin,
+) PageList.SplitError!void {
+ // Get our capacities. We include our target row because its
+ // capacity will be preserved.
+ const bytes_above = Page.layout(pin.node.data.exactRowCapacity(
+ 0,
+ pin.y + 1,
+ )).total_size;
+ const bytes_below = Page.layout(pin.node.data.exactRowCapacity(
+ pin.y,
+ pin.node.data.size.rows,
+ )).total_size;
+
+ // We need to track the old cursor pin because if our split
+ // moves the cursor pin we need to update our accounting.
+ const old_cursor = self.cursor.page_pin.*;
+
+ // If our bytes above are less than bytes below, we move the pin
+ // to split down one since splitting includes the pinned row in
+ // the new node.
+ try self.pages.split(if (bytes_above < bytes_below)
+ pin.down(1) orelse pin
+ else
+ pin);
+
+ // Cursor didn't change nodes, we're done.
+ if (self.cursor.page_pin.node == old_cursor.node) return;
+
+ // Cursor changed, we need to restore the old pin then use
+ // cursorChangePin to move to the new pin. The old node is guaranteed
+ // to still exist, just not the row.
+ //
+ // Note that page_row and all that will be invalid, it points to the
+ // new node, but at the time of writing this we don't need any of that
+ // to be right in cursorChangePin.
+ const new_cursor = self.cursor.page_pin.*;
+ self.cursor.page_pin.* = old_cursor;
+ self.cursorChangePin(new_cursor);
}
/// Append a grapheme to the given cell within the current cursor row.
-pub fn appendGrapheme(self: *Screen, cell: *Cell, cp: u21) !void {
+pub fn appendGrapheme(
+ self: *Screen,
+ cell: *Cell,
+ cp: u21,
+) PageList.IncreaseCapacityError!void {
defer self.cursor.page_pin.node.data.assertIntegrity();
self.cursor.page_pin.node.data.appendGrapheme(
self.cursor.page_row,
@@ -2011,11 +2125,9 @@ pub fn appendGrapheme(self: *Screen, cell: *Cell, cp: u21) !void {
// Adjust our capacity. This will update our cursor page pin and
// force us to reload.
- const original_node = self.cursor.page_pin.node;
- const new_bytes = original_node.data.capacity.grapheme_bytes * 2;
- _ = try self.adjustCapacity(
- original_node,
- .{ .grapheme_bytes = new_bytes },
+ _ = try self.increaseCapacity(
+ self.cursor.page_pin.node,
+ .grapheme_bytes,
);
// The cell pointer is now invalid, so we need to get it from
@@ -2026,17 +2138,22 @@ pub fn appendGrapheme(self: *Screen, cell: *Cell, cp: u21) !void {
.gt => self.cursorCellRight(@intCast(cell_idx - self.cursor.x)),
};
- try self.cursor.page_pin.node.data.appendGrapheme(
+ self.cursor.page_pin.node.data.appendGrapheme(
self.cursor.page_row,
reloaded_cell,
cp,
- );
+ ) catch |err2| {
+ comptime assert(@TypeOf(err2) == error{OutOfMemory});
+ // This should never happen because we just increased capacity.
+ // Log loudly but still return an error so we don't just
+ // crash.
+ log.err("grapheme append failed after capacity increase", .{});
+ return err2;
+ };
},
};
}
-pub const StartHyperlinkError = Allocator.Error || PageList.AdjustCapacityError;
-
/// Start the hyperlink state. Future cells will be marked as hyperlinks with
/// this state. Note that various terminal operations may clear the hyperlink
/// state, such as switching screens (alt screen).
@@ -2044,7 +2161,7 @@ pub fn startHyperlink(
self: *Screen,
uri: []const u8,
id_: ?[]const u8,
-) StartHyperlinkError!void {
+) PageList.IncreaseCapacityError!void {
// Create our pending entry.
const link: hyperlink.Hyperlink = .{
.uri = uri,
@@ -2069,21 +2186,21 @@ pub fn startHyperlink(
error.OutOfMemory => return error.OutOfMemory,
// strings table is out of memory, adjust it up
- error.StringsOutOfMemory => _ = try self.adjustCapacity(
+ error.StringsOutOfMemory => _ = try self.increaseCapacity(
self.cursor.page_pin.node,
- .{ .string_bytes = self.cursor.page_pin.node.data.capacity.string_bytes * 2 },
+ .string_bytes,
),
// hyperlink set is out of memory, adjust it up
- error.SetOutOfMemory => _ = try self.adjustCapacity(
+ error.SetOutOfMemory => _ = try self.increaseCapacity(
self.cursor.page_pin.node,
- .{ .hyperlink_bytes = self.cursor.page_pin.node.data.capacity.hyperlink_bytes * 2 },
+ .hyperlink_bytes,
),
// hyperlink set is too full, rehash it
- error.SetNeedsRehash => _ = try self.adjustCapacity(
+ error.SetNeedsRehash => _ = try self.increaseCapacity(
self.cursor.page_pin.node,
- .{},
+ null,
),
}
@@ -2145,7 +2262,7 @@ pub fn endHyperlink(self: *Screen) void {
}
/// Set the current hyperlink state on the current cell.
-pub fn cursorSetHyperlink(self: *Screen) !void {
+pub fn cursorSetHyperlink(self: *Screen) PageList.IncreaseCapacityError!void {
assert(self.cursor.hyperlink_id != 0);
var page = &self.cursor.page_pin.node.data;
@@ -2160,40 +2277,38 @@ pub fn cursorSetHyperlink(self: *Screen) !void {
} else |err| switch (err) {
// hyperlink_map is out of space, realloc the page to be larger
error.HyperlinkMapOutOfMemory => {
- const uri_size = if (self.cursor.hyperlink) |link| link.uri.len else 0;
-
- var string_bytes = page.capacity.string_bytes;
-
// Attempt to allocate the space that would be required to
// insert a new copy of the cursor hyperlink uri in to the
- // string alloc, since right now adjustCapacity always just
+ // string alloc, since right now increaseCapacity always just
// adds an extra copy even if one already exists in the page.
// If this alloc fails then we know we also need to grow our
// string bytes.
//
- // FIXME: This SUCKS
- if (page.string_alloc.alloc(
- u8,
- page.memory,
- uri_size,
- )) |slice| {
- // We don't bother freeing because we're
- // about to free the entire page anyway.
- _ = &slice;
- } else |_| {
- // We didn't have enough room, let's just double our
- // string bytes until there's definitely enough room
- // for our uri.
- const before = string_bytes;
- while (string_bytes - before < uri_size) string_bytes *= 2;
+ // FIXME: increaseCapacity should not do this.
+ while (self.cursor.hyperlink) |link| {
+ if (page.string_alloc.alloc(
+ u8,
+ page.memory,
+ link.uri.len,
+ )) |slice| {
+ // We don't bother freeing because we're
+ // about to free the entire page anyway.
+ _ = slice;
+ break;
+ } else |_| {}
+
+ // We didn't have enough room, let's increase string bytes
+ const new_node = try self.increaseCapacity(
+ self.cursor.page_pin.node,
+ .string_bytes,
+ );
+ assert(new_node == self.cursor.page_pin.node);
+ page = &new_node.data;
}
- _ = try self.adjustCapacity(
+ _ = try self.increaseCapacity(
self.cursor.page_pin.node,
- .{
- .hyperlink_bytes = page.capacity.hyperlink_bytes * 2,
- .string_bytes = string_bytes,
- },
+ .hyperlink_bytes,
);
// Retry
@@ -2502,11 +2617,15 @@ pub fn selectAll(self: *Screen) ?Selection {
/// end_pt (inclusive). Because it selects "nearest" to start point, start
/// point can be before or after end point.
///
+/// The boundary_codepoints parameter should be a slice of u21 codepoints that
+/// mark word boundaries, passed through to selectWord.
+///
/// TODO: test this
pub fn selectWordBetween(
self: *Screen,
start: Pin,
end: Pin,
+ boundary_codepoints: []const u21,
) ?Selection {
const dir: PageList.Direction = if (start.before(end)) .right_down else .left_up;
var it = start.cellIterator(dir, end);
@@ -2518,7 +2637,7 @@ pub fn selectWordBetween(
}
// If we found a word, then return it
- if (self.selectWord(pin)) |sel| return sel;
+ if (self.selectWord(pin, boundary_codepoints)) |sel| return sel;
}
return null;
@@ -2530,33 +2649,16 @@ pub fn selectWordBetween(
///
/// This will return null if a selection is impossible. The only scenario
/// this happens is if the point pt is outside of the written screen space.
-pub fn selectWord(self: *Screen, pin: Pin) ?Selection {
+///
+/// The boundary_codepoints parameter should be a slice of u21 codepoints that
+/// mark word boundaries. This is expected to be pre-parsed from the config.
+pub fn selectWord(
+ self: *Screen,
+ pin: Pin,
+ boundary_codepoints: []const u21,
+) ?Selection {
_ = self;
- // Boundary characters for selection purposes
- const boundary = &[_]u32{
- 0,
- ' ',
- '\t',
- '\'',
- '"',
- 'โ',
- '`',
- '|',
- ':',
- ';',
- ',',
- '(',
- ')',
- '[',
- ']',
- '{',
- '}',
- '<',
- '>',
- '$',
- };
-
// If our cell is empty we can't select a word, because we can't select
// areas where the screen is not yet written.
const start_cell = pin.rowAndCell().cell;
@@ -2564,9 +2666,9 @@ pub fn selectWord(self: *Screen, pin: Pin) ?Selection {
// Determine if we are a boundary or not to determine what our boundary is.
const expect_boundary = std.mem.indexOfAny(
- u32,
- boundary,
- &[_]u32{start_cell.content.codepoint},
+ u21,
+ boundary_codepoints,
+ &[_]u21{start_cell.content.codepoint},
) != null;
// Go forwards to find our end boundary
@@ -2582,9 +2684,9 @@ pub fn selectWord(self: *Screen, pin: Pin) ?Selection {
// If we do not match our expected set, we hit a boundary
const this_boundary = std.mem.indexOfAny(
- u32,
- boundary,
- &[_]u32{cell.content.codepoint},
+ u21,
+ boundary_codepoints,
+ &[_]u21{cell.content.codepoint},
) != null;
if (this_boundary != expect_boundary) break :end prev;
@@ -2619,9 +2721,9 @@ pub fn selectWord(self: *Screen, pin: Pin) ?Selection {
// If we do not match our expected set, we hit a boundary
const this_boundary = std.mem.indexOfAny(
- u32,
- boundary,
- &[_]u32{cell.content.codepoint},
+ u21,
+ boundary_codepoints,
+ &[_]u21{cell.content.codepoint},
) != null;
if (this_boundary != expect_boundary) break :start prev;
@@ -3015,15 +3117,15 @@ pub fn testWriteString(self: *Screen, text: []const u8) !void {
.protected = self.cursor.protected,
};
- // If we have a hyperlink, add it to the cell.
- if (self.cursor.hyperlink_id > 0) try self.cursorSetHyperlink();
-
// If we have a ref-counted style, increase.
if (self.cursor.style_id != style.default_id) {
const page = self.cursor.page_pin.node.data;
page.styles.use(page.memory, self.cursor.style_id);
self.cursor.page_row.styled = true;
}
+
+ // If we have a hyperlink, add it to the cell.
+ if (self.cursor.hyperlink_id > 0) try self.cursorSetHyperlink();
},
2 => {
@@ -7584,6 +7686,12 @@ test "Screen: selectWord" {
defer s.deinit();
try s.testWriteString("ABC DEF\n 123\n456");
+ // Default boundary codepoints for word selection
+ const boundary_codepoints = &[_]u21{
+ 0, ' ', '\t', '\'', '"', 'โ', '`', '|', ':', ';',
+ ',', '(', ')', '[', ']', '{', '}', '<', '>', '$',
+ };
+
// Outside of active area
// try testing.expect(s.selectWord(.{ .x = 9, .y = 0 }) == null);
// try testing.expect(s.selectWord(.{ .x = 0, .y = 5 }) == null);
@@ -7593,7 +7701,7 @@ test "Screen: selectWord" {
var sel = s.selectWord(s.pages.pin(.{ .active = .{
.x = 0,
.y = 0,
- } }).?).?;
+ } }).?, boundary_codepoints).?;
defer sel.deinit(&s);
try testing.expectEqual(point.Point{ .screen = .{
.x = 0,
@@ -7610,7 +7718,7 @@ test "Screen: selectWord" {
var sel = s.selectWord(s.pages.pin(.{ .active = .{
.x = 2,
.y = 0,
- } }).?).?;
+ } }).?, boundary_codepoints).?;
defer sel.deinit(&s);
try testing.expectEqual(point.Point{ .screen = .{
.x = 0,
@@ -7627,7 +7735,7 @@ test "Screen: selectWord" {
var sel = s.selectWord(s.pages.pin(.{ .active = .{
.x = 1,
.y = 0,
- } }).?).?;
+ } }).?, boundary_codepoints).?;
defer sel.deinit(&s);
try testing.expectEqual(point.Point{ .screen = .{
.x = 0,
@@ -7644,7 +7752,7 @@ test "Screen: selectWord" {
var sel = s.selectWord(s.pages.pin(.{ .active = .{
.x = 3,
.y = 0,
- } }).?).?;
+ } }).?, boundary_codepoints).?;
defer sel.deinit(&s);
try testing.expectEqual(point.Point{ .screen = .{
.x = 3,
@@ -7661,7 +7769,7 @@ test "Screen: selectWord" {
var sel = s.selectWord(s.pages.pin(.{ .active = .{
.x = 0,
.y = 1,
- } }).?).?;
+ } }).?, boundary_codepoints).?;
defer sel.deinit(&s);
try testing.expectEqual(point.Point{ .screen = .{
.x = 0,
@@ -7678,7 +7786,7 @@ test "Screen: selectWord" {
var sel = s.selectWord(s.pages.pin(.{ .active = .{
.x = 1,
.y = 2,
- } }).?).?;
+ } }).?, boundary_codepoints).?;
defer sel.deinit(&s);
try testing.expectEqual(point.Point{ .screen = .{
.x = 0,
@@ -7699,6 +7807,12 @@ test "Screen: selectWord across soft-wrap" {
defer s.deinit();
try s.testWriteString(" 1234012\n 123");
+ // Default boundary codepoints for word selection
+ const boundary_codepoints = &[_]u21{
+ 0, ' ', '\t', '\'', '"', 'โ', '`', '|', ':', ';',
+ ',', '(', ')', '[', ']', '{', '}', '<', '>', '$',
+ };
+
{
const contents = try s.dumpStringAlloc(alloc, .{ .screen = .{} });
defer alloc.free(contents);
@@ -7710,7 +7824,7 @@ test "Screen: selectWord across soft-wrap" {
var sel = s.selectWord(s.pages.pin(.{ .active = .{
.x = 1,
.y = 0,
- } }).?).?;
+ } }).?, boundary_codepoints).?;
defer sel.deinit(&s);
try testing.expectEqual(point.Point{ .screen = .{
.x = 1,
@@ -7727,7 +7841,7 @@ test "Screen: selectWord across soft-wrap" {
var sel = s.selectWord(s.pages.pin(.{ .active = .{
.x = 1,
.y = 1,
- } }).?).?;
+ } }).?, boundary_codepoints).?;
defer sel.deinit(&s);
try testing.expectEqual(point.Point{ .screen = .{
.x = 1,
@@ -7744,7 +7858,7 @@ test "Screen: selectWord across soft-wrap" {
var sel = s.selectWord(s.pages.pin(.{ .active = .{
.x = 3,
.y = 0,
- } }).?).?;
+ } }).?, boundary_codepoints).?;
defer sel.deinit(&s);
try testing.expectEqual(point.Point{ .screen = .{
.x = 1,
@@ -7765,12 +7879,18 @@ test "Screen: selectWord whitespace across soft-wrap" {
defer s.deinit();
try s.testWriteString("1 1\n 123");
+ // Default boundary codepoints for word selection
+ const boundary_codepoints = &[_]u21{
+ 0, ' ', '\t', '\'', '"', 'โ', '`', '|', ':', ';',
+ ',', '(', ')', '[', ']', '{', '}', '<', '>', '$',
+ };
+
// Going forward
{
var sel = s.selectWord(s.pages.pin(.{ .active = .{
.x = 1,
.y = 0,
- } }).?).?;
+ } }).?, boundary_codepoints).?;
defer sel.deinit(&s);
try testing.expectEqual(point.Point{ .screen = .{
.x = 1,
@@ -7787,7 +7907,7 @@ test "Screen: selectWord whitespace across soft-wrap" {
var sel = s.selectWord(s.pages.pin(.{ .active = .{
.x = 1,
.y = 1,
- } }).?).?;
+ } }).?, boundary_codepoints).?;
defer sel.deinit(&s);
try testing.expectEqual(point.Point{ .screen = .{
.x = 1,
@@ -7804,7 +7924,7 @@ test "Screen: selectWord whitespace across soft-wrap" {
var sel = s.selectWord(s.pages.pin(.{ .active = .{
.x = 3,
.y = 0,
- } }).?).?;
+ } }).?, boundary_codepoints).?;
defer sel.deinit(&s);
try testing.expectEqual(point.Point{ .screen = .{
.x = 1,
@@ -7821,6 +7941,12 @@ test "Screen: selectWord with character boundary" {
const testing = std.testing;
const alloc = testing.allocator;
+ // Default boundary codepoints for word selection
+ const boundary_codepoints = &[_]u21{
+ 0, ' ', '\t', '\'', '"', 'โ', '`', '|', ':', ';',
+ ',', '(', ')', '[', ']', '{', '}', '<', '>', '$',
+ };
+
const cases = [_][]const u8{
" 'abc' \n123",
" \"abc\" \n123",
@@ -7851,7 +7977,7 @@ test "Screen: selectWord with character boundary" {
var sel = s.selectWord(s.pages.pin(.{ .active = .{
.x = 2,
.y = 0,
- } }).?).?;
+ } }).?, boundary_codepoints).?;
defer sel.deinit(&s);
try testing.expectEqual(point.Point{ .screen = .{
.x = 2,
@@ -7868,7 +7994,7 @@ test "Screen: selectWord with character boundary" {
var sel = s.selectWord(s.pages.pin(.{ .active = .{
.x = 4,
.y = 0,
- } }).?).?;
+ } }).?, boundary_codepoints).?;
defer sel.deinit(&s);
try testing.expectEqual(point.Point{ .screen = .{
.x = 2,
@@ -7885,7 +8011,7 @@ test "Screen: selectWord with character boundary" {
var sel = s.selectWord(s.pages.pin(.{ .active = .{
.x = 3,
.y = 0,
- } }).?).?;
+ } }).?, boundary_codepoints).?;
defer sel.deinit(&s);
try testing.expectEqual(point.Point{ .screen = .{
.x = 2,
@@ -7904,7 +8030,7 @@ test "Screen: selectWord with character boundary" {
var sel = s.selectWord(s.pages.pin(.{ .active = .{
.x = 1,
.y = 0,
- } }).?).?;
+ } }).?, boundary_codepoints).?;
defer sel.deinit(&s);
try testing.expectEqual(point.Point{ .screen = .{
.x = 0,
@@ -8904,132 +9030,437 @@ test "Screen: cursorSetHyperlink OOM + URI too large for string alloc" {
try testing.expect(base_string_bytes < s.cursor.page_pin.node.data.capacity.string_bytes);
}
-test "Screen: adjustCapacity cursor style ref count" {
+test "Screen: increaseCapacity cursor style ref count preserved" {
const testing = std.testing;
const alloc = testing.allocator;
- var s = try init(alloc, .{ .cols = 5, .rows = 5, .max_scrollback = 0 });
+ var s = try init(alloc, .{
+ .cols = 5,
+ .rows = 5,
+ .max_scrollback = 0,
+ });
defer s.deinit();
-
- try s.setAttribute(.{ .bold = {} });
+ try s.setAttribute(.bold);
try s.testWriteString("1ABCD");
+ // We should have one page and it should be our cursor page
+ try testing.expect(s.pages.pages.first == s.pages.pages.last);
+ try testing.expect(s.pages.pages.first == s.cursor.page_pin.node);
+
+ const old_style = s.cursor.style;
+
{
const page = &s.pages.pages.last.?.data;
+ // 5 chars + cursor = 6 refs
try testing.expectEqual(
- 6, // All chars + cursor
+ 6,
page.styles.refCount(page.memory, s.cursor.style_id),
);
}
- // This forces the page to change.
- _ = try s.adjustCapacity(
+ // This forces the page to change via increaseCapacity.
+ const new_node = try s.increaseCapacity(
s.cursor.page_pin.node,
- .{ .grapheme_bytes = s.cursor.page_pin.node.data.capacity.grapheme_bytes * 2 },
+ .grapheme_bytes,
);
- // Our ref counts should still be the same
+ // Cursor's page_pin should now point to the new node
+ try testing.expect(s.cursor.page_pin.node == new_node);
+
+ // Verify cursor's page_cell and page_row are correctly reloaded from the pin
+ const page_rac = s.cursor.page_pin.rowAndCell();
+ try testing.expect(s.cursor.page_row == page_rac.row);
+ try testing.expect(s.cursor.page_cell == page_rac.cell);
+
+ // Style should be preserved
+ try testing.expectEqual(old_style, s.cursor.style);
+ try testing.expect(s.cursor.style_id != style.default_id);
+
+ // After increaseCapacity, the 5 chars are cloned (5 refs) and
+ // the cursor's style is re-added (1 ref) = 6 total.
{
const page = &s.pages.pages.last.?.data;
- try testing.expectEqual(
- 6, // All chars + cursor
- page.styles.refCount(page.memory, s.cursor.style_id),
- );
+ const ref_count = page.styles.refCount(page.memory, s.cursor.style_id);
+ try testing.expectEqual(6, ref_count);
}
}
-test "Screen: adjustCapacity cursor hyperlink exceeds string alloc size" {
+test "Screen: increaseCapacity cursor hyperlink ref count preserved" {
const testing = std.testing;
const alloc = testing.allocator;
- var s = try init(alloc, .{ .cols = 80, .rows = 24, .max_scrollback = 0 });
+ var s = try init(alloc, .{
+ .cols = 5,
+ .rows = 5,
+ .max_scrollback = 0,
+ });
defer s.deinit();
+ try s.startHyperlink("https://example.com/", null);
+ try s.testWriteString("1ABCD");
- // Start a hyperlink with a URI that just barely fits in the string alloc.
- // This will ensure that the redundant copy added in `adjustCapacity` won't
- // fit in the available string alloc space.
- const uri = "a" ** (pagepkg.std_capacity.string_bytes - 8);
- try s.startHyperlink(uri, null);
+ // We should have one page and it should be our cursor page
+ try testing.expect(s.pages.pages.first == s.pages.pages.last);
+ try testing.expect(s.pages.pages.first == s.cursor.page_pin.node);
- // Write some characters with this so that the URI
- // is copied to the new page when adjusting capacity.
- try s.testWriteString("Hello");
+ {
+ const page = &s.pages.pages.last.?.data;
+ // Cursor has the hyperlink active = 1 count in hyperlink_set
+ try testing.expectEqual(1, page.hyperlink_set.count());
+ try testing.expect(s.cursor.hyperlink_id != 0);
+ try testing.expect(s.cursor.hyperlink != null);
+ }
- // Adjust the capacity, right now this will cause a redundant copy of
- // the URI to be added to the string alloc, but since there isn't room
- // for this this will clear the cursor hyperlink.
- _ = try s.adjustCapacity(s.cursor.page_pin.node, .{});
+ // This forces the page to change via increaseCapacity.
+ _ = try s.increaseCapacity(
+ s.cursor.page_pin.node,
+ .grapheme_bytes,
+ );
- // The cursor hyperlink should have been cleared by the `adjustCapacity`
- // call, because there isn't enough room to add the redundant URI string.
- //
- // This behavior will change, causing this test to fail, if any of these
- // changes are made:
- //
- // - The string alloc is changed to intern strings.
- //
- // - The adjustCapacity function is changed to ensure the new
- // capacity will fit the redundant copy of the hyperlink uri.
- //
- // - The cursor managed memory handling is reworked so that it
- // doesn't reside in the pages anymore and doesn't need this
- // accounting.
- //
- // In such a case, adjust this test accordingly.
- try testing.expectEqual(null, s.cursor.hyperlink);
- try testing.expectEqual(0, s.cursor.hyperlink_id);
+ // Hyperlink should be preserved with correct URI
+ try testing.expect(s.cursor.hyperlink != null);
+ try testing.expect(s.cursor.hyperlink_id != 0);
+ try testing.expectEqualStrings("https://example.com/", s.cursor.hyperlink.?.uri);
+
+ // After increaseCapacity, the hyperlink is re-added to the new page.
+ {
+ const page = &s.pages.pages.last.?.data;
+ try testing.expectEqual(1, page.hyperlink_set.count());
+ }
}
-test "Screen: adjustCapacity cursor style exceeds style set capacity" {
+test "Screen: increaseCapacity cursor with both style and hyperlink preserved" {
const testing = std.testing;
const alloc = testing.allocator;
- var s = try init(alloc, .{ .cols = 80, .rows = 24, .max_scrollback = 1000 });
+ var s = try init(alloc, .{
+ .cols = 5,
+ .rows = 5,
+ .max_scrollback = 0,
+ });
defer s.deinit();
+ // Set both a non-default style AND an active hyperlink.
+ // Write one character first with bold to mark the row as styled,
+ // then start the hyperlink and write more characters.
+ try s.setAttribute(.bold);
+ try s.startHyperlink("https://example.com/", null);
+ try s.testWriteString("1ABCD");
+
+ // We should have one page and it should be our cursor page
+ try testing.expect(s.pages.pages.first == s.pages.pages.last);
+ try testing.expect(s.pages.pages.first == s.cursor.page_pin.node);
+
+ const old_style = s.cursor.style;
+
+ {
+ const page = &s.pages.pages.last.?.data;
+ // 5 chars + cursor = 6 refs for bold style
+ try testing.expectEqual(
+ 6,
+ page.styles.refCount(page.memory, s.cursor.style_id),
+ );
+ // Cursor has the hyperlink active = 1 count in hyperlink_set
+ try testing.expectEqual(1, page.hyperlink_set.count());
+ try testing.expect(s.cursor.style_id != style.default_id);
+ try testing.expect(s.cursor.hyperlink_id != 0);
+ try testing.expect(s.cursor.hyperlink != null);
+ }
+
+ // This forces the page to change via increaseCapacity.
+ _ = try s.increaseCapacity(
+ s.cursor.page_pin.node,
+ .grapheme_bytes,
+ );
+
+ // Style should be preserved
+ try testing.expectEqual(old_style, s.cursor.style);
+ try testing.expect(s.cursor.style_id != style.default_id);
+
+ // Hyperlink should be preserved with correct URI
+ try testing.expect(s.cursor.hyperlink != null);
+ try testing.expect(s.cursor.hyperlink_id != 0);
+ try testing.expectEqualStrings("https://example.com/", s.cursor.hyperlink.?.uri);
+
+ // After increaseCapacity, both style and hyperlink are re-added to the new page.
+ {
+ const page = &s.pages.pages.last.?.data;
+ const ref_count = page.styles.refCount(page.memory, s.cursor.style_id);
+ try testing.expectEqual(6, ref_count);
+ try testing.expectEqual(1, page.hyperlink_set.count());
+ }
+}
+
+test "Screen: increaseCapacity non-cursor page returns early" {
+ // Test that calling increaseCapacity on a page that is NOT the cursor's
+ // page properly delegates to pages.increaseCapacity without doing the
+ // extra cursor accounting (style/hyperlink re-adding).
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var s = try init(alloc, .{
+ .cols = 80,
+ .rows = 24,
+ .max_scrollback = 10000,
+ });
+ defer s.deinit();
+
+ // Set up a custom style and hyperlink on the cursor
+ try s.setAttribute(.bold);
+ try s.startHyperlink("https://example.com/", null);
+ try s.testWriteString("Hello");
+
+ // Store cursor state before growing pages
+ const old_style = s.cursor.style;
+ const old_style_id = s.cursor.style_id;
+ const old_hyperlink = s.cursor.hyperlink;
+ const old_hyperlink_id = s.cursor.hyperlink_id;
+
+ // The cursor is on the first (and only) page
+ try testing.expect(s.pages.pages.first == s.pages.pages.last);
+ try testing.expect(s.cursor.page_pin.node == s.pages.pages.first.?);
+
+ // Grow pages until we have multiple pages. The cursor's pin stays on
+ // the first page since we're just adding rows.
+ const first_page_node = s.pages.pages.first.?;
+ first_page_node.data.pauseIntegrityChecks(true);
+ for (0..first_page_node.data.capacity.rows - first_page_node.data.size.rows) |_| {
+ _ = try s.pages.grow();
+ }
+ first_page_node.data.pauseIntegrityChecks(false);
+ _ = try s.pages.grow();
+
+ // Now we have two pages
+ try testing.expect(s.pages.pages.first != s.pages.pages.last);
+ const second_page = s.pages.pages.last.?;
+
+ // Cursor should still be on the first page (where it was created)
+ try testing.expect(s.cursor.page_pin.node == s.pages.pages.first.?);
+ try testing.expect(s.cursor.page_pin.node != second_page);
+
+ const second_page_styles_cap = second_page.data.capacity.styles;
+ const cursor_page_styles_cap = s.cursor.page_pin.node.data.capacity.styles;
+
+ // Call increaseCapacity on the second page (NOT the cursor's page)
+ const new_second_page = try s.increaseCapacity(second_page, .styles);
+
+ // The second page should have increased capacity
+ try testing.expectEqual(
+ second_page_styles_cap * 2,
+ new_second_page.data.capacity.styles,
+ );
+
+ // The cursor's page (first page) should be unchanged
+ try testing.expectEqual(
+ cursor_page_styles_cap,
+ s.cursor.page_pin.node.data.capacity.styles,
+ );
+
+ // Cursor state should be completely unchanged since we didn't touch its page
+ try testing.expectEqual(old_style, s.cursor.style);
+ try testing.expectEqual(old_style_id, s.cursor.style_id);
+ try testing.expectEqual(old_hyperlink, s.cursor.hyperlink);
+ try testing.expectEqual(old_hyperlink_id, s.cursor.hyperlink_id);
+
+ // Verify hyperlink is still valid
+ try testing.expect(s.cursor.hyperlink != null);
+ try testing.expectEqualStrings("https://example.com/", s.cursor.hyperlink.?.uri);
+}
+
+test "Screen: cursorDown to page with insufficient capacity" {
+ // Regression test for https://github.com/ghostty-org/ghostty/issues/10282
+ //
+ // This test exposes a use-after-realloc bug in cursorDown (and similar
+ // cursor movement functions). The bug pattern:
+ //
+ // 1. cursorDown creates a by-value copy of the pin via page_pin.down(n)
+ // 2. cursorChangePin is called, which may trigger adjustCapacity
+ // if the target page's style map is full
+ // 3. adjustCapacity frees the old page and creates a new one
+ // 4. The local pin copy still points to the freed page
+ // 5. rowAndCell() on the stale pin accesses freed memory
+
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ // Small screen to make page boundary crossing easy to set up
+ var s = try init(alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 1 });
+ defer s.deinit();
+
+ // Scroll down enough to create a second page
+ const start_page = &s.pages.pages.last.?.data;
+ const rem = start_page.capacity.rows;
+ start_page.pauseIntegrityChecks(true);
+ for (0..rem) |_| try s.cursorDownOrScroll();
+ start_page.pauseIntegrityChecks(false);
+
+ // Cursor should now be on a new page
+ const new_page = &s.cursor.page_pin.node.data;
+ try testing.expect(start_page != new_page);
+
+ // Fill new_page's style map to capacity. When we move INTO this page
+ // with a style set, adjustCapacity will be triggered.
+ {
+ new_page.pauseIntegrityChecks(true);
+ defer new_page.pauseIntegrityChecks(false);
+ defer new_page.assertIntegrity();
+
+ var n: u24 = 1;
+ while (new_page.styles.add(
+ new_page.memory,
+ .{ .bg_color = .{ .rgb = @bitCast(n) } },
+ )) |_| n += 1 else |_| {}
+ }
+
+ // Move cursor to start of active area and set a style
+ s.cursorAbsolute(0, 0);
+ try s.setAttribute(.bold);
+ try testing.expect(s.cursor.style.flags.bold);
+ try testing.expect(s.cursor.style_id != style.default_id);
+
+ // Find the row just before the page boundary
+ for (0..s.pages.rows - 1) |row| {
+ s.cursorAbsolute(0, @intCast(row));
+ const cur_node = s.cursor.page_pin.node;
+ if (s.cursor.page_pin.down(1)) |next_pin| {
+ if (next_pin.node != cur_node) {
+ // Cursor is at 'row', moving down crosses to new_page
+ try testing.expect(&next_pin.node.data == new_page);
+
+ // This cursorDown triggers the bug: the local page_pin copy
+ // becomes stale after adjustCapacity, causing rowAndCell()
+ // to access freed memory.
+ s.cursorDown(1);
+
+ // If the fix is applied, verify correct state
+ try testing.expect(s.cursor.y == row + 1);
+ try testing.expect(s.cursor.style.flags.bold);
+
+ break;
+ }
+ }
+ } else {
+ // Didn't find boundary
+ try testing.expect(false);
+ }
+}
+
+test "Screen setAttribute increases capacity when style map is full" {
+ // Tests that setAttribute succeeds when the style map is full by
+ // increasing page capacity. When capacity is at max and increaseCapacity
+ // returns OutOfSpace, manualStyleUpdate will split the page instead.
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ // Use a small screen with multiple rows
+ var s = try init(alloc, .{ .cols = 10, .rows = 5, .max_scrollback = 10 });
+ defer s.deinit();
+
+ // Write content to multiple rows
+ try s.testWriteString("line1\nline2\nline3\nline4\nline5");
+
+ // Get the page and fill its style map to capacity
const page = &s.cursor.page_pin.node.data;
+ const original_styles_capacity = page.capacity.styles;
- // We add unique styles to the page until no more will fit.
- fill: for (0..255) |bg| {
- for (0..255) |fg| {
- const st: style.Style = .{
- .bg_color = .{ .palette = @intCast(bg) },
- .fg_color = .{ .palette = @intCast(fg) },
- };
+ // Fill the style map to capacity using the StyleSet's layout capacity
+ // which accounts for the load factor
+ {
+ page.pauseIntegrityChecks(true);
+ defer page.pauseIntegrityChecks(false);
+ defer page.assertIntegrity();
- s.cursor.style = st;
-
- // Try to insert the new style, if it doesn't fit then
- // we succeeded in filling the style set, so we break.
- s.cursor.style_id = page.styles.add(
+ const max_items = page.styles.layout.cap;
+ var n: usize = 1;
+ while (n < max_items) : (n += 1) {
+ _ = page.styles.add(
page.memory,
- s.cursor.style,
- ) catch break :fill;
-
- try s.testWriteString("a");
+ .{ .bg_color = .{ .rgb = @bitCast(@as(u24, @intCast(n))) } },
+ ) catch break;
}
}
- // Adjust the capacity, this should cause the style set to reach the
- // same state it was in to begin with, since it will clone the page
- // in the same order as the styles were added to begin with, meaning
- // the cursor style will not be able to be added to the set, which
- // should, right now, result in the cursor style being cleared.
- _ = try s.adjustCapacity(s.cursor.page_pin.node, .{});
+ // Now try to set a new unique attribute that would require a new style slot
+ // This should succeed by increasing capacity (or splitting if at max capacity)
+ try s.setAttribute(.bold);
- // The cursor style should have been cleared by the `adjustCapacity`.
- //
- // This behavior will change, causing this test to fail, if either
- // of these changes are made:
- //
- // - The adjustCapacity function is changed to ensure the
- // new capacity will definitely fit the cursor style.
- //
- // - The cursor managed memory handling is reworked so that it
- // doesn't reside in the pages anymore and doesn't need this
- // accounting.
- //
- // In such a case, adjust this test accordingly.
- try testing.expect(s.cursor.style.default());
- try testing.expectEqual(style.default_id, s.cursor.style_id);
+ // The style should have been applied (bold flag set)
+ try testing.expect(s.cursor.style.flags.bold);
+
+ // The cursor should have a valid non-default style_id
+ try testing.expect(s.cursor.style_id != style.default_id);
+
+ // Either the capacity increased or the page was split/changed
+ const current_page = &s.cursor.page_pin.node.data;
+ const capacity_increased = current_page.capacity.styles > original_styles_capacity;
+ const page_changed = current_page != page;
+ try testing.expect(capacity_increased or page_changed);
+}
+
+test "Screen setAttribute splits page on OutOfSpace at max styles" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var s = try init(alloc, .{
+ .cols = 10,
+ .rows = 10,
+ .max_scrollback = 0,
+ });
+ defer s.deinit();
+
+ // Write content to multiple rows so we have something to split
+ try s.testWriteString("line1\nline2\nline3\nline4\nline5");
+
+ // Remember the original node
+ const original_node = s.cursor.page_pin.node;
+
+ // Increase the page's style capacity to max by repeatedly calling increaseCapacity
+ // Use Screen.increaseCapacity to properly maintain cursor state
+ const max_styles = std.math.maxInt(size.CellCountInt);
+ while (s.cursor.page_pin.node.data.capacity.styles < max_styles) {
+ _ = s.increaseCapacity(
+ s.cursor.page_pin.node,
+ .styles,
+ ) catch break;
+ }
+
+ // Get the page reference after increaseCapacity - cursor may have moved
+ var page = &s.cursor.page_pin.node.data;
+ try testing.expectEqual(max_styles, page.capacity.styles);
+
+ // Fill the style map to capacity using the StyleSet's layout capacity
+ // which accounts for the load factor
+ {
+ page.pauseIntegrityChecks(true);
+ defer page.pauseIntegrityChecks(false);
+ defer page.assertIntegrity();
+
+ const max_items = page.styles.layout.cap;
+ var n: usize = 1;
+ while (n < max_items) : (n += 1) {
+ _ = page.styles.add(
+ page.memory,
+ .{ .bg_color = .{ .rgb = @bitCast(@as(u24, @intCast(n))) } },
+ ) catch break;
+ }
+ }
+
+ // Track the node before setAttribute
+ const node_before_set = s.cursor.page_pin.node;
+
+ // Now try to set a new unique attribute that would require a new style slot
+ // At max capacity, increaseCapacity will return OutOfSpace, triggering page split
+ try s.setAttribute(.bold);
+
+ // The style should have been applied (bold flag set)
+ try testing.expect(s.cursor.style.flags.bold);
+
+ // The cursor should have a valid non-default style_id
+ try testing.expect(s.cursor.style_id != style.default_id);
+
+ // The page should have been split
+ const page_was_split = s.cursor.page_pin.node != node_before_set or
+ node_before_set.next != null or
+ node_before_set.prev != null or
+ s.cursor.page_pin.node != original_node;
+ try testing.expect(page_was_split);
}
diff --git a/src/terminal/StringMap.zig b/src/terminal/StringMap.zig
index 4ac47eeab..f7d88d1c8 100644
--- a/src/terminal/StringMap.zig
+++ b/src/terminal/StringMap.zig
@@ -147,3 +147,131 @@ test "StringMap searchIterator" {
try testing.expect(try it.next() == null);
}
+
+test "StringMap searchIterator URL detection" {
+ if (comptime !build_options.oniguruma) return error.SkipZigTest;
+
+ const testing = std.testing;
+ const alloc = testing.allocator;
+ const url = @import("../config/url.zig");
+
+ // Initialize URL regex
+ try oni.testing.ensureInit();
+ var re = try oni.Regex.init(
+ url.regex,
+ .{},
+ oni.Encoding.utf8,
+ oni.Syntax.default,
+ null,
+ );
+ defer re.deinit();
+
+ // Initialize our screen with text containing a URL
+ var s = try Screen.init(alloc, .{ .cols = 40, .rows = 5, .max_scrollback = 0 });
+ defer s.deinit();
+ try s.testWriteString("hello https://example.com/path world");
+
+ // Get the line
+ const line = s.selectLine(.{
+ .pin = s.pages.pin(.{ .active = .{
+ .x = 10,
+ .y = 0,
+ } }).?,
+ }).?;
+ var map: StringMap = undefined;
+ const sel_str = try s.selectionString(alloc, .{
+ .sel = line,
+ .trim = false,
+ .map = &map,
+ });
+ alloc.free(sel_str);
+ defer map.deinit(alloc);
+
+ // Search for URL match
+ var it = map.searchIterator(re);
+ {
+ var match = (try it.next()).?;
+ defer match.deinit();
+
+ const sel = match.selection();
+ // URL should start at x=6 ("https://example.com/path" starts after "hello ")
+ try testing.expectEqual(point.Point{ .screen = .{
+ .x = 6,
+ .y = 0,
+ } }, s.pages.pointFromPin(.screen, sel.start()).?);
+ // URL should end at x=29 (end of "/path")
+ try testing.expectEqual(point.Point{ .screen = .{
+ .x = 29,
+ .y = 0,
+ } }, s.pages.pointFromPin(.screen, sel.end()).?);
+ }
+
+ try testing.expect(try it.next() == null);
+}
+
+test "StringMap searchIterator URL with click position" {
+ if (comptime !build_options.oniguruma) return error.SkipZigTest;
+
+ const testing = std.testing;
+ const alloc = testing.allocator;
+ const url = @import("../config/url.zig");
+
+ // Initialize URL regex
+ try oni.testing.ensureInit();
+ var re = try oni.Regex.init(
+ url.regex,
+ .{},
+ oni.Encoding.utf8,
+ oni.Syntax.default,
+ null,
+ );
+ defer re.deinit();
+
+ // Initialize our screen with text containing a URL
+ var s = try Screen.init(alloc, .{ .cols = 40, .rows = 5, .max_scrollback = 0 });
+ defer s.deinit();
+ try s.testWriteString("hello https://example.com world");
+
+ // Simulate clicking on "example" (x=14)
+ const click_pin = s.pages.pin(.{ .active = .{
+ .x = 14,
+ .y = 0,
+ } }).?;
+
+ // Get the line
+ const line = s.selectLine(.{
+ .pin = click_pin,
+ }).?;
+ var map: StringMap = undefined;
+ const sel_str = try s.selectionString(alloc, .{
+ .sel = line,
+ .trim = false,
+ .map = &map,
+ });
+ alloc.free(sel_str);
+ defer map.deinit(alloc);
+
+ // Search for URL match and verify click position is within URL
+ var it = map.searchIterator(re);
+ var found_url = false;
+ while (true) {
+ var match = (try it.next()) orelse break;
+ defer match.deinit();
+
+ const sel = match.selection();
+ if (sel.contains(&s, click_pin)) {
+ found_url = true;
+ // Verify URL bounds
+ try testing.expectEqual(point.Point{ .screen = .{
+ .x = 6,
+ .y = 0,
+ } }, s.pages.pointFromPin(.screen, sel.start()).?);
+ try testing.expectEqual(point.Point{ .screen = .{
+ .x = 24,
+ .y = 0,
+ } }, s.pages.pointFromPin(.screen, sel.end()).?);
+ break;
+ }
+ }
+ try testing.expect(found_url);
+}
diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig
index 6c9d8b585..45d19fa06 100644
--- a/src/terminal/Terminal.zig
+++ b/src/terminal/Terminal.zig
@@ -9,6 +9,7 @@ const assert = @import("../quirks.zig").inlineAssert;
const testing = std.testing;
const Allocator = std.mem.Allocator;
const unicode = @import("../unicode/main.zig");
+const uucode = @import("uucode");
const ansi = @import("ansi.zig");
const modespkg = @import("modes.zig");
@@ -361,7 +362,7 @@ pub fn print(self: *Terminal, c: u21) !void {
if (prev.cell.codepoint() == 0) break :grapheme;
const grapheme_break = brk: {
- var state: unicode.GraphemeBreakState = .{};
+ var state: uucode.grapheme.BreakState = .default;
var cp1: u21 = prev.cell.content.codepoint;
if (prev.cell.hasGrapheme()) {
const cps = self.screens.active.cursor.page_pin.node.data.lookupGrapheme(prev.cell).?;
@@ -512,7 +513,7 @@ pub fn print(self: *Terminal, c: u21) !void {
// If this is a emoji variation selector, prev must be an emoji
if (c == 0xFE0F or c == 0xFE0E) {
const prev_props = unicode.table.get(prev.content.codepoint);
- const emoji = prev_props.grapheme_boundary_class == .extended_pictographic;
+ const emoji = prev_props.grapheme_break == .extended_pictographic;
if (!emoji) return;
}
@@ -675,7 +676,7 @@ fn printCell(
// TODO: this case was not handled in the old terminal implementation
// but it feels like we should do something. investigate other
- // terminals (xterm mainly) and see whats up.
+ // terminals (xterm mainly) and see what's up.
.spacer_head => {},
}
}
@@ -996,7 +997,7 @@ pub fn saveCursor(self: *Terminal) void {
///
/// The primary and alternate screen have distinct save state.
/// If no save was done before values are reset to their initial values.
-pub fn restoreCursor(self: *Terminal) !void {
+pub fn restoreCursor(self: *Terminal) void {
const saved: Screen.SavedCursor = self.screens.active.saved_cursor orelse .{
.x = 0,
.y = 0,
@@ -1008,10 +1009,17 @@ pub fn restoreCursor(self: *Terminal) !void {
};
// Set the style first because it can fail
- const old_style = self.screens.active.cursor.style;
self.screens.active.cursor.style = saved.style;
- errdefer self.screens.active.cursor.style = old_style;
- try self.screens.active.manualStyleUpdate();
+ self.screens.active.manualStyleUpdate() catch |err| {
+ // Regardless of the error here, we revert back to an unstyled
+ // cursor. It is more important that the restore succeeds in
+ // other attributes because terminals have no way to communicate
+ // failure back.
+ log.warn("restoreCursor error updating style err={}", .{err});
+ const screen: *Screen = self.screens.active;
+ screen.cursor.style = .{};
+ self.screens.active.manualStyleUpdate() catch unreachable;
+ };
self.screens.active.charset = saved.charset;
self.modes.set(.origin, saved.origin);
@@ -1602,9 +1610,6 @@ pub fn insertLines(self: *Terminal, count: usize) void {
const cur_rac = cur_p.rowAndCell();
const cur_row: *Row = cur_rac.row;
- // Mark the row as dirty
- cur_p.markDirty();
-
// If this is one of the lines we need to shift, do so
if (y > adjusted_count) {
const off_p = cur_p.up(adjusted_count).?;
@@ -1637,54 +1642,48 @@ pub fn insertLines(self: *Terminal, count: usize) void {
self.scrolling_region.left,
self.scrolling_region.right + 1,
) catch |err| {
- const cap = dst_p.node.data.capacity;
// Adjust our page capacity to make
// room for we didn't have space for
- _ = self.screens.active.adjustCapacity(
+ _ = self.screens.active.increaseCapacity(
dst_p.node,
switch (err) {
// Rehash the sets
error.StyleSetNeedsRehash,
error.HyperlinkSetNeedsRehash,
- => .{},
+ => null,
// Increase style memory
error.StyleSetOutOfMemory,
- => .{ .styles = cap.styles * 2 },
+ => .styles,
// Increase string memory
error.StringAllocOutOfMemory,
- => .{ .string_bytes = cap.string_bytes * 2 },
+ => .string_bytes,
// Increase hyperlink memory
error.HyperlinkSetOutOfMemory,
error.HyperlinkMapOutOfMemory,
- => .{ .hyperlink_bytes = cap.hyperlink_bytes * 2 },
+ => .hyperlink_bytes,
// Increase grapheme memory
error.GraphemeMapOutOfMemory,
error.GraphemeAllocOutOfMemory,
- => .{ .grapheme_bytes = cap.grapheme_bytes * 2 },
+ => .grapheme_bytes,
},
) catch |e| switch (e) {
- // This shouldn't be possible because above we're only
- // adjusting capacity _upwards_. So it should have all
- // the existing capacity it had to fit the adjusted
- // data. Panic since we don't expect this.
- error.StyleSetOutOfMemory,
- error.StyleSetNeedsRehash,
- error.StringAllocOutOfMemory,
- error.HyperlinkSetOutOfMemory,
- error.HyperlinkSetNeedsRehash,
- error.HyperlinkMapOutOfMemory,
- error.GraphemeMapOutOfMemory,
- error.GraphemeAllocOutOfMemory,
- => @panic("adjustCapacity resulted in capacity errors"),
-
- // The system allocator is OOM. We can't currently do
- // anything graceful here. We panic.
+ // System OOM. We have no way to recover from this
+ // currently. We should probably change insertLines
+ // to raise an error here.
error.OutOfMemory,
- => @panic("adjustCapacity system allocator OOM"),
+ => @panic("increaseCapacity system allocator OOM"),
+
+ // The page can't accommodate the managed memory required
+ // for this operation. We previously just corrupted
+ // memory here so a crash is better. The right long
+ // term solution is to allocate a new page here
+ // move this row to the new page, and start over.
+ error.OutOfSpace,
+ => @panic("increaseCapacity OutOfSpace"),
};
// Continue the loop to try handling this row again.
@@ -1699,9 +1698,6 @@ pub fn insertLines(self: *Terminal, count: usize) void {
dst_row.* = src_row.*;
src_row.* = dst;
- // Make sure the row is marked as dirty though.
- dst_row.dirty = true;
-
// Ensure what we did didn't corrupt the page
cur_p.node.data.assertIntegrity();
} else {
@@ -1728,6 +1724,9 @@ pub fn insertLines(self: *Terminal, count: usize) void {
);
}
+ // Mark the row as dirty
+ cur_p.markDirty();
+
// We have successfully processed a line.
y -= 1;
// Move our pin up to the next row.
@@ -1805,9 +1804,6 @@ pub fn deleteLines(self: *Terminal, count: usize) void {
const cur_rac = cur_p.rowAndCell();
const cur_row: *Row = cur_rac.row;
- // Mark the row as dirty
- cur_p.markDirty();
-
// If this is one of the lines we need to shift, do so
if (y < rem - adjusted_count) {
const off_p = cur_p.down(adjusted_count).?;
@@ -1840,49 +1836,41 @@ pub fn deleteLines(self: *Terminal, count: usize) void {
self.scrolling_region.left,
self.scrolling_region.right + 1,
) catch |err| {
- const cap = dst_p.node.data.capacity;
// Adjust our page capacity to make
// room for we didn't have space for
- _ = self.screens.active.adjustCapacity(
+ _ = self.screens.active.increaseCapacity(
dst_p.node,
switch (err) {
// Rehash the sets
error.StyleSetNeedsRehash,
error.HyperlinkSetNeedsRehash,
- => .{},
+ => null,
// Increase style memory
error.StyleSetOutOfMemory,
- => .{ .styles = cap.styles * 2 },
+ => .styles,
// Increase string memory
error.StringAllocOutOfMemory,
- => .{ .string_bytes = cap.string_bytes * 2 },
+ => .string_bytes,
// Increase hyperlink memory
error.HyperlinkSetOutOfMemory,
error.HyperlinkMapOutOfMemory,
- => .{ .hyperlink_bytes = cap.hyperlink_bytes * 2 },
+ => .hyperlink_bytes,
// Increase grapheme memory
error.GraphemeMapOutOfMemory,
error.GraphemeAllocOutOfMemory,
- => .{ .grapheme_bytes = cap.grapheme_bytes * 2 },
+ => .grapheme_bytes,
},
) catch |e| switch (e) {
- // See insertLines which has the same error capture.
- error.StyleSetOutOfMemory,
- error.StyleSetNeedsRehash,
- error.StringAllocOutOfMemory,
- error.HyperlinkSetOutOfMemory,
- error.HyperlinkSetNeedsRehash,
- error.HyperlinkMapOutOfMemory,
- error.GraphemeMapOutOfMemory,
- error.GraphemeAllocOutOfMemory,
- => @panic("adjustCapacity resulted in capacity errors"),
-
+ // See insertLines
error.OutOfMemory,
- => @panic("adjustCapacity system allocator OOM"),
+ => @panic("increaseCapacity system allocator OOM"),
+
+ error.OutOfSpace,
+ => @panic("increaseCapacity OutOfSpace"),
};
// Continue the loop to try handling this row again.
@@ -1897,9 +1885,6 @@ pub fn deleteLines(self: *Terminal, count: usize) void {
dst_row.* = src_row.*;
src_row.* = dst;
- // Make sure the row is marked as dirty though.
- dst_row.dirty = true;
-
// Ensure what we did didn't corrupt the page
cur_p.node.data.assertIntegrity();
} else {
@@ -1926,6 +1911,9 @@ pub fn deleteLines(self: *Terminal, count: usize) void {
);
}
+ // Mark the row as dirty
+ cur_p.markDirty();
+
// We have successfully processed a line.
y += 1;
// Move our pin down to the next row.
@@ -2767,12 +2755,7 @@ pub fn switchScreenMode(
}
} else {
assert(self.screens.active_key == .primary);
- self.restoreCursor() catch |err| {
- log.warn(
- "restore cursor on switch screen failed to={} err={}",
- .{ to, err },
- );
- };
+ self.restoreCursor();
},
}
}
@@ -4014,6 +3997,53 @@ test "Terminal: overwrite multicodepoint grapheme tail clears grapheme data" {
try testing.expectEqual(@as(usize, 0), page.graphemeCount());
}
+test "Terminal: print breaks valid grapheme cluster with Prepend + ASCII for speed" {
+ const alloc = testing.allocator;
+ var t = try init(alloc, .{ .rows = 5, .cols = 5 });
+ defer t.deinit(alloc);
+ t.modes.set(.grapheme_cluster, true);
+
+ // Make sure we're not at cursor.x == 0 for the next char.
+ try t.print('_');
+
+ // U+0600 ARABIC NUMBER SIGN (Prepend)
+ try t.print(0x0600);
+ try t.print('1');
+
+ // We should have 3 cells taken up, each narrow. Note that this is
+ // **incorrect** grapheme break behavior, since a Prepend code point should
+ // not break with the one following it per UAX #29 GB9b. However, as an
+ // optimization we assume a grapheme break when c <= 255, and note that
+ // this deviation only affects these very uncommon scenarios (e.g. the
+ // Arabic number sign should precede Arabic-script digits).
+ try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.y);
+ try testing.expectEqual(@as(usize, 3), t.screens.active.cursor.x);
+ // This is what we'd expect if we did break correctly:
+ //try testing.expectEqual(@as(usize, 2), t.screens.active.cursor.x);
+
+ // Assert various properties about our screen to verify
+ // we have all expected cells.
+ {
+ const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 1, .y = 0 } }).?;
+ const cell = list_cell.cell;
+ try testing.expectEqual(@as(u21, 0x0600), cell.content.codepoint);
+ try testing.expect(!cell.hasGrapheme());
+ // This is what we'd expect if we did break correctly:
+ //try testing.expect(cell.hasGrapheme());
+ //try testing.expectEqualSlices(u21, &.{'1'}, list_cell.node.data.lookupGrapheme(cell).?);
+ try testing.expectEqual(Cell.Wide.narrow, cell.wide);
+ }
+ {
+ const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 2, .y = 0 } }).?;
+ const cell = list_cell.cell;
+ try testing.expectEqual(@as(u21, '1'), cell.content.codepoint);
+ // This is what we'd expect if we did break correctly:
+ //try testing.expectEqual(@as(u21, 0), cell.content.codepoint);
+ try testing.expect(!cell.hasGrapheme());
+ try testing.expectEqual(Cell.Wide.narrow, cell.wide);
+ }
+}
+
test "Terminal: print writes to bottom if scrolled" {
var t = try init(testing.allocator, .{ .cols = 5, .rows = 2 });
defer t.deinit(testing.allocator);
@@ -4827,7 +4857,7 @@ test "Terminal: horizontal tab back with cursor before left margin" {
t.saveCursor();
t.modes.set(.enable_left_and_right_margin, true);
t.setLeftAndRightMargin(5, 0);
- try t.restoreCursor();
+ t.restoreCursor();
try t.horizontalTabBack();
try t.print('X');
@@ -5419,6 +5449,52 @@ test "Terminal: insertLines top/bottom scroll region" {
}
}
+test "Terminal: insertLines across page boundary marks all shifted rows dirty" {
+ const alloc = testing.allocator;
+ var t = try init(alloc, .{ .rows = 5, .cols = 10, .max_scrollback = 1024 });
+ defer t.deinit(alloc);
+
+ const first_page = t.screens.active.pages.pages.first.?;
+ const first_page_nrows = first_page.data.capacity.rows;
+
+ // Fill up the first page minus 3 rows
+ for (0..first_page_nrows - 3) |_| try t.linefeed();
+
+ // Add content that will cross a page boundary
+ try t.printString("1AAAA");
+ t.carriageReturn();
+ try t.linefeed();
+ try t.printString("2BBBB");
+ t.carriageReturn();
+ try t.linefeed();
+ try t.printString("3CCCC");
+ t.carriageReturn();
+ try t.linefeed();
+ try t.printString("4DDDD");
+ t.carriageReturn();
+ try t.linefeed();
+ try t.printString("5EEEE");
+
+ // Verify we now have a second page
+ try testing.expect(first_page.next != null);
+
+ t.setCursorPos(1, 1);
+ t.clearDirty();
+ t.insertLines(1);
+
+ try testing.expect(t.isDirty(.{ .active = .{ .x = 0, .y = 0 } }));
+ try testing.expect(t.isDirty(.{ .active = .{ .x = 0, .y = 1 } }));
+ try testing.expect(t.isDirty(.{ .active = .{ .x = 0, .y = 2 } }));
+ try testing.expect(t.isDirty(.{ .active = .{ .x = 0, .y = 3 } }));
+ try testing.expect(t.isDirty(.{ .active = .{ .x = 0, .y = 4 } }));
+
+ {
+ const str = try t.plainString(testing.allocator);
+ defer testing.allocator.free(str);
+ try testing.expectEqualStrings("\n1AAAA\n2BBBB\n3CCCC\n4DDDD", str);
+ }
+}
+
test "Terminal: insertLines (legacy test)" {
const alloc = testing.allocator;
var t = try init(alloc, .{ .cols = 2, .rows = 5 });
@@ -8042,6 +8118,52 @@ test "Terminal: deleteLines colors with bg color" {
}
}
+test "Terminal: deleteLines across page boundary marks all shifted rows dirty" {
+ const alloc = testing.allocator;
+ var t = try init(alloc, .{ .rows = 5, .cols = 10, .max_scrollback = 1024 });
+ defer t.deinit(alloc);
+
+ const first_page = t.screens.active.pages.pages.first.?;
+ const first_page_nrows = first_page.data.capacity.rows;
+
+ // Fill up the first page minus 3 rows
+ for (0..first_page_nrows - 3) |_| try t.linefeed();
+
+ // Add content that will cross a page boundary
+ try t.printString("1AAAA");
+ t.carriageReturn();
+ try t.linefeed();
+ try t.printString("2BBBB");
+ t.carriageReturn();
+ try t.linefeed();
+ try t.printString("3CCCC");
+ t.carriageReturn();
+ try t.linefeed();
+ try t.printString("4DDDD");
+ t.carriageReturn();
+ try t.linefeed();
+ try t.printString("5EEEE");
+
+ // Verify we now have a second page
+ try testing.expect(first_page.next != null);
+
+ t.setCursorPos(1, 1);
+ t.clearDirty();
+ t.deleteLines(1);
+
+ try testing.expect(t.isDirty(.{ .active = .{ .x = 0, .y = 0 } }));
+ try testing.expect(t.isDirty(.{ .active = .{ .x = 0, .y = 1 } }));
+ try testing.expect(t.isDirty(.{ .active = .{ .x = 0, .y = 2 } }));
+ try testing.expect(t.isDirty(.{ .active = .{ .x = 0, .y = 3 } }));
+ try testing.expect(t.isDirty(.{ .active = .{ .x = 0, .y = 4 } }));
+
+ {
+ const str = try t.plainString(testing.allocator);
+ defer testing.allocator.free(str);
+ try testing.expectEqualStrings("2BBBB\n3CCCC\n4DDDD\n5EEEE", str);
+ }
+}
+
test "Terminal: deleteLines (legacy)" {
const alloc = testing.allocator;
var t = try init(alloc, .{ .cols = 80, .rows = 80 });
@@ -9801,7 +9923,7 @@ test "Terminal: saveCursor" {
t.screens.active.charset.gr = .G0;
try t.setAttribute(.{ .unset = {} });
t.modes.set(.origin, false);
- try t.restoreCursor();
+ t.restoreCursor();
try testing.expect(t.screens.active.cursor.style.flags.bold);
try testing.expect(t.screens.active.charset.gr == .G3);
try testing.expect(t.modes.get(.origin));
@@ -9817,7 +9939,7 @@ test "Terminal: saveCursor position" {
t.saveCursor();
t.setCursorPos(1, 1);
try t.print('B');
- try t.restoreCursor();
+ t.restoreCursor();
try t.print('X');
{
@@ -9837,7 +9959,7 @@ test "Terminal: saveCursor pending wrap state" {
t.saveCursor();
t.setCursorPos(1, 1);
try t.print('B');
- try t.restoreCursor();
+ t.restoreCursor();
try t.print('X');
{
@@ -9857,7 +9979,7 @@ test "Terminal: saveCursor origin mode" {
t.modes.set(.enable_left_and_right_margin, true);
t.setLeftAndRightMargin(3, 5);
t.setTopAndBottomMargin(2, 4);
- try t.restoreCursor();
+ t.restoreCursor();
try t.print('X');
{
@@ -9875,7 +9997,7 @@ test "Terminal: saveCursor resize" {
t.setCursorPos(1, 10);
t.saveCursor();
try t.resize(alloc, 5, 5);
- try t.restoreCursor();
+ t.restoreCursor();
try t.print('X');
{
@@ -9896,7 +10018,7 @@ test "Terminal: saveCursor protected pen" {
t.saveCursor();
t.setProtectedMode(.off);
try testing.expect(!t.screens.active.cursor.protected);
- try t.restoreCursor();
+ t.restoreCursor();
try testing.expect(t.screens.active.cursor.protected);
}
@@ -9909,10 +10031,67 @@ test "Terminal: saveCursor doesn't modify hyperlink state" {
const id = t.screens.active.cursor.hyperlink_id;
t.saveCursor();
try testing.expectEqual(id, t.screens.active.cursor.hyperlink_id);
- try t.restoreCursor();
+ t.restoreCursor();
try testing.expectEqual(id, t.screens.active.cursor.hyperlink_id);
}
+test "Terminal: restoreCursor uses default style on OutOfSpace" {
+ // Tests that restoreCursor falls back to default style when
+ // manualStyleUpdate fails with OutOfSpace (can't split a 1-row page
+ // and styles are at max capacity).
+ const alloc = testing.allocator;
+
+ // Use a single row so the page can't be split
+ var t = try init(alloc, .{ .cols = 10, .rows = 1 });
+ defer t.deinit(alloc);
+
+ // Set a style and save the cursor
+ try t.setAttribute(.{ .bold = {} });
+ t.saveCursor();
+
+ // Clear the style
+ try t.setAttribute(.{ .unset = {} });
+ try testing.expect(!t.screens.active.cursor.style.flags.bold);
+
+ // Fill the style map to max capacity
+ const max_styles = std.math.maxInt(size.CellCountInt);
+ while (t.screens.active.cursor.page_pin.node.data.capacity.styles < max_styles) {
+ _ = t.screens.active.increaseCapacity(
+ t.screens.active.cursor.page_pin.node,
+ .styles,
+ ) catch break;
+ }
+
+ const page = &t.screens.active.cursor.page_pin.node.data;
+ try testing.expectEqual(max_styles, page.capacity.styles);
+
+ // Fill all style slots using the StyleSet's layout capacity which accounts
+ // for the load factor. The capacity in the layout is the actual max number
+ // of items that can be stored.
+ {
+ page.pauseIntegrityChecks(true);
+ defer page.pauseIntegrityChecks(false);
+ defer page.assertIntegrity();
+
+ const max_items = page.styles.layout.cap;
+ var n: usize = 1;
+ while (n < max_items) : (n += 1) {
+ _ = page.styles.add(
+ page.memory,
+ .{ .bg_color = .{ .rgb = @bitCast(@as(u24, @intCast(n))) } },
+ ) catch break;
+ }
+ }
+
+ // Restore cursor - should fall back to default style since page
+ // can't be split (1 row) and styles are at max capacity
+ t.restoreCursor();
+
+ // The style should be reset to default because OutOfSpace occurred
+ try testing.expect(!t.screens.active.cursor.style.flags.bold);
+ try testing.expectEqual(style.default_id, t.screens.active.cursor.style_id);
+}
+
test "Terminal: setProtectedMode" {
const alloc = testing.allocator;
var t = try init(alloc, .{ .cols = 3, .rows = 3 });
@@ -11304,7 +11483,7 @@ test "Terminal: resize with reflow and saved cursor" {
t.saveCursor();
try t.resize(alloc, 5, 3);
- try t.restoreCursor();
+ t.restoreCursor();
{
const str = try t.plainString(testing.allocator);
@@ -11345,7 +11524,7 @@ test "Terminal: resize with reflow and saved cursor pending wrap" {
t.saveCursor();
try t.resize(alloc, 5, 3);
- try t.restoreCursor();
+ t.restoreCursor();
{
const str = try t.plainString(testing.allocator);
diff --git a/src/terminal/bitmap_allocator.zig b/src/terminal/bitmap_allocator.zig
index 258d73071..23a5048e1 100644
--- a/src/terminal/bitmap_allocator.zig
+++ b/src/terminal/bitmap_allocator.zig
@@ -63,6 +63,14 @@ pub fn BitmapAllocator(comptime chunk_size: comptime_int) type {
};
}
+ /// Returns the number of bytes required to allocate n elements of
+ /// type T. This accounts for the chunk size alignment used by the
+ /// bitmap allocator.
+ pub fn bytesRequired(comptime T: type, n: usize) usize {
+ const byte_count = @sizeOf(T) * n;
+ return alignForward(usize, byte_count, chunk_size);
+ }
+
/// Allocate n elements of type T. This will return error.OutOfMemory
/// if there isn't enough space in the backing buffer.
///
@@ -955,3 +963,45 @@ test "BitmapAllocator alloc and free two 1.5 bitmaps offset 0.75" {
bm.bitmap.ptr(buf)[0..4],
);
}
+
+test "BitmapAllocator bytesRequired" {
+ const testing = std.testing;
+
+ // Chunk size of 16 bytes (like grapheme_chunk in page.zig)
+ {
+ const Alloc = BitmapAllocator(16);
+
+ // Single byte rounds up to chunk size
+ try testing.expectEqual(16, Alloc.bytesRequired(u8, 1));
+ try testing.expectEqual(16, Alloc.bytesRequired(u8, 16));
+ try testing.expectEqual(32, Alloc.bytesRequired(u8, 17));
+
+ // u21 (4 bytes each)
+ try testing.expectEqual(16, Alloc.bytesRequired(u21, 1)); // 4 bytes -> 16
+ try testing.expectEqual(16, Alloc.bytesRequired(u21, 4)); // 16 bytes -> 16
+ try testing.expectEqual(32, Alloc.bytesRequired(u21, 5)); // 20 bytes -> 32
+ try testing.expectEqual(32, Alloc.bytesRequired(u21, 6)); // 24 bytes -> 32
+ }
+
+ // Chunk size of 4 bytes
+ {
+ const Alloc = BitmapAllocator(4);
+
+ try testing.expectEqual(4, Alloc.bytesRequired(u8, 1));
+ try testing.expectEqual(4, Alloc.bytesRequired(u8, 4));
+ try testing.expectEqual(8, Alloc.bytesRequired(u8, 5));
+
+ // u32 (4 bytes each) - exactly one chunk per element
+ try testing.expectEqual(4, Alloc.bytesRequired(u32, 1));
+ try testing.expectEqual(8, Alloc.bytesRequired(u32, 2));
+ }
+
+ // Chunk size of 32 bytes (like string_chunk in page.zig)
+ {
+ const Alloc = BitmapAllocator(32);
+
+ try testing.expectEqual(32, Alloc.bytesRequired(u8, 1));
+ try testing.expectEqual(32, Alloc.bytesRequired(u8, 32));
+ try testing.expectEqual(64, Alloc.bytesRequired(u8, 33));
+ }
+}
diff --git a/src/terminal/formatter.zig b/src/terminal/formatter.zig
index 74bbfe482..4249187a7 100644
--- a/src/terminal/formatter.zig
+++ b/src/terminal/formatter.zig
@@ -1042,6 +1042,13 @@ pub const PageFormatter = struct {
}
if (blank_rows > 0) {
+ // Reset style before emitting newlines to prevent background
+ // colors from bleeding into the next line's leading cells.
+ if (!style.default()) {
+ try self.formatStyleClose(writer);
+ style = .{};
+ }
+
const sequence: []const u8 = switch (self.opts.emit) {
// Plaintext just uses standard newlines because newlines
// on their own usually move the cursor back in anywhere
@@ -1114,13 +1121,26 @@ pub const PageFormatter = struct {
// If we have a zero value, then we accumulate a counter. We
// only want to turn zero values into spaces if we have a non-zero
// char sometime later.
- if (!cell.hasText()) {
- blank_cells += 1;
- continue;
- }
- if (cell.codepoint() == ' ' and self.opts.trim) {
- blank_cells += 1;
- continue;
+ blank: {
+ // If we're emitting styled output (not plaintext) and
+ // the cell has some kind of styling or is not empty
+ // then this isn't blank.
+ if (self.opts.emit.styled() and
+ (!cell.isEmpty() or cell.hasStyling())) break :blank;
+
+ // Cells with no text are blank
+ if (!cell.hasText()) {
+ blank_cells += 1;
+ continue;
+ }
+
+ // Trailing spaces are blank. We know it is trailing
+ // because if we get a non-empty cell later we'll
+ // fill the blanks.
+ if (cell.codepoint() == ' ' and self.opts.trim) {
+ blank_cells += 1;
+ continue;
+ }
}
// This cell is not blank. If we have accumulated blank cells
@@ -1158,63 +1178,64 @@ pub const PageFormatter = struct {
blank_cells = 0;
}
+ style: {
+ // If we aren't emitting styled output then we don't
+ // have to worry about styles.
+ if (!self.opts.emit.styled()) break :style;
+
+ // Get our cell style.
+ const cell_style = self.cellStyle(cell);
+
+ // If the style hasn't changed, don't bloat output.
+ if (cell_style.eql(style)) break :style;
+
+ // If we had a previous style, we need to close it,
+ // because we've confirmed we have some new style
+ // (which is maybe default).
+ if (!style.default()) switch (self.opts.emit) {
+ .html => try self.formatStyleClose(writer),
+
+ // For VT, we only close if we're switching to a default
+ // style because any non-default style will emit
+ // a \x1b[0m as the start of a VT coloring sequence.
+ .vt => if (cell_style.default()) try self.formatStyleClose(writer),
+
+ // Unreachable because of the styled() check at the
+ // top of this block.
+ .plain => unreachable,
+ };
+
+ // At this point, we can copy our style over
+ style = cell_style;
+
+ // If we're just the default style now, we're done.
+ if (cell_style.default()) break :style;
+
+ // New style, emit it.
+ try self.formatStyleOpen(
+ writer,
+ &style,
+ );
+
+ // If we have a point map, we map the style to
+ // this cell.
+ if (self.point_map) |*map| {
+ var discarding: std.Io.Writer.Discarding = .init(&.{});
+ try self.formatStyleOpen(
+ &discarding.writer,
+ &style,
+ );
+ for (0..discarding.count) |_| map.map.append(map.alloc, .{
+ .x = x,
+ .y = y,
+ }) catch return error.WriteFailed;
+ }
+ }
+
switch (cell.content_tag) {
// We combine codepoint and graphemes because both have
// shared style handling. We use comptime to dup it.
inline .codepoint, .codepoint_grapheme => |tag| {
- // Handle closing our styling if we go back to unstyled
- // content.
- if (self.opts.emit.styled() and
- !cell.hasStyling() and
- !style.default())
- {
- try self.formatStyleClose(writer);
- style = .{};
- }
-
- // If we're emitting styling and we have styles, then
- // we need to load the style and emit any sequences
- // as necessary.
- if (self.opts.emit.styled() and cell.hasStyling()) style: {
- // Get the style.
- const cell_style = self.page.styles.get(
- self.page.memory,
- cell.style_id,
- );
-
- // If the style hasn't changed since our last
- // emitted style, don't bloat the output.
- if (cell_style.eql(style)) break :style;
-
- // We need to emit a closing tag if the style
- // was non-default before, which means we set
- // styles once.
- const closing = !style.default();
-
- // New style, emit it.
- style = cell_style.*;
- try self.formatStyleOpen(
- writer,
- &style,
- closing,
- );
-
- // If we have a point map, we map the style to
- // this cell.
- if (self.point_map) |*map| {
- var discarding: std.Io.Writer.Discarding = .init(&.{});
- try self.formatStyleOpen(
- &discarding.writer,
- &style,
- closing,
- );
- for (0..discarding.count) |_| map.map.append(map.alloc, .{
- .x = x,
- .y = y,
- }) catch return error.WriteFailed;
- }
- }
-
try self.writeCell(tag, writer, cell);
// If we have a point map, all codepoints map to this
@@ -1229,10 +1250,15 @@ pub const PageFormatter = struct {
}
},
- // Unreachable since we do hasText() above
- .bg_color_palette,
- .bg_color_rgb,
- => unreachable,
+ // Cells with only background color (no text). Emit a space
+ // with the appropriate background color SGR sequence.
+ .bg_color_palette, .bg_color_rgb => {
+ try writer.writeByte(' ');
+ if (self.point_map) |*map| map.map.append(
+ map.alloc,
+ .{ .x = x, .y = y },
+ ) catch return error.WriteFailed;
+ },
}
}
}
@@ -1265,6 +1291,14 @@ pub const PageFormatter = struct {
writer: *std.Io.Writer,
cell: *const Cell,
) !void {
+ // Blank cells get an empty space that isn't replaced by anything
+ // because it isn't really a space. We do this so that formatting
+ // is preserved if we're emitting styles.
+ if (!cell.hasText()) {
+ try writer.writeByte(' ');
+ return;
+ }
+
try self.writeCodepointWithReplacement(writer, cell.content.codepoint);
if (comptime tag == .codepoint_grapheme) {
for (self.page.lookupGrapheme(cell).?) |cp| {
@@ -1348,18 +1382,47 @@ pub const PageFormatter = struct {
}
}
+ /// Returns the style for the given cell. If there is no styling this
+ /// will return the default style.
+ fn cellStyle(
+ self: *const PageFormatter,
+ cell: *const Cell,
+ ) Style {
+ return switch (cell.content_tag) {
+ inline .codepoint, .codepoint_grapheme => if (!cell.hasStyling())
+ .{}
+ else
+ self.page.styles.get(
+ self.page.memory,
+ cell.style_id,
+ ).*,
+
+ .bg_color_palette => .{
+ .bg_color = .{
+ .palette = cell.content.color_palette,
+ },
+ },
+
+ .bg_color_rgb => .{
+ .bg_color = .{
+ .rgb = .{
+ .r = cell.content.color_rgb.r,
+ .g = cell.content.color_rgb.g,
+ .b = cell.content.color_rgb.b,
+ },
+ },
+ },
+ };
+ }
+
fn formatStyleOpen(
self: PageFormatter,
writer: *std.Io.Writer,
style: *const Style,
- closing: bool,
) std.Io.Writer.Error!void {
switch (self.opts.emit) {
.plain => unreachable,
- // Note: we don't use closing on purpose because VT sequences
- // always reset the prior style. Our formatter always emits a
- // \x1b[0m before emitting a new style if necessary.
.vt => {
var formatter = style.formatterVt();
formatter.palette = self.opts.palette;
@@ -1369,7 +1432,6 @@ pub const PageFormatter = struct {
// We use `display: inline` so that the div doesn't impact
// layout since we're primarily using it as a CSS wrapper.
.html => {
- if (closing) try writer.writeAll("");
var formatter = style.formatterHtml();
formatter.palette = self.opts.palette;
try writer.print(
@@ -3345,7 +3407,9 @@ test "Page VT multi-line with styles" {
try formatter.format(&builder.writer);
const output = builder.writer.buffered();
- try testing.expectEqualStrings("\x1b[0m\x1b[1mfirst\r\n\x1b[0m\x1b[3msecond\x1b[0m", output);
+ // Note: style is reset before newline to prevent background colors from
+ // bleeding to the next line's leading cells.
+ try testing.expectEqualStrings("\x1b[0m\x1b[1mfirst\x1b[0m\r\n\x1b[0m\x1b[3msecond\x1b[0m", output);
// Verify point map matches output length
try testing.expectEqual(output.len, point_map.items.len);
@@ -5819,3 +5883,57 @@ test "Page codepoint_map empty map" {
const output = builder.writer.buffered();
try testing.expectEqualStrings("hello world", output);
}
+
+test "Page VT background color on trailing blank cells" {
+ // This test reproduces a bug where trailing cells with background color
+ // but no text are emitted as plain spaces without SGR sequences.
+ // This causes TUIs like htop to lose background colors on rehydration.
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var builder: std.Io.Writer.Allocating = .init(alloc);
+ defer builder.deinit();
+
+ var t = try Terminal.init(alloc, .{
+ .cols = 20,
+ .rows = 5,
+ });
+ defer t.deinit(alloc);
+
+ var s = t.vtStream();
+ defer s.deinit();
+
+ // Simulate a TUI row: "CPU:" with text, then trailing cells with red background
+ // to end of line (no text after the colored region).
+ // \x1b[41m sets red background, then EL fills rest of row with that bg.
+ try s.nextSlice("CPU:\x1b[41m\x1b[K");
+ // Reset colors and move to next line with different content
+ try s.nextSlice("\x1b[0m\r\nline2");
+
+ const pages = &t.screens.active.pages;
+ const page = &pages.pages.last.?.data;
+
+ var formatter: PageFormatter = .init(page, .vt);
+ formatter.opts.trim = false; // Don't trim so we can see the trailing behavior
+
+ try formatter.format(&builder.writer);
+ const output = builder.writer.buffered();
+
+ // The output should preserve the red background SGR for trailing cells on line 1.
+ // Bug: the first row outputs "CPU:\r\n" only - losing the background color fill.
+ // The red background should appear BEFORE the newline, not after.
+
+ // Find position of CRLF
+ const crlf_pos = std.mem.indexOf(u8, output, "\r\n") orelse {
+ // No CRLF found, fail the test
+ return error.TestUnexpectedResult;
+ };
+
+ // Check that red background (48;5;1) appears BEFORE the newline (on line 1)
+ const line1 = output[0..crlf_pos];
+ const has_red_bg_line1 = std.mem.indexOf(u8, line1, "\x1b[41m") != null or
+ std.mem.indexOf(u8, line1, "\x1b[48;5;1m") != null;
+
+ // This should be true but currently fails due to the bug
+ try testing.expect(has_red_bg_line1);
+}
diff --git a/src/terminal/highlight.zig b/src/terminal/highlight.zig
index 582ef6f06..bc3a1758e 100644
--- a/src/terminal/highlight.zig
+++ b/src/terminal/highlight.zig
@@ -180,6 +180,15 @@ pub const Flattened = struct {
};
}
+ pub fn endPin(self: Flattened) Pin {
+ const slice = self.chunks.slice();
+ return .{
+ .node = slice.items(.node)[slice.len - 1],
+ .x = self.bot_x,
+ .y = slice.items(.end)[slice.len - 1] - 1,
+ };
+ }
+
/// Convert to an Untracked highlight.
pub fn untracked(self: Flattened) Untracked {
// Note: we don't use startPin/endPin here because it is slightly
diff --git a/src/terminal/hyperlink.zig b/src/terminal/hyperlink.zig
index 975e6f30e..94f86466c 100644
--- a/src/terminal/hyperlink.zig
+++ b/src/terminal/hyperlink.zig
@@ -13,8 +13,9 @@ const autoHash = std.hash.autoHash;
const autoHashStrat = std.hash.autoHashStrat;
/// The unique identifier for a hyperlink. This is at most the number of cells
-/// that can fit in a single terminal page.
-pub const Id = size.CellCountInt;
+/// that can fit in a single terminal page, since each cell can only contain
+/// at most one hyperlink.
+pub const Id = size.HyperlinkCountInt;
// The mapping of cell to hyperlink. We use an offset hash map to save space
// since its very unlikely a cell is a hyperlink, so its a waste to store
diff --git a/src/terminal/kitty/color.zig b/src/terminal/kitty/color.zig
index deeabcfb7..c1072c390 100644
--- a/src/terminal/kitty/color.zig
+++ b/src/terminal/kitty/color.zig
@@ -17,6 +17,10 @@ pub const OSC = struct {
/// request.
terminator: Terminator = .st,
+ pub fn deinit(self: *OSC, alloc: std.mem.Allocator) void {
+ self.list.deinit(alloc);
+ }
+
/// We don't currently support encoding this to C in any way.
pub const C = void;
diff --git a/src/terminal/kitty/graphics_unicode.zig b/src/terminal/kitty/graphics_unicode.zig
index ceadf63ee..a223797ba 100644
--- a/src/terminal/kitty/graphics_unicode.zig
+++ b/src/terminal/kitty/graphics_unicode.zig
@@ -256,7 +256,7 @@ pub const Placement = struct {
if (img_scale_source.y < img_scaled.y_offset) {
// If our source rect y is within the offset area, we need to
// adjust our source rect and destination since the source texture
- // doesnt actually have the offset area blank.
+ // doesn't actually have the offset area blank.
const offset: f64 = img_scaled.y_offset - img_scale_source.y;
img_scale_source.height -= offset;
y_offset = offset;
@@ -286,7 +286,7 @@ pub const Placement = struct {
if (img_scale_source.x < img_scaled.x_offset) {
// If our source rect x is within the offset area, we need to
// adjust our source rect and destination since the source texture
- // doesnt actually have the offset area blank.
+ // doesn't actually have the offset area blank.
const offset: f64 = img_scaled.x_offset - img_scale_source.x;
img_scale_source.width -= offset;
x_offset = offset;
diff --git a/src/terminal/osc.zig b/src/terminal/osc.zig
index f62b7a6cd..14d501eaa 100644
--- a/src/terminal/osc.zig
+++ b/src/terminal/osc.zig
@@ -12,11 +12,11 @@ const mem = std.mem;
const assert = @import("../quirks.zig").inlineAssert;
const Allocator = mem.Allocator;
const LibEnum = @import("../lib/enum.zig").Enum;
-const RGB = @import("color.zig").RGB;
const kitty_color = @import("kitty/color.zig");
-const osc_color = @import("osc/color.zig");
-const string_encoding = @import("../os/string_encoding.zig");
-pub const color = osc_color;
+const parsers = @import("osc/parsers.zig");
+const encoding = @import("osc/encoding.zig");
+
+pub const color = parsers.color;
const log = std.log.scoped(.osc);
@@ -146,8 +146,8 @@ pub const Command = union(Key) {
///
/// 4, 5, 10-19, 104, 105, 110-119
color_operation: struct {
- op: osc_color.Operation,
- requests: osc_color.List = .{},
+ op: color.Operation,
+ requests: color.List = .{},
terminator: Terminator = .st,
},
@@ -193,6 +193,9 @@ pub const Command = union(Key) {
/// ConEmu GUI macro (OSC 9;6)
conemu_guimacro: [:0]const u8,
+ /// Kitty text sizing protocol (OSC 66)
+ kitty_text_sizing: parsers.kitty_text_sizing.OSC,
+
pub const Key = LibEnum(
if (build_options.c_abi) .c else .zig,
// NOTE: Order matters, see LibEnum documentation.
@@ -218,6 +221,7 @@ pub const Command = union(Key) {
"conemu_progress_report",
"conemu_wait_input",
"conemu_guimacro",
+ "kitty_text_sizing",
},
);
@@ -309,6 +313,9 @@ pub const Terminator = enum {
};
pub const Parser = struct {
+ /// Maximum size of a "normal" OSC.
+ pub const MAX_BUF = 2048;
+
/// Optional allocator used to accept data longer than MAX_BUF.
/// This only applies to some commands (e.g. OSC 52) that can
/// reasonably exceed MAX_BUF.
@@ -317,162 +324,81 @@ pub const Parser = struct {
/// Current state of the parser.
state: State,
- /// Current command of the parser, this accumulates.
+ /// Buffer for temporary storage of OSC data
+ buffer: [MAX_BUF]u8,
+ /// Fixed writer for accumulating OSC data
+ fixed: ?std.Io.Writer,
+ /// Allocating writer for accumulating OSC data
+ allocating: ?std.Io.Writer.Allocating,
+ /// Pointer to the active writer for accumulating OSC data
+ writer: ?*std.Io.Writer,
+
+ /// The command that is the result of parsing.
command: Command,
- /// Buffer that stores the input we see for a single OSC command.
- /// Slices in Command are offsets into this buffer.
- buf: [MAX_BUF]u8,
- buf_start: usize,
- buf_idx: usize,
- buf_dynamic: ?*std.ArrayListUnmanaged(u8),
-
- /// True when a command is complete/valid to return.
- complete: bool,
-
- /// Temporary state that is dependent on the current state.
- temp_state: union {
- /// Current string parameter being populated
- str: *[:0]const u8,
-
- /// Current numeric parameter being populated
- num: u16,
-
- /// Temporary state for key/value pairs
- key: []const u8,
- },
-
- // Maximum length of a single OSC command. This is the full OSC command
- // sequence length (excluding ESC ]). This is arbitrary, I couldn't find
- // any definitive resource on how long this should be.
- //
- // NOTE: This does mean certain OSC sequences such as OSC 8 (hyperlinks)
- // won't work if their parameters are larger than fit in the buffer.
- const MAX_BUF = 2048;
-
pub const State = enum {
- empty,
+ start,
invalid,
- swallow,
- // Command prefixes. We could just accumulate and compare (mem.eql)
- // but the state space is small enough that we just build it up this way.
+ // OSC command prefixes. Not all of these are valid OSCs, but may be
+ // needed to "bridge" to a valid OSC (e.g. to support OSC 777 we need to
+ // have a state "77" even though there is no OSC 77).
@"0",
@"1",
+ @"2",
+ @"4",
+ @"5",
+ @"6",
+ @"7",
+ @"8",
+ @"9",
@"10",
- @"104",
@"11",
@"12",
@"13",
- @"133",
@"14",
@"15",
@"16",
@"17",
@"18",
@"19",
- @"2",
@"21",
@"22",
- @"4",
- @"5",
@"52",
- @"7",
+ @"66",
@"77",
+ @"104",
+ @"110",
+ @"111",
+ @"112",
+ @"113",
+ @"114",
+ @"115",
+ @"116",
+ @"117",
+ @"118",
+ @"119",
+ @"133",
@"777",
- @"8",
- @"9",
-
- // We're in a semantic prompt OSC command but we aren't sure
- // what the command is yet, i.e. `133;`
- semantic_prompt,
- semantic_option_start,
- semantic_option_key,
- semantic_option_value,
- semantic_exit_code_start,
- semantic_exit_code,
-
- // Get/set clipboard states
- clipboard_kind,
- clipboard_kind_end,
-
- // OSC color operation.
- osc_color,
-
- // Hyperlinks
- hyperlink_param_key,
- hyperlink_param_value,
- hyperlink_uri,
-
- // rxvt extension. Only used for OSC 777 and only the value "notify" is
- // supported
- rxvt_extension,
-
- // Title of a desktop notification
- notification_title,
-
- // Expect a string parameter. param_str must be set as well as
- // buf_start.
- string,
-
- // A string that can grow beyond MAX_BUF. This uses the allocator.
- // If the parser has no allocator then it is treated as if the
- // buffer is full.
- allocable_string,
-
- // Kitty color protocol
- // https://sw.kovidgoyal.net/kitty/color-stack/#id1
- kitty_color_protocol_key,
- kitty_color_protocol_value,
-
- // OSC 9 is used by ConEmu and iTerm2 for different things.
- // iTerm2 uses it to post a notification[1].
- // ConEmu uses it to implement many custom functions[2].
- //
- // Some Linux applications (namely systemd and flatpak) have
- // adopted the ConEmu implementation but this causes bogus
- // notifications on iTerm2 compatible terminal emulators.
- //
- // Ghostty supports both by disallowing ConEmu-specific commands
- // from being shown as desktop notifications.
- //
- // [1]: https://iterm2.com/documentation-escape-codes.html
- // [2]: https://conemu.github.io/en/AnsiEscapeCodes.html#OSC_Operating_system_commands
- osc_9,
-
- // ConEmu specific substates
- conemu_sleep,
- conemu_sleep_value,
- conemu_message_box,
- conemu_tab,
- conemu_tab_txt,
- conemu_progress_prestate,
- conemu_progress_state,
- conemu_progress_prevalue,
- conemu_progress_value,
- conemu_guimacro,
};
pub fn init(alloc: ?Allocator) Parser {
var result: Parser = .{
.alloc = alloc,
- .state = .empty,
+ .state = .start,
+ .fixed = null,
+ .allocating = null,
+ .writer = null,
.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,
+ .buffer = 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;
+ result.buffer = undefined;
}
return result;
@@ -485,107 +411,123 @@ pub const Parser = struct {
/// Reset the parser state.
pub fn reset(self: *Parser) void {
- // If the state is already empty then we do nothing because
- // we may touch uninitialized memory.
- if (self.state == .empty) {
- assert(self.buf_start == 0);
- assert(self.buf_idx == 0);
- assert(!self.complete);
- assert(self.buf_dynamic == null);
- return;
- }
+ // If we set up an allocating writer, free up that memory.
+ if (self.allocating) |*allocating| allocating.deinit();
- // Some commands have their own memory management we need to clear.
+ // Handle any cleanup that individual OSCs require.
switch (self.command) {
- .kitty_color_protocol => |*v| v.list.deinit(self.alloc.?),
- .color_operation => |*v| v.requests.deinit(self.alloc.?),
- else => {},
+ .kitty_color_protocol => |*v| kitty_color_protocol: {
+ v.deinit(self.alloc orelse break :kitty_color_protocol);
+ },
+ .change_window_icon,
+ .change_window_title,
+ .clipboard_contents,
+ .color_operation,
+ .conemu_change_tab_title,
+ .conemu_guimacro,
+ .conemu_progress_report,
+ .conemu_show_message_box,
+ .conemu_sleep,
+ .conemu_wait_input,
+ .end_of_command,
+ .end_of_input,
+ .hyperlink_end,
+ .hyperlink_start,
+ .invalid,
+ .mouse_shape,
+ .prompt_end,
+ .prompt_start,
+ .report_pwd,
+ .show_desktop_notification,
+ .kitty_text_sizing,
+ => {},
}
- self.state = .empty;
- self.buf_start = 0;
- self.buf_idx = 0;
+ self.state = .start;
+ self.fixed = null;
+ self.allocating = null;
+ self.writer = null;
self.command = .invalid;
- self.complete = false;
- if (self.buf_dynamic) |ptr| {
- const alloc = self.alloc.?;
- ptr.deinit(alloc);
- alloc.destroy(ptr);
- self.buf_dynamic = null;
+
+ if (std.valgrind.runningOnValgrind() > 0) {
+ // Initialize our undefined fields so Valgrind can catch it.
+ // https://github.com/ziglang/zig/issues/19148
+ self.buffer = undefined;
}
}
+ /// Make sure that we have an allocator. If we don't, set the state to
+ /// invalid so that any additional OSC data is discarded.
+ inline fn ensureAllocator(self: *Parser) bool {
+ if (self.alloc != null) return true;
+ log.warn("An allocator is required to process OSC {t} but none was provided.", .{self.state});
+ self.state = .invalid;
+ return false;
+ }
+
+ /// Set up a fixed Writer to collect the rest of the OSC data.
+ inline fn writeToFixed(self: *Parser) void {
+ self.fixed = .fixed(&self.buffer);
+ self.writer = &self.fixed.?;
+ }
+
+ /// Set up an allocating Writer to collect the rest of the OSC data. If we
+ /// don't have an allocator or setting up the allocator fails, fall back to
+ /// writing to a fixed buffer and hope that it's big enough.
+ inline fn writeToAllocating(self: *Parser) void {
+ const alloc = self.alloc orelse {
+ // We don't have an allocator - fall back to a fixed buffer and hope
+ // that it's big enough.
+ self.writeToFixed();
+ return;
+ };
+
+ self.allocating = std.Io.Writer.Allocating.initCapacity(alloc, 2048) catch {
+ // The allocator failed for some reason, fall back to a fixed buffer
+ // and hope that it's big enough.
+ self.writeToFixed();
+ return;
+ };
+
+ self.writer = &self.allocating.?.writer;
+ }
+
/// Consume the next character c and advance the parser state.
pub fn next(self: *Parser, c: u8) void {
- // If our buffer is full then we're invalid, so we set our state
- // accordingly and indicate the sequence is incomplete so that we
- // don't accidentally issue a command when ending.
- //
- // We always keep space for 1 byte at the end to null-terminate
- // values.
- if (self.buf_idx >= self.buf.len - 1) {
- @branchHint(.cold);
- if (self.state != .invalid) {
- log.warn(
- "OSC sequence too long (> {d}), ignoring. state={}",
- .{ self.buf.len, self.state },
- );
- }
+ // If the state becomes invalid for any reason, just discard
+ // any further input.
+ if (self.state == .invalid) return;
- self.state = .invalid;
-
- // We have to do this here because it will never reach the
- // switch statement below, since our buf_idx will always be
- // too high after this.
- self.complete = false;
+ // If a writer has been initialized, we just accumulate the rest of the
+ // OSC sequence in the writer's buffer and skip the state machine.
+ if (self.writer) |writer| {
+ writer.writeByte(c) catch |err| switch (err) {
+ // We have overflowed our buffer or had some other error, set the
+ // state to invalid so that we discard any further input.
+ error.WriteFailed => self.state = .invalid,
+ };
return;
}
- // We store everything in the buffer so we can do a better job
- // logging if we get to an invalid command.
- self.buf[self.buf_idx] = c;
- self.buf_idx += 1;
-
- // log.warn("state = {} c = {x}", .{ self.state, c });
-
switch (self.state) {
- // If we get something during the invalid state, we've
- // ruined our entry.
- .invalid => self.complete = false,
+ // handled above, so should never be here
+ .invalid => unreachable,
- .empty => switch (c) {
+ .start => switch (c) {
'0' => self.state = .@"0",
'1' => self.state = .@"1",
'2' => self.state = .@"2",
'4' => self.state = .@"4",
'5' => self.state = .@"5",
+ '6' => self.state = .@"6",
'7' => self.state = .@"7",
'8' => self.state = .@"8",
'9' => self.state = .@"9",
else => self.state = .invalid,
},
- .swallow => {},
-
- .@"0" => switch (c) {
- ';' => {
- self.command = .{ .change_window_title = undefined };
- self.complete = true;
- self.state = .string;
- self.temp_state = .{ .str = &self.command.change_window_title };
- self.buf_start = self.buf_idx;
- },
- else => self.state = .invalid,
- },
-
.@"1" => switch (c) {
- ';' => {
- self.command = .{ .change_window_icon = undefined };
-
- self.state = .string;
- self.temp_state = .{ .str = &self.command.change_window_icon };
- self.buf_start = self.buf_idx;
- },
+ ';' => self.writeToFixed(),
'0' => self.state = .@"10",
'1' => self.state = .@"11",
'2' => self.state = .@"12",
@@ -600,390 +542,88 @@ pub const Parser = struct {
},
.@"10" => switch (c) {
- ';' => osc_10: {
- if (self.alloc == null) {
- log.warn("OSC 10 requires an allocator, but none was provided", .{});
- self.state = .invalid;
- break :osc_10;
- }
- self.command = .{ .color_operation = .{
- .op = .osc_10,
- } };
- self.state = .osc_color;
- self.buf_start = self.buf_idx;
- self.complete = true;
- },
- '4' => {
- self.state = .@"104";
- // If we have an allocator, then we can complete the OSC104
- if (self.alloc != null) self.complete = true;
- },
+ ';' => if (self.ensureAllocator()) self.writeToFixed(),
+ '4' => self.state = .@"104",
else => self.state = .invalid,
},
.@"104" => switch (c) {
- ';' => osc_104: {
- if (self.alloc == null) {
- log.warn("OSC 104 requires an allocator, but none was provided", .{});
- self.state = .invalid;
- break :osc_104;
- }
- self.command = .{
- .color_operation = .{
- .op = .osc_104,
- },
- };
- self.state = .osc_color;
- self.buf_start = self.buf_idx;
- self.complete = true;
- },
+ ';' => if (self.ensureAllocator()) self.writeToFixed(),
else => self.state = .invalid,
},
.@"11" => switch (c) {
- ';' => osc_11: {
- if (self.alloc == null) {
- log.warn("OSC 11 requires an allocator, but none was provided", .{});
- self.state = .invalid;
- break :osc_11;
- }
- self.command = .{ .color_operation = .{
- .op = .osc_11,
- } };
- self.state = .osc_color;
- self.buf_start = self.buf_idx;
- self.complete = true;
- },
- '0'...'9' => blk: {
- if (self.alloc == null) {
- log.warn("OSC 11{c} requires an allocator, but none was provided", .{c});
- self.state = .invalid;
- break :blk;
- }
-
- self.command = .{
- .color_operation = .{
- .op = switch (c) {
- '0' => .osc_110,
- '1' => .osc_111,
- '2' => .osc_112,
- '3' => .osc_113,
- '4' => .osc_114,
- '5' => .osc_115,
- '6' => .osc_116,
- '7' => .osc_117,
- '8' => .osc_118,
- '9' => .osc_119,
- else => unreachable,
- },
- },
- };
- self.state = .osc_color;
- self.buf_start = self.buf_idx;
- self.complete = true;
- },
+ ';' => if (self.ensureAllocator()) self.writeToFixed(),
+ '0' => self.state = .@"110",
+ '1' => self.state = .@"111",
+ '2' => self.state = .@"112",
+ '3' => self.state = .@"113",
+ '4' => self.state = .@"114",
+ '5' => self.state = .@"115",
+ '6' => self.state = .@"116",
+ '7' => self.state = .@"117",
+ '8' => self.state = .@"118",
+ '9' => self.state = .@"119",
else => self.state = .invalid,
},
- .@"12" => switch (c) {
- ';' => osc_12: {
- if (self.alloc == null) {
- log.warn("OSC 12 requires an allocator, but none was provided", .{});
- self.state = .invalid;
- break :osc_12;
- }
- self.command = .{ .color_operation = .{
- .op = .osc_12,
- } };
- self.state = .osc_color;
- self.buf_start = self.buf_idx;
- self.complete = true;
- },
+ .@"4",
+ .@"12",
+ .@"14",
+ .@"15",
+ .@"16",
+ .@"17",
+ .@"18",
+ .@"19",
+ .@"21",
+ .@"110",
+ .@"111",
+ .@"112",
+ .@"113",
+ .@"114",
+ .@"115",
+ .@"116",
+ .@"117",
+ .@"118",
+ .@"119",
+ => switch (c) {
+ ';' => if (self.ensureAllocator()) self.writeToFixed(),
else => self.state = .invalid,
},
.@"13" => switch (c) {
- ';' => osc_13: {
- if (self.alloc == null) {
- log.warn("OSC 13 requires an allocator, but none was provided", .{});
- self.state = .invalid;
- break :osc_13;
- }
- self.command = .{ .color_operation = .{
- .op = .osc_13,
- } };
- self.state = .osc_color;
- self.buf_start = self.buf_idx;
- self.complete = true;
- },
+ ';' => if (self.ensureAllocator()) self.writeToFixed(),
'3' => self.state = .@"133",
else => self.state = .invalid,
},
- .@"133" => switch (c) {
- ';' => self.state = .semantic_prompt,
- else => self.state = .invalid,
- },
-
- .@"14" => switch (c) {
- ';' => osc_14: {
- if (self.alloc == null) {
- log.warn("OSC 14 requires an allocator, but none was provided", .{});
- self.state = .invalid;
- break :osc_14;
- }
- self.command = .{ .color_operation = .{
- .op = .osc_14,
- } };
- self.state = .osc_color;
- self.buf_start = self.buf_idx;
- self.complete = true;
- },
- else => self.state = .invalid,
- },
-
- .@"15" => switch (c) {
- ';' => osc_15: {
- if (self.alloc == null) {
- log.warn("OSC 15 requires an allocator, but none was provided", .{});
- self.state = .invalid;
- break :osc_15;
- }
- self.command = .{ .color_operation = .{
- .op = .osc_15,
- } };
- self.state = .osc_color;
- self.buf_start = self.buf_idx;
- self.complete = true;
- },
- else => self.state = .invalid,
- },
-
- .@"16" => switch (c) {
- ';' => osc_16: {
- if (self.alloc == null) {
- log.warn("OSC 16 requires an allocator, but none was provided", .{});
- self.state = .invalid;
- break :osc_16;
- }
- self.command = .{ .color_operation = .{
- .op = .osc_16,
- } };
- self.state = .osc_color;
- self.buf_start = self.buf_idx;
- self.complete = true;
- },
- else => self.state = .invalid,
- },
-
- .@"17" => switch (c) {
- ';' => osc_17: {
- if (self.alloc == null) {
- log.warn("OSC 17 requires an allocator, but none was provided", .{});
- self.state = .invalid;
- break :osc_17;
- }
- self.command = .{ .color_operation = .{
- .op = .osc_17,
- } };
- self.state = .osc_color;
- self.buf_start = self.buf_idx;
- self.complete = true;
- },
- else => self.state = .invalid,
- },
-
- .@"18" => switch (c) {
- ';' => osc_18: {
- if (self.alloc == null) {
- log.warn("OSC 18 requires an allocator, but none was provided", .{});
- self.state = .invalid;
- break :osc_18;
- }
- self.command = .{ .color_operation = .{
- .op = .osc_18,
- } };
- self.state = .osc_color;
- self.buf_start = self.buf_idx;
- self.complete = true;
- },
- else => self.state = .invalid,
- },
-
- .@"19" => switch (c) {
- ';' => osc_19: {
- if (self.alloc == null) {
- log.warn("OSC 19 requires an allocator, but none was provided", .{});
- self.state = .invalid;
- break :osc_19;
- }
- self.command = .{ .color_operation = .{
- .op = .osc_19,
- } };
- self.state = .osc_color;
- self.buf_start = self.buf_idx;
- self.complete = true;
- },
- else => self.state = .invalid,
- },
-
- .osc_color => {},
-
.@"2" => switch (c) {
+ ';' => self.writeToFixed(),
'1' => self.state = .@"21",
'2' => self.state = .@"22",
- ';' => {
- self.command = .{ .change_window_title = undefined };
- self.complete = true;
- self.state = .string;
- self.temp_state = .{ .str = &self.command.change_window_title };
- self.buf_start = self.buf_idx;
- },
- else => self.state = .invalid,
- },
-
- .@"21" => switch (c) {
- ';' => kitty: {
- if (self.alloc == null) {
- log.info("OSC 21 requires an allocator, but none was provided", .{});
- self.state = .invalid;
- break :kitty;
- }
-
- self.command = .{
- .kitty_color_protocol = .{
- .list = .empty,
- },
- };
-
- self.temp_state = .{ .key = "" };
- self.state = .kitty_color_protocol_key;
- self.complete = true;
- self.buf_start = self.buf_idx;
- },
- else => self.state = .invalid,
- },
-
- .kitty_color_protocol_key => switch (c) {
- ';' => {
- self.temp_state = .{ .key = self.buf[self.buf_start .. self.buf_idx - 1] };
- self.endKittyColorProtocolOption(.key_only, false);
- self.state = .kitty_color_protocol_key;
- self.buf_start = self.buf_idx;
- },
- '=' => {
- self.temp_state = .{ .key = self.buf[self.buf_start .. self.buf_idx - 1] };
- self.state = .kitty_color_protocol_value;
- self.buf_start = self.buf_idx;
- },
- else => {},
- },
-
- .kitty_color_protocol_value => switch (c) {
- ';' => {
- self.endKittyColorProtocolOption(.key_and_value, false);
- self.state = .kitty_color_protocol_key;
- self.buf_start = self.buf_idx;
- },
- else => {},
- },
-
- .@"22" => switch (c) {
- ';' => {
- self.command = .{ .mouse_shape = undefined };
-
- self.state = .string;
- self.temp_state = .{ .str = &self.command.mouse_shape.value };
- self.buf_start = self.buf_idx;
- },
- else => self.state = .invalid,
- },
-
- .@"4" => switch (c) {
- ';' => osc_4: {
- if (self.alloc == null) {
- log.info("OSC 4 requires an allocator, but none was provided", .{});
- self.state = .invalid;
- break :osc_4;
- }
- self.command = .{
- .color_operation = .{
- .op = .osc_4,
- },
- };
- self.state = .osc_color;
- self.buf_start = self.buf_idx;
- self.complete = true;
- },
else => self.state = .invalid,
},
.@"5" => switch (c) {
- ';' => osc_5: {
- if (self.alloc == null) {
- log.info("OSC 5 requires an allocator, but none was provided", .{});
- self.state = .invalid;
- break :osc_5;
- }
- self.command = .{
- .color_operation = .{
- .op = .osc_5,
- },
- };
- self.state = .osc_color;
- self.buf_start = self.buf_idx;
- self.complete = true;
- },
+ ';' => if (self.ensureAllocator()) self.writeToFixed(),
'2' => self.state = .@"52",
else => self.state = .invalid,
},
- .@"52" => switch (c) {
- ';' => {
- self.command = .{ .clipboard_contents = undefined };
- self.state = .clipboard_kind;
- },
+ .@"6" => switch (c) {
+ '6' => self.state = .@"66",
else => self.state = .invalid,
},
- .clipboard_kind => switch (c) {
- ';' => {
- self.command.clipboard_contents.kind = 'c';
- self.temp_state = .{ .str = &self.command.clipboard_contents.data };
- self.buf_start = self.buf_idx;
- self.prepAllocableString();
-
- // See clipboard_kind_end
- self.complete = true;
- },
- else => {
- self.command.clipboard_contents.kind = c;
- self.state = .clipboard_kind_end;
- },
- },
-
- .clipboard_kind_end => switch (c) {
- ';' => {
- self.temp_state = .{ .str = &self.command.clipboard_contents.data };
- self.buf_start = self.buf_idx;
- self.prepAllocableString();
-
- // OSC 52 can have empty payloads (quoting xterm ctlseqs):
- // "If the second parameter is neither a base64 string nor ?,
- // then the selection is cleared."
- self.complete = true;
- },
+ .@"52",
+ .@"66",
+ => switch (c) {
+ ';' => self.writeToAllocating(),
else => self.state = .invalid,
},
.@"7" => switch (c) {
- ';' => {
- self.command = .{ .report_pwd = .{ .value = "" } };
- self.complete = true;
- self.state = .string;
- self.temp_state = .{ .str = &self.command.report_pwd.value };
- self.buf_start = self.buf_idx;
- },
+ ';' => self.writeToFixed(),
'7' => self.state = .@"77",
else => self.state = .invalid,
},
@@ -993,711 +633,19 @@ pub const Parser = struct {
else => self.state = .invalid,
},
- .@"777" => switch (c) {
- ';' => {
- self.state = .rxvt_extension;
- self.buf_start = self.buf_idx;
- },
+ .@"0",
+ .@"133",
+ .@"22",
+ .@"777",
+ .@"8",
+ .@"9",
+ => switch (c) {
+ ';' => self.writeToFixed(),
else => self.state = .invalid,
},
-
- .@"8" => switch (c) {
- ';' => {
- self.command = .{ .hyperlink_start = .{
- .uri = "",
- } };
-
- self.state = .hyperlink_param_key;
- self.buf_start = self.buf_idx;
- },
- else => self.state = .invalid,
- },
-
- .hyperlink_param_key => switch (c) {
- ';' => {
- self.complete = true;
- self.state = .hyperlink_uri;
- self.buf_start = self.buf_idx;
- },
- '=' => {
- self.temp_state = .{ .key = self.buf[self.buf_start .. self.buf_idx - 1] };
- self.state = .hyperlink_param_value;
- self.buf_start = self.buf_idx;
- },
- else => {},
- },
-
- .hyperlink_param_value => switch (c) {
- ':' => {
- self.endHyperlinkOptionValue();
- self.state = .hyperlink_param_key;
- self.buf_start = self.buf_idx;
- },
- ';' => {
- self.endHyperlinkOptionValue();
- self.state = .string;
- self.temp_state = .{ .str = &self.command.hyperlink_start.uri };
- self.buf_start = self.buf_idx;
- },
- else => {},
- },
-
- .hyperlink_uri => {},
-
- .rxvt_extension => switch (c) {
- 'a'...'z' => {},
- ';' => {
- const ext = self.buf[self.buf_start .. self.buf_idx - 1];
- if (!std.mem.eql(u8, ext, "notify")) {
- @branchHint(.cold);
- log.warn("unknown rxvt extension: {s}", .{ext});
- self.state = .invalid;
- return;
- }
-
- self.command = .{ .show_desktop_notification = undefined };
- self.buf_start = self.buf_idx;
- self.state = .notification_title;
- },
- else => self.state = .invalid,
- },
-
- .notification_title => switch (c) {
- ';' => {
- self.buf[self.buf_idx - 1] = 0;
- self.command.show_desktop_notification.title = self.buf[self.buf_start .. self.buf_idx - 1 :0];
- self.temp_state = .{ .str = &self.command.show_desktop_notification.body };
- self.buf_start = self.buf_idx;
- self.state = .string;
- },
- else => {},
- },
-
- .@"9" => switch (c) {
- ';' => {
- self.buf_start = self.buf_idx;
- self.state = .osc_9;
- },
- else => self.state = .invalid,
- },
-
- .osc_9 => switch (c) {
- '1' => {
- self.state = .conemu_sleep;
- // This will end up being either a ConEmu sleep OSC 9;1,
- // or a desktop notification OSC 9 that begins with '1', so
- // mark as complete.
- self.complete = true;
- },
- '2' => {
- self.state = .conemu_message_box;
- // This will end up being either a ConEmu message box OSC 9;2,
- // or a desktop notification OSC 9 that begins with '2', so
- // mark as complete.
- self.complete = true;
- },
- '3' => {
- self.state = .conemu_tab;
- // This will end up being either a ConEmu message box OSC 9;3,
- // or a desktop notification OSC 9 that begins with '3', so
- // mark as complete.
- self.complete = true;
- },
- '4' => {
- self.state = .conemu_progress_prestate;
- // This will end up being either a ConEmu progress report
- // OSC 9;4, or a desktop notification OSC 9 that begins with
- // '4', so mark as complete.
- self.complete = true;
- },
- '5' => {
- // Note that sending an OSC 9 desktop notification that
- // starts with 5 is impossible due to this.
- self.state = .swallow;
- self.command = .conemu_wait_input;
- self.complete = true;
- },
- '6' => {
- self.state = .conemu_guimacro;
- // This will end up being either a ConEmu GUI macro OSC 9;6,
- // or a desktop notification OSC 9 that begins with '6', so
- // mark as complete.
- self.complete = true;
- },
-
- // Todo: parse out other ConEmu operating system commands. Even
- // if we don't support them we probably don't want them showing
- // up as desktop notifications.
-
- else => self.showDesktopNotification(),
- },
-
- .conemu_sleep => switch (c) {
- ';' => {
- self.command = .{ .conemu_sleep = .{ .duration_ms = 100 } };
- self.buf_start = self.buf_idx;
- self.complete = true;
- self.state = .conemu_sleep_value;
- },
-
- // OSC 9;1 is a desktop
- // notification.
- else => self.showDesktopNotification(),
- },
-
- .conemu_sleep_value => switch (c) {
- else => self.complete = true,
- },
-
- .conemu_message_box => switch (c) {
- ';' => {
- self.command = .{ .conemu_show_message_box = undefined };
- self.temp_state = .{ .str = &self.command.conemu_show_message_box };
- self.buf_start = self.buf_idx;
- self.complete = true;
- self.prepAllocableString();
- },
-
- // OSC 9;2 is a desktop
- // notification.
- else => self.showDesktopNotification(),
- },
-
- .conemu_tab => switch (c) {
- ';' => {
- self.state = .conemu_tab_txt;
- self.command = .{ .conemu_change_tab_title = .reset };
- self.buf_start = self.buf_idx;
- self.complete = true;
- },
-
- // OSC 9;3 is a desktop
- // notification.
- else => self.showDesktopNotification(),
- },
-
- .conemu_tab_txt => {
- self.command = .{ .conemu_change_tab_title = .{ .value = undefined } };
- self.temp_state = .{ .str = &self.command.conemu_change_tab_title.value };
- self.complete = true;
- self.prepAllocableString();
- },
-
- .conemu_progress_prestate => switch (c) {
- ';' => {
- self.command = .{ .conemu_progress_report = .{
- .state = undefined,
- } };
- self.state = .conemu_progress_state;
- },
-
- // OSC 9;4 is a desktop
- // notification.
- else => self.showDesktopNotification(),
- },
-
- .conemu_progress_state => switch (c) {
- '0' => {
- self.command.conemu_progress_report.state = .remove;
- self.state = .swallow;
- self.complete = true;
- },
- '1' => {
- self.command.conemu_progress_report.state = .set;
- self.command.conemu_progress_report.progress = 0;
- self.state = .conemu_progress_prevalue;
- },
- '2' => {
- self.command.conemu_progress_report.state = .@"error";
- self.complete = true;
- self.state = .conemu_progress_prevalue;
- },
- '3' => {
- self.command.conemu_progress_report.state = .indeterminate;
- self.complete = true;
- self.state = .swallow;
- },
- '4' => {
- self.command.conemu_progress_report.state = .pause;
- self.complete = true;
- self.state = .conemu_progress_prevalue;
- },
-
- // OSC 9;4; is a desktop
- // notification.
- else => self.showDesktopNotification(),
- },
-
- .conemu_progress_prevalue => switch (c) {
- ';' => {
- self.state = .conemu_progress_value;
- },
-
- // OSC 9;4;<0-4> is a desktop
- // notification.
- else => self.showDesktopNotification(),
- },
-
- .conemu_progress_value => switch (c) {
- '0'...'9' => value: {
- // No matter what substate we're in, a number indicates
- // a completed ConEmu progress command.
- self.complete = true;
-
- // If we aren't a set substate, then we don't care
- // about the value.
- const p = &self.command.conemu_progress_report;
- switch (p.state) {
- .remove,
- .indeterminate,
- => break :value,
- .set,
- .@"error",
- .pause,
- => {},
- }
-
- if (p.state == .set)
- assert(p.progress != null)
- else if (p.progress == null)
- p.progress = 0;
-
- // If we're over 100% we're done.
- if (p.progress.? >= 100) break :value;
-
- // If we're over 10 then any new digit forces us to
- // be 100.
- if (p.progress.? >= 10)
- p.progress = 100
- else {
- const d = std.fmt.charToDigit(c, 10) catch 0;
- p.progress = @min(100, (p.progress.? * 10) + d);
- }
- },
-
- else => {
- self.state = .swallow;
- self.complete = true;
- },
- },
-
- .conemu_guimacro => switch (c) {
- ';' => {
- self.command = .{ .conemu_guimacro = undefined };
- self.temp_state = .{ .str = &self.command.conemu_guimacro };
- self.buf_start = self.buf_idx;
- self.state = .string;
- self.complete = true;
- },
-
- // OSC 9;6 is a desktop
- // notification.
- else => self.showDesktopNotification(),
- },
-
- .semantic_prompt => switch (c) {
- 'A' => {
- self.state = .semantic_option_start;
- self.command = .{ .prompt_start = .{} };
- self.complete = true;
- },
-
- 'B' => {
- self.state = .semantic_option_start;
- self.command = .{ .prompt_end = {} };
- self.complete = true;
- },
-
- 'C' => {
- self.state = .semantic_option_start;
- self.command = .{ .end_of_input = .{} };
- self.complete = true;
- },
-
- 'D' => {
- self.state = .semantic_exit_code_start;
- self.command = .{ .end_of_command = .{} };
- self.complete = true;
- },
-
- else => self.state = .invalid,
- },
-
- .semantic_option_start => switch (c) {
- ';' => {
- self.state = .semantic_option_key;
- self.buf_start = self.buf_idx;
- },
- else => self.state = .invalid,
- },
-
- .semantic_option_key => switch (c) {
- '=' => {
- self.temp_state = .{ .key = self.buf[self.buf_start .. self.buf_idx - 1] };
- self.state = .semantic_option_value;
- self.buf_start = self.buf_idx;
- },
- else => {},
- },
-
- .semantic_option_value => switch (c) {
- ';' => {
- self.endSemanticOptionValue();
- self.state = .semantic_option_key;
- self.buf_start = self.buf_idx;
- },
- else => {},
- },
-
- .semantic_exit_code_start => switch (c) {
- ';' => {
- // No longer complete, if ';' shows up we expect some code.
- self.complete = false;
- self.state = .semantic_exit_code;
- self.temp_state = .{ .num = 0 };
- self.buf_start = self.buf_idx;
- },
- else => self.state = .invalid,
- },
-
- .semantic_exit_code => switch (c) {
- '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' => {
- self.complete = true;
-
- const idx = self.buf_idx - self.buf_start;
- if (idx > 0) self.temp_state.num *|= 10;
- self.temp_state.num +|= c - '0';
- },
- ';' => {
- self.endSemanticExitCode();
- self.state = .semantic_option_key;
- self.buf_start = self.buf_idx;
- },
- else => self.state = .invalid,
- },
-
- .allocable_string => {
- const alloc = self.alloc.?;
- const list = self.buf_dynamic.?;
- list.append(alloc, c) catch {
- self.state = .invalid;
- return;
- };
-
- // Never consume buffer space for allocable strings
- self.buf_idx -= 1;
-
- // We can complete at any time
- self.complete = true;
- },
-
- .string => self.complete = true,
}
}
- fn showDesktopNotification(self: *Parser) void {
- self.command = .{ .show_desktop_notification = .{
- .title = "",
- .body = undefined,
- } };
-
- self.temp_state = .{ .str = &self.command.show_desktop_notification.body };
- self.state = .string;
- // Set as complete as we've already seen one character that should be
- // part of the notification. If we wait for another character to set
- // `complete` when the state is `.string` we won't be able to send any
- // single character notifications.
- self.complete = true;
- }
-
- fn prepAllocableString(self: *Parser) void {
- assert(self.buf_dynamic == null);
-
- // We need an allocator. If we don't have an allocator, we
- // pretend we're just a fixed buffer string and hope we fit!
- const alloc = self.alloc orelse {
- self.state = .string;
- return;
- };
-
- // Allocate our dynamic buffer
- const list = alloc.create(std.ArrayListUnmanaged(u8)) catch {
- self.state = .string;
- return;
- };
- list.* = .{};
-
- self.buf_dynamic = list;
- self.state = .allocable_string;
- }
-
- fn endHyperlink(self: *Parser) void {
- switch (self.command) {
- .hyperlink_start => |*v| {
- self.buf[self.buf_idx] = 0;
- const value = self.buf[self.buf_start..self.buf_idx :0];
- if (v.id == null and value.len == 0) {
- self.command = .{ .hyperlink_end = {} };
- return;
- }
-
- v.uri = value;
- },
-
- else => unreachable,
- }
- }
-
- fn endHyperlinkOptionValue(self: *Parser) void {
- const value: [:0]const u8 = if (self.buf_start == self.buf_idx)
- ""
- else buf: {
- self.buf[self.buf_idx - 1] = 0;
- break :buf self.buf[self.buf_start .. self.buf_idx - 1 :0];
- };
-
- if (mem.eql(u8, self.temp_state.key, "id")) {
- switch (self.command) {
- .hyperlink_start => |*v| {
- // We treat empty IDs as null ids so that we can
- // auto-assign.
- if (value.len > 0) v.id = value;
- },
- else => {},
- }
- } else log.info("unknown hyperlink option: {s}", .{self.temp_state.key});
- }
-
- fn endSemanticOptionValue(self: *Parser) void {
- const value = value: {
- self.buf[self.buf_idx] = 0;
- defer self.buf_idx += 1;
- break :value self.buf[self.buf_start..self.buf_idx :0];
- };
-
- if (mem.eql(u8, self.temp_state.key, "aid")) {
- switch (self.command) {
- .prompt_start => |*v| v.aid = value,
- else => {},
- }
- } else if (mem.eql(u8, self.temp_state.key, "cmdline")) {
- // https://sw.kovidgoyal.net/kitty/shell-integration/#notes-for-shell-developers
- switch (self.command) {
- .end_of_input => |*v| v.cmdline = string_encoding.printfQDecode(value) catch null,
- else => {},
- }
- } else if (mem.eql(u8, self.temp_state.key, "cmdline_url")) {
- // https://sw.kovidgoyal.net/kitty/shell-integration/#notes-for-shell-developers
- switch (self.command) {
- .end_of_input => |*v| v.cmdline = string_encoding.urlPercentDecode(value) catch null,
- else => {},
- }
- } else if (mem.eql(u8, self.temp_state.key, "redraw")) {
- // https://sw.kovidgoyal.net/kitty/shell-integration/#notes-for-shell-developers
- switch (self.command) {
- .prompt_start => |*v| {
- const valid = if (value.len == 1) valid: {
- switch (value[0]) {
- '0' => v.redraw = false,
- '1' => v.redraw = true,
- else => break :valid false,
- }
-
- break :valid true;
- } else false;
-
- if (!valid) {
- log.info("OSC 133 A invalid redraw value: {s}", .{value});
- }
- },
- else => {},
- }
- } else if (mem.eql(u8, self.temp_state.key, "special_key")) {
- // https://sw.kovidgoyal.net/kitty/shell-integration/#notes-for-shell-developers
- switch (self.command) {
- .prompt_start => |*v| {
- const valid = if (value.len == 1) valid: {
- switch (value[0]) {
- '0' => v.special_key = false,
- '1' => v.special_key = true,
- else => break :valid false,
- }
-
- break :valid true;
- } else false;
-
- if (!valid) {
- log.info("OSC 133 A invalid special_key value: {s}", .{value});
- }
- },
- else => {},
- }
- } else if (mem.eql(u8, self.temp_state.key, "click_events")) {
- // https://sw.kovidgoyal.net/kitty/shell-integration/#notes-for-shell-developers
- switch (self.command) {
- .prompt_start => |*v| {
- const valid = if (value.len == 1) valid: {
- switch (value[0]) {
- '0' => v.click_events = false,
- '1' => v.click_events = true,
- else => break :valid false,
- }
-
- break :valid true;
- } else false;
-
- if (!valid) {
- log.info("OSC 133 A invalid click_events value: {s}", .{value});
- }
- },
- else => {},
- }
- } else if (mem.eql(u8, self.temp_state.key, "k")) {
- // https://sw.kovidgoyal.net/kitty/shell-integration/#notes-for-shell-developers
- // The "k" marks the kind of prompt, or "primary" if we don't know.
- // This can be used to distinguish between the first (initial) prompt,
- // a continuation, etc.
- switch (self.command) {
- .prompt_start => |*v| if (value.len == 1) {
- v.kind = switch (value[0]) {
- 'c' => .continuation,
- 's' => .secondary,
- 'r' => .right,
- 'i' => .primary,
- else => .primary,
- };
- },
- else => {},
- }
- } else log.info("unknown semantic prompts option: {s}", .{self.temp_state.key});
- }
-
- fn endSemanticExitCode(self: *Parser) void {
- switch (self.command) {
- .end_of_command => |*v| v.exit_code = @truncate(self.temp_state.num),
- else => {},
- }
- }
-
- fn endString(self: *Parser) void {
- self.buf[self.buf_idx] = 0;
- defer self.buf_idx += 1;
- self.temp_state.str.* = self.buf[self.buf_start..self.buf_idx :0];
- }
-
- fn endConEmuSleepValue(self: *Parser) void {
- switch (self.command) {
- .conemu_sleep => |*v| v.duration_ms = value: {
- const str = self.buf[self.buf_start..self.buf_idx];
- if (str.len == 0) break :value 100;
-
- if (std.fmt.parseUnsigned(u16, str, 10)) |num| {
- break :value @min(num, 10_000);
- } else |_| {
- break :value 100;
- }
- },
- else => {},
- }
- }
-
- fn endKittyColorProtocolOption(self: *Parser, kind: enum { key_only, key_and_value }, final: bool) void {
- if (self.temp_state.key.len == 0) {
- @branchHint(.cold);
- log.warn("zero length key in kitty color protocol", .{});
- return;
- }
-
- const key = kitty_color.Kind.parse(self.temp_state.key) orelse {
- @branchHint(.cold);
- log.warn("unknown key in kitty color protocol: {s}", .{self.temp_state.key});
- return;
- };
-
- const value = value: {
- if (self.buf_start == self.buf_idx) break :value "";
- if (final) break :value std.mem.trim(u8, self.buf[self.buf_start..self.buf_idx], " ");
- break :value std.mem.trim(u8, self.buf[self.buf_start .. self.buf_idx - 1], " ");
- };
-
- switch (self.command) {
- .kitty_color_protocol => |*v| {
- // Cap our allocation amount for our list.
- if (v.list.items.len >= @as(usize, kitty_color.Kind.max) * 2) {
- @branchHint(.cold);
- self.state = .invalid;
- log.warn("exceeded limit for number of keys in kitty color protocol, ignoring", .{});
- return;
- }
-
- // Asserted when the command is set to kitty_color_protocol
- // that we have an allocator.
- const alloc = self.alloc.?;
-
- if (kind == .key_only or value.len == 0) {
- v.list.append(alloc, .{ .reset = key }) catch |err| {
- @branchHint(.cold);
- log.warn("unable to append kitty color protocol option: {}", .{err});
- return;
- };
- } else if (mem.eql(u8, "?", value)) {
- v.list.append(alloc, .{ .query = key }) catch |err| {
- @branchHint(.cold);
- log.warn("unable to append kitty color protocol option: {}", .{err});
- return;
- };
- } else {
- v.list.append(alloc, .{
- .set = .{
- .key = key,
- .color = RGB.parse(value) catch |err| switch (err) {
- error.InvalidFormat => {
- log.warn("invalid color format in kitty color protocol: {s}", .{value});
- return;
- },
- },
- },
- }) catch |err| {
- @branchHint(.cold);
- log.warn("unable to append kitty color protocol option: {}", .{err});
- return;
- };
- }
- },
- else => {},
- }
- }
-
- fn endOscColor(self: *Parser) void {
- const alloc = self.alloc.?;
- assert(self.command == .color_operation);
- const data = self.buf[self.buf_start..self.buf_idx];
- self.command.color_operation.requests = osc_color.parse(
- alloc,
- self.command.color_operation.op,
- data,
- ) catch |err| list: {
- log.info(
- "failed to parse OSC color request err={} data={s}",
- .{ err, data },
- );
- break :list .{};
- };
- }
-
- fn endAllocableString(self: *Parser) void {
- const alloc = self.alloc.?;
- const list = self.buf_dynamic.?;
- list.append(alloc, 0) catch {
- @branchHint(.cold);
- log.warn("allocation failed on allocable string termination", .{});
- self.temp_state.str.* = "";
- return;
- };
-
- self.temp_state.str.* = list.items[0 .. list.items.len - 1 :0];
- }
-
/// End the sequence and return the command, if any. If the return value
/// is null, then no valid command was found. The optional terminator_ch
/// is the final character in the OSC sequence. This is used to determine
@@ -1706,1634 +654,68 @@ pub const Parser = struct {
/// The returned pointer is only valid until the next call to the parser.
/// Callers should copy out any data they wish to retain across calls.
pub fn end(self: *Parser, terminator_ch: ?u8) ?*Command {
- if (!self.complete) {
- if (comptime !builtin.is_test) log.warn(
- "invalid OSC command: {s}",
- .{self.buf[0..self.buf_idx]},
- );
- return null;
- }
+ return switch (self.state) {
+ .start => null,
- // Other cleanup we may have to do depending on state.
- switch (self.state) {
- .allocable_string => self.endAllocableString(),
- .semantic_exit_code => self.endSemanticExitCode(),
- .semantic_option_value => self.endSemanticOptionValue(),
- .hyperlink_uri => self.endHyperlink(),
- .string => self.endString(),
- .conemu_sleep_value => self.endConEmuSleepValue(),
- .kitty_color_protocol_key => self.endKittyColorProtocolOption(.key_only, true),
- .kitty_color_protocol_value => self.endKittyColorProtocolOption(.key_and_value, true),
- .osc_color => self.endOscColor(),
+ .invalid => null,
- // 104 abruptly ended turns into a reset palette command.
- .@"104" => {
- self.command = .{ .color_operation = .{
- .op = .osc_104,
- } };
- self.state = .osc_color;
- self.buf_start = self.buf_idx;
- self.endOscColor();
- },
+ .@"0",
+ .@"2",
+ => parsers.change_window_title.parse(self, terminator_ch),
- // We received OSC 9;X ST, but nothing else, finish off as a
- // desktop notification with "X" as the body.
- .conemu_sleep,
- .conemu_message_box,
- .conemu_tab,
- .conemu_progress_prestate,
- .conemu_progress_state,
- .conemu_guimacro,
- => {
- self.showDesktopNotification();
- self.endString();
- },
+ .@"1" => parsers.change_window_icon.parse(self, terminator_ch),
- // A ConEmu progress report that has reached these states is
- // complete, don't do anything to them.
- .conemu_progress_prevalue,
- .conemu_progress_value,
- => {},
+ .@"4",
+ .@"5",
+ .@"10",
+ .@"11",
+ .@"12",
+ .@"13",
+ .@"14",
+ .@"15",
+ .@"16",
+ .@"17",
+ .@"18",
+ .@"19",
+ .@"104",
+ .@"110",
+ .@"111",
+ .@"112",
+ .@"113",
+ .@"114",
+ .@"115",
+ .@"116",
+ .@"117",
+ .@"118",
+ .@"119",
+ => parsers.color.parse(self, terminator_ch),
- else => {},
- }
+ .@"7" => parsers.report_pwd.parse(self, terminator_ch),
- switch (self.command) {
- .kitty_color_protocol => |*c| c.terminator = .init(terminator_ch),
- .color_operation => |*c| c.terminator = .init(terminator_ch),
- else => {},
- }
+ .@"8" => parsers.hyperlink.parse(self, terminator_ch),
- return &self.command;
+ .@"9" => parsers.osc9.parse(self, terminator_ch),
+
+ .@"21" => parsers.kitty_color.parse(self, terminator_ch),
+
+ .@"22" => parsers.mouse_shape.parse(self, terminator_ch),
+
+ .@"52" => parsers.clipboard_operation.parse(self, terminator_ch),
+
+ .@"6" => null,
+
+ .@"66" => parsers.kitty_text_sizing.parse(self, terminator_ch),
+
+ .@"77" => null,
+
+ .@"133" => parsers.semantic_prompt.parse(self, terminator_ch),
+
+ .@"777" => parsers.rxvt_extension.parse(self, terminator_ch),
+ };
}
};
test {
- _ = osc_color;
-}
-
-test "OSC 0: change_window_title" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
- p.next('0');
- p.next(';');
- p.next('a');
- p.next('b');
- const cmd = p.end(null).?.*;
- try testing.expect(cmd == .change_window_title);
- try testing.expectEqualStrings("ab", cmd.change_window_title);
-}
-
-test "OSC 0: longer than buffer" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "0;" ++ "a" ** (Parser.MAX_BUF + 2);
- for (input) |ch| p.next(ch);
-
- try testing.expect(p.end(null) == null);
- try testing.expect(p.complete == false);
-}
-
-test "OSC 0: one shorter than buffer length" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const prefix = "0;";
- const title = "a" ** (Parser.MAX_BUF - prefix.len - 1);
- const input = prefix ++ title;
- for (input) |ch| p.next(ch);
-
- const cmd = p.end(null).?.*;
- try testing.expect(cmd == .change_window_title);
- try testing.expectEqualStrings(title, cmd.change_window_title);
-}
-
-test "OSC 0: exactly at buffer length" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const prefix = "0;";
- const title = "a" ** (Parser.MAX_BUF - prefix.len);
- const input = prefix ++ title;
- for (input) |ch| p.next(ch);
-
- // This should be null because we always reserve space for a null terminator.
- try testing.expect(p.end(null) == null);
- try testing.expect(p.complete == false);
-}
-
-test "OSC 1: change_window_icon" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
- p.next('1');
- p.next(';');
- p.next('a');
- p.next('b');
- const cmd = p.end(null).?.*;
- try testing.expect(cmd == .change_window_icon);
- try testing.expectEqualStrings("ab", cmd.change_window_icon);
-}
-
-test "OSC 2: change_window_title with 2" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
- p.next('2');
- p.next(';');
- p.next('a');
- p.next('b');
- const cmd = p.end(null).?.*;
- try testing.expect(cmd == .change_window_title);
- try testing.expectEqualStrings("ab", cmd.change_window_title);
-}
-
-test "OSC 2: change_window_title with utf8" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
- p.next('2');
- p.next(';');
- // 'โ' EM DASH U+2014 (E2 80 94)
- p.next(0xE2);
- p.next(0x80);
- p.next(0x94);
-
- p.next(' ');
- // 'โ' HYPHEN U+2010 (E2 80 90)
- // Intententionally chosen to conflict with the 0x90 C1 control
- p.next(0xE2);
- p.next(0x80);
- p.next(0x90);
- const cmd = p.end(null).?.*;
- try testing.expect(cmd == .change_window_title);
- try testing.expectEqualStrings("โ โ", cmd.change_window_title);
-}
-
-test "OSC 2: change_window_title empty" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
- p.next('2');
- p.next(';');
- const cmd = p.end(null).?.*;
- try testing.expect(cmd == .change_window_title);
- try testing.expectEqualStrings("", cmd.change_window_title);
-}
-
-test "OSC 4: empty param" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "4;;";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end('\x1b');
- try testing.expect(cmd == null);
-}
-
-// See src/terminal/osc/color.zig for more OSC 4 tests.
-
-// See src/terminal/osc/color.zig for OSC 5 tests.
-
-test "OSC 7: report pwd" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "7;file:///tmp/example";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end(null).?.*;
- try testing.expect(cmd == .report_pwd);
- try testing.expectEqualStrings("file:///tmp/example", cmd.report_pwd.value);
-}
-
-test "OSC 7: report pwd empty" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "7;";
- for (input) |ch| p.next(ch);
- const cmd = p.end(null).?.*;
- try testing.expect(cmd == .report_pwd);
- try testing.expectEqualStrings("", cmd.report_pwd.value);
-}
-
-test "OSC 8: hyperlink" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "8;;http://example.com";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end('\x1b').?.*;
- try testing.expect(cmd == .hyperlink_start);
- try testing.expectEqualStrings(cmd.hyperlink_start.uri, "http://example.com");
-}
-
-test "OSC 8: hyperlink with id set" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "8;id=foo;http://example.com";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end('\x1b').?.*;
- try testing.expect(cmd == .hyperlink_start);
- try testing.expectEqualStrings(cmd.hyperlink_start.id.?, "foo");
- try testing.expectEqualStrings(cmd.hyperlink_start.uri, "http://example.com");
-}
-
-test "OSC 8: hyperlink with empty id" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "8;id=;http://example.com";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end('\x1b').?.*;
- try testing.expect(cmd == .hyperlink_start);
- try testing.expectEqual(null, cmd.hyperlink_start.id);
- try testing.expectEqualStrings(cmd.hyperlink_start.uri, "http://example.com");
-}
-
-test "OSC 8: hyperlink with incomplete key" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "8;id;http://example.com";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end('\x1b').?.*;
- try testing.expect(cmd == .hyperlink_start);
- try testing.expectEqual(null, cmd.hyperlink_start.id);
- try testing.expectEqualStrings(cmd.hyperlink_start.uri, "http://example.com");
-}
-
-test "OSC 8: hyperlink with empty key" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "8;=value;http://example.com";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end('\x1b').?.*;
- try testing.expect(cmd == .hyperlink_start);
- try testing.expectEqual(null, cmd.hyperlink_start.id);
- try testing.expectEqualStrings(cmd.hyperlink_start.uri, "http://example.com");
-}
-
-test "OSC 8: hyperlink with empty key and id" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "8;=value:id=foo;http://example.com";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end('\x1b').?.*;
- try testing.expect(cmd == .hyperlink_start);
- try testing.expectEqualStrings(cmd.hyperlink_start.id.?, "foo");
- try testing.expectEqualStrings(cmd.hyperlink_start.uri, "http://example.com");
-}
-
-test "OSC 8: hyperlink with empty uri" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "8;id=foo;";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end('\x1b');
- try testing.expect(cmd == null);
-}
-
-test "OSC 8: hyperlink end" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "8;;";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end('\x1b').?.*;
- try testing.expect(cmd == .hyperlink_end);
-}
-
-test "OSC 9: show desktop notification" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "9;Hello world";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end('\x1b').?.*;
- try testing.expect(cmd == .show_desktop_notification);
- try testing.expectEqualStrings("", cmd.show_desktop_notification.title);
- try testing.expectEqualStrings("Hello world", cmd.show_desktop_notification.body);
-}
-
-test "OSC 9: show single character desktop notification" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "9;H";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end('\x1b').?.*;
- try testing.expect(cmd == .show_desktop_notification);
- try testing.expectEqualStrings("", cmd.show_desktop_notification.title);
- try testing.expectEqualStrings("H", cmd.show_desktop_notification.body);
-}
-
-test "OSC 9;1: ConEmu sleep" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "9;1;420";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end('\x1b').?.*;
-
- try testing.expect(cmd == .conemu_sleep);
- try testing.expectEqual(420, cmd.conemu_sleep.duration_ms);
-}
-
-test "OSC 9;1: ConEmu sleep with no value default to 100ms" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "9;1;";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end('\x1b').?.*;
-
- try testing.expect(cmd == .conemu_sleep);
- try testing.expectEqual(100, cmd.conemu_sleep.duration_ms);
-}
-
-test "OSC 9;1: conemu sleep cannot exceed 10000ms" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "9;1;12345";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end('\x1b').?.*;
-
- try testing.expect(cmd == .conemu_sleep);
- try testing.expectEqual(10000, cmd.conemu_sleep.duration_ms);
-}
-
-test "OSC 9;1: conemu sleep invalid input" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "9;1;foo";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end('\x1b').?.*;
-
- try testing.expect(cmd == .conemu_sleep);
- try testing.expectEqual(100, cmd.conemu_sleep.duration_ms);
-}
-
-test "OSC 9;1: conemu sleep -> desktop notification 1" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "9;1";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end('\x1b').?.*;
-
- try testing.expect(cmd == .show_desktop_notification);
- try testing.expectEqualStrings("1", cmd.show_desktop_notification.body);
-}
-
-test "OSC 9;1: conemu sleep -> desktop notification 2" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "9;1a";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end('\x1b').?.*;
-
- try testing.expect(cmd == .show_desktop_notification);
- try testing.expectEqualStrings("1a", cmd.show_desktop_notification.body);
-}
-
-test "OSC 9;2: ConEmu message box" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "9;2;hello world";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end('\x1b').?.*;
- try testing.expect(cmd == .conemu_show_message_box);
- try testing.expectEqualStrings("hello world", cmd.conemu_show_message_box);
-}
-
-test "OSC 9;2: ConEmu message box invalid input" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "9;2";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end('\x1b').?.*;
- try testing.expect(cmd == .show_desktop_notification);
- try testing.expectEqualStrings("2", cmd.show_desktop_notification.body);
-}
-
-test "OSC 9;2: ConEmu message box empty message" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "9;2;";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end('\x1b').?.*;
- try testing.expect(cmd == .conemu_show_message_box);
- try testing.expectEqualStrings("", cmd.conemu_show_message_box);
-}
-
-test "OSC 9;2: ConEmu message box spaces only message" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "9;2; ";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end('\x1b').?.*;
- try testing.expect(cmd == .conemu_show_message_box);
- try testing.expectEqualStrings(" ", cmd.conemu_show_message_box);
-}
-
-test "OSC 9;2: message box -> desktop notification 1" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "9;2";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end('\x1b').?.*;
-
- try testing.expect(cmd == .show_desktop_notification);
- try testing.expectEqualStrings("2", cmd.show_desktop_notification.body);
-}
-
-test "OSC 9;2: message box -> desktop notification 2" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "9;2a";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end('\x1b').?.*;
-
- try testing.expect(cmd == .show_desktop_notification);
- try testing.expectEqualStrings("2a", cmd.show_desktop_notification.body);
-}
-
-test "OSC 9;3: ConEmu change tab title" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "9;3;foo bar";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end('\x1b').?.*;
- try testing.expect(cmd == .conemu_change_tab_title);
- try testing.expectEqualStrings("foo bar", cmd.conemu_change_tab_title.value);
-}
-
-test "OSC 9;3: ConEmu change tab title reset" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "9;3;";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end('\x1b').?.*;
-
- const expected_command: Command = .{ .conemu_change_tab_title = .reset };
- try testing.expectEqual(expected_command, cmd);
-}
-
-test "OSC 9;3: ConEmu change tab title spaces only" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "9;3; ";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end('\x1b').?.*;
-
- try testing.expect(cmd == .conemu_change_tab_title);
- try testing.expectEqualStrings(" ", cmd.conemu_change_tab_title.value);
-}
-
-test "OSC 9;3: change tab title -> desktop notification 1" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "9;3";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end('\x1b').?.*;
-
- try testing.expect(cmd == .show_desktop_notification);
- try testing.expectEqualStrings("3", cmd.show_desktop_notification.body);
-}
-
-test "OSC 9;3: message box -> desktop notification 2" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "9;3a";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end('\x1b').?.*;
-
- try testing.expect(cmd == .show_desktop_notification);
- try testing.expectEqualStrings("3a", cmd.show_desktop_notification.body);
-}
-
-test "OSC 9;4: ConEmu progress set" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "9;4;1;100";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end('\x1b').?.*;
- try testing.expect(cmd == .conemu_progress_report);
- try testing.expect(cmd.conemu_progress_report.state == .set);
- try testing.expect(cmd.conemu_progress_report.progress == 100);
-}
-
-test "OSC 9;4: ConEmu progress set overflow" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "9;4;1;900";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end('\x1b').?.*;
- try testing.expect(cmd == .conemu_progress_report);
- try testing.expect(cmd.conemu_progress_report.state == .set);
- try testing.expectEqual(100, cmd.conemu_progress_report.progress);
-}
-
-test "OSC 9;4: ConEmu progress set single digit" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "9;4;1;9";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end('\x1b').?.*;
- try testing.expect(cmd == .conemu_progress_report);
- try testing.expect(cmd.conemu_progress_report.state == .set);
- try testing.expect(cmd.conemu_progress_report.progress == 9);
-}
-
-test "OSC 9;4: ConEmu progress set double digit" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "9;4;1;94";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end('\x1b').?.*;
- try testing.expect(cmd == .conemu_progress_report);
- try testing.expect(cmd.conemu_progress_report.state == .set);
- try testing.expectEqual(94, cmd.conemu_progress_report.progress);
-}
-
-test "OSC 9;4: ConEmu progress set extra semicolon ignored" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "9;4;1;100";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end('\x1b').?.*;
- try testing.expect(cmd == .conemu_progress_report);
- try testing.expect(cmd.conemu_progress_report.state == .set);
- try testing.expectEqual(100, cmd.conemu_progress_report.progress);
-}
-
-test "OSC 9;4: ConEmu progress remove with no progress" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "9;4;0;";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end('\x1b').?.*;
- try testing.expect(cmd == .conemu_progress_report);
- try testing.expect(cmd.conemu_progress_report.state == .remove);
- try testing.expect(cmd.conemu_progress_report.progress == null);
-}
-
-test "OSC 9;4: ConEmu progress remove with double semicolon" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "9;4;0;;";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end('\x1b').?.*;
- try testing.expect(cmd == .conemu_progress_report);
- try testing.expect(cmd.conemu_progress_report.state == .remove);
- try testing.expect(cmd.conemu_progress_report.progress == null);
-}
-
-test "OSC 9;4: ConEmu progress remove ignores progress" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "9;4;0;100";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end('\x1b').?.*;
- try testing.expect(cmd == .conemu_progress_report);
- try testing.expect(cmd.conemu_progress_report.state == .remove);
- try testing.expect(cmd.conemu_progress_report.progress == null);
-}
-
-test "OSC 9;4: ConEmu progress remove extra semicolon" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "9;4;0;100;";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end('\x1b').?.*;
- try testing.expect(cmd == .conemu_progress_report);
- try testing.expect(cmd.conemu_progress_report.state == .remove);
-}
-
-test "OSC 9;4: ConEmu progress error" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "9;4;2";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end('\x1b').?.*;
- try testing.expect(cmd == .conemu_progress_report);
- try testing.expect(cmd.conemu_progress_report.state == .@"error");
- try testing.expect(cmd.conemu_progress_report.progress == null);
-}
-
-test "OSC 9;4: ConEmu progress error with progress" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "9;4;2;100";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end('\x1b').?.*;
- try testing.expect(cmd == .conemu_progress_report);
- try testing.expect(cmd.conemu_progress_report.state == .@"error");
- try testing.expect(cmd.conemu_progress_report.progress == 100);
-}
-
-test "OSC 9;4: progress pause" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "9;4;4";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end('\x1b').?.*;
- try testing.expect(cmd == .conemu_progress_report);
- try testing.expect(cmd.conemu_progress_report.state == .pause);
- try testing.expect(cmd.conemu_progress_report.progress == null);
-}
-
-test "OSC 9;4: ConEmu progress pause with progress" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "9;4;4;100";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end('\x1b').?.*;
- try testing.expect(cmd == .conemu_progress_report);
- try testing.expect(cmd.conemu_progress_report.state == .pause);
- try testing.expect(cmd.conemu_progress_report.progress == 100);
-}
-
-test "OSC 9;4: progress -> desktop notification 1" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "9;4";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end('\x1b').?.*;
-
- try testing.expect(cmd == .show_desktop_notification);
- try testing.expectEqualStrings("4", cmd.show_desktop_notification.body);
-}
-
-test "OSC 9;4: progress -> desktop notification 2" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "9;4;";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end('\x1b').?.*;
-
- try testing.expect(cmd == .show_desktop_notification);
- try testing.expectEqualStrings("4;", cmd.show_desktop_notification.body);
-}
-
-test "OSC 9;4: progress -> desktop notification 3" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "9;4;5";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end('\x1b').?.*;
-
- try testing.expect(cmd == .show_desktop_notification);
- try testing.expectEqualStrings("4;5", cmd.show_desktop_notification.body);
-}
-
-test "OSC 9;4: progress -> desktop notification 4" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "9;4;5a";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end('\x1b').?.*;
-
- try testing.expect(cmd == .show_desktop_notification);
- try testing.expectEqualStrings("4;5a", cmd.show_desktop_notification.body);
-}
-
-test "OSC 9;5: ConEmu wait input" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "9;5";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end('\x1b').?.*;
- try testing.expect(cmd == .conemu_wait_input);
-}
-
-test "OSC 9;5: ConEmu wait ignores trailing characters" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "9;5;foo";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end('\x1b').?.*;
- try testing.expect(cmd == .conemu_wait_input);
-}
-
-test "OSC 9;6: ConEmu guimacro 1" {
- const testing = std.testing;
-
- var p: Parser = .init(testing.allocator);
- defer p.deinit();
-
- const input = "9;6;a";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end('\x1b').?.*;
- try testing.expect(cmd == .conemu_guimacro);
- try testing.expectEqualStrings("a", cmd.conemu_guimacro);
-}
-
-test "OSC: 9;6: ConEmu guimacro 2" {
- const testing = std.testing;
-
- var p: Parser = .init(testing.allocator);
- defer p.deinit();
-
- const input = "9;6;ab";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end('\x1b').?.*;
- try testing.expect(cmd == .conemu_guimacro);
- try testing.expectEqualStrings("ab", cmd.conemu_guimacro);
-}
-
-test "OSC: 9;6: ConEmu guimacro 3 incomplete -> desktop notification" {
- const testing = std.testing;
-
- var p: Parser = .init(testing.allocator);
- defer p.deinit();
-
- const input = "9;6";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end('\x1b').?.*;
- try testing.expect(cmd == .show_desktop_notification);
- try testing.expectEqualStrings("6", cmd.show_desktop_notification.body);
-}
-
-// See src/terminal/osc/color.zig for OSC 10 tests.
-
-// See src/terminal/osc/color.zig for OSC 11 tests.
-
-// See src/terminal/osc/color.zig for OSC 12 tests.
-
-// See src/terminal/osc/color.zig for OSC 13 tests.
-
-// See src/terminal/osc/color.zig for OSC 14 tests.
-
-// See src/terminal/osc/color.zig for OSC 15 tests.
-
-// See src/terminal/osc/color.zig for OSC 16 tests.
-
-// See src/terminal/osc/color.zig for OSC 17 tests.
-
-// See src/terminal/osc/color.zig for OSC 18 tests.
-
-// See src/terminal/osc/color.zig for OSC 19 tests.
-
-test "OSC 21: kitty color protocol" {
- const testing = std.testing;
- const Kind = kitty_color.Kind;
-
- var p: Parser = .init(testing.allocator);
- 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";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end('\x1b').?.*;
- try testing.expect(cmd == .kitty_color_protocol);
- try testing.expectEqual(@as(usize, 9), cmd.kitty_color_protocol.list.items.len);
- {
- const item = cmd.kitty_color_protocol.list.items[0];
- try testing.expect(item == .query);
- try testing.expectEqual(Kind{ .special = .foreground }, item.query);
- }
- {
- const item = cmd.kitty_color_protocol.list.items[1];
- try testing.expect(item == .set);
- try testing.expectEqual(Kind{ .special = .background }, item.set.key);
- try testing.expectEqual(@as(u8, 0xf0), item.set.color.r);
- try testing.expectEqual(@as(u8, 0xf8), item.set.color.g);
- try testing.expectEqual(@as(u8, 0xff), item.set.color.b);
- }
- {
- const item = cmd.kitty_color_protocol.list.items[2];
- try testing.expect(item == .set);
- try testing.expectEqual(Kind{ .special = .cursor }, item.set.key);
- try testing.expectEqual(@as(u8, 0xf0), item.set.color.r);
- try testing.expectEqual(@as(u8, 0xf8), item.set.color.g);
- try testing.expectEqual(@as(u8, 0xff), item.set.color.b);
- }
- {
- const item = cmd.kitty_color_protocol.list.items[3];
- try testing.expect(item == .reset);
- try testing.expectEqual(Kind{ .special = .cursor_text }, item.reset);
- }
- {
- const item = cmd.kitty_color_protocol.list.items[4];
- try testing.expect(item == .reset);
- try testing.expectEqual(Kind{ .special = .visual_bell }, item.reset);
- }
- {
- const item = cmd.kitty_color_protocol.list.items[5];
- try testing.expect(item == .query);
- try testing.expectEqual(Kind{ .special = .selection_background }, item.query);
- }
- {
- const item = cmd.kitty_color_protocol.list.items[6];
- try testing.expect(item == .set);
- try testing.expectEqual(Kind{ .special = .selection_background }, item.set.key);
- try testing.expectEqual(@as(u8, 0xaa), item.set.color.r);
- try testing.expectEqual(@as(u8, 0xbb), item.set.color.g);
- try testing.expectEqual(@as(u8, 0xcc), item.set.color.b);
- }
- {
- const item = cmd.kitty_color_protocol.list.items[7];
- try testing.expect(item == .query);
- try testing.expectEqual(Kind{ .palette = 2 }, item.query);
- }
- {
- const item = cmd.kitty_color_protocol.list.items[8];
- try testing.expect(item == .set);
- try testing.expectEqual(Kind{ .palette = 3 }, item.set.key);
- try testing.expectEqual(@as(u8, 0xff), item.set.color.r);
- try testing.expectEqual(@as(u8, 0xff), item.set.color.g);
- try testing.expectEqual(@as(u8, 0xff), item.set.color.b);
- }
-}
-
-test "OSC 21: kitty color protocol without allocator" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
- defer p.deinit();
-
- const input = "21;foreground=?";
- for (input) |ch| p.next(ch);
- try testing.expect(p.end('\x1b') == null);
-}
-
-test "OSC 21: kitty color protocol double reset" {
- const testing = std.testing;
-
- var p: Parser = .init(testing.allocator);
- 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";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end('\x1b').?.*;
- try testing.expect(cmd == .kitty_color_protocol);
-
- p.reset();
- p.reset();
-}
-
-test "OSC 21: kitty color protocol reset after invalid" {
- const testing = std.testing;
-
- var p: Parser = .init(testing.allocator);
- 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";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end('\x1b').?.*;
- try testing.expect(cmd == .kitty_color_protocol);
-
- p.reset();
-
- try testing.expectEqual(Parser.State.empty, p.state);
- p.next('X');
- try testing.expectEqual(Parser.State.invalid, p.state);
-
- p.reset();
-}
-
-test "OSC 21: kitty color protocol no key" {
- const testing = std.testing;
-
- var p: Parser = .init(testing.allocator);
- defer p.deinit();
-
- const input = "21;";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end('\x1b').?.*;
- try testing.expect(cmd == .kitty_color_protocol);
- try testing.expectEqual(0, cmd.kitty_color_protocol.list.items.len);
-}
-
-test "OSC 22: pointer cursor" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "22;pointer";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end(null).?.*;
- try testing.expect(cmd == .mouse_shape);
- try testing.expectEqualStrings("pointer", cmd.mouse_shape.value);
-}
-
-test "OSC 52: get/set clipboard" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "52;s;?";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end(null).?.*;
- try testing.expect(cmd == .clipboard_contents);
- try testing.expect(cmd.clipboard_contents.kind == 's');
- try testing.expectEqualStrings("?", cmd.clipboard_contents.data);
-}
-
-test "OSC 52: get/set clipboard (optional parameter)" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "52;;?";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end(null).?.*;
- try testing.expect(cmd == .clipboard_contents);
- try testing.expect(cmd.clipboard_contents.kind == 'c');
- try testing.expectEqualStrings("?", cmd.clipboard_contents.data);
-}
-
-test "OSC 52: get/set clipboard with allocator" {
- const testing = std.testing;
-
- var p: Parser = .init(testing.allocator);
- defer p.deinit();
-
- const input = "52;s;?";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end(null).?.*;
- try testing.expect(cmd == .clipboard_contents);
- try testing.expect(cmd.clipboard_contents.kind == 's');
- try testing.expectEqualStrings("?", cmd.clipboard_contents.data);
-}
-
-test "OSC 52: clear clipboard" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
- defer p.deinit();
-
- const input = "52;;";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end(null).?.*;
- try testing.expect(cmd == .clipboard_contents);
- try testing.expect(cmd.clipboard_contents.kind == 'c');
- try testing.expectEqualStrings("", cmd.clipboard_contents.data);
-}
-
-// See src/terminal/osc/color.zig for OSC 104 tests.
-
-// See src/terminal/osc/color.zig for OSC 105 tests.
-
-// See src/terminal/osc/color.zig for OSC 110 tests.
-
-// See src/terminal/osc/color.zig for OSC 111 tests.
-
-// See src/terminal/osc/color.zig for OSC 112 tests.
-
-// See src/terminal/osc/color.zig for OSC 113 tests.
-
-// See src/terminal/osc/color.zig for OSC 114 tests.
-
-// See src/terminal/osc/color.zig for OSC 115 tests.
-
-// See src/terminal/osc/color.zig for OSC 116 tests.
-
-// See src/terminal/osc/color.zig for OSC 117 tests.
-
-// See src/terminal/osc/color.zig for OSC 118 tests.
-
-// See src/terminal/osc/color.zig for OSC 119 tests.
-
-test "OSC 133: prompt_start" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "133;A";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end(null).?.*;
- try testing.expect(cmd == .prompt_start);
- try testing.expect(cmd.prompt_start.aid == null);
- try testing.expect(cmd.prompt_start.redraw);
-}
-
-test "OSC 133: prompt_start with single option" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "133;A;aid=14";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end(null).?.*;
- try testing.expect(cmd == .prompt_start);
- try testing.expectEqualStrings("14", cmd.prompt_start.aid.?);
-}
-
-test "OSC 133: prompt_start with redraw disabled" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "133;A;redraw=0";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end(null).?.*;
- try testing.expect(cmd == .prompt_start);
- try testing.expect(!cmd.prompt_start.redraw);
-}
-
-test "OSC 133: prompt_start with redraw invalid value" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "133;A;redraw=42";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end(null).?.*;
- try testing.expect(cmd == .prompt_start);
- try testing.expect(cmd.prompt_start.redraw);
- try testing.expect(cmd.prompt_start.kind == .primary);
-}
-
-test "OSC 133: prompt_start with continuation" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "133;A;k=c";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end(null).?.*;
- try testing.expect(cmd == .prompt_start);
- try testing.expect(cmd.prompt_start.kind == .continuation);
-}
-
-test "OSC 133: prompt_start with secondary" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "133;A;k=s";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end(null).?.*;
- try testing.expect(cmd == .prompt_start);
- try testing.expect(cmd.prompt_start.kind == .secondary);
-}
-
-test "OSC 133: prompt_start with special_key" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "133;A;special_key=1";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end(null).?.*;
- try testing.expect(cmd == .prompt_start);
- try testing.expect(cmd.prompt_start.special_key == true);
-}
-
-test "OSC 133: prompt_start with special_key invalid" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "133;A;special_key=bobr";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end(null).?.*;
- try testing.expect(cmd == .prompt_start);
- try testing.expect(cmd.prompt_start.special_key == false);
-}
-
-test "OSC 133: prompt_start with special_key 0" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "133;A;special_key=0";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end(null).?.*;
- try testing.expect(cmd == .prompt_start);
- try testing.expect(cmd.prompt_start.special_key == false);
-}
-
-test "OSC 133: prompt_start with special_key empty" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "133;A;special_key=";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end(null).?.*;
- try testing.expect(cmd == .prompt_start);
- try testing.expect(cmd.prompt_start.special_key == false);
-}
-
-test "OSC 133: prompt_start with click_events true" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "133;A;click_events=1";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end(null).?.*;
- try testing.expect(cmd == .prompt_start);
- try testing.expect(cmd.prompt_start.click_events == true);
-}
-
-test "OSC 133: prompt_start with click_events false" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "133;A;click_events=0";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end(null).?.*;
- try testing.expect(cmd == .prompt_start);
- try testing.expect(cmd.prompt_start.click_events == false);
-}
-
-test "OSC 133: prompt_start with click_events empty" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "133;A;click_events=";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end(null).?.*;
- try testing.expect(cmd == .prompt_start);
- try testing.expect(cmd.prompt_start.click_events == false);
-}
-
-test "OSC 133: end_of_command no exit code" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "133;D";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end(null).?.*;
- try testing.expect(cmd == .end_of_command);
-}
-
-test "OSC 133: end_of_command with exit code" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "133;D;25";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end(null).?.*;
- try testing.expect(cmd == .end_of_command);
- try testing.expectEqual(@as(u8, 25), cmd.end_of_command.exit_code.?);
-}
-
-test "OSC 133: prompt_end" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "133;B";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end(null).?.*;
- try testing.expect(cmd == .prompt_end);
-}
-
-test "OSC 133: end_of_input" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "133;C";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end(null).?.*;
- try testing.expect(cmd == .end_of_input);
-}
-
-test "OSC 133: end_of_input with cmdline 1" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "133;C;cmdline=echo bobr kurwa";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end(null).?.*;
- try testing.expect(cmd == .end_of_input);
- try testing.expect(cmd.end_of_input.cmdline != null);
- try testing.expectEqualStrings("echo bobr kurwa", cmd.end_of_input.cmdline.?);
-}
-
-test "OSC 133: end_of_input with cmdline 2" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "133;C;cmdline=echo bobr\\ kurwa";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end(null).?.*;
- try testing.expect(cmd == .end_of_input);
- try testing.expect(cmd.end_of_input.cmdline != null);
- try testing.expectEqualStrings("echo bobr kurwa", cmd.end_of_input.cmdline.?);
-}
-
-test "OSC 133: end_of_input with cmdline 3" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "133;C;cmdline=echo bobr\\nkurwa";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end(null).?.*;
- try testing.expect(cmd == .end_of_input);
- try testing.expect(cmd.end_of_input.cmdline != null);
- try testing.expectEqualStrings("echo bobr\nkurwa", cmd.end_of_input.cmdline.?);
-}
-
-test "OSC 133: end_of_input with cmdline 4" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "133;C;cmdline=$'echo bobr kurwa'";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end(null).?.*;
- try testing.expect(cmd == .end_of_input);
- try testing.expect(cmd.end_of_input.cmdline != null);
- try testing.expectEqualStrings("echo bobr kurwa", cmd.end_of_input.cmdline.?);
-}
-
-test "OSC 133: end_of_input with cmdline 5" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "133;C;cmdline='echo bobr kurwa'";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end(null).?.*;
- try testing.expect(cmd == .end_of_input);
- try testing.expect(cmd.end_of_input.cmdline != null);
- try testing.expectEqualStrings("echo bobr kurwa", cmd.end_of_input.cmdline.?);
-}
-
-test "OSC 133: end_of_input with cmdline 6" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "133;C;cmdline='echo bobr kurwa";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end(null).?.*;
- try testing.expect(cmd == .end_of_input);
- try testing.expect(cmd.end_of_input.cmdline == null);
-}
-
-test "OSC 133: end_of_input with cmdline 7" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "133;C;cmdline=$'echo bobr kurwa";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end(null).?.*;
- try testing.expect(cmd == .end_of_input);
- try testing.expect(cmd.end_of_input.cmdline == null);
-}
-
-test "OSC 133: end_of_input with cmdline 8" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "133;C;cmdline=$'";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end(null).?.*;
- try testing.expect(cmd == .end_of_input);
- try testing.expect(cmd.end_of_input.cmdline == null);
-}
-
-test "OSC 133: end_of_input with cmdline 9" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "133;C;cmdline=$'";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end(null).?.*;
- try testing.expect(cmd == .end_of_input);
- try testing.expect(cmd.end_of_input.cmdline == null);
-}
-
-test "OSC 133: end_of_input with cmdline 10" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "133;C;cmdline=";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end(null).?.*;
- try testing.expect(cmd == .end_of_input);
- try testing.expect(cmd.end_of_input.cmdline != null);
- try testing.expectEqualStrings("", cmd.end_of_input.cmdline.?);
-}
-
-test "OSC 133: end_of_input with cmdline_url 1" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "133;C;cmdline_url=echo bobr kurwa";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end(null).?.*;
- try testing.expect(cmd == .end_of_input);
- try testing.expect(cmd.end_of_input.cmdline != null);
- try testing.expectEqualStrings("echo bobr kurwa", cmd.end_of_input.cmdline.?);
-}
-
-test "OSC 133: end_of_input with cmdline_url 2" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "133;C;cmdline_url=echo bobr%20kurwa";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end(null).?.*;
- try testing.expect(cmd == .end_of_input);
- try testing.expect(cmd.end_of_input.cmdline != null);
- try testing.expectEqualStrings("echo bobr kurwa", cmd.end_of_input.cmdline.?);
-}
-
-test "OSC 133: end_of_input with cmdline_url 3" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "133;C;cmdline_url=echo bobr%3bkurwa";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end(null).?.*;
- try testing.expect(cmd == .end_of_input);
- try testing.expect(cmd.end_of_input.cmdline != null);
- try testing.expectEqualStrings("echo bobr;kurwa", cmd.end_of_input.cmdline.?);
-}
-
-test "OSC 133: end_of_input with cmdline_url 4" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "133;C;cmdline_url=echo bobr%3kurwa";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end(null).?.*;
- try testing.expect(cmd == .end_of_input);
- try testing.expect(cmd.end_of_input.cmdline == null);
-}
-
-test "OSC 133: end_of_input with cmdline_url 5" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "133;C;cmdline_url=echo bobr%kurwa";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end(null).?.*;
- try testing.expect(cmd == .end_of_input);
- try testing.expect(cmd.end_of_input.cmdline == null);
-}
-
-test "OSC 133: end_of_input with cmdline_url 6" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "133;C;cmdline_url=echo bobr%kurwa";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end(null).?.*;
- try testing.expect(cmd == .end_of_input);
- try testing.expect(cmd.end_of_input.cmdline == null);
-}
-
-test "OSC 133: end_of_input with cmdline_url 7" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "133;C;cmdline_url=echo bobr kurwa%20";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end(null).?.*;
- try testing.expect(cmd == .end_of_input);
- try testing.expect(cmd.end_of_input.cmdline != null);
- try testing.expectEqualStrings("echo bobr kurwa ", cmd.end_of_input.cmdline.?);
-}
-
-test "OSC 133: end_of_input with cmdline_url 8" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "133;C;cmdline_url=echo bobr kurwa%2";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end(null).?.*;
- try testing.expect(cmd == .end_of_input);
- try testing.expect(cmd.end_of_input.cmdline == null);
-}
-
-test "OSC 133: end_of_input with cmdline_url 9" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "133;C;cmdline_url=echo bobr kurwa%2";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end(null).?.*;
- try testing.expect(cmd == .end_of_input);
- try testing.expect(cmd.end_of_input.cmdline == null);
-}
-
-test "OSC: OSC 777 show desktop notification with title" {
- const testing = std.testing;
-
- var p: Parser = .init(null);
-
- const input = "777;notify;Title;Body";
- for (input) |ch| p.next(ch);
-
- const cmd = p.end('\x1b').?.*;
- try testing.expect(cmd == .show_desktop_notification);
- try testing.expectEqualStrings(cmd.show_desktop_notification.title, "Title");
- try testing.expectEqualStrings(cmd.show_desktop_notification.body, "Body");
+ _ = parsers;
+ _ = encoding;
}
diff --git a/src/terminal/osc/encoding.zig b/src/terminal/osc/encoding.zig
new file mode 100644
index 000000000..7491d10c2
--- /dev/null
+++ b/src/terminal/osc/encoding.zig
@@ -0,0 +1,38 @@
+//! Specialized encodings used in some OSC protocols.
+const std = @import("std");
+
+/// Kitty defines "Escape code safe UTF-8" as valid UTF-8 with the
+/// additional requirement of not containing any C0 escape codes
+/// (0x00-0x1f), DEL (0x7f) and C1 escape codes (0x80-0x9f).
+///
+/// Used by OSC 66 (text sizing) and OSC 99 (Kitty notifications).
+///
+/// See: https://sw.kovidgoyal.net/kitty/desktop-notifications/#safe-utf8
+pub fn isSafeUtf8(s: []const u8) bool {
+ const utf8 = std.unicode.Utf8View.init(s) catch {
+ @branchHint(.cold);
+ return false;
+ };
+
+ var it = utf8.iterator();
+ while (it.nextCodepoint()) |cp| switch (cp) {
+ 0x00...0x1f, 0x7f, 0x80...0x9f => {
+ @branchHint(.cold);
+ return false;
+ },
+ else => {},
+ };
+
+ return true;
+}
+
+test isSafeUtf8 {
+ const testing = std.testing;
+
+ try testing.expect(isSafeUtf8("Hello world!"));
+ try testing.expect(isSafeUtf8("ๅฎๅ
จ็ใฆใใณใผใโ๏ธ"));
+ try testing.expect(!isSafeUtf8("No linebreaks\nallowed"));
+ try testing.expect(!isSafeUtf8("\x07no bells"));
+ try testing.expect(!isSafeUtf8("\x1b]9;no OSCs\x1b\\\x1b[m"));
+ try testing.expect(!isSafeUtf8("\x9f8-bit escapes are clever, but no"));
+}
diff --git a/src/terminal/osc/parsers.zig b/src/terminal/osc/parsers.zig
new file mode 100644
index 000000000..9c1c39b2c
--- /dev/null
+++ b/src/terminal/osc/parsers.zig
@@ -0,0 +1,29 @@
+const std = @import("std");
+
+pub const change_window_icon = @import("parsers/change_window_icon.zig");
+pub const change_window_title = @import("parsers/change_window_title.zig");
+pub const clipboard_operation = @import("parsers/clipboard_operation.zig");
+pub const color = @import("parsers/color.zig");
+pub const hyperlink = @import("parsers/hyperlink.zig");
+pub const kitty_color = @import("parsers/kitty_color.zig");
+pub const kitty_text_sizing = @import("parsers/kitty_text_sizing.zig");
+pub const mouse_shape = @import("parsers/mouse_shape.zig");
+pub const osc9 = @import("parsers/osc9.zig");
+pub const report_pwd = @import("parsers/report_pwd.zig");
+pub const rxvt_extension = @import("parsers/rxvt_extension.zig");
+pub const semantic_prompt = @import("parsers/semantic_prompt.zig");
+
+test {
+ _ = change_window_icon;
+ _ = change_window_title;
+ _ = clipboard_operation;
+ _ = color;
+ _ = hyperlink;
+ _ = kitty_color;
+ _ = kitty_text_sizing;
+ _ = mouse_shape;
+ _ = osc9;
+ _ = report_pwd;
+ _ = rxvt_extension;
+ _ = semantic_prompt;
+}
diff --git a/src/terminal/osc/parsers/change_window_icon.zig b/src/terminal/osc/parsers/change_window_icon.zig
new file mode 100644
index 000000000..aefe17696
--- /dev/null
+++ b/src/terminal/osc/parsers/change_window_icon.zig
@@ -0,0 +1,33 @@
+const std = @import("std");
+const Parser = @import("../../osc.zig").Parser;
+const Command = @import("../../osc.zig").Command;
+
+/// Parse OSC 1
+pub fn parse(parser: *Parser, _: ?u8) ?*Command {
+ const writer = parser.writer orelse {
+ parser.state = .invalid;
+ return null;
+ };
+ writer.writeByte(0) catch {
+ parser.state = .invalid;
+ return null;
+ };
+ const data = writer.buffered();
+ parser.command = .{
+ .change_window_icon = data[0 .. data.len - 1 :0],
+ };
+ return &parser.command;
+}
+
+test "OSC 1: change_window_icon" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+ p.next('1');
+ p.next(';');
+ p.next('a');
+ p.next('b');
+ const cmd = p.end(null).?.*;
+ try testing.expect(cmd == .change_window_icon);
+ try testing.expectEqualStrings("ab", cmd.change_window_icon);
+}
diff --git a/src/terminal/osc/parsers/change_window_title.zig b/src/terminal/osc/parsers/change_window_title.zig
new file mode 100644
index 000000000..b0bf44dd3
--- /dev/null
+++ b/src/terminal/osc/parsers/change_window_title.zig
@@ -0,0 +1,119 @@
+const std = @import("std");
+
+const Parser = @import("../../osc.zig").Parser;
+const Command = @import("../../osc.zig").Command;
+
+/// Parse OSC 0 and OSC 2
+pub fn parse(parser: *Parser, _: ?u8) ?*Command {
+ const writer = parser.writer orelse {
+ parser.state = .invalid;
+ return null;
+ };
+ writer.writeByte(0) catch {
+ parser.state = .invalid;
+ return null;
+ };
+ const data = writer.buffered();
+ parser.command = .{
+ .change_window_title = data[0 .. data.len - 1 :0],
+ };
+ return &parser.command;
+}
+
+test "OSC 0: change_window_title" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+ p.next('0');
+ p.next(';');
+ p.next('a');
+ p.next('b');
+ const cmd = p.end(null).?.*;
+ try testing.expect(cmd == .change_window_title);
+ try testing.expectEqualStrings("ab", cmd.change_window_title);
+}
+
+test "OSC 0: longer than buffer" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "0;" ++ "a" ** (Parser.MAX_BUF + 2);
+ for (input) |ch| p.next(ch);
+
+ try testing.expect(p.end(null) == null);
+}
+
+test "OSC 0: one shorter than buffer length" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const prefix = "0;";
+ const title = "a" ** (Parser.MAX_BUF - 1);
+ const input = prefix ++ title;
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end(null).?.*;
+ try testing.expect(cmd == .change_window_title);
+ try testing.expectEqualStrings(title, cmd.change_window_title);
+}
+
+test "OSC 0: exactly at buffer length" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const prefix = "0;";
+ const title = "a" ** Parser.MAX_BUF;
+ const input = prefix ++ title;
+ for (input) |ch| p.next(ch);
+
+ // This should be null because we always reserve space for a null terminator.
+ try testing.expect(p.end(null) == null);
+}
+test "OSC 2: change_window_title with 2" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+ p.next('2');
+ p.next(';');
+ p.next('a');
+ p.next('b');
+ const cmd = p.end(null).?.*;
+ try testing.expect(cmd == .change_window_title);
+ try testing.expectEqualStrings("ab", cmd.change_window_title);
+}
+
+test "OSC 2: change_window_title with utf8" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+ p.next('2');
+ p.next(';');
+ // 'โ' EM DASH U+2014 (E2 80 94)
+ p.next(0xE2);
+ p.next(0x80);
+ p.next(0x94);
+
+ p.next(' ');
+ // 'โ' HYPHEN U+2010 (E2 80 90)
+ // Intententionally chosen to conflict with the 0x90 C1 control
+ p.next(0xE2);
+ p.next(0x80);
+ p.next(0x90);
+ const cmd = p.end(null).?.*;
+ try testing.expect(cmd == .change_window_title);
+ try testing.expectEqualStrings("โ โ", cmd.change_window_title);
+}
+
+test "OSC 2: change_window_title empty" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+ p.next('2');
+ p.next(';');
+ const cmd = p.end(null).?.*;
+ try testing.expect(cmd == .change_window_title);
+ try testing.expectEqualStrings("", cmd.change_window_title);
+}
diff --git a/src/terminal/osc/parsers/clipboard_operation.zig b/src/terminal/osc/parsers/clipboard_operation.zig
new file mode 100644
index 000000000..59a8831bc
--- /dev/null
+++ b/src/terminal/osc/parsers/clipboard_operation.zig
@@ -0,0 +1,106 @@
+const std = @import("std");
+
+const assert = @import("../../../quirks.zig").inlineAssert;
+
+const Parser = @import("../../osc.zig").Parser;
+const Command = @import("../../osc.zig").Command;
+
+/// Parse OSC 52
+pub fn parse(parser: *Parser, _: ?u8) ?*Command {
+ assert(parser.state == .@"52");
+ const writer = parser.writer orelse {
+ parser.state = .invalid;
+ return null;
+ };
+ writer.writeByte(0) catch {
+ parser.state = .invalid;
+ return null;
+ };
+ const data = writer.buffered();
+ if (data.len == 1) {
+ parser.state = .invalid;
+ return null;
+ }
+ if (data[0] == ';') {
+ parser.command = .{
+ .clipboard_contents = .{
+ .kind = 'c',
+ .data = data[1 .. data.len - 1 :0],
+ },
+ };
+ } else {
+ if (data.len < 2) {
+ parser.state = .invalid;
+ return null;
+ }
+ if (data[1] != ';') {
+ parser.state = .invalid;
+ return null;
+ }
+ parser.command = .{
+ .clipboard_contents = .{
+ .kind = data[0],
+ .data = data[2 .. data.len - 1 :0],
+ },
+ };
+ }
+ return &parser.command;
+}
+
+test "OSC 52: get/set clipboard" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "52;s;?";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end(null).?.*;
+ try testing.expect(cmd == .clipboard_contents);
+ try testing.expect(cmd.clipboard_contents.kind == 's');
+ try testing.expectEqualStrings("?", cmd.clipboard_contents.data);
+}
+
+test "OSC 52: get/set clipboard (optional parameter)" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "52;;?";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end(null).?.*;
+ try testing.expect(cmd == .clipboard_contents);
+ try testing.expect(cmd.clipboard_contents.kind == 'c');
+ try testing.expectEqualStrings("?", cmd.clipboard_contents.data);
+}
+
+test "OSC 52: get/set clipboard with allocator" {
+ const testing = std.testing;
+
+ var p: Parser = .init(testing.allocator);
+ defer p.deinit();
+
+ const input = "52;s;?";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end(null).?.*;
+ try testing.expect(cmd == .clipboard_contents);
+ try testing.expect(cmd.clipboard_contents.kind == 's');
+ try testing.expectEqualStrings("?", cmd.clipboard_contents.data);
+}
+
+test "OSC 52: clear clipboard" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+ defer p.deinit();
+
+ const input = "52;;";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end(null).?.*;
+ try testing.expect(cmd == .clipboard_contents);
+ try testing.expect(cmd.clipboard_contents.kind == 'c');
+ try testing.expectEqualStrings("", cmd.clipboard_contents.data);
+}
diff --git a/src/terminal/osc/color.zig b/src/terminal/osc/parsers/color.zig
similarity index 86%
rename from src/terminal/osc/color.zig
rename to src/terminal/osc/parsers/color.zig
index 9fd81ed63..7d3dc68c0 100644
--- a/src/terminal/osc/color.zig
+++ b/src/terminal/osc/parsers/color.zig
@@ -1,10 +1,15 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
-const DynamicColor = @import("../color.zig").Dynamic;
-const SpecialColor = @import("../color.zig").Special;
-const RGB = @import("../color.zig").RGB;
-pub const ParseError = Allocator.Error || error{
+const DynamicColor = @import("../../color.zig").Dynamic;
+const SpecialColor = @import("../../color.zig").Special;
+const RGB = @import("../../color.zig").RGB;
+const Parser = @import("../../osc.zig").Parser;
+const Command = @import("../../osc.zig").Command;
+
+const log = std.log.scoped(.osc_color);
+
+const ParseError = Allocator.Error || error{
MissingOperation,
};
@@ -36,6 +41,76 @@ pub const Operation = enum {
osc_119,
};
+/// Parse OSCs 4, 5, 10-19, 104, 110-119
+pub fn parse(parser: *Parser, terminator_ch: ?u8) ?*Command {
+ const alloc = parser.alloc orelse {
+ parser.state = .invalid;
+ return null;
+ };
+ // If we've collected any extra data parse that, otherwise use an empty
+ // string.
+ const data = data: {
+ const writer = parser.writer orelse break :data "";
+ break :data writer.buffered();
+ };
+ // Check and make sure that we're parsing the correct OSCs
+ const op: Operation = switch (parser.state) {
+ .@"4" => .osc_4,
+ .@"5" => .osc_5,
+ .@"10" => .osc_10,
+ .@"11" => .osc_11,
+ .@"12" => .osc_12,
+ .@"13" => .osc_13,
+ .@"14" => .osc_14,
+ .@"15" => .osc_15,
+ .@"16" => .osc_16,
+ .@"17" => .osc_17,
+ .@"18" => .osc_18,
+ .@"19" => .osc_19,
+ .@"104" => .osc_104,
+ .@"110" => .osc_110,
+ .@"111" => .osc_111,
+ .@"112" => .osc_112,
+ .@"113" => .osc_113,
+ .@"114" => .osc_114,
+ .@"115" => .osc_115,
+ .@"116" => .osc_116,
+ .@"117" => .osc_117,
+ .@"118" => .osc_118,
+ .@"119" => .osc_119,
+ else => {
+ parser.state = .invalid;
+ return null;
+ },
+ };
+ parser.command = .{
+ .color_operation = .{
+ .op = op,
+ .requests = parseColor(alloc, op, data) catch |err| list: {
+ log.info(
+ "failed to parse OSC {t} color request err={} data={s}",
+ .{ parser.state, err, data },
+ );
+ break :list .{};
+ },
+ .terminator = .init(terminator_ch),
+ },
+ };
+ return &parser.command;
+}
+
+test "OSC 4: empty param" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "4;;";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end('\x1b');
+ try testing.expect(cmd == null);
+}
+
/// Parse any color operation string. This should NOT include the operation
/// itself, but only the body of the operation. e.g. for "4;a;b;c" the body
/// should be "a;b;c" and the operation should be set accordingly.
@@ -46,7 +121,7 @@ pub const Operation = enum {
/// request) but grants us an easier to understand and testable implementation.
///
/// If color changing ends up being a bottleneck we can optimize this later.
-pub fn parse(
+fn parseColor(
alloc: Allocator,
op: Operation,
buf: []const u8,
@@ -295,7 +370,7 @@ test "OSC 4:" {
);
defer alloc.free(body);
- var list = try parse(alloc, .osc_4, body);
+ var list = try parseColor(alloc, .osc_4, body);
defer list.deinit(alloc);
try testing.expectEqual(1, list.count());
try testing.expectEqual(
@@ -317,7 +392,7 @@ test "OSC 4:" {
);
defer alloc.free(body);
- var list = try parse(alloc, .osc_4, body);
+ var list = try parseColor(alloc, .osc_4, body);
defer list.deinit(alloc);
try testing.expectEqual(1, list.count());
try testing.expectEqual(
@@ -336,7 +411,7 @@ test "OSC 4:" {
);
defer alloc.free(body);
- var list = try parse(alloc, .osc_4, body);
+ var list = try parseColor(alloc, .osc_4, body);
defer list.deinit(alloc);
try testing.expectEqual(1, list.count());
try testing.expectEqual(
@@ -360,7 +435,7 @@ test "OSC 4:" {
);
defer alloc.free(body);
- var list = try parse(alloc, .osc_4, body);
+ var list = try parseColor(alloc, .osc_4, body);
defer list.deinit(alloc);
try testing.expectEqual(1, list.count());
try testing.expectEqual(
@@ -387,7 +462,7 @@ test "OSC 4:" {
);
defer alloc.free(body);
- var list = try parse(alloc, .osc_4, body);
+ var list = try parseColor(alloc, .osc_4, body);
defer list.deinit(alloc);
try testing.expectEqual(1, list.count());
try testing.expectEqual(
@@ -419,7 +494,7 @@ test "OSC 5:" {
);
defer alloc.free(body);
- var list = try parse(alloc, .osc_5, body);
+ var list = try parseColor(alloc, .osc_5, body);
defer list.deinit(alloc);
try testing.expectEqual(1, list.count());
try testing.expectEqual(
@@ -439,7 +514,7 @@ test "OSC 4: multiple requests" {
// printf '\e]4;0;red;1;blue\e\\'
{
- var list = try parse(
+ var list = try parseColor(
alloc,
.osc_4,
"0;red;1;blue",
@@ -465,7 +540,7 @@ test "OSC 4: multiple requests" {
// Multiple requests with same index overwrite each other
// printf '\e]4;0;red;0;blue\e\\'
{
- var list = try parse(
+ var list = try parseColor(
alloc,
.osc_4,
"0;red;0;blue",
@@ -505,7 +580,7 @@ test "OSC 104:" {
);
defer alloc.free(body);
- var list = try parse(alloc, .osc_104, body);
+ var list = try parseColor(alloc, .osc_104, body);
defer list.deinit(alloc);
try testing.expectEqual(1, list.count());
try testing.expectEqual(
@@ -529,7 +604,7 @@ test "OSC 104:" {
);
defer alloc.free(body);
- var list = try parse(alloc, .osc_104, body);
+ var list = try parseColor(alloc, .osc_104, body);
defer list.deinit(alloc);
try testing.expectEqual(1, list.count());
try testing.expectEqual(
@@ -544,7 +619,7 @@ test "OSC 104: empty index" {
const testing = std.testing;
const alloc = testing.allocator;
- var list = try parse(alloc, .osc_104, "0;;1");
+ var list = try parseColor(alloc, .osc_104, "0;;1");
defer list.deinit(alloc);
try testing.expectEqual(2, list.count());
try testing.expectEqual(
@@ -561,7 +636,7 @@ test "OSC 104: invalid index" {
const testing = std.testing;
const alloc = testing.allocator;
- var list = try parse(alloc, .osc_104, "ffff;1");
+ var list = try parseColor(alloc, .osc_104, "ffff;1");
defer list.deinit(alloc);
try testing.expectEqual(1, list.count());
try testing.expectEqual(
@@ -574,7 +649,7 @@ test "OSC 104: reset all" {
const testing = std.testing;
const alloc = testing.allocator;
- var list = try parse(alloc, .osc_104, "");
+ var list = try parseColor(alloc, .osc_104, "");
defer list.deinit(alloc);
try testing.expectEqual(1, list.count());
try testing.expectEqual(
@@ -587,7 +662,7 @@ test "OSC 105: reset all" {
const testing = std.testing;
const alloc = testing.allocator;
- var list = try parse(alloc, .osc_105, "");
+ var list = try parseColor(alloc, .osc_105, "");
defer list.deinit(alloc);
try testing.expectEqual(1, list.count());
try testing.expectEqual(
@@ -611,7 +686,7 @@ test "OSC 10: OSC 11: OSC 12: OSC: 13: OSC 14: OSC 15: OSC: 16: OSC 17: OSC 18:
// Example script:
// printf '\e]10;red\e\\'
{
- var list = try parse(alloc, op, "red");
+ var list = try parseColor(alloc, op, "red");
defer list.deinit(alloc);
try testing.expectEqual(1, list.count());
try testing.expectEqual(
@@ -632,7 +707,7 @@ test "OSC 10: OSC 11: OSC 12: OSC: 13: OSC 14: OSC 15: OSC: 16: OSC 17: OSC 18:
// Example script:
// printf '\e]11;red;blue\e\\'
{
- var list = try parse(
+ var list = try parseColor(
alloc,
.osc_11,
"red;blue",
@@ -671,7 +746,7 @@ test "OSC 110: OSC 111: OSC 112: OSC: 113: OSC 114: OSC 115: OSC: 116: OSC 117:
// Example script:
// printf '\e]110\e\\'
{
- var list = try parse(alloc, op, "");
+ var list = try parseColor(alloc, op, "");
defer list.deinit(alloc);
try testing.expectEqual(1, list.count());
try testing.expectEqual(
@@ -684,7 +759,7 @@ test "OSC 110: OSC 111: OSC 112: OSC: 113: OSC 114: OSC 115: OSC: 116: OSC 117:
//
// printf '\e]110;\e\\'
{
- var list = try parse(alloc, op, ";");
+ var list = try parseColor(alloc, op, ";");
defer list.deinit(alloc);
try testing.expectEqual(1, list.count());
try testing.expectEqual(
@@ -697,7 +772,7 @@ test "OSC 110: OSC 111: OSC 112: OSC: 113: OSC 114: OSC 115: OSC: 116: OSC 117:
//
// printf '\e]110 \e\\'
{
- var list = try parse(alloc, op, " ");
+ var list = try parseColor(alloc, op, " ");
defer list.deinit(alloc);
try testing.expectEqual(0, list.count());
}
diff --git a/src/terminal/osc/parsers/hyperlink.zig b/src/terminal/osc/parsers/hyperlink.zig
new file mode 100644
index 000000000..cf328beb5
--- /dev/null
+++ b/src/terminal/osc/parsers/hyperlink.zig
@@ -0,0 +1,164 @@
+const std = @import("std");
+
+const Parser = @import("../../osc.zig").Parser;
+const Command = @import("../../osc.zig").Command;
+
+const log = std.log.scoped(.osc_hyperlink);
+
+/// Parse OSC 8 hyperlinks
+pub fn parse(parser: *Parser, _: ?u8) ?*Command {
+ const writer = parser.writer orelse {
+ parser.state = .invalid;
+ return null;
+ };
+ writer.writeByte(0) catch {
+ parser.state = .invalid;
+ return null;
+ };
+ const data = writer.buffered();
+ const s = std.mem.indexOfScalar(u8, data, ';') orelse {
+ parser.state = .invalid;
+ return null;
+ };
+
+ parser.command = .{
+ .hyperlink_start = .{
+ .uri = data[s + 1 .. data.len - 1 :0],
+ },
+ };
+
+ data[s] = 0;
+ const kvs = data[0 .. s + 1];
+ std.mem.replaceScalar(u8, kvs, ':', 0);
+ var kv_start: usize = 0;
+ while (kv_start < kvs.len) {
+ const kv_end = std.mem.indexOfScalarPos(u8, kvs, kv_start + 1, 0) orelse break;
+ const kv = data[kv_start .. kv_end + 1];
+ const v = std.mem.indexOfScalar(u8, kv, '=') orelse break;
+ const key = kv[0..v];
+ const value = kv[v + 1 .. kv.len - 1 :0];
+ if (std.mem.eql(u8, key, "id")) {
+ if (value.len > 0) parser.command.hyperlink_start.id = value;
+ } else {
+ log.warn("unknown hyperlink option: '{s}'", .{key});
+ }
+ kv_start = kv_end + 1;
+ }
+
+ if (parser.command.hyperlink_start.uri.len == 0) {
+ if (parser.command.hyperlink_start.id != null) {
+ parser.state = .invalid;
+ return null;
+ }
+ parser.command = .hyperlink_end;
+ }
+
+ return &parser.command;
+}
+
+test "OSC 8: hyperlink" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "8;;http://example.com";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end('\x1b').?.*;
+ try testing.expect(cmd == .hyperlink_start);
+ try testing.expectEqualStrings(cmd.hyperlink_start.uri, "http://example.com");
+}
+
+test "OSC 8: hyperlink with id set" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "8;id=foo;http://example.com";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end('\x1b').?.*;
+ try testing.expect(cmd == .hyperlink_start);
+ try testing.expectEqualStrings(cmd.hyperlink_start.id.?, "foo");
+ try testing.expectEqualStrings(cmd.hyperlink_start.uri, "http://example.com");
+}
+
+test "OSC 8: hyperlink with empty id" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "8;id=;http://example.com";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end('\x1b').?.*;
+ try testing.expect(cmd == .hyperlink_start);
+ try testing.expectEqual(null, cmd.hyperlink_start.id);
+ try testing.expectEqualStrings(cmd.hyperlink_start.uri, "http://example.com");
+}
+
+test "OSC 8: hyperlink with incomplete key" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "8;id;http://example.com";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end('\x1b').?.*;
+ try testing.expect(cmd == .hyperlink_start);
+ try testing.expectEqual(null, cmd.hyperlink_start.id);
+ try testing.expectEqualStrings(cmd.hyperlink_start.uri, "http://example.com");
+}
+
+test "OSC 8: hyperlink with empty key" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "8;=value;http://example.com";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end('\x1b').?.*;
+ try testing.expect(cmd == .hyperlink_start);
+ try testing.expectEqual(null, cmd.hyperlink_start.id);
+ try testing.expectEqualStrings(cmd.hyperlink_start.uri, "http://example.com");
+}
+
+test "OSC 8: hyperlink with empty key and id" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "8;=value:id=foo;http://example.com";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end('\x1b').?.*;
+ try testing.expect(cmd == .hyperlink_start);
+ try testing.expectEqualStrings(cmd.hyperlink_start.id.?, "foo");
+ try testing.expectEqualStrings(cmd.hyperlink_start.uri, "http://example.com");
+}
+
+test "OSC 8: hyperlink with empty uri" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "8;id=foo;";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end('\x1b');
+ try testing.expect(cmd == null);
+}
+
+test "OSC 8: hyperlink end" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "8;;";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end('\x1b').?.*;
+ try testing.expect(cmd == .hyperlink_end);
+}
diff --git a/src/terminal/osc/parsers/kitty_color.zig b/src/terminal/osc/parsers/kitty_color.zig
new file mode 100644
index 000000000..30a7fe77f
--- /dev/null
+++ b/src/terminal/osc/parsers/kitty_color.zig
@@ -0,0 +1,212 @@
+const std = @import("std");
+
+const assert = @import("../../../quirks.zig").inlineAssert;
+
+const Parser = @import("../../osc.zig").Parser;
+const Command = @import("../../osc.zig").Command;
+const kitty_color = @import("../../kitty/color.zig");
+const RGB = @import("../../color.zig").RGB;
+
+const log = std.log.scoped(.osc_kitty_color);
+
+/// Parse OSC 21, the Kitty Color Protocol.
+pub fn parse(parser: *Parser, terminator_ch: ?u8) ?*Command {
+ assert(parser.state == .@"21");
+
+ const alloc = parser.alloc orelse {
+ parser.state = .invalid;
+ return null;
+ };
+ const writer = parser.writer orelse {
+ parser.state = .invalid;
+ return null;
+ };
+ parser.command = .{
+ .kitty_color_protocol = .{
+ .list = .empty,
+ .terminator = .init(terminator_ch),
+ },
+ };
+ const list = &parser.command.kitty_color_protocol.list;
+ const data = writer.buffered();
+ var kv_it = std.mem.splitScalar(u8, data, ';');
+ while (kv_it.next()) |kv| {
+ if (list.items.len >= @as(usize, kitty_color.Kind.max) * 2) {
+ log.warn("exceeded limit for number of keys in kitty color protocol, ignoring", .{});
+ parser.state = .invalid;
+ return null;
+ }
+ var it = std.mem.splitScalar(u8, kv, '=');
+ const k = it.next() orelse continue;
+ if (k.len == 0) {
+ log.warn("zero length key in kitty color protocol", .{});
+ continue;
+ }
+ const key = kitty_color.Kind.parse(k) orelse {
+ log.warn("unknown key in kitty color protocol: {s}", .{k});
+ continue;
+ };
+ const value = std.mem.trim(u8, it.rest(), " ");
+ if (value.len == 0) {
+ list.append(alloc, .{ .reset = key }) catch |err| {
+ log.warn("unable to append kitty color protocol option: {}", .{err});
+ continue;
+ };
+ } else if (std.mem.eql(u8, "?", value)) {
+ list.append(alloc, .{ .query = key }) catch |err| {
+ log.warn("unable to append kitty color protocol option: {}", .{err});
+ continue;
+ };
+ } else {
+ list.append(alloc, .{
+ .set = .{
+ .key = key,
+ .color = RGB.parse(value) catch |err| switch (err) {
+ error.InvalidFormat => {
+ log.warn("invalid color format in kitty color protocol: {s}", .{value});
+ continue;
+ },
+ },
+ },
+ }) catch |err| {
+ log.warn("unable to append kitty color protocol option: {}", .{err});
+ continue;
+ };
+ }
+ }
+ return &parser.command;
+}
+
+test "OSC 21: kitty color protocol" {
+ const testing = std.testing;
+ const Kind = kitty_color.Kind;
+
+ var p: Parser = .init(testing.allocator);
+ 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";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end('\x1b').?.*;
+ try testing.expect(cmd == .kitty_color_protocol);
+ try testing.expectEqual(@as(usize, 9), cmd.kitty_color_protocol.list.items.len);
+ {
+ const item = cmd.kitty_color_protocol.list.items[0];
+ try testing.expect(item == .query);
+ try testing.expectEqual(Kind{ .special = .foreground }, item.query);
+ }
+ {
+ const item = cmd.kitty_color_protocol.list.items[1];
+ try testing.expect(item == .set);
+ try testing.expectEqual(Kind{ .special = .background }, item.set.key);
+ try testing.expectEqual(@as(u8, 0xf0), item.set.color.r);
+ try testing.expectEqual(@as(u8, 0xf8), item.set.color.g);
+ try testing.expectEqual(@as(u8, 0xff), item.set.color.b);
+ }
+ {
+ const item = cmd.kitty_color_protocol.list.items[2];
+ try testing.expect(item == .set);
+ try testing.expectEqual(Kind{ .special = .cursor }, item.set.key);
+ try testing.expectEqual(@as(u8, 0xf0), item.set.color.r);
+ try testing.expectEqual(@as(u8, 0xf8), item.set.color.g);
+ try testing.expectEqual(@as(u8, 0xff), item.set.color.b);
+ }
+ {
+ const item = cmd.kitty_color_protocol.list.items[3];
+ try testing.expect(item == .reset);
+ try testing.expectEqual(Kind{ .special = .cursor_text }, item.reset);
+ }
+ {
+ const item = cmd.kitty_color_protocol.list.items[4];
+ try testing.expect(item == .reset);
+ try testing.expectEqual(Kind{ .special = .visual_bell }, item.reset);
+ }
+ {
+ const item = cmd.kitty_color_protocol.list.items[5];
+ try testing.expect(item == .query);
+ try testing.expectEqual(Kind{ .special = .selection_background }, item.query);
+ }
+ {
+ const item = cmd.kitty_color_protocol.list.items[6];
+ try testing.expect(item == .set);
+ try testing.expectEqual(Kind{ .special = .selection_background }, item.set.key);
+ try testing.expectEqual(@as(u8, 0xaa), item.set.color.r);
+ try testing.expectEqual(@as(u8, 0xbb), item.set.color.g);
+ try testing.expectEqual(@as(u8, 0xcc), item.set.color.b);
+ }
+ {
+ const item = cmd.kitty_color_protocol.list.items[7];
+ try testing.expect(item == .query);
+ try testing.expectEqual(Kind{ .palette = 2 }, item.query);
+ }
+ {
+ const item = cmd.kitty_color_protocol.list.items[8];
+ try testing.expect(item == .set);
+ try testing.expectEqual(Kind{ .palette = 3 }, item.set.key);
+ try testing.expectEqual(@as(u8, 0xff), item.set.color.r);
+ try testing.expectEqual(@as(u8, 0xff), item.set.color.g);
+ try testing.expectEqual(@as(u8, 0xff), item.set.color.b);
+ }
+}
+
+test "OSC 21: kitty color protocol without allocator" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+ defer p.deinit();
+
+ const input = "21;foreground=?";
+ for (input) |ch| p.next(ch);
+ try testing.expect(p.end('\x1b') == null);
+}
+
+test "OSC 21: kitty color protocol double reset" {
+ const testing = std.testing;
+
+ var p: Parser = .init(testing.allocator);
+ 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";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end('\x1b').?.*;
+ try testing.expect(cmd == .kitty_color_protocol);
+
+ p.reset();
+ p.reset();
+}
+
+test "OSC 21: kitty color protocol reset after invalid" {
+ const testing = std.testing;
+
+ var p: Parser = .init(testing.allocator);
+ 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";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end('\x1b').?.*;
+ try testing.expect(cmd == .kitty_color_protocol);
+
+ p.reset();
+
+ try testing.expectEqual(Parser.State.start, p.state);
+ p.next('X');
+ try testing.expectEqual(Parser.State.invalid, p.state);
+
+ p.reset();
+}
+
+test "OSC 21: kitty color protocol no key" {
+ const testing = std.testing;
+
+ var p: Parser = .init(testing.allocator);
+ defer p.deinit();
+
+ const input = "21;";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end('\x1b').?.*;
+ try testing.expect(cmd == .kitty_color_protocol);
+ try testing.expectEqual(0, cmd.kitty_color_protocol.list.items.len);
+}
diff --git a/src/terminal/osc/parsers/kitty_text_sizing.zig b/src/terminal/osc/parsers/kitty_text_sizing.zig
new file mode 100644
index 000000000..2c2d1b8fd
--- /dev/null
+++ b/src/terminal/osc/parsers/kitty_text_sizing.zig
@@ -0,0 +1,250 @@
+//! Kitty's text sizing protocol (OSC 66)
+//! Specification: https://sw.kovidgoyal.net/kitty/text-sizing-protocol/
+
+const std = @import("std");
+const build_options = @import("terminal_options");
+
+const assert = @import("../../../quirks.zig").inlineAssert;
+
+const Parser = @import("../../osc.zig").Parser;
+const Command = @import("../../osc.zig").Command;
+const encoding = @import("../encoding.zig");
+const lib = @import("../../../lib/main.zig");
+const lib_target: lib.Target = if (build_options.c_abi) .c else .zig;
+
+const log = std.log.scoped(.kitty_text_sizing);
+
+pub const max_payload_length = 4096;
+
+pub const VAlign = lib.Enum(lib_target, &.{
+ "top",
+ "bottom",
+ "center",
+});
+
+pub const HAlign = lib.Enum(lib_target, &.{
+ "left",
+ "right",
+ "center",
+});
+
+pub const OSC = struct {
+ scale: u3 = 1, // 1 - 7
+ width: u3 = 0, // 0 - 7 (0 means default)
+ numerator: u4 = 0,
+ denominator: u4 = 0,
+ valign: VAlign = .top,
+ halign: HAlign = .left,
+ text: [:0]const u8,
+
+ /// We don't currently support encoding this to C in any way.
+ pub const C = void;
+
+ pub fn cval(_: OSC) C {
+ return {};
+ }
+
+ fn update(self: *OSC, key: u8, value: []const u8) !void {
+ // All values are numeric, so we can do a small hack here
+ const v = try std.fmt.parseInt(u4, value, 10);
+
+ switch (key) {
+ 's' => {
+ if (v == 0) return error.InvalidValue;
+ self.scale = std.math.cast(u3, v) orelse return error.Overflow;
+ },
+ 'w' => self.width = std.math.cast(u3, v) orelse return error.Overflow,
+ 'n' => self.numerator = v,
+ 'd' => self.denominator = v,
+ 'v' => self.valign = std.enums.fromInt(VAlign, v) orelse return error.InvalidValue,
+ 'h' => self.halign = std.enums.fromInt(HAlign, v) orelse return error.InvalidValue,
+ else => return error.UnknownKey,
+ }
+ }
+};
+
+pub fn parse(parser: *Parser, _: ?u8) ?*Command {
+ assert(parser.state == .@"66");
+
+ const writer = parser.writer orelse {
+ parser.state = .invalid;
+ return null;
+ };
+
+ // Write a NUL byte to ensure that `text` is NUL-terminated
+ writer.writeByte(0) catch {
+ parser.state = .invalid;
+ return null;
+ };
+ const data = writer.buffered();
+
+ const payload_start = std.mem.indexOfScalar(u8, data, ';') orelse {
+ log.warn("missing semicolon before payload", .{});
+ parser.state = .invalid;
+ return null;
+ };
+ const payload = data[payload_start + 1 .. data.len - 1 :0];
+
+ // Payload has to be a URL-safe UTF-8 string,
+ // and be under the size limit.
+ if (payload.len > max_payload_length) {
+ log.warn("payload is too long", .{});
+ parser.state = .invalid;
+ return null;
+ }
+ if (!encoding.isSafeUtf8(payload)) {
+ log.warn("payload is not escape code safe UTF-8", .{});
+ parser.state = .invalid;
+ return null;
+ }
+
+ parser.command = .{
+ .kitty_text_sizing = .{ .text = payload },
+ };
+ const cmd = &parser.command.kitty_text_sizing;
+
+ // Parse any arguments if given
+ if (payload_start > 0) {
+ var kv_it = std.mem.splitScalar(
+ u8,
+ data[0..payload_start],
+ ':',
+ );
+
+ while (kv_it.next()) |kv| {
+ var it = std.mem.splitScalar(u8, kv, '=');
+ const k = it.next() orelse {
+ log.warn("missing key", .{});
+ continue;
+ };
+ if (k.len != 1) {
+ log.warn("key must be a single character", .{});
+ continue;
+ }
+
+ const value = it.next() orelse {
+ log.warn("missing value", .{});
+ continue;
+ };
+
+ cmd.update(k[0], value) catch |err| {
+ switch (err) {
+ error.UnknownKey => log.warn("unknown key: '{c}'", .{k[0]}),
+ else => log.warn("invalid value for key '{c}': {}", .{ k[0], err }),
+ }
+ continue;
+ };
+ }
+ }
+
+ return &parser.command;
+}
+
+test "OSC 66: empty parameters" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "66;;bobr";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end('\x1b').?.*;
+ try testing.expect(cmd == .kitty_text_sizing);
+ try testing.expectEqual(1, cmd.kitty_text_sizing.scale);
+ try testing.expectEqualStrings("bobr", cmd.kitty_text_sizing.text);
+}
+
+test "OSC 66: single parameter" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "66;s=2;kurwa";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end('\x1b').?.*;
+ try testing.expect(cmd == .kitty_text_sizing);
+ try testing.expectEqual(2, cmd.kitty_text_sizing.scale);
+ try testing.expectEqualStrings("kurwa", cmd.kitty_text_sizing.text);
+}
+
+test "OSC 66: multiple parameters" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "66;s=2:w=7:n=13:d=15:v=1:h=2;long";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end('\x1b').?.*;
+ try testing.expect(cmd == .kitty_text_sizing);
+ try testing.expectEqual(2, cmd.kitty_text_sizing.scale);
+ try testing.expectEqual(7, cmd.kitty_text_sizing.width);
+ try testing.expectEqual(13, cmd.kitty_text_sizing.numerator);
+ try testing.expectEqual(15, cmd.kitty_text_sizing.denominator);
+ try testing.expectEqual(.bottom, cmd.kitty_text_sizing.valign);
+ try testing.expectEqual(.center, cmd.kitty_text_sizing.halign);
+ try testing.expectEqualStrings("long", cmd.kitty_text_sizing.text);
+}
+
+test "OSC 66: scale is zero" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "66;s=0;nope";
+ for (input) |ch| p.next(ch);
+ const cmd = p.end('\x1b').?.*;
+
+ try testing.expect(cmd == .kitty_text_sizing);
+ try testing.expectEqual(1, cmd.kitty_text_sizing.scale);
+}
+
+test "OSC 66: invalid parameters" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ for ("66;w=8:v=3:n=16;") |ch| p.next(ch);
+ const cmd = p.end('\x1b').?.*;
+
+ try testing.expect(cmd == .kitty_text_sizing);
+ try testing.expectEqual(0, cmd.kitty_text_sizing.width);
+ try testing.expect(cmd.kitty_text_sizing.valign == .top);
+ try testing.expectEqual(0, cmd.kitty_text_sizing.numerator);
+}
+
+test "OSC 66: UTF-8" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "66;;๐ป้ญ้ญ
้ญ้ญใดใผในใใใฃ";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end('\x1b').?.*;
+ try testing.expect(cmd == .kitty_text_sizing);
+ try testing.expectEqualStrings("๐ป้ญ้ญ
้ญ้ญใดใผในใใใฃ", cmd.kitty_text_sizing.text);
+}
+
+test "OSC 66: unsafe UTF-8" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "66;;\n";
+ for (input) |ch| p.next(ch);
+
+ try testing.expect(p.end('\x1b') == null);
+}
+
+test "OSC 66: overlong UTF-8" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "66;;" ++ "bobr" ** 1025;
+ for (input) |ch| p.next(ch);
+
+ try testing.expect(p.end('\x1b') == null);
+}
diff --git a/src/terminal/osc/parsers/mouse_shape.zig b/src/terminal/osc/parsers/mouse_shape.zig
new file mode 100644
index 000000000..91c5ab270
--- /dev/null
+++ b/src/terminal/osc/parsers/mouse_shape.zig
@@ -0,0 +1,39 @@
+const std = @import("std");
+
+const assert = @import("../../../quirks.zig").inlineAssert;
+
+const Parser = @import("../../osc.zig").Parser;
+const Command = @import("../../osc.zig").Command;
+
+// Parse OSC 22
+pub fn parse(parser: *Parser, _: ?u8) ?*Command {
+ assert(parser.state == .@"22");
+ const writer = parser.writer orelse {
+ parser.state = .invalid;
+ return null;
+ };
+ writer.writeByte(0) catch {
+ parser.state = .invalid;
+ return null;
+ };
+ const data = writer.buffered();
+ parser.command = .{
+ .mouse_shape = .{
+ .value = data[0 .. data.len - 1 :0],
+ },
+ };
+ return &parser.command;
+}
+
+test "OSC 22: pointer cursor" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "22;pointer";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end(null).?.*;
+ try testing.expect(cmd == .mouse_shape);
+ try testing.expectEqualStrings("pointer", cmd.mouse_shape.value);
+}
diff --git a/src/terminal/osc/parsers/osc9.zig b/src/terminal/osc/parsers/osc9.zig
new file mode 100644
index 000000000..1ca7ba5a0
--- /dev/null
+++ b/src/terminal/osc/parsers/osc9.zig
@@ -0,0 +1,766 @@
+const std = @import("std");
+
+const Parser = @import("../../osc.zig").Parser;
+const Command = @import("../../osc.zig").Command;
+
+/// Parse OSC 9, which could be an iTerm2 notification or a ConEmu extension.
+pub fn parse(parser: *Parser, _: ?u8) ?*Command {
+ const writer = parser.writer orelse {
+ parser.state = .invalid;
+ return null;
+ };
+
+ // Check first to see if this is a ConEmu OSC
+ // https://conemu.github.io/en/AnsiEscapeCodes.html#ConEmu_specific_OSC
+ conemu: {
+ var data = writer.buffered();
+ if (data.len == 0) break :conemu;
+ switch (data[0]) {
+ // Check for OSC 9;1 9;10 9;12
+ '1' => {
+ if (data.len < 2) break :conemu;
+ switch (data[1]) {
+ // OSC 9;1
+ ';' => {
+ parser.command = .{
+ .conemu_sleep = .{
+ .duration_ms = if (std.fmt.parseUnsigned(u16, data[2..], 10)) |num| @min(num, 10_000) else |_| 100,
+ },
+ };
+ return &parser.command;
+ },
+ // OSC 9;10
+ '0' => {
+ parser.state = .invalid;
+ return null;
+ },
+ // OSC 9;12
+ '2' => {
+ parser.command = .{
+ .prompt_start = .{},
+ };
+ return &parser.command;
+ },
+ else => break :conemu,
+ }
+ },
+ // OSC 9;2
+ '2' => {
+ if (data.len < 2) break :conemu;
+ if (data[1] != ';') break :conemu;
+ writer.writeByte(0) catch {
+ parser.state = .invalid;
+ return null;
+ };
+ data = writer.buffered();
+ parser.command = .{
+ .conemu_show_message_box = data[2 .. data.len - 1 :0],
+ };
+ return &parser.command;
+ },
+ // OSC 9;3
+ '3' => {
+ if (data.len < 2) break :conemu;
+ if (data[1] != ';') break :conemu;
+ if (data.len == 2) {
+ parser.command = .{
+ .conemu_change_tab_title = .reset,
+ };
+ return &parser.command;
+ }
+ writer.writeByte(0) catch {
+ parser.state = .invalid;
+ return null;
+ };
+ data = writer.buffered();
+ parser.command = .{
+ .conemu_change_tab_title = .{
+ .value = data[2 .. data.len - 1 :0],
+ },
+ };
+ return &parser.command;
+ },
+ // OSC 9;4
+ '4' => {
+ if (data.len < 2) break :conemu;
+ if (data[1] != ';') break :conemu;
+ if (data.len < 3) break :conemu;
+ switch (data[2]) {
+ '0' => {
+ parser.command = .{
+ .conemu_progress_report = .{
+ .state = .remove,
+ },
+ };
+ },
+ '1' => {
+ parser.command = .{
+ .conemu_progress_report = .{
+ .state = .set,
+ .progress = 0,
+ },
+ };
+ },
+ '2' => {
+ parser.command = .{
+ .conemu_progress_report = .{
+ .state = .@"error",
+ },
+ };
+ },
+ '3' => {
+ parser.command = .{
+ .conemu_progress_report = .{
+ .state = .indeterminate,
+ },
+ };
+ },
+ '4' => {
+ parser.command = .{
+ .conemu_progress_report = .{
+ .state = .pause,
+ },
+ };
+ },
+ else => break :conemu,
+ }
+ switch (parser.command.conemu_progress_report.state) {
+ .remove, .indeterminate => {},
+ .set, .@"error", .pause => progress: {
+ if (data.len < 4) break :progress;
+ if (data[3] != ';') break :progress;
+ // parse the progress value
+ parser.command.conemu_progress_report.progress = value: {
+ break :value @intCast(std.math.clamp(
+ std.fmt.parseUnsigned(usize, data[4..], 10) catch break :value null,
+ 0,
+ 100,
+ ));
+ };
+ },
+ }
+ return &parser.command;
+ },
+ // OSC 9;5
+ '5' => {
+ parser.command = .conemu_wait_input;
+ return &parser.command;
+ },
+ // OSC 9;6
+ '6' => {
+ if (data.len < 2) break :conemu;
+ if (data[1] != ';') break :conemu;
+ writer.writeByte(0) catch {
+ parser.state = .invalid;
+ return null;
+ };
+ data = writer.buffered();
+ parser.command = .{
+ .conemu_guimacro = data[2 .. data.len - 1 :0],
+ };
+ return &parser.command;
+ },
+ // OSC 9;7
+ '7' => {
+ if (data.len < 2) break :conemu;
+ if (data[1] != ';') break :conemu;
+ parser.state = .invalid;
+ return null;
+ },
+ // OSC 9;8
+ '8' => {
+ if (data.len < 2) break :conemu;
+ if (data[1] != ';') break :conemu;
+ parser.state = .invalid;
+ return null;
+ },
+ // OSC 9;9
+ '9' => {
+ if (data.len < 2) break :conemu;
+ if (data[1] != ';') break :conemu;
+ parser.state = .invalid;
+ return null;
+ },
+ else => break :conemu,
+ }
+ }
+
+ // If it's not a ConEmu OSC, it's an iTerm2 notification
+
+ writer.writeByte(0) catch {
+ parser.state = .invalid;
+ return null;
+ };
+ const data = writer.buffered();
+ parser.command = .{
+ .show_desktop_notification = .{
+ .title = "",
+ .body = data[0 .. data.len - 1 :0],
+ },
+ };
+ return &parser.command;
+}
+
+test "OSC 9: show desktop notification" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "9;Hello world";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end('\x1b').?.*;
+ try testing.expect(cmd == .show_desktop_notification);
+ try testing.expectEqualStrings("", cmd.show_desktop_notification.title);
+ try testing.expectEqualStrings("Hello world", cmd.show_desktop_notification.body);
+}
+
+test "OSC 9: show single character desktop notification" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "9;H";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end('\x1b').?.*;
+ try testing.expect(cmd == .show_desktop_notification);
+ try testing.expectEqualStrings("", cmd.show_desktop_notification.title);
+ try testing.expectEqualStrings("H", cmd.show_desktop_notification.body);
+}
+
+test "OSC 9;1: ConEmu sleep" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "9;1;420";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end('\x1b').?.*;
+
+ try testing.expect(cmd == .conemu_sleep);
+ try testing.expectEqual(420, cmd.conemu_sleep.duration_ms);
+}
+
+test "OSC 9;1: ConEmu sleep with no value default to 100ms" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "9;1;";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end('\x1b').?.*;
+
+ try testing.expect(cmd == .conemu_sleep);
+ try testing.expectEqual(100, cmd.conemu_sleep.duration_ms);
+}
+
+test "OSC 9;1: conemu sleep cannot exceed 10000ms" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "9;1;12345";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end('\x1b').?.*;
+
+ try testing.expect(cmd == .conemu_sleep);
+ try testing.expectEqual(10000, cmd.conemu_sleep.duration_ms);
+}
+
+test "OSC 9;1: conemu sleep invalid input" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "9;1;foo";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end('\x1b').?.*;
+
+ try testing.expect(cmd == .conemu_sleep);
+ try testing.expectEqual(100, cmd.conemu_sleep.duration_ms);
+}
+
+test "OSC 9;1: conemu sleep -> desktop notification 1" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "9;1";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end('\x1b').?.*;
+
+ try testing.expect(cmd == .show_desktop_notification);
+ try testing.expectEqualStrings("1", cmd.show_desktop_notification.body);
+}
+
+test "OSC 9;1: conemu sleep -> desktop notification 2" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "9;1a";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end('\x1b').?.*;
+
+ try testing.expect(cmd == .show_desktop_notification);
+ try testing.expectEqualStrings("1a", cmd.show_desktop_notification.body);
+}
+
+test "OSC 9;2: ConEmu message box" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "9;2;hello world";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end('\x1b').?.*;
+ try testing.expect(cmd == .conemu_show_message_box);
+ try testing.expectEqualStrings("hello world", cmd.conemu_show_message_box);
+}
+
+test "OSC 9;2: ConEmu message box invalid input" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "9;2";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end('\x1b').?.*;
+ try testing.expect(cmd == .show_desktop_notification);
+ try testing.expectEqualStrings("2", cmd.show_desktop_notification.body);
+}
+
+test "OSC 9;2: ConEmu message box empty message" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "9;2;";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end('\x1b').?.*;
+ try testing.expect(cmd == .conemu_show_message_box);
+ try testing.expectEqualStrings("", cmd.conemu_show_message_box);
+}
+
+test "OSC 9;2: ConEmu message box spaces only message" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "9;2; ";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end('\x1b').?.*;
+ try testing.expect(cmd == .conemu_show_message_box);
+ try testing.expectEqualStrings(" ", cmd.conemu_show_message_box);
+}
+
+test "OSC 9;2: message box -> desktop notification 1" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "9;2";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end('\x1b').?.*;
+
+ try testing.expect(cmd == .show_desktop_notification);
+ try testing.expectEqualStrings("2", cmd.show_desktop_notification.body);
+}
+
+test "OSC 9;2: message box -> desktop notification 2" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "9;2a";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end('\x1b').?.*;
+
+ try testing.expect(cmd == .show_desktop_notification);
+ try testing.expectEqualStrings("2a", cmd.show_desktop_notification.body);
+}
+
+test "OSC 9;3: ConEmu change tab title" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "9;3;foo bar";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end('\x1b').?.*;
+ try testing.expect(cmd == .conemu_change_tab_title);
+ try testing.expectEqualStrings("foo bar", cmd.conemu_change_tab_title.value);
+}
+
+test "OSC 9;3: ConEmu change tab title reset" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "9;3;";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end('\x1b').?.*;
+
+ const expected_command: Command = .{ .conemu_change_tab_title = .reset };
+ try testing.expectEqual(expected_command, cmd);
+}
+
+test "OSC 9;3: ConEmu change tab title spaces only" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "9;3; ";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end('\x1b').?.*;
+
+ try testing.expect(cmd == .conemu_change_tab_title);
+ try testing.expectEqualStrings(" ", cmd.conemu_change_tab_title.value);
+}
+
+test "OSC 9;3: change tab title -> desktop notification 1" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "9;3";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end('\x1b').?.*;
+
+ try testing.expect(cmd == .show_desktop_notification);
+ try testing.expectEqualStrings("3", cmd.show_desktop_notification.body);
+}
+
+test "OSC 9;3: message box -> desktop notification 2" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "9;3a";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end('\x1b').?.*;
+
+ try testing.expect(cmd == .show_desktop_notification);
+ try testing.expectEqualStrings("3a", cmd.show_desktop_notification.body);
+}
+
+test "OSC 9;4: ConEmu progress set" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "9;4;1;100";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end('\x1b').?.*;
+ try testing.expect(cmd == .conemu_progress_report);
+ try testing.expect(cmd.conemu_progress_report.state == .set);
+ try testing.expect(cmd.conemu_progress_report.progress == 100);
+}
+
+test "OSC 9;4: ConEmu progress set overflow" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "9;4;1;900";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end('\x1b').?.*;
+ try testing.expect(cmd == .conemu_progress_report);
+ try testing.expect(cmd.conemu_progress_report.state == .set);
+ try testing.expectEqual(100, cmd.conemu_progress_report.progress);
+}
+
+test "OSC 9;4: ConEmu progress set single digit" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "9;4;1;9";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end('\x1b').?.*;
+ try testing.expect(cmd == .conemu_progress_report);
+ try testing.expect(cmd.conemu_progress_report.state == .set);
+ try testing.expect(cmd.conemu_progress_report.progress == 9);
+}
+
+test "OSC 9;4: ConEmu progress set double digit" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "9;4;1;94";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end('\x1b').?.*;
+ try testing.expect(cmd == .conemu_progress_report);
+ try testing.expect(cmd.conemu_progress_report.state == .set);
+ try testing.expectEqual(94, cmd.conemu_progress_report.progress);
+}
+
+test "OSC 9;4: ConEmu progress set extra semicolon ignored" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "9;4;1;100";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end('\x1b').?.*;
+ try testing.expect(cmd == .conemu_progress_report);
+ try testing.expect(cmd.conemu_progress_report.state == .set);
+ try testing.expectEqual(100, cmd.conemu_progress_report.progress);
+}
+
+test "OSC 9;4: ConEmu progress remove with no progress" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "9;4;0;";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end('\x1b').?.*;
+ try testing.expect(cmd == .conemu_progress_report);
+ try testing.expect(cmd.conemu_progress_report.state == .remove);
+ try testing.expect(cmd.conemu_progress_report.progress == null);
+}
+
+test "OSC 9;4: ConEmu progress remove with double semicolon" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "9;4;0;;";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end('\x1b').?.*;
+ try testing.expect(cmd == .conemu_progress_report);
+ try testing.expect(cmd.conemu_progress_report.state == .remove);
+ try testing.expect(cmd.conemu_progress_report.progress == null);
+}
+
+test "OSC 9;4: ConEmu progress remove ignores progress" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "9;4;0;100";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end('\x1b').?.*;
+ try testing.expect(cmd == .conemu_progress_report);
+ try testing.expect(cmd.conemu_progress_report.state == .remove);
+ try testing.expect(cmd.conemu_progress_report.progress == null);
+}
+
+test "OSC 9;4: ConEmu progress remove extra semicolon" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "9;4;0;100;";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end('\x1b').?.*;
+ try testing.expect(cmd == .conemu_progress_report);
+ try testing.expect(cmd.conemu_progress_report.state == .remove);
+}
+
+test "OSC 9;4: ConEmu progress error" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "9;4;2";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end('\x1b').?.*;
+ try testing.expect(cmd == .conemu_progress_report);
+ try testing.expect(cmd.conemu_progress_report.state == .@"error");
+ try testing.expect(cmd.conemu_progress_report.progress == null);
+}
+
+test "OSC 9;4: ConEmu progress error with progress" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "9;4;2;100";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end('\x1b').?.*;
+ try testing.expect(cmd == .conemu_progress_report);
+ try testing.expect(cmd.conemu_progress_report.state == .@"error");
+ try testing.expect(cmd.conemu_progress_report.progress == 100);
+}
+
+test "OSC 9;4: progress pause" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "9;4;4";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end('\x1b').?.*;
+ try testing.expect(cmd == .conemu_progress_report);
+ try testing.expect(cmd.conemu_progress_report.state == .pause);
+ try testing.expect(cmd.conemu_progress_report.progress == null);
+}
+
+test "OSC 9;4: ConEmu progress pause with progress" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "9;4;4;100";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end('\x1b').?.*;
+ try testing.expect(cmd == .conemu_progress_report);
+ try testing.expect(cmd.conemu_progress_report.state == .pause);
+ try testing.expect(cmd.conemu_progress_report.progress == 100);
+}
+
+test "OSC 9;4: progress -> desktop notification 1" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "9;4";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end('\x1b').?.*;
+
+ try testing.expect(cmd == .show_desktop_notification);
+ try testing.expectEqualStrings("4", cmd.show_desktop_notification.body);
+}
+
+test "OSC 9;4: progress -> desktop notification 2" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "9;4;";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end('\x1b').?.*;
+
+ try testing.expect(cmd == .show_desktop_notification);
+ try testing.expectEqualStrings("4;", cmd.show_desktop_notification.body);
+}
+
+test "OSC 9;4: progress -> desktop notification 3" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "9;4;5";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end('\x1b').?.*;
+
+ try testing.expect(cmd == .show_desktop_notification);
+ try testing.expectEqualStrings("4;5", cmd.show_desktop_notification.body);
+}
+
+test "OSC 9;4: progress -> desktop notification 4" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "9;4;5a";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end('\x1b').?.*;
+
+ try testing.expect(cmd == .show_desktop_notification);
+ try testing.expectEqualStrings("4;5a", cmd.show_desktop_notification.body);
+}
+
+test "OSC 9;5: ConEmu wait input" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "9;5";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end('\x1b').?.*;
+ try testing.expect(cmd == .conemu_wait_input);
+}
+
+test "OSC 9;5: ConEmu wait ignores trailing characters" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "9;5;foo";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end('\x1b').?.*;
+ try testing.expect(cmd == .conemu_wait_input);
+}
+
+test "OSC 9;6: ConEmu guimacro 1" {
+ const testing = std.testing;
+
+ var p: Parser = .init(testing.allocator);
+ defer p.deinit();
+
+ const input = "9;6;a";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end('\x1b').?.*;
+ try testing.expect(cmd == .conemu_guimacro);
+ try testing.expectEqualStrings("a", cmd.conemu_guimacro);
+}
+
+test "OSC: 9;6: ConEmu guimacro 2" {
+ const testing = std.testing;
+
+ var p: Parser = .init(testing.allocator);
+ defer p.deinit();
+
+ const input = "9;6;ab";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end('\x1b').?.*;
+ try testing.expect(cmd == .conemu_guimacro);
+ try testing.expectEqualStrings("ab", cmd.conemu_guimacro);
+}
+
+test "OSC: 9;6: ConEmu guimacro 3 incomplete -> desktop notification" {
+ const testing = std.testing;
+
+ var p: Parser = .init(testing.allocator);
+ defer p.deinit();
+
+ const input = "9;6";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end('\x1b').?.*;
+ try testing.expect(cmd == .show_desktop_notification);
+ try testing.expectEqualStrings("6", cmd.show_desktop_notification.body);
+}
diff --git a/src/terminal/osc/parsers/report_pwd.zig b/src/terminal/osc/parsers/report_pwd.zig
new file mode 100644
index 000000000..080b9cbb0
--- /dev/null
+++ b/src/terminal/osc/parsers/report_pwd.zig
@@ -0,0 +1,48 @@
+const std = @import("std");
+
+const Parser = @import("../../osc.zig").Parser;
+const Command = @import("../../osc.zig").Command;
+
+/// Parse OSC 7
+pub fn parse(parser: *Parser, _: ?u8) ?*Command {
+ const writer = parser.writer orelse {
+ parser.state = .invalid;
+ return null;
+ };
+ writer.writeByte(0) catch {
+ parser.state = .invalid;
+ return null;
+ };
+ const data = writer.buffered();
+ parser.command = .{
+ .report_pwd = .{
+ .value = data[0 .. data.len - 1 :0],
+ },
+ };
+ return &parser.command;
+}
+
+test "OSC 7: report pwd" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "7;file:///tmp/example";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end(null).?.*;
+ try testing.expect(cmd == .report_pwd);
+ try testing.expectEqualStrings("file:///tmp/example", cmd.report_pwd.value);
+}
+
+test "OSC 7: report pwd empty" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "7;";
+ for (input) |ch| p.next(ch);
+ const cmd = p.end(null).?.*;
+ try testing.expect(cmd == .report_pwd);
+ try testing.expectEqualStrings("", cmd.report_pwd.value);
+}
diff --git a/src/terminal/osc/parsers/rxvt_extension.zig b/src/terminal/osc/parsers/rxvt_extension.zig
new file mode 100644
index 000000000..94a0961d2
--- /dev/null
+++ b/src/terminal/osc/parsers/rxvt_extension.zig
@@ -0,0 +1,59 @@
+const std = @import("std");
+
+const Parser = @import("../../osc.zig").Parser;
+const Command = @import("../../osc.zig").Command;
+
+const log = std.log.scoped(.osc_rxvt_extension);
+
+/// Parse OSC 777
+pub fn parse(parser: *Parser, _: ?u8) ?*Command {
+ const writer = parser.writer orelse {
+ parser.state = .invalid;
+ return null;
+ };
+ // ensure that we are sentinel terminated
+ writer.writeByte(0) catch {
+ parser.state = .invalid;
+ return null;
+ };
+ const data = writer.buffered();
+ const k = std.mem.indexOfScalar(u8, data, ';') orelse {
+ parser.state = .invalid;
+ return null;
+ };
+ const ext = data[0..k];
+ if (!std.mem.eql(u8, ext, "notify")) {
+ log.warn("unknown rxvt extension: {s}", .{ext});
+ parser.state = .invalid;
+ return null;
+ }
+ const t = std.mem.indexOfScalarPos(u8, data, k + 1, ';') orelse {
+ log.warn("rxvt notify extension is missing the title", .{});
+ parser.state = .invalid;
+ return null;
+ };
+ data[t] = 0;
+ const title = data[k + 1 .. t :0];
+ const body = data[t + 1 .. data.len - 1 :0];
+ parser.command = .{
+ .show_desktop_notification = .{
+ .title = title,
+ .body = body,
+ },
+ };
+ return &parser.command;
+}
+
+test "OSC: OSC 777 show desktop notification with title" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "777;notify;Title;Body";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end('\x1b').?.*;
+ try testing.expect(cmd == .show_desktop_notification);
+ try testing.expectEqualStrings(cmd.show_desktop_notification.title, "Title");
+ try testing.expectEqualStrings(cmd.show_desktop_notification.body, "Body");
+}
diff --git a/src/terminal/osc/parsers/semantic_prompt.zig b/src/terminal/osc/parsers/semantic_prompt.zig
new file mode 100644
index 000000000..652fe34da
--- /dev/null
+++ b/src/terminal/osc/parsers/semantic_prompt.zig
@@ -0,0 +1,771 @@
+const std = @import("std");
+
+const string_encoding = @import("../../../os/string_encoding.zig");
+
+const Parser = @import("../../osc.zig").Parser;
+const Command = @import("../../osc.zig").Command;
+
+const log = std.log.scoped(.osc_semantic_prompt);
+
+/// Parse OSC 133, semantic prompts
+pub fn parse(parser: *Parser, _: ?u8) ?*Command {
+ const writer = parser.writer orelse {
+ parser.state = .invalid;
+ return null;
+ };
+ const data = writer.buffered();
+ if (data.len == 0) {
+ parser.state = .invalid;
+ return null;
+ }
+ switch (data[0]) {
+ 'A' => prompt_start: {
+ parser.command = .{
+ .prompt_start = .{},
+ };
+ if (data.len == 1) break :prompt_start;
+ if (data[1] != ';') {
+ parser.state = .invalid;
+ return null;
+ }
+ var it = SemanticPromptKVIterator.init(writer) catch {
+ parser.state = .invalid;
+ return null;
+ };
+ while (it.next()) |kv| {
+ const key = kv.key orelse continue;
+ if (std.mem.eql(u8, key, "aid")) {
+ parser.command.prompt_start.aid = kv.value;
+ } else if (std.mem.eql(u8, key, "redraw")) redraw: {
+ // https://sw.kovidgoyal.net/kitty/shell-integration/#notes-for-shell-developers
+ // Kitty supports a "redraw" option for prompt_start. I can't find
+ // this documented anywhere but can see in the code that this is used
+ // by shell environments to tell the terminal that the shell will NOT
+ // redraw the prompt so we should attempt to resize it.
+ parser.command.prompt_start.redraw = (value: {
+ const value = kv.value orelse break :value null;
+ if (value.len != 1) break :value null;
+ switch (value[0]) {
+ '0' => break :value false,
+ '1' => break :value true,
+ else => break :value null,
+ }
+ }) orelse {
+ log.info("OSC 133 A: invalid redraw value: {?s}", .{kv.value});
+ break :redraw;
+ };
+ } else if (std.mem.eql(u8, key, "special_key")) redraw: {
+ // https://sw.kovidgoyal.net/kitty/shell-integration/#notes-for-shell-developers
+ parser.command.prompt_start.special_key = (value: {
+ const value = kv.value orelse break :value null;
+ if (value.len != 1) break :value null;
+ switch (value[0]) {
+ '0' => break :value false,
+ '1' => break :value true,
+ else => break :value null,
+ }
+ }) orelse {
+ log.info("OSC 133 A invalid special_key value: {?s}", .{kv.value});
+ break :redraw;
+ };
+ } else if (std.mem.eql(u8, key, "click_events")) redraw: {
+ // https://sw.kovidgoyal.net/kitty/shell-integration/#notes-for-shell-developers
+ parser.command.prompt_start.click_events = (value: {
+ const value = kv.value orelse break :value null;
+ if (value.len != 1) break :value null;
+ switch (value[0]) {
+ '0' => break :value false,
+ '1' => break :value true,
+ else => break :value null,
+ }
+ }) orelse {
+ log.info("OSC 133 A invalid click_events value: {?s}", .{kv.value});
+ break :redraw;
+ };
+ } else if (std.mem.eql(u8, key, "k")) k: {
+ // The "k" marks the kind of prompt, or "primary" if we don't know.
+ // This can be used to distinguish between the first (initial) prompt,
+ // a continuation, etc.
+ const value = kv.value orelse break :k;
+ if (value.len != 1) break :k;
+ parser.command.prompt_start.kind = switch (value[0]) {
+ 'c' => .continuation,
+ 's' => .secondary,
+ 'r' => .right,
+ 'i' => .primary,
+ else => .primary,
+ };
+ } else log.info("OSC 133 A: unknown semantic prompt option: {?s}", .{kv.key});
+ }
+ },
+ 'B' => prompt_end: {
+ parser.command = .prompt_end;
+ if (data.len == 1) break :prompt_end;
+ if (data[1] != ';') {
+ parser.state = .invalid;
+ return null;
+ }
+ var it = SemanticPromptKVIterator.init(writer) catch {
+ parser.state = .invalid;
+ return null;
+ };
+ while (it.next()) |kv| {
+ log.info("OSC 133 B: unknown semantic prompt option: {?s}", .{kv.key});
+ }
+ },
+ 'C' => end_of_input: {
+ parser.command = .{
+ .end_of_input = .{},
+ };
+ if (data.len == 1) break :end_of_input;
+ if (data[1] != ';') {
+ parser.state = .invalid;
+ return null;
+ }
+ var it = SemanticPromptKVIterator.init(writer) catch {
+ parser.state = .invalid;
+ return null;
+ };
+ while (it.next()) |kv| {
+ const key = kv.key orelse continue;
+ if (std.mem.eql(u8, key, "cmdline")) {
+ parser.command.end_of_input.cmdline = if (kv.value) |value| string_encoding.printfQDecode(value) catch null else null;
+ } else if (std.mem.eql(u8, key, "cmdline_url")) {
+ parser.command.end_of_input.cmdline = if (kv.value) |value| string_encoding.urlPercentDecode(value) catch null else null;
+ } else {
+ log.info("OSC 133 C: unknown semantic prompt option: {s}", .{key});
+ }
+ }
+ },
+ 'D' => {
+ const exit_code: ?u8 = exit_code: {
+ if (data.len == 1) break :exit_code null;
+ if (data[1] != ';') {
+ parser.state = .invalid;
+ return null;
+ }
+ break :exit_code std.fmt.parseUnsigned(u8, data[2..], 10) catch null;
+ };
+ parser.command = .{
+ .end_of_command = .{
+ .exit_code = exit_code,
+ },
+ };
+ },
+ else => {
+ parser.state = .invalid;
+ return null;
+ },
+ }
+ return &parser.command;
+}
+
+const SemanticPromptKVIterator = struct {
+ index: usize,
+ string: []u8,
+
+ pub const SemanticPromptKV = struct {
+ key: ?[:0]u8,
+ value: ?[:0]u8,
+ };
+
+ pub fn init(writer: *std.Io.Writer) std.Io.Writer.Error!SemanticPromptKVIterator {
+ // add a semicolon to make it easier to find and sentinel terminate the values
+ try writer.writeByte(';');
+ return .{
+ .index = 0,
+ .string = writer.buffered()[2..],
+ };
+ }
+
+ pub fn next(self: *SemanticPromptKVIterator) ?SemanticPromptKV {
+ if (self.index >= self.string.len) return null;
+
+ const kv = kv: {
+ const index = std.mem.indexOfScalarPos(u8, self.string, self.index, ';') orelse {
+ self.index = self.string.len;
+ return null;
+ };
+ self.string[index] = 0;
+ const kv = self.string[self.index..index :0];
+ self.index = index + 1;
+ break :kv kv;
+ };
+
+ // If we have an empty item, we return a null key and value.
+ //
+ // This allows for trailing semicolons, but also lets us parse
+ // (or rather, ignore) empty fields; for example `a=b;;e=f`.
+ if (kv.len < 1) return .{
+ .key = null,
+ .value = null,
+ };
+
+ const key = key: {
+ const index = std.mem.indexOfScalar(u8, kv, '=') orelse {
+ // If there is no '=' return entire `kv` string as the key and
+ // a null value.
+ return .{
+ .key = kv,
+ .value = null,
+ };
+ };
+ kv[index] = 0;
+ const key = kv[0..index :0];
+ break :key key;
+ };
+
+ const value = kv[key.len + 1 .. :0];
+
+ return .{
+ .key = key,
+ .value = value,
+ };
+ }
+};
+
+test "OSC 133: prompt_start" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "133;A";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end(null).?.*;
+ try testing.expect(cmd == .prompt_start);
+ try testing.expect(cmd.prompt_start.aid == null);
+ try testing.expect(cmd.prompt_start.redraw);
+}
+
+test "OSC 133: prompt_start with single option" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "133;A;aid=14";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end(null).?.*;
+ try testing.expect(cmd == .prompt_start);
+ try testing.expectEqualStrings("14", cmd.prompt_start.aid.?);
+}
+
+test "OSC 133: prompt_start with '=' in aid" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "133;A;aid=a=b;redraw=0";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end(null).?.*;
+ try testing.expect(cmd == .prompt_start);
+ try testing.expectEqualStrings("a=b", cmd.prompt_start.aid.?);
+ try testing.expect(!cmd.prompt_start.redraw);
+}
+
+test "OSC 133: prompt_start with redraw disabled" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "133;A;redraw=0";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end(null).?.*;
+ try testing.expect(cmd == .prompt_start);
+ try testing.expect(!cmd.prompt_start.redraw);
+}
+
+test "OSC 133: prompt_start with redraw invalid value" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "133;A;redraw=42";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end(null).?.*;
+ try testing.expect(cmd == .prompt_start);
+ try testing.expect(cmd.prompt_start.redraw);
+ try testing.expect(cmd.prompt_start.kind == .primary);
+}
+
+test "OSC 133: prompt_start with continuation" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "133;A;k=c";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end(null).?.*;
+ try testing.expect(cmd == .prompt_start);
+ try testing.expect(cmd.prompt_start.kind == .continuation);
+}
+
+test "OSC 133: prompt_start with secondary" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "133;A;k=s";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end(null).?.*;
+ try testing.expect(cmd == .prompt_start);
+ try testing.expect(cmd.prompt_start.kind == .secondary);
+}
+
+test "OSC 133: prompt_start with special_key" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "133;A;special_key=1";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end(null).?.*;
+ try testing.expect(cmd == .prompt_start);
+ try testing.expect(cmd.prompt_start.special_key == true);
+}
+
+test "OSC 133: prompt_start with special_key invalid" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "133;A;special_key=bobr";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end(null).?.*;
+ try testing.expect(cmd == .prompt_start);
+ try testing.expect(cmd.prompt_start.special_key == false);
+}
+
+test "OSC 133: prompt_start with special_key 0" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "133;A;special_key=0";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end(null).?.*;
+ try testing.expect(cmd == .prompt_start);
+ try testing.expect(cmd.prompt_start.special_key == false);
+}
+
+test "OSC 133: prompt_start with special_key empty" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "133;A;special_key=";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end(null).?.*;
+ try testing.expect(cmd == .prompt_start);
+ try testing.expect(cmd.prompt_start.special_key == false);
+}
+
+test "OSC 133: prompt_start with trailing ;" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "133;A;";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end(null).?.*;
+ try testing.expect(cmd == .prompt_start);
+}
+
+test "OSC 133: prompt_start with click_events true" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "133;A;click_events=1";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end(null).?.*;
+ try testing.expect(cmd == .prompt_start);
+ try testing.expect(cmd.prompt_start.click_events == true);
+}
+
+test "OSC 133: prompt_start with click_events false" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "133;A;click_events=0";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end(null).?.*;
+ try testing.expect(cmd == .prompt_start);
+ try testing.expect(cmd.prompt_start.click_events == false);
+}
+
+test "OSC 133: prompt_start with click_events empty" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "133;A;click_events=";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end(null).?.*;
+ try testing.expect(cmd == .prompt_start);
+ try testing.expect(cmd.prompt_start.click_events == false);
+}
+
+test "OSC 133: prompt_start with click_events bare key" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "133;A;click_events";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end(null).?.*;
+ try testing.expect(cmd == .prompt_start);
+ try testing.expect(cmd.prompt_start.click_events == false);
+}
+
+test "OSC 133: prompt_start with invalid bare key" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "133;A;barekey";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end(null).?.*;
+ try testing.expect(cmd == .prompt_start);
+ try testing.expect(cmd.prompt_start.aid == null);
+ try testing.expectEqual(.primary, cmd.prompt_start.kind);
+ try testing.expect(cmd.prompt_start.redraw == true);
+ try testing.expect(cmd.prompt_start.special_key == false);
+ try testing.expect(cmd.prompt_start.click_events == false);
+}
+
+test "OSC 133: end_of_command no exit code" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "133;D";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end(null).?.*;
+ try testing.expect(cmd == .end_of_command);
+}
+
+test "OSC 133: end_of_command with exit code" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "133;D;25";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end(null).?.*;
+ try testing.expect(cmd == .end_of_command);
+ try testing.expectEqual(@as(u8, 25), cmd.end_of_command.exit_code.?);
+}
+
+test "OSC 133: prompt_end" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "133;B";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end(null).?.*;
+ try testing.expect(cmd == .prompt_end);
+}
+
+test "OSC 133: end_of_input" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "133;C";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end(null).?.*;
+ try testing.expect(cmd == .end_of_input);
+}
+
+test "OSC 133: end_of_input with cmdline 1" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "133;C;cmdline=echo bobr kurwa";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end(null).?.*;
+ try testing.expect(cmd == .end_of_input);
+ try testing.expect(cmd.end_of_input.cmdline != null);
+ try testing.expectEqualStrings("echo bobr kurwa", cmd.end_of_input.cmdline.?);
+}
+
+test "OSC 133: end_of_input with cmdline 2" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "133;C;cmdline=echo bobr\\ kurwa";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end(null).?.*;
+ try testing.expect(cmd == .end_of_input);
+ try testing.expect(cmd.end_of_input.cmdline != null);
+ try testing.expectEqualStrings("echo bobr kurwa", cmd.end_of_input.cmdline.?);
+}
+
+test "OSC 133: end_of_input with cmdline 3" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "133;C;cmdline=echo bobr\\nkurwa";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end(null).?.*;
+ try testing.expect(cmd == .end_of_input);
+ try testing.expect(cmd.end_of_input.cmdline != null);
+ try testing.expectEqualStrings("echo bobr\nkurwa", cmd.end_of_input.cmdline.?);
+}
+
+test "OSC 133: end_of_input with cmdline 4" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "133;C;cmdline=$'echo bobr kurwa'";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end(null).?.*;
+ try testing.expect(cmd == .end_of_input);
+ try testing.expect(cmd.end_of_input.cmdline != null);
+ try testing.expectEqualStrings("echo bobr kurwa", cmd.end_of_input.cmdline.?);
+}
+
+test "OSC 133: end_of_input with cmdline 5" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "133;C;cmdline='echo bobr kurwa'";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end(null).?.*;
+ try testing.expect(cmd == .end_of_input);
+ try testing.expect(cmd.end_of_input.cmdline != null);
+ try testing.expectEqualStrings("echo bobr kurwa", cmd.end_of_input.cmdline.?);
+}
+
+test "OSC 133: end_of_input with cmdline 6" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "133;C;cmdline='echo bobr kurwa";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end(null).?.*;
+ try testing.expect(cmd == .end_of_input);
+ try testing.expect(cmd.end_of_input.cmdline == null);
+}
+
+test "OSC 133: end_of_input with cmdline 7" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "133;C;cmdline=$'echo bobr kurwa";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end(null).?.*;
+ try testing.expect(cmd == .end_of_input);
+ try testing.expect(cmd.end_of_input.cmdline == null);
+}
+
+test "OSC 133: end_of_input with cmdline 8" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "133;C;cmdline=$'";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end(null).?.*;
+ try testing.expect(cmd == .end_of_input);
+ try testing.expect(cmd.end_of_input.cmdline == null);
+}
+
+test "OSC 133: end_of_input with cmdline 9" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "133;C;cmdline=$'";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end(null).?.*;
+ try testing.expect(cmd == .end_of_input);
+ try testing.expect(cmd.end_of_input.cmdline == null);
+}
+
+test "OSC 133: end_of_input with cmdline 10" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "133;C;cmdline=";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end(null).?.*;
+ try testing.expect(cmd == .end_of_input);
+ try testing.expect(cmd.end_of_input.cmdline != null);
+ try testing.expectEqualStrings("", cmd.end_of_input.cmdline.?);
+}
+
+test "OSC 133: end_of_input with cmdline_url 1" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "133;C;cmdline_url=echo bobr kurwa";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end(null).?.*;
+ try testing.expect(cmd == .end_of_input);
+ try testing.expect(cmd.end_of_input.cmdline != null);
+ try testing.expectEqualStrings("echo bobr kurwa", cmd.end_of_input.cmdline.?);
+}
+
+test "OSC 133: end_of_input with cmdline_url 2" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "133;C;cmdline_url=echo bobr%20kurwa";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end(null).?.*;
+ try testing.expect(cmd == .end_of_input);
+ try testing.expect(cmd.end_of_input.cmdline != null);
+ try testing.expectEqualStrings("echo bobr kurwa", cmd.end_of_input.cmdline.?);
+}
+
+test "OSC 133: end_of_input with cmdline_url 3" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "133;C;cmdline_url=echo bobr%3bkurwa";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end(null).?.*;
+ try testing.expect(cmd == .end_of_input);
+ try testing.expect(cmd.end_of_input.cmdline != null);
+ try testing.expectEqualStrings("echo bobr;kurwa", cmd.end_of_input.cmdline.?);
+}
+
+test "OSC 133: end_of_input with cmdline_url 4" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "133;C;cmdline_url=echo bobr%3kurwa";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end(null).?.*;
+ try testing.expect(cmd == .end_of_input);
+ try testing.expect(cmd.end_of_input.cmdline == null);
+}
+
+test "OSC 133: end_of_input with cmdline_url 5" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "133;C;cmdline_url=echo bobr%kurwa";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end(null).?.*;
+ try testing.expect(cmd == .end_of_input);
+ try testing.expect(cmd.end_of_input.cmdline == null);
+}
+
+test "OSC 133: end_of_input with cmdline_url 6" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "133;C;cmdline_url=echo bobr%kurwa";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end(null).?.*;
+ try testing.expect(cmd == .end_of_input);
+ try testing.expect(cmd.end_of_input.cmdline == null);
+}
+
+test "OSC 133: end_of_input with cmdline_url 7" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "133;C;cmdline_url=echo bobr kurwa%20";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end(null).?.*;
+ try testing.expect(cmd == .end_of_input);
+ try testing.expect(cmd.end_of_input.cmdline != null);
+ try testing.expectEqualStrings("echo bobr kurwa ", cmd.end_of_input.cmdline.?);
+}
+
+test "OSC 133: end_of_input with cmdline_url 8" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "133;C;cmdline_url=echo bobr kurwa%2";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end(null).?.*;
+ try testing.expect(cmd == .end_of_input);
+ try testing.expect(cmd.end_of_input.cmdline == null);
+}
+
+test "OSC 133: end_of_input with cmdline_url 9" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "133;C;cmdline_url=echo bobr kurwa%2";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end(null).?.*;
+ try testing.expect(cmd == .end_of_input);
+ try testing.expect(cmd.end_of_input.cmdline == null);
+}
+
+test "OSC 133: end_of_input with bare key" {
+ const testing = std.testing;
+
+ var p: Parser = .init(null);
+
+ const input = "133;C;cmdline_url";
+ for (input) |ch| p.next(ch);
+
+ const cmd = p.end(null).?.*;
+ try testing.expect(cmd == .end_of_input);
+ try testing.expect(cmd.end_of_input.cmdline == null);
+}
diff --git a/src/terminal/page.zig b/src/terminal/page.zig
index 124ff2545..6a5958681 100644
--- a/src/terminal/page.zig
+++ b/src/terminal/page.zig
@@ -196,7 +196,8 @@ pub const Page = struct {
// We need to go through and initialize all the rows so that
// they point to a valid offset into the cells, since the rows
// zero-initialized aren't valid.
- const cells_ptr = cells.ptr(buf)[0 .. cap.cols * cap.rows];
+ const cells_len = @as(usize, cap.cols) * @as(usize, cap.rows);
+ const cells_ptr = cells.ptr(buf)[0..cells_len];
for (rows.ptr(buf)[0..cap.rows], 0..) |*row, y| {
const start = y * cap.cols;
row.* = .{
@@ -632,6 +633,114 @@ pub const Page = struct {
HyperlinkError ||
GraphemeError;
+ /// Compute the exact capacity required to store a range of rows from
+ /// this page.
+ ///
+ /// The returned capacity will have the same number of columns as this
+ /// page and the number of rows equal to the range given. The returned
+ /// capacity is by definition strictly less than or equal to this
+ /// page's capacity, so the layout is guaranteed to succeed.
+ ///
+ /// Preconditions:
+ /// - Range must be at least 1 row
+ /// - Start and end must be valid for this page
+ pub fn exactRowCapacity(
+ self: *const Page,
+ y_start: usize,
+ y_end: usize,
+ ) Capacity {
+ assert(y_start < y_end);
+ assert(y_end <= self.size.rows);
+
+ // Track unique IDs using a bitset. Both style IDs and hyperlink IDs
+ // are CellCountInt (u16), so we reuse this set for both to save
+ // stack memory (~8KB instead of ~16KB).
+ const CellCountSet = std.StaticBitSet(std.math.maxInt(size.CellCountInt) + 1);
+ comptime assert(size.StyleCountInt == size.CellCountInt);
+ comptime assert(size.HyperlinkCountInt == size.CellCountInt);
+
+ // Accumulators
+ var id_set: CellCountSet = .initEmpty();
+ var grapheme_bytes: usize = 0;
+ var string_bytes: usize = 0;
+
+ // First pass: count styles and grapheme bytes
+ const rows = self.rows.ptr(self.memory)[y_start..y_end];
+ for (rows) |*row| {
+ const cells = row.cells.ptr(self.memory)[0..self.size.cols];
+ for (cells) |*cell| {
+ if (cell.style_id != stylepkg.default_id) {
+ id_set.set(cell.style_id);
+ }
+
+ if (cell.hasGrapheme()) {
+ if (self.lookupGrapheme(cell)) |cps| {
+ grapheme_bytes += GraphemeAlloc.bytesRequired(u21, cps.len);
+ }
+ }
+ }
+ }
+ const styles_cap = StyleSet.capacityForCount(id_set.count());
+
+ // Second pass: count hyperlinks and string bytes
+ // We count both unique hyperlinks (for hyperlink_set) and total
+ // hyperlink cells (for hyperlink_map capacity).
+ id_set = .initEmpty();
+ var hyperlink_cells: usize = 0;
+ for (rows) |*row| {
+ const cells = row.cells.ptr(self.memory)[0..self.size.cols];
+ for (cells) |*cell| {
+ if (cell.hyperlink) {
+ hyperlink_cells += 1;
+ if (self.lookupHyperlink(cell)) |id| {
+ // Only count each unique hyperlink once for set sizing
+ if (!id_set.isSet(id)) {
+ id_set.set(id);
+
+ // Get the hyperlink entry to compute string bytes
+ const entry = self.hyperlink_set.get(self.memory, id);
+ string_bytes += StringAlloc.bytesRequired(u8, entry.uri.len);
+
+ switch (entry.id) {
+ .implicit => {},
+ .explicit => |slice| {
+ string_bytes += StringAlloc.bytesRequired(u8, slice.len);
+ },
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // The hyperlink_map capacity in layout() is computed as:
+ // hyperlink_count * hyperlink_cell_multiplier (rounded to power of 2)
+ // We need enough hyperlink_bytes so that when layout() computes
+ // the map capacity, it can accommodate all hyperlink cells. This
+ // is unit tested.
+ const hyperlink_cap = cap: {
+ const hyperlink_count = id_set.count();
+ const hyperlink_set_cap = hyperlink.Set.capacityForCount(hyperlink_count);
+ const hyperlink_map_min = std.math.divCeil(
+ usize,
+ hyperlink_cells,
+ hyperlink_cell_multiplier,
+ ) catch 0;
+ break :cap @max(hyperlink_set_cap, hyperlink_map_min);
+ };
+
+ // All the intCasts below are safe because we should have a
+ // capacity strictly less than or equal to this page's capacity.
+ return .{
+ .cols = self.size.cols,
+ .rows = @intCast(y_end - y_start),
+ .styles = @intCast(styles_cap),
+ .grapheme_bytes = @intCast(grapheme_bytes),
+ .hyperlink_bytes = @intCast(hyperlink_cap * @sizeOf(hyperlink.Set.Item)),
+ .string_bytes = @intCast(string_bytes),
+ };
+ }
+
/// Clone the contents of another page into this page. The capacities
/// can be different, but the size of the other page must fit into
/// this page.
@@ -1556,7 +1665,7 @@ pub const Page = struct {
const rows_start = 0;
const rows_end: usize = rows_start + (rows_count * @sizeOf(Row));
- const cells_count: usize = @intCast(cap.cols * cap.rows);
+ const cells_count: usize = @as(usize, cap.cols) * @as(usize, cap.rows);
const cells_start = alignForward(usize, rows_end, @alignOf(Cell));
const cells_end = cells_start + (cells_count * @sizeOf(Cell));
@@ -1568,7 +1677,13 @@ pub const Page = struct {
const grapheme_alloc_start = alignForward(usize, styles_end, GraphemeAlloc.base_align.toByteUnits());
const grapheme_alloc_end = grapheme_alloc_start + grapheme_alloc_layout.total_size;
- const grapheme_count = @divFloor(cap.grapheme_bytes, grapheme_chunk);
+ const grapheme_count: usize = count: {
+ if (cap.grapheme_bytes == 0) break :count 0;
+ // Use divCeil to match GraphemeAlloc.layout() which uses alignForward,
+ // ensuring grapheme_map has capacity when grapheme_alloc has chunks.
+ const base = std.math.divCeil(usize, cap.grapheme_bytes, grapheme_chunk) catch unreachable;
+ break :count std.math.ceilPowerOfTwo(usize, base) catch unreachable;
+ };
const grapheme_map_layout = GraphemeMap.layout(@intCast(grapheme_count));
const grapheme_map_start = alignForward(usize, grapheme_alloc_end, GraphemeMap.base_align.toByteUnits());
const grapheme_map_end = grapheme_map_start + grapheme_map_layout.total_size;
@@ -1638,67 +1753,74 @@ pub const Size = struct {
};
/// Capacity of this page.
+///
+/// This capacity can be maxed out (every field max) and still fit
+/// within a 64-bit memory space. If you need more than this, you will
+/// need to split data across separate pages.
+///
+/// For 32-bit systems, it is possible to overflow the addressable
+/// space and this is something we still need to address in the future
+/// likely by limiting the maximum capacity on 32-bit systems further.
pub const Capacity = struct {
/// Number of columns and rows we can know about.
cols: size.CellCountInt,
rows: size.CellCountInt,
/// Number of unique styles that can be used on this page.
- styles: usize = 16,
+ styles: size.StyleCountInt = 16,
/// Number of bytes to allocate for hyperlink data. Note that the
/// amount of data used for hyperlinks in total is more than this because
/// hyperlinks use string data as well as a small amount of lookup metadata.
/// This number is a rough approximation.
- hyperlink_bytes: usize = hyperlink_bytes_default,
+ hyperlink_bytes: size.HyperlinkCountInt = hyperlink_bytes_default,
/// Number of bytes to allocate for grapheme data.
- grapheme_bytes: usize = grapheme_bytes_default,
+ grapheme_bytes: size.GraphemeBytesInt = grapheme_bytes_default,
/// Number of bytes to allocate for strings.
- string_bytes: usize = string_bytes_default,
+ string_bytes: size.StringBytesInt = string_bytes_default,
pub const Adjustment = struct {
cols: ?size.CellCountInt = null,
};
+ /// Returns the maximum number of columns that can be used with this
+ /// capacity while still fitting at least one row. Returns null if even
+ /// a single column cannot fit (which would indicate an unusable capacity).
+ ///
+ /// Note that this is the maximum number of columns that never increases
+ /// the amount of memory the original capacity will take. If you modify
+ /// the original capacity to add rows, then you can fit more columns.
+ pub fn maxCols(self: Capacity) ?size.CellCountInt {
+ const available_bits = self.availableBitsForGrid();
+
+ // If we can't even fit the row metadata, return null
+ if (available_bits <= @bitSizeOf(Row)) return null;
+
+ // We do the math of how many columns we can fit in the remaining
+ // bits ignoring the metadata of a row.
+ const remaining_bits = available_bits - @bitSizeOf(Row);
+ const max_cols = remaining_bits / @bitSizeOf(Cell);
+
+ // Clamp to CellCountInt max
+ return @min(std.math.maxInt(size.CellCountInt), max_cols);
+ }
+
/// Adjust the capacity parameters while retaining the same total size.
+ ///
/// Adjustments always happen by limiting the rows in the page. Everything
/// else can grow. If it is impossible to achieve the desired adjustment,
/// OutOfMemory is returned.
pub fn adjust(self: Capacity, req: Adjustment) Allocator.Error!Capacity {
var adjusted = self;
if (req.cols) |cols| {
- // The math below only works if there is no alignment gap between
- // the end of the rows array and the start of the cells array.
- //
- // To guarantee this, we assert that Row's size is a multiple of
- // Cell's alignment, so that any length array of Rows will end on
- // a valid alignment for the start of the Cell array.
- assert(@sizeOf(Row) % @alignOf(Cell) == 0);
-
- const layout = Page.layout(self);
-
- // In order to determine the amount of space in the page available
- // for rows & cells (which will allow us to calculate the number of
- // rows we can fit at a certain column width) we need to layout the
- // "meta" members of the page (i.e. everything else) from the end.
- const hyperlink_map_start = alignBackward(usize, layout.total_size - layout.hyperlink_map_layout.total_size, hyperlink.Map.base_align.toByteUnits());
- const hyperlink_set_start = alignBackward(usize, hyperlink_map_start - layout.hyperlink_set_layout.total_size, hyperlink.Set.base_align.toByteUnits());
- const string_alloc_start = alignBackward(usize, hyperlink_set_start - layout.string_alloc_layout.total_size, StringAlloc.base_align.toByteUnits());
- const grapheme_map_start = alignBackward(usize, string_alloc_start - layout.grapheme_map_layout.total_size, GraphemeMap.base_align.toByteUnits());
- const grapheme_alloc_start = alignBackward(usize, grapheme_map_start - layout.grapheme_alloc_layout.total_size, GraphemeAlloc.base_align.toByteUnits());
- const styles_start = alignBackward(usize, grapheme_alloc_start - layout.styles_layout.total_size, StyleSet.base_align.toByteUnits());
+ const available_bits = self.availableBitsForGrid();
// The size per row is:
// - The row metadata itself
// - The cells per row (n=cols)
- const bits_per_row: usize = size: {
- var bits: usize = @bitSizeOf(Row); // Row metadata
- bits += @bitSizeOf(Cell) * @as(usize, @intCast(cols)); // Cells (n=cols)
- break :size bits;
- };
- const available_bits: usize = styles_start * 8;
+ const bits_per_row: usize = @bitSizeOf(Row) + @bitSizeOf(Cell) * @as(usize, @intCast(cols));
const new_rows: usize = @divFloor(available_bits, bits_per_row);
// If our rows go to zero then we can't fit any row metadata
@@ -1711,6 +1833,34 @@ pub const Capacity = struct {
return adjusted;
}
+
+ /// Computes the number of bits available for rows and cells in the page.
+ ///
+ /// This is done by laying out the "meta" members (styles, graphemes,
+ /// hyperlinks, strings) from the end of the page and finding where they
+ /// start, which gives us the space available for rows and cells.
+ fn availableBitsForGrid(self: Capacity) usize {
+ // The math below only works if there is no alignment gap between
+ // the end of the rows array and the start of the cells array.
+ //
+ // To guarantee this, we assert that Row's size is a multiple of
+ // Cell's alignment, so that any length array of Rows will end on
+ // a valid alignment for the start of the Cell array.
+ assert(@sizeOf(Row) % @alignOf(Cell) == 0);
+
+ const l = Page.layout(self);
+
+ // Layout meta members from the end to find styles_start
+ const hyperlink_map_start = alignBackward(usize, l.total_size - l.hyperlink_map_layout.total_size, hyperlink.Map.base_align.toByteUnits());
+ const hyperlink_set_start = alignBackward(usize, hyperlink_map_start - l.hyperlink_set_layout.total_size, hyperlink.Set.base_align.toByteUnits());
+ const string_alloc_start = alignBackward(usize, hyperlink_set_start - l.string_alloc_layout.total_size, StringAlloc.base_align.toByteUnits());
+ const grapheme_map_start = alignBackward(usize, string_alloc_start - l.grapheme_map_layout.total_size, GraphemeMap.base_align.toByteUnits());
+ const grapheme_alloc_start = alignBackward(usize, grapheme_map_start - l.grapheme_alloc_layout.total_size, GraphemeAlloc.base_align.toByteUnits());
+ const styles_start = alignBackward(usize, grapheme_alloc_start - l.styles_layout.total_size, StyleSet.base_align.toByteUnits());
+
+ // Multiply by 8 to convert bytes to bits
+ return styles_start * 8;
+ }
};
pub const Row = packed struct(u64) {
@@ -1997,6 +2147,21 @@ pub const Cell = packed struct(u64) {
// //const pages = total_size / std.heap.page_size_min;
// }
+test "Page.layout can take a maxed capacity" {
+ // Our intention is for a maxed-out capacity to always fit
+ // within a page layout without triggering runtime safety on any
+ // overflow. This simplifies some of our handling downstream of the
+ // call (relevant to: https://github.com/ghostty-org/ghostty/issues/10258)
+ var cap: Capacity = undefined;
+ inline for (@typeInfo(Capacity).@"struct".fields) |field| {
+ @field(cap, field.name) = std.math.maxInt(field.type);
+ }
+
+ // Note that a max capacity will exceed our max_page_size so we
+ // can't init a page with it, but it should layout.
+ _ = Page.layout(cap);
+}
+
test "Cell is zero by default" {
const cell = Cell.init(0);
const cell_int: u64 = @bitCast(cell);
@@ -2070,6 +2235,40 @@ test "Page capacity adjust cols too high" {
);
}
+test "Capacity maxCols basic" {
+ const cap = std_capacity;
+ const max = cap.maxCols().?;
+
+ // maxCols should be >= current cols (since current capacity is valid)
+ try testing.expect(max >= cap.cols);
+
+ // Adjusting to maxCols should succeed with at least 1 row
+ const adjusted = try cap.adjust(.{ .cols = max });
+ try testing.expect(adjusted.rows >= 1);
+
+ // Adjusting to maxCols + 1 should fail
+ try testing.expectError(
+ error.OutOfMemory,
+ cap.adjust(.{ .cols = max + 1 }),
+ );
+}
+
+test "Capacity maxCols preserves total size" {
+ const cap = std_capacity;
+ const original_size = Page.layout(cap).total_size;
+ const max = cap.maxCols().?;
+ const adjusted = try cap.adjust(.{ .cols = max });
+ const adjusted_size = Page.layout(adjusted).total_size;
+ try testing.expectEqual(original_size, adjusted_size);
+}
+
+test "Capacity maxCols with 1 row exactly" {
+ const cap = std_capacity;
+ const max = cap.maxCols().?;
+ const adjusted = try cap.adjust(.{ .cols = max });
+ try testing.expectEqual(@as(size.CellCountInt, 1), adjusted.rows);
+}
+
test "Page init" {
var page = try Page.init(.{
.cols = 120,
@@ -3129,3 +3328,512 @@ test "Page verifyIntegrity zero cols" {
page.verifyIntegrity(testing.allocator),
);
}
+
+test "Page exactRowCapacity empty rows" {
+ var page = try Page.init(.{
+ .cols = 10,
+ .rows = 10,
+ .styles = 8,
+ .hyperlink_bytes = 32 * @sizeOf(hyperlink.Set.Item),
+ .string_bytes = 512,
+ });
+ defer page.deinit();
+
+ // Empty page: all capacity fields should be 0 (except cols/rows)
+ const cap = page.exactRowCapacity(0, 5);
+ try testing.expectEqual(10, cap.cols);
+ try testing.expectEqual(5, cap.rows);
+ try testing.expectEqual(0, cap.styles);
+ try testing.expectEqual(0, cap.grapheme_bytes);
+ try testing.expectEqual(0, cap.hyperlink_bytes);
+ try testing.expectEqual(0, cap.string_bytes);
+}
+
+test "Page exactRowCapacity styles" {
+ var page = try Page.init(.{
+ .cols = 10,
+ .rows = 10,
+ .styles = 8,
+ });
+ defer page.deinit();
+
+ // No styles: capacity should be 0
+ {
+ const cap = page.exactRowCapacity(0, 5);
+ try testing.expectEqual(0, cap.styles);
+ }
+
+ // Add one style to a cell
+ const style1_id = try page.styles.add(page.memory, .{ .flags = .{ .bold = true } });
+ {
+ const rac = page.getRowAndCell(0, 0);
+ rac.row.styled = true;
+ rac.cell.style_id = style1_id;
+ }
+
+ // One unique style - capacity accounts for load factor
+ const cap_one_style = page.exactRowCapacity(0, 5);
+ {
+ try testing.expectEqual(StyleSet.capacityForCount(1), cap_one_style.styles);
+ }
+
+ // Add same style to another cell (duplicate) - capacity unchanged
+ {
+ const rac = page.getRowAndCell(1, 0);
+ rac.cell.style_id = style1_id;
+ }
+ {
+ const cap = page.exactRowCapacity(0, 5);
+ try testing.expectEqual(cap_one_style.styles, cap.styles);
+ }
+
+ // Add a different style
+ const style2_id = try page.styles.add(page.memory, .{ .flags = .{ .italic = true } });
+ {
+ const rac = page.getRowAndCell(2, 0);
+ rac.cell.style_id = style2_id;
+ }
+
+ // Two unique styles - capacity accounts for load factor
+ const cap_two_styles = page.exactRowCapacity(0, 5);
+ {
+ try testing.expectEqual(StyleSet.capacityForCount(2), cap_two_styles.styles);
+ try testing.expect(cap_two_styles.styles > cap_one_style.styles);
+ }
+
+ // Style outside the row range should not be counted
+ {
+ const rac = page.getRowAndCell(0, 7);
+ rac.row.styled = true;
+ rac.cell.style_id = try page.styles.add(page.memory, .{ .flags = .{ .underline = .single } });
+ }
+ {
+ const cap = page.exactRowCapacity(0, 5);
+ try testing.expectEqual(cap_two_styles.styles, cap.styles);
+ }
+
+ // Full range includes the new style
+ {
+ const cap = page.exactRowCapacity(0, 10);
+ try testing.expectEqual(StyleSet.capacityForCount(3), cap.styles);
+ }
+
+ // Verify clone works with exact capacity and produces same result
+ {
+ const cap = page.exactRowCapacity(0, 5);
+ var cloned = try Page.init(cap);
+ defer cloned.deinit();
+ for (0..5) |y| {
+ const src_row = &page.rows.ptr(page.memory)[y];
+ const dst_row = &cloned.rows.ptr(cloned.memory)[y];
+ try cloned.cloneRowFrom(&page, dst_row, src_row);
+ }
+ const cloned_cap = cloned.exactRowCapacity(0, 5);
+ try testing.expectEqual(cap, cloned_cap);
+ }
+}
+
+test "Page exactRowCapacity single style clone" {
+ // Regression test: verify a single style can be cloned with exact capacity.
+ // This tests that capacityForCount properly accounts for ID 0 being reserved.
+ var page = try Page.init(.{
+ .cols = 10,
+ .rows = 2,
+ .styles = 8,
+ });
+ defer page.deinit();
+
+ // Add exactly one style to row 0
+ const style_id = try page.styles.add(page.memory, .{ .flags = .{ .bold = true } });
+ {
+ const rac = page.getRowAndCell(0, 0);
+ rac.row.styled = true;
+ rac.cell.style_id = style_id;
+ }
+
+ // exactRowCapacity for just row 0 should give capacity for 1 style
+ const cap = page.exactRowCapacity(0, 1);
+ try testing.expectEqual(StyleSet.capacityForCount(1), cap.styles);
+
+ // Create a new page with exact capacity and clone
+ var cloned = try Page.init(cap);
+ defer cloned.deinit();
+
+ const src_row = &page.rows.ptr(page.memory)[0];
+ const dst_row = &cloned.rows.ptr(cloned.memory)[0];
+
+ // This must not fail with StyleSetOutOfMemory
+ try cloned.cloneRowFrom(&page, dst_row, src_row);
+
+ // Verify the style was cloned correctly
+ const cloned_cell = &cloned.rows.ptr(cloned.memory)[0].cells.ptr(cloned.memory)[0];
+ try testing.expect(cloned_cell.style_id != stylepkg.default_id);
+}
+
+test "Page exactRowCapacity styles max single row" {
+ var page = try Page.init(.{
+ .cols = std.math.maxInt(size.CellCountInt),
+ .rows = 1,
+ .styles = std.math.maxInt(size.StyleCountInt),
+ });
+ defer page.deinit();
+
+ // Style our first row
+ const row = &page.rows.ptr(page.memory)[0];
+ row.styled = true;
+
+ // Fill cells with styles until we get OOM, but limit to a reasonable count
+ // to avoid overflow when computing capacityForCount near maxInt
+ const cells = row.cells.ptr(page.memory)[0..page.size.cols];
+ var count: usize = 0;
+ const max_count: usize = 1000; // Limit to avoid overflow in capacity calculation
+ for (cells, 0..) |*cell, i| {
+ if (count >= max_count) break;
+ const style_id = page.styles.add(page.memory, .{
+ .fg_color = .{ .rgb = .{
+ .r = @intCast(i & 0xFF),
+ .g = @intCast((i >> 8) & 0xFF),
+ .b = 0,
+ } },
+ }) catch break;
+ cell.style_id = style_id;
+ count += 1;
+ }
+
+ // Verify we added a meaningful number of styles
+ try testing.expect(count > 0);
+
+ // Capacity should be at least count (adjusted for load factor)
+ const cap = page.exactRowCapacity(0, 1);
+ try testing.expectEqual(StyleSet.capacityForCount(count), cap.styles);
+}
+
+test "Page exactRowCapacity grapheme_bytes" {
+ var page = try Page.init(.{
+ .cols = 10,
+ .rows = 10,
+ .styles = 8,
+ });
+ defer page.deinit();
+
+ // No graphemes: capacity should be 0
+ {
+ const cap = page.exactRowCapacity(0, 5);
+ try testing.expectEqual(0, cap.grapheme_bytes);
+ }
+
+ // Add one grapheme (1 codepoint) to a cell - rounds up to grapheme_chunk
+ {
+ const rac = page.getRowAndCell(0, 0);
+ rac.cell.* = .init('a');
+ try page.appendGrapheme(rac.row, rac.cell, 0x0301); // combining acute accent
+ }
+ {
+ const cap = page.exactRowCapacity(0, 5);
+ // 1 codepoint = 4 bytes, rounds up to grapheme_chunk (16)
+ try testing.expectEqual(grapheme_chunk, cap.grapheme_bytes);
+ }
+
+ // Add another grapheme to a different cell - should sum
+ {
+ const rac = page.getRowAndCell(1, 0);
+ rac.cell.* = .init('e');
+ try page.appendGrapheme(rac.row, rac.cell, 0x0300); // combining grave accent
+ }
+ {
+ const cap = page.exactRowCapacity(0, 5);
+ // 2 graphemes, each 1 codepoint = 2 * grapheme_chunk
+ try testing.expectEqual(grapheme_chunk * 2, cap.grapheme_bytes);
+ }
+
+ // Add a larger grapheme (multiple codepoints) that fits in one chunk
+ {
+ const rac = page.getRowAndCell(2, 0);
+ rac.cell.* = .init('o');
+ try page.appendGrapheme(rac.row, rac.cell, 0x0301);
+ try page.appendGrapheme(rac.row, rac.cell, 0x0302);
+ try page.appendGrapheme(rac.row, rac.cell, 0x0303);
+ }
+ {
+ const cap = page.exactRowCapacity(0, 5);
+ // First two cells: 2 * grapheme_chunk
+ // Third cell: 3 codepoints = 12 bytes, rounds up to grapheme_chunk
+ try testing.expectEqual(grapheme_chunk * 3, cap.grapheme_bytes);
+ }
+
+ // Grapheme outside the row range should not be counted
+ {
+ const rac = page.getRowAndCell(0, 7);
+ rac.cell.* = .init('x');
+ try page.appendGrapheme(rac.row, rac.cell, 0x0304);
+ }
+ {
+ const cap = page.exactRowCapacity(0, 5);
+ try testing.expectEqual(grapheme_chunk * 3, cap.grapheme_bytes);
+ }
+
+ // Full range includes the new grapheme
+ {
+ const cap = page.exactRowCapacity(0, 10);
+ try testing.expectEqual(grapheme_chunk * 4, cap.grapheme_bytes);
+ }
+
+ // Verify clone works with exact capacity and produces same result
+ {
+ const cap = page.exactRowCapacity(0, 5);
+ var cloned = try Page.init(cap);
+ defer cloned.deinit();
+ for (0..5) |y| {
+ const src_row = &page.rows.ptr(page.memory)[y];
+ const dst_row = &cloned.rows.ptr(cloned.memory)[y];
+ try cloned.cloneRowFrom(&page, dst_row, src_row);
+ }
+ const cloned_cap = cloned.exactRowCapacity(0, 5);
+ try testing.expectEqual(cap, cloned_cap);
+ }
+}
+
+test "Page exactRowCapacity grapheme_bytes larger than chunk" {
+ var page = try Page.init(.{
+ .cols = 10,
+ .rows = 10,
+ .styles = 8,
+ });
+ defer page.deinit();
+
+ // Add a grapheme larger than one chunk (grapheme_chunk_len = 4 codepoints)
+ const rac = page.getRowAndCell(0, 0);
+ rac.cell.* = .init('a');
+
+ // Add 6 codepoints - requires 2 chunks (6 * 4 = 24 bytes, rounds up to 32)
+ for (0..6) |i| {
+ try page.appendGrapheme(rac.row, rac.cell, @intCast(0x0300 + i));
+ }
+
+ const cap = page.exactRowCapacity(0, 1);
+ // 6 codepoints = 24 bytes, alignForward(24, 16) = 32
+ try testing.expectEqual(32, cap.grapheme_bytes);
+
+ // Verify clone works with exact capacity and produces same result
+ var cloned = try Page.init(cap);
+ defer cloned.deinit();
+ const src_row = &page.rows.ptr(page.memory)[0];
+ const dst_row = &cloned.rows.ptr(cloned.memory)[0];
+ try cloned.cloneRowFrom(&page, dst_row, src_row);
+ const cloned_cap = cloned.exactRowCapacity(0, 1);
+ try testing.expectEqual(cap, cloned_cap);
+}
+
+test "Page exactRowCapacity hyperlinks" {
+ var page = try Page.init(.{
+ .cols = 10,
+ .rows = 10,
+ .styles = 8,
+ .hyperlink_bytes = 32 * @sizeOf(hyperlink.Set.Item),
+ .string_bytes = 512,
+ });
+ defer page.deinit();
+
+ // No hyperlinks: capacity should be 0
+ {
+ const cap = page.exactRowCapacity(0, 5);
+ try testing.expectEqual(0, cap.hyperlink_bytes);
+ try testing.expectEqual(0, cap.string_bytes);
+ }
+
+ // Add one hyperlink with implicit ID
+ const uri1 = "https://example.com";
+ const id1 = blk: {
+ const rac = page.getRowAndCell(0, 0);
+
+ // Create and add hyperlink entry
+ const id = try page.insertHyperlink(.{
+ .id = .{ .implicit = 1 },
+ .uri = uri1,
+ });
+ try page.setHyperlink(rac.row, rac.cell, id);
+ break :blk id;
+ };
+ // 1 hyperlink - capacity accounts for load factor
+ const cap_one_link = page.exactRowCapacity(0, 5);
+ {
+ try testing.expectEqual(hyperlink.Set.capacityForCount(1) * @sizeOf(hyperlink.Set.Item), cap_one_link.hyperlink_bytes);
+ // URI "https://example.com" = 19 bytes, rounds up to string_chunk (32)
+ try testing.expectEqual(string_chunk, cap_one_link.string_bytes);
+ }
+
+ // Add same hyperlink to another cell (duplicate ID) - capacity unchanged
+ {
+ const rac = page.getRowAndCell(1, 0);
+
+ // Use the same hyperlink ID for another cell
+ page.hyperlink_set.use(page.memory, id1);
+ try page.setHyperlink(rac.row, rac.cell, id1);
+ }
+ {
+ const cap = page.exactRowCapacity(0, 5);
+ try testing.expectEqual(cap_one_link.hyperlink_bytes, cap.hyperlink_bytes);
+ try testing.expectEqual(cap_one_link.string_bytes, cap.string_bytes);
+ }
+
+ // Add a different hyperlink with explicit ID
+ const uri2 = "https://other.example.org/path";
+ const explicit_id = "my-link-id";
+ {
+ const rac = page.getRowAndCell(2, 0);
+
+ const id = try page.insertHyperlink(.{
+ .id = .{ .explicit = explicit_id },
+ .uri = uri2,
+ });
+ try page.setHyperlink(rac.row, rac.cell, id);
+ }
+ // 2 hyperlinks - capacity accounts for load factor
+ const cap_two_links = page.exactRowCapacity(0, 5);
+ {
+ try testing.expectEqual(hyperlink.Set.capacityForCount(2) * @sizeOf(hyperlink.Set.Item), cap_two_links.hyperlink_bytes);
+ // First URI: 19 bytes -> 32, Second URI: 30 bytes -> 32, Explicit ID: 10 bytes -> 32
+ try testing.expectEqual(string_chunk * 3, cap_two_links.string_bytes);
+ }
+
+ // Hyperlink outside the row range should not be counted
+ {
+ const rac = page.getRowAndCell(0, 7); // row 7 is outside range [0, 5)
+
+ const id = try page.insertHyperlink(.{
+ .id = .{ .implicit = 99 },
+ .uri = "https://outside.example.com",
+ });
+ try page.setHyperlink(rac.row, rac.cell, id);
+ }
+ {
+ const cap = page.exactRowCapacity(0, 5);
+ try testing.expectEqual(cap_two_links.hyperlink_bytes, cap.hyperlink_bytes);
+ try testing.expectEqual(cap_two_links.string_bytes, cap.string_bytes);
+ }
+
+ // Full range includes the new hyperlink
+ {
+ const cap = page.exactRowCapacity(0, 10);
+ try testing.expectEqual(hyperlink.Set.capacityForCount(3) * @sizeOf(hyperlink.Set.Item), cap.hyperlink_bytes);
+ // Third URI: 27 bytes -> 32
+ try testing.expectEqual(string_chunk * 4, cap.string_bytes);
+ }
+
+ // Verify clone works with exact capacity and produces same result
+ {
+ const cap = page.exactRowCapacity(0, 5);
+ var cloned = try Page.init(cap);
+ defer cloned.deinit();
+ for (0..5) |y| {
+ const src_row = &page.rows.ptr(page.memory)[y];
+ const dst_row = &cloned.rows.ptr(cloned.memory)[y];
+ try cloned.cloneRowFrom(&page, dst_row, src_row);
+ }
+ const cloned_cap = cloned.exactRowCapacity(0, 5);
+ try testing.expectEqual(cap, cloned_cap);
+ }
+}
+
+test "Page exactRowCapacity single hyperlink clone" {
+ // Regression test: verify a single hyperlink can be cloned with exact capacity.
+ // This tests that capacityForCount properly accounts for ID 0 being reserved.
+ var page = try Page.init(.{
+ .cols = 10,
+ .rows = 2,
+ .styles = 8,
+ .hyperlink_bytes = 32 * @sizeOf(hyperlink.Set.Item),
+ .string_bytes = 512,
+ });
+ defer page.deinit();
+
+ // Add exactly one hyperlink to row 0
+ const uri = "https://example.com";
+ const id = blk: {
+ const rac = page.getRowAndCell(0, 0);
+ const link_id = try page.insertHyperlink(.{
+ .id = .{ .implicit = 1 },
+ .uri = uri,
+ });
+ try page.setHyperlink(rac.row, rac.cell, link_id);
+ break :blk link_id;
+ };
+ _ = id;
+
+ // exactRowCapacity for just row 0 should give capacity for 1 hyperlink
+ const cap = page.exactRowCapacity(0, 1);
+ try testing.expectEqual(hyperlink.Set.capacityForCount(1) * @sizeOf(hyperlink.Set.Item), cap.hyperlink_bytes);
+
+ // Create a new page with exact capacity and clone
+ var cloned = try Page.init(cap);
+ defer cloned.deinit();
+
+ const src_row = &page.rows.ptr(page.memory)[0];
+ const dst_row = &cloned.rows.ptr(cloned.memory)[0];
+
+ // This must not fail with HyperlinkSetOutOfMemory
+ try cloned.cloneRowFrom(&page, dst_row, src_row);
+
+ // Verify the hyperlink was cloned correctly
+ const cloned_cell = &cloned.rows.ptr(cloned.memory)[0].cells.ptr(cloned.memory)[0];
+ try testing.expect(cloned_cell.hyperlink);
+}
+
+test "Page exactRowCapacity hyperlink map capacity for many cells" {
+ // A single hyperlink spanning many cells requires hyperlink_map capacity
+ // based on cell count, not unique hyperlink count.
+ const cols = 50;
+ var page = try Page.init(.{
+ .cols = cols,
+ .rows = 2,
+ .styles = 8,
+ .hyperlink_bytes = 32 * @sizeOf(hyperlink.Set.Item),
+ .string_bytes = 512,
+ });
+ defer page.deinit();
+
+ // Add one hyperlink spanning all 50 columns in row 0
+ const uri = "https://example.com";
+ const id = blk: {
+ const rac = page.getRowAndCell(0, 0);
+ const link_id = try page.insertHyperlink(.{
+ .id = .{ .implicit = 1 },
+ .uri = uri,
+ });
+ try page.setHyperlink(rac.row, rac.cell, link_id);
+ break :blk link_id;
+ };
+
+ // Apply same hyperlink to remaining cells in row 0
+ for (1..cols) |x| {
+ const rac = page.getRowAndCell(@intCast(x), 0);
+ page.hyperlink_set.use(page.memory, id);
+ try page.setHyperlink(rac.row, rac.cell, id);
+ }
+
+ // exactRowCapacity must account for 50 hyperlink cells, not just 1 unique hyperlink
+ const cap = page.exactRowCapacity(0, 1);
+
+ // The hyperlink_bytes must be large enough that layout() computes sufficient
+ // hyperlink_map capacity. With hyperlink_cell_multiplier=16, we need at least
+ // ceil(50/16) = 4 hyperlink entries worth of bytes for the map.
+ const min_for_map = std.math.divCeil(usize, cols, hyperlink_cell_multiplier) catch 0;
+ const min_hyperlink_bytes = min_for_map * @sizeOf(hyperlink.Set.Item);
+ try testing.expect(cap.hyperlink_bytes >= min_hyperlink_bytes);
+
+ // Create a new page with exact capacity and clone - must not fail
+ var cloned = try Page.init(cap);
+ defer cloned.deinit();
+
+ const src_row = &page.rows.ptr(page.memory)[0];
+ const dst_row = &cloned.rows.ptr(cloned.memory)[0];
+
+ // This must not fail with HyperlinkMapOutOfMemory
+ try cloned.cloneRowFrom(&page, dst_row, src_row);
+
+ // Verify all hyperlinks were cloned correctly
+ for (0..cols) |x| {
+ const cloned_cell = &cloned.rows.ptr(cloned.memory)[0].cells.ptr(cloned.memory)[x];
+ try testing.expect(cloned_cell.hyperlink);
+ }
+}
diff --git a/src/terminal/ref_counted_set.zig b/src/terminal/ref_counted_set.zig
index e67682ff5..8040039ae 100644
--- a/src/terminal/ref_counted_set.zig
+++ b/src/terminal/ref_counted_set.zig
@@ -64,6 +64,20 @@ pub fn RefCountedSet(
@alignOf(Id),
));
+ /// This is the max load until the set returns OutOfMemory and
+ /// requires more capacity.
+ ///
+ /// Experimentally, this load factor works quite well.
+ pub const load_factor = 0.8125;
+
+ /// Returns the minimum capacity needed to store `n` items,
+ /// accounting for the load factor and the reserved ID 0.
+ pub fn capacityForCount(n: usize) usize {
+ if (n == 0) return 0;
+ // +1 because ID 0 is reserved, so we need at least n+1 slots.
+ return @intFromFloat(@ceil(@as(f64, @floatFromInt(n + 1)) / load_factor));
+ }
+
/// Set item
pub const Item = struct {
/// The value this item represents.
@@ -154,9 +168,6 @@ pub fn RefCountedSet(
/// The returned layout `cap` property will be 1 more than the number
/// of items that the set can actually store, since ID 0 is reserved.
pub fn init(cap: usize) Layout {
- // Experimentally, this load factor works quite well.
- const load_factor = 0.8125;
-
assert(cap <= @as(usize, @intCast(std.math.maxInt(Id))) + 1);
// Zero-cap set is valid, return special case
@@ -215,7 +226,7 @@ pub fn RefCountedSet(
OutOfMemory,
/// The set needs to be rehashed, as there are many dead
- /// items with lower IDs which are inaccessible for re-use.
+ /// items with lower IDs which are inaccessible for reuse.
NeedsRehash,
};
@@ -437,7 +448,7 @@ pub fn RefCountedSet(
}
/// Delete an item, removing any references from
- /// the table, and freeing its ID to be re-used.
+ /// the table, and freeing its ID to be reused.
fn deleteItem(self: *Self, base: anytype, id: Id, ctx: Context) void {
const table = self.table.ptr(base);
const items = self.items.ptr(base);
@@ -585,7 +596,7 @@ pub fn RefCountedSet(
const item = &items[id];
// If there's a dead item then we resurrect it
- // for our value so that we can re-use its ID,
+ // for our value so that we can reuse its ID,
// unless its ID is greater than the one we're
// given (i.e. prefer smaller IDs).
if (item.meta.ref == 0) {
@@ -645,7 +656,7 @@ pub fn RefCountedSet(
}
// Our chosen ID may have changed if we decided
- // to re-use a dead item's ID, so we make sure
+ // to reuse a dead item's ID, so we make sure
// the chosen bucket contains the correct ID.
table[new_item.meta.bucket] = chosen_id;
diff --git a/src/terminal/render.zig b/src/terminal/render.zig
index 093476f2c..9d75fe4b7 100644
--- a/src/terminal/render.zig
+++ b/src/terminal/render.zig
@@ -816,6 +816,12 @@ pub const RenderState = struct {
const row_pins = row_slice.items(.pin);
const row_cells = row_slice.items(.cells);
+ // Our viewport point is sent in by the caller and can't be trusted.
+ // If it is outside the valid area then just return empty because
+ // we can't possibly have a link there.
+ if (viewport_point.x >= self.cols or
+ viewport_point.y >= row_pins.len) return result;
+
// Grab our link ID
const link_pin: PageList.Pin = row_pins[viewport_point.y];
const link_page: *page.Page = &link_pin.node.data;
@@ -1360,6 +1366,44 @@ test "linkCells with scrollback spanning pages" {
try testing.expectEqual(@as(usize, 4), cells.count());
}
+test "linkCells with invalid viewport point" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var t = try Terminal.init(alloc, .{
+ .cols = 10,
+ .rows = 5,
+ });
+ defer t.deinit(alloc);
+
+ var s = t.vtStream();
+ defer s.deinit();
+
+ var state: RenderState = .empty;
+ defer state.deinit(alloc);
+ try state.update(alloc, &t);
+
+ // Row out of bound
+ {
+ var cells = try state.linkCells(
+ alloc,
+ .{ .x = 0, .y = t.rows + 10 },
+ );
+ defer cells.deinit(alloc);
+ try testing.expectEqual(0, cells.count());
+ }
+
+ // Col out of bound
+ {
+ var cells = try state.linkCells(
+ alloc,
+ .{ .x = t.cols + 10, .y = 0 },
+ );
+ defer cells.deinit(alloc);
+ try testing.expectEqual(0, cells.count());
+ }
+}
+
test "dirty row resets highlights" {
const testing = std.testing;
const alloc = testing.allocator;
diff --git a/src/terminal/search/Thread.zig b/src/terminal/search/Thread.zig
index 8f2d73f16..3f5377417 100644
--- a/src/terminal/search/Thread.zig
+++ b/src/terminal/search/Thread.zig
@@ -257,18 +257,46 @@ fn select(self: *Thread, sel: ScreenSearch.Select) !void {
self.opts.mutex.lock();
defer self.opts.mutex.unlock();
- // The selection will trigger a selection change notification
- // if it did change.
- if (try screen_search.select(sel)) scroll: {
- if (screen_search.selected) |m| {
- // Selection changed, let's scroll the viewport to see it
- // since we have the lock anyways.
- const screen = self.opts.terminal.screens.get(
- s.last_screen.key,
- ) orelse break :scroll;
- screen.scroll(.{ .pin = m.highlight.start.* });
+ // Make the selection. Ignore the result because we don't
+ // care if the selection didn't change.
+ _ = try screen_search.select(sel);
+
+ // Grab our match if we have one. If we don't have a selection
+ // then we do nothing.
+ const flattened = screen_search.selectedMatch() orelse return;
+
+ // No matter what we reset our selected match cache. This will
+ // trigger a callback which will trigger the renderer to wake up
+ // so it can be notified the screen scrolled.
+ s.last_screen.selected = null;
+
+ // Grab the current screen and see if this match is visible within
+ // the viewport already. If it is, we do nothing.
+ const screen = self.opts.terminal.screens.get(
+ s.last_screen.key,
+ ) orelse return;
+
+ // Grab the viewport. Viewports and selections are usually small
+ // so this check isn't very expensive, despite appearing O(N^2),
+ // both Ns are usually equal to 1.
+ var it = screen.pages.pageIterator(
+ .right_down,
+ .{ .viewport = .{} },
+ null,
+ );
+ const hl_chunks = flattened.chunks.slice();
+ while (it.next()) |chunk| {
+ for (0..hl_chunks.len) |i| {
+ const hl_chunk = hl_chunks.get(i);
+ if (chunk.overlaps(.{
+ .node = hl_chunk.node,
+ .start = hl_chunk.start,
+ .end = hl_chunk.end,
+ })) return;
}
}
+
+ screen.scroll(.{ .pin = flattened.startPin() });
}
/// Change the search term to the given value.
diff --git a/src/terminal/search/screen.zig b/src/terminal/search/screen.zig
index 0ae7f8a1f..179e7da87 100644
--- a/src/terminal/search/screen.zig
+++ b/src/terminal/search/screen.zig
@@ -412,6 +412,12 @@ pub const ScreenSearch = struct {
// pages then we need to re-search the pages and add it to
// our history results.
+ // If our screen has no scrollback then we have no history.
+ if (self.screen.no_scrollback) {
+ assert(self.history == null);
+ break :history;
+ }
+
const history_: ?*HistorySearch = if (self.history) |*h| state: {
// If our start pin became garbage, it means we pruned all
// the way up through it, so we have no history anymore.
@@ -575,8 +581,43 @@ pub const ScreenSearch = struct {
},
}
- // Active area search was successful. Now we have to fixup our
- // selection if we had one.
+ // If we have no scrollback, we need to prune any active results
+ // that aren't in the actual active area. We only do this for the
+ // no scrollback scenario because with scrollback we actually
+ // rely on our active search searching by page to find history
+ // items as well. This is all related to the fact that PageList
+ // scrollback limits are discrete by page size except we special
+ // case zero.
+ if (self.screen.no_scrollback and
+ self.active_results.items.len > 0)
+ active_prune: {
+ const items = self.active_results.items;
+ const tl = self.screen.pages.getTopLeft(.active);
+ for (0.., items) |i, *hl| {
+ if (!tl.before(hl.endPin())) {
+ // Deinit because its going to be pruned no matter
+ // what at some point for not being in the active area.
+ hl.deinit(alloc);
+ continue;
+ }
+
+ // In the active area! Since our results are sorted
+ // that means everything after this is also in the active
+ // area, so we prune up to this i.
+ if (i > 0) self.active_results.replaceRangeAssumeCapacity(
+ 0,
+ i,
+ &.{},
+ );
+
+ break :active_prune;
+ }
+
+ // None are in the active area...
+ self.active_results.clearRetainingCapacity();
+ }
+
+ // Now we have to fixup our selection if we had one.
fixup: {
const old_idx = old_selection_idx orelse break :fixup;
const m = if (self.selected) |*m| m else break :fixup;
@@ -1333,3 +1374,37 @@ test "select prev with history" {
} }, t.screens.active.pages.pointFromPin(.active, sel.end).?);
}
}
+
+test "screen search no scrollback has no history" {
+ const alloc = testing.allocator;
+ var t: Terminal = try .init(alloc, .{
+ .cols = 10,
+ .rows = 2,
+ .max_scrollback = 0,
+ });
+ defer t.deinit(alloc);
+
+ // Alt screen has no scrollback
+ _ = try t.switchScreen(.alternate);
+
+ var s = t.vtStream();
+ defer s.deinit();
+
+ // This will probably stop working at some point and we'll have
+ // no way to test it using public APIs, but at the time of writing
+ // this test, CSI 22 J (scroll complete) pushes into scrollback
+ // with alt screen.
+ try s.nextSlice("Fizz\r\n");
+ try s.nextSlice("\x1b[22J");
+ try s.nextSlice("hello.");
+
+ var search: ScreenSearch = try .init(alloc, t.screens.active, "Fizz");
+ defer search.deinit();
+ try search.searchAll();
+ try testing.expectEqual(0, search.active_results.items.len);
+
+ // Get all matches
+ const matches = try search.matches(alloc);
+ defer alloc.free(matches);
+ try testing.expectEqual(0, matches.len);
+}
diff --git a/src/terminal/search/sliding_window.zig b/src/terminal/search/sliding_window.zig
index 3d64042ce..c3c29e085 100644
--- a/src/terminal/search/sliding_window.zig
+++ b/src/terminal/search/sliding_window.zig
@@ -575,9 +575,16 @@ pub const SlidingWindow = struct {
);
}
+ // If our written data is empty, then there is nothing to
+ // add to our data set.
+ const written = encoded.written();
+ if (written.len == 0) {
+ self.assertIntegrity();
+ return 0;
+ }
+
// Get our written data. If we're doing a reverse search then we
// need to reverse all our encodings.
- const written = encoded.written();
switch (self.direction) {
.forward => {},
.reverse => {
@@ -1637,3 +1644,33 @@ test "SlidingWindow single append reversed soft wrapped" {
try testing.expect(w.next() == null);
try testing.expect(w.next() == null);
}
+
+// This tests a real bug that occurred where a whitespace-only page
+// that encodes to zero bytes would crash.
+test "SlidingWindow append whitespace only node" {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ var w: SlidingWindow = try .init(alloc, .forward, "x");
+ defer w.deinit();
+
+ var s = try Screen.init(alloc, .{
+ .cols = 80,
+ .rows = 24,
+ .max_scrollback = 0,
+ });
+ defer s.deinit();
+
+ // By setting the empty page to wrap we get a zero-byte page.
+ // This is invasive but its otherwise hard to reproduce naturally
+ // without creating a slow test.
+ const node: *PageList.List.Node = s.pages.pages.first.?;
+ const last_row = node.data.getRow(node.data.size.rows - 1);
+ last_row.wrap = true;
+
+ try testing.expect(s.pages.pages.first == s.pages.pages.last);
+ _ = try w.append(node);
+
+ // No matches expected
+ try testing.expect(w.next() == null);
+}
diff --git a/src/terminal/size.zig b/src/terminal/size.zig
index 0dedfcc14..7be09739e 100644
--- a/src/terminal/size.zig
+++ b/src/terminal/size.zig
@@ -11,9 +11,32 @@ pub const max_page_size = std.math.maxInt(u32);
/// derived from the maximum terminal page size.
pub const OffsetInt = std.math.IntFittingRange(0, max_page_size - 1);
-/// The int type that can contain the maximum number of cells in a page.
-pub const CellCountInt = u16; // TODO: derive
+/// Int types for maximum values of things. A lot of these sizes are
+/// based on "X is enough for any reasonable use case" principles.
+// The goal is that a user can have the maxInt amount of all of these
+// present at one time and be able to address them in a single Page.zig.
+
+// Total number of cells that are possible in each dimension (row/col).
+// Based on 2^16 being enough for any reasonable terminal size and allowing
+// IDs to remain 16-bit.
+pub const CellCountInt = u16;
+
+// Total number of styles and hyperlinks that are possible in a page.
+// We match CellCountInt here because each cell in a single row can have at
+// most one style, making it simple to split a page by splitting rows.
//
+// Note due to the way RefCountedSet works, we are short one value, but
+// this is a theoretical limit we accept. A page with a single row max
+// columns wide would be one short of having every cell have a unique style.
+pub const StyleCountInt = CellCountInt;
+pub const HyperlinkCountInt = CellCountInt;
+
+// Total number of bytes that can be taken up by grapheme data and string
+// data. Both of these technically unlimited with malicious input, but
+// we choose a reasonable limit of 2^32 (4GB) per.
+pub const GraphemeBytesInt = u32;
+pub const StringBytesInt = u32;
+
/// The offset from the base address of the page to the start of some data.
/// This is typed for ease of use.
///
diff --git a/src/terminal/stream.zig b/src/terminal/stream.zig
index ba6b57d5c..74a01e8a6 100644
--- a/src/terminal/stream.zig
+++ b/src/terminal/stream.zig
@@ -1641,7 +1641,7 @@ pub fn Stream(comptime Handler: type) type {
},
},
else => {
- log.warn("invalid set curor style command: {f}", .{input});
+ log.warn("invalid set cursor style command: {f}", .{input});
return;
},
};
@@ -2107,8 +2107,9 @@ pub fn Stream(comptime Handler: type) type {
.conemu_change_tab_title,
.conemu_wait_input,
.conemu_guimacro,
+ .kitty_text_sizing,
=> {
- log.warn("unimplemented OSC callback: {}", .{cmd});
+ log.debug("unimplemented OSC callback: {}", .{cmd});
},
.invalid => {
diff --git a/src/terminal/stream_readonly.zig b/src/terminal/stream_readonly.zig
index c33dba1bb..9b4999116 100644
--- a/src/terminal/stream_readonly.zig
+++ b/src/terminal/stream_readonly.zig
@@ -4,7 +4,7 @@ const stream = @import("stream.zig");
const Action = stream.Action;
const Screen = @import("Screen.zig");
const modes = @import("modes.zig");
-const osc_color = @import("osc/color.zig");
+const osc_color = @import("osc/parsers/color.zig");
const kitty_color = @import("kitty/color.zig");
const Terminal = @import("Terminal.zig");
@@ -125,7 +125,7 @@ pub const Handler = struct {
}
},
.save_cursor => self.terminal.saveCursor(),
- .restore_cursor => try self.terminal.restoreCursor(),
+ .restore_cursor => self.terminal.restoreCursor(),
.invoke_charset => self.terminal.invokeCharset(value.bank, value.charset, value.locking),
.configure_charset => self.terminal.configureCharset(value.slot, value.charset),
.set_attribute => switch (value) {
@@ -240,7 +240,7 @@ pub const Handler = struct {
.save_cursor => if (enabled) {
self.terminal.saveCursor();
} else {
- try self.terminal.restoreCursor();
+ self.terminal.restoreCursor();
},
.enable_mode_3 => {},
diff --git a/src/terminal/style.zig b/src/terminal/style.zig
index e5c47b9fe..7908beefa 100644
--- a/src/terminal/style.zig
+++ b/src/terminal/style.zig
@@ -11,7 +11,7 @@ const RefCountedSet = @import("ref_counted_set.zig").RefCountedSet;
/// The unique identifier for a style. This is at most the number of cells
/// that can fit into a terminal page.
-pub const Id = size.CellCountInt;
+pub const Id = size.StyleCountInt;
/// The Id to use for default styling.
pub const default_id: Id = 0;
diff --git a/src/terminal/tmux/viewer.zig b/src/terminal/tmux/viewer.zig
index 0fcaaf207..62a0f1d00 100644
--- a/src/terminal/tmux/viewer.zig
+++ b/src/terminal/tmux/viewer.zig
@@ -208,7 +208,7 @@ pub const Viewer = struct {
/// caller is responsible for diffing the new window list against
/// the prior one. Remember that for a given Viewer, window IDs
/// are guaranteed to be stable. Additionally, tmux (as of Dec 2025)
- /// never re-uses window IDs within a server process lifetime.
+ /// never reuses window IDs within a server process lifetime.
windows: []const Window,
pub fn format(self: Action, writer: *std.Io.Writer) !void {
diff --git a/src/termio/Exec.zig b/src/termio/Exec.zig
index 7c7b711fd..0e7cdc172 100644
--- a/src/termio/Exec.zig
+++ b/src/termio/Exec.zig
@@ -750,15 +750,15 @@ const Subprocess = struct {
else => "sh",
} };
+ // Always set up shell features (GHOSTTY_SHELL_FEATURES). These are
+ // used by both automatic and manual shell integrations.
+ try shell_integration.setupFeatures(
+ &env,
+ cfg.shell_integration_features,
+ );
+
const force: ?shell_integration.Shell = switch (cfg.shell_integration) {
.none => {
- // Even if shell integration is none, we still want to
- // set up the feature env vars
- try shell_integration.setupFeatures(
- &env,
- cfg.shell_integration_features,
- );
-
// This is a source of confusion for users despite being
// opt-in since it results in some Ghostty features not
// working. We always want to log it.
@@ -770,6 +770,7 @@ const Subprocess = struct {
.bash => .bash,
.elvish => .elvish,
.fish => .fish,
+ .nushell => .nushell,
.zsh => .zsh,
};
@@ -784,7 +785,6 @@ const Subprocess = struct {
default_shell_command,
&env,
force,
- cfg.shell_integration_features,
) orelse {
log.warn("shell could not be detected, no automatic shell integration will be injected", .{});
break :shell default_shell_command;
diff --git a/src/termio/Termio.zig b/src/termio/Termio.zig
index 7263418a7..a1bcea6d3 100644
--- a/src/termio/Termio.zig
+++ b/src/termio/Termio.zig
@@ -165,6 +165,7 @@ pub const DerivedConfig = struct {
osc_color_report_format: configpkg.Config.OSCColorReportFormat,
clipboard_write: configpkg.ClipboardAccess,
enquiry_response: []const u8,
+ conditional_state: configpkg.ConditionalState,
pub fn init(
alloc_gpa: Allocator,
@@ -185,6 +186,7 @@ pub const DerivedConfig = struct {
.osc_color_report_format = config.@"osc-color-report-format",
.clipboard_write = config.@"clipboard-write",
.enquiry_response = try alloc.dupe(u8, config.@"enquiry-response"),
+ .conditional_state = config._conditional_state,
// This has to be last so that we copy AFTER the arena allocations
// above happen (Zig assigns in order).
@@ -712,6 +714,25 @@ fn processOutputLocked(self: *Termio, buf: []const u8) void {
}
}
+/// Sends a DSR response for the current color scheme to the pty.
+pub fn colorSchemeReport(self: *Termio, td: *ThreadData, force: bool) !void {
+ self.renderer_state.mutex.lock();
+ defer self.renderer_state.mutex.unlock();
+
+ try self.colorSchemeReportLocked(td, force);
+}
+
+pub fn colorSchemeReportLocked(self: *Termio, td: *ThreadData, force: bool) !void {
+ if (!force and !self.renderer_state.terminal.modes.get(.report_color_scheme)) {
+ return;
+ }
+ const output = switch (self.config.conditional_state.theme) {
+ .light => "\x1B[?997;2n",
+ .dark => "\x1B[?997;1n",
+ };
+ try self.queueWrite(td, output, false);
+}
+
/// ThreadData is the data created and stored in the termio thread
/// when the thread is started and destroyed when the thread is
/// stopped.
diff --git a/src/termio/Thread.zig b/src/termio/Thread.zig
index b111d5a52..6aa5e1c26 100644
--- a/src/termio/Thread.zig
+++ b/src/termio/Thread.zig
@@ -311,6 +311,7 @@ fn drainMailbox(
log.debug("mailbox message={s}", .{@tagName(message)});
switch (message) {
+ .color_scheme_report => |v| try io.colorSchemeReport(data, v.force),
.crash => @panic("crash request, crashing intentionally"),
.change_config => |config| {
defer config.alloc.destroy(config.ptr);
diff --git a/src/termio/message.zig b/src/termio/message.zig
index f78da2058..d7a59bf5e 100644
--- a/src/termio/message.zig
+++ b/src/termio/message.zig
@@ -16,6 +16,12 @@ pub const Message = union(enum) {
/// in the future.
pub const WriteReq = MessageData(u8, 38);
+ /// Request a color scheme report is sent to the pty.
+ color_scheme_report: struct {
+ /// Force write the current color scheme
+ force: bool,
+ },
+
/// Purposely crash the renderer. This is used for testing and debugging.
/// See the "crash" binding action.
crash: void,
diff --git a/src/termio/shell_integration.zig b/src/termio/shell_integration.zig
index 71492230e..ab6dcd6ff 100644
--- a/src/termio/shell_integration.zig
+++ b/src/termio/shell_integration.zig
@@ -14,6 +14,7 @@ pub const Shell = enum {
bash,
elvish,
fish,
+ nushell,
zsh,
};
@@ -44,100 +45,43 @@ pub fn setup(
command: config.Command,
env: *EnvMap,
force_shell: ?Shell,
- features: config.ShellIntegrationFeatures,
) !?ShellIntegration {
- const exe = if (force_shell) |shell| switch (shell) {
- .bash => "bash",
- .elvish => "elvish",
- .fish => "fish",
- .zsh => "zsh",
- } else switch (command) {
- .direct => |v| std.fs.path.basename(v[0]),
- .shell => |v| exe: {
- // Shell strings can include spaces so we want to only
- // look up to the space if it exists. No shell that we integrate
- // has spaces.
- const idx = std.mem.indexOfScalar(u8, v, ' ') orelse v.len;
- break :exe std.fs.path.basename(v[0..idx]);
- },
- };
+ const shell: Shell = force_shell orelse
+ try detectShell(alloc_arena, command) orelse
+ return null;
- const result = try setupShell(
- alloc_arena,
- resource_dir,
- command,
- env,
- exe,
- );
-
- // Setup our feature env vars
- try setupFeatures(env, features);
-
- return result;
-}
-
-fn setupShell(
- alloc_arena: Allocator,
- resource_dir: []const u8,
- command: config.Command,
- env: *EnvMap,
- exe: []const u8,
-) !?ShellIntegration {
- if (std.mem.eql(u8, "bash", exe)) {
- // Apple distributes their own patched version of Bash 3.2
- // on macOS that disables the ENV-based POSIX startup path.
- // This means we're unable to perform our automatic shell
- // integration sequence in this specific environment.
- //
- // If we're running "/bin/bash" on Darwin, we can assume
- // we're using Apple's Bash because /bin is non-writable
- // on modern macOS due to System Integrity Protection.
- if (comptime builtin.target.os.tag.isDarwin()) {
- if (std.mem.eql(u8, "/bin/bash", switch (command) {
- .direct => |v| v[0],
- .shell => |v| v,
- })) {
- return null;
- }
- }
-
- const new_command = try setupBash(
+ const new_command: config.Command = switch (shell) {
+ .bash => try setupBash(
alloc_arena,
command,
resource_dir,
env,
- ) orelse return null;
- return .{
- .shell = .bash,
- .command = new_command,
- };
- }
+ ),
- if (std.mem.eql(u8, "elvish", exe)) {
- try setupXdgDataDirs(alloc_arena, resource_dir, env);
- return .{
- .shell = .elvish,
- .command = try command.clone(alloc_arena),
- };
- }
+ .nushell => try setupNushell(
+ alloc_arena,
+ command,
+ resource_dir,
+ env,
+ ),
- if (std.mem.eql(u8, "fish", exe)) {
- try setupXdgDataDirs(alloc_arena, resource_dir, env);
- return .{
- .shell = .fish,
- .command = try command.clone(alloc_arena),
- };
- }
+ .zsh => try setupZsh(
+ alloc_arena,
+ command,
+ resource_dir,
+ env,
+ ),
- if (std.mem.eql(u8, "zsh", exe)) {
- try setupZsh(resource_dir, env);
- return .{
- .shell = .zsh,
- .command = try command.clone(alloc_arena),
- };
- }
+ .elvish, .fish => xdg: {
+ if (!try setupXdgDataDirs(alloc_arena, resource_dir, env)) return null;
+ break :xdg try command.clone(alloc_arena);
+ },
+ } orelse return null;
- return null;
+ return .{
+ .shell = shell,
+ .command = new_command,
+ };
}
test "force shell" {
@@ -152,18 +96,94 @@ test "force shell" {
inline for (@typeInfo(Shell).@"enum".fields) |field| {
const shell = @field(Shell, field.name);
+
+ var res: TmpResourcesDir = try .init(alloc, shell);
+ defer res.deinit();
+
const result = try setup(
alloc,
- ".",
+ res.path,
.{ .shell = "sh" },
&env,
shell,
- .{},
);
try testing.expectEqual(shell, result.?.shell);
}
}
+test "shell integration failure" {
+ const testing = std.testing;
+
+ var arena = ArenaAllocator.init(testing.allocator);
+ defer arena.deinit();
+ const alloc = arena.allocator();
+
+ var env = EnvMap.init(alloc);
+ defer env.deinit();
+
+ const result = try setup(
+ alloc,
+ "/nonexistent",
+ .{ .shell = "sh" },
+ &env,
+ null,
+ );
+
+ try testing.expect(result == null);
+ try testing.expectEqual(0, env.count());
+}
+
+fn detectShell(alloc: Allocator, command: config.Command) !?Shell {
+ var arg_iter = try command.argIterator(alloc);
+ defer arg_iter.deinit();
+
+ const arg0 = arg_iter.next() orelse return null;
+ const exe = std.fs.path.basename(arg0);
+
+ if (std.mem.eql(u8, "bash", exe)) {
+ // Apple distributes their own patched version of Bash 3.2
+ // on macOS that disables the ENV-based POSIX startup path.
+ // This means we're unable to perform our automatic shell
+ // integration sequence in this specific environment.
+ //
+ // If we're running "/bin/bash" on Darwin, we can assume
+ // we're using Apple's Bash because /bin is non-writable
+ // on modern macOS due to System Integrity Protection.
+ if (comptime builtin.target.os.tag.isDarwin()) {
+ if (std.mem.eql(u8, "/bin/bash", arg0)) {
+ return null;
+ }
+ }
+ return .bash;
+ }
+
+ if (std.mem.eql(u8, "elvish", exe)) return .elvish;
+ if (std.mem.eql(u8, "fish", exe)) return .fish;
+ if (std.mem.eql(u8, "nu", exe)) return .nushell;
+ if (std.mem.eql(u8, "zsh", exe)) return .zsh;
+
+ return null;
+}
+
+test detectShell {
+ const testing = std.testing;
+ const alloc = testing.allocator;
+
+ try testing.expect(try detectShell(alloc, .{ .shell = "sh" }) == null);
+ try testing.expectEqual(.bash, try detectShell(alloc, .{ .shell = "bash" }));
+ try testing.expectEqual(.elvish, try detectShell(alloc, .{ .shell = "elvish" }));
+ try testing.expectEqual(.fish, try detectShell(alloc, .{ .shell = "fish" }));
+ try testing.expectEqual(.nushell, try detectShell(alloc, .{ .shell = "nu" }));
+ try testing.expectEqual(.zsh, try detectShell(alloc, .{ .shell = "zsh" }));
+
+ if (comptime builtin.target.os.tag.isDarwin()) {
+ try testing.expect(try detectShell(alloc, .{ .shell = "/bin/bash" }) == null);
+ }
+
+ try testing.expectEqual(.bash, try detectShell(alloc, .{ .shell = "bash -c 'command'" }));
+ try testing.expectEqual(.bash, try detectShell(alloc, .{ .shell = "\"/a b/bash\"" }));
+}
+
/// Set up the shell integration features environment variable.
pub fn setupFeatures(
env: *EnvMap,
@@ -230,7 +250,7 @@ test "setup features" {
var env = EnvMap.init(alloc);
defer env.deinit();
- try setupFeatures(&env, .{ .cursor = false, .sudo = false, .title = false, .@"ssh-env" = false, .@"ssh-terminfo" = false, .path = false });
+ try setupFeatures(&env, std.mem.zeroes(config.ShellIntegrationFeatures));
try testing.expect(env.get("GHOSTTY_SHELL_FEATURES") == null);
}
@@ -318,6 +338,28 @@ fn setupBash(
try cmd.appendArg(arg);
}
}
+
+ // Preserve an existing ENV value. We're about to overwrite it.
+ if (env.get("ENV")) |v| {
+ try env.put("GHOSTTY_BASH_ENV", v);
+ }
+
+ // Set our new ENV to point to our integration script.
+ var script_path_buf: [std.fs.max_path_bytes]u8 = undefined;
+ const script_path = try std.fmt.bufPrint(
+ &script_path_buf,
+ "{s}/shell-integration/bash/ghostty.bash",
+ .{resource_dir},
+ );
+ if (std.fs.openFileAbsolute(script_path, .{})) |file| {
+ file.close();
+ try env.put("ENV", script_path);
+ } else |err| {
+ log.warn("unable to open {s}: {}", .{ script_path, err });
+ env.remove("GHOSTTY_BASH_ENV");
+ return null;
+ }
+
try env.put("GHOSTTY_BASH_INJECT", buf[0..inject.end]);
if (rcfile) |v| {
try env.put("GHOSTTY_BASH_RCFILE", v);
@@ -339,25 +381,8 @@ fn setupBash(
}
}
- // Preserve an existing ENV value. We're about to overwrite it.
- if (env.get("ENV")) |v| {
- try env.put("GHOSTTY_BASH_ENV", v);
- }
-
- // Set our new ENV to point to our integration script.
- var path_buf: [std.fs.max_path_bytes]u8 = undefined;
- const integ_dir = try std.fmt.bufPrint(
- &path_buf,
- "{s}/shell-integration/bash/ghostty.bash",
- .{resource_dir},
- );
- try env.put("ENV", integ_dir);
-
- // Get the command string from the builder, then copy it to the arena
- // allocator. The stackFallback allocator's memory becomes invalid after
- // this function returns, so we must copy to the arena.
- const cmd_str = try cmd.toOwnedSlice();
- return .{ .shell = try alloc.dupeZ(u8, cmd_str) };
+ // Return a copy of our modified command line to use as the shell command.
+ return .{ .shell = try alloc.dupeZ(u8, try cmd.toOwnedSlice()) };
}
test "bash" {
@@ -366,14 +391,21 @@ test "bash" {
defer arena.deinit();
const alloc = arena.allocator();
+ var res: TmpResourcesDir = try .init(alloc, .bash);
+ defer res.deinit();
+
var env = EnvMap.init(alloc);
defer env.deinit();
- const command = try setupBash(alloc, .{ .shell = "bash" }, ".", &env);
-
+ const command = try setupBash(alloc, .{ .shell = "bash" }, res.path, &env);
try testing.expectEqualStrings("bash --posix", command.?.shell);
- try testing.expectEqualStrings("./shell-integration/bash/ghostty.bash", env.get("ENV").?);
try testing.expectEqualStrings("1", env.get("GHOSTTY_BASH_INJECT").?);
+
+ var path_buf: [std.fs.max_path_bytes]u8 = undefined;
+ try testing.expectEqualStrings(
+ try std.fmt.bufPrint(&path_buf, "{s}/ghostty.bash", .{res.shell_path}),
+ env.get("ENV").?,
+ );
}
test "bash: unsupported options" {
@@ -382,6 +414,9 @@ test "bash: unsupported options" {
defer arena.deinit();
const alloc = arena.allocator();
+ var res: TmpResourcesDir = try .init(alloc, .bash);
+ defer res.deinit();
+
const cmdlines = [_][:0]const u8{
"bash --posix",
"bash --rcfile script.sh --posix",
@@ -394,10 +429,8 @@ test "bash: unsupported options" {
var env = EnvMap.init(alloc);
defer env.deinit();
- try testing.expect(try setupBash(alloc, .{ .shell = cmdline }, ".", &env) == null);
- try testing.expect(env.get("GHOSTTY_BASH_INJECT") == null);
- try testing.expect(env.get("GHOSTTY_BASH_RCFILE") == null);
- try testing.expect(env.get("GHOSTTY_BASH_UNEXPORT_HISTFILE") == null);
+ try testing.expect(try setupBash(alloc, .{ .shell = cmdline }, res.path, &env) == null);
+ try testing.expectEqual(0, env.count());
}
}
@@ -407,13 +440,15 @@ test "bash: inject flags" {
defer arena.deinit();
const alloc = arena.allocator();
+ var res: TmpResourcesDir = try .init(alloc, .bash);
+ defer res.deinit();
+
// bash --norc
{
var env = EnvMap.init(alloc);
defer env.deinit();
- const command = try setupBash(alloc, .{ .shell = "bash --norc" }, ".", &env);
-
+ const command = try setupBash(alloc, .{ .shell = "bash --norc" }, res.path, &env);
try testing.expectEqualStrings("bash --posix", command.?.shell);
try testing.expectEqualStrings("1 --norc", env.get("GHOSTTY_BASH_INJECT").?);
}
@@ -423,8 +458,7 @@ test "bash: inject flags" {
var env = EnvMap.init(alloc);
defer env.deinit();
- const command = try setupBash(alloc, .{ .shell = "bash --noprofile" }, ".", &env);
-
+ const command = try setupBash(alloc, .{ .shell = "bash --noprofile" }, res.path, &env);
try testing.expectEqualStrings("bash --posix", command.?.shell);
try testing.expectEqualStrings("1 --noprofile", env.get("GHOSTTY_BASH_INJECT").?);
}
@@ -436,19 +470,22 @@ test "bash: rcfile" {
defer arena.deinit();
const alloc = arena.allocator();
+ var res: TmpResourcesDir = try .init(alloc, .bash);
+ defer res.deinit();
+
var env = EnvMap.init(alloc);
defer env.deinit();
// bash --rcfile
{
- const command = try setupBash(alloc, .{ .shell = "bash --rcfile profile.sh" }, ".", &env);
+ const command = try setupBash(alloc, .{ .shell = "bash --rcfile profile.sh" }, res.path, &env);
try testing.expectEqualStrings("bash --posix", command.?.shell);
try testing.expectEqualStrings("profile.sh", env.get("GHOSTTY_BASH_RCFILE").?);
}
// bash --init-file
{
- const command = try setupBash(alloc, .{ .shell = "bash --init-file profile.sh" }, ".", &env);
+ const command = try setupBash(alloc, .{ .shell = "bash --init-file profile.sh" }, res.path, &env);
try testing.expectEqualStrings("bash --posix", command.?.shell);
try testing.expectEqualStrings("profile.sh", env.get("GHOSTTY_BASH_RCFILE").?);
}
@@ -460,12 +497,15 @@ test "bash: HISTFILE" {
defer arena.deinit();
const alloc = arena.allocator();
+ var res: TmpResourcesDir = try .init(alloc, .bash);
+ defer res.deinit();
+
// HISTFILE unset
{
var env = EnvMap.init(alloc);
defer env.deinit();
- _ = try setupBash(alloc, .{ .shell = "bash" }, ".", &env);
+ _ = try setupBash(alloc, .{ .shell = "bash" }, res.path, &env);
try testing.expect(std.mem.endsWith(u8, env.get("HISTFILE").?, ".bash_history"));
try testing.expectEqualStrings("1", env.get("GHOSTTY_BASH_UNEXPORT_HISTFILE").?);
}
@@ -477,7 +517,7 @@ test "bash: HISTFILE" {
try env.put("HISTFILE", "my_history");
- _ = try setupBash(alloc, .{ .shell = "bash" }, ".", &env);
+ _ = try setupBash(alloc, .{ .shell = "bash" }, res.path, &env);
try testing.expectEqualStrings("my_history", env.get("HISTFILE").?);
try testing.expect(env.get("GHOSTTY_BASH_UNEXPORT_HISTFILE") == null);
}
@@ -489,14 +529,22 @@ test "bash: ENV" {
defer arena.deinit();
const alloc = arena.allocator();
+ var res: TmpResourcesDir = try .init(alloc, .bash);
+ defer res.deinit();
+
var env = EnvMap.init(alloc);
defer env.deinit();
try env.put("ENV", "env.sh");
- _ = try setupBash(alloc, .{ .shell = "bash" }, ".", &env);
- try testing.expectEqualStrings("./shell-integration/bash/ghostty.bash", env.get("ENV").?);
+ _ = try setupBash(alloc, .{ .shell = "bash" }, res.path, &env);
try testing.expectEqualStrings("env.sh", env.get("GHOSTTY_BASH_ENV").?);
+
+ var path_buf: [std.fs.max_path_bytes]u8 = undefined;
+ try testing.expectEqualStrings(
+ try std.fmt.bufPrint(&path_buf, "{s}/ghostty.bash", .{res.shell_path}),
+ env.get("ENV").?,
+ );
}
test "bash: additional arguments" {
@@ -505,22 +553,44 @@ test "bash: additional arguments" {
defer arena.deinit();
const alloc = arena.allocator();
+ var res: TmpResourcesDir = try .init(alloc, .bash);
+ defer res.deinit();
+
var env = EnvMap.init(alloc);
defer env.deinit();
// "-" argument separator
{
- const command = try setupBash(alloc, .{ .shell = "bash - --arg file1 file2" }, ".", &env);
+ const command = try setupBash(alloc, .{ .shell = "bash - --arg file1 file2" }, res.path, &env);
try testing.expectEqualStrings("bash --posix - --arg file1 file2", command.?.shell);
}
// "--" argument separator
{
- const command = try setupBash(alloc, .{ .shell = "bash -- --arg file1 file2" }, ".", &env);
+ const command = try setupBash(alloc, .{ .shell = "bash -- --arg file1 file2" }, res.path, &env);
try testing.expectEqualStrings("bash --posix -- --arg file1 file2", command.?.shell);
}
}
+test "bash: missing resources" {
+ const testing = std.testing;
+ var arena = ArenaAllocator.init(testing.allocator);
+ defer arena.deinit();
+ const alloc = arena.allocator();
+
+ var tmp_dir = testing.tmpDir(.{});
+ defer tmp_dir.cleanup();
+
+ const resources_dir = try tmp_dir.dir.realpathAlloc(alloc, ".");
+ defer alloc.free(resources_dir);
+
+ var env = EnvMap.init(alloc);
+ defer env.deinit();
+
+ try testing.expect(try setupBash(alloc, .{ .shell = "bash" }, resources_dir, &env) == null);
+ try testing.expectEqual(0, env.count());
+}
+
/// Setup automatic shell integration for shells that include
/// their modules from paths in `XDG_DATA_DIRS` env variable.
///
@@ -529,30 +599,35 @@ test "bash: additional arguments" {
/// so that the shell can refer to it and safely remove this directory
/// from `XDG_DATA_DIRS` when integration is complete.
fn setupXdgDataDirs(
- alloc_arena: Allocator,
+ alloc: Allocator,
resource_dir: []const u8,
env: *EnvMap,
-) !void {
+) !bool {
var path_buf: [std.fs.max_path_bytes]u8 = undefined;
// Get our path to the shell integration directory.
- const integ_dir = try std.fmt.bufPrint(
+ const integ_path = try std.fmt.bufPrint(
&path_buf,
"{s}/shell-integration",
.{resource_dir},
);
+ var integ_dir = std.fs.openDirAbsolute(integ_path, .{}) catch |err| {
+ log.warn("unable to open {s}: {}", .{ integ_path, err });
+ return false;
+ };
+ integ_dir.close();
// Set an env var so we can remove this from XDG_DATA_DIRS later.
// This happens in the shell integration config itself. We do this
// so that our modifications don't interfere with other commands.
- try env.put("GHOSTTY_SHELL_INTEGRATION_XDG_DIR", integ_dir);
+ try env.put("GHOSTTY_SHELL_INTEGRATION_XDG_DIR", integ_path);
// We attempt to avoid allocating by using the stack up to 4K.
// Max stack size is considerably larger on mac
// 4K is a reasonable size for this for most cases. However, env
// vars can be significantly larger so if we have to we fall
// back to a heap allocated value.
- var stack_alloc_state = std.heap.stackFallback(4096, alloc_arena);
+ var stack_alloc_state = std.heap.stackFallback(4096, alloc);
const stack_alloc = stack_alloc_state.get();
// If no XDG_DATA_DIRS set use the default value as specified.
@@ -565,9 +640,11 @@ fn setupXdgDataDirs(
try internal_os.prependEnv(
stack_alloc,
env.get(xdg_data_dirs_key) orelse "/usr/local/share:/usr/share",
- integ_dir,
+ integ_path,
),
);
+
+ return true;
}
test "xdg: empty XDG_DATA_DIRS" {
@@ -577,13 +654,23 @@ test "xdg: empty XDG_DATA_DIRS" {
defer arena.deinit();
const alloc = arena.allocator();
+ var res: TmpResourcesDir = try .init(alloc, .fish);
+ defer res.deinit();
+
var env = EnvMap.init(alloc);
defer env.deinit();
- try setupXdgDataDirs(alloc, ".", &env);
+ try testing.expect(try setupXdgDataDirs(alloc, res.path, &env));
- try testing.expectEqualStrings("./shell-integration", env.get("GHOSTTY_SHELL_INTEGRATION_XDG_DIR").?);
- try testing.expectEqualStrings("./shell-integration:/usr/local/share:/usr/share", env.get("XDG_DATA_DIRS").?);
+ var path_buf: [std.fs.max_path_bytes]u8 = undefined;
+ try testing.expectEqualStrings(
+ try std.fmt.bufPrint(&path_buf, "{s}/shell-integration", .{res.path}),
+ env.get("GHOSTTY_SHELL_INTEGRATION_XDG_DIR").?,
+ );
+ try testing.expectEqualStrings(
+ try std.fmt.bufPrint(&path_buf, "{s}/shell-integration:/usr/local/share:/usr/share", .{res.path}),
+ env.get("XDG_DATA_DIRS").?,
+ );
}
test "xdg: existing XDG_DATA_DIRS" {
@@ -593,23 +680,198 @@ test "xdg: existing XDG_DATA_DIRS" {
defer arena.deinit();
const alloc = arena.allocator();
+ var res: TmpResourcesDir = try .init(alloc, .fish);
+ defer res.deinit();
+
var env = EnvMap.init(alloc);
defer env.deinit();
try env.put("XDG_DATA_DIRS", "/opt/share");
- try setupXdgDataDirs(alloc, ".", &env);
- try testing.expectEqualStrings("./shell-integration", env.get("GHOSTTY_SHELL_INTEGRATION_XDG_DIR").?);
- try testing.expectEqualStrings("./shell-integration:/opt/share", env.get("XDG_DATA_DIRS").?);
+ try testing.expect(try setupXdgDataDirs(alloc, res.path, &env));
+
+ var path_buf: [std.fs.max_path_bytes]u8 = undefined;
+ try testing.expectEqualStrings(
+ try std.fmt.bufPrint(&path_buf, "{s}/shell-integration", .{res.path}),
+ env.get("GHOSTTY_SHELL_INTEGRATION_XDG_DIR").?,
+ );
+ try testing.expectEqualStrings(
+ try std.fmt.bufPrint(&path_buf, "{s}/shell-integration:/opt/share", .{res.path}),
+ env.get("XDG_DATA_DIRS").?,
+ );
+}
+
+test "xdg: missing resources" {
+ const testing = std.testing;
+ var arena = ArenaAllocator.init(testing.allocator);
+ defer arena.deinit();
+ const alloc = arena.allocator();
+
+ var tmp_dir = testing.tmpDir(.{});
+ defer tmp_dir.cleanup();
+
+ const resources_dir = try tmp_dir.dir.realpathAlloc(alloc, ".");
+ defer alloc.free(resources_dir);
+
+ var env = EnvMap.init(alloc);
+ defer env.deinit();
+
+ try testing.expect(!try setupXdgDataDirs(alloc, resources_dir, &env));
+ try testing.expectEqual(0, env.count());
+}
+
+/// Set up automatic Nushell shell integration. This works by adding our
+/// shell resource directory to the `XDG_DATA_DIRS` environment variable,
+/// which Nushell will use to load `nushell/vendor/autoload/ghostty.nu`.
+///
+/// We then add `--execute 'use ghostty ...'` to the nu command line to
+/// automatically enable our shelll features.
+fn setupNushell(
+ alloc: Allocator,
+ command: config.Command,
+ resource_dir: []const u8,
+ env: *EnvMap,
+) !?config.Command {
+ // Add our XDG_DATA_DIRS entry (for nushell/vendor/autoload/). This
+ // makes our 'ghostty' module automatically available, even if any
+ // of the later checks abort the rest of our automatic integration.
+ if (!try setupXdgDataDirs(alloc, resource_dir, env)) return null;
+
+ var stack_fallback = std.heap.stackFallback(4096, alloc);
+ var cmd = internal_os.shell.ShellCommandBuilder.init(stack_fallback.get());
+ defer cmd.deinit();
+
+ // Iterator that yields each argument in the original command line.
+ // This will allocate once proportionate to the command line length.
+ var iter = try command.argIterator(alloc);
+ defer iter.deinit();
+
+ // Start accumulating arguments with the executable and initial flags.
+ if (iter.next()) |exe| {
+ try cmd.appendArg(exe);
+ } else return null;
+
+ // Tell nu to immediately "use" all of the exported functions in our
+ // 'ghostty' module.
+ //
+ // We can consider making this more specific based on the set of
+ // enabled shell features (e.g. `use ghostty sudo`). At the moment,
+ // shell features are all runtime-guarded in the nushell script.
+ try cmd.appendArg("--execute 'use ghostty *'");
+
+ // Walk through the rest of the given arguments. If we see an option that
+ // would require complex or unsupported integration behavior, we bail out
+ // and skip loading our shell integration. Users can still manually source
+ // the shell integration module.
+ //
+ // Unsupported options:
+ // -c / --command -c is always non-interactive
+ // --lsp --lsp starts the language server
+ while (iter.next()) |arg| {
+ if (std.mem.eql(u8, arg, "--command") or std.mem.eql(u8, arg, "--lsp")) {
+ return null;
+ } else if (arg.len > 1 and arg[0] == '-' and arg[1] != '-') {
+ if (std.mem.indexOfScalar(u8, arg, 'c') != null) {
+ return null;
+ }
+ try cmd.appendArg(arg);
+ } else if (std.mem.eql(u8, arg, "-") or std.mem.eql(u8, arg, "--")) {
+ // All remaining arguments should be passed directly to the shell
+ // command. We shouldn't perform any further option processing.
+ try cmd.appendArg(arg);
+ while (iter.next()) |remaining_arg| {
+ try cmd.appendArg(remaining_arg);
+ }
+ break;
+ } else {
+ try cmd.appendArg(arg);
+ }
+ }
+
+ // Return a copy of our modified command line to use as the shell command.
+ return .{ .shell = try alloc.dupeZ(u8, try cmd.toOwnedSlice()) };
+}
+
+test "nushell" {
+ const testing = std.testing;
+ var arena = ArenaAllocator.init(testing.allocator);
+ defer arena.deinit();
+ const alloc = arena.allocator();
+
+ var res: TmpResourcesDir = try .init(alloc, .nushell);
+ defer res.deinit();
+
+ var env = EnvMap.init(alloc);
+ defer env.deinit();
+
+ const command = try setupNushell(alloc, .{ .shell = "nu" }, res.path, &env);
+ try testing.expectEqualStrings("nu --execute 'use ghostty *'", command.?.shell);
+
+ var path_buf: [std.fs.max_path_bytes]u8 = undefined;
+ try testing.expectEqualStrings(
+ try std.fmt.bufPrint(&path_buf, "{s}/shell-integration", .{res.path}),
+ env.get("GHOSTTY_SHELL_INTEGRATION_XDG_DIR").?,
+ );
+ try testing.expectStringStartsWith(
+ env.get("XDG_DATA_DIRS").?,
+ try std.fmt.bufPrint(&path_buf, "{s}/shell-integration", .{res.path}),
+ );
+}
+
+test "nushell: unsupported options" {
+ const testing = std.testing;
+ var arena = ArenaAllocator.init(testing.allocator);
+ defer arena.deinit();
+ const alloc = arena.allocator();
+
+ var res: TmpResourcesDir = try .init(alloc, .nushell);
+ defer res.deinit();
+
+ const cmdlines = [_][:0]const u8{
+ "nu --command exit",
+ "nu --lsp",
+ "nu -c script.sh",
+ "nu -ic script.sh",
+ };
+
+ for (cmdlines) |cmdline| {
+ var env = EnvMap.init(alloc);
+ defer env.deinit();
+
+ try testing.expect(try setupNushell(alloc, .{ .shell = cmdline }, res.path, &env) == null);
+ try testing.expect(env.get("XDG_DATA_DIRS") != null);
+ try testing.expect(env.get("GHOSTTY_SHELL_INTEGRATION_XDG_DIR") != null);
+ }
+}
+
+test "nushell: missing resources" {
+ const testing = std.testing;
+ var arena = ArenaAllocator.init(testing.allocator);
+ defer arena.deinit();
+ const alloc = arena.allocator();
+
+ var tmp_dir = testing.tmpDir(.{});
+ defer tmp_dir.cleanup();
+
+ const resources_dir = try tmp_dir.dir.realpathAlloc(alloc, ".");
+ defer alloc.free(resources_dir);
+
+ var env = EnvMap.init(alloc);
+ defer env.deinit();
+
+ try testing.expect(try setupNushell(alloc, .{ .shell = "nu" }, resources_dir, &env) == null);
+ try testing.expectEqual(0, env.count());
}
/// Setup the zsh automatic shell integration. This works by setting
/// ZDOTDIR to our resources dir so that zsh will load our config. This
/// config then loads the true user config.
fn setupZsh(
+ alloc: Allocator,
+ command: config.Command,
resource_dir: []const u8,
env: *EnvMap,
-) !void {
+) !?config.Command {
// Preserve an existing ZDOTDIR value. We're about to overwrite it.
if (env.get("ZDOTDIR")) |old| {
try env.put("GHOSTTY_ZSH_ZDOTDIR", old);
@@ -617,34 +879,128 @@ fn setupZsh(
// Set our new ZDOTDIR to point to our shell resource directory.
var path_buf: [std.fs.max_path_bytes]u8 = undefined;
- const integ_dir = try std.fmt.bufPrint(
+ const integ_path = try std.fmt.bufPrint(
&path_buf,
"{s}/shell-integration/zsh",
.{resource_dir},
);
- try env.put("ZDOTDIR", integ_dir);
+ var integ_dir = std.fs.openDirAbsolute(integ_path, .{}) catch |err| {
+ log.warn("unable to open {s}: {}", .{ integ_path, err });
+ return null;
+ };
+ integ_dir.close();
+ try env.put("ZDOTDIR", integ_path);
+
+ return try command.clone(alloc);
}
test "zsh" {
const testing = std.testing;
+ var arena = ArenaAllocator.init(testing.allocator);
+ defer arena.deinit();
+ const alloc = arena.allocator();
+
+ var res: TmpResourcesDir = try .init(testing.allocator, .zsh);
+ defer res.deinit();
+
var env = EnvMap.init(testing.allocator);
defer env.deinit();
- try setupZsh(".", &env);
- try testing.expectEqualStrings("./shell-integration/zsh", env.get("ZDOTDIR").?);
+ const command = try setupZsh(alloc, .{ .shell = "zsh" }, res.path, &env);
+ try testing.expectEqualStrings("zsh", command.?.shell);
+ try testing.expectEqualStrings(res.shell_path, env.get("ZDOTDIR").?);
try testing.expect(env.get("GHOSTTY_ZSH_ZDOTDIR") == null);
}
test "zsh: ZDOTDIR" {
const testing = std.testing;
+ var arena = ArenaAllocator.init(testing.allocator);
+ defer arena.deinit();
+ const alloc = arena.allocator();
+
+ var res: TmpResourcesDir = try .init(testing.allocator, .zsh);
+ defer res.deinit();
+
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").?);
+ const command = try setupZsh(alloc, .{ .shell = "zsh" }, res.path, &env);
+ try testing.expectEqualStrings("zsh", command.?.shell);
+ try testing.expectEqualStrings(res.shell_path, env.get("ZDOTDIR").?);
try testing.expectEqualStrings("$HOME/.config/zsh", env.get("GHOSTTY_ZSH_ZDOTDIR").?);
}
+
+test "zsh: missing resources" {
+ const testing = std.testing;
+ var arena = ArenaAllocator.init(testing.allocator);
+ defer arena.deinit();
+ const alloc = arena.allocator();
+
+ var tmp_dir = testing.tmpDir(.{});
+ defer tmp_dir.cleanup();
+
+ const resources_dir = try tmp_dir.dir.realpathAlloc(alloc, ".");
+ defer alloc.free(resources_dir);
+
+ var env = EnvMap.init(alloc);
+ defer env.deinit();
+
+ try testing.expect(try setupZsh(alloc, .{ .shell = "zsh" }, resources_dir, &env) == null);
+ try testing.expectEqual(0, env.count());
+}
+
+/// Test helper that creates a temporary resources directory with shell integration paths.
+const TmpResourcesDir = struct {
+ allocator: Allocator,
+ tmp_dir: std.testing.TmpDir,
+ path: []const u8,
+ shell_path: []const u8,
+
+ fn init(allocator: Allocator, shell: Shell) !TmpResourcesDir {
+ var tmp_dir = std.testing.tmpDir(.{});
+ errdefer tmp_dir.cleanup();
+
+ var path_buf: [std.fs.max_path_bytes]u8 = undefined;
+ const relative_shell_path = try std.fmt.bufPrint(
+ &path_buf,
+ "shell-integration/{s}",
+ .{@tagName(shell)},
+ );
+ try tmp_dir.dir.makePath(relative_shell_path);
+
+ const path = try tmp_dir.dir.realpathAlloc(allocator, ".");
+ errdefer allocator.free(path);
+
+ const shell_path = try std.fmt.allocPrint(
+ allocator,
+ "{s}/{s}",
+ .{ path, relative_shell_path },
+ );
+ errdefer allocator.free(shell_path);
+
+ switch (shell) {
+ .bash => try tmp_dir.dir.writeFile(.{
+ .sub_path = "shell-integration/bash/ghostty.bash",
+ .data = "",
+ }),
+ else => {},
+ }
+
+ return .{
+ .allocator = allocator,
+ .tmp_dir = tmp_dir,
+ .path = path,
+ .shell_path = shell_path,
+ };
+ }
+
+ fn deinit(self: *TmpResourcesDir) void {
+ self.allocator.free(self.shell_path);
+ self.allocator.free(self.path);
+ self.tmp_dir.cleanup();
+ }
+};
diff --git a/src/termio/stream_handler.zig b/src/termio/stream_handler.zig
index 182770339..e027a5d21 100644
--- a/src/termio/stream_handler.zig
+++ b/src/termio/stream_handler.zig
@@ -119,7 +119,7 @@ pub const StreamHandler = struct {
};
// The config could have changed any of our colors so update mode 2031
- self.surfaceMessageWriter(.{ .report_color_scheme = false });
+ self.messageWriter(.{ .color_scheme_report = .{ .force = false } });
}
inline fn surfaceMessageWriter(
@@ -398,11 +398,16 @@ pub const StreamHandler = struct {
break :tmux;
},
- .exit => if (self.tmux_viewer) |viewer| {
- // Free our viewer state
- viewer.deinit();
- self.alloc.destroy(viewer);
- self.tmux_viewer = null;
+ .exit => {
+ // Free our viewer state if we have one
+ if (self.tmux_viewer) |viewer| {
+ viewer.deinit();
+ self.alloc.destroy(viewer);
+ self.tmux_viewer = null;
+ }
+
+ // And always break since we assert below
+ // that we're not handling an exit command.
break :tmux;
},
@@ -716,7 +721,7 @@ pub const StreamHandler = struct {
if (enabled) {
self.terminal.saveCursor();
} else {
- try self.terminal.restoreCursor();
+ self.terminal.restoreCursor();
}
},
@@ -866,7 +871,7 @@ pub const StreamHandler = struct {
self.messageWriter(msg);
},
- .color_scheme => self.surfaceMessageWriter(.{ .report_color_scheme = true }),
+ .color_scheme => self.messageWriter(.{ .color_scheme_report = .{ .force = true } }),
}
}
@@ -928,7 +933,7 @@ pub const StreamHandler = struct {
}
pub inline fn restoreCursor(self: *StreamHandler) !void {
- try self.terminal.restoreCursor();
+ self.terminal.restoreCursor();
}
pub fn enquiry(self: *StreamHandler) !void {
@@ -951,7 +956,10 @@ pub const StreamHandler = struct {
try self.setMouseShape(.text);
// Reset resets our palette so we report it for mode 2031.
- self.surfaceMessageWriter(.{ .report_color_scheme = false });
+ self.messageWriter(.{ .color_scheme_report = .{ .force = false } });
+
+ // Clear the progress bar
+ self.progressReport(.{ .state = .remove });
}
pub fn queryKittyKeyboard(self: *StreamHandler) !void {
diff --git a/src/unicode/grapheme.zig b/src/unicode/grapheme.zig
index 47be43bb0..d233dec72 100644
--- a/src/unicode/grapheme.zig
+++ b/src/unicode/grapheme.zig
@@ -1,6 +1,6 @@
const std = @import("std");
const table = @import("props_table.zig").table;
-const GraphemeBoundaryClass = @import("props.zig").GraphemeBoundaryClass;
+const uucode = @import("uucode");
/// Determines if there is a grapheme break between two codepoints. This
/// must be called sequentially maintaining the state between calls.
@@ -9,11 +9,11 @@ const GraphemeBoundaryClass = @import("props.zig").GraphemeBoundaryClass;
/// line feeds, and carriage returns are expected to be filtered out before
/// calling this function. This is because this function is tuned for
/// Ghostty.
-pub fn graphemeBreak(cp1: u21, cp2: u21, state: *BreakState) bool {
+pub fn graphemeBreak(cp1: u21, cp2: u21, state: *uucode.grapheme.BreakState) bool {
const value = Precompute.data[
(Precompute.Key{
- .gbc1 = table.get(cp1).grapheme_boundary_class,
- .gbc2 = table.get(cp2).grapheme_boundary_class,
+ .gb1 = table.get(cp1).grapheme_break,
+ .gb2 = table.get(cp2).grapheme_break,
.state = state.*,
}).index()
];
@@ -21,133 +21,64 @@ pub fn graphemeBreak(cp1: u21, cp2: u21, state: *BreakState) bool {
return value.result;
}
-/// The state that must be maintained between calls to `graphemeBreak`.
-pub const BreakState = packed struct(u2) {
- extended_pictographic: bool = false,
- regional_indicator: bool = false,
-};
-
/// This is all the structures and data for the precomputed lookup table
-/// for all possible permutations of state and grapheme boundary classes.
-/// Precomputation only requires 2^10 keys of 3 bit values so the whole
-/// table is less than 1KB.
+/// for all possible permutations of state and grapheme break properties.
+/// Precomputation requires 2^13 keys of 4 bit values so the whole table is
+/// 8KB.
const Precompute = struct {
- const Key = packed struct(u10) {
- state: BreakState,
- gbc1: GraphemeBoundaryClass,
- gbc2: GraphemeBoundaryClass,
+ const Key = packed struct(u13) {
+ state: uucode.grapheme.BreakState,
+ gb1: uucode.x.types.GraphemeBreakNoControl,
+ gb2: uucode.x.types.GraphemeBreakNoControl,
fn index(self: Key) usize {
- return @intCast(@as(u10, @bitCast(self)));
+ return @intCast(@as(u13, @bitCast(self)));
}
};
- const Value = packed struct(u3) {
+ const Value = packed struct(u4) {
result: bool,
- state: BreakState,
+ state: uucode.grapheme.BreakState,
};
const data = precompute: {
- var result: [std.math.maxInt(u10)]Value = undefined;
+ var result: [std.math.maxInt(u13) + 1]Value = undefined;
- @setEvalBranchQuota(3_000);
- const info = @typeInfo(GraphemeBoundaryClass).@"enum";
- for (0..std.math.maxInt(u2) + 1) |state_init| {
+ const max_state_int = blk: {
+ var max: usize = 0;
+ for (@typeInfo(uucode.grapheme.BreakState).@"enum".fields) |field| {
+ if (field.value > max) max = field.value;
+ }
+ break :blk max;
+ };
+
+ @setEvalBranchQuota(10_000);
+ const info = @typeInfo(uucode.x.types.GraphemeBreakNoControl).@"enum";
+ for (0..max_state_int + 1) |state_int| {
for (info.fields) |field1| {
for (info.fields) |field2| {
- var state: BreakState = @bitCast(@as(u2, @intCast(state_init)));
+ var state: uucode.grapheme.BreakState = @enumFromInt(state_int);
+
const key: Key = .{
- .gbc1 = @field(GraphemeBoundaryClass, field1.name),
- .gbc2 = @field(GraphemeBoundaryClass, field2.name),
+ .gb1 = @field(uucode.x.types.GraphemeBreakNoControl, field1.name),
+ .gb2 = @field(uucode.x.types.GraphemeBreakNoControl, field2.name),
.state = state,
};
- const v = graphemeBreakClass(key.gbc1, key.gbc2, &state);
+ const v = uucode.x.grapheme.computeGraphemeBreakNoControl(
+ key.gb1,
+ key.gb2,
+ &state,
+ );
result[key.index()] = .{ .result = v, .state = state };
}
}
}
+ std.debug.assert(@sizeOf(@TypeOf(result)) == 8192);
break :precompute result;
};
};
-/// This is the algorithm from utf8proc. We only use this offline for
-/// precomputing the lookup table.
-fn graphemeBreakClass(
- gbc1: GraphemeBoundaryClass,
- gbc2: GraphemeBoundaryClass,
- state: *BreakState,
-) bool {
- // GB11: Emoji Extend* ZWJ x Emoji
- if (!state.extended_pictographic and gbc1.isExtendedPictographic()) {
- state.extended_pictographic = true;
- }
-
- // These two properties are ignored because they're not relevant to
- // Ghostty -- they're filtered out before checking grapheme boundaries.
- // GB3: CR x LF
- // GB4: Control
-
- // GB6: Hangul L x (L|V|LV|VT)
- if (gbc1 == .L) {
- if (gbc2 == .L or
- gbc2 == .V or
- gbc2 == .LV or
- gbc2 == .LVT) return false;
- }
-
- // GB7: Hangul (LV | V) x (V | T)
- if (gbc1 == .LV or gbc1 == .V) {
- if (gbc2 == .V or
- gbc2 == .T) return false;
- }
-
- // GB8: Hangul (LVT | T) x T
- if (gbc1 == .LVT or gbc1 == .T) {
- if (gbc2 == .T) return false;
- }
-
- // GB9b: x (Extend | ZWJ)
- if (gbc2 == .extend or gbc2 == .zwj) return false;
-
- // GB9a: x Spacing
- if (gbc2 == .spacing_mark) return false;
-
- // GB9b: Prepend x
- if (gbc1 == .prepend) return false;
-
- // GB12, GB13: RI x RI
- if (gbc1 == .regional_indicator and gbc2 == .regional_indicator) {
- if (state.regional_indicator) {
- state.regional_indicator = false;
- return true;
- } else {
- state.regional_indicator = true;
- return false;
- }
- }
-
- // GB11: Emoji Extend* ZWJ x Emoji
- if (state.extended_pictographic and
- gbc1 == .zwj and
- gbc2.isExtendedPictographic())
- {
- state.extended_pictographic = false;
- return false;
- }
-
- // UTS #51. This isn't covered by UAX #29 as far as I can tell (but
- // I'm probably wrong). This is a special case for emoji modifiers
- // which only do not break if they're next to a base.
- //
- // emoji_modifier_sequence := emoji_modifier_base emoji_modifier
- if (gbc2 == .emoji_modifier and gbc1 == .extended_pictographic_base) {
- return false;
- }
-
- return true;
-}
-
/// If you build this file as a binary, we will verify the grapheme break
/// implementation. This iterates over billions of codepoints so it is
/// SLOW. It's not meant to be run in CI, but it's useful for debugging.
@@ -156,13 +87,11 @@ fn graphemeBreakClass(
/// adding a `-Demit-unicode-test` option for `zig build`, but that
/// hasn't been done here.
pub fn main() !void {
- const uucode = @import("uucode");
-
// Set the min and max to control the test range.
const min = 0;
const max = uucode.config.max_code_point + 1;
- var state: BreakState = .{};
+ var state: uucode.grapheme.BreakState = .default;
var uu_state: uucode.grapheme.BreakState = .default;
for (min..max) |cp1| {
if (cp1 % 1000 == 0) std.log.warn("progress cp1={}", .{cp1});
@@ -199,13 +128,53 @@ test "grapheme break: emoji modifier" {
// Emoji and modifier
{
- var state: BreakState = .{};
+ var state: uucode.grapheme.BreakState = .default;
try testing.expect(!graphemeBreak(0x261D, 0x1F3FF, &state));
}
// Non-emoji and emoji modifier
{
- var state: BreakState = .{};
+ var state: uucode.grapheme.BreakState = .default;
try testing.expect(graphemeBreak(0x22, 0x1F3FF, &state));
}
}
+
+test "long emoji zwj sequences" {
+ var state: uucode.grapheme.BreakState = .default;
+ // ๐ฉโ๐ฉโ๐งโ๐ฆ (family: woman, woman, girl, boy)
+ var it = uucode.utf8.Iterator.init("\u{1F469}\u{200D}\u{1F469}\u{200D}\u{1F467}\u{200D}\u{1F466}_");
+ var cp1 = it.next() orelse unreachable;
+ var cp2 = it.next() orelse unreachable;
+ try std.testing.expect(cp1 == 0x1F469); // ๐ฉ
+ try std.testing.expect(!graphemeBreak(cp1, cp2, &state));
+
+ cp1 = cp2;
+ cp2 = it.next() orelse unreachable;
+ try std.testing.expect(cp1 == 0x200D);
+ try std.testing.expect(!graphemeBreak(cp1, cp2, &state));
+
+ cp1 = cp2;
+ cp2 = it.next() orelse unreachable;
+ try std.testing.expect(cp1 == 0x1F469); // ๐ฉ
+ try std.testing.expect(!graphemeBreak(cp1, cp2, &state));
+
+ cp1 = cp2;
+ cp2 = it.next() orelse unreachable;
+ try std.testing.expect(cp1 == 0x200D);
+ try std.testing.expect(!graphemeBreak(cp1, cp2, &state));
+
+ cp1 = cp2;
+ cp2 = it.next() orelse unreachable;
+ try std.testing.expect(cp1 == 0x1F467); // ๐ง
+ try std.testing.expect(!graphemeBreak(cp1, cp2, &state));
+
+ cp1 = cp2;
+ cp2 = it.next() orelse unreachable;
+ try std.testing.expect(cp1 == 0x200D);
+ try std.testing.expect(!graphemeBreak(cp1, cp2, &state));
+
+ cp1 = cp2;
+ cp2 = it.next() orelse unreachable;
+ try std.testing.expect(cp1 == 0x1F466); // ๐ฆ
+ try std.testing.expect(graphemeBreak(cp1, cp2, &state)); // break
+}
diff --git a/src/unicode/main.zig b/src/unicode/main.zig
index 427c65614..11ecbd903 100644
--- a/src/unicode/main.zig
+++ b/src/unicode/main.zig
@@ -4,7 +4,6 @@ const grapheme = @import("grapheme.zig");
pub const table = @import("props_table.zig").table;
pub const Properties = @import("props.zig").Properties;
pub const graphemeBreak = grapheme.graphemeBreak;
-pub const GraphemeBreakState = grapheme.BreakState;
test {
@import("std").testing.refAllDecls(@This());
diff --git a/src/unicode/props.zig b/src/unicode/props.zig
index 492dad34a..a6615e56e 100644
--- a/src/unicode/props.zig
+++ b/src/unicode/props.zig
@@ -5,6 +5,7 @@
//! benchmarks in src/bench to verify that we haven't regressed.
const std = @import("std");
+const uucode = @import("uucode");
pub const Properties = packed struct {
/// Codepoint width. We clamp to [0, 2] since Ghostty handles control
@@ -12,8 +13,8 @@ pub const Properties = packed struct {
/// becomes a 2-em dash).
width: u2 = 0,
- /// Grapheme boundary class.
- grapheme_boundary_class: GraphemeBoundaryClass = .invalid,
+ /// Grapheme break property.
+ grapheme_break: uucode.x.types.GraphemeBreakNoControl = .other,
/// Emoji VS compatibility
emoji_vs_base: bool = false,
@@ -21,7 +22,7 @@ pub const Properties = packed struct {
// Needed for lut.Generator
pub fn eql(a: Properties, b: Properties) bool {
return a.width == b.width and
- a.grapheme_boundary_class == b.grapheme_boundary_class and
+ a.grapheme_break == b.grapheme_break and
a.emoji_vs_base == b.emoji_vs_base;
}
@@ -33,46 +34,13 @@ pub const Properties = packed struct {
try writer.print(
\\.{{
\\ .width= {},
- \\ .grapheme_boundary_class= .{s},
+ \\ .grapheme_break= .{s},
\\ .emoji_vs_base= {},
\\}}
, .{
self.width,
- @tagName(self.grapheme_boundary_class),
+ @tagName(self.grapheme_break),
self.emoji_vs_base,
});
}
};
-
-/// Possible grapheme boundary classes. This isn't an exhaustive list:
-/// we omit control, CR, LF, etc. because in Ghostty's usage that are
-/// impossible because they're handled by the terminal.
-pub const GraphemeBoundaryClass = enum(u4) {
- invalid,
- L,
- V,
- T,
- LV,
- LVT,
- prepend,
- extend,
- zwj,
- spacing_mark,
- regional_indicator,
- extended_pictographic,
- extended_pictographic_base, // \p{Extended_Pictographic} & \p{Emoji_Modifier_Base}
- emoji_modifier, // \p{Emoji_Modifier}
-
- /// Returns true if this is an extended pictographic type. This
- /// should be used instead of comparing the enum value directly
- /// because we classify multiple.
- pub fn isExtendedPictographic(self: GraphemeBoundaryClass) bool {
- return switch (self) {
- .extended_pictographic,
- .extended_pictographic_base,
- => true,
-
- else => false,
- };
- }
-};
diff --git a/src/unicode/props_uucode.zig b/src/unicode/props_uucode.zig
index 2440d437c..d876bf4ac 100644
--- a/src/unicode/props_uucode.zig
+++ b/src/unicode/props_uucode.zig
@@ -4,56 +4,17 @@ const assert = std.debug.assert;
const uucode = @import("uucode");
const lut = @import("lut.zig");
const Properties = @import("props.zig").Properties;
-const GraphemeBoundaryClass = @import("props.zig").GraphemeBoundaryClass;
-
-/// Gets the grapheme boundary class for a codepoint.
-/// The use case for this is only in generating lookup tables.
-fn graphemeBoundaryClass(cp: u21) GraphemeBoundaryClass {
- if (cp > uucode.config.max_code_point) return .invalid;
-
- return switch (uucode.get(.grapheme_break, cp)) {
- .extended_pictographic => .extended_pictographic,
- .l => .L,
- .v => .V,
- .t => .T,
- .lv => .LV,
- .lvt => .LVT,
- .prepend => .prepend,
- .zwj => .zwj,
- .spacing_mark => .spacing_mark,
- .regional_indicator => .regional_indicator,
- .emoji_modifier => .emoji_modifier,
- .emoji_modifier_base => .extended_pictographic_base,
-
- .zwnj,
- .indic_conjunct_break_extend,
- .indic_conjunct_break_linker,
- => .extend,
-
- // This is obviously not INVALID invalid, there is SOME grapheme
- // boundary class for every codepoint. But we don't care about
- // anything that doesn't fit into the above categories. Also note
- // that `indic_conjunct_break_consonant` is `other` in
- // 'GraphemeBreakProperty.txt' (it's missing).
- .other,
- .indic_conjunct_break_consonant,
- .cr,
- .lf,
- .control,
- => .invalid,
- };
-}
pub fn get(cp: u21) Properties {
if (cp > uucode.config.max_code_point) return .{
.width = 1,
- .grapheme_boundary_class = .invalid,
+ .grapheme_break = .other,
.emoji_vs_base = false,
};
return .{
.width = uucode.get(.width, cp),
- .grapheme_boundary_class = graphemeBoundaryClass(cp),
+ .grapheme_break = uucode.get(.grapheme_break_no_control, cp),
.emoji_vs_base = uucode.get(.is_emoji_vs_base, cp),
};
}
@@ -87,7 +48,10 @@ pub fn main() !void {
var buf: [4096]u8 = undefined;
var stdout = std.fs.File.stdout().writer(&buf);
try t.writeZig(&stdout.interface);
- try stdout.end();
+ // Use flush instead of end because stdout is a pipe when captured by
+ // the build system, and pipes cannot be truncated (Windows returns
+ // INVALID_PARAMETER, Linux returns EINVAL).
+ try stdout.interface.flush();
// Uncomment when manually debugging to see our table sizes.
// std.log.warn("stage1={} stage2={} stage3={}", .{
diff --git a/src/unicode/symbols_uucode.zig b/src/unicode/symbols_uucode.zig
index 8cbd59211..794ca5bab 100644
--- a/src/unicode/symbols_uucode.zig
+++ b/src/unicode/symbols_uucode.zig
@@ -34,7 +34,10 @@ pub fn main() !void {
var buf: [4096]u8 = undefined;
var stdout = std.fs.File.stdout().writer(&buf);
try t.writeZig(&stdout.interface);
- try stdout.end();
+ // Use flush instead of end because stdout is a pipe when captured by
+ // the build system, and pipes cannot be truncated (Windows returns
+ // INVALID_PARAMETER, Linux returns EINVAL).
+ try stdout.interface.flush();
// Uncomment when manually debugging to see our table sizes.
// std.log.warn("stage1={} stage2={} stage3={}", .{
diff --git a/typos.toml b/typos.toml
index 27ec9d684..8eb8d9937 100644
--- a/typos.toml
+++ b/typos.toml
@@ -19,6 +19,7 @@ extend-exclude = [
# Fonts
"*.ttf",
"*.otf",
+ "*.bdf",
# Images
"*.png",
"*.ico",
diff --git a/valgrind.supp b/valgrind.supp
index eeb395d03..27479fd5c 100644
--- a/valgrind.supp
+++ b/valgrind.supp
@@ -72,7 +72,16 @@
fun:gdk_surface_handle_event
...
}
-
+{
+ GTK CSS Node Validation
+ Memcheck:Leak
+ match-leak-kinds: possible
+ fun:malloc
+ ...
+ fun:gtk_css_node_validate_internal
+ fun:gtk_css_node_validate
+ ...
+}
{
GTK CSS Provider Leak
Memcheck:Leak
@@ -196,8 +205,44 @@
fun:svga_context_flush
...
}
-
{
+ SVGA Stuff
+ Memcheck:Leak
+ match-leak-kinds: definite
+ fun:calloc
+ fun:svga_create_surface_view
+ fun:svga_set_framebuffer_state
+ fun:st_update_framebuffer_state
+ fun:st_Clear
+ fun:gsk_gpu_render_pass_op_gl_command
+ ...
+}
+{
+ GTK Icon
+ Memcheck:Leak
+ match-leak-kinds: possible
+ fun:*alloc
+ ...
+ fun:gtk_icon_theme_set_display
+ fun:gtk_icon_theme_get_for_display
+ ...
+}
+{
+ GDK Wayland Connection
+ Memcheck:Leak
+ match-leak-kinds: possible
+ fun:calloc
+ fun:wl_closure_init
+ fun:wl_connection_demarshal
+ fun:wl_display_read_events
+ fun:gdk_wayland_poll_source_check
+ fun:g_main_context_check_unlocked
+ fun:g_main_context_iterate_unlocked.isra.0
+ fun:g_main_context_iteration
+ ...
+}
+{
+
GSK Renderer GPU Stuff
Memcheck:Leak
match-leak-kinds: possible
@@ -297,6 +342,21 @@
fun:g_main_context_iteration
...
}
+{
+ GSK More Forms
+ Memcheck:Leak
+ match-leak-kinds: possible
+ ...
+ fun:gsk_gl_device_use_program
+ fun:gsk_gl_frame_use_program
+ fun:gsk_gpu_shader_op_gl_command_n
+ fun:gsk_gpu_render_pass_op_gl_command
+ fun:gsk_gl_frame_submit
+ fun:gsk_gpu_renderer_render_texture
+ fun:gsk_renderer_render_texture
+ fun:render_contents
+ ...
+}
{
GTK Shader Selector
Memcheck:Leak