Commit Graph

14492 Commits

Author SHA1 Message Date
Mitchell Hashimoto
dada3cd5fd Add MockView and SplitTreeTests (#10778)
This PR introduces unit tests and a supporting Mock NSView for testing
the SplitTree implementation in Swift. It includes 51 tests which
achieve approximately 93.13% (949/1019) coverage of SplitTree.swift's
branches.

<details>
  <summary>Coverage</summary>
  <pre>
./ghostty/macos/Sources/Features/Splits/SplitTree.swift 93.13%
(949/1019)
SplitTree.Path.isEmpty.getter 100.00% (1/1)
SplitTree.isEmpty.getter 100.00% (3/3)
SplitTree.isSplit.getter 100.00% (3/3)
SplitTree.init() 100.00% (3/3)
SplitTree.init(view:) 100.00% (3/3)
SplitTree.contains(_:) 100.00% (4/4)
SplitTree.inserting(view:at:direction:) 100.00% (6/6)
SplitTree.find(id:) 100.00% (4/4)
SplitTree.removing(_:) 93.75% (15/16)
SplitTree.replacing(node:with:) 93.75% (15/16)
SplitTree.focusTarget(for:from:) 82.14% (46/56)
closure #1 in SplitTree.focusTarget(for:from:) 100.00% (1/1)
closure #2 in SplitTree.focusTarget(for:from:) 100.00% (1/1)
closure #3 in SplitTree.focusTarget(for:from:) 100.00% (3/3)
implicit closure #1 in SplitTree.focusTarget(for:from:) 0.00% (0/1)
SplitTree.equalized() 100.00% (5/5)
SplitTree.resizing(node:by:in:with:) 92.00% (69/75)
closure #1 in SplitTree.resizing(node:by:in:with:) 100.00% (1/1)
SplitTree.viewBounds() 100.00% (4/4)
SplitTree.init(from:) 76.00% (19/25)
SplitTree.encode(to:) 100.00% (15/15)
SplitTree.Node.find(id:) 100.00% (13/13)
SplitTree.Node.node(view:) 88.89% (16/18)
SplitTree.Node.path(to:) 100.00% (32/32)
search #1 <A>(_:) in SplitTree.Node.path(to:) 100.00% (27/27)
SplitTree.Node.node(at:) 89.47% (17/19)
SplitTree.Node.inserting(view:at:direction:) 86.84% (33/38)
SplitTree.Node.replacingNode(at:with:) 100.00% (43/43)
replaceInner #1 <A>(current:pathOffset:) in
SplitTree.Node.replacingNode(at:with:) 96.67% (29/30)
SplitTree.Node.remove(_:) 70.27% (26/37)
implicit closure #1 in SplitTree.Node.remove(_:) 100.00% (1/1)
SplitTree.Node.resizing(to:) 100.00% (16/16)
SplitTree.Node.leftmostLeaf() 87.50% (7/8)
SplitTree.Node.rightmostLeaf() 87.50% (7/8)
SplitTree.Node.equalize() 100.00% (4/4)
SplitTree.Node.equalizeWithWeight() 100.00% (30/30)
SplitTree.Node.weightForDirection(_:) 83.33% (10/12)
SplitTree.Node.calculateViewBounds(in:) 100.00% (50/50)
SplitTree.Node.viewBounds() 100.00% (26/26)
SplitTree.Node.spatial(within:) 100.00% (18/18)
SplitTree.Node.dimensions() 80.77% (21/26)
SplitTree.Node.spatialSlots(in:) 100.00% (53/53)
SplitTree.Spatial.slots(in:from:) 100.00% (47/47)
closure #1 in SplitTree.Spatial.slots(in:from:) 100.00% (1/1)
distance #1 <A>(from:to:) in SplitTree.Spatial.slots(in:from:) 100.00%
(6/6)
closure #2 in SplitTree.Spatial.slots(in:from:) 100.00% (3/3)
implicit closure #1 in closure #2 in SplitTree.Spatial.slots(in:from:)
100.00% (1/1)
closure #3 in SplitTree.Spatial.slots(in:from:) 100.00% (3/3)
closure #4 in SplitTree.Spatial.slots(in:from:) 100.00% (3/3)
implicit closure #1 in closure #4 in SplitTree.Spatial.slots(in:from:)
100.00% (1/1)
closure #5 in SplitTree.Spatial.slots(in:from:) 100.00% (3/3)
closure #6 in SplitTree.Spatial.slots(in:from:) 100.00% (3/3)
implicit closure #1 in closure #6 in SplitTree.Spatial.slots(in:from:)
100.00% (1/1)
closure #7 in SplitTree.Spatial.slots(in:from:) 100.00% (3/3)
closure #8 in SplitTree.Spatial.slots(in:from:) 100.00% (3/3)
implicit closure #1 in closure #8 in SplitTree.Spatial.slots(in:from:)
100.00% (1/1)
closure #9 in SplitTree.Spatial.slots(in:from:) 100.00% (3/3)
SplitTree.Spatial.doesBorder(side:from:) 100.00% (20/20)
closure #1 in SplitTree.Spatial.doesBorder(side:from:) 100.00% (1/1)
closure #2 in SplitTree.Spatial.doesBorder(side:from:) 100.00% (3/3)
static SplitTree.Node.== infix(_:_:) 100.00% (13/13)
SplitTree.Node.init(from:) 66.67% (12/18)
SplitTree.Node.encode(to:) 100.00% (11/11)
SplitTree.Node.leaves() 100.00% (9/9)
SplitTree.makeIterator() 100.00% (3/3)
implicit closure #1 in SplitTree.makeIterator() 100.00% (1/1)
SplitTree.Node.makeIterator() 0.00% (0/3)
SplitTree.startIndex.getter 100.00% (3/3)
SplitTree.endIndex.getter 100.00% (3/3)
implicit closure #1 in SplitTree.endIndex.getter 100.00% (1/1)
SplitTree.subscript.getter 100.00% (5/5)
implicit closure #1 in SplitTree.subscript.getter 100.00% (1/1)
implicit closure #2 in implicit closure #1 in SplitTree.subscript.getter
100.00% (1/1)
implicit closure #3 in SplitTree.subscript.getter 0.00% (0/1)
implicit closure #4 in SplitTree.subscript.getter 0.00% (0/1)
SplitTree.index(after:) 100.00% (4/4)
implicit closure #1 in SplitTree.index(after:) 100.00% (1/1)
implicit closure #2 in SplitTree.index(after:) 0.00% (0/1)
SplitTree.Node.structuralIdentity.getter 100.00% (3/3)
SplitTree.Node.StructuralIdentity.init(_:) 100.00% (3/3)
static SplitTree.Node.StructuralIdentity.== infix(_:_:) 100.00% (3/3)
SplitTree.Node.StructuralIdentity.hash(into:) 100.00% (3/3)
SplitTree.Node.isStructurallyEqual(to:) 100.00% (18/18)
implicit closure #1 in SplitTree.Node.isStructurallyEqual(to:) 100.00%
(1/1)
implicit closure #2 in SplitTree.Node.isStructurallyEqual(to:) 100.00%
(1/1)
SplitTree.Node.hashStructure(into:) 100.00% (14/14)
SplitTree.structuralIdentity.getter 100.00% (3/3)
SplitTree.StructuralIdentity.init(_:) 100.00% (4/4)
static SplitTree.StructuralIdentity.== infix(_:_:) 100.00% (4/4)
implicit closure #1 in static SplitTree.StructuralIdentity.==
infix(_:_:) 100.00% (1/1)
SplitTree.StructuralIdentity.hash(into:) 80.00% (8/10)
static SplitTree.StructuralIdentity.areNodesStructurallyEqual(_:_:)
90.00% (9/10)
  </pre>
</details>

I chose this as a good place to start contributing to Ghostty because I
was curious about the macOS implementation, and there was a specific
request for help with testing (#7879).

My process for writing the tests was basically reading
[SplitTree.swift](./macos/Sources/Features/Splits/SplitTree.swift) to
understand it, then writing tests for each high-level method and
checking against code coverage to capture all the code paths:

## Running
```bash
rm -rf /tmp/ghostty-test.xcresult
xcodebuild -project macos/Ghostty.xcodeproj \
    -scheme GhosttyTest \
    -configuration Debug \
    test \
    -destination 'platform=macOS' \
    -enableCodeCoverage YES \
    -resultBundlePath /tmp/ghostty-test.xcresult \
    -only-testing:GhosttyTests/SplitTreeTests \
    2>&1 | xcbeautify
```

## Coverage
```bash
xcrun xccov view --report /tmp/ghostty-test.xcresult | grep 'SplitTree\.'
```

This was originally implemented in [~38
commits](https://github.com/pouwerkerk/ghostty/pull/1/commits), but I
squashed them down to 1 commit for easier review.

## AI Disclosure
The tests were written by me, but I used Opus 4.6 to explain some parts
of the code, and then finally to provide feedback on the tests. It
suggested tests for `nodeStructuralIdentityInSet` and
`nodeStructuralIdentityDistinguishesLeaves` as well as [the
Parameterized
test](6a0bca43f6),
`resizingAdjustsRatio`, which seemed like a clever way to collapse 12
individual tests into 3 parameterized ones that still run 12 cases
total. I didn't know this feature existed, and it seems like a great way
to write tests that are more maintainable. I read this relatively new
feature in the [Swift
Docs](https://developer.apple.com/documentation/testing/parameterizedtesting).
I find this to be a particularly useful feature of Claude/related
agents, where it can suggest better ways of writing something in a more
idiomatic way, and it taught me something new, which is always fun.

I'm more than happy to continue work on tests for #7879 and always
welcome to any feedback you have.
2026-02-17 16:18:53 -08:00
ghostty-vouch[bot]
b652a1ced3 Update VOUCHED list (#10804)
Triggered by [discussion
comment](https://github.com/ghostty-org/ghostty/discussions/10803) from
@jcollie.

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-02-17 22:35:12 +00:00
Mitchell Hashimoto
41cea500f4 termio: don't auto-generate palette if user didn't customize any (#10802)
This fixes the issue where our palette generation was changing our
default palette. The default palette is based on some well known values
chosen from various terminals and it was a bit jarring to have it
change.

We now only auto-generate the palette if the user has customized at
least one entry.
2026-02-17 13:08:26 -08:00
Mitchell Hashimoto
b25edc3e93 termio: don't auto-generate palette if user didn't customize any
This fixes the issue where our palette generation was changing our
default palette. The default palette is based on some well known values
chosen from various terminals and it was a bit jarring to have it
change.

We now only auto-generate the palette if the user has customized at
least one entry.
2026-02-17 13:04:35 -08:00
Mitchell Hashimoto
16cc707c80 terminal: add osc8 tag handling for HTML formatter (#9415)
The HTML page formatter can now track hyperlink state so <a> tags open
and close when the OSC 8 data changes. Also added a new
`writeHtmlEscaped` helper to keep generated markup safe.

Originally written with Copilot, revised by hand.
2026-02-17 12:54:43 -08:00
Mitchell Hashimoto
62968e423d terminal: clean up HTML OSC8 formatting 2026-02-17 12:48:35 -08:00
Matthew Hrehirchuk
9868bf3789 fix: replaced redundant writeHtmlEscaped method with writeCodepoint 2026-02-17 11:22:39 -08:00
Matthew Hrehirchuk
5e265c9c0d feat: add osc8 to <a> tag handling for html formatter 2026-02-17 11:20:40 -08:00
Mitchell Hashimoto
10039da572 bash: avoid mapfile for bash 3.2 compatibility (#10800)
We continue to support bash 3.2 for compatibility with /bin/bash on
macOS. `mapfile` was introduced in bash 4.0, so this change introduces a
`read -r`-based helper function for populating COMPREPLY from a list of
lines.

See: #3042
2026-02-17 10:39:35 -08:00
Mitchell Hashimoto
8435dffea9 generate 256 palette (#10554)
Hi Ghostty team,

I believe that terminals should generate the 256-color palette based on
the user's base16 theme.

The rationale and approach is written up
[here](https://gist.github.com/jake-stewart/0a8ea46159a7da2c808e5be2177e1783).

I consider it important that terminals support this out of the box so
that such behaviour can become normal and expected, because then
terminal program maintainers will consider the palette a viable choice.

I have created a PR for kitty and the maintainer seems interested. I
plan to offer this to more terminals soon.
2026-02-17 09:59:41 -08:00
Mitchell Hashimoto
f0a1b05f63 rename config 2026-02-17 09:54:34 -08:00
Mitchell Hashimoto
89dfb76778 terminal: clean up 256 color gen 2026-02-17 09:45:34 -08:00
Pieter Ouwerkerk
ce66bea581 Move MockView to SplitTreeTests itself 2026-02-17 09:37:02 -08:00
Mitchell Hashimoto
fded0e97cb terminal: clean up LAB methods, add tests, comments 2026-02-17 09:36:28 -08:00
Mitchell Hashimoto
50698c5c72 fmt 2026-02-17 09:18:03 -08:00
Jake Stewart
44d2ea25d0 explain mask 2026-02-17 09:17:54 -08:00
Jake Stewart
e268ff9a8b rename param 2026-02-17 09:17:54 -08:00
Jake Stewart
7729714935 refactor 256 color gen 2026-02-17 09:17:54 -08:00
Jake Stewart
5f89228a7a refactor lab colors 2026-02-17 09:17:54 -08:00
Jake Stewart
fad72e0ed1 generate 256 palette 2026-02-17 09:17:54 -08:00
ghostty-vouch[bot]
fa216edacc Update VOUCHED list (#10798)
Triggered by [discussion
comment](https://github.com/ghostty-org/ghostty/discussions/10782) from
@mitchellh.

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-02-17 17:16:21 +00:00
ghostty-vouch[bot]
e67a8b2da4 Update VOUCHED list (#10797)
Triggered by [discussion
comment](https://github.com/ghostty-org/ghostty/discussions/10784) from
@mitchellh.

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-02-17 17:14:50 +00:00
ghostty-vouch[bot]
34637b843e Update VOUCHED list (#10796)
Triggered by [discussion
comment](https://github.com/ghostty-org/ghostty/discussions/10785) from
@mitchellh.

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-02-17 17:14:22 +00:00
Jon Parise
54f2be8e7d bash: avoid mapfile for bash 3.2 compatibility
We continue to support bash 3.2 for compatibility with /bin/bash on
macOS. `mapfile` was introduced in bash 4.0, so this change introduces a
`read -r`-based helper function for populating COMPREPLY from a list of
lines.
2026-02-17 11:35:33 -05:00
ghostty-vouch[bot]
3e92aa2c3a Update VOUCHED list (#10795)
Triggered by
[comment](https://github.com/ghostty-org/ghostty/issues/10794#issuecomment-3915601262)
from @trag1c.

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-02-17 16:09:06 +00:00
ghostty-vouch[bot]
5c8d977304 Update VOUCHED list (#10793)
Triggered by
[comment](https://github.com/ghostty-org/ghostty/issues/10792#issuecomment-3915522813)
from @trag1c.

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-02-17 15:54:21 +00:00
Kat
f07b92a022 Update zh_TW Traditional Chinese locale, cc #10632 (#10788)
GitHub Copilot pull request summary:

> This pull request updates the Traditional Chinese (zh_TW) translations
for the "Change Tab Title" strings in the application. These updates
provide accurate translations for UI elements related to changing tab
titles.
> 
> **Translation updates:**
> 
> * Added the Traditional Chinese translation for the "Change Tab
Title…" menu item in `po/zh_TW.UTF-8.po`
> * Added the Traditional Chinese translation for the "Change Tab Title"
dialog label in `po/zh_TW.UTF-8.po`
2026-02-17 15:33:30 +00:00
ghostty-vouch[bot]
0aa88e0dac Update VOUCHED list (#10790)
Triggered by
[comment](https://github.com/ghostty-org/ghostty/issues/10788#issuecomment-3915336424)
from @00-kat.

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-02-17 15:20:52 +00:00
Peter Dave Hello
ebdf38999b Update zh_TW Traditional Chinese locale, cc #10632 2026-02-17 23:13:31 +08:00
Mitchell Hashimoto
b6dbd445d0 ci: update create-github-app-token 2026-02-16 21:02:44 -08:00
ghostty-vouch[bot]
a3dd93ae75 Update VOUCHED list (#10781)
Triggered by
[comment](https://github.com/ghostty-org/ghostty/issues/10379#issuecomment-3912263144)
from @mitchellh.

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-02-17 04:49:58 +00:00
ghostty-vouch[bot]
f1277737ff Update VOUCHED list (#10780)
Triggered by
[comment](https://github.com/ghostty-org/ghostty/issues/10379#issuecomment-3912261301)
from @mitchellh.

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-02-17 04:49:08 +00:00
Mitchell Hashimoto
39a10ecc0c ci: vouch uses the Ghostty Vouch GitHub app to bypass merge restrictions (#10779) 2026-02-16 20:48:25 -08:00
Pieter Ouwerkerk
8fdedbce45 Add MockView and SplitTreeTests 2026-02-16 20:23:48 -08:00
Mitchell Hashimoto
b5bb87161b renderer: drop opaque background for preedit cells (#10774)
Discussed in https://github.com/ghostty-org/ghostty/discussions/10739

## Summary

Remove the hardcoded opaque background (alpha=255) from IME preedit
cells so they respect `background-opacity` like all other cells.

When `background-opacity` is less than 1, preedit (composition) text was
rendered with a fully opaque background, causing the text to appear
highlighted and hard to read. This change removes the explicit per-cell
background from `addPreeditCell`, letting preedit cells fall through to
the global background. The underline indicator is preserved to mark the
preedit region.

---

`background-opacity` が 1
未満のとき、IME入力中(preedit)のセルが完全不透明な背景で描画され、ハイライトされたように見えて読みづらくなる問題を修正しました。

`addPreeditCell` のセル背景描画を削除し、グローバル背景に委ねることで通常セルと同じ透過表示になります。
preedit領域のアンダーラインは維持されます。

## Test plan

- Set `background-opacity` to a value less than 1 (e.g. 0.5)
- Type Japanese (or other IME input) to trigger preedit
- Verify preedit text no longer appears highlighted
- Verify the underline indicator is still drawn under preedit text

AI disclosure: I used Claude Code to investigate the source code and
generate code changes in this PR.
2026-02-16 19:55:34 -08:00
Shunya Yamashita
b1dce5f942 renderer: drop opaque background for preedit cells 2026-02-17 10:29:09 +09:00
Mitchell Hashimoto
cdcfac4e4a renderer: only compute and draw preedit cells if they change (#10772)
Fixes #10424
Replaces #10431

The issue is that when the row where preedit was wasn't dirty, we were
layering more preedit cells (identical ones) on top, so it'd appear to
get "thicker".
2026-02-16 14:28:06 -08:00
Jeffrey C. Ollie
6b6be837de gtk: Change tab title (#9904)
Closes #9880. 



https://github.com/user-attachments/assets/7110fae1-a7ca-42b6-8956-833b8bdd5d98

> [!NOTE]
>
> Used AI to get to explain to me some of the APIs and functionality
between `windows` and `tabs` and their connections. But the code itself,
I implemented everything, just used it for help with explaining some
things


## If you feel like reading more:
- I feel there is a lot of duplication now with the `prompt surface
title` functionality. For the dialog creation, i basically copied-pasted
the `surface_title_dialog`, and some of the `titleOverride` and the
`TabDialogSet` functionality is identical to that of the
`prompt_surface_title`

- I would think about abstracting it out, but first I think Id be nice
to think what the maintainers think about this approach before doing
anything else.
2026-02-16 16:24:47 -06:00
Mitchell Hashimoto
3c074b5aec renderer: only compute and draw preedit cells if they change
Fixes #10424
Replaces #10431

The issue is that when the row where preedit was wasn't dirty, we were
layering more preedit cells (identical ones) on top, so it'd appear to
get "thicker".
2026-02-16 14:23:44 -08:00
github-actions[bot]
bdcee2b05a Update VOUCHED list (#10770)
Triggered by [discussion
comment](https://github.com/ghostty-org/ghostty/discussions/10769) from
@jcollie.

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-02-16 22:13:22 +00:00
David Matos
7e31078882 Update translations 2026-02-16 23:11:19 +01:00
Mitchell Hashimoto
7895bf1d02 shell-integration: respect cursor-style-blink (#10643)
The `cursor` shell feature always used a blinking bar (beam), often to
the surprise of users who set `cursor-style-blink = false`.

This change extends our GHOSTTY_SHELL_FEATURES format to include either
`cursor:blink` (default) or `cursor:steady` based on cursor-style-blink
when the `cursor` feature is enabled, and all shell integrations have
been updated to use that additional information to choose the DECSCUSR
cursor value (5=blinking bar, 6=steady bar).

I also considered passing a DECSCUSR value in GHOSTTY_SHELL_FEATURES
(e.g. `cursor:5`). This mostly worked well, but zsh also needs the blink
state on its own for its block cursor. We also don't support any other
shell feature cursor configurability (e.g. the shape), so this was an
over generalization.

This does change the behavior for users who like the blinking bar in the
shell but have `cursor-blink-style = false` for other reasons. We could
provide additional `cursor` shell feature configurability (e.g.
`cursor:blink` in `shell-integration-features`), but I'll propose that
as its own change.

See: #2812
Closes: #8681

---

**AI Disclosure:** I did a lot of rubber ducking with Claude Code while
trying out various ideas. It was particularly useful for this kind of
feature because I could try out one thing and have it evaluate the
impact on all of the shell integration scripts at once.
2026-02-16 14:08:23 -08:00
Mitchell Hashimoto
f79906c7b8 osc: parse OSC 5522 - Kitty clipboard protocol (#10560)
This PR only adds support for parsing the OSCs. No support has been
added to DECRQM/DECRPM/DECSET/DECRST for mode 5522.

Related to #10549
2026-02-16 14:06:43 -08:00
David Matos
c067f2b911 Merge branch 'main' into gtk-prompt-tab-title 2026-02-16 23:06:04 +01:00
Mitchell Hashimoto
65c9208c08 zsh: update PS1 substitution to include 'cl=line' (#10768)
Our PS1 cleanup code (where we remove any markers we added) was still
looking for the previous 133;A form. Update it to include 'cl=line',
which was added in 8595558.
2026-02-16 13:59:59 -08:00
Jon Parise
e5e063c89d zsh: update PS1 substitution to include 'cl=line'
Our PS1 cleanup code (where we remove any markers we added) was still
looking for the previous 133;A form. Update it to include 'cl=line',
which was added in 8595558.
2026-02-16 16:49:03 -05:00
Mitchell Hashimoto
b358705b65 macos: rename shellQuoted() to Ghostty.Shell.quote() (#10765)
We already had an established Ghostty.Shell namespace (previously a
struct; now a more idiomatic enum), and locating these functions next to
each other makes it clearer how they relate to one another.
2026-02-16 12:40:13 -08:00
Mitchell Hashimoto
d1774e1423 macos: sort INFOPLIST_KEY_ names (#10764)
Xcode wants these to be sorted and will update this list when the
project file is saved so proactively make this change before it gets
mixed up in other work.
2026-02-16 12:31:33 -08:00
Jon Parise
df6feba417 macos: rename shellQuoted() to Ghostty.Shell.quote()
We already had an established Ghostty.Shell namespace (previously a
struct; now a more idiomatic enum), and locating these functions next to
each other makes it clearer how they relate to one another.
2026-02-16 15:27:57 -05:00
Jon Parise
d49ac65b16 macos: sort INFOPLIST_KEY_ names
Xcode wants these to be sorted and will update this list when the
project file is saved so proactively make this change before it gets
mixed up in other work.
2026-02-16 15:25:53 -05:00