Package libxyz :: Package ui :: Module cmd

[hide private]
[frames] | no frames]

# Source Code for Module libxyz.ui.cmd

      1  #-*- coding: utf8 -*
      2  #
      3  # Max E. Kuznecov ~syhpoon <syhpoon@syhpoon.name> 2008
      4  #
      5  # This file is part of XYZCommander.
      6  # XYZCommander is free software: you can redistribute it and/or modify
      7  # it under the terms of the GNU Lesser Public License as published by
      8  # the Free Software Foundation, either version 3 of the License, or
      9  # (at your option) any later version.
     10  # XYZCommander is distributed in the hope that it will be useful,
     11  # but WITHOUT ANY WARRANTY; without even the implied warranty of
     12  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
     13  # GNU Lesser Public License for more details.
     14  # You should have received a copy of the GNU Lesser Public License
     15  # along with XYZCommander. If not, see <http://www.gnu.org/licenses/>.
     16
     17  import copy
     18  import traceback
     19  import re
     20
     21  import libxyz.core
     22
     23  from libxyz.ui import lowui
     24  from libxyz.ui import Prompt
     25  from libxyz.ui import XYZListBox
     26  from libxyz.ui import NumEntry
     27  from libxyz.ui import Keys
     28  from libxyz.ui.utils import refresh
     29  from libxyz.core.utils import ustring, bstring, is_func, split_cmd
     30  from libxyz.core.dsl import XYZ
     31  from libxyz.exceptions import XYZRuntimeError
     32
     33 -class Cmd(lowui.FlowWidget):
     34      """
     35      Command line widget
     36      """
     37
     38      resolution = (u"cmd",)
     39
     40      LEFT = u"left"
     41      RIGHT = u"right"
     42      END = u"end"
     43      UNDER = u"under"
     44
     45 -    def __init__(self, xyz):
     46          """
     47          @param xyz: XYZData instance
     48
     49          Resources used: text, prompt
     50          """
     51
     52          super(Cmd, self).__init__()
     53
     54          self.xyz = xyz
     55          self._attr = lambda x: xyz.skin.attr(self.resolution, x)
     56
     57          self._keys = Keys()
     58
     59          self._text_attr = self._attr(u"text")
     60          self._data = []
     61          # Internal cursor index. Value is in range(0,len(self._data))
     62          self._index = 0
     63          # Virtual cursor index. Value is in range(0,maxcol)
     64          self._vindex = 0
     65          self._hindex = 0
     66
     67          self.context = None
     68          self._panel = self.xyz.pm.load(":sys:panel")
     69
     70          self._plugin = self._init_plugin()
     71          self._ud = libxyz.core.UserData()
     72          self._history_file = "history"
     73
     74          _conf = self._plugin.conf
     75          self.prompt = Prompt(_conf[u"prompt"], self._attr(u"prompt"))
     76          self._undo = libxyz.core.Queue(_conf[u"undo_depth"])
     77          self._history = libxyz.core.Queue(_conf[u"history_depth"])
     78
     79          self.xyz.hm.register("event:conf_update", self._update_conf_hook)
     80          self.xyz.hm.register("event:startup", self._load_history_hook)
     81          self.xyz.hm.register("event:shutdown", self._save_history_hook)
     82
     83      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     84
     85 -    def _update_conf_hook(self, var, val, sect):
     86          """
     87          Hook for update conf event
     88          """
     89
     90          # Not ours
     91          if sect != "plugins" or var != self._plugin.ns.pfull:
     92              return
     93
     94          mapping = {
     95              "prompt": lambda x: self._set_prompt(x),
     96              "undo_depth": lambda x: self._undo.set_size(x),
     97              "history_depth": lambda x: self._history.set_size(x),
     98              }
     99
    100          for k, v in val.iteritems():
    101              if k in mapping:
    102                  mapping[k](v)
    103
    104      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    105
    106 -    def _save_history_hook(self):
    107          """
    108          Save history at shutdown
    109          """
    110
    111          f = None
    112          try:
    113              f = self._ud.openfile(self._history_file, "w", "data")
    114              f.write("\n".join([bstring(u"".join(x)) for x in self._history]))
    115          except XYZRuntimeError, e:
    116              if f:
    117                  f.close()
    118
    119              xyzlog.info(_(u"Unable to open history data file: %s")
    120                          % ustring(str(e)))
    121          else:
    122              if f:
    123                  f.close()
    124
    125      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    126
    127 -    def _load_history_hook(self):
    128          """
    129          Load history at startup
    130          """
    131
    132          f = None
    133
    134          try:
    135              f = self._ud.openfile(self._history_file, "r", "data")
    136              data = f.readlines()
    137
    138              if len(data) > self._history.maxsize:
    139                  data = data[-self._history.maxsize]
    140
    141              self._history.clear()
    142
    143              for line in data:
    144                  self._history.push([x for x in ustring(line.rstrip())])
    145          except Exception:
    146              pass
    147
    148          if f:
    149              f.close()
    150
    151      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    152
    153 -    def _init_plugin(self):
    154          """
    155          Init virtual plugin
    156          """
    157
    158          _cmd_plugin = libxyz.core.plugins.VirtualPlugin(self.xyz, u"cmd")
    159          _cmd_plugin.AUTHOR = u"Max E. Kuznecov <syhpoon@syhpoon.name>"
    160          _cmd_plugin.VERSION = u"0.1"
    161          _cmd_plugin.BRIEF_DESCRIPTION = u"Command line plugin"
    162          _cmd_plugin.FULL_DESCRIPTION = u"Command line plugin. "\
    163                                         u"It allows to enter, edit and "\
    164                                         u"execute commands."
    165          _cmd_plugin.DOC = u"Configuration variables:\n"\
    166                            u"undo_depth - Specifies how many undo levels to "\
    167                            u"keep. Default - 10\n"\
    168                            u"history_depth - Specifies how many entered "\
    169                            u"commands to keep. Default - 50\n"\
    170                            u"prompt - Command line prompt. Default - '$ '"
    171
    172          _cmd_plugin.export(self.del_char)
    173          _cmd_plugin.export(self.del_char_left)
    174          _cmd_plugin.export(self.del_word_left)
    175          _cmd_plugin.export(self.del_word_right)
    176          _cmd_plugin.export(self.clear)
    177          _cmd_plugin.export(self.clear_left)
    178          _cmd_plugin.export(self.clear_right)
    179          _cmd_plugin.export(self.cursor_begin)
    180          _cmd_plugin.export(self.cursor_end)
    181          _cmd_plugin.export(self.cursor_left)
    182          _cmd_plugin.export(self.cursor_right)
    183          _cmd_plugin.export(self.cursor_word_left)
    184          _cmd_plugin.export(self.cursor_word_right)
    185          _cmd_plugin.export(self.is_empty)
    186          _cmd_plugin.export(self.undo)
    187          _cmd_plugin.export(self.undo_clear)
    188          _cmd_plugin.export(self.execute)
    189          _cmd_plugin.export(self.history_prev)
    190          _cmd_plugin.export(self.history_next)
    191          _cmd_plugin.export(self.history_clear)
    192          _cmd_plugin.export(self.show_history)
    193          _cmd_plugin.export(self.put_active_object)
    194          _cmd_plugin.export(self.put_active_object_path)
    195          _cmd_plugin.export(self.put_inactive_object)
    196          _cmd_plugin.export(self.put_inactive_object_path)
    197          _cmd_plugin.export(self.put_active_cwd)
    198          _cmd_plugin.export(self.put_inactive_cwd)
    199          _cmd_plugin.export(self.put)
    200          _cmd_plugin.export(self.get)
    201          _cmd_plugin.export(self.append)
    202          _cmd_plugin.export(self.escape)
    203          _cmd_plugin.export(self.replace_aliases)
    204
    205          self.xyz.pm.register(_cmd_plugin)
    206
    207          self.context = _cmd_plugin.ns.pfull
    208
    209          return _cmd_plugin
    210
    211      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    212
    213 -    def selectable(self):
    214          return True
    215
    216      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    217
    218 -    def rows(self, (maxcol,), focus=False):
    219          """
    220          Return the number of lines that will be rendered
    221          """
    222
    223          return 1
    224
    225      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    226
    227 -    def render(self, (maxcol,), focus=False):
    228          """
    229          Render the command line
    230          """
    231
    232          if self.prompt is not None:
    233              _canv_prompt = self.prompt.render((maxcol,))
    234              _prompt_len = len(self.prompt)
    235          else:
    236              _canv_prompt = lowui.Text(u"").render((maxcol,))
    237              _prompt_len = 0
    238
    239          _data = [bstring(x) for x in self._get_visible(maxcol)]
    240          _text_len = abs(maxcol - _prompt_len)
    241
    242          _canv_text = lowui.AttrWrap(lowui.Text("".join(_data)),
    243                                      self._text_attr).render((maxcol,))
    244
    245          _canvases = []
    246
    247          if _prompt_len > 0:
    248              _canvases.append((_canv_prompt, None, False, _prompt_len))
    249
    250          _canvases.append((_canv_text, 0, True, _text_len))
    251
    252          canv = lowui.CanvasJoin(_canvases)
    253          canv.cursor = self.get_cursor_coords((maxcol,))
    254
    255          return canv
    256
    257      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    258
    259 -    def _get_visible(self, maxcol):
    260          """
    261          Calculate and return currently visible piece of cmd data
    262          """
    263
    264          maxcol -= 1
    265
    266          _plen = len(self.prompt)
    267          _dlen = len(self._data)
    268          _xindex = _plen + self._index
    269
    270          if self._vindex >= maxcol:
    271              self._vindex = maxcol - 1
    272
    273          if _plen + _dlen >= maxcol:
    274              _off = _xindex - maxcol
    275              _to = _xindex
    276
    277              if _off < 0:
    278                  _off = 0
    279                  _to = maxcol - _plen + 1
    280
    281              _data = self._data[_off:_to]
    282          else:
    283              _data = self._data
    284
    285          return _data
    286
    287      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    288
    289 -    def get_cursor_coords(self, (maxcol,)):
    290          """
    291          Return the (x,y) coordinates of cursor within widget.
    292          """
    293
    294          return len(self.prompt) + self._vindex, 0
    295
    296      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    297
    298 -    def _put_object(self, char):
    299          self._data.insert(self._index, ustring(char))
    300          self._index += 1
    301          self._vindex += 1
    302
    303      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    304
    305 -    def keypress(self, size, key):
    306          """
    307          Process pressed key
    308          """
    309
    310          _meth = self.xyz.km.process(key)
    311
    312          if _meth is not None:
    313              return _meth()
    314          else:
    315              _good = [x for x in key if len(x) == 1]
    316
    317              if _good:
    318                  try:
    319                      map(lambda x: self._put_object(x), _good)
    320                  except Exception, e:
    321                      xyzlog.error(_(ustring(str(e))))
    322                      xyzlog.debug(ustring(traceback.format_exc()))
    323                  else:
    324                      self._invalidate()
    325
    326      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    327
    328 -    def _save_undo(self):
    329          """
    330          Save undo data
    331          """
    332
    333          self._undo.push((self._index, copy.copy(self._data)))
    334
    335          #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    336
    337 -    def _restore_undo(self):
    338          """
    339          Restore one undo level
    340          """
    341
    342          if self._undo:
    343              self._index, self._data = self._undo.pop()
    344              self._vindex = self._index
    345
    346      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    347
    348 -    def _save_history(self):
    349          """
    350          Save typed command history
    351          """
    352
    353          # Prevent duplicating entries
    354          if not self._history.tail() == self._data:
    355              self._history.push(copy.copy(self._data))
    356
    357          self._hindex = len(self._history)
    358
    359      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    360
    361 -    def _clear_cmd(self):
    362          """
    363          Internal clear
    364          """
    365
    366          self._data = []
    367          self._index = 0
    368          self._vindex = 0
    369
    370      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    371
    372 -    def _move_cursor(self, direction, chars=None, topred=None):
    373          """
    374          Generic cursor moving procedure
    375          @param direction: LEFT or RIGHT
    376          @param chars: Number of character to move or END to move to the end
    377                        in corresponding direction
    378          @param topred: Predicate function which must return True if char
    379                         under the cursor is endpoint in move
    380          """
    381
    382          _newindex = None
    383
    384          # Using predicate
    385          if callable(topred):
    386              if direction == self.LEFT:
    387                  _range = range(self._index - 1, 0, -1)
    388              else:
    389                  _range = range(self._index + 1, len(self._data))
    390
    391              for i in _range:
    392                  if topred(self._data[i]):
    393                      _newindex = i
    394                      break
    395
    396              if _newindex is None:
    397                  # To start or end, depending on direction
    398                  return self._move_cursor(direction, chars=self.END)
    399
    400          elif direction == self.LEFT:
    401              if chars == self.END:
    402                  _newindex = 0
    403              elif chars is not None and self._index >= chars:
    404                  _newindex = self._index - chars
    405
    406          elif direction == self.RIGHT:
    407              if chars == self.END:
    408                  _newindex = len(self._data)
    409
    410              elif (self._index + chars) <= len(self._data):
    411                  _newindex = self._index + chars
    412
    413          if _newindex is not None:
    414              self._index = _newindex
    415              self._vindex = _newindex
    416              self._invalidate()
    417
    418      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    419
    420      @refresh
    421 -    def _delete(self, direction, chars=None, topred=None):
    422          """
    423          Generic delete procedure
    424          @param direction: LEFT, RIGHT or UNDER
    425          @param chars: Number of characters to delete
    426          @param topred: Predicate function which must return True if char
    427                         under the cursor is endpoint in delete
    428          """
    429
    430          _newindex = None
    431          _delindex = None
    432          _newdata = None
    433
    434          if callable(topred):
    435              if direction == self.LEFT:
    436                  _range = range(self._index - 1, 0, -1)
    437              else:
    438                  _range = range(self._index + 1, len(self._data))
    439
    440              _found = False
    441
    442              for i in _range:
    443                  if topred(self._data[i]):
    444                      _found = True
    445                      if direction == self.LEFT:
    446                          _newindex = i
    447                          _newdata = self._data[:_newindex] + \
    448                                     self._data[self._index:]
    449                      else:
    450                          _newdata = self._data[:self._index] + self._data[i:]
    451
    452                      self._save_undo()
    453                      break
    454
    455              if not _found:
    456                  return self._delete(direction, chars=self.END)
    457
    458          elif direction == self.UNDER:
    459              if self._index >= 0 and self._index < len(self._data):
    460                  _delindex = self._index
    461
    462          elif direction == self.LEFT:
    463              if chars == self.END:
    464                  self._save_undo()
    465                  _newdata = self._data[self._index:]
    466                  _newindex = 0
    467              elif chars is not None and self._index >= chars:
    468                  _newindex = self._index - chars
    469                  _delindex = _newindex
    470
    471          elif direction == self.RIGHT:
    472              if chars == self.END:
    473                  self._save_undo()
    474                  _newdata = self._data[:self._index]
    475
    476          if _newindex is not None:
    477              self._index = _newindex
    478              self._vindex = _newindex
    479          if _newdata is not None:
    480              self._data = _newdata
    481          if _delindex is not None:
    482              del(self._data[_delindex])
    483
    484      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    485
    486      # Public methods
    487
    488 -    def del_char_left(self):
    489          """
    490          Delete single character left to the cursor
    491          """
    492
    493          self._delete(self.LEFT, chars=1)
    494
    495      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    496
    497 -    def del_char(self):
    498          """
    499          Delete single character under the cursor
    500          """
    501
    502          return self._delete(self.UNDER)
    503
    504      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    505
    506 -    def del_word_left(self):
    507          """
    508          Delete a word left to the cursor
    509          """
    510
    511          return self._delete(self.LEFT, topred=lambda x: x.isspace())
    512
    513      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    514
    515 -    def del_word_right(self):
    516          """
    517          Delete a word right to the cursor
    518          """
    519
    520          return self._delete(self.RIGHT, topred=lambda x: x.isspace())
    521
    522      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    523
    524 -    def clear(self):
    525          """
    526          Clear the whole cmd line
    527          """
    528
    529          self._save_undo()
    530          self._clear_cmd()
    531          self._invalidate()
    532
    533      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    534
    535 -    def clear_left(self):
    536          """
    537          Clear the cmd line from the cursor to the left
    538          """
    539
    540          self._delete(self.LEFT, chars=self.END)
    541
    542      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    543
    544 -    def clear_right(self):
    545          """
    546          Clear the cmd line from the cursor to the right
    547          """
    548
    549          return self._delete(self.RIGHT, chars=self.END)
    550
    551      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    552
    553 -    def cursor_begin(self):
    554          """
    555          Move cursor to the beginning of the command line
    556          """
    557
    558          self._move_cursor(self.LEFT, chars=self.END)
    559
    560      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    561
    562 -    def cursor_end(self):
    563          """
    564          Move cursor to the end of the command line
    565          """
    566
    567          self._move_cursor(self.RIGHT, chars=self.END)
    568
    569      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    570
    571 -    def cursor_left(self):
    572          """
    573          Move cursor left
    574          """
    575
    576          self._move_cursor(self.LEFT, chars=1)
    577
    578      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    579
    580 -    def cursor_right(self):
    581          """
    582          Move cursor right
    583          """
    584
    585          self._move_cursor(self.RIGHT, chars=1)
    586
    587      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    588
    589 -    def cursor_word_left(self):
    590          """
    591          Move cursor one word left
    592          """
    593
    594          self._move_cursor(self.LEFT, topred=lambda x: x.isspace())
    595
    596      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    597
    598 -    def cursor_word_right(self):
    599          """
    600          Move cursor one word right
    601          """
    602
    603          self._move_cursor(self.RIGHT, topred=lambda x: x.isspace())
    604
    605      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    606
    607 -    def execute(self):
    608          """
    609          Execute cmd contents
    610          """
    611
    612          # We're inside non-local VFS, execution is not allowed
    613          if XYZ.call(":sys:panel:vfs_driver"):
    614              xyzlog.error(
    615                  _(u"Unable to execute commands on non-local filesystems"))
    616              return
    617
    618          if not self._data:
    619              return
    620
    621          self._save_history()
    622          _data = self.replace_aliases(bstring(u"".join(self._data)))
    623          _cmd, _rest = _split_cmd(_data)
    624
    625          # Do not run shell, execute internal command
    626          if _cmd in self.xyz.conf["commands"]:
    627              try:
    628                  if _rest is None:
    629                      arg = _rest
    630                  else:
    631                      arg = _rest
    632
    633                  self.xyz.conf["commands"][_cmd](arg)
    634              except Exception, e:
    635                  xyzlog.error(_("Error executing internal command %s: %s") %
    636                               (_cmd, ustring(str(e))))
    637          else:
    638              if not hasattr(self, "_execf"):
    639                  self._execf = self.xyz.pm.from_load(":core:shell", "execute")
    640
    641              if not hasattr(self, "_reloadf"):
    642                  self._reloadf = self.xyz.pm.from_load(":sys:panel",
    643                                                        "reload_all")
    644
    645              self._execf(_data)
    646              self._reloadf()
    647
    648          self._clear_cmd()
    649          self._invalidate()
    650
    651      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    652
    653 -    def replace_aliases(self, data):
    654          """
    655          Check if first word of the command line (which is supposed to be a
    656          command to execute) is in our aliases table, if it is, replace it.
    657
    658          @param data: String
    659          """
    660
    661          cmd, _ = _split_cmd(data)
    662
    663          try:
    664              raw_alias = self.xyz.conf["aliases"][cmd]
    665
    666              if isinstance(raw_alias, basestring):
    667                  alias = raw_alias
    668              elif is_func(raw_alias):
    669                  alias = raw_alias()
    670              else:
    671                  xyzlog.error(_(u"Invalid alias type: %s") %
    672                               ustring(str(type(raw_alias))))
    673                  return data
    674
    675              return re.sub(r"^%s" % cmd, alias, data)
    676          except KeyError:
    677              return data
    678          except Exception, e:
    679              xyzlog.error(_(u"Unable to replace an alias %s: %s") %
    680                           (ustring(cmd), ustring(str(e))))
    681              return data
    682
    683      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    684
    685
    686 -    def is_empty(self):
    687          """
    688          Return True if cmd is empty, i.e. has no contents
    689          """
    690
    691          return self._data == []
    692
    693      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    694
    695 -    def undo(self):
    696          """
    697          Restore one level from undo buffer
    698          """
    699
    700          self._restore_undo()
    701          self._invalidate()
    702
    703      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    704
    705 -    def undo_clear(self):
    706          """
    707          Clear undo buffer
    708          """
    709
    710          self._undo.clear()
    711          self._invalidate()
    712
    713      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    714
    715 -    def history_prev(self):
    716          """
    717          Scroll through list of saved commands backward
    718          """
    719
    720          if self._hindex > 0:
    721              self._hindex -= 1
    722              self._data = copy.copy(self._history[self._hindex])
    723              self.cursor_end()
    724
    725      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    726
    727 -    def history_next(self):
    728          """
    729          Scroll through list of saved commands forward
    730          """
    731
    732          if self._hindex < len(self._history) - 1:
    733              self._hindex += 1
    734              self._data = copy.copy(self._history[self._hindex])
    735              self.cursor_end()
    736
    737      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    738
    739 -    def history_clear(self):
    740          """
    741          Clear commands history
    742          """
    743
    744          self._history.clear()
    745
    746      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    747
    748 -    def show_history(self):
    749          """
    750          Show commands history list
    751          """
    752
    753          def _enter_cb(num):
    754              if num >= len(self._history):
    755                  return
    756
    757              self._data = copy.copy(self._history[num])
    758              self.cursor_end()
    759
    760          #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    761
    762          _sel_attr = self.xyz.skin.attr(XYZListBox.resolution, u"selected")
    763
    764          _wdata = []
    765
    766          for i in range(len(self._history)):
    767              _wdata.append(NumEntry(u"".join([ustring(x) for x in
    768                                               self._history[i]]),
    769                                     _sel_attr, i,
    770                                     enter_cb=_enter_cb))
    771
    772          _walker = lowui.SimpleListWalker(_wdata)
    773          _walker.focus = len(_walker) - 1
    774
    775          _dim = tuple([x - 2 for x in self.xyz.screen.get_cols_rows()])
    776
    777          _ek = [self._keys.ENTER]
    778
    779          XYZListBox(self.xyz, self.xyz.top, _walker, _(u"History"),
    780                     _dim).show(exit_keys=_ek)
    781
    782      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    783
    784 -    def put_active_object(self):
    785          """
    786          Put currently selected VFS object name in panel to cmd line
    787          """
    788
    789          return self._put_engine(self._panel.get_selected().name)
    790
    791      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    792
    793 -    def put_active_object_path(self):
    794          """
    795          Put currently selected VFS object full path in panel to cmd line
    796          """
    797
    798          return self._put_engine(self._panel.get_selected().path)
    799
    800      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    801
    802 -    def put_inactive_object(self):
    803          """
    804          Put selected VFS object name in inactive panel to cmd line
    805          """
    806
    807          return self._put_engine(self._panel.get_selected(False).name)
    808
    809      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    810
    811 -    def put_inactive_object_path(self):
    812          """
    813          Put selected VFS object full path in inactive panel to cmd line
    814          """
    815
    816          return self._put_engine(self._panel.get_selected(False).path)
    817
    818      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    819
    820 -    def put_active_cwd(self):
    821          """
    822          Put current working directory of active panel to cmd line
    823          """
    824
    825          return self._put_engine(self._panel.cwd())
    826
    827      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    828
    829 -    def put_inactive_cwd(self):
    830          """
    831          Put current working directory of inactive panel to cmd line
    832          """
    833
    834          return self._put_engine(self._panel.cwd(False))
    835
    836      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    837
    838 -    def put(self, obj, space=True):
    839          """
    840          Put arbitrary string to cmd line starting from the cursor position
    841          @param space: Flag indicating whether to append space char after the obj
    842          """
    843
    844          return self._put_engine(obj, space=space)
    845
    846      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    847
    848 -    def get(self):
    849          """
    850          Get cmd contents
    851          """
    852
    853          return bstring(u"".join(self._data))
    854
    855      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    856
    857 -    def append(self, obj):
    858          """
    859          Append arbitrary string at the end of cmd
    860          """
    861
    862          self.cursor_end()
    863          self.put(obj, space=False)
    864
    865      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    866
    867 -    def _put_engine(self, obj, space=True):
    868          """
    869          Put list content to cmd
    870          """
    871
    872          if space:
    873              extra = [u" "]
    874          else:
    875              extra = []
    876
    877          map(lambda x: self._put_object(x),
    878              self.escape([x for x in ustring(obj)]) + extra)
    879          self._invalidate()
    880
    881      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    882
    883 -    def escape(self, obj, join=False):
    884          """
    885          Escape filename
    886          @param obj: String to escape
    887          @param join: If False return list otherwise return joined string
    888          """
    889
    890          result = []
    891          toescape = [u" ", u"'", u'"', u"*", u"?", u"\\", u"&", u"#",
    892                      u"(", ")",
    893                      u"[", "]",
    894                      u"{", "}",
    895                      ]
    896
    897          if isinstance(obj, basestring):
    898              obj = ustring(obj)
    899
    900          for x in obj:
    901              if x in toescape:
    902                  result.extend([u"\\", x])
    903              else:
    904                  result.append(x)
    905
    906          if join:
    907              return u"".join(result)
    908          else:
    909              return result
    910
    911      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    912
    913 -    def _set_prompt(self, new):
    914          """
    915          Set command line prompt
    916          """
    917
    918          self.prompt = Prompt(new, self._attr(u"prompt"))
    919          self._invalidate()
    920
    921  #++++++++++++++++++++++++++++++++++++++++++++++++
    922
    923 -def _split_cmd(cmdline):
    924      """
    925      Return command name and the rest of the command line
    926      """
    927
    928      _r = split_cmd(cmdline)
    929
    930      if len(_r) == 1:
    931          return _r[0], None
    932      else:
    933          return _r[0], _r[1]
    934