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
Code | Effect | Note |
---|---|---|
0 | Reset / Normal | all attributes off |
1 | Bold or increased intensity | |
2 | Faint (decreased intensity) | Not widely supported. |
3 | Italic | Not widely supported. Sometimes treated as inverse. |
4 | Underline | |
5 | Slow Blink | less than 150 per minute |
6 | Rapid Blink | MS-DOS ANSI.SYS; 150+ per minute; not widely supported |
7 | [[reverse video]] | swap foreground and background colors |
8 | Conceal | Not widely supported. |
9 | Crossed-out | Characters legible, but marked for deletion. Not widely supported. |
10 | Primary(default) font | |
11–19 | Alternate font | Select alternate font n-10 |
20 | Fraktur | hardly ever supported |
21 | Bold off or Double Underline | Bold off not widely supported; double underline hardly ever supported. |
22 | Normal color or intensity | Neither bold nor faint |
23 | Not italic, not Fraktur | |
24 | Underline off | Not singly or doubly underlined |
25 | Blink off | |
27 | Inverse off | |
28 | Reveal | conceal off |
29 | Not crossed out | |
30–37 | Set foreground color | See color table below |
38 | Set foreground color | Next arguments are 5;<n> or 2;<r>;<g>;<b> , see below |
39 | Default foreground color | implementation defined (according to standard) |
40–47 | Set background color | See color table below |
48 | Set background color | Next arguments are 5;<n> or 2;<r>;<g>;<b> , see below |
49 | Default background color | implementation defined (according to standard) |
51 | Framed | |
52 | Encircled | |
53 | Overlined | |
54 | Not framed or encircled | |
55 | Not overlined | |
60 | ideogram underline | hardly ever supported |
61 | ideogram double underline | hardly ever supported |
62 | ideogram overline | hardly ever supported |
63 | ideogram double overline | hardly ever supported |
64 | ideogram stress marking | hardly ever supported |
65 | ideogram attributes off | reset the effects of all of 60-64 |
90–97 | Set bright foreground color | aixterm (not in standard) |
100–107 | Set bright background color | aixterm (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
Deleterowsatindexpaths Crashing
Avaudioplayer.Play() Works But Avaudioplayernode.Play() Fails
What Are 3 Items in a Swift Method Parameter For
Checkboxes in Uitableview State Persistence
How to Use Querystartingatvalue in My Searchcontroller to Search for Users
How to Make That an Object of the Same Class Become the Return Value of Initializer
Value' Is Inaccessible Due to 'Internal' Protection Level
Swift Optional Property Using Kvc Causes Crash
Error: Couldn't Irgen Expression, No Additional Error
Can't Use 'Shape' as Type in Swiftui
Elegant 'Bounded' Methodology in Swift
Retrieve an Image from Firebase to an Uiimage Swift5
Failable Initializers with Codable
How to Figure Out the Day Difference in the Following Example