Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Update current line with command line tool in Swift

I built a OS X command line tool in Swift (same problem in Objective-C) for downloading certain files. I am trying to update the command line with download progress. Unfortunately, I cannot prevent the print statement to jump to the next line.

According to my research, the carriage return \r should jump to the beginning of the same line (while \n would insert a new line).

All tests have been performed in the OS X Terminal app, not the Xcode console.

let logString = String(format: "%2i%% %.2fM \r", percentage, megaBytes)
print(logString)

Still, the display inserts a new line. How to prevent this?

like image 844
Mundi Avatar asked Dec 01 '22 18:12

Mundi


1 Answers

Note: This won't work in Xcode's debugger window because it's not a real terminal emulator and doesn't fully support escape sequences. So, to test things, you have to compile it and then manually run it in a Terminal window.

\r should work to move to the beginning of the current line in most terminals, but you should also take a look at VT100 Terminal Control Escape Sequences. They work by sending an escape character, \u{1B} in Swift, and then a command. (Warning: they make some pretty ugly string definitions)

One that you'll probably need is \u{1B}[K which clears the line from the current cursor position to the end. If you don't do this and your progress output varies in length at all, you'll get artifacts left over from previous print statements.

Some other useful ones are:

  • Move to any (x, y) position: \u{1B}[\(y);\(x)H Note: x and y are Ints inserted with string interpolation.
  • Save cursor state and position: \u{1B}7
  • Restore cursor state and position: \u{1B}8
  • Clear screen: \u{1B}[2J

You can also do interesting things like set text foreground and background colors.

If for some reason you can't get \r to work, you could work around it by saving the cursor state/position just before you print your logString and then restoring it after:

let logString = String(format: "\u{1B}7%2i%% %.2fM \u{1B}8", percentage, megaBytes)
print(logString)

Or by moving to a pre-defined (x, y) position, before printing it:

let x = 0
let y = 1 // Rows typically start at 1 not 0, but it depends on the terminal and shell
let logString = String(format: "\u{1B}[\(y);\(x)H%2i%% %.2fM ", percentage, megaBytes)
print(logString)
like image 116
Mike S Avatar answered Dec 04 '22 05:12

Mike S