How to Read Ansi Escape Code Response Value in Swift

How to read ANSI Escape code response value in Swift?

A terminal is by default in "canonical input processing" mode, which means that a read request will not return until an entire line has been typed and processed. The tcsetattr() function is used to disable both the input processing and echo, see for example

  • How to get cursor position in C using ANSI code
  • Reading the Device Status Report ANSI escape sequence reply

and the tcsetattr(3) and termios(4) manual pages.

Here is a simple example in Swift, inspired by the C code in the above Q&As, and also by Xcode Swift Command Line Tool reads 1 char from keyboard without echo or need to press return:

#if os(Linux)
import Glibc
#else
import Darwin
#endif

// Write escape sequence:
write(STDOUT_FILENO, "\u{1b}[6n", 4)

// Save terminal attributes:
var oldt = termios()
tcgetattr(STDIN_FILENO, &oldt)

// Disable canonical input processing and echo:
var newt = oldt
newt.c_lflag &= ~tcflag_t(ICANON)
newt.c_lflag &= ~tcflag_t(ECHO)
tcsetattr(STDIN_FILENO, TCSANOW, &newt)

// Read response:
var response: [UInt8] = []
var c: UInt8 = 0
repeat {
read(STDIN_FILENO, &c, 1)
response.append(c)
} while c != UInt8(ascii: "R")

// Restore original terminal attributes:
tcsetattr(STDIN_FILENO, TCSANOW, &oldt)

print(response) // [27, 91, 52, 59, 49, 82] == ESC[4;1R

Note that this works only when run in a Terminal window, not within Xcode.

Reading the Device Status Report ANSI escape sequence reply

Your program is working but is waiting for an EOL character.

scanf is line oriented so it waits for a new line before processing. Try running your program and then hit the enter key.

The solution is to use something else that doesn't need a new line to read the input and then use sscanf to parse the values out.

You will also need to make stdin non-blocking or you won't get the input until the buffer is full or stdin is closed. See this question Making stdin non-blocking

You should also call fflush(stdout); after your printf to ensure it is actually written (printf is often line buffered so without a newline it may not flush the buffer).

How to get Keyboard inputs in native Swift?

You'll need to adjust with the terminal line discipline (termios) to turn off buffering and give you each character as it's typed. You'll also probably want to turn off terminal echo.

// You said no frameworks. But I'm guessing you'll accept libc.
import Darwin.libc

// Fetch the terminal settings
var term = termios()
tcgetattr(STDIN_FILENO, &term)

// Save them if you need to restore them later
var savedTerm = term

// Turn off canonical input (buffered) and echo
term.c_lflag &= ~(UInt(ICANON) | UInt(ECHO))

// Set the terminal settings immediately
tcsetattr(STDIN_FILENO, TCSANOW, &term)

// Now you can read a character directly
let c = getchar()

// It's an Int32 Unicode code point, so you may want to convert
// it to something more useful
if let input = UnicodeScalar(Int(c)) {
print("Got \(input)")
}

// Restore the settings if you need to. Most shells will do this automatically
tcgetattr(STDIN_FILENO, &savedTerm)

List of ANSI color escape sequences

The ANSI escape sequences you're looking for are the Select Graphic Rendition subset. All of these have the form

\033[XXXm

where XXX is a series of semicolon-separated parameters.

To say, make text red, bold, and underlined (we'll discuss many other options below) in C you might write:

printf("\033[31;1;4mHello\033[0m");

In C++ you'd use

std::cout<<"\033[31;1;4mHello\033[0m";

In Python3 you'd use

print("\033[31;1;4mHello\033[0m")

and in Bash you'd use

echo -e "\033[31;1;4mHello\033[0m"

where the first part makes the text red (31), bold (1), underlined (4) and the last part clears all this (0).

As described in the table below, there are a large number of text properties you can set, such as boldness, font, underlining, &c.

Font Effects




















































































































































































































CodeEffectNote
0Reset / Normalall attributes off
1Bold or increased intensity
2Faint (decreased intensity)Not widely supported.
3ItalicNot widely supported. Sometimes treated as inverse.
4Underline
5Slow Blinkless than 150 per minute
6Rapid BlinkMS-DOS ANSI.SYS; 150+ per minute; not widely supported
7[[reverse video]]swap foreground and background colors
8ConcealNot widely supported.
9Crossed-outCharacters legible, but marked for deletion. Not widely supported.
10Primary(default) font
11–19Alternate fontSelect alternate font n-10
20Frakturhardly ever supported
21Bold off or Double UnderlineBold off not widely supported; double underline hardly ever supported.
22Normal color or intensityNeither bold nor faint
23Not italic, not Fraktur
24Underline offNot singly or doubly underlined
25Blink off
27Inverse off
28Revealconceal off
29Not crossed out
30–37Set foreground colorSee color table below
38Set foreground colorNext arguments are 5;<n> or 2;<r>;<g>;<b>, see below
39Default foreground colorimplementation defined (according to standard)
40–47Set background colorSee color table below
48Set background colorNext arguments are 5;<n> or 2;<r>;<g>;<b>, see below
49Default background colorimplementation defined (according to standard)
51Framed
52Encircled
53Overlined
54Not framed or encircled
55Not overlined
60ideogram underlinehardly ever supported
61ideogram double underlinehardly ever supported
62ideogram overlinehardly ever supported
63ideogram double overlinehardly ever supported
64ideogram stress markinghardly ever supported
65ideogram attributes offreset the effects of all of 60-64
90–97Set bright foreground coloraixterm (not in standard)
100–107Set bright background coloraixterm (not in standard)

How to get string from ASCII code in Swift?

As a character:

let c = Character(UnicodeScalar(65))

Or as a string:

let s = String(UnicodeScalar(UInt8(65)))

Or another way as a string:

let s = "\u{41}"

(Note that the \u escape sequence is in hexadecimal, not decimal)

Parse ANSI color codes and set the according color attributes for NSAttributedString

You need to approach it differently.
You cannot set attribute ranges and then modify the string in this way. That changes the range of the attribute.
There are lots of ways to do it.
An easier way to do this without getting confused is to first split the string into an array based on the matches.
Then remove the ANSI color prefix from each string in the array and apply the color.
Then join the array into one string.

Another approach would be to first convert the non-attributed string to another format.
It could be HTML or RTF for example. Then all you would be doing is converting the ANSI color tags to a format that the cocoa text system can already handle for you.

Looking for a UserControl which supports ANSI escape codes

Take a look at Dart's Vt.NET control (assuming that a VTxxx emulation is close enough to an ansi emulation).



Related Topics



Leave a reply



Submit