Skip to content

Commit 14e3802

Browse files
Merge pull request #621 from migueldeicaza/arabic-support
Bidi support
2 parents 7b3ef90 + 2847b8a commit 14e3802

61 files changed

Lines changed: 7866 additions & 184 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

PERFORMANCE.md

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
Performance testing
2+
===================
3+
4+
SwiftTerm has three levels of performance measurement, from fastest to most
5+
realistic:
6+
7+
1. **Headless feed benchmarks** — measure the terminal-emulation engine
8+
(parser + buffer) with no rendering.
9+
2. **RenderBench** — a deterministic harness that drives the real
10+
`TerminalView` render path with synthetic workloads. This is the primary
11+
tool for render-path work and for Instruments profiling.
12+
3. **In-app measurement** — vtebench or timed `cat` runs typed into the
13+
MacTerminal sample app, over a real PTY and shell.
14+
15+
When comparing two revisions, build the second revision in a git worktree so
16+
both binaries exist at once:
17+
18+
```bash
19+
git worktree add /tmp/swiftterm-main main
20+
# ... build the same harness in both checkouts, run them back to back ...
21+
git worktree remove --force /tmp/swiftterm-main
22+
```
23+
24+
1. Headless feed benchmarks
25+
---------------------------
26+
27+
The tests live in `Tests/SwiftTermTests/PerformanceTest.swift`. They feed
28+
byte streams into a `HeadlessTerminal` for a fixed duration and print
29+
throughput in calls/second. Release mode is required for meaningful numbers,
30+
and `@testable import` in release needs `-enable-testing`:
31+
32+
```bash
33+
swift test -c release -Xswiftc -enable-testing --filter "PerformaceTests/testPerformance2"
34+
```
35+
36+
Run each test individually with `--filter` — Swift Testing runs tests
37+
concurrently by default, which corrupts throughput measurements.
38+
39+
Two of the tests need external data files and silently skip when absent:
40+
41+
- `repeatBigBlob` / `measureBigBlogFeed` read `~/cvs/vtebench/x`, generated
42+
with [vtebench](https://github.com/alacritty/vtebench):
43+
`target/release/vtebench --max-samples 1 -b benchmarks/medium_cells/`
44+
- `repeatDataFile` reads `~/data-file` (any large terminal capture).
45+
46+
Duration-based tests complete a whole number of iterations, so a 10-second
47+
test that finishes ~13 iterations has ±7% quantization — treat differences
48+
smaller than that as noise.
49+
50+
2. RenderBench (render path, Instruments)
51+
-----------------------------------------
52+
53+
`Tools/RenderBench` is a small SPM executable that hosts a real
54+
`TerminalView` in an on-screen window and feeds it synthetic frames as fast
55+
as the main run loop accepts them — no PTY, no shell, byte-identical input on
56+
every run (fixed seed), so two builds are directly comparable.
57+
58+
```bash
59+
cd Tools/RenderBench
60+
swift build -c release
61+
.build/release/RenderBench --seconds 10 --scenario dense
62+
```
63+
64+
It prints MB/s and frames/s every second and a `TOTAL` line at the end.
65+
66+
Options:
67+
68+
- `--scenario dense` — every cell gets its own truecolor foreground and
69+
background (vtebench dense_cells shape; stresses attribute handling, run
70+
fragmentation, and color conversion)
71+
- `--scenario medium` — a color change every 8 cells (longer runs)
72+
- `--scenario scroll` — plain scrolling ASCII (parser + scroll + full-screen
73+
redraw)
74+
- `--scenario arabic` — scrolling Arabic words (BiDi paragraph analysis,
75+
shaping, font fallback)
76+
- `--seconds N` — run duration (default 15)
77+
- `--metal` — use the Metal renderer instead of CoreGraphics
78+
79+
The package pins its dependency identity (`.package(name: "SwiftTerm",
80+
path: "../..")`), so it also builds inside a worktree whose directory is not
81+
named `SwiftTerm` — copy `Tools/RenderBench` into the worktree if the
82+
revision under test predates it.
83+
84+
### Profiling with Instruments
85+
86+
```bash
87+
cd Tools/RenderBench
88+
swift build -c release
89+
xcrun xctrace record --template 'Time Profiler' --output ~/dense.trace \
90+
--launch -- .build/release/RenderBench --seconds 20 --scenario dense
91+
open ~/dense.trace
92+
```
93+
94+
Each `feed` call is wrapped in an os_signpost (subsystem
95+
`org.tirania.SwiftTerm`, category `RenderBench`), so adding the os_signpost
96+
instrument splits main-thread time between the feed/parse side and the
97+
AppKit draw cycles. For A/B analysis, record the same scenario from both
98+
checkouts and diff the heaviest stacks under `buildAttributedString` and the
99+
draw loop.
100+
101+
3. In-app measurement
102+
---------------------
103+
104+
For end-to-end numbers over a real PTY, build the sample app in Release
105+
(Debug builds SwiftTerm at `-Onone` and exaggerates Swift-level costs):
106+
107+
```bash
108+
cd TerminalApp
109+
xcodebuild -project MacTerminal.xcodeproj -scheme MacTerminal \
110+
-configuration Release -derivedDataPath /tmp/dd build
111+
```
112+
113+
Then, inside the running terminal window, run vtebench:
114+
115+
```bash
116+
vtebench -b benchmarks/dense_cells --max-secs 6 --dat /tmp/results.dat
117+
```
118+
119+
The `.dat` file has one column per benchmark with per-sample times in ms;
120+
more samples completed in the fixed time budget = faster. The app defaults
121+
to the CoreGraphics renderer; flip `setUseMetal(false)` to `true` in
122+
`TerminalApp/MacTerminal/ViewController.swift` to measure Metal (and revert
123+
afterwards). Keep the window size identical between runs — cols × rows
124+
changes the per-frame workload.
125+
126+
Methodology notes
127+
-----------------
128+
129+
- **Pair your A/B runs.** Absolute numbers drift between sessions (thermal
130+
state, display state, background load). Run main and the branch back to
131+
back in the same block, and re-run any surprising result before believing
132+
it — a transient machine state can halve one configuration's numbers for
133+
minutes at a time while others look normal.
134+
- **Interpret cat/PTY timings carefully.** `time cat file` inside a terminal
135+
measures how fast the terminal drains the PTY; payloads under a few MB fit
136+
in kernel buffering and undercount. Use payloads of 10 MB+.
137+
- **vtebench sample distributions are bimodal** (fast PTY-buffered samples
138+
next to render-synced ones); compare sample counts and means, not medians,
139+
and treat differences under ~10% as noise.
140+
- **What each scenario is sensitive to:** `dense` regresses when per-cell or
141+
per-run work is added to attribute handling (dictionary copies, bridging,
142+
color conversion); `scroll` when scroll/feed or full-screen redraw gets
143+
slower; `arabic` when BiDi paragraph analysis, shaping, or font fallback
144+
gets slower. A change that only moves `arabic` costs RTL users only; a
145+
change that moves `dense`/`scroll` costs everyone.

README.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ Features
5050

5151
* Pretty decent terminal emulation, on or better than XtermSharp and xterm.js (and more comprehensive in many ways)
5252
* Unicode rendering (including Emoji, and combining characters and emoji)
53+
* Bidirectional text (Arabic, Hebrew) following the [terminal-wg BiDi recommendation](https://terminal-wg.pages.freedesktop.org/bidi/), with Arabic contextual shaping
5354
* Reusable and pluggable engine allows multiple user interfaces to be built on top of it:
5455
* Bundled MacOS and iOS
5556
* Bundled Headless terminal.
@@ -217,6 +218,51 @@ test suite to run.
217218
If using Xcode, you can select the "SwiftTerm" project, and then use Command-U
218219
to run the test suite.
219220

221+
## Bidirectional text (BiDi)
222+
223+
SwiftTerm implements the [terminal-wg BiDi
224+
recommendation](https://terminal-wg.pages.freedesktop.org/bidi/) for
225+
right-to-left and mixed-direction text on the Apple views (both the
226+
CoreGraphics and Metal renderers):
227+
228+
* The buffer stays in logical order; each paragraph is reordered at render
229+
time with the Unicode Bidirectional Algorithm, with Arabic contextual
230+
shaping, lam-alef ligatures, and bracket mirroring.
231+
* All six presentation modes from the recommendation are supported:
232+
implicit/explicit, fixed LTR/RTL, and autodetection from the first strong
233+
character. The default (implicit + autodetect + LTR fallback) renders RTL
234+
text correctly out of the box and leaves LTR output unchanged.
235+
* Terminal applications control the behavior with the standard sequences:
236+
BDSM (`CSI 8 h/l`), SCP (`CSI Ps SP k`), and DEC private modes 2501
237+
(autodetection), 2500 (box-drawing mirroring), and 1243 (arrow-key
238+
swapping), including DECRQM queries and XTSAVE/XTRESTORE.
239+
* Embedders can set the initial state through `TerminalOptions`
240+
(`initialBidiState`, `initialBidiArrowKeySwap`, `maximumBidiParagraphRows`),
241+
inspect it via `Terminal.currentBidiState`, and opt a view out entirely
242+
with `TerminalView.bidiHostPolicy = .legacyLeftToRight`.
243+
244+
The details are in the [BiDi
245+
documentation](https://migueldeicaza.github.io/SwiftTerm/documentation/swiftterm/bidi).
246+
247+
## BiDi visual test harness
248+
249+
The [SwiftTerm BiDi harness](Tools/BidiHarness/README.md) is an AppKit app for
250+
visual BiDi tests. It shows SwiftTerm beside a WebKit reference. Its scenarios
251+
cover paragraph reflow, terminal modes, reset behavior, box mirroring,
252+
combining marks, selection, cursor movement, and scrollback.
253+
254+
Run it from the repository root:
255+
256+
```sh
257+
Tools/BidiHarness/Scripts/run-harness.sh --artifacts /tmp/bidi-artifacts
258+
```
259+
260+
Use the controls in the app to select a scenario, move through its steps,
261+
resize the terminal, scroll, change the renderer, and save a capture. The app
262+
also has a local control socket for repeatable test runs. See the harness README
263+
for the control commands, Xcode instructions, artifact paths, and the macOS
264+
permission that Metal window capture needs.
265+
220266
Screenshots
221267
===========
222268

0 commit comments

Comments
 (0)