Add TerminalBuffer & TerminalCursor, parse \n \r \b, and more escape codes#85
Add TerminalBuffer & TerminalCursor, parse \n \r \b, and more escape codes#85ian-mcdowell wants to merge 21 commits into
Conversation
| /// return an attributed string that renders the character | ||
| private func handle(_ character: Character) -> (output: NSAttributedString?, didEnd: Bool) { | ||
| // | ||
| // swiftlint:disable cyclomatic_complexity |
There was a problem hiding this comment.
I decided to disable the swiftlint error in this method instead of splitting it up into smaller methods. This method basically models a state machine, and doesn't do much other than change states and call delegate methods. It would, in my opinion, reduce readability a lot if I were to move the inner switches into methods and call those.
@louisdh thoughts?
| struct ANSITextState { | ||
| var foregroundColor: UIColor = UserDefaultsController.shared.terminalTextColor | ||
| var backgroundColor: UIColor = UserDefaultsController.shared.terminalBackgroundColor | ||
| var backgroundColor: UIColor = .clear |
There was a problem hiding this comment.
Now, switching background colors while terminal is open will not keep remnants of the background color visible where text was printed.
|
Tests were added for both new classes. The |
|
One regression I didn't address here: The OutputSanitizer (replace Does anyone have feedback on what should be done? Is that replacement necessary? We could sanitize after every character is printed, but that seems too expensive. |
|
The OutputSanitizer is quite necessary for a nice user experience in my opinion. On top of that, it might be required for App Store approval. I'm speculating here, but Apple may not like an app displaying paths containing "/private/var/mobile/...". |
|
Great work btw, making OpenTerm behave more like established terminals is a nice improvement. Also good to see some tests. |
|
Thanks! Assuming that the sanitizer won't ever need to replace multiple lines at once, we could avoid displaying each character as it comes in. Whenever we receive \r, \n, etc, or the input ends, run the sanitizer then display the full line . |
|
This is ready to merge now.
|
|
Two unit tests are failing in the latest commit of this branch: |
6e4801e to
d09ebcd
Compare
|
Sorry about that, tests were broken with OutputSanitizer change. They are back up now 🙂 |
* Added libtext.dylib to OpenTerm * Fixed share stdin
300b802 to
55804dc
Compare
|
Is this good to merge? |
|
We will need additional validation before it ships, as it may break some commands. Lets keep it in this branch for a while, until I have more confidence in its correctness. |
|
Now that OpenTerm 2.0 is done, it would be great to get this PR up to date. Would it be possible to fix all these conflicts? |
|
I’ll take a look ASAP! |
This change adds another layer between the
ParserandTerminalTextViewcalled theTerminalBuffer(referred to as the "buffer"). The buffer contains an NSTextStorage (NSMutableAttributedString subclass) that theTerminalTextViewreads from.The buffer is responsible for adding new text that comes in to the proper position in the text storage. It uses the
TerminalCursor(referred to as the "cursor") to keep track of where it needs to insert new text.Parsing
The buffer is now the Parser's delegate, and receives events from the Parser, some of which are:
\n)\r)Depending on what the Parser sends, the buffer will update the cursor's position and/or modify the text storage.
Cursor
The cursor (different than the UITextView's cursor), keeps track of a position within the text storage. It's only valid for a single text storage. It has a readable x,y coordinate, as well as an offset (current distance from the beginning of the storage).
The cursor contains mutating methods that adjust its state, and is not able to be modified except through those methods. Every time the x, y, or offset change, it makes sure to keep the other values in sync.
Example
In this example, I'll demonstrate what happens when a carriage return is received while parsing.
At the beginning, the storage contains a 2-line string, and the cursor is right after the last character of the first word:
Now, when a carriage return is received, the
parserDidReceiveCarriageReturn(_:)method is called by theParser. After this is handled, here is the state:Now, if more text comes in from the parser - "bonjour", here is the end state: