Class ui.Panel

A panel widget is the central component in MC’s display.

It lists files.

A pane has four modes: it may display a tree, a quick-view, one file’s information, or file listing. When we speak of a “panel” we always refer to the file listing mode.)

Events

<<activate>> Triggered when a panel becomes the current one.
<<before-chdir>> Triggered before the directory is changed.
<<draw>> Triggered after a panel has been painted.
<<flush>> Triggered when the user reloads (C-r) the panel.
<<load>> Triggered after a directory has been read into the panel.
<<panelize>> Triggered after a panel has been panelized.
<<select-file>> Triggered when a file is selected in the panel.

General methods

ui.Panel.current rw Returns the current file.
ui.Panel.dir rw The panel’s directory.
ui.Panel:files() Iterates over the files displayed in the panel.
ui.Panel.filter rw The filter.
ui.Panel:filter_by_fn(fn) Filter files by a predicate function.
ui.Panel:panelize_by_command(command) Populates the panel with the output of a shell command.
ui.Panel:panelize_by_list(list) Populates the panel with a list of files.
ui.Panel.panelized rw Whether the listing is “panelized”.
ui.Panel:reload() Reloads the panel.
ui.Panel.vdir rw The panel’s directory (as a vpath).

Marking and unmarking files

ui.Panel:clear() Unmarks all the files.
ui.Panel:glob(pattern) Marks files by glob pattern.
ui.Panel:mark(list) Marks files by name.
ui.Panel:mark_by_fn(fn) Marks files by a predicate function.
ui.Panel.marked rw Returns a table of the marked files.
ui.Panel:unglob(pattern) Unmarks files by glob pattern.
ui.Panel:unmark(list) Unmarks files by name.
ui.Panel:unmark_by_fn(fn) Unmarks files by a predicate function.

The view

ui.Panel.custom_format rw Custom format for the listing.
ui.Panel.custom_mini_status rw Whether to show a custom format for the mini status.
ui.Panel.custom_mini_status_format rw Custom format for the mini status.
ui.Panel.list_type rw The list type.
ui.Panel.num_brief_cols rw Number of columns for the “brief” list type.
ui.Panel.sort_field rw The field by which to sort.
ui.Panel.sort_reverse rw Whether to reverse the sort.

Low-level methods

ui.Panel:_get_current_index() Gets the index of the current (“selected”) file.
ui.Panel:_get_file_by_index(i, skip_stat) Gets meta information about a file.
ui.Panel:_get_max_index() Gets the number of files in the panel.
ui.Panel:_get_metrics() Returns various measurements.
ui.Panel:_mark_file_by_index(i, mark) Changes the mark status of a file.
ui.Panel:_remove(i) Removes a file.
ui.Panel:_set_current_index(i) Sets the the current (“selected”) file, by index.

Static panel functions

ui.Panel.current r The “current” panel.
ui.Panel.left r The left panel.
ui.Panel.other r The “other” panel.
ui.Panel.right r The right panel.

Static panel functions and properties

ui.Panel.bind_if_commandline_empty(keyseq, fn) Binds a key, when the command-line is empty.
ui.Panel.register_field(info) Registers a field.


Events

<<activate>>
Triggered when a panel becomes the current one. (E.g., as a result of tabbing to it.)

ui.Panel.bind("<<activate>>", function(pnl)
  ....
end)

Example: The set-gterm-cwd.lua script uses this event, together with <<load>>, to inform GNOME Terminal of the current directory.

<<before-chdir>>

Triggered before the directory is changed.

In other words, it is triggered before the user navigates to another directory (e.g., by pressing ENTER on a directory).

Compare this with <<load>>, which is triggered after the directory has changed.

Example:

-- This simple code makes the selection (the marked files)
-- persistent.

local selections = {}

ui.Panel.bind("<<before-chdir>>", function(pnl)
  selections[pnl.dir] = pnl.marked
end)

ui.Panel.bind("<<load>>", function(pnl)
   pnl.marked = selections[pnl.dir]
end)
<<draw>>

Triggered after a panel has been painted.

You may use this event to add more information to a panel’s display.

-- Prints the directory at the panel's bottom.

ui.Panel.bind('<<draw>>', function(pnl)
  local c = pnl:get_canvas()
  c:set_style(tty.style('yellow, red'))
  c:goto_xy(2, pnl.rows - 1)
  c:draw_string(pnl.dir)
end)
<<flush>>
Triggered when the user reloads (C-r) the panel.

You may use this event to clear expensive caches, as demonstrated in the user guide.

ui.Panel.bind("<<flush>>", function(pnl)
  ....
end)

Filesystems have their own flush event.

<<load>>
Triggered after a directory has been read into the panel.

This happens, for example, when you navigate to a new directory, when you return from running a shell command, or when you reload (C-r) the panel.

You may use this event to clear caches, as demonstrated in the user guide. You may also use it, together with <<activate>>, to inform the environment of the current directory.

ui.Panel.bind("<<load>>", function(pnl)
  ....
end)

Sometimes code you wish to run in this event may trigger <<load>> again. For example, setting sort_field to “unsorted” causes the directory to be re-read. In such cases you'll cause an infinite recursion, which may bring about a program crash.

You can solve the problem by wrapping the offending code in ui.queue or wrapping the whole event handler (or just the offending code) in utils.magic.once:

ui.Panel.bind("<<load>>", utils.magic.once(function(pnl)
  if pnl.dir:find 'audio' then
    pnl.sort_field = 'unsorted'
  else
    pnl.sort_field = 'name'
  end
end))
<<panelize>>
Triggered after a panel has been panelized.

ui.Panel.bind("<<panelize>>", function(pnl)
  ....
end)

There’s no <<unpanelize>> event (you should probably use <<load>> for that).

This event is also triggered after you set the panelized property, to any value.

<<select-file>>

Triggered when a file is selected in the panel.

Terminology: When we say that a file is “selected” we mean that it has become the current file. Don’t confuse the selected file with the marked files: the selected file isn’t necessarily marked.

-- Read aloud the current filename, after the user
-- rests on it for a second.

local say = timer.debounce(function(text)
  -- Note: we run espeak in the background (&) or else
  -- we'll be blocked till it finishes voicing the text.
  os.execute(('espeak %q &'):format(text))
end, 1000)

ui.Panel.bind("<<select-file>>", function(pnl)
  say(pnl.current)
end)

General methods

ui.Panel.current rw

Returns the current file.

That is, the file on which the “cursor” stands:

ui.Panel.bind("C-y", function(pnl)
  alert(pnl.current)
end

Besides the filename, several more values are returned. You have to use the method calling syntax — :get_current() — to access them:

-- Ask for confirmation before editing a huge file.
ui.Panel.bind("f4", function(pnl)
  local filename, stat = pnl:get_current()
  if stat.size < 5000000
      or prompts.confirm(T"This file is huge. You really want to edit it?") then
    return false  -- continue to the default action.
  end
end)

Returns:

  1. the filename
  2. the StatBuf
  3. boolean: whether the file is marked.
  4. boolean: whether the file is the “current” one.
  5. boolean: whether this is a broken symlink.
  6. boolean: whether it’s a directory whose size has been computed (this size is recorded in the StatBuf).
ui.Panel.dir rw
The panel’s directory.

-- Insert the panel's directory name into the command line.
ui.Panel.bind("f16", function(pnl)
  local ipt = ui.current_widget("Input")
  if ipt then
    ipt:insert(pnl.dir)
  end
end)

-- This is a better version of the above, which works for
-- any input line.
ui.Input.bind("f16", function(ipt)
  -- When using /usr/bin/mcedit there are no panels, hence
  -- the "and" check below.
  ipt:insert(ui.Panel.current and ui.Panel.current.dir or "")
end)

-- An even better version!
ui.Input.bind("f16", function(ipt)
  ipt:insert(fs.current_dir())
end)

To change the panel’s directory you can do either pnl.dir = '/whatever' or pnl:set_dir('/whatever') (the former being syntactic sugar for the latter). The latter lets you inspect the return value to see if the operation was successful.

See also vdir.

ui.Panel:files()

Iterates over the files displayed in the panel.

The values returned by the iterator are the ones described at current.

ui.Panel.bind('C-y', function(pnl)
  local count = 0

  for fname, stat in pnl:files() do
    if stat.type == "directory" and fname ~= ".." then
      count = count + 1
    end
  end

  alert(locale.format_plural(
    "There is %d directory here",
    "There are %d directories here",
    count))
end)
ui.Panel.filter rw
The filter.

A shell pattern determining the files to show. Set it to nil if you want to clear the filter. Example:

ui.Panel.bind('C-y', function(pnl)
  pnl.filter = '*.c'
end)

MC has two filter bugs:

  • When the filter is the empty string (""), the panel header won’t give an indication that a filter is active.
  • When the “Shell patterns” option is off, the filter string will still be interpreted as a shell pattern instead of a regex.

Additionally, a filter doesn’t affect a panelized panel.

ui.Panel:filter_by_fn(fn)
Filter files by a predicate function.

Only files for which the function returns true will remain. The arguments the function receives are the ones described at current.

-- Filter the display to only the marked files. (Press C-r to go
-- back to full view.)
--
-- (This is quite useful, as sometimes you wish to see which
-- files are marked without having to scroll in a giant list.)

ui.Panel.bind("M-f", function(pnl)
  pnl:filter_by_fn(function(fname, stat, is_marked)
    return is_marked
  end)
end)

This filtering, in contrast to the one offered by the filter property, isn’t persitent. Its effect vanishes once the panel is reloaded (e.g., when the user returns to the panel from the editor).

You can remedy this by one of two means:

  • Set the panelized property to true. This will “fixate” your changes.

  • You can create an illusion of persitance by attaching this filtering to the <<load>> event:

-- Filter out zero-size files and *.pyc files, from
-- the home directory only.
ui.Panel.bind("<<load>>", function(pnl)
  if pnl.dir == os.getenv("HOME") then
    pnl:filter_by_fn(function(fname, stat)
      return stat.size ~= 0 and not fname:find "%.pyc$"
    end)
  end
end)
ui.Panel:panelize_by_command(command)

Populates the panel with the output of a shell command.

This is known as “external panelize” in MC’s lingo.

-- Make the panel show all MP3 files in the
-- current folder and all subfolders.

ui.Panel.bind('C-y', function(pnl)
  pnl:panelize_by_command('find . -iname "*.mp3" -print')
end)

Another example:

-- Make the panel show only the files having the
-- same extension as the current file.
-- Files in subfolders too are shown.

ui.Panel.bind('C-y', function(pnl)

  local original_current = pnl.current
  local extension = pnl.current:match '.*(%..*)' or ''

  pnl:panelize_by_command(('find . -name "*"%q -print'):format(extension))

  -- Restore the cursor to the file we've been standing on originally:
  pnl.current = original_current

end)
ui.Panel:panelize_by_list(list)
Populates the panel with a list of files.

-- Make the panel show all MP3 files in the
-- current folder and all subfolders.

ui.Panel.bind('C-y', function(pnl)
  pnl:panelize_by_list( fs.tglob('**/*.mp3') )
end)

This is just a convenience function that uses panelize_by_command internally. It works by writing the file names to a temporary file and calling panelize_by_command with the cat shell command.

A file must exist to be shown in the panel. If you name files that don’t exist, they simply won’t be shown:

-- Assuming you don't have files named "one", "two" and "three"
-- in the panel's folder, you'll see an empty panel after running
-- this code:

pnl:panelize_by_list( { "one", "two", "three" } )

This “limitation”, of course, pertains to panelize_by_command too. MC needs to stat every file you feed it (in order to show its size, date, etc), so it can only deal with existing files.

You may use absolute pathnames:

ui.Panel.bind('C-y', function(pnl)
  pnl:panelize_by_list( { "/etc/fstab", "/usr/include/stdio.h" } )
end)
ui.Panel.panelized rw
Whether the listing is “panelized”.

Under the hood, panelized is merely a flag set on a panel that tells MC not to reload the listing. Setting (or clearing) this flag has no other consequence. Specifically: it won’t cause a reload (if that’s your intention (which only you know) you'll have to call reload yourself).

See a comment in filter_by_fn demonstrating the usefulness of this property.

ui.Panel:reload()
Reloads the panel.

(An operation also known as “rescan” or “reread” in MC.)

Currently, because of some deficiency in MC’s API, this method reloads both panels.

You may alternatively do pnl.dialog:command "reread"; it reloads the “current” panel only. But it might be that it’s the “other” panel you want reloaded.

When the panel is panelized, this method will not cause it to forget its files and instead load the directory’s contents. It will only cause it to update the meta data of the files displayed (sizes, modification dates, etc.).

ui.Panel.vdir rw
The panel’s directory (as a vpath).

Marking and unmarking files

ui.Panel:clear()
Unmarks all the files.
ui.Panel:glob(pattern)
Marks files by glob pattern.

pnl:glob("*.c")

You can also do pnl:mark(fs.tglob("*.c")), but this, contrary to the above, would involve disk access. See example at marked for an interesting use of fs.tglob.

ui.Panel:mark(list)
Marks files by name.

Marks all the files given as the list parameter.

Previously marked files remain marked. If you wish to unmark them, call clear before calling this method.

pnl:clear()
pnl:mark({"Makefile", "hook.c"})

-- Add all the files marked on the other panel:
pnl:mark(ui.Panel.other.marked)

-- And the selected file:
pnl:mark(pnl.current)

See another example in mark_files_by_contents.lua.

Parameters:

  • list A table of file names. Can also be a string. Can also be nil (in which case it’s a no-op).
ui.Panel:mark_by_fn(fn)

Marks files by a predicate function.

Mark all files satisfying some condition. Receives as an argument a function that’s to return true if a file is to be marked. The arguments the function receives are the ones described at current.

ui.Panel.bind("C-x plus s", function(pnl)
  local min_size_s = prompts.input(T'Mark all files bigger than: (enter, for example, "200K", "50M", ...)')
  if min_size_s then
    local min_size = abortive(utils.text.parse_size(min_size_s))
    pnl:mark_by_fn(function(fname, stat)
      return stat.size >= min_size
    end)
  end
end)
ui.Panel.marked rw
Returns a table of the marked files.

If no files are marked, returns nil; this makes it easier to provide a default value using “or”:

local files_to_operate_on = pnl.marked or { pnl.current }

This property is writable, which simply makes it an alternative to clear+mark:

-- Mark all web pages saved from Wikipedia.

ui.Panel.bind('C-y', function(pnl)
  pnl.marked = prompts.please_wait(T"Locating files",
    function()
      return fs.tglob('*.mht', {conditions={
        function(path) return fs.read(path, 1024):find('Content-Location: http://en.wikipedia.org', 1, true) end
      }})
    end
  )
end)

(A variation of the code above is available as mark_files_by_contents.lua.)

ui.Panel:unglob(pattern)
Unmarks files by glob pattern.
ui.Panel:unmark(list)
Unmarks files by name.

Unmarks all the files given as the list parameter.

See further details at mark.

ui.Panel:unmark_by_fn(fn)
Unmarks files by a predicate function.

See mark_by_fn.

The view

ui.Panel.custom_format rw
Custom format for the listing.

When list_type is set to “custom”, this property specifies the format to use.

ui.Panel.bind('C-y', function(pnl)
  pnl.list_type = "custom"
  pnl.custom_format = "half type name | size | perm | gitstatus | gitdate | gitauthor | gitmessage"
end)

The syntax of the format string is:

all              := panel_format? format
panel_format     := [full|half] [1-9]
format           := one_format | format , one_format

one_format       := align FIELD_ID [opt_width]
align            := [<=>]
opt_width        := : size [opt_expand]
width            := [0-9]+
opt_expand       := +

(This syntax description was copied, with some minor modifications, from a comment in src/filemanager/panel.c.)

ui.Panel.custom_mini_status rw
Whether to show a custom format for the mini status.

Boolean.

ui.Panel.custom_mini_status_format rw
Custom format for the mini status.

When custom_mini_status is enabled, this property is the format to use.

ui.Panel.bind('C-y', function(pnl)
  pnl.custom_mini_status = true
  pnl.custom_mini_status_format = "half type name:20 | gitcommit:10 | gitmessage"
end)

MC keeps track of four custom mini-status formats: one per each list_type. This custom_mini_status_format property reflects the one belonging to the list_type currently in use.

ui.Panel.list_type rw
The list type.

How files are listed. It is one of “full”, “brief”, “long”, or “custom”. For “custom”, the format is specified with custom_format.

“custom” is entitled “User defined” in MC’s interface. In our API we use the word “custom”, rather than “user”, because the latter’s meaning isn’t clear when embedded in names of other properties.

ui.Panel.num_brief_cols rw

Number of columns for the “brief” list type.

Valid values are 1 to 9 (if you provide a value outside this range it will be clamped to fit the range).

Setting this property does not automatically set the list type to “brief” (You'll have to do this yourself, as shown in the example).

Example:

-- Quickly change the number of columns with the +/- keys.

ui.Panel.bind('C-y plus', function(pnl)
  pnl.list_type = 'brief'
  pnl.num_brief_cols = pnl.num_brief_cols + 1
end)

ui.Panel.bind('C-y minus', function(pnl)
  pnl.list_type = 'brief'
  pnl.num_brief_cols = pnl.num_brief_cols - 1
end)
ui.Panel.sort_field rw

The field by which to sort.

It is a string identifying a built-in field, like "name", "size", "mtime", "extension", a custom field you yourself created, or "unsorted" for the disk’s raw order.

-- Toggle between two sorts.
ui.Panel.bind('C-y', function(pnl)
  if pnl.sort_field == "name" then
    pnl.sort_field = "size"
  else
    pnl.sort_field = "name"
end)
ui.Panel.sort_reverse rw

Whether to reverse the sort.

ui.Panel.bind('C-y', function(pnl)
  pnl.sort_reverse = not pnl.sort_reverse
end)

Low-level methods

These methods aren’t intended for use by end-users. These are methods upon which higher-level methods are built.

A note to MC developers: these methods are implemented in C. Higher-level methods are implemented in Lua. This lets us experiment easily in designing the public API.

ui.Panel:_get_current_index()
Gets the index of the current (“selected”) file.
ui.Panel:_get_file_by_index(i, skip_stat)
Gets meta information about a file.

Multiple values are returned. See current for details.

Parameters:

  • i index
  • skip_stat Whether to return a nil instead of the StatBuf (for efficiency).
ui.Panel:_get_max_index()
Gets the number of files in the panel.
ui.Panel:_get_metrics()

Returns various measurements.

Returns several values, in this order:

  • The index of the top file displayed.
  • The number of screen lines used for displaying files.
  • The number of columns.
ui.Panel:_mark_file_by_index(i, mark)
Changes the mark status of a file.

For efficiency, this function doesn’t redraw the widget. After you're done marking the files you want, call :redraw() yourself.

Parameters:

  • i index
  • mark Boolean. Whether to mark or unmark the file.
ui.Panel:_remove(i)
Removes a file.

Removes a file from the listing (not from disk, of course), by its index.

This can be used to implement filtering.

(The panel is not redrawn —for efficiency— as it’s assumed you might want to remove multiple files. You have to call :redraw() yourself.)

Parameters:

  • i index
ui.Panel:_set_current_index(i)
Sets the the current (“selected”) file, by index.

Static panel functions

Any of the properties below may return nil. E.g., if the left pane is showing a directory tree, left will return nil; when running as “mcedit”, current will return nil. So don’t assume the panels exist.
ui.Panel.current r
The “current” panel.

This is the active panel, the one that has the focus.

Example:

-- Insert the panel's dir into the edited text. (tip:
-- replace "Editbox" with "Input" to make it work with
-- any input box.)
ui.Editbox.bind('C-y', function(edt)
  -- "<none>" is emitted when using mcedit.
  edt:insert(ui.Panel.current and ui.Panel.current.dir or "<none>")
end)

You can make a panel the current one by calling its focus method. Example:

-- Make the '\' key switch panels. Like the TAB key.
ui.Panel.bind_if_commandline_empty('\\', function()
  if ui.Panel.other then
    ui.Panel.other:focus()
  end
end)
ui.Panel.left r
The left panel.
ui.Panel.other r

The “other” panel.

That’s the panel which is not the current one.

ui.Panel.bind('f5', function(pnl)
  alert(T"You wanna copy something from here to %s":format(
    ui.Panel.other and ui.Panel.other.dir or T"<nowhere>"))
end)
ui.Panel.right r
The right panel.

Static panel functions and properties

ui.Panel.bind_if_commandline_empty(keyseq, fn)
Binds a key, when the command-line is empty.

This function is similar to ui.Panel.bind except that the key is active only when nothing is already typed on the command-line.

You already recognize this behavior from MC’s built-in keys “+” (Select), “–” (Unselect), etc., which aren’t effective when the command-line isn’t empty. Naturally, if you pick a printable key you'd pick one that doesn’t usually start a shell command, and you probably won’t find the need to use the modifiers ALT or CTRL.

Example:

-- By pressing '@', when then command-line is empty, you
-- select/unselect files with the same extension as the
-- current file.

ui.Panel.bind_if_commandline_empty('@', function(pnl)
  pnl:command 'SelectExt'
  pnl:redraw()
end)

There’s nothing “magical” in this function. Its implementation is quite simple:

function ui.Panel.bind_if_commandline_empty(keyseq, fn)
  ui.Panel.bind(keyseq, function(pnl)
    if ui.current_widget('Input') and ui.current_widget('Input').text ~= '' then
      return false
    end
    return fn(pnl)
  end)
end
ui.Panel.register_field(info)
Registers a field.

The subject of creating fields is covered extensively in the reference and user guide.

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