Reading ANSI code response value

console
ansi
stdio

(Mr Bee) #1

Hi all,

I'm playing around with ANSI code (ESC sequence) using Swift as a console app. Sending an ESC command is trivial, such as setting text color. However, reading a response value from an ESC command is challenging. Here's a simple test program:

print("Get cursor position:", "\u{1b}[6n", terminator: "")
let s = readLine()!
print(s)

The program sends <ESC>[6n to get the current cursor position and the console would return <ESC>[<line>;<column>R string. Here are the problems:

  1. readLine() keeps waiting for input until user press Return or Enter key. I thought it will automatically stop reading once the input buffer gets empty.
  2. readLine() strangely doesn't seem to read the response value from the console although it's clearly printed on the screen. What's is happening?
  3. The response value is printed on the console. I'd like to have it silently, like the way print() prints the ESC command. Is there a way to temporarily redirect standard input into a variable?

System:
• MacOS Mojave
• XCode 10
• Swift 4.2
• run on Terminal app

I've been looking at GitHub and Google to find some answers, but I got no luck. So, would anyone here give me a hint where to start solving this problem? Thank you.

Regards,

~Bee.


(Vincent Duvert) #2

Hi,

You need to set the terminal to “cbreak mode” instead of “line editing mode” so your program can read the characters entered on the keyboard (or in your case, generated by the terminal) without having to wait for a newline character to be entered. See for instance:

https://blog.nelhage.com/2009/12/a-brief-introduction-to-termios-termios3-and-stty/

This involves calling the tcgetattr/tcsetattr C functions to temporarily change the terminal attributes (in your case, you’d need to clear the ICANON and ECHO flags from the c_lflag attributes of the termios structure). If you import Darwin you should be able to call these functions from your Swift code.

Unfortunately, this will not be sufficient because readLine only returns once it reads a newline from standard input, so you need to use something else. You may be able to use the read C function for this (or maybe getchar, if you’re fine reading character values one-by-one and you know when to stop reading).

There may also be some issues with output buffering; IIRC print(...) output is buffered until a newline is printed, so the terminal will not have received the command when you start waiting for it. To fix this, add a fflush(stdout) call after the call to print.


(Mr Bee) #3

Thank you, I'll take a look at your recommended article.

So, it's not as easy as I thought. No wonder I couldn't find any usable answer or solution on Google and Github.