This crux of this module is the Terminal class which is a pure-Python implementation of a quintessential Unix-style terminal emulator. It actually does its best to emulate an xterm. This means it supports the majority of the relevant portions of ECMA-48. This includes support for emulating varous VT-* terminal types as well as the "linux" terminal type.
The Terminal class's emulation support is not complete but it should suffice for most terminal emulation needs. If additional support for certain escape sequences or modes are required please feel free to provide a patch or to simply ask for something to be added.
Note that Terminal was written from scratch in order to be as fast as possible. Comments have been placed where different implementations/development patterns have been tried and ultimately failed to provide speed improvements. Any and all suggestions or patches to improve speed (or emulation support) are welcome!
Without any special mode settings or parameters, Terminal should be able to support most applications under the following terminal types (e.g. "export TERM=<terminal type>"):
- xterm (the most important one)
- ECMA-48/ANSI X3.64
- Nearly all the VT-* types: VT-52, VT-100, VT-220, VT-320, VT-420, and VT-520
- Linux console ("linux")
The Terminal class is meant to emulate the display portion of a given terminal. It does not translate keystrokes into escape sequences or special control codes--you'll have to take care of that in your application (or at the client-side like Gate One). It does, however, keep track of many keystroke-specific modes of operation such as Application Cursor Keys and the G0 and G1 charset modes with callbacks that can be used to notify your application when something changes.
Many methods inside Terminal start with an underscore. This was done to indicate that such methods shouldn't be called directly (from a program that imported the module). If it was thought that a situation might arise where a method could be used externally by a controlling program, the underscore was omitted.
To support asynchronous usage (and make everything faster), Terminal was written to support extensive callbacks that are called when certain events are encountered. Here are the events and their callbacks:
Callback | Called when... |
---|---|
Terminal.CALLBACK_SCROLL_UP | The terminal is scrolled up (back). |
Terminal.CALLBACK_CHANGED | The screen is changed/updated. |
Terminal.CALLBACK_CURSOR_POS | The cursor position changes. |
Terminal.CALLBACK_DSR | A Device Status Report (DSR) is requested (via the DSR escape sequence). |
Terminal.CALLBACK_TITLE | The terminal title changes (xterm-style) |
Terminal.CALLBACK_BELL | The bell character (^G) is encountered. |
Terminal.CALLBACK_OPT | The special optional escape sequence is encountered. |
Terminal.CALLBACK_MODE | The terminal mode setting changes (e.g. use alternate screen buffer). |
Note that Terminal.CALLBACK_DSR is special in that it in most cases it will be called with arguments. See the code for examples of how and when this happens.
Also, in most cases it is unwise to override Terminal.CALLBACK_MODE since this method is primarily meant for internal use within the Terminal class.
Gate One makes extensive use of the Terminal class and its callbacks. So that's a great place to look for specific examples (gateone.py and termio.py, specifically). Having said that, implementing Terminal is pretty straightforward:
>>> import terminal
>>> term = terminal.Terminal(24, 80)
>>> term.write("This text will be written to the terminal screen.")
>>> term.dump()
[u'This text will be written to the terminal screen. ',
<snip>
u' ']
Here's an example with some basic callbacks:
>>> def mycallback():
... "This will be called whenever the screen changes."
... print("Screen update! Perfect time to dump the terminal screen.")
... print(term.dump()[0]) # Only need to see the top line for this demo =)
... print("Just dumped the screen.")
>>> import terminal
>>> term = terminal.Terminal(24, 80)
>>> term.callbacks[term.CALLBACK_CHANGED] = mycallback
>>> term.write("This should result in mycallback() being called")
Screen update! Perfect time to dump the terminal screen.
This should result in mycallback() being called
Just dumped the screen.
Note
In testing Gate One it was determined that it is faster to perform the conversion of a terminal screen to HTML on the server side than it is on the client side (via JavaScript anyway).
The Terminal class implements a scrollback buffer. Here's how it works: Whenever a scroll_up() event occurs, the line (or lines) that will be removed from the top of the screen will be placed into Terminal.scrollback_buf. Then, whenever dump_html() is called, the scrollback buffer will be returned along with the screen output and reset to an empty state.
Why do this? In the event that a very large write() occurs (e.g. 'ps aux'), it gives the controlling program the ability to capture what went past the screen without some fancy tracking logic surrounding Terminal.write().
More information about how this works can be had by looking at the dump_html() function itself.
Note
There's more than one function that empties Terminal.scrollback_buf when called. You'll just have to have a look around =)
Terminal controller class.
Fills self.screen with empty lines of (unicode) spaces using self.cols and self.rows for the dimensions.
NOTE: Just because each line starts out with a uniform length does not mean it will stay that way. Processing of escape sequences is handled when an output function is called.
Fills self.renditions with lists of None using self.cols and self.rows for the dimenions.
Resets the terminal back to an empty screen with all defaults.
Resizes the terminal window, adding or removing rows or columns as needed.
Sets self.title to title and executes self.callbacks[self.CALLBACK_TITLE]()
Saves the cursor position and current rendition settings to self.saved_cursorX, self.saved_cursorY, and self.saved_rendition
Restores the cursor position and rendition settings from self.saved_cursorX, self.saved_cursorY, and self.saved_rendition (if they're set).
Sets the terminal's G0 (default) charset to the type specified by char
NOTE: Doesn't actually do anything other than set the variable.
Sets the terminal's G1 (alt) charset to the type specified by char
NOTE: Doesn't actually do anything other than set the variable.
Write chars to the terminal at the current cursor position advancing the cursor as it does so. If chars is not unicode, it will be converted to unicode before being stored in self.screen.
Only here to make Terminal compatible with programs that want to use file-like methods.
Scrolls up the terminal screen by n lines (default: 1). The callbacks CALLBACK_CHANGED and CALLBACK_SCROLL_UP are called after scrolling the screen.
NOTE: This will only scroll up the region within self.top_margin and self.bottom_margin (if set).
Scrolls down the terminal screen by n lines (default: 1). The callbacks CALLBACK_CHANGED and CALLBACK_SCROLL_DOWN are called after scrolling the screen.
If alt is True, copy the current screen and renditions to self.alt_screen and self.alt_renditions then re-init self.screen and self.renditions. If alt is False, restore the saved screen buffer and renditions then nullify self.alt_screen and self.alt_renditions.
Same as self.alternate_screen_buffer but saves/restores the cursor location.
Turns on or off local echo dependong on the value of onoff
self.local_echo = onoff
DCH - Deletes (to the left) the specified number of characters at the cursor position. As characters are deleted, the remaining characters between the cursor and right margin move to the left. Character attributes (renditions) move with the characters. The terminal adds blank spaces with no visual character attributes at the right margin. DCH has no effect outside the scrolling margins.
NOTE: Deletes renditions too.
ESCnH CUP (Cursor Position). Move the cursor to the given coordinates.
coordinates: Should be something like, 'row;col' (1-based) but, 'row', 'row;', and ';col' are also valid (assumes 1 on missing value).
If coordinates is '', the cursor will be moved to the top left (1;1).
Vertical Line Position Absolute (VPA) - Moves the cursor to given line.
CSI*n*J ED (Erase Data). This escape sequence uses the following rules:
Esc[J Clear screen from cursor down ED0
Esc[0J Clear screen from cursor down ED0
Esc[1J Clear screen from cursor up ED1
Esc[2J Clear entire screen ED2
CSI*n*K EL (Erase in Line). This escape sequence uses the following rules:
Esc[K Clear screen from cursor right EL0
Esc[0K Clear screen from cursor right EL0
Esc[1K Clear screen from cursor left EL1
Esc[2K Clear entire line ED2
Sets the values the dict, self.leds depending on n using the following rules:
Esc[0q Turn off all four leds DECLL0 Esc[1q Turn on LED #1 DECLL1 Esc[2q Turn on LED #2 DECLL2 Esc[3q Turn on LED #3 DECLL3 Esc[4q Turn on LED #4 DECLL4
Dumps the terminal screen as a list of HTML-formatted lines.
Note: This places <span class="cursor">(current character)</span> around the cursor location.
Dumps the screen and the scrollback buffer as-is then empties the scrollback buffer.