1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
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
82
83
84
86 """
87 Hook for update conf event
88 """
89
90
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
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
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
208
209
210
213
214
215
216 - def rows(self, (maxcol,), focus=False):
217 """
218 Return the number of lines that will be rendered
219 """
220
221 return 1
222
223
224
225 - def render(self, (maxcol,), focus=False):
226 """
227 Render the command line
228 """
229
230 if self.prompt is not None:
231 _canv_prompt = self.prompt.render((maxcol,))
232 _prompt_len = len(self.prompt)
233 else:
234 _canv_prompt = lowui.Text(u"").render((maxcol,))
235 _prompt_len = 0
236
237 _data = [bstring(x) for x in self._get_visible(maxcol)]
238 _text_len = abs(maxcol - _prompt_len)
239
240 _canv_text = lowui.AttrWrap(lowui.Text("".join(_data)),
241 self._text_attr).render((maxcol,))
242
243 _canvases = []
244
245 if _prompt_len > 0:
246 _canvases.append((_canv_prompt, None, False, _prompt_len))
247
248 _canvases.append((_canv_text, 0, True, _text_len))
249
250 canv = lowui.CanvasJoin(_canvases)
251 canv.cursor = self.get_cursor_coords((maxcol,))
252
253 return canv
254
255
256
258 """
259 Calculate and return currently visible piece of cmd data
260 """
261
262 maxcol -= 1
263
264 _plen = len(self.prompt)
265 _dlen = len(self._data)
266 _xindex = _plen + self._index
267
268 if self._vindex >= maxcol:
269 self._vindex = maxcol - 1
270
271 if _plen + _dlen >= maxcol:
272 _off = _xindex - maxcol
273 _to = _xindex
274
275 if _off < 0:
276 _off = 0
277 _to = maxcol - _plen + 1
278
279 _data = self._data[_off:_to]
280 else:
281 _data = self._data
282
283 return _data
284
285
286
288 """
289 Return the (x,y) coordinates of cursor within widget.
290 """
291
292 return len(self.prompt) + self._vindex, 0
293
294
295
297 self._data.insert(self._index, char)
298 self._index += 1
299 self._vindex += 1
300
301
302
304 """
305 Process pressed key
306 """
307
308 _meth = self.xyz.km.process(key)
309
310 if _meth is not None:
311 return _meth()
312 else:
313 _good = [x for x in key if len(x) == 1]
314
315 if _good:
316 try:
317 map(lambda x: self._put_object(x), _good)
318 except Exception, e:
319 xyzlog.error(_(ustring(str(e))))
320 xyzlog.debug(ustring(traceback.format_exc()))
321 else:
322 self._invalidate()
323
324
325
327 """
328 Save undo data
329 """
330
331 self._undo.push((self._index, copy.copy(self._data)))
332
333
334
336 """
337 Restore one undo level
338 """
339
340 if self._undo:
341 self._index, self._data = self._undo.pop()
342 self._vindex = self._index
343
344
345
346 - def _save_history(self):
347 """
348 Save typed command history
349 """
350
351
352 if not self._history.tail() == self._data:
353 self._history.push(copy.copy(self._data))
354
355 self._hindex = len(self._history)
356
357
358
360 """
361 Internal clear
362 """
363
364 self._data = []
365 self._index = 0
366 self._vindex = 0
367
368
369
370 - def _move_cursor(self, direction, chars=None, topred=None):
371 """
372 Generic cursor moving procedure
373 @param direction: LEFT or RIGHT
374 @param chars: Number of character to move or END to move to the end
375 in corresponding direction
376 @param topred: Predicate function which must return True if char
377 under the cursor is endpoint in move
378 """
379
380 _newindex = None
381
382
383 if callable(topred):
384 if direction == self.LEFT:
385 _range = range(self._index - 1, 0, -1)
386 else:
387 _range = range(self._index + 1, len(self._data))
388
389 for i in _range:
390 if topred(self._data[i]):
391 _newindex = i
392 break
393
394 if _newindex is None:
395
396 return self._move_cursor(direction, chars=self.END)
397
398 elif direction == self.LEFT:
399 if chars == self.END:
400 _newindex = 0
401 elif chars is not None and self._index >= chars:
402 _newindex = self._index - chars
403
404 elif direction == self.RIGHT:
405 if chars == self.END:
406 _newindex = len(self._data)
407
408 elif (self._index + chars) <= len(self._data):
409 _newindex = self._index + chars
410
411 if _newindex is not None:
412 self._index = _newindex
413 self._vindex = _newindex
414 self._invalidate()
415
416
417
418 @refresh
419 - def _delete(self, direction, chars=None, topred=None):
420 """
421 Generic delete procedure
422 @param direction: LEFT, RIGHT or UNDER
423 @param chars: Number of characters to delete
424 @param topred: Predicate function which must return True if char
425 under the cursor is endpoint in delete
426 """
427
428 _newindex = None
429 _delindex = None
430 _newdata = None
431
432 if callable(topred):
433 if direction == self.LEFT:
434 _range = range(self._index - 1, 0, -1)
435 else:
436 _range = range(self._index + 1, len(self._data))
437
438 _found = False
439
440 for i in _range:
441 if topred(self._data[i]):
442 _found = True
443 if direction == self.LEFT:
444 _newindex = i
445 _newdata = self._data[:_newindex] + \
446 self._data[self._index:]
447 else:
448 _newdata = self._data[:self._index] + self._data[i:]
449
450 self._save_undo()
451 break
452
453 if not _found:
454 return self._delete(direction, chars=self.END)
455
456 elif direction == self.UNDER:
457 if self._index >= 0 and self._index < len(self._data):
458 _delindex = self._index
459
460 elif direction == self.LEFT:
461 if chars == self.END:
462 self._save_undo()
463 _newdata = self._data[self._index:]
464 _newindex = 0
465 elif chars is not None and self._index >= chars:
466 _newindex = self._index - chars
467 _delindex = _newindex
468
469 elif direction == self.RIGHT:
470 if chars == self.END:
471 self._save_undo()
472 _newdata = self._data[:self._index]
473
474 if _newindex is not None:
475 self._index = _newindex
476 self._vindex = _newindex
477 if _newdata is not None:
478 self._data = _newdata
479 if _delindex is not None:
480 del(self._data[_delindex])
481
482
483
484
485
487 """
488 Delete single character left to the cursor
489 """
490
491 self._delete(self.LEFT, chars=1)
492
493
494
496 """
497 Delete single character under the cursor
498 """
499
500 return self._delete(self.UNDER)
501
502
503
505 """
506 Delete a word left to the cursor
507 """
508
509 return self._delete(self.LEFT, topred=lambda x: x.isspace())
510
511
512
514 """
515 Delete a word right to the cursor
516 """
517
518 return self._delete(self.RIGHT, topred=lambda x: x.isspace())
519
520
521
523 """
524 Clear the whole cmd line
525 """
526
527 self._save_undo()
528 self._clear_cmd()
529 self._invalidate()
530
531
532
534 """
535 Clear the cmd line from the cursor to the left
536 """
537
538 self._delete(self.LEFT, chars=self.END)
539
540
541
543 """
544 Clear the cmd line from the cursor to the right
545 """
546
547 return self._delete(self.RIGHT, chars=self.END)
548
549
550
552 """
553 Move cursor to the beginning of the command line
554 """
555
556 self._move_cursor(self.LEFT, chars=self.END)
557
558
559
561 """
562 Move cursor to the end of the command line
563 """
564
565 self._move_cursor(self.RIGHT, chars=self.END)
566
567
568
570 """
571 Move cursor left
572 """
573
574 self._move_cursor(self.LEFT, chars=1)
575
576
577
584
585
586
588 """
589 Move cursor one word left
590 """
591
592 self._move_cursor(self.LEFT, topred=lambda x: x.isspace())
593
594
595
597 """
598 Move cursor one word right
599 """
600
601 self._move_cursor(self.RIGHT, topred=lambda x: x.isspace())
602
603
604
606 """
607 Execute cmd contents
608 """
609
610
611
612 if XYZ.call(":sys:panel:vfs_driver"):
613 xyzlog.error(
614 _(u"Unable to execute commands on non-local filesystems"))
615 return
616
617 if not self._data:
618 return
619
620 self._save_history()
621 _data = self.replace_aliases("".join(self._data))
622 _cmd, _rest = split_cmd(_data)
623
624
625 if _cmd in self.xyz.conf["commands"]:
626 try:
627 if _rest is None:
628 arg = _rest
629 else:
630 arg = bstring(_rest)
631
632 self.xyz.conf["commands"][_cmd](arg)
633 except Exception, e:
634 xyzlog.error(_("Error executing internal command %s: %s") %
635 (_cmd, ustring(str(e))))
636 else:
637 if not hasattr(self, "_execf"):
638 self._execf = self.xyz.pm.from_load(":core:shell", "execute")
639
640 if not hasattr(self, "_reloadf"):
641 self._reloadf =self.xyz.pm.from_load(":sys:panel",
642 "reload_all")
643
644 self._execf(_data)
645 self._reloadf()
646
647 self._clear_cmd()
648 self._invalidate()
649
650
651
653 """
654 Check if first word of the command line (which is supposed to be a
655 command to execute) is in our aliases table, if it is, replace it.
656
657 @param data: String
658 """
659
660 cmd, _ = split_cmd(data)
661
662 try:
663 raw_alias = self.xyz.conf["aliases"][cmd]
664
665 if isinstance(raw_alias, basestring):
666 alias = raw_alias
667 elif is_func(raw_alias):
668 alias = raw_alias()
669 else:
670 xyzlog.error(_(u"Invalid alias type: %s") %
671 ustring(str(type(raw_alias))))
672 return data
673
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
687 """
688 Return True if cmd is empty, i.e. has no contents
689 """
690
691 return self._data == []
692
693
694
696 """
697 Restore one level from undo buffer
698 """
699
700 self._restore_undo()
701 self._invalidate()
702
703
704
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
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
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
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
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
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
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):
839 """
840 Put arbitrary string to cmd line starting from the cursor position
841 """
842
843 return self._put_engine(obj)
844
845
846
848 """
849 Put list content to cmd
850 """
851
852 map(lambda x: self._put_object(x),
853 self.escape([bstring(x) for x in ustring(obj)]) + [" "])
854 self._invalidate()
855
856
857
858 - def escape(self, obj, join=False):
859 """
860 Escape filename
861 @param obj: String to escape
862 @param join: If False return list otherwise return joined string
863 """
864
865 result = []
866 toescape = [" ", "'", '"', "*", "?", "\\", "&", "#",
867 "(", ")",
868 "[", "]",
869 "{", "}",
870 ]
871
872 for x in obj:
873 if x in toescape:
874 result.extend(["\\", x])
875 else:
876 result.append(x)
877
878 if join:
879 return "".join(result)
880 else:
881 return result
882
883
884
886 """
887 Set command line prompt
888 """
889
890 self.prompt = Prompt(new, self._attr(u"prompt"))
891 self._invalidate()
892
896 """
897 Return command name and the rest of the command line
898 """
899
900 _r = cmdline.split(" ", 1)
901
902 if len(_r) == 1:
903 return _r[0], None
904 else:
905 return _r[0], _r[1]
906