Skip to content

Commit c8d927c

Browse files
committed
Sync pi-tui Unicode input + tests, bump to 0.1.1
1 parent 11d9cfb commit c8d927c

File tree

7 files changed

+129
-5
lines changed

7 files changed

+129
-5
lines changed

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Changelog
2+
3+
All notable changes to this project will be documented in this file.
4+
5+
## [0.1.1] - 2025-11-17
6+
- Allow printable Unicode in editor paste path (drops only control characters) to match upstream pi-tui Unicode input behavior.
7+
- Add Unicode-focused editor tests (emoji, umlauts, cursor movement, control-char stripping).
8+
- Document printable Unicode handling and Alt+Enter newline guidance in spec/README.
9+
10+
## [0.1.0] - 2025-11-15
11+
- Initial TauTUI sync with pi-tui core features (editor, autocomplete, markdown, loader, select list, renderer).

Package.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33

44
import PackageDescription
55

6+
// Current package version
7+
private let packageVersion = "0.1.1"
8+
69
let package = Package(
710
name: "TauTUI",
811
platforms: [

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ editor.onSubmit = { print("Submitted", $0) }
145145
editor.setAutocompleteProvider(CombinedAutocompleteProvider(commands: [DemoCommand()]))
146146
```
147147

148-
Key bindings include Enter/Shift+Enter, Ctrl+K/U/W/A/E, Option word motion, Tab for autocomplete, Escape to cancel, etc.
148+
Key bindings include Enter for submit, Shift/Ctrl/Alt+Enter for newlines (Alt is the most reliable across terminals), Ctrl+K/U/W/A/E, Option word motion, Tab for autocomplete, Escape to cancel, etc.
149149

150150
### SelectList
151151

Sources/TauTUI/Components/Editor.swift

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -329,8 +329,9 @@ public final class Editor: Component {
329329
partial.append(char)
330330
return
331331
}
332-
guard let scalar = char.unicodeScalars.first else { return }
333-
if scalar.value >= 32, scalar.value <= 126 {
332+
// Keep any printable Unicode character; drop control chars (< 0x20).
333+
let hasControl = char.unicodeScalars.contains { $0.value < 32 }
334+
if !hasControl {
334335
partial.append(char)
335336
}
336337
}
@@ -349,7 +350,7 @@ public final class Editor: Component {
349350
return
350351
}
351352
if lines.count == 1 {
352-
for char in normalized {
353+
for char in sanitized {
353354
self.insertCharacter(String(char))
354355
}
355356
return
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import Foundation
2+
import Testing
3+
@testable import TauTUI
4+
5+
private func type(_ text: String, into editor: Editor) {
6+
for char in text {
7+
editor.handle(input: .key(.character(char)))
8+
}
9+
}
10+
11+
@Suite("Editor + Unicode")
12+
struct EditorUnicodeTests {
13+
@Test
14+
func insertsMixedUnicode() async throws {
15+
let editor = Editor()
16+
type("Hello äöü 😀", into: editor)
17+
#expect(editor.getText() == "Hello äöü 😀")
18+
}
19+
20+
@Test
21+
func backspaceHandlesSingleAndMultiScalarCharacters() async throws {
22+
let editor = Editor()
23+
type("ä👍", into: editor)
24+
25+
editor.handle(input: .key(.backspace)) // remove 👍
26+
#expect(editor.getText() == "ä")
27+
28+
editor.handle(input: .key(.backspace)) // remove ä
29+
#expect(editor.getText().isEmpty)
30+
}
31+
32+
@Test
33+
func arrowNavigationAcrossEmoji() async throws {
34+
let editor = Editor()
35+
type("😀👍", into: editor)
36+
editor.handle(input: .key(.arrowLeft))
37+
editor.handle(input: .key(.character("x")))
38+
#expect(editor.getText() == "😀x👍")
39+
}
40+
41+
@Test
42+
func insertAfterCursorMoveOverUmlauts() async throws {
43+
let editor = Editor()
44+
type("äöü", into: editor)
45+
editor.handle(input: .key(.arrowLeft))
46+
editor.handle(input: .key(.arrowLeft))
47+
editor.handle(input: .key(.character("x")))
48+
#expect(editor.getText() == "äxöü")
49+
}
50+
51+
@Test
52+
func pastePreservesUnicodeAndStripsControlChars() async throws {
53+
let editor = Editor()
54+
editor.handle(input: .paste("Hällö\u{0007} Wörld! 😀 äöüÄÖÜß"))
55+
#expect(editor.getText() == "Hällö Wörld! 😀 äöüÄÖÜß")
56+
}
57+
58+
@Test
59+
func preservesUmlautsAcrossLineBreaks() async throws {
60+
let editor = Editor()
61+
type("äöü\nÄÖÜ", into: editor)
62+
#expect(editor.getText() == "äöü\nÄÖÜ")
63+
}
64+
65+
@Test
66+
func setTextReplacesDocumentWithUnicode() async throws {
67+
let editor = Editor()
68+
editor.setText("Hällö Wörld! 😀 äöüÄÖÜß")
69+
#expect(editor.getText() == "Hällö Wörld! 😀 äöüÄÖÜß")
70+
}
71+
72+
@Test
73+
func ctrlAMoveThenInsertWithUnicodePresent() async throws {
74+
let editor = Editor()
75+
type("äöü", into: editor)
76+
editor.handle(input: .key(.character("a"), modifiers: [.control]))
77+
editor.handle(input: .key(.character("X")))
78+
#expect(editor.getText() == "Xäöü")
79+
}
80+
}

docs/pitui-sync.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# pi-tui sync log (Nov 15–17, 2025)
2+
3+
Context: track upstream changes in `packages/tui` from `pi-mono` since **2025-11-15** (last pre-window commit: `1afe40e` / v0.7.10). Current head inspected: `origin/main` as of **2025-11-17**.
4+
5+
## Commits (newest → oldest)
6+
- **2025-11-16 23:08 CET — ed53fce — v0.7.13:** version bump after Unicode input fix (no code changes).
7+
- **2025-11-16 23:06 CET — a032d41 — merge fix/support-umlauts-and-unicode:** pulls in editor Unicode work + README tweak; keeps new tests.
8+
- **2025-11-16 23:05 CET — adc8b0e — refactor:** clarifies the Unicode filter by checking `charCodeAt(0) >= 32` for regular characters.
9+
- **2025-11-16 22:56 CET — b2491aa — v0.7.12:** version bump for models.json work (no TUI code touched).
10+
- **2025-11-16 21:05 CET — 7efcda6 — test(editor):** reorganizes Unicode tests for clarity.
11+
- **2025-11-16 21:01 CET — fd2b2ec — Filter model selector…:** only bumps `packages/tui/package.json`; logic elsewhere.
12+
- **2025-11-16 18:15 CET — 500e0f8 — test(editor):** adds coverage for umlauts, emojis, cursor movement over multi-code-unit chars, Backspace on surrogate pairs, setText with Unicode, and Ctrl+A insertion.
13+
- **2025-11-16 18:09 CET — efa6a00 — feat(tui):** functional change—editor now treats any `charCode >= 32` as insertable (was ASCII-only) and updates keybinding docs to note `Alt+Enter` as the most reliable “new line” chord.
14+
15+
## Porting impact for TauTUI
16+
- **Input filtering:** `packages/tui/src/components/editor.ts` now allows all printable Unicode (`charCode >= 32`) and removes the upper ASCII bound in paste filtering. Our Swift `Editor` still limits pasted characters to scalars `<= 126` (see `Sources/TauTUI/Components/Editor.swift`, `handlePaste`), so Unicode input/paste parity is missing—needs to be relaxed to `>= 32` with no upper bound.
17+
- **Keybinding doc:** Upstream README calls out `Alt+Enter` as the most reliable way to insert a newline in terminals; mirror this note in our docs (`docs/spec.md` and/or README) when we next touch keybinding guidance.
18+
- **Tests to mirror:** Upstream added explicit Unicode scenarios. Add equivalent Swift tests covering:
19+
- Mixed ASCII + umlaut + emoji insertion stays literal.
20+
- Backspace over single-code-unit umlauts vs. multi-code-unit emojis (requiring two deletes).
21+
- Cursor movement across multi-code-unit emojis before insertion.
22+
- Unicode preserved across newlines and via `setText`/paste.
23+
- Ctrl+A move-to-start followed by insertion with Unicode present.
24+
- **Version-only commits:** b2491aa/fd2b2ec/ed53fce are version bumps; no TauTUI action beyond tracking upstream tag alignment.
25+
26+
## Next steps
27+
1) Update `Editor.handlePaste` (and any other printable-character guards) to accept all `>= 0x20` code points.
28+
2) Add the Unicode-focused tests to `Tests/TauTUITests` to verify Backspace behavior on surrogate pairs.
29+
3) Refresh newline keybinding wording to include `Alt+Enter` reliability note.

docs/spec.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ public protocol Terminal: AnyObject {
109109
- `PasteManager`: tracks bracketed paste state, large paste markers (`[paste #N +xx lines]`), Map from ID → content for substitution on submit.
110110
- `AutocompleteController`: wraps `AutocompleteProvider`, handles Tab triggers, slash command detection, forced file completion, `SelectList` overlay.
111111
- Rendering: draw horizontal lines (chalk gray equivalent) above/below; show fake cursor via reverse video; append autocomplete list when active (reusing `SelectList` rendering output).
112-
- Input pipeline replicates pi-tui order: bracketed paste start/end, autocomplete keys, Tab logic, control shortcuts (Ctrl+A/E/K/U/W, Option+Backspace, Shift+Enter combos), newline vs submit decision, backspace/delete/arrow keys, printable ASCII insertion.
112+
- Input pipeline replicates pi-tui order: bracketed paste start/end, autocomplete keys, Tab logic, control shortcuts (Ctrl+A/E/K/U/W, Option+Backspace, Shift+Enter combos), newline vs submit decision, backspace/delete/arrow keys, printable Unicode insertion (charCode ≥ 0x20).
113113
- Public API:
114114
```swift
115115
final class Editor: Component {

0 commit comments

Comments
 (0)