Module tty

Terminal-related facilities.

Keyboard keys

keycode_to_keyname(keycode) Converts a keycode to a keyname.
keyname_to_keycode(keyname) Converts a keyname to a keycode.

Drawing and Refreshing the Screen

get_canvas() Returns a canvas object encompassing the whole screen.
redraw() Redraws all the screen’s contents.
refresh() Refreshes the screen.

Styles

destruct_style(style) Destructures a style.
is_color() Whether it’s a color terminal.
is_hicolor() Whether it’s a rich color terminal.
style(spec) Creates a style.

Text handling

text_align(s, width, align_mode) Aligns (“justifies”) a string.
text_cols(s, i [, j]) Returns a “visual” substring of a string.
text_width(s) Calculates a string’s “visual” width.

Misc functions

beep() Sounds a beep.
get_cols() Returns the terminal width, in characters.
get_rows() Returns the terminal height, in lines.
is_idle() Whether the terminal is idle.
is_ui_ready() Whether the UI is ready.
skin_change(skin_name[, force]) Changes the active skin.
skin_get(property_name, default) Fetches a skin’s property.

Encodings

conv(s, encoding_name) Converts a string to the terminal’s encoding.
is_utf8() Whether the terminal is UTF-8 encoded.


Keyboard keys

Usually we, as end-users, don’t need to handle keys. In a few cases, however, for example when working with a ui.Custom, we want to interact with the keys.

A key —for example q, R, Control-S, F4, ESC— is represented as a number. We call this number a keycode. We, as humans, would like to deal not with such a number but with a descriptive name. We call this name a keyname.

The TTY module provides us with two functions to deal with keys.

The foremost function is keyname_to_keycode, which translates a keyname to a keycode. The other function, keycode_to_keyname, does the opposite.

keycode_to_keyname(keycode)
Converts a keycode to a keyname.

Throws an exception if the keycode is invalid.

Returns two values: the key’s “short” name, and its “long” name.

keyname_to_keycode(keyname)
Converts a keyname to a keycode.

Throws an exception if the keyname is invalid.

See use example at ui.Custom:on_key.

Drawing and Refreshing the Screen

It’s useful to know a bit about how things are drawn on the screen, especially if you're programming with timers.

MC is a curses application. In such applications drawing (others may call it “painting”) is made to a virtual screen, which is simply a memory buffer in the program. This virtual screen is then written out to the physical screen.

This two-stage process helps curses minimize the amount of data transferred to the physical screen. Only the smallest region (usually a rectangle) in the virtual screen that actually differs from the contents of the physical screen is written out.

Likewise in Lua. Any function (or method) dealing with the screen belongs to one, and one only, of the stages.

Functions belonging to the drawing stage have “redraw” in their names. They don’t affect the physical screen, only the virtual one. We'll call this the drawing stage.

Functions belonging to the second stage, which affects the physical screen, have “refresh” in their names. We'll call this the refresh stage.

Here’s a summary of the functions (or method) dealing with the screen:

The drawing stage

The refresh stage

  • tty.refresh

    Copies the virtual screen onto the physical one.

  • dialog:refresh

    A utility function that also positions the cursor.

When to call which function?

You may feel overwhelmed by the many functions you have at your hands. Don’t feel so.

In normal code you won’t need to call any of them. When you set a property of some widget, its :redraw() method will be called automatically. Then, as part of MC’s event loop, tty.refresh will be called. So the screen will be updated appropriately without your explicit intervention.

On the other hand, when using a ui.Custom you have to call its :redraw() yourself whenever its state changes in a way that affects its display because only you know when this happens.

Working with timers

We explained that you normally don’t need to call tty.refresh yourself as this is done automatically. One exception, however, is when working with timers. If your timed function affects the display (for example, if it updates some widget, as in label.text = "new label") then you need to call tty.refresh (or dialog:refresh) yourself to refresh the screen:

timer.set_timeout(function()
  label.text = label.text .. "!"
  dlg:refresh()
  -- dialog:refresh() is like tty.refresh() except that
  -- it also puts the cursor at the focused widget. Had
  -- we called tty.refresh() instead, the cursor would have
  -- appeared at the last widget to draw itself (the label).
end, 1000)

-- another example:

timer.set_timeout(function()
  alert('hi')
  tty.refresh()
end, 1000)

The reason for this is the way MC’s event loop works. Here’s a schema of it:

event_loop:
  repeat:
    (A) while there's no keyboard or mouse events:
          execute timers
    (B) get keyboard or mouse event
    (C) process the event
    (D) position the cursor at the focused widget
    (E) refresh the screen

When our timer function returns, MC still sits there waiting (loop (A)) for a keyboard (or mouse) event. Step (E) isn’t arrived at, and hence the screen isn’t refreshed. That’s why we need to refresh the screen explicitly from our timer function.

The previous paragraph gave a “schema” of MC’s event loop. Here’s an overview of the actual C code, for interested programmers:

dlg_run() {
  dlg_init() {
    dlg_redraw()
  }
  frontend_dlg_run() {
    while (dlg->state == DLG_ACTIVE) {
      update_cursor(dlg)
      event = tty_get_event() {
        mc_refresh()
        while (no keyboard or mouse event) {
          execute pending timeouts
        }
        read event
      }
      dlg_process_event(dlg, event)
    }
  }
}

(You'll notice that, in the while loop, steps (D) and (E) actually are the first to happen, but that’s insignificant.)

get_canvas()
Returns a canvas object encompassing the whole screen.

This lets you draw on the screen. Alternatively you can use the :get_canvas() method of a widget if you're interested in its limited screen area.

redraw()

Redraws all the screen’s contents.

All the dialogs on screen are redrawn, from the bottom to the top.

You'll hardly ever need to use this function yourself. This documentation entry exists because this function is used, once, in the implementation of the ui module: It’s used to redraw the screen after a dialog box is closed. Except for achieving some “pyrotechnics”, as demonstrated below, there’s little to no reason to use this function.

keymap.bind('C-y', function()
  local dlg = ui.Dialog()
  local btn = ui.Button(T"Move this dialog to the right")
  btn.on_click = function()
    dlg:set_dimensions(dlg.x + 1, dlg.y)
    tty.redraw() -- comment this out to see what happens.
  end
  dlg:add(btn):run()
end)
refresh()
Refreshes the screen.

This copies the virtual screen onto the physical one.

Only the regions that are known to differ from the target are copied.

Styles

The way a character is displayed on the screen is called a style. A style is composed of three things:

  • Foreground color
  • Background color
  • Attributes: underlined, italic, bold and/or reversed.

A style happens to be represented internally as a numeric handle. For example, on your system the style 64 may mean “red foreground, green background, italic.” We, as humans, don’t manipulate such numbers directly but instead use style() to convert a human-readable style description to this number.

destruct_style(style)

Destructures a style.

Does the opposite of tty.style. Given a style, returns a table with the fields fg, bg, attr (and their numeric counterparts).

You'll not normally use this function. It can be used to implement exotic features like converting an ui.Editbox syntax-highlighted contents into HTML, or creating “screen shots”.

ui.Editbox.bind('C-y', function(edt)
  devel.view(tty.destruct_style(
    edt:get_style_at(edt.cursor_offs)
  ))
end)
is_color()
Whether it’s a color terminal.

Return true if the terminal supports colors, or false if it’s a monochrome terminal.

is_hicolor()
Whether it’s a rich color terminal.

Return true if the terminal supports 256 colors (in this case is_color too will return true), or false otherwise.

style(spec)
Creates a style.

The argument for this function is a description of a style. The function “compiles” this description and returns an integer which can be used wherever a style value is required.

There are three ways to describe a style.

(1) As a string with the three components (foreground color, background color, attributes — in this order) separated by commas or semicolons:

local style = tty.style('white, red; underline')
local style = tty.style('white; red, underline')
local style = tty.style('white, red, underline')

Type mc --help-color at the shell to see the valid names.

Any of the components may be omitted:

local style = tty.style('white, red')
local style = tty.style(', red')
local style = tty.style(',, underline')
local style = tty.style('')

(2) As a string naming a skin property:

local style = tty.style('editor.bookmark')

(3) You may also specify several style in a table, keyed by the terminal type:

local style = tty.style {
  mono = "reverse",
  color = "yellow, green",
  hicolor = "rgb452, rgb111"
}
-- 'hicolor' is for 256 color terminals.

For examples of using styles, see ui.Canvas and ui.Editbox:bookmark_set.

There is a limit to the number of styles that can be allocated. Generally, you'll be able to allocate around two hundred. An exception will be raised when you reach the limit.

If the same style description is used again and again, it will be allocated only once.

When the user changes the skin, the numbers returned by tty.style, if you cached them in some variables, become invalid. You can solve this problem by resetting your variables on the ui::skin-change event, as demonstrated in ui.Canvas:set_style. (Of course, restarting Lua too would solve the problem.)

Text handling

text_align(s, width, align_mode)

Aligns (“justifies”) a string.

Fits a string to width terminal columns by padding it with spaces or trimming it.

align_mode may be:

  • “left”
  • “right”
  • “center”
  • “center or left”

if the string is wider than width, the excess it cut off. You may instead append “~” to the align mode to shorten the string by replacing characters from its middle with a tilde character.

assert(tty.text_align("Alice", 10, "left") == "Alice     ")
assert(tty.text_align("Alice", 10, "right") == "     Alice")
assert(tty.text_align("Alice", 10, "center") == "  Alice   ")

assert(tty.text_align("Alice in Wonderland", 10, "left") == "Alice in W")
assert(tty.text_align("Alice in Wonderland", 10, "left~") == "Alice~land")
assert(tty.text_align("Alice in Wonderland", 10, "center") == "")
-- "center or left" means to center if there's enough room, and align
-- to left otherwise.
assert(tty.text_align("Alice in Wonderland", 10, "center or left")
        == "Alice in W")

-- Multiple lines are not supported:
assert(tty.text_align("one\ntwo", 8, "left") == "one.two ")
text_cols(s, i [, j])
Returns a “visual” substring of a string.

Given a string in the terminal’s encoding, returns the substring falling within certain screen columns.

The arguments to this function are the same as string.sub’s. Indeed, you can think of this function as equivalent to string.sub except that the indices are interpreted to be screen columns instead of bytes.

(See discussion at tty.text_width.)

assert(tty.text_cols('ンab᷉c᷉d', 4, 5) == 'b᷉c᷉')

-- ...and now, assuming this is a UTF-8 encoded source file, compare
-- this with string.sub(), which is oblivious to characters and
-- their properties:
assert(string.sub('ンab᷉c᷉d', 4, 5) == 'ab')

(If the terminal’s encoding isn’t UTF-8, this function is identical to string.sub.)

If you want to draw part of a string on screen, use ui.Canvas:draw_clipped_string, which does the “hard” calculations for you.

text_width(s)

Calculates a string’s “visual” width.

Given a string in the terminal’s encoding, returns the amount of columns needed to display it.

While in English there’s a one-to-one correspondence between characters and columns, in other languages this isn’t always so. E.g., diacritic characters consume 0 columns and Asian characters consume 2 columns.

assert(tty.text_width 'ンab᷉c᷉d' == 6)

-- ...and now, assuming this is a UTF-8 encoded source file, compare
-- this with string.len(), which is oblivious to characters and
-- their properties:
assert(string.len 'ンab᷉c᷉d' == 13)

(If the terminal’s encoding isn’t UTF-8, this function is identical to string.len (except for handling multiple lines).)

if the string contains multiple lines, the width of the widest line is returned. Also returned is the number of lines:

assert(tty.text_width "once\nupon\na time" = 6)
assert(select(2, tty.text_width "once\nupon\na time") = 3)

Misc functions

beep()
Sounds a beep.
get_cols()
Returns the terminal width, in characters.
get_rows()
Returns the terminal height, in lines.
is_idle()
Whether the terminal is idle. That is, whether there are no pending keyboard or mouse events.

This function can be used, for example, to early exist lengthy drawing tasks and thereby collapsing them into a final one, when the terminal becomes idle again. See examples at dialog-drag.lua and ruler.lua.

Caveat: you're likely to misuse this function.

Usually it'd be timer.debounce, not is_idle, what you're looking for.

The terminal can be “idle” even when a keyboard key is held down (console applications are oblivious to the physical state of the key — they only see the characters sent to them at the keyboard repeat rate). is_idle, therefore, should only be used when a “throttle” pattern is desired, not a “debounce” pattern.

is_ui_ready()
Whether the UI is ready.

It tells us if curses has taken control of the terminal. This is when you can use dialog boxes.

The terminal can be in one of two states:

  • When MC just starts, the terminal is in the so-called cooked mode. It’s the mode most Unix command-line utilities work in, where the terminal behaves like a line-printer.

This is also the initial state for standalone mode.

  • Soon afterwards curses (or slang) takes control of the terminal. The application gets control of the whole area of the screen and displays its data in dialog boxes.

The second state is when we say “the UI is ready”, or “UI mode”. The first state is “non-UI mode”.

When MC loads your user scripts it does this very early, still in non-UI mode. This is why you can’t call functions like tty.style at the top-level of your user scripts.

skin_change(skin_name[, force])

Changes the active skin.

Example:

-- There are three ways to specify a skin.

ui.Panel.bind('C-q', function()

  tty.skin_change('gotar')

  -- Or you may include the '.ini' suffix:
  tty.skin_change('gotar.ini')

  -- Or: if your skin file isn't located in one of the
  -- predefined places, you can use it by providing
  -- an absolute path (but not relative!) to it:
  tty.skin_change('/usr/share/mc/skins/gotar.ini')

end)

Here’s how to change the skin depending on which directory you're in:

-- Use a special skin when we're in the /etc tree.

local function on_chdir(pnl)
  if pnl == ui.Panel.current then  -- <<load>> is fired for the "other" (inactive) panel as well.
    if pnl.dir:find '^/etc' then
      tty.skin_change('gotar')
    else
      tty.skin_change('default')
    end
  end
end

ui.Panel.bind("<<load>>", on_chdir)      -- When the user navigates between directories.
ui.Panel.bind("<<activate>>", on_chdir)  -- When the user switches between panels.

-- Note: if you're interested in this idea, see the 'dynamic-skin'
-- module, which is more robust.

Parameters:

  • skin_name The name of the skin (with or without the ending ‘.ini’), or an absolute path to a skin file.
  • force Normally the function only changes the skin if it’s not already the active one. This saves you from worrying about performance yourself if you call the function frequently. But you can pass true as the force flag to always change the skin (useful if, for example, you've edited it on disk and want to reload it). (optional)

Returns:

    Returns the name of the skin now active. It should match the name of the skin you fed this function, unless some error occurred. You may call this function with a nil skin_name if you're only interested in the active skin name.

    Changing the skin while in the editor will jumble up the syntax-highlighting colors. (Exiting and re-entering the editor fixes this.)

skin_get(property_name, default)
Fetches a skin’s property.

Example:

tty.skin_get('invasion-from-mars.missile', '>===))>')

tty.skin_get('chess.white-knight', tty.is_utf8() and '♘' or 'N')

tty.skin_get('Lines.horiz', '-')

Skin files are, by convention, encoded in UTF-8, and the properties read from them are converted to the terminal’s encoding. Therefore you can directly use them in the UI (in widgets' data and drawing functions): there’s no need to re-encode them first.

Parameters:

  • property_name A string of the form group.name.
  • default A value to return if the property wasn’t found.

Encodings

Whenever you output a string to the terminal —for example, when you pass it to a function like alert() or to widgets like ui.Label, ui.Listbox— you should convert it first to the terminal’s encoding.

This is the result of not living in a perfect world: The file you're editing, or a string you read from some data file, may be of a different encoding than your terminal’s. For example, your terminal’s encoding may be UTF-8 whereas your data may be encoded in ISO-8859-8.

This isn’t really an issue on modern systems where everything is encoded in UTF-8. It'd be legitimate for you, therefore, to decide to ignore this issue —especially if you're the only user of your script. Nevertheless, you should at least be aware of this issue in order to support your user-base.

There are two ways to convert a string to the terminal’s encoding:

(1) When you know the string’s encoding, use tty.conv:

-- Displaying the contents of a ISO-8859-8 encoded file.
local s = assert(fs.read('data_file.txt'))
alert(tty.conv(s, 'iso-5589-8'))

(2) When the string originates in some widget like ui.Editbox, which keeps its data encoded independently of the terminal, use the widget’s :to_tty method:

alert('The current word is ' .. edt:to_tty(edt.current_word))

(In the example above we could've used instead the hypothetical code tty.conv(edt.current_word, edt.encoding) but an ui.Editbox doesn’t keep the name of its encoding.)

conv(s, encoding_name)
Converts a string to the terminal’s encoding.

See example in the discussion above.

Parameters:

  • s The string to convert.
  • encoding_name The string’s encoding name. Like “ISO-8859-8”, “KOI8-R”, etc. This name is case-insensitive. If the encoding name is unknown to the system, an exception will be raised.
is_utf8()
Whether the terminal is UTF-8 encoded.

See example in skin_get.

generated by LDoc 1.4.3 Last updated 2016-08-23 17:29:40