Merge remote-tracking branch 'upstream/main' into jacob/uucode
commit
ae21e2c8cf
|
|
@ -39,7 +39,7 @@ jobs:
|
|||
|
||||
echo "Version is valid: ${{ github.event.inputs.version }}"
|
||||
|
||||
- name: Exract the Version
|
||||
- name: Extract the Version
|
||||
id: extract_version
|
||||
run: |
|
||||
if [[ "${{ github.event_name }}" == "push" ]]; then
|
||||
|
|
@ -100,8 +100,10 @@ jobs:
|
|||
|
||||
- name: Create Tarball
|
||||
run: |
|
||||
rm -rf zig-out/dist
|
||||
nix develop -c zig build distcheck
|
||||
cp zig-out/dist/ghostty-${GHOSTTY_VERSION}.tar.gz .
|
||||
cp zig-out/dist/ghostty-${GHOSTTY_VERSION}.tar.gz ghostty-source.tar.gz
|
||||
|
||||
- name: Sign Tarball
|
||||
run: |
|
||||
|
|
@ -117,6 +119,8 @@ jobs:
|
|||
path: |-
|
||||
ghostty-${{ env.GHOSTTY_VERSION }}.tar.gz
|
||||
ghostty-${{ env.GHOSTTY_VERSION }}.tar.gz.minisig
|
||||
ghostty-source.tar.gz
|
||||
ghostty-source.tar.gz.minisig
|
||||
|
||||
build-macos:
|
||||
needs: [setup]
|
||||
|
|
@ -139,14 +143,14 @@ jobs:
|
|||
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
||||
|
||||
- name: XCode Select
|
||||
run: sudo xcode-select -s /Applications/Xcode_16.4.app
|
||||
run: sudo xcode-select -s /Applications/Xcode_26.0.app
|
||||
|
||||
- name: Xcode Version
|
||||
run: xcodebuild -version
|
||||
|
||||
- name: Setup Sparkle
|
||||
env:
|
||||
SPARKLE_VERSION: 2.7.1
|
||||
SPARKLE_VERSION: 2.7.3
|
||||
run: |
|
||||
mkdir -p .action/sparkle
|
||||
cd .action/sparkle
|
||||
|
|
@ -311,7 +315,7 @@ jobs:
|
|||
|
||||
- name: Setup Sparkle
|
||||
env:
|
||||
SPARKLE_VERSION: 2.7.1
|
||||
SPARKLE_VERSION: 2.7.3
|
||||
run: |
|
||||
mkdir -p .action/sparkle
|
||||
cd .action/sparkle
|
||||
|
|
|
|||
|
|
@ -243,7 +243,7 @@ jobs:
|
|||
# Setup Sparkle
|
||||
- name: Setup Sparkle
|
||||
env:
|
||||
SPARKLE_VERSION: 2.7.1
|
||||
SPARKLE_VERSION: 2.7.3
|
||||
run: |
|
||||
mkdir -p .action/sparkle
|
||||
cd .action/sparkle
|
||||
|
|
@ -481,7 +481,7 @@ jobs:
|
|||
# Setup Sparkle
|
||||
- name: Setup Sparkle
|
||||
env:
|
||||
SPARKLE_VERSION: 2.7.1
|
||||
SPARKLE_VERSION: 2.7.3
|
||||
run: |
|
||||
mkdir -p .action/sparkle
|
||||
cd .action/sparkle
|
||||
|
|
@ -666,7 +666,7 @@ jobs:
|
|||
# Setup Sparkle
|
||||
- name: Setup Sparkle
|
||||
env:
|
||||
SPARKLE_VERSION: 2.7.1
|
||||
SPARKLE_VERSION: 2.7.3
|
||||
run: |
|
||||
mkdir -p .action/sparkle
|
||||
cd .action/sparkle
|
||||
|
|
|
|||
|
|
@ -273,6 +273,7 @@ jobs:
|
|||
ghostty-source.tar.gz
|
||||
|
||||
trigger-snap:
|
||||
if: github.event_name != 'pull_request'
|
||||
runs-on: namespace-profile-ghostty-xsm
|
||||
needs: build-dist
|
||||
steps:
|
||||
|
|
|
|||
|
|
@ -39,10 +39,14 @@ jobs:
|
|||
|
||||
- name: Run zig fetch
|
||||
id: zig_fetch
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
UPSTREAM_REV="$(curl "https://api.github.com/repos/mbadolato/iTerm2-Color-Schemes/commits/master" | jq -r '.sha')"
|
||||
nix develop -c zig fetch --save="iterm2_themes" "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/$UPSTREAM_REV.tar.gz"
|
||||
echo "upstream_rev=$UPSTREAM_REV" >> "$GITHUB_OUTPUT"
|
||||
# Get the latest release from iTerm2-Color-Schemes
|
||||
RELEASE_INFO=$(gh api repos/mbadolato/iTerm2-Color-Schemes/releases/latest)
|
||||
TAG_NAME=$(echo "$RELEASE_INFO" | jq -r '.tag_name')
|
||||
nix develop -c zig fetch --save="iterm2_themes" "https://github.com/mbadolato/iTerm2-Color-Schemes/releases/download/${TAG_NAME}/ghostty-themes.tgz"
|
||||
echo "tag_name=$TAG_NAME" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Update zig cache hash
|
||||
run: |
|
||||
|
|
@ -71,5 +75,5 @@ jobs:
|
|||
build.zig.zon.json
|
||||
flatpak/zig-packages.json
|
||||
body: |
|
||||
Upstream revision: https://github.com/mbadolato/iTerm2-Color-Schemes/tree/${{ steps.zig_fetch.outputs.upstream_rev }}
|
||||
Upstream release: https://github.com/mbadolato/iTerm2-Color-Schemes/releases/tag/${{ steps.zig_fetch.outputs.tag_name }}
|
||||
labels: dependencies
|
||||
|
|
|
|||
|
|
@ -183,6 +183,7 @@
|
|||
/po/ga_IE.UTF-8.po @ghostty-org/ga_IE
|
||||
/po/ko_KR.UTF-8.po @ghostty-org/ko_KR
|
||||
/po/he_IL.UTF-8.po @ghostty-org/he_IL
|
||||
/po/it_IT.UTF-8.po @ghostty-org/it_IT
|
||||
|
||||
# Packaging - Snap
|
||||
/snap/ @ghostty-org/snap
|
||||
|
|
|
|||
|
|
@ -150,7 +150,7 @@ hash in CI, and builds will fail if it drifts.
|
|||
To update it, you can run the following in the repository root:
|
||||
|
||||
```
|
||||
./nix/build-support/check-zig-cache-hash.sh --update
|
||||
./nix/build-support/check-zig-cache.sh --update
|
||||
```
|
||||
|
||||
This will write out the `nix/zigCacheHash.nix` file with the updated hash
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
.{
|
||||
.name = .ghostty,
|
||||
.version = "1.1.4",
|
||||
.version = "1.2.1",
|
||||
.paths = .{""},
|
||||
.fingerprint = 0x64407a2a0b4147e5,
|
||||
.dependencies = .{
|
||||
|
|
@ -20,8 +20,8 @@
|
|||
},
|
||||
.z2d = .{
|
||||
// vancluever/z2d
|
||||
.url = "https://github.com/vancluever/z2d/archive/refs/tags/v0.8.0.tar.gz",
|
||||
.hash = "z2d-0.8.0-j5P_HgW8DQBvCefcGWPZA1WC5Nco08WhKjG3XCW3thMr",
|
||||
.url = "https://github.com/vancluever/z2d/archive/refs/tags/v0.8.1.tar.gz",
|
||||
.hash = "z2d-0.8.1-j5P_Hq8vDwB8ZaDA54-SzESDLF2zznG_zvTHiQNJImZP",
|
||||
.lazy = true,
|
||||
},
|
||||
.zig_objc = .{
|
||||
|
|
@ -111,8 +111,8 @@
|
|||
// Other
|
||||
.apple_sdk = .{ .path = "./pkg/apple-sdk" },
|
||||
.iterm2_themes = .{
|
||||
.url = "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/b314fc540434cc037c2811fc048d32854b5b78c3.tar.gz",
|
||||
.hash = "N-V-__8AAGupuwFrRxb2dkqFqmEChLEa4J3e95GReqvomV1b",
|
||||
.url = "https://deps.files.ghostty.org/ghostty-themes-20250915-162204-b1fe546.tgz",
|
||||
.hash = "N-V-__8AANodAwDnyHwhlOv5cVRn2rx_dTvija-wy5YtTw1B",
|
||||
.lazy = true,
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -49,10 +49,10 @@
|
|||
"url": "https://deps.files.ghostty.org/imgui-1220bc6b9daceaf7c8c60f3c3998058045ba0c5c5f48ae255ff97776d9cd8bfc6402.tar.gz",
|
||||
"hash": "sha256-oF/QHgTPEat4Hig4fGIdLkIPHmBEyOJ6JeYD6pnveGA="
|
||||
},
|
||||
"N-V-__8AAGupuwFrRxb2dkqFqmEChLEa4J3e95GReqvomV1b": {
|
||||
"N-V-__8AANodAwDnyHwhlOv5cVRn2rx_dTvija-wy5YtTw1B": {
|
||||
"name": "iterm2_themes",
|
||||
"url": "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/b314fc540434cc037c2811fc048d32854b5b78c3.tar.gz",
|
||||
"hash": "sha256-3vPlDDjv6BCLyro1YytzPtF0FfBH20skYuA9laDWhac="
|
||||
"url": "https://deps.files.ghostty.org/ghostty-themes-20250915-162204-b1fe546.tgz",
|
||||
"hash": "sha256-6rKNFpaUvSbvNZ0/+u0h4I/RRaV5V7xIPQ9y7eNVbCA="
|
||||
},
|
||||
"N-V-__8AAIC5lwAVPJJzxnCAahSvZTIlG-HhtOvnM1uh-66x": {
|
||||
"name": "jetbrains_mono",
|
||||
|
|
@ -134,10 +134,10 @@
|
|||
"url": "https://deps.files.ghostty.org/wuffs-122037b39d577ec2db3fd7b2130e7b69ef6cc1807d68607a7c232c958315d381b5cd.tar.gz",
|
||||
"hash": "sha256-nkzSCr6W5sTG7enDBXEIhgEm574uLD41UVR2wlC+HBM="
|
||||
},
|
||||
"z2d-0.8.0-j5P_HgW8DQBvCefcGWPZA1WC5Nco08WhKjG3XCW3thMr": {
|
||||
"z2d-0.8.1-j5P_Hq8vDwB8ZaDA54-SzESDLF2zznG_zvTHiQNJImZP": {
|
||||
"name": "z2d",
|
||||
"url": "https://github.com/vancluever/z2d/archive/refs/tags/v0.8.0.tar.gz",
|
||||
"hash": "sha256-0yR5Yc5MxOJBV1cv4LOWBwWkZYcGU53qFZd40TlZPcg="
|
||||
"url": "https://github.com/vancluever/z2d/archive/refs/tags/v0.8.1.tar.gz",
|
||||
"hash": "sha256-0DbDKSYA1ejhVx/WbOkwTgD57PNRFcnRviqBh8xpPZ0="
|
||||
},
|
||||
"zf-0.10.3-OIRy8aiIAACLrBllz0zjxaH0aOe5oNm3KtEMyCntST-9": {
|
||||
"name": "zf",
|
||||
|
|
|
|||
|
|
@ -163,11 +163,11 @@ in
|
|||
};
|
||||
}
|
||||
{
|
||||
name = "N-V-__8AAGupuwFrRxb2dkqFqmEChLEa4J3e95GReqvomV1b";
|
||||
name = "N-V-__8AANodAwDnyHwhlOv5cVRn2rx_dTvija-wy5YtTw1B";
|
||||
path = fetchZigArtifact {
|
||||
name = "iterm2_themes";
|
||||
url = "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/b314fc540434cc037c2811fc048d32854b5b78c3.tar.gz";
|
||||
hash = "sha256-3vPlDDjv6BCLyro1YytzPtF0FfBH20skYuA9laDWhac=";
|
||||
url = "https://deps.files.ghostty.org/ghostty-themes-20250915-162204-b1fe546.tgz";
|
||||
hash = "sha256-6rKNFpaUvSbvNZ0/+u0h4I/RRaV5V7xIPQ9y7eNVbCA=";
|
||||
};
|
||||
}
|
||||
{
|
||||
|
|
@ -299,11 +299,11 @@ in
|
|||
};
|
||||
}
|
||||
{
|
||||
name = "z2d-0.8.0-j5P_HgW8DQBvCefcGWPZA1WC5Nco08WhKjG3XCW3thMr";
|
||||
name = "z2d-0.8.1-j5P_Hq8vDwB8ZaDA54-SzESDLF2zznG_zvTHiQNJImZP";
|
||||
path = fetchZigArtifact {
|
||||
name = "z2d";
|
||||
url = "https://github.com/vancluever/z2d/archive/refs/tags/v0.8.0.tar.gz";
|
||||
hash = "sha256-0yR5Yc5MxOJBV1cv4LOWBwWkZYcGU53qFZd40TlZPcg=";
|
||||
url = "https://github.com/vancluever/z2d/archive/refs/tags/v0.8.1.tar.gz";
|
||||
hash = "sha256-0DbDKSYA1ejhVx/WbOkwTgD57PNRFcnRviqBh8xpPZ0=";
|
||||
};
|
||||
}
|
||||
{
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ https://deps.files.ghostty.org/breakpad-b99f444ba5f6b98cac261cbb391d8766b34a5918
|
|||
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-20250915-162204-b1fe546.tgz
|
||||
https://deps.files.ghostty.org/glslang-12201278a1a05c0ce0b6eb6026c65cd3e9247aa041b1c260324bf29cee559dd23ba1.tar.gz
|
||||
https://deps.files.ghostty.org/gtk4-layer-shell-1.1.0.tar.gz
|
||||
https://deps.files.ghostty.org/harfbuzz-11.0.0.tar.xz
|
||||
|
|
@ -28,8 +29,7 @@ https://deps.files.ghostty.org/zig_js-12205a66d423259567764fa0fc60c82be35365c21a
|
|||
https://deps.files.ghostty.org/zlib-1220fed0c74e1019b3ee29edae2051788b080cd96e90d56836eea857b0b966742efb.tar.gz
|
||||
https://github.com/jacobsandlund/uucode/archive/69782fbe79e06a34ee177978d3479ed5801ce0af.tar.gz
|
||||
https://github.com/jcollie/ghostty-gobject/releases/download/0.15.1-2025-09-04-48-1/ghostty-gobject-0.15.1-2025-09-04-48-1.tar.zst
|
||||
https://github.com/mbadolato/iTerm2-Color-Schemes/archive/b314fc540434cc037c2811fc048d32854b5b78c3.tar.gz
|
||||
https://github.com/mitchellh/libxev/archive/7f803181b158a10fec8619f793e3b4df515566cb.tar.gz
|
||||
https://github.com/mitchellh/zig-objc/archive/c9e917a4e15a983b672ca779c7985d738a2d517c.tar.gz
|
||||
https://github.com/natecraddock/zf/archive/7aacbe6d155d64d15937ca95ca6c014905eb531f.tar.gz
|
||||
https://github.com/vancluever/z2d/archive/refs/tags/v0.8.0.tar.gz
|
||||
https://github.com/vancluever/z2d/archive/refs/tags/v0.8.1.tar.gz
|
||||
|
|
|
|||
|
|
@ -61,9 +61,9 @@
|
|||
},
|
||||
{
|
||||
"type": "archive",
|
||||
"url": "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/b314fc540434cc037c2811fc048d32854b5b78c3.tar.gz",
|
||||
"dest": "vendor/p/N-V-__8AAGupuwFrRxb2dkqFqmEChLEa4J3e95GReqvomV1b",
|
||||
"sha256": "def3e50c38efe8108bcaba35632b733ed17415f047db4b2462e03d95a0d685a7"
|
||||
"url": "https://deps.files.ghostty.org/ghostty-themes-20250915-162204-b1fe546.tgz",
|
||||
"dest": "vendor/p/N-V-__8AANodAwDnyHwhlOv5cVRn2rx_dTvija-wy5YtTw1B",
|
||||
"sha256": "eab28d169694bd26ef359d3ffaed21e08fd145a57957bc483d0f72ede3556c20"
|
||||
},
|
||||
{
|
||||
"type": "archive",
|
||||
|
|
@ -163,9 +163,9 @@
|
|||
},
|
||||
{
|
||||
"type": "archive",
|
||||
"url": "https://github.com/vancluever/z2d/archive/refs/tags/v0.8.0.tar.gz",
|
||||
"dest": "vendor/p/z2d-0.8.0-j5P_HgW8DQBvCefcGWPZA1WC5Nco08WhKjG3XCW3thMr",
|
||||
"sha256": "d3247961ce4cc4e24157572fe0b3960705a4658706539dea159778d139593dc8"
|
||||
"url": "https://github.com/vancluever/z2d/archive/refs/tags/v0.8.1.tar.gz",
|
||||
"dest": "vendor/p/z2d-0.8.1-j5P_Hq8vDwB8ZaDA54-SzESDLF2zznG_zvTHiQNJImZP",
|
||||
"sha256": "d036c3292600d5e8e1571fd66ce9304e00f9ecf35115c9d1be2a8187cc693d9d"
|
||||
},
|
||||
{
|
||||
"type": "archive",
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@
|
|||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/sparkle-project/Sparkle",
|
||||
"state" : {
|
||||
"revision" : "df074165274afaa39539c05d57b0832620775b11",
|
||||
"version" : "2.7.1"
|
||||
"revision" : "06beff60e3b609a485290e4d5d2d1e2eedb1e55d",
|
||||
"version" : "2.7.3"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
|
|||
|
|
@ -147,6 +147,16 @@ class AppDelegate: NSObject,
|
|||
// Disable the automatic full screen menu item because we handle
|
||||
// it manually.
|
||||
"NSFullScreenMenuItemEverywhere": false,
|
||||
|
||||
// On macOS 26 RC1, the autofill heuristic controller causes unusable levels
|
||||
// of slowdowns and CPU usage in the terminal window under certain [unknown]
|
||||
// conditions. We don't know exactly why/how. This disables the full heuristic
|
||||
// controller.
|
||||
//
|
||||
// Practically, this means things like SMS autofill don't work, but that is
|
||||
// a desirable behavior to NOT have happen for a terminal, so this is a win.
|
||||
// Manual autofill via the `Edit => AutoFill` menu item still work as expected.
|
||||
"NSAutoFillHeuristicControllerEnabled": false,
|
||||
])
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -123,7 +123,8 @@ struct NewTerminalIntent: AppIntent {
|
|||
|
||||
if let view = controller.newSplit(
|
||||
at: parent,
|
||||
direction: location.splitDirection!
|
||||
direction: location.splitDirection!,
|
||||
baseConfig: config
|
||||
) {
|
||||
return .result(value: TerminalEntity(view))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -165,7 +165,12 @@ fileprivate struct CommandPaletteQuery: View {
|
|||
.textFieldStyle(.plain)
|
||||
.focused($isTextFieldFocused)
|
||||
.onAppear {
|
||||
isTextFieldFocused = true
|
||||
// We want to grab focus on appearance. We have to do this after a tick
|
||||
// on macOS Tahoe otherwise this doesn't work. See:
|
||||
// https://github.com/ghostty-org/ghostty/issues/8497
|
||||
DispatchQueue.main.async {
|
||||
isTextFieldFocused = true
|
||||
}
|
||||
}
|
||||
.onChange(of: isTextFieldFocused) { focused in
|
||||
if !focused {
|
||||
|
|
|
|||
|
|
@ -1219,6 +1219,23 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr
|
|||
|
||||
// Get our target window
|
||||
let targetWindow = tabbedWindows[finalIndex]
|
||||
|
||||
// Moving tabs on macOS 26 RC causes very nasty visual glitches in the titlebar tabs.
|
||||
// I believe this is due to messed up constraints for our hacky tab bar. I'd like to
|
||||
// find a better workaround. For now, this improves things dramatically.
|
||||
//
|
||||
// Reproduction: titlebar tabs, create two tabs, "move tab left"
|
||||
if #available(macOS 26, *) {
|
||||
if window is TitlebarTabsTahoeTerminalWindow {
|
||||
tabGroup.removeWindow(selectedWindow)
|
||||
targetWindow.addTabbedWindow(selectedWindow, ordered: action.amount < 0 ? .below : .above)
|
||||
DispatchQueue.main.async {
|
||||
selectedWindow.makeKey()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Begin a group of window operations to minimize visual updates
|
||||
NSAnimationContext.beginGrouping()
|
||||
|
|
|
|||
|
|
@ -156,7 +156,7 @@ class TitlebarTabsTahoeTerminalWindow: TransparentTitlebarTerminalWindow, NSTool
|
|||
accessoryView.needsLayout = true
|
||||
|
||||
// Setup an observer for the NSTabBar frame. When system appearance changes or
|
||||
// other events occur, the tab bar can temporarily become zero-sized. When this
|
||||
// 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
|
||||
|
|
@ -167,9 +167,6 @@ class TitlebarTabsTahoeTerminalWindow: TransparentTitlebarTerminalWindow, NSTool
|
|||
) { [weak self] _ in
|
||||
guard let self else { return }
|
||||
|
||||
// Check if either width or height is zero
|
||||
guard tabBar.frame.size.width == 0 || tabBar.frame.size.height == 0 else { return }
|
||||
|
||||
// Remove the observer so we can call setup again.
|
||||
self.tabBarObserver = nil
|
||||
|
||||
|
|
|
|||
|
|
@ -80,16 +80,13 @@ class TransparentTitlebarTerminalWindow: TerminalWindow {
|
|||
// window background but with opacity. The window background is set using the
|
||||
// "preferred background color" property.
|
||||
//
|
||||
// As an inverse, if we don't have transparency, we don't bother with this because
|
||||
// the window background will be set to the correct color so we can just hide the
|
||||
// titlebar completely and we're good to go.
|
||||
if !isOpaque {
|
||||
if let titlebarView = titlebarContainer?.firstDescendant(withClassName: "NSTitlebarView") {
|
||||
titlebarView.wantsLayer = true
|
||||
titlebarView.layer?.backgroundColor = preferredBackgroundColor?.cgColor
|
||||
}
|
||||
// Even if we aren't transparent, we still set this because this becomes the
|
||||
// color of the titlebar in native fullscreen view.
|
||||
if let titlebarView = titlebarContainer?.firstDescendant(withClassName: "NSTitlebarView") {
|
||||
titlebarView.wantsLayer = true
|
||||
titlebarView.layer?.backgroundColor = 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
|
||||
|
|
|
|||
|
|
@ -509,13 +509,14 @@ extension Ghostty {
|
|||
// Make the text field the first responder so it gets focus
|
||||
alert.window.initialFirstResponder = textField
|
||||
|
||||
let response = alert.runModal()
|
||||
|
||||
// Check if the user clicked "OK"
|
||||
if response == .alertFirstButtonReturn {
|
||||
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 {
|
||||
// Empty means that user wants the title to be set automatically
|
||||
// We also need to reload the config for the "title" property to be
|
||||
|
|
@ -529,6 +530,16 @@ extension Ghostty {
|
|||
title = newTitle
|
||||
}
|
||||
}
|
||||
|
||||
// We prefer to run our alert in a sheet modal if we have a window.
|
||||
if let window {
|
||||
alert.beginSheetModal(for: window, completionHandler: completionHandler)
|
||||
} else {
|
||||
// On macOS 26 RC, this codepath results in the "OK" button not being
|
||||
// visible. The above codepath should be taken most times but I'm just
|
||||
// noting this as something I noticed consistently.
|
||||
completionHandler(alert.runModal())
|
||||
}
|
||||
}
|
||||
|
||||
func setTitle(_ title: String) {
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ help() {
|
|||
echo "To fix, please (manually) re-run the script from the repository root,"
|
||||
echo "commit, and submit a PR with the update:"
|
||||
echo ""
|
||||
echo " ./nix/build-support/check-zig-cache-hash.sh --update"
|
||||
echo " ./nix/build-support/check-zig-cache.sh --update"
|
||||
echo " git add build.zig.zon.nix build.zig.zon.txt build.zig.zon.json flatpak/zig-packages.json"
|
||||
echo " git commit -m \"nix: update build.zig.zon.nix build.zig.zon.txt build.zig.zon.json flatpak/zig-packages.json\""
|
||||
echo ""
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@
|
|||
in
|
||||
stdenv.mkDerivation (finalAttrs: {
|
||||
pname = "ghostty";
|
||||
version = "1.1.4";
|
||||
version = "1.2.1";
|
||||
|
||||
# We limit source like this to try and reduce the amount of rebuilds as possible
|
||||
# thus we only provide the source that is needed for the build
|
||||
|
|
|
|||
|
|
@ -0,0 +1,320 @@
|
|||
# Italian translations for com.mitchellh.ghostty package
|
||||
# Traduzioni italiane per il pacchetto com.mitchellh.ghostty.
|
||||
# Copyright (C) 2025 "Mitchell Hashimoto, Ghostty contributors"
|
||||
# This file is distributed under the same license as the com.mitchellh.ghostty package.
|
||||
# Giacomo Bettini <giaco.bettini@gmail.com>, 2025.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: com.mitchellh.ghostty\n"
|
||||
"Report-Msgid-Bugs-To: m@mitchellh.com\n"
|
||||
"POT-Creation-Date: 2025-07-22 17:18+0000\n"
|
||||
"PO-Revision-Date: 2025-09-06 19:40+0200\n"
|
||||
"Last-Translator: Giacomo Bettini <giaco.bettini@gmail.com>\n"
|
||||
"Language-Team: Italian <tp@lists.linux.it>\n"
|
||||
"Language: it\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:5
|
||||
msgid "Change Terminal Title"
|
||||
msgstr "Cambia il titolo del terminale"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:6
|
||||
msgid "Leave blank to restore the default title."
|
||||
msgstr "Lasciare vuoto per ripristinare il titolo predefinito."
|
||||
|
||||
#: 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 "Annulla"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:10
|
||||
msgid "OK"
|
||||
msgstr "OK"
|
||||
|
||||
#: 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 "Errori di configurazione"
|
||||
|
||||
#: 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 ""
|
||||
"Sono stati trovati uno o più errori di configurazione. Controlla gli errori seguenti, "
|
||||
"poi ricarica la tua configurazione o ignora quegli errori."
|
||||
|
||||
#: 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 "Ignora"
|
||||
|
||||
#: 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 "Ricarica configurazione"
|
||||
|
||||
#: 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 "Dividi in alto"
|
||||
|
||||
#: 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 "Dividi in basso"
|
||||
|
||||
#: 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 "Dividi a sinistra"
|
||||
|
||||
#: 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 "Dividi a destra"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/command-palette.blp:16
|
||||
msgid "Execute a command…"
|
||||
msgstr "Esegui un comando…"
|
||||
|
||||
#: 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 "Copia"
|
||||
|
||||
#: 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 "Incolla"
|
||||
|
||||
#: 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 "Pulisci"
|
||||
|
||||
#: 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 "Reimposta"
|
||||
|
||||
#: 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 "Divisione"
|
||||
|
||||
#: 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 "Cambia titolo…"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:59
|
||||
msgid "Tab"
|
||||
msgstr "Scheda"
|
||||
|
||||
#: 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 "Nuova scheda"
|
||||
|
||||
#: 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 "Chiudi scheda"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:73
|
||||
msgid "Window"
|
||||
msgstr "Finestra"
|
||||
|
||||
#: 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 "Nuova finestra"
|
||||
|
||||
#: 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 "Chiudi finestra"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:89
|
||||
msgid "Config"
|
||||
msgstr "Configurazione"
|
||||
|
||||
#: 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 "Apri configurazione"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:85
|
||||
msgid "Command Palette"
|
||||
msgstr "Riquadro comandi"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:90
|
||||
msgid "Terminal Inspector"
|
||||
msgstr "Ispettore del terminale"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:107
|
||||
#: src/apprt/gtk/Window.zig:1038
|
||||
msgid "About Ghostty"
|
||||
msgstr "Informazioni su Ghostty"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:112
|
||||
msgid "Quit"
|
||||
msgstr "Chiudi"
|
||||
|
||||
#: 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 "Consenti accesso agli Appunti"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:7
|
||||
#: src/apprt/gtk/ui/1.2/ccw-osc-52-read.blp:7
|
||||
msgid ""
|
||||
"An application is attempting to read from the clipboard. The current "
|
||||
"clipboard contents are shown below."
|
||||
msgstr ""
|
||||
"Un'applicazione sta cercando di leggere dagli Appunti. Il contenuto "
|
||||
"attuale degli Appunti è mostrato di seguito."
|
||||
|
||||
#: 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 "Non consentire"
|
||||
|
||||
#: 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 "Consenti"
|
||||
|
||||
#: 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 "Ricorda scelta per questa divisione"
|
||||
|
||||
#: 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 "Ricarica la configurazione per visualizzare nuovamente questo messaggio"
|
||||
|
||||
#: 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 ""
|
||||
"Un'applicazione sta cercando di scrivere negli Appunti. Il contenuto "
|
||||
"attuale degli Appunti è mostrato di seguito."
|
||||
|
||||
#: 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 "Attenzione: Incolla potenzialmente pericoloso"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-paste.blp:7 src/apprt/gtk/ui/1.2/ccw-paste.blp:7
|
||||
msgid ""
|
||||
"Pasting this text into the terminal may be dangerous as it looks like some "
|
||||
"commands may be executed."
|
||||
msgstr ""
|
||||
"Incollare questo testo nel terminale potrebbe essere pericoloso poiché "
|
||||
"sembra contenere comandi che potrebbero venire eseguiti."
|
||||
|
||||
#: src/apprt/gtk/CloseDialog.zig:47 src/apprt/gtk/Surface.zig:2531
|
||||
msgid "Close"
|
||||
msgstr "Chiudi"
|
||||
|
||||
#: src/apprt/gtk/CloseDialog.zig:87
|
||||
msgid "Quit Ghostty?"
|
||||
msgstr "Chiudere Ghostty?"
|
||||
|
||||
#: src/apprt/gtk/CloseDialog.zig:88
|
||||
msgid "Close Window?"
|
||||
msgstr "Chiudere la finestra?"
|
||||
|
||||
#: src/apprt/gtk/CloseDialog.zig:89
|
||||
msgid "Close Tab?"
|
||||
msgstr "Chiudere la scheda?"
|
||||
|
||||
#: src/apprt/gtk/CloseDialog.zig:90
|
||||
msgid "Close Split?"
|
||||
msgstr "Chiudere la divisione?"
|
||||
|
||||
#: src/apprt/gtk/CloseDialog.zig:96
|
||||
msgid "All terminal sessions will be terminated."
|
||||
msgstr "Tutte le sessioni del terminale saranno terminate."
|
||||
|
||||
#: src/apprt/gtk/CloseDialog.zig:97
|
||||
msgid "All terminal sessions in this window will be terminated."
|
||||
msgstr "Tutte le sessioni del terminale in questa finestra saranno terminate."
|
||||
|
||||
#: src/apprt/gtk/CloseDialog.zig:98
|
||||
msgid "All terminal sessions in this tab will be terminated."
|
||||
msgstr "Tutte le sessioni del terminale in questa scheda saranno terminate."
|
||||
|
||||
#: src/apprt/gtk/CloseDialog.zig:99
|
||||
msgid "The currently running process in this split will be terminated."
|
||||
msgstr "Il processo attualmente in esecuzione in questa divisione sarà terminato."
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:1266
|
||||
msgid "Copied to clipboard"
|
||||
msgstr "Copiato negli Appunti"
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:1268
|
||||
msgid "Cleared clipboard"
|
||||
msgstr "Appunti svuotati"
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:2525
|
||||
msgid "Command succeeded"
|
||||
msgstr "Comando riuscito"
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:2527
|
||||
msgid "Command failed"
|
||||
msgstr "Comando fallito"
|
||||
|
||||
#: src/apprt/gtk/Window.zig:216
|
||||
msgid "Main Menu"
|
||||
msgstr "Menù principale"
|
||||
|
||||
#: src/apprt/gtk/Window.zig:239
|
||||
msgid "View Open Tabs"
|
||||
msgstr "Vedi schede aperte"
|
||||
|
||||
#: src/apprt/gtk/Window.zig:266
|
||||
msgid "New Split"
|
||||
msgstr "Nuova divisione"
|
||||
|
||||
#: src/apprt/gtk/Window.zig:329
|
||||
msgid ""
|
||||
"⚠️ You're running a debug build of Ghostty! Performance will be degraded."
|
||||
msgstr ""
|
||||
"⚠️ Stai usando una build di debug di Ghostty! Le prestazioni saranno ridotte."
|
||||
|
||||
#: src/apprt/gtk/Window.zig:775
|
||||
msgid "Reloaded the configuration"
|
||||
msgstr "Configurazione ricaricata"
|
||||
|
||||
#: src/apprt/gtk/Window.zig:1019
|
||||
msgid "Ghostty Developers"
|
||||
msgstr "Sviluppatori di Ghostty"
|
||||
|
||||
#: src/apprt/gtk/inspector.zig:144
|
||||
msgid "Ghostty: Terminal Inspector"
|
||||
msgstr "Ghostty: Ispettore del terminale"
|
||||
|
|
@ -9,7 +9,7 @@ msgstr ""
|
|||
"Project-Id-Version: com.mitchellh.ghostty\n"
|
||||
"Report-Msgid-Bugs-To: m@mitchellh.com\n"
|
||||
"POT-Creation-Date: 2025-07-22 17:18+0000\n"
|
||||
"PO-Revision-Date: 2025-03-21 00:08+0900\n"
|
||||
"PO-Revision-Date: 2025-09-01 14:43+0900\n"
|
||||
"Last-Translator: Lon Sagisawa <lon@sagisawa.me>\n"
|
||||
"Language-Team: Japanese\n"
|
||||
"Language: ja\n"
|
||||
|
|
@ -88,7 +88,7 @@ msgstr "右に分割"
|
|||
|
||||
#: src/apprt/gtk/ui/1.5/command-palette.blp:16
|
||||
msgid "Execute a command…"
|
||||
msgstr ""
|
||||
msgstr "コマンドを実行…"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:6
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:6
|
||||
|
|
@ -161,7 +161,7 @@ msgstr "設定ファイルを開く"
|
|||
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:85
|
||||
msgid "Command Palette"
|
||||
msgstr ""
|
||||
msgstr "コマンドパレット"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:90
|
||||
msgid "Terminal Inspector"
|
||||
|
|
@ -209,12 +209,12 @@ msgstr "許可"
|
|||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:81
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:77
|
||||
msgid "Remember choice for this split"
|
||||
msgstr ""
|
||||
msgstr "この分割ウィンドウに対して設定を記憶する"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:82
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:78
|
||||
msgid "Reload configuration to show this prompt again"
|
||||
msgstr ""
|
||||
msgstr "このプロンプトを再び表示するには設定を再読み込みしてください"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:7
|
||||
#: src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp:7
|
||||
|
|
@ -279,15 +279,15 @@ msgstr "クリップボードにコピーしました"
|
|||
|
||||
#: src/apprt/gtk/Surface.zig:1268
|
||||
msgid "Cleared clipboard"
|
||||
msgstr ""
|
||||
msgstr "クリップボードを空にしました"
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:2525
|
||||
msgid "Command succeeded"
|
||||
msgstr ""
|
||||
msgstr "コマンド実行成功"
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:2527
|
||||
msgid "Command failed"
|
||||
msgstr ""
|
||||
msgstr "コマンド実行失敗"
|
||||
|
||||
#: src/apprt/gtk/Window.zig:216
|
||||
msgid "Main Menu"
|
||||
|
|
@ -299,7 +299,7 @@ msgstr "開いているすべてのタブを表示"
|
|||
|
||||
#: src/apprt/gtk/Window.zig:266
|
||||
msgid "New Split"
|
||||
msgstr ""
|
||||
msgstr "新しい分割"
|
||||
|
||||
#: src/apprt/gtk/Window.zig:329
|
||||
msgid ""
|
||||
|
|
|
|||
|
|
@ -4,14 +4,15 @@
|
|||
# This file is distributed under the same license as the com.mitchellh.ghostty package.
|
||||
# Gustavo Peres <gsodevel@gmail.com>, 2025.
|
||||
# Guilherme Tiscoski <github@guilhermetiscoski.com>, 2025.
|
||||
# Nilton Perim Neto <niltonperimneto@gmail.com>, 2025.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: com.mitchellh.ghostty\n"
|
||||
"Report-Msgid-Bugs-To: m@mitchellh.com\n"
|
||||
"POT-Creation-Date: 2025-07-22 17:18+0000\n"
|
||||
"PO-Revision-Date: 2025-08-25 11:46-0500\n"
|
||||
"Last-Translator: Guilherme Tiscoski <github@guihermetiscoski.com>\n"
|
||||
"PO-Revision-Date: 2025-09-15 13:57-0300\n"
|
||||
"Last-Translator: Nilton Perim Neto <niltonperimneto@gmail.com>\n"
|
||||
"Language-Team: Brazilian Portuguese <ldpbr-translation@lists.sourceforge."
|
||||
"net>\n"
|
||||
"Language: pt_BR\n"
|
||||
|
|
@ -26,7 +27,7 @@ msgstr "Mudar título do Terminal"
|
|||
|
||||
#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:6
|
||||
msgid "Leave blank to restore the default title."
|
||||
msgstr "Deixe em branco para restaurar o título original."
|
||||
msgstr "Deixe em branco para restaurar o título padrão."
|
||||
|
||||
#: 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
|
||||
|
|
@ -315,8 +316,8 @@ msgstr "Configuração recarregada"
|
|||
|
||||
#: src/apprt/gtk/Window.zig:1019
|
||||
msgid "Ghostty Developers"
|
||||
msgstr "Desenvolvedores Ghostty"
|
||||
msgstr "Desenvolvedores do Ghostty"
|
||||
|
||||
#: src/apprt/gtk/inspector.zig:144
|
||||
msgid "Ghostty: Terminal Inspector"
|
||||
msgstr "Ghostty: Inspetor de terminal"
|
||||
msgstr "Ghostty: Inspetor do terminal"
|
||||
|
|
|
|||
|
|
@ -66,6 +66,12 @@ font_grid_key: font.SharedGridSet.Key,
|
|||
font_size: font.face.DesiredSize,
|
||||
font_metrics: font.Metrics,
|
||||
|
||||
/// This keeps track of if the font size was ever modified. If it wasn't,
|
||||
/// then config reloading will change the font. If it was manually adjusted,
|
||||
/// we don't change it on config reload since we assume the user wants
|
||||
/// a specific size.
|
||||
font_size_adjusted: bool,
|
||||
|
||||
/// The renderer for this surface.
|
||||
renderer: Renderer,
|
||||
|
||||
|
|
@ -514,6 +520,7 @@ pub fn init(
|
|||
.rt_surface = rt_surface,
|
||||
.font_grid_key = font_grid_key,
|
||||
.font_size = font_size,
|
||||
.font_size_adjusted = false,
|
||||
.font_metrics = font_grid.metrics,
|
||||
.renderer = renderer_impl,
|
||||
.renderer_thread = render_thread,
|
||||
|
|
@ -863,18 +870,24 @@ pub fn handleMessage(self: *Surface, msg: Message) !void {
|
|||
}, .unlocked);
|
||||
},
|
||||
|
||||
.color_change => |change| {
|
||||
.color_change => |change| color_change: {
|
||||
// Notify our apprt, but don't send a mode 2031 DSR report
|
||||
// because VT sequences were used to change the color.
|
||||
_ = try self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.color_change,
|
||||
.{
|
||||
.kind = switch (change.kind) {
|
||||
.background => .background,
|
||||
.foreground => .foreground,
|
||||
.cursor => .cursor,
|
||||
.kind = switch (change.target) {
|
||||
.palette => |v| @enumFromInt(v),
|
||||
.dynamic => |dyn| switch (dyn) {
|
||||
.foreground => .foreground,
|
||||
.background => .background,
|
||||
.cursor => .cursor,
|
||||
// Unsupported dynamic color change notification type
|
||||
else => break :color_change,
|
||||
},
|
||||
// Special colors aren't supported for change notification
|
||||
.special => break :color_change,
|
||||
},
|
||||
.r = change.color.r,
|
||||
.g = change.color.g,
|
||||
|
|
@ -1440,7 +1453,21 @@ pub fn updateConfig(
|
|||
// but this is easier and pretty rare so it's not a performance concern.
|
||||
//
|
||||
// (Calling setFontSize builds and sends a new font grid to the renderer.)
|
||||
try self.setFontSize(self.font_size);
|
||||
try self.setFontSize(font_size: {
|
||||
// If we have manually adjusted the font size, keep it that way.
|
||||
if (self.font_size_adjusted) {
|
||||
log.info("font size manually adjusted, preserving previous size on config reload", .{});
|
||||
break :font_size self.font_size;
|
||||
}
|
||||
|
||||
// If we haven't, then we update to the configured font size.
|
||||
// This allows config changes to update the font size. We used to
|
||||
// never do this but it was a common source of confusion and people
|
||||
// assumed that Ghostty was broken! This logic makes more sense.
|
||||
var size = self.font_size;
|
||||
size.points = std.math.clamp(config.@"font-size", 1.0, 255.0);
|
||||
break :font_size size;
|
||||
});
|
||||
|
||||
// We need to store our configs in a heap-allocated pointer so that
|
||||
// our messages aren't huge.
|
||||
|
|
@ -3985,7 +4012,7 @@ pub fn cursorPosCallback(
|
|||
|
||||
// Stop selection scrolling when inside the viewport within a 1px buffer
|
||||
// for fullscreen windows, but only when selection scrolling is active.
|
||||
if (pos.x >= 1 and pos.y >= 1 and self.selection_scroll_active) {
|
||||
if (pos.y >= 1 and self.selection_scroll_active) {
|
||||
self.io.queueMessage(
|
||||
.{ .selection_scroll = false },
|
||||
.locked,
|
||||
|
|
@ -4631,10 +4658,13 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
|
|||
|
||||
log.debug("increase font size={}", .{clamped_delta});
|
||||
|
||||
var size = self.font_size;
|
||||
// Max point size is somewhat arbitrary.
|
||||
var size = self.font_size;
|
||||
size.points = @min(size.points + clamped_delta, 255);
|
||||
try self.setFontSize(size);
|
||||
|
||||
// Mark that we manually adjusted the font size
|
||||
self.font_size_adjusted = true;
|
||||
},
|
||||
|
||||
.decrease_font_size => |delta| {
|
||||
|
|
@ -4646,6 +4676,9 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
|
|||
var size = self.font_size;
|
||||
size.points = @max(1, size.points - clamped_delta);
|
||||
try self.setFontSize(size);
|
||||
|
||||
// Mark that we manually adjusted the font size
|
||||
self.font_size_adjusted = true;
|
||||
},
|
||||
|
||||
.reset_font_size => {
|
||||
|
|
@ -4654,6 +4687,9 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
|
|||
var size = self.font_size;
|
||||
size.points = self.config.original_font_size;
|
||||
try self.setFontSize(size);
|
||||
|
||||
// Reset font size also resets the manual adjustment state
|
||||
self.font_size_adjusted = false;
|
||||
},
|
||||
|
||||
.set_font_size => |points| {
|
||||
|
|
@ -4662,6 +4698,9 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
|
|||
var size = self.font_size;
|
||||
size.points = std.math.clamp(points, 1.0, 255.0);
|
||||
try self.setFontSize(size);
|
||||
|
||||
// Mark that we manually adjusted the font size
|
||||
self.font_size_adjusted = true;
|
||||
},
|
||||
|
||||
.prompt_surface_title => return try self.rt_app.performAction(
|
||||
|
|
|
|||
|
|
@ -78,10 +78,7 @@ pub const Message = union(enum) {
|
|||
password_input: bool,
|
||||
|
||||
/// A terminal color was changed using OSC sequences.
|
||||
color_change: struct {
|
||||
kind: terminal.osc.Command.ColorOperation.Kind,
|
||||
color: terminal.color.RGB,
|
||||
},
|
||||
color_change: terminal.osc.color.ColoredTarget,
|
||||
|
||||
/// Notifies the surface that a tick of the timer that is timing
|
||||
/// out selection scrolling has occurred. "selection scrolling"
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ const GitVersion = @import("GitVersion.zig");
|
|||
/// TODO: When Zig 0.14 is released, derive this from build.zig.zon directly.
|
||||
/// Until then this MUST match build.zig.zon and should always be the
|
||||
/// _next_ version to release.
|
||||
const app_version: std.SemanticVersion = .{ .major = 1, .minor = 1, .patch = 4 };
|
||||
const app_version: std.SemanticVersion = .{ .major = 1, .minor = 2, .patch = 1 };
|
||||
|
||||
/// Standard build configuration options.
|
||||
optimize: std.builtin.OptimizeMode,
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@ pub fn init(b: *std.Build, cfg: *const Config) !GhosttyResources {
|
|||
// Themes
|
||||
if (b.lazyDependency("iterm2_themes", .{})) |upstream| {
|
||||
const install_step = b.addInstallDirectory(.{
|
||||
.source_dir = upstream.path("ghostty"),
|
||||
.source_dir = upstream.path(""),
|
||||
.install_dir = .{ .custom = "share" },
|
||||
.install_subdir = b.pathJoin(&.{ "ghostty", "themes" }),
|
||||
.exclude_extensions = &.{".md"},
|
||||
|
|
|
|||
|
|
@ -127,9 +127,6 @@ pub const compatibility = std.StaticStringMap(
|
|||
/// this within config files if you want to clear previously set values in
|
||||
/// configuration files or on the CLI if you want to clear values set on the
|
||||
/// CLI.
|
||||
///
|
||||
/// Changing this configuration at runtime will only affect new terminals, i.e.
|
||||
/// new windows, tabs, etc.
|
||||
@"font-family": RepeatableString = .{},
|
||||
@"font-family-bold": RepeatableString = .{},
|
||||
@"font-family-italic": RepeatableString = .{},
|
||||
|
|
@ -214,11 +211,12 @@ pub const compatibility = std.StaticStringMap(
|
|||
///
|
||||
/// For example, 13.5pt @ 2px/pt = 27px
|
||||
///
|
||||
/// Changing this configuration at runtime will only affect new terminals,
|
||||
/// i.e. new windows, tabs, etc. Note that you may still not see the change
|
||||
/// depending on your `window-inherit-font-size` setting. If that setting is
|
||||
/// true, only the first window will be affected by this change since all
|
||||
/// subsequent windows will inherit the font size of the previous window.
|
||||
/// Changing this configuration at runtime will only affect existing
|
||||
/// terminals that have NOT manually adjusted their font size in some way
|
||||
/// (e.g. increased or decreased the font size). Terminals that have manually
|
||||
/// adjusted their font size will retain their manually adjusted size.
|
||||
/// Otherwise, the font size of existing terminals will be updated on
|
||||
/// reload.
|
||||
///
|
||||
/// On Linux with GTK, font size is scaled according to both display-wide and
|
||||
/// text-specific scaling factors, which are often managed by your desktop
|
||||
|
|
@ -515,7 +513,7 @@ pub const compatibility = std.StaticStringMap(
|
|||
///
|
||||
/// To specify a different theme for light and dark mode, use the following
|
||||
/// syntax: `light:theme-name,dark:theme-name`. For example:
|
||||
/// `light:rose-pine-dawn,dark:rose-pine`. Whitespace around all values are
|
||||
/// `light:Rose Pine Dawn,dark:Rose Pine`. Whitespace around all values are
|
||||
/// trimmed and order of light and dark does not matter. Both light and dark
|
||||
/// must be specified in this form. In this form, the theme used will be
|
||||
/// based on the current desktop environment theme.
|
||||
|
|
@ -2146,6 +2144,8 @@ keybind: Keybinds = .{},
|
|||
/// from the first by a comma (`,`). Percentage and pixel sizes can be mixed
|
||||
/// together: for instance, a size of `50%,500px` for a top-positioned quick
|
||||
/// terminal would be half a screen tall, and 500 pixels wide.
|
||||
///
|
||||
/// Available since: 1.2.0
|
||||
@"quick-terminal-size": QuickTerminalSize = .{},
|
||||
|
||||
/// The layer of the quick terminal window. The higher the layer,
|
||||
|
|
@ -5564,6 +5564,17 @@ pub const Keybinds = struct {
|
|||
);
|
||||
|
||||
{
|
||||
try self.set.put(
|
||||
alloc,
|
||||
.{ .key = .{ .physical = .copy } },
|
||||
.{ .copy_to_clipboard = {} },
|
||||
);
|
||||
try self.set.put(
|
||||
alloc,
|
||||
.{ .key = .{ .physical = .paste } },
|
||||
.{ .paste_from_clipboard = {} },
|
||||
);
|
||||
|
||||
// On non-MacOS desktop envs (Windows, KDE, Gnome, Xfce), ctrl+insert is an
|
||||
// alt keybinding for Copy and shift+ins is an alt keybinding for Paste
|
||||
//
|
||||
|
|
|
|||
|
|
@ -38,9 +38,11 @@ pub const Shaper = switch (options.backend) {
|
|||
/// for a shaping call. Note all terminal cells may be present; only
|
||||
/// cells that have a glyph that needs to be rendered.
|
||||
pub const Cell = struct {
|
||||
/// The column that this cell occupies. Since a set of shaper cells is
|
||||
/// always on the same line, only the X is stored. It is expected the
|
||||
/// caller has access to the original screen cell.
|
||||
/// The X position of this shaper cell relative to the offset of the
|
||||
/// run. Because runs are always within a single row, it is expected
|
||||
/// that the caller can reconstruct the full position of the cell by
|
||||
/// using the known Y position of the cell and adding the X position
|
||||
/// to the run offset.
|
||||
x: u16,
|
||||
|
||||
/// An additional offset to apply to the rendering.
|
||||
|
|
|
|||
|
|
@ -17,9 +17,15 @@ pub const TextRun = struct {
|
|||
/// lower the chance of hash collisions if they become a problem. If
|
||||
/// there are hash collisions, it would result in rendering issues but
|
||||
/// the core data would be correct.
|
||||
///
|
||||
/// The hash is position-independent within the row by using relative
|
||||
/// cluster positions. This allows identical runs in different positions
|
||||
/// to share the same cache entry, improving cache efficiency.
|
||||
hash: u64,
|
||||
|
||||
/// The offset in the row where this run started
|
||||
/// The offset in the row where this run started. This is added to the
|
||||
/// X position of the final shaped cells to get the absolute position
|
||||
/// in the row where they belong.
|
||||
offset: u16,
|
||||
|
||||
/// The total number of cells produced by this run.
|
||||
|
|
@ -77,7 +83,11 @@ pub const RunIterator = struct {
|
|||
// Go through cell by cell and accumulate while we build our run.
|
||||
var j: usize = self.i;
|
||||
while (j < max) : (j += 1) {
|
||||
const cluster = j;
|
||||
// Use relative cluster positions (offset from run start) to make
|
||||
// the shaping cache position-independent. This ensures that runs
|
||||
// with identical content but different starting positions in the
|
||||
// row produce the same hash, enabling cache reuse.
|
||||
const cluster = j - self.i;
|
||||
const cell = &cells[j];
|
||||
|
||||
// If we have a selection and we're at a boundary point, then
|
||||
|
|
|
|||
|
|
@ -64,11 +64,35 @@ pub const Parser = struct {
|
|||
const flags, const start_idx = try parseFlags(raw_input);
|
||||
const input = raw_input[start_idx..];
|
||||
|
||||
// Find the last = which splits are mapping into the trigger
|
||||
// and action, respectively.
|
||||
// We use the last = because the keybind itself could contain
|
||||
// raw equal signs (for the = codepoint)
|
||||
const eql_idx = std.mem.lastIndexOf(u8, input, "=") orelse return Error.InvalidFormat;
|
||||
// Find the equal sign. This is more complicated than it seems on
|
||||
// the surface because we need to ignore equal signs that are
|
||||
// part of the trigger.
|
||||
const eql_idx: usize = eql: {
|
||||
// TODO: We should change this parser into a real state machine
|
||||
// based parser that parses the trigger fully, then yields the
|
||||
// action after. The loop below is a total mess.
|
||||
var offset: usize = 0;
|
||||
while (std.mem.indexOfScalar(
|
||||
u8,
|
||||
input[offset..],
|
||||
'=',
|
||||
)) |offset_idx| {
|
||||
// Find: '=+ctrl' or '==action'
|
||||
const idx = offset + offset_idx;
|
||||
if (idx < input.len - 1 and
|
||||
(input[idx + 1] == '+' or
|
||||
input[idx + 1] == '='))
|
||||
{
|
||||
offset += offset_idx + 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Looks like the real equal sign.
|
||||
break :eql idx;
|
||||
}
|
||||
|
||||
return Error.InvalidFormat;
|
||||
};
|
||||
|
||||
// Sequence iterator goes up to the equal, action is after. We can
|
||||
// parse the action now.
|
||||
|
|
@ -698,7 +722,7 @@ pub const Action = union(enum) {
|
|||
/// All actions are only undoable/redoable for a limited time.
|
||||
/// For example, restoring a closed split can only be done for
|
||||
/// some number of seconds since the split was closed. The exact
|
||||
/// amount is configured with `TODO`.
|
||||
/// amount is configured with the `undo-timeout` configuration settings.
|
||||
///
|
||||
/// The undo/redo actions being limited ensures that there is
|
||||
/// bounded memory usage over time, closed surfaces don't continue running
|
||||
|
|
@ -2301,6 +2325,39 @@ test "parse: equals sign" {
|
|||
try testing.expectError(Error.InvalidFormat, parseSingle("=ignore"));
|
||||
}
|
||||
|
||||
test "parse: text action equals sign" {
|
||||
const testing = std.testing;
|
||||
{
|
||||
const binding = try parseSingle("==text:=");
|
||||
try testing.expectEqual(Trigger{ .key = .{ .unicode = '=' } }, binding.trigger);
|
||||
try testing.expectEqualStrings("=", binding.action.text);
|
||||
}
|
||||
|
||||
{
|
||||
const binding = try parseSingle("==text:=hello");
|
||||
try testing.expectEqual(Trigger{ .key = .{ .unicode = '=' } }, binding.trigger);
|
||||
try testing.expectEqualStrings("=hello", binding.action.text);
|
||||
}
|
||||
|
||||
{
|
||||
const binding = try parseSingle("ctrl+==text:=hello");
|
||||
try testing.expectEqual(Trigger{
|
||||
.key = .{ .unicode = '=' },
|
||||
.mods = .{ .ctrl = true },
|
||||
}, binding.trigger);
|
||||
try testing.expectEqualStrings("=hello", binding.action.text);
|
||||
}
|
||||
|
||||
{
|
||||
const binding = try parseSingle("=+ctrl=text:=hello");
|
||||
try testing.expectEqual(Trigger{
|
||||
.key = .{ .unicode = '=' },
|
||||
.mods = .{ .ctrl = true },
|
||||
}, binding.trigger);
|
||||
try testing.expectEqualStrings("=hello", binding.action.text);
|
||||
}
|
||||
}
|
||||
|
||||
// For Ghostty 1.2+ we changed our key names to match the W3C and removed
|
||||
// `physical:`. This tests the backwards compatibility with the old format.
|
||||
// Note that our backwards compatibility isn't 100% perfect since triggers
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ pub const locales = [_][:0]const u8{
|
|||
"es_AR.UTF-8",
|
||||
"pt_BR.UTF-8",
|
||||
"ca_ES.UTF-8",
|
||||
"it_IT.UTF-8",
|
||||
"bg_BG.UTF-8",
|
||||
"ga_IE.UTF-8",
|
||||
"hu_HU.UTF-8",
|
||||
|
|
|
|||
|
|
@ -2528,9 +2528,11 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
|||
break :cache cells;
|
||||
};
|
||||
|
||||
const cells = shaper_cells.?;
|
||||
|
||||
// Advance our index until we reach or pass
|
||||
// our current x position in the shaper cells.
|
||||
while (shaper_cells.?[shaper_cells_i].x < x) {
|
||||
while (run.offset + cells[shaper_cells_i].x < x) {
|
||||
shaper_cells_i += 1;
|
||||
}
|
||||
}
|
||||
|
|
@ -2769,13 +2771,13 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
|||
// 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(cells[shaper_cells_i].x >= x);
|
||||
assert(run.offset + 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 < cells.len and cells[shaper_cells_i].x == x) : ({
|
||||
while (shaper_cells_i < cells.len and run.offset + cells[shaper_cells_i].x == x) : ({
|
||||
shaper_cells_i += 1;
|
||||
}) {
|
||||
self.addGlyph(
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ fi
|
|||
|
||||
# SSH Integration
|
||||
if [[ "$GHOSTTY_SHELL_FEATURES" == *ssh-* ]]; then
|
||||
ssh() {
|
||||
function ssh() {
|
||||
builtin local ssh_term ssh_opts
|
||||
ssh_term="xterm-256color"
|
||||
ssh_opts=()
|
||||
|
|
|
|||
|
|
@ -915,21 +915,53 @@ test "osc: 112 incomplete sequence" {
|
|||
const cmd = a[0].?.osc_dispatch;
|
||||
try testing.expect(cmd == .color_operation);
|
||||
try testing.expectEqual(cmd.color_operation.terminator, .bel);
|
||||
try testing.expect(cmd.color_operation.source == .reset_cursor);
|
||||
try testing.expect(cmd.color_operation.operations.count() == 1);
|
||||
var it = cmd.color_operation.operations.constIterator(0);
|
||||
try testing.expect(cmd.color_operation.op == .osc_112);
|
||||
try testing.expect(cmd.color_operation.requests.count() == 1);
|
||||
var it = cmd.color_operation.requests.constIterator(0);
|
||||
{
|
||||
const op = it.next().?;
|
||||
try testing.expect(op.* == .reset);
|
||||
try testing.expectEqual(
|
||||
osc.Command.ColorOperation.Kind.cursor,
|
||||
op.reset,
|
||||
osc.color.Request{ .reset = .{ .dynamic = .cursor } },
|
||||
op.*,
|
||||
);
|
||||
}
|
||||
try std.testing.expect(it.next() == null);
|
||||
}
|
||||
}
|
||||
|
||||
test "osc: 104 empty" {
|
||||
var p: Parser = init();
|
||||
defer p.deinit();
|
||||
p.osc_parser.alloc = std.testing.allocator;
|
||||
|
||||
_ = p.next(0x1B);
|
||||
_ = p.next(']');
|
||||
_ = p.next('1');
|
||||
_ = p.next('0');
|
||||
_ = p.next('4');
|
||||
|
||||
{
|
||||
const a = p.next(0x07);
|
||||
try testing.expect(p.state == .ground);
|
||||
try testing.expect(a[0].? == .osc_dispatch);
|
||||
try testing.expect(a[1] == null);
|
||||
try testing.expect(a[2] == null);
|
||||
|
||||
const cmd = a[0].?.osc_dispatch;
|
||||
try testing.expect(cmd == .color_operation);
|
||||
try testing.expectEqual(cmd.color_operation.terminator, .bel);
|
||||
try testing.expect(cmd.color_operation.op == .osc_104);
|
||||
try testing.expect(cmd.color_operation.requests.count() == 1);
|
||||
var it = cmd.color_operation.requests.constIterator(0);
|
||||
{
|
||||
const op = it.next().?;
|
||||
try testing.expect(op.* == .reset_palette);
|
||||
}
|
||||
try std.testing.expect(it.next() == null);
|
||||
}
|
||||
}
|
||||
|
||||
test "csi: too many params" {
|
||||
var p = init();
|
||||
_ = p.next(0x1B);
|
||||
|
|
|
|||
|
|
@ -94,6 +94,85 @@ pub const Name = enum(u8) {
|
|||
}
|
||||
};
|
||||
|
||||
/// The "special colors" as denoted by xterm. These can be set via
|
||||
/// OSC 5 or via OSC 4 by adding the palette length to it.
|
||||
///
|
||||
/// https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
|
||||
pub const Special = enum(u3) {
|
||||
bold = 0,
|
||||
underline = 1,
|
||||
blink = 2,
|
||||
reverse = 3,
|
||||
italic = 4,
|
||||
|
||||
pub fn osc4(self: Special) u16 {
|
||||
// "The special colors can also be set by adding the maximum
|
||||
// number of colors (e.g., 88 or 256) to these codes in an
|
||||
// OSC 4 control" - xterm ctlseqs
|
||||
const max = @typeInfo(Palette).array.len;
|
||||
return @as(u16, @intCast(@intFromEnum(self))) + max;
|
||||
}
|
||||
|
||||
test "osc4" {
|
||||
const testing = std.testing;
|
||||
try testing.expectEqual(256, Special.bold.osc4());
|
||||
try testing.expectEqual(257, Special.underline.osc4());
|
||||
try testing.expectEqual(258, Special.blink.osc4());
|
||||
try testing.expectEqual(259, Special.reverse.osc4());
|
||||
try testing.expectEqual(260, Special.italic.osc4());
|
||||
}
|
||||
};
|
||||
|
||||
test Special {
|
||||
_ = Special;
|
||||
}
|
||||
|
||||
/// The "dynamic colors" as denoted by xterm. These can be set via
|
||||
/// OSC 10 through 19.
|
||||
pub const Dynamic = enum(u5) {
|
||||
foreground = 10,
|
||||
background = 11,
|
||||
cursor = 12,
|
||||
pointer_foreground = 13,
|
||||
pointer_background = 14,
|
||||
tektronix_foreground = 15,
|
||||
tektronix_background = 16,
|
||||
highlight_background = 17,
|
||||
tektronix_cursor = 18,
|
||||
highlight_foreground = 19,
|
||||
|
||||
/// The next dynamic color sequentially. This is required because
|
||||
/// specifying colors sequentially without their index will automatically
|
||||
/// use the next dynamic color.
|
||||
///
|
||||
/// "Each successive parameter changes the next color in the list. The
|
||||
/// value of Ps tells the starting point in the list."
|
||||
pub fn next(self: Dynamic) ?Dynamic {
|
||||
return std.meta.intToEnum(
|
||||
Dynamic,
|
||||
@intFromEnum(self) + 1,
|
||||
) catch null;
|
||||
}
|
||||
|
||||
test "next" {
|
||||
const testing = std.testing;
|
||||
try testing.expectEqual(.background, Dynamic.foreground.next());
|
||||
try testing.expectEqual(.cursor, Dynamic.background.next());
|
||||
try testing.expectEqual(.pointer_foreground, Dynamic.cursor.next());
|
||||
try testing.expectEqual(.pointer_background, Dynamic.pointer_foreground.next());
|
||||
try testing.expectEqual(.tektronix_foreground, Dynamic.pointer_background.next());
|
||||
try testing.expectEqual(.tektronix_background, Dynamic.tektronix_foreground.next());
|
||||
try testing.expectEqual(.highlight_background, Dynamic.tektronix_background.next());
|
||||
try testing.expectEqual(.tektronix_cursor, Dynamic.highlight_background.next());
|
||||
try testing.expectEqual(.highlight_foreground, Dynamic.tektronix_cursor.next());
|
||||
try testing.expectEqual(null, Dynamic.highlight_foreground.next());
|
||||
}
|
||||
};
|
||||
|
||||
test Dynamic {
|
||||
_ = Dynamic;
|
||||
}
|
||||
|
||||
/// RGB
|
||||
pub const RGB = packed struct(u24) {
|
||||
r: u8 = 0,
|
||||
|
|
|
|||
1367
src/terminal/osc.zig
1367
src/terminal/osc.zig
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,705 @@
|
|||
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{
|
||||
MissingOperation,
|
||||
};
|
||||
|
||||
/// The possible operations we support for colors.
|
||||
pub const Operation = enum {
|
||||
osc_4,
|
||||
osc_5,
|
||||
osc_10,
|
||||
osc_11,
|
||||
osc_12,
|
||||
osc_13,
|
||||
osc_14,
|
||||
osc_15,
|
||||
osc_16,
|
||||
osc_17,
|
||||
osc_18,
|
||||
osc_19,
|
||||
osc_104,
|
||||
osc_105,
|
||||
osc_110,
|
||||
osc_111,
|
||||
osc_112,
|
||||
osc_113,
|
||||
osc_114,
|
||||
osc_115,
|
||||
osc_116,
|
||||
osc_117,
|
||||
osc_118,
|
||||
osc_119,
|
||||
};
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// Color parsing is fairly complicated so we pull this out to a specialized
|
||||
/// function rather than go through our OSC parsing state machine. This is
|
||||
/// much slower and requires more memory (since we need to buffer the full
|
||||
/// 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(
|
||||
alloc: Allocator,
|
||||
op: Operation,
|
||||
buf: []const u8,
|
||||
) ParseError!List {
|
||||
var it = std.mem.tokenizeScalar(u8, buf, ';');
|
||||
return switch (op) {
|
||||
.osc_4 => try parseGetSetAnsiColor(alloc, .osc_4, &it),
|
||||
.osc_5 => try parseGetSetAnsiColor(alloc, .osc_5, &it),
|
||||
.osc_104 => try parseResetAnsiColor(alloc, .osc_104, &it),
|
||||
.osc_105 => try parseResetAnsiColor(alloc, .osc_105, &it),
|
||||
.osc_10 => try parseGetSetDynamicColor(alloc, .foreground, &it),
|
||||
.osc_11 => try parseGetSetDynamicColor(alloc, .background, &it),
|
||||
.osc_12 => try parseGetSetDynamicColor(alloc, .cursor, &it),
|
||||
.osc_13 => try parseGetSetDynamicColor(alloc, .pointer_foreground, &it),
|
||||
.osc_14 => try parseGetSetDynamicColor(alloc, .pointer_background, &it),
|
||||
.osc_15 => try parseGetSetDynamicColor(alloc, .tektronix_foreground, &it),
|
||||
.osc_16 => try parseGetSetDynamicColor(alloc, .tektronix_background, &it),
|
||||
.osc_17 => try parseGetSetDynamicColor(alloc, .highlight_background, &it),
|
||||
.osc_18 => try parseGetSetDynamicColor(alloc, .tektronix_cursor, &it),
|
||||
.osc_19 => try parseGetSetDynamicColor(alloc, .highlight_foreground, &it),
|
||||
.osc_110 => try parseResetDynamicColor(alloc, .foreground, &it),
|
||||
.osc_111 => try parseResetDynamicColor(alloc, .background, &it),
|
||||
.osc_112 => try parseResetDynamicColor(alloc, .cursor, &it),
|
||||
.osc_113 => try parseResetDynamicColor(alloc, .pointer_foreground, &it),
|
||||
.osc_114 => try parseResetDynamicColor(alloc, .pointer_background, &it),
|
||||
.osc_115 => try parseResetDynamicColor(alloc, .tektronix_foreground, &it),
|
||||
.osc_116 => try parseResetDynamicColor(alloc, .tektronix_background, &it),
|
||||
.osc_117 => try parseResetDynamicColor(alloc, .highlight_background, &it),
|
||||
.osc_118 => try parseResetDynamicColor(alloc, .tektronix_cursor, &it),
|
||||
.osc_119 => try parseResetDynamicColor(alloc, .highlight_foreground, &it),
|
||||
};
|
||||
}
|
||||
|
||||
/// OSC 4/5
|
||||
fn parseGetSetAnsiColor(
|
||||
alloc: Allocator,
|
||||
comptime op: Operation,
|
||||
it: *std.mem.TokenIterator(u8, .scalar),
|
||||
) Allocator.Error!List {
|
||||
// Note: in ANY error scenario below we return the accumulated results.
|
||||
// This matches the xterm behavior (see misc.c ChangeAnsiColorRequest)
|
||||
|
||||
var result: List = .{};
|
||||
errdefer result.deinit(alloc);
|
||||
while (true) {
|
||||
// We expect a `c; spec` pair. If either doesn't exist then
|
||||
// we return the results up to this point.
|
||||
const color_str = it.next() orelse return result;
|
||||
const spec_str = it.next() orelse return result;
|
||||
|
||||
// Color must be numeric. u9 because that'll fit our palette + special
|
||||
const color: u9 = std.fmt.parseInt(
|
||||
u9,
|
||||
color_str,
|
||||
10,
|
||||
) catch return result;
|
||||
|
||||
// Parse the color.
|
||||
const target: Target = switch (op) {
|
||||
// OSC5 maps directly to the Special enum.
|
||||
.osc_5 => .{ .special = std.meta.intToEnum(
|
||||
SpecialColor,
|
||||
std.math.cast(u3, color) orelse return result,
|
||||
) catch return result },
|
||||
|
||||
// OSC4 maps 0-255 to palette, 256-259 to special offset
|
||||
// by the palette count.
|
||||
.osc_4 => if (std.math.cast(u8, color)) |idx| .{
|
||||
.palette = idx,
|
||||
} else .{ .special = std.meta.intToEnum(
|
||||
SpecialColor,
|
||||
std.math.cast(u3, color - 256) orelse return result,
|
||||
) catch return result },
|
||||
|
||||
else => comptime unreachable,
|
||||
};
|
||||
|
||||
// "?" always results in a query.
|
||||
if (std.mem.eql(u8, spec_str, "?")) {
|
||||
const req = try result.addOne(alloc);
|
||||
req.* = .{ .query = target };
|
||||
continue;
|
||||
}
|
||||
|
||||
const rgb = RGB.parse(spec_str) catch return result;
|
||||
const req = try result.addOne(alloc);
|
||||
req.* = .{ .set = .{
|
||||
.target = target,
|
||||
.color = rgb,
|
||||
} };
|
||||
}
|
||||
}
|
||||
|
||||
/// OSC 104/105: Reset ANSI Colors
|
||||
fn parseResetAnsiColor(
|
||||
alloc: Allocator,
|
||||
comptime op: Operation,
|
||||
it: *std.mem.TokenIterator(u8, .scalar),
|
||||
) Allocator.Error!List {
|
||||
// Note: xterm stops parsing the reset list on any error, but we're
|
||||
// more flexible and try the next value. This matches the behavior of
|
||||
// Kitty and I don't see a downside to being more flexible here. Hopefully
|
||||
// no one depends on the exact behavior of xterm.
|
||||
|
||||
var result: List = .{};
|
||||
errdefer result.deinit(alloc);
|
||||
while (true) {
|
||||
const color_str = it.next() orelse {
|
||||
// If no parameters are given, we reset the full table.
|
||||
if (result.count() == 0) {
|
||||
const req = try result.addOne(alloc);
|
||||
req.* = switch (op) {
|
||||
.osc_104 => .reset_palette,
|
||||
.osc_105 => .reset_special,
|
||||
else => comptime unreachable,
|
||||
};
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
// Empty color strings are ignored, not treated as an error.
|
||||
if (color_str.len == 0) continue;
|
||||
|
||||
// Color must be numeric. u9 because that'll fit our palette + special
|
||||
const color: u9 = std.fmt.parseInt(
|
||||
u9,
|
||||
color_str,
|
||||
10,
|
||||
) catch continue;
|
||||
|
||||
// Parse the color.
|
||||
const target: Target = switch (op) {
|
||||
// OSC105 maps directly to the Special enum.
|
||||
.osc_105 => .{ .special = std.meta.intToEnum(
|
||||
SpecialColor,
|
||||
std.math.cast(u3, color) orelse continue,
|
||||
) catch continue },
|
||||
|
||||
// OSC104 maps 0-255 to palette, 256-259 to special offset
|
||||
// by the palette count.
|
||||
.osc_104 => if (std.math.cast(u8, color)) |idx| .{
|
||||
.palette = idx,
|
||||
} else .{ .special = std.meta.intToEnum(
|
||||
SpecialColor,
|
||||
std.math.cast(u3, color - 256) orelse continue,
|
||||
) catch continue },
|
||||
|
||||
else => comptime unreachable,
|
||||
};
|
||||
|
||||
const req = try result.addOne(alloc);
|
||||
req.* = .{ .reset = target };
|
||||
}
|
||||
}
|
||||
|
||||
/// OSC 10-19: Get/Set Dynamic Colors
|
||||
fn parseGetSetDynamicColor(
|
||||
alloc: Allocator,
|
||||
start: DynamicColor,
|
||||
it: *std.mem.TokenIterator(u8, .scalar),
|
||||
) Allocator.Error!List {
|
||||
// Note: in ANY error scenario below we return the accumulated results.
|
||||
// This matches the xterm behavior (see misc.c ChangeColorsRequest)
|
||||
|
||||
var result: List = .{};
|
||||
var color: DynamicColor = start;
|
||||
while (true) {
|
||||
const spec_str = it.next() orelse return result;
|
||||
|
||||
if (std.mem.eql(u8, spec_str, "?")) {
|
||||
const req = try result.addOne(alloc);
|
||||
req.* = .{ .query = .{ .dynamic = color } };
|
||||
} else {
|
||||
const rgb = RGB.parse(spec_str) catch return result;
|
||||
const req = try result.addOne(alloc);
|
||||
req.* = .{ .set = .{
|
||||
.target = .{ .dynamic = color },
|
||||
.color = rgb,
|
||||
} };
|
||||
}
|
||||
|
||||
// Each successive value uses the next color so long as it exists.
|
||||
color = color.next() orelse return result;
|
||||
}
|
||||
}
|
||||
|
||||
/// OSC 110-119: Reset Dynamic Colors
|
||||
fn parseResetDynamicColor(
|
||||
alloc: Allocator,
|
||||
color: DynamicColor,
|
||||
it: *std.mem.TokenIterator(u8, .scalar),
|
||||
) Allocator.Error!List {
|
||||
var result: List = .{};
|
||||
errdefer result.deinit(alloc);
|
||||
if (it.next() != null) return result;
|
||||
const req = try result.addOne(alloc);
|
||||
req.* = .{ .reset = .{ .dynamic = color } };
|
||||
return result;
|
||||
}
|
||||
|
||||
/// A segmented list is used to avoid copying when many operations
|
||||
/// are given in a single OSC. In most cases, OSC 4/104/etc. send
|
||||
/// very few so the prealloc is optimized for that.
|
||||
///
|
||||
/// The exact prealloc value is chosen arbitrarily assuming most
|
||||
/// color ops have very few. If we can get empirical data on more
|
||||
/// typical values we can switch to that.
|
||||
pub const List = std.SegmentedList(
|
||||
Request,
|
||||
2,
|
||||
);
|
||||
|
||||
/// A single operation related to the terminal color palette.
|
||||
pub const Request = union(enum) {
|
||||
set: ColoredTarget,
|
||||
query: Target,
|
||||
reset: Target,
|
||||
reset_palette,
|
||||
reset_special,
|
||||
};
|
||||
|
||||
pub const Target = union(enum) {
|
||||
palette: u8,
|
||||
special: SpecialColor,
|
||||
dynamic: DynamicColor,
|
||||
};
|
||||
|
||||
pub const ColoredTarget = struct {
|
||||
target: Target,
|
||||
color: RGB,
|
||||
};
|
||||
|
||||
test "osc4" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
||||
// Test every palette index
|
||||
for (0..std.math.maxInt(u8)) |idx| {
|
||||
// Simple color set
|
||||
// printf '\e]4;0;red\\'
|
||||
{
|
||||
const body = try std.fmt.allocPrint(
|
||||
alloc,
|
||||
"{d};red",
|
||||
.{idx},
|
||||
);
|
||||
defer alloc.free(body);
|
||||
|
||||
var list = try parse(alloc, .osc_4, body);
|
||||
defer list.deinit(alloc);
|
||||
try testing.expectEqual(1, list.count());
|
||||
try testing.expectEqual(
|
||||
Request{ .set = .{
|
||||
.target = .{ .palette = @intCast(idx) },
|
||||
.color = RGB{ .r = 255, .g = 0, .b = 0 },
|
||||
} },
|
||||
list.at(0).*,
|
||||
);
|
||||
}
|
||||
|
||||
// Simple color query
|
||||
// printf '\e]4;0;?\\'
|
||||
{
|
||||
const body = try std.fmt.allocPrint(
|
||||
alloc,
|
||||
"{d};?",
|
||||
.{idx},
|
||||
);
|
||||
defer alloc.free(body);
|
||||
|
||||
var list = try parse(alloc, .osc_4, body);
|
||||
defer list.deinit(alloc);
|
||||
try testing.expectEqual(1, list.count());
|
||||
try testing.expectEqual(
|
||||
Request{ .query = .{ .palette = @intCast(idx) } },
|
||||
list.at(0).*,
|
||||
);
|
||||
}
|
||||
|
||||
// Trailing invalid data produces results up to that point
|
||||
// printf '\e]4;0;red;\e\\'
|
||||
{
|
||||
const body = try std.fmt.allocPrint(
|
||||
alloc,
|
||||
"{d};red;",
|
||||
.{idx},
|
||||
);
|
||||
defer alloc.free(body);
|
||||
|
||||
var list = try parse(alloc, .osc_4, body);
|
||||
defer list.deinit(alloc);
|
||||
try testing.expectEqual(1, list.count());
|
||||
try testing.expectEqual(
|
||||
Request{ .set = .{
|
||||
.target = .{ .palette = @intCast(idx) },
|
||||
.color = RGB{ .r = 255, .g = 0, .b = 0 },
|
||||
} },
|
||||
list.at(0).*,
|
||||
);
|
||||
}
|
||||
|
||||
// Whitespace doesn't produce a working value in xterm but we
|
||||
// allow it because Kitty does and it seems harmless.
|
||||
//
|
||||
// printf '\e]4;0;red \e\\'
|
||||
{
|
||||
const body = try std.fmt.allocPrint(
|
||||
alloc,
|
||||
"{d};red ",
|
||||
.{idx},
|
||||
);
|
||||
defer alloc.free(body);
|
||||
|
||||
var list = try parse(alloc, .osc_4, body);
|
||||
defer list.deinit(alloc);
|
||||
try testing.expectEqual(1, list.count());
|
||||
try testing.expectEqual(
|
||||
Request{ .set = .{
|
||||
.target = .{ .palette = @intCast(idx) },
|
||||
.color = RGB{ .r = 255, .g = 0, .b = 0 },
|
||||
} },
|
||||
list.at(0).*,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Test every special color
|
||||
for (0..@typeInfo(SpecialColor).@"enum".fields.len) |i| {
|
||||
const special = try std.meta.intToEnum(SpecialColor, i);
|
||||
|
||||
// Simple color set
|
||||
// printf '\e]4;256;red\\'
|
||||
{
|
||||
const body = try std.fmt.allocPrint(
|
||||
alloc,
|
||||
"{d};red",
|
||||
.{256 + i},
|
||||
);
|
||||
defer alloc.free(body);
|
||||
|
||||
var list = try parse(alloc, .osc_4, body);
|
||||
defer list.deinit(alloc);
|
||||
try testing.expectEqual(1, list.count());
|
||||
try testing.expectEqual(
|
||||
Request{ .set = .{
|
||||
.target = .{ .special = special },
|
||||
.color = RGB{ .r = 255, .g = 0, .b = 0 },
|
||||
} },
|
||||
list.at(0).*,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test "osc5" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
||||
// Test every special color
|
||||
for (0..@typeInfo(SpecialColor).@"enum".fields.len) |i| {
|
||||
const special = try std.meta.intToEnum(SpecialColor, i);
|
||||
|
||||
// Simple color set
|
||||
// printf '\e]4;256;red\\'
|
||||
{
|
||||
const body = try std.fmt.allocPrint(
|
||||
alloc,
|
||||
"{d};red",
|
||||
.{i},
|
||||
);
|
||||
defer alloc.free(body);
|
||||
|
||||
var list = try parse(alloc, .osc_5, body);
|
||||
defer list.deinit(alloc);
|
||||
try testing.expectEqual(1, list.count());
|
||||
try testing.expectEqual(
|
||||
Request{ .set = .{
|
||||
.target = .{ .special = special },
|
||||
.color = RGB{ .r = 255, .g = 0, .b = 0 },
|
||||
} },
|
||||
list.at(0).*,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test "osc4: multiple requests" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
||||
// printf '\e]4;0;red;1;blue\e\\'
|
||||
{
|
||||
var list = try parse(
|
||||
alloc,
|
||||
.osc_4,
|
||||
"0;red;1;blue",
|
||||
);
|
||||
defer list.deinit(alloc);
|
||||
try testing.expectEqual(2, list.count());
|
||||
try testing.expectEqual(
|
||||
Request{ .set = .{
|
||||
.target = .{ .palette = 0 },
|
||||
.color = RGB{ .r = 255, .g = 0, .b = 0 },
|
||||
} },
|
||||
list.at(0).*,
|
||||
);
|
||||
try testing.expectEqual(
|
||||
Request{ .set = .{
|
||||
.target = .{ .palette = 1 },
|
||||
.color = RGB{ .r = 0, .g = 0, .b = 255 },
|
||||
} },
|
||||
list.at(1).*,
|
||||
);
|
||||
}
|
||||
|
||||
// Multiple requests with same index overwrite each other
|
||||
// printf '\e]4;0;red;0;blue\e\\'
|
||||
{
|
||||
var list = try parse(
|
||||
alloc,
|
||||
.osc_4,
|
||||
"0;red;0;blue",
|
||||
);
|
||||
defer list.deinit(alloc);
|
||||
try testing.expectEqual(2, list.count());
|
||||
try testing.expectEqual(
|
||||
Request{ .set = .{
|
||||
.target = .{ .palette = 0 },
|
||||
.color = RGB{ .r = 255, .g = 0, .b = 0 },
|
||||
} },
|
||||
list.at(0).*,
|
||||
);
|
||||
try testing.expectEqual(
|
||||
Request{ .set = .{
|
||||
.target = .{ .palette = 0 },
|
||||
.color = RGB{ .r = 0, .g = 0, .b = 255 },
|
||||
} },
|
||||
list.at(1).*,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
test "osc104" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
||||
// Test every palette index
|
||||
for (0..std.math.maxInt(u8)) |idx| {
|
||||
// Simple color set
|
||||
// printf '\e]104;0\\'
|
||||
{
|
||||
const body = try std.fmt.allocPrint(
|
||||
alloc,
|
||||
"{d}",
|
||||
.{idx},
|
||||
);
|
||||
defer alloc.free(body);
|
||||
|
||||
var list = try parse(alloc, .osc_104, body);
|
||||
defer list.deinit(alloc);
|
||||
try testing.expectEqual(1, list.count());
|
||||
try testing.expectEqual(
|
||||
Request{ .reset = .{ .palette = @intCast(idx) } },
|
||||
list.at(0).*,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Test every special color
|
||||
for (0..@typeInfo(SpecialColor).@"enum".fields.len) |i| {
|
||||
const special = try std.meta.intToEnum(SpecialColor, i);
|
||||
|
||||
// Simple color set
|
||||
// printf '\e]104;256\\'
|
||||
{
|
||||
const body = try std.fmt.allocPrint(
|
||||
alloc,
|
||||
"{d}",
|
||||
.{256 + i},
|
||||
);
|
||||
defer alloc.free(body);
|
||||
|
||||
var list = try parse(alloc, .osc_104, body);
|
||||
defer list.deinit(alloc);
|
||||
try testing.expectEqual(1, list.count());
|
||||
try testing.expectEqual(
|
||||
Request{ .reset = .{ .special = special } },
|
||||
list.at(0).*,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test "osc104 empty index" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
||||
var list = try parse(alloc, .osc_104, "0;;1");
|
||||
defer list.deinit(alloc);
|
||||
try testing.expectEqual(2, list.count());
|
||||
try testing.expectEqual(
|
||||
Request{ .reset = .{ .palette = 0 } },
|
||||
list.at(0).*,
|
||||
);
|
||||
try testing.expectEqual(
|
||||
Request{ .reset = .{ .palette = 1 } },
|
||||
list.at(1).*,
|
||||
);
|
||||
}
|
||||
|
||||
test "osc104 invalid index" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
||||
var list = try parse(alloc, .osc_104, "ffff;1");
|
||||
defer list.deinit(alloc);
|
||||
try testing.expectEqual(1, list.count());
|
||||
try testing.expectEqual(
|
||||
Request{ .reset = .{ .palette = 1 } },
|
||||
list.at(0).*,
|
||||
);
|
||||
}
|
||||
|
||||
test "osc104 reset all" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
||||
var list = try parse(alloc, .osc_104, "");
|
||||
defer list.deinit(alloc);
|
||||
try testing.expectEqual(1, list.count());
|
||||
try testing.expectEqual(
|
||||
Request{ .reset_palette = {} },
|
||||
list.at(0).*,
|
||||
);
|
||||
}
|
||||
|
||||
test "osc105 reset all" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
||||
var list = try parse(alloc, .osc_105, "");
|
||||
defer list.deinit(alloc);
|
||||
try testing.expectEqual(1, list.count());
|
||||
try testing.expectEqual(
|
||||
Request{ .reset_special = {} },
|
||||
list.at(0).*,
|
||||
);
|
||||
}
|
||||
|
||||
// OSC 10-19: Get/Set Dynamic Colors
|
||||
test "dynamic" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
||||
inline for (@typeInfo(DynamicColor).@"enum".fields) |field| {
|
||||
const color = @field(DynamicColor, field.name);
|
||||
const op = @field(Operation, std.fmt.comptimePrint(
|
||||
"osc_{d}",
|
||||
.{field.value},
|
||||
));
|
||||
|
||||
// Example script:
|
||||
// printf '\e]10;red\e\\'
|
||||
{
|
||||
var list = try parse(alloc, op, "red");
|
||||
defer list.deinit(alloc);
|
||||
try testing.expectEqual(1, list.count());
|
||||
try testing.expectEqual(
|
||||
Request{ .set = .{
|
||||
.target = .{ .dynamic = color },
|
||||
.color = RGB{ .r = 255, .g = 0, .b = 0 },
|
||||
} },
|
||||
list.at(0).*,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test "dynamic multiple" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
||||
// Example script:
|
||||
// printf '\e]11;red;blue\e\\'
|
||||
{
|
||||
var list = try parse(
|
||||
alloc,
|
||||
.osc_11,
|
||||
"red;blue",
|
||||
);
|
||||
defer list.deinit(alloc);
|
||||
try testing.expectEqual(2, list.count());
|
||||
try testing.expectEqual(
|
||||
Request{ .set = .{
|
||||
.target = .{ .dynamic = .background },
|
||||
.color = RGB{ .r = 255, .g = 0, .b = 0 },
|
||||
} },
|
||||
list.at(0).*,
|
||||
);
|
||||
try testing.expectEqual(
|
||||
Request{ .set = .{
|
||||
.target = .{ .dynamic = .cursor },
|
||||
.color = RGB{ .r = 0, .g = 0, .b = 255 },
|
||||
} },
|
||||
list.at(1).*,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// OSC 110-119: Reset Dynamic Colors
|
||||
test "reset dynamic" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
||||
inline for (@typeInfo(DynamicColor).@"enum".fields) |field| {
|
||||
const color = @field(DynamicColor, field.name);
|
||||
const op = @field(Operation, std.fmt.comptimePrint(
|
||||
"osc_1{d}",
|
||||
.{field.value},
|
||||
));
|
||||
|
||||
// Example script:
|
||||
// printf '\e]110\e\\'
|
||||
{
|
||||
var list = try parse(alloc, op, "");
|
||||
defer list.deinit(alloc);
|
||||
try testing.expectEqual(1, list.count());
|
||||
try testing.expectEqual(
|
||||
Request{ .reset = .{ .dynamic = color } },
|
||||
list.at(0).*,
|
||||
);
|
||||
}
|
||||
|
||||
// xterm allows a trailing semicolon. script to verify:
|
||||
//
|
||||
// printf '\e]110;\e\\'
|
||||
{
|
||||
var list = try parse(alloc, op, ";");
|
||||
defer list.deinit(alloc);
|
||||
try testing.expectEqual(1, list.count());
|
||||
try testing.expectEqual(
|
||||
Request{ .reset = .{ .dynamic = color } },
|
||||
list.at(0).*,
|
||||
);
|
||||
}
|
||||
|
||||
// xterm does NOT allow any whitespace
|
||||
//
|
||||
// printf '\e]110 \e\\'
|
||||
{
|
||||
var list = try parse(alloc, op, " ");
|
||||
defer list.deinit(alloc);
|
||||
try testing.expectEqual(0, list.count());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1565,7 +1565,11 @@ pub fn Stream(comptime Handler: type) type {
|
|||
|
||||
.color_operation => |v| {
|
||||
if (@hasDecl(T, "handleColorOperation")) {
|
||||
try self.handler.handleColorOperation(v.source, &v.operations, v.terminator);
|
||||
try self.handler.handleColorOperation(
|
||||
v.op,
|
||||
&v.requests,
|
||||
v.terminator,
|
||||
);
|
||||
return;
|
||||
} else log.warn("unimplemented OSC callback: {}", .{cmd});
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1187,12 +1187,15 @@ pub const StreamHandler = struct {
|
|||
|
||||
pub fn handleColorOperation(
|
||||
self: *StreamHandler,
|
||||
source: terminal.osc.Command.ColorOperation.Source,
|
||||
operations: *const terminal.osc.Command.ColorOperation.List,
|
||||
op: terminal.osc.color.Operation,
|
||||
requests: *const terminal.osc.color.List,
|
||||
terminator: terminal.osc.Terminator,
|
||||
) !void {
|
||||
// We'll need op one day if we ever implement reporting special colors.
|
||||
_ = op;
|
||||
|
||||
// return early if there is nothing to do
|
||||
if (operations.count() == 0) return;
|
||||
if (requests.count() == 0) return;
|
||||
|
||||
var buffer: [1024]u8 = undefined;
|
||||
var fba: std.heap.FixedBufferAllocator = .init(&buffer);
|
||||
|
|
@ -1201,63 +1204,71 @@ pub const StreamHandler = struct {
|
|||
var response: std.ArrayListUnmanaged(u8) = .empty;
|
||||
const writer = response.writer(alloc);
|
||||
|
||||
var report: bool = false;
|
||||
|
||||
try writer.print("\x1b]{}", .{source});
|
||||
|
||||
var it = operations.constIterator(0);
|
||||
|
||||
while (it.next()) |op| {
|
||||
switch (op.*) {
|
||||
var it = requests.constIterator(0);
|
||||
while (it.next()) |req| {
|
||||
switch (req.*) {
|
||||
.set => |set| {
|
||||
switch (set.kind) {
|
||||
switch (set.target) {
|
||||
.palette => |i| {
|
||||
self.terminal.flags.dirty.palette = true;
|
||||
self.terminal.color_palette.colors[i] = set.color;
|
||||
self.terminal.color_palette.mask.set(i);
|
||||
},
|
||||
.foreground => {
|
||||
self.foreground_color = set.color;
|
||||
_ = self.renderer_mailbox.push(.{
|
||||
.foreground_color = set.color,
|
||||
}, .{ .forever = {} });
|
||||
},
|
||||
.background => {
|
||||
self.background_color = set.color;
|
||||
_ = self.renderer_mailbox.push(.{
|
||||
.background_color = set.color,
|
||||
}, .{ .forever = {} });
|
||||
},
|
||||
.cursor => {
|
||||
self.cursor_color = set.color;
|
||||
_ = self.renderer_mailbox.push(.{
|
||||
.cursor_color = set.color,
|
||||
}, .{ .forever = {} });
|
||||
.dynamic => |dynamic| switch (dynamic) {
|
||||
.foreground => {
|
||||
self.foreground_color = set.color;
|
||||
_ = self.renderer_mailbox.push(.{
|
||||
.foreground_color = set.color,
|
||||
}, .{ .forever = {} });
|
||||
},
|
||||
.background => {
|
||||
self.background_color = set.color;
|
||||
_ = self.renderer_mailbox.push(.{
|
||||
.background_color = set.color,
|
||||
}, .{ .forever = {} });
|
||||
},
|
||||
.cursor => {
|
||||
self.cursor_color = set.color;
|
||||
_ = self.renderer_mailbox.push(.{
|
||||
.cursor_color = set.color,
|
||||
}, .{ .forever = {} });
|
||||
},
|
||||
.pointer_foreground,
|
||||
.pointer_background,
|
||||
.tektronix_foreground,
|
||||
.tektronix_background,
|
||||
.highlight_background,
|
||||
.tektronix_cursor,
|
||||
.highlight_foreground,
|
||||
=> log.info("setting dynamic color {s} not implemented", .{
|
||||
@tagName(dynamic),
|
||||
}),
|
||||
},
|
||||
.special => log.info("setting special colors not implemented", .{}),
|
||||
}
|
||||
|
||||
// Notify the surface of the color change
|
||||
self.surfaceMessageWriter(.{ .color_change = .{
|
||||
.kind = set.kind,
|
||||
.target = set.target,
|
||||
.color = set.color,
|
||||
} });
|
||||
},
|
||||
|
||||
.reset => |kind| {
|
||||
switch (kind) {
|
||||
.palette => |i| {
|
||||
const mask = &self.terminal.color_palette.mask;
|
||||
self.terminal.flags.dirty.palette = true;
|
||||
self.terminal.color_palette.colors[i] = self.terminal.default_palette[i];
|
||||
mask.unset(i);
|
||||
.reset => |target| switch (target) {
|
||||
.palette => |i| {
|
||||
const mask = &self.terminal.color_palette.mask;
|
||||
self.terminal.flags.dirty.palette = true;
|
||||
self.terminal.color_palette.colors[i] = self.terminal.default_palette[i];
|
||||
mask.unset(i);
|
||||
|
||||
self.surfaceMessageWriter(.{
|
||||
.color_change = .{
|
||||
.kind = .{ .palette = @intCast(i) },
|
||||
.color = self.terminal.color_palette.colors[i],
|
||||
},
|
||||
});
|
||||
},
|
||||
self.surfaceMessageWriter(.{
|
||||
.color_change = .{
|
||||
.target = target,
|
||||
.color = self.terminal.color_palette.colors[i],
|
||||
},
|
||||
});
|
||||
},
|
||||
.dynamic => |dynamic| switch (dynamic) {
|
||||
.foreground => {
|
||||
self.foreground_color = null;
|
||||
_ = self.renderer_mailbox.push(.{
|
||||
|
|
@ -1265,7 +1276,7 @@ pub const StreamHandler = struct {
|
|||
}, .{ .forever = {} });
|
||||
|
||||
self.surfaceMessageWriter(.{ .color_change = .{
|
||||
.kind = .foreground,
|
||||
.target = target,
|
||||
.color = self.default_foreground_color,
|
||||
} });
|
||||
},
|
||||
|
|
@ -1276,7 +1287,7 @@ pub const StreamHandler = struct {
|
|||
}, .{ .forever = {} });
|
||||
|
||||
self.surfaceMessageWriter(.{ .color_change = .{
|
||||
.kind = .background,
|
||||
.target = target,
|
||||
.color = self.default_background_color,
|
||||
} });
|
||||
},
|
||||
|
|
@ -1289,33 +1300,83 @@ pub const StreamHandler = struct {
|
|||
|
||||
if (self.default_cursor_color) |color| {
|
||||
self.surfaceMessageWriter(.{ .color_change = .{
|
||||
.kind = .cursor,
|
||||
.target = target,
|
||||
.color = color,
|
||||
} });
|
||||
}
|
||||
},
|
||||
}
|
||||
.pointer_foreground,
|
||||
.pointer_background,
|
||||
.tektronix_foreground,
|
||||
.tektronix_background,
|
||||
.highlight_background,
|
||||
.tektronix_cursor,
|
||||
.highlight_foreground,
|
||||
=> log.warn("resetting dynamic color {s} not implemented", .{
|
||||
@tagName(dynamic),
|
||||
}),
|
||||
},
|
||||
.special => log.info("resetting special colors not implemented", .{}),
|
||||
},
|
||||
|
||||
.report => |kind| report: {
|
||||
if (self.osc_color_report_format == .none) break :report;
|
||||
.reset_palette => {
|
||||
const mask = &self.terminal.color_palette.mask;
|
||||
var mask_iterator = mask.iterator(.{});
|
||||
while (mask_iterator.next()) |i| {
|
||||
self.terminal.flags.dirty.palette = true;
|
||||
self.terminal.color_palette.colors[i] = self.terminal.default_palette[i];
|
||||
self.surfaceMessageWriter(.{
|
||||
.color_change = .{
|
||||
.target = .{ .palette = @intCast(i) },
|
||||
.color = self.terminal.color_palette.colors[i],
|
||||
},
|
||||
});
|
||||
}
|
||||
mask.* = .initEmpty();
|
||||
},
|
||||
|
||||
report = true;
|
||||
.reset_special => log.warn(
|
||||
"resetting all special colors not implemented",
|
||||
.{},
|
||||
),
|
||||
|
||||
.query => |kind| report: {
|
||||
if (self.osc_color_report_format == .none) break :report;
|
||||
|
||||
const color = switch (kind) {
|
||||
.palette => |i| self.terminal.color_palette.colors[i],
|
||||
.foreground => self.foreground_color orelse self.default_foreground_color,
|
||||
.background => self.background_color orelse self.default_background_color,
|
||||
.cursor => self.cursor_color orelse
|
||||
self.default_cursor_color orelse
|
||||
self.foreground_color orelse
|
||||
self.default_foreground_color,
|
||||
.dynamic => |dynamic| switch (dynamic) {
|
||||
.foreground => self.foreground_color orelse self.default_foreground_color,
|
||||
.background => self.background_color orelse self.default_background_color,
|
||||
.cursor => self.cursor_color orelse
|
||||
self.default_cursor_color orelse
|
||||
self.foreground_color orelse
|
||||
self.default_foreground_color,
|
||||
.pointer_foreground,
|
||||
.pointer_background,
|
||||
.tektronix_foreground,
|
||||
.tektronix_background,
|
||||
.highlight_background,
|
||||
.tektronix_cursor,
|
||||
.highlight_foreground,
|
||||
=> {
|
||||
log.info(
|
||||
"reporting dynamic color {s} not implemented",
|
||||
.{@tagName(dynamic)},
|
||||
);
|
||||
break :report;
|
||||
},
|
||||
},
|
||||
.special => {
|
||||
log.info("reporting special colors not implemented", .{});
|
||||
break :report;
|
||||
},
|
||||
};
|
||||
|
||||
switch (self.osc_color_report_format) {
|
||||
.@"16-bit" => switch (kind) {
|
||||
.palette => |i| try writer.print(
|
||||
";{d};rgb:{x:0>4}/{x:0>4}/{x:0>4}",
|
||||
"\x1b]4;{d};rgb:{x:0>4}/{x:0>4}/{x:0>4}",
|
||||
.{
|
||||
i,
|
||||
@as(u16, color.r) * 257,
|
||||
|
|
@ -1323,19 +1384,21 @@ pub const StreamHandler = struct {
|
|||
@as(u16, color.b) * 257,
|
||||
},
|
||||
),
|
||||
else => try writer.print(
|
||||
";rgb:{x:0>4}/{x:0>4}/{x:0>4}",
|
||||
.dynamic => |dynamic| try writer.print(
|
||||
"\x1b]{d};rgb:{x:0>4}/{x:0>4}/{x:0>4}",
|
||||
.{
|
||||
@intFromEnum(dynamic),
|
||||
@as(u16, color.r) * 257,
|
||||
@as(u16, color.g) * 257,
|
||||
@as(u16, color.b) * 257,
|
||||
},
|
||||
),
|
||||
.special => unreachable,
|
||||
},
|
||||
|
||||
.@"8-bit" => switch (kind) {
|
||||
.palette => |i| try writer.print(
|
||||
";{d};rgb:{x:0>2}/{x:0>2}/{x:0>2}",
|
||||
"\x1b]4;{d};rgb:{x:0>2}/{x:0>2}/{x:0>2}",
|
||||
.{
|
||||
i,
|
||||
@as(u16, color.r),
|
||||
|
|
@ -1343,25 +1406,29 @@ pub const StreamHandler = struct {
|
|||
@as(u16, color.b),
|
||||
},
|
||||
),
|
||||
else => try writer.print(
|
||||
";rgb:{x:0>2}/{x:0>2}/{x:0>2}",
|
||||
.dynamic => |dynamic| try writer.print(
|
||||
"\x1b]{d};rgb:{x:0>2}/{x:0>2}/{x:0>2}",
|
||||
.{
|
||||
@intFromEnum(dynamic),
|
||||
@as(u16, color.r),
|
||||
@as(u16, color.g),
|
||||
@as(u16, color.b),
|
||||
},
|
||||
),
|
||||
.special => unreachable,
|
||||
},
|
||||
|
||||
.none => unreachable,
|
||||
}
|
||||
|
||||
try writer.writeAll(terminator.string());
|
||||
},
|
||||
}
|
||||
}
|
||||
if (report) {
|
||||
|
||||
if (response.items.len > 0) {
|
||||
// If any of the operations were reports, finalize the report
|
||||
// string and send it to the terminal.
|
||||
try writer.writeAll(terminator.string());
|
||||
const msg = try termio.Message.writeReq(self.alloc, response.items);
|
||||
self.messageWriter(msg);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue