Terminal Component - TJohn2017/Laputa GitHub Wiki

Terminal View

Third party library for terminal emulator: SwiftTerm

The bulk of the terminal emulator was already implemented by the SwiftTerm library. SwiftTerm provides the TerminalView and TerminalViewDelegate, classes which provide base terminal emulation and have been extended to provide custom functionality for Laputa. We implemented the protocols for TerminalView and TerminalViewDelegate in our own UIView controller. Additionally, we needed to combine SwiftTerm's terminal emulation with the SSH connection functionality provided by the SSHConnection class (implemented with NMSSH) in order to connect to hosts.

The high-level object that is considered the "Terminal Component" is the SwiftUITerminal View. This is the view that is utilized by Session Page and manages all of the other terminal components within it.

SSHTerminalView

The SSHTerminalView class is a UIKit UIView that owns and manages a single SSH connection while displaying its terminal output using the terminal emulation of SwiftTerm. To establish its connection the SSHTerminalView creates and stores as a member variable a SSHConnection object. Then, in order to receive data from this connection object the class implements the NMSSHChannelDelegate protocol, which exposes didReadData and didReadErrorFunctions. These functions supply the data that is ingested by the terminal emulator. This class also implements the TerminalViewDelegate protocol which exposes functions for managing UI attributes of the terminal such as the title, size, and current directory as well as providing the send function which is used to flush bytes over the SSH connection as they are typed by the user.

SSHTerminalViewController

The SSHTerminalViewController class extends the functionality of the SSHTerminalView and controls it's UI attributes (it constructs the frame which determines size and location). Specifically, it creates the buttons present on the terminal (output catching, keyboard toggle), handles resizing, handles keyboard presentation, implements the gestures which control output catching (for code cards) and scrolling, and displays a custom error view on connection failures. We will further discuss the details of gesture implementation below in order to explain how we went about creating our most interesting extensions: output catching/code card creation and scrolling.

Touch gesture interaction

Capture terminal output

Capturing terminal output allows users to highlight a selection of rows from their terminal which will be saved using Core Data so that the selected output can be displayed on the attached canvas (required) in a draggable and resizable card which we refer to as a code card. In order to handle toggling in and out of output catching mode we simply track a boolean member variable which is toggled when pressing the button. Then, in order to implement the highlight and save process we attach a pan gesture recognizer to the view. When a user begins a pan gesture we save the initial location to a member variable. As the user drags we receive periodic updates to the pan gesture in which we use the initial drag point as well as the translation data to calculate the range of y values in pixels that the user has dragged over. We translate these values to y coordinates (row index) in terms of number of characters and visually highlight these rows during the pan gesture. Then, when a pan gesture is complete we do the same calculations on the range again and fetch these lines from the underlying terminal engine (using the getTerminal() function provided by SwiftTerm). These lines are turned into one content string and are then saved using Core Data in a CodeCard object which is affiliated with the current canvas so that they are immediately displayed to the user. Finally, highlighting is removed and output catching is toggled back off.

Scrolling

SwiftTerm provides api call to scroll the terminal up (terminalView.scrollUp(lines: int)) or down (terminalView.scrollDown(lines: int)). We attached a pan gesture to the terminal view controller. If we weren't capturing lines of terminal to be put into a code card, pan gesture will recognize it as scrolling.

Additional Features

Keyboard

Keyboard appears through the tap of a button. Allows user to bring up the keyboard in order to type into the terminal when necessary.

SwiftUITerminal

UIViewControllerRepresentable that wraps our TerminalViewController. This is the object used in SwiftUI views i.e. in the Session Page component. Passes down important information to the SSHTerminalViewController such as the desired height of the terminal view. Also responsible for managing static copies of any view controllers that correspond to active connections in the current session in order to support multiple terminal functionality (see above).

Features

Multiple terminals

SwiftUITerminal is partially made responsible for maintaining multiple terminal functionality. At the moment multiple terminal functionality works by having SwiftUITerminal maintain a static var array of any SSH Terminal view controllers that have actively used by any active connections in the current session. When a user switches off from one terminal to another, this static var ensures that their current state gets preserved and not reset when they switch back. The actual multiple-terminal / terminal-tab var is handled by SessionPage and helper components.