Skip to content

Commit c63739e

Browse files
committed
Move away from unsafe code to decode a UTF8 byte stream, and use Swift's APIs
for it. And also implement the usage of the variation selector to choose the width for a handful of scenarios. See #412 for discussion
1 parent c659b20 commit c63739e

2 files changed

Lines changed: 54 additions & 19 deletions

File tree

Sources/SwiftTerm/Terminal.swift

Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1174,23 +1174,29 @@ open class Terminal {
11741174
for _ in 1..<n {
11751175
x.append (readingBuffer.getNext())
11761176
}
1177-
x.append(0)
1178-
x.withUnsafeBytes { ptr in
1179-
let unsafeBound = ptr.bindMemory(to: UInt8.self)
1180-
let unsafePointer = unsafeBound.baseAddress!
1181-
1182-
let s = String (cString: unsafePointer)
1183-
ch = s.first ?? Character (" ")
1184-
1185-
// Now the challenge is that we have a character, not a rune, and we want to compute
1186-
// the width of it.
1187-
if ch.unicodeScalars.count == 1 {
1188-
chWidth = UnicodeUtil.columnWidth(rune: ch.unicodeScalars.first!)
1189-
} else {
1190-
chWidth = 0
1191-
for scalar in ch.unicodeScalars {
1192-
chWidth = max (chWidth, UnicodeUtil.columnWidth(rune: scalar))
1193-
}
1177+
1178+
var iterator = x.makeIterator()
1179+
var decoder = UTF8()
1180+
switch decoder.decode(&iterator) {
1181+
case .scalarValue(let scalar):
1182+
ch = Character(scalar)
1183+
default:
1184+
// Invalid UTF-8 sequence, fall back to interpreting the first byte
1185+
let rune = UnicodeScalar(code)
1186+
chWidth = UnicodeUtil.columnWidth(rune: rune)
1187+
let charData = CharData (attribute: curAttr, scalar: rune, size: Int8 (chWidth))
1188+
buffer.insertCharacter(charData)
1189+
continue
1190+
}
1191+
1192+
// Now the challenge is that we have a character, not a rune, and we want to compute
1193+
// the width of it.
1194+
if ch.unicodeScalars.count == 1 {
1195+
chWidth = UnicodeUtil.columnWidth(rune: ch.unicodeScalars.first!)
1196+
} else {
1197+
chWidth = 0
1198+
for scalar in ch.unicodeScalars {
1199+
chWidth = max (chWidth, UnicodeUtil.columnWidth(rune: scalar))
11941200
}
11951201
}
11961202
} else {
@@ -1239,7 +1245,15 @@ open class Terminal {
12391245
// If the resulting string is 1 grapheme cluster, then it combined properly
12401246
if newStr.count == 1 {
12411247
if let newCh = newStr.first {
1242-
cd.setValue(char: newCh, size: Int32 (cd.width))
1248+
switch firstScalar.value {
1249+
// This is the "This should use color modifier" on the previous item
1250+
// and we are going to take this to mean two columns
1251+
// See https://github.com/migueldeicaza/SwiftTerm/pull/412
1252+
case 0xFE0F:
1253+
cd.setValue(char: newCh, size: 2)
1254+
default:
1255+
cd.setValue(char: newCh, size: Int32 (cd.width))
1256+
}
12431257
existingLine [lastx] = cd
12441258
updateRange (last.y)
12451259
continue

Tests/SwiftTermTests/UnicodeTests.swift

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,28 @@ final class SwiftTermUnicode: XCTestCase {
3131
XCTAssertEqual(t.getCharacter (col:0, row: 4), "b⃑")
3232

3333
}
34-
34+
35+
func testVariationSelector ()
36+
{
37+
let h = HeadlessTerminal (queue: SwiftTermTests.queue) { exitCode in }
38+
let t = h.terminal!
39+
40+
// This will send ⛩️ (0x26e9) is actually in a special class: it can either be one-column (⛩) or two-columns (⛩️)
41+
// depending on the unicode "variation selector" that follows: 0x26e9 0xfe0e = ⛩, 0x26e9 0xfe0f = ⛩️.
42+
// Globally, any unicode character followed by 0xfe0e will be single column, any unicode character
43+
// followed by 0xfe0f will be double-column:
44+
// https://en.wikipedia.org/wiki/Variation_Selectors_(Unicode_block)
45+
t.feed (text: "\u{026e9}\u{0fe0f}\n\r\u{026e9}\u{0fe0e}")
46+
47+
// The first line should have 2 columns
48+
let char0_0 = t.getCharData(col: 0, row: 0)
49+
XCTAssertEqual(char0_0?.width, 2)
50+
51+
// The first line should have 1 columns
52+
let char1_0 = t.getCharData(col: 0, row: 1)
53+
XCTAssertEqual(char1_0?.width, 1)
54+
}
55+
3556
func testEmoji ()
3657
{
3758
let h = HeadlessTerminal (queue: SwiftTermTests.queue) { exitCode in }

0 commit comments

Comments
 (0)