Making SwiftKilo Part 2: Raw mode
May 13, 2017 -In the next chapter of the tutorial, I went through the steps to enable raw mode. In a typical command line app, you print some things on the screen and maybe wait for input for the user on the next line. The terminal is setup by default to send your program this input one line at a time which means waiting until the user hits return/enter. For a text editor where we want to handle things like keyboard shortcuts and special characters, this isn't good enough. Fortunately, the terminal can be told to change its behavior and send each individual keypress to your program one at a time. Unfortunately, it takes several steps to do this. Also, since you're changing the behavior of your terminal for your program, you need to make sure you turn the settings back to normal when your program is done.
Many of the functions used to read and write input from the terminal operate on CChar's and arrays of CChars. These are ascii characters and not the same as Swift's Character which understands unicode. Technically CChar's are just 8-bit integers that happens to correlate with ascii values. C has many "types" that are basically just some kind of integer and a decent amount of the code in this app relies on not really caring about the difference. For example: in later chapters we compare a CChar (which is an Int8) to numbers like 10000 which can't be stored in an Int8 because it's entirely too large. In C this is fine, but in Swift you'll have to do a bunch of converting between Int types. I wouldn't be suprised if this fast-and-loose handling of number types in C code is the root cause of many of the exploits and crashes you hear about in the news.
In Swift, when you work with Characters, you can make new ones with the string literal like so:
let someChar: Character = "s"
There is no support by default for doing the same thing with CChars which made things a bit trickier to write in Swift. Fortunately, Swift allows you to enable creating literally anything from string literals (or other literals like nil
or 134
). I added my own fairly lazy implementation which seems to work fine for CChar:
extension CChar: ExpressibleByStringLiteral {
public typealias StringLiteralType = String
public typealias ExtendedGraphemeClusterLiteralType = Character
public typealias UnicodeScalarLiteralType = UnicodeScalar
public init(stringLiteral value: String) {
self.init(value.utf8CString[0])
}
public init(extendedGraphemeClusterLiteral value: Character) {
self.init(stringLiteral: String(value))
}
public init(unicodeScalarLiteral value: UnicodeScalar) {
self.init(stringLiteral: String(value))
}
}
Creating something from a string literal in Swift means handling unicode characters because Swift lets us make strings with values like "🕺🏾". CChar's can't represent those anyway, so I figure attempting to make a string from the literal and then grabbing the very first character is good enough. There are probably much better ways to do this.
Even with this added convenience, there are still oddities to deal with. A handy function iscntrl
can be used to determine if a character is a regular keypress or the combination of control and another key (which would be represented by a single ascii character, not by a seqence or characters). You would think a C funcion like this was made to accept CChars as input since other C functions like read
work with CChars, but you'd be wrong. iscntrl
expects an Int32 which is not a CChar. I'm not sure why this is, but fortunately you can make Int32's out of Int8's easily (though it does look a bit disappointing when you read the code later).
At this stage, the app doesn't do much but print in the console whether a key is a control character or a printable letter each time a user presses a button. To get this far though, you'll need to learn about terminal configuration with termios
and gain at least a general understanding of string encoding. I knew about strings personally, but had no idea about the rest, so it was fun for me to learn. At this point in my project, I made my first commit, you can see it on github. Tune in next time for more fun with low level Swift!