1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 import os
18 import sys
19 import traceback
20
21 import libxyz.ui
22 import libxyz.core
23 import libxyz.const
24 import libxyz.exceptions
25
26 from libxyz.core.utils import ustring, bstring, is_func
27 from libxyz.ui import lowui
28 from libxyz.ui import align
29 from libxyz.ui import Shortcut
30 from libxyz.ui import BlockEntries
31 from libxyz.ui.utils import refresh
32 from libxyz.ui.utils import truncate
33 from libxyz.vfs.types import VFSTypeFile
34 from libxyz.vfs.local import LocalVFSObject
35
36 -class Panel(lowui.WidgetWrap):
37 """
38 Panel is used to display filesystem hierarchy
39 """
40
41 resolution = (u"panel", u"widget")
42 context = u":sys:panel"
43
44 EVENT_SHUTDOWN = u"event:shutdown"
45 EVENT_BEFORE_SWITCH_TAB = u"event:sys:panel:before_switch_tab"
46 EVENT_SWITCH_TAB = u"event:sys:panel:switch_tab"
47 EVENT_NEW_TAB = u"event:sys:panel:new_tab"
48 EVENT_DEL_TAB = u"event:sys:panel:del_tab"
49
51 self.xyz = xyz
52 self.conf = self.xyz.conf[u"plugins"][u":sys:panel"]
53
54 self._keys = libxyz.ui.Keys()
55
56 _size = self.xyz.screen.get_cols_rows()
57 _blocksize = libxyz.ui.Size(rows=_size[1] - 1, cols=_size[0] / 2 - 2)
58 self._enc = xyzenc
59 self._stop = False
60 self._resize = False
61
62 self._set_plugins()
63 self._cmd = libxyz.ui.Cmd(xyz)
64 _cwd = os.getcwd()
65
66 self.filters = self._build_filters()
67 self.xyz.hm.register("event:conf_update", self._update_conf_hook)
68
69 self.block1 = Block(xyz, _blocksize, _cwd, self._enc, active=True)
70 self.block2 = Block(xyz, _blocksize, _cwd, self._enc)
71 self._compose()
72
73 super(Panel, self).__init__(self._widget)
74
75
76
78 """
79 Hook for update conf event
80 """
81
82
83 if sect != "plugins" or var != ":sys:panel":
84 return
85
86 if "filters" in val or "filters_enabled" in val:
87 self.filters = self._build_filters()
88
89
90
92 """
93 Compose widgets
94 """
95
96 columns = lowui.Columns([self.block1, self.block2], 0)
97 self._widget = lowui.Pile([columns, self._cmd])
98
99
100
102 """
103 Compile filters
104 """
105
106 filters = []
107
108
109 if not self.conf[u"filters_enabled"]:
110 return filters
111
112 for f in self.conf[u"filters"]:
113 try:
114 rule = libxyz.core.FSRule(ustring(f))
115 except libxyz.exceptions.ParseError, e:
116 xyzlog.error(_(u"Error compiling filter: %s") % unicode(e))
117 continue
118 else:
119 filters.append(rule)
120
121 return filters
122
123
124
125 @property
127 if self.block1.active:
128 return self.block1
129 else:
130 return self.block2
131
132
133
134 @property
136 if self.block1.active:
137 return self.block2
138 else:
139 return self.block1
140
141
142
144 """
145 Start working loop
146 """
147
148 _dim = self.xyz.screen.get_cols_rows()
149
150 while True:
151 if self._stop:
152 break
153
154 canv = self.xyz.top.render(_dim, True)
155 self.xyz.screen.draw_screen(_dim, canv)
156
157 _input = self.xyz.input.get()
158
159 if _input:
160 try:
161 self._cmd.keypress(_dim, _input)
162 except Exception, e:
163 xyzlog.error(_(u"Error executing bind (%s): %s") %
164 (Shortcut(_input), unicode(e)))
165 xyzlog.debug(ustring(traceback.format_exc(),
166 self._enc))
167
168 if self.xyz.input.resized:
169 self._resize = True
170
171 if self._resize:
172 self._resize = False
173 _dim = self.xyz.screen.get_cols_rows()
174 _bsize = libxyz.ui.Size(rows=_dim[1] - 1,
175 cols=_dim[0] / 2 - 2)
176
177 self.block1.size = _bsize
178 self.block2.size = _bsize
179 self._cmd._invalidate()
180 self.block1._invalidate()
181 self.block2._invalidate()
182
183
184
186 """
187 Set virtual plugins
188 """
189
190
191 _run_plugin = libxyz.core.plugins.VirtualPlugin(self.xyz, u"run")
192 _run_plugin.export(self.shutdown)
193 _run_plugin.export(self.repaint)
194
195 _run_plugin.VERSION = u"0.1"
196 _run_plugin.AUTHOR = u"Max E. Kuznecov <syhpoon@syhpoon.name>"
197 _run_plugin.BRIEF_DESCRIPTION = _(u"System runtime plugin")
198 _run_plugin.HOMEPAGE = u"xyzcmd.syhpoon.name"
199
200
201 _panel_plugin = libxyz.core.plugins.VirtualPlugin(self.xyz, u"panel")
202 _panel_plugin.export(self.entry_next)
203 _panel_plugin.export(self.entry_prev)
204 _panel_plugin.export(self.entry_top)
205 _panel_plugin.export(self.entry_bottom)
206 _panel_plugin.export(self.block_next)
207 _panel_plugin.export(self.block_prev)
208 _panel_plugin.export(self.switch_active)
209 _panel_plugin.export(self.get_selected)
210 _panel_plugin.export(self.get_tagged)
211 _panel_plugin.export(self.get_untagged)
212 _panel_plugin.export(self.get_current)
213 _panel_plugin.export(self.get_all)
214 _panel_plugin.export(self.get_active)
215 _panel_plugin.export(self.toggle_tag)
216 _panel_plugin.export(self.tag_all)
217 _panel_plugin.export(self.untag_all)
218 _panel_plugin.export(self.tag_invert)
219 _panel_plugin.export(self.tag_rule)
220 _panel_plugin.export(self.untag_rule)
221 _panel_plugin.export(self.tag_diff)
222 _panel_plugin.export(self.swap_blocks)
223 _panel_plugin.export(self.reload)
224 _panel_plugin.export(self.reload_all)
225 _panel_plugin.export(self.action)
226 _panel_plugin.export(self.chdir)
227 _panel_plugin.export(self.search_forward)
228 _panel_plugin.export(self.search_backward)
229 _panel_plugin.export(self.search_cycle)
230 _panel_plugin.export(self.show_tagged)
231 _panel_plugin.export(self.select)
232 _panel_plugin.export(self.cwd)
233 _panel_plugin.export(self.vfs_driver)
234 _panel_plugin.export(self.filter)
235 _panel_plugin.export(self.sort)
236 _panel_plugin.export(self.new_tab)
237 _panel_plugin.export(self.del_tab)
238 _panel_plugin.export(self.switch_tab)
239 _panel_plugin.export(self.next_tab)
240 _panel_plugin.export(self.prev_tab)
241 _panel_plugin.export(self.get_tabs)
242 _panel_plugin.export(self.active_tab)
243
244 _panel_plugin.VERSION = u"0.1"
245 _panel_plugin.AUTHOR = u"Max E. Kuznecov <syhpoon@syhpoon.name>"
246 _panel_plugin.BRIEF_DESCRIPTION = _(u"Panel plugin")
247 _panel_plugin.HOMEPAGE = u"xyzcmd.syhpoon.name"
248 _panel_plugin.DOC = _(u"""\
249 Configuration variables:
250 filters_enabled - Enable permanent filters. Default - False
251 filters_policy - Filters policy.
252 If True - filter out objects matching the rule.
253 If False - filter out objects which do not match the rule. Default - True
254 filters - List of permanent filters.
255 Filters applied in defined order sequentially. Default - []
256 sorting_policy - Active sorting policy name or None. Default - None
257 sorting - Defined sorting policies. Each key corresponds to a policy name
258 and value is either a function with two arguments (VFSObject) behaving
259 like cmp() or a list of those functions. If value is a list, each function
260 applied sequentially. Default - []""")
261
262 _panel_plugin.EVENTS = [
263 (u"before_switch_tab",
264 _(u"Fires before switching to another tab. "\
265 u"Arguments: Block instance and old tab index.")),
266
267 (u"switch_tab",
268 _(u"Fires when switching to another tab. "\
269 u"Arguments: Block instance and new tab index.")),
270
271 (u"new_tab",
272 _(u"Fires when new tab is added. "\
273 u"Arguments: Block instance and new tab index.")),
274
275 (u"del_tab",
276 _(u"Fires when tab is delete. "\
277 u"Arguments: Block instance and deleted tab index.")),
278 ]
279
280 self.xyz.pm.register(_run_plugin)
281 self.xyz.pm.register(_panel_plugin)
282
283
284
302
303
304
306 """
307 Repaint screen
308 """
309
310 self._resize = True
311 self.xyz.screen.clear()
312
313
314
315 - def entry_next(self):
316 """
317 Next entry
318 """
319
320 return self.active.next()
321
322
323
324 - def entry_prev(self):
325 """
326 Previous entry
327 """
328
329 return self.active.prev()
330
331
332
333 - def entry_top(self):
334 """
335 Top entry
336 """
337
338 return self.active.top()
339
340
341
342 - def entry_bottom(self):
343 """
344 Bottom entry
345 """
346
347 return self.active.bottom()
348
349
350
362
363
364
371
372
373
380
381
382
384 """
385 Get selected VFSObject instance
386 """
387
388 if active:
389 obj = self.active
390 else:
391 obj = self.inactive
392
393 return obj.get_selected()
394
395
396
398 """
399 Return list of tagged VFSObject instances
400 """
401
402 if active:
403 obj = self.active
404 else:
405 obj = self.inactive
406
407 return obj.get_tagged()
408
409
410
412 """
413 Return list of not tagged VFSObject instances
414 """
415
416 if active:
417 obj = self.active
418 else:
419 obj = self.inactive
420
421 return obj.get_untagged()
422
423
424
426 """
427 Return VFSObject instance of selected entry
428 """
429
430 if active:
431 obj = self.active
432 else:
433 obj = self.inactive
434
435 return obj.get_current()
436
437
438
440 """
441 Return list of tagged VFSObject instances or list of single selected
442 object if none tagged
443 """
444
445 return self.get_tagged() or [self.get_selected()]
446
447
448
450 """
451 Return list of VFSObject instances in panel
452 """
453
454 if active:
455 obj = self.active
456 else:
457 obj = self.inactive
458
459 return obj.get_all()
460
461
462
474
475
476
478 """
479 Tag every single object in current dir
480 """
481
482 if active:
483 obj = self.active
484 else:
485 obj = self.inactive
486
487 return obj.tag_all()
488
489
490
492 """
493 Untag every single object in current dir
494 """
495
496 if active:
497 obj = self.active
498 else:
499 obj = self.inactive
500
501 return obj.untag_all()
502
503
504
506 """
507 Invert currently tagged files
508 """
509
510 if active:
511 obj = self.active
512 else:
513 obj = self.inactive
514
515 return obj.tag_invert()
516
517
518
520 """
521 Tag files by combined rule
522 """
523
524 if active:
525 obj = self.active
526 else:
527 obj = self.inactive
528
529 return obj.tag_rule()
530
531
532
534 """
535 Untag files by combined rules
536 """
537
538 if active:
539 obj = self.active
540 else:
541 obj = self.inactive
542
543 return obj.untag_rule()
544
545
546
548 """
549 Tag all the objects in active panel which are missing from the inactive
550 one
551 """
552
553 if active:
554 obj = self.active
555 else:
556 obj = self.inactive
557
558 return obj.tag_diff()
559
560
561
563 """
564 Swap panel blocks
565 """
566
567 self.block1, self.block2 = self.block2, self.block1
568 self._compose()
569
570 if hasattr(self, "set_w"):
571 self.set_w(self._widget)
572 else:
573 self._w = self._widget
574
575
576
577 - def reload(self, active=True):
578 """
579 Reload contents
580 """
581
582 if active:
583 obj = self.active
584 else:
585 obj = self.inactive
586
587 return obj.reload()
588
589
590
598
599
600
601 - def action(self, active=True):
602 """
603 Perfrom action on selected object
604 """
605
606 if active:
607 obj = self.active
608 else:
609 obj = self.inactive
610
611 return obj.action()
612
613
614
615 - def chdir(self, path, active=True):
626
627
628
635
636
637
644
645
646
648 """
649 Enable cyclic search-when-you-type mode
650 """
651
652 self.active.search_cycle()
653
654
655
667
668
669
670 - def select(self, name, active=True):
671 """
672 Select VFS object by given name in current directory
673 """
674
675 if active:
676 obj = self.active
677 else:
678 obj = self.inactive
679
680 return obj.select(name)
681
682
683
684 - def cwd(self, active=True):
685 """
686 Get current working directory
687 """
688
689 if active:
690 obj = self.active
691 else:
692 obj = self.inactive
693
694 return obj.cwd
695
696
697
699 """
700 Return vfs driver used by object. None stands for LocalVFS
701 """
702
703 if active:
704 obj = self.active
705 else:
706 obj = self.inactive
707
708 return obj.vfs_driver
709
710
711
713 """
714 Filter objects
715 """
716
717 if not self.conf["filters_enabled"]:
718 return objects
719
720 policy = self.conf["filters_policy"]
721
722 def policyf(res):
723 if policy == True:
724 result = not res
725 else:
726 result = res
727
728 return result
729
730 for f in self.filters:
731 objects = [x for x in objects if policyf(f.match(x))]
732
733 return objects
734
735
736
737 - def sort(self, objects):
738 """
739 Sort objects
740 """
741
742 policy = self.conf["sorting_policy"]
743
744 if policy is None:
745 return objects
746
747 if policy not in self.conf["sorting"]:
748 xyzlog.warning(_(u"Unable to find `%s` sorting policy") %
749 ustring(policy))
750 return objects
751
752 policy_data = self.conf["sorting"][policy]
753
754 if is_func(policy_data):
755 objects.sort(cmp=policy_data)
756 elif isinstance(policy_data, list):
757 for f in policy_data:
758 objects.sort(cmp=f)
759
760 return objects
761
762
763
764 - def new_tab(self, tabname=None, active=True):
765 """
766 Create new tab
767 """
768
769 if active:
770 obj = self.active
771 else:
772 obj = self.inactive
773
774 return obj.tab_bar.new_tab(tabname)
775
776
777
778 - def del_tab(self, index=None, active=True):
779 """
780 Delete tab. If index is None - delete current tab
781 """
782
783 if active:
784 obj = self.active
785 else:
786 obj = self.inactive
787
788 return obj.tab_bar.del_tab(index)
789
790
791
803
804
805
807 """
808 Switch to the next tab
809 """
810
811 if active:
812 obj = self.active
813 else:
814 obj = self.inactive
815
816 return obj.tab_bar.next_tab()
817
818
819
821 """
822 Switch to the previous tab
823 """
824
825 if active:
826 obj = self.active
827 else:
828 obj = self.inactive
829
830 return obj.tab_bar.prev_tab()
831
832
833
835 """
836 Return list of tabs in format:
837 [(path, selected_name)]
838 """
839
840 if active:
841 obj = self.active
842 else:
843 obj = self.inactive
844
845 return obj.get_tabs()
846
847
848
850 """
851 Get active tab index
852 """
853
854 if active:
855 obj = self.active
856 else:
857 obj = self.inactive
858
859 return obj.tab_bar.active_tab
860
861
862
863 -class Block(lowui.FlowWidget):
864 """
865 Single panel block
866 """
867
868 - def __init__(self, xyz, size, path, enc, active=False):
869 """
870 @param xyz: XYZData instance
871 @param size: Block widget size
872 @type size: L{libxyz.ui.Size}
873 @param enc: Local encoding
874 @param active: Boolean flag, True if block is active
875
876 Required resources: cwdtitle, cwdtitleinact, panel, cursor, info
877 border, tagged
878 """
879
880 self.xyz = xyz
881 self.size = size
882 self.attr = lambda x: self.xyz.skin.attr(Panel.resolution, x)
883
884 self.term_width = lambda x: lowui.util.calc_width(x, 0, len(x))
885
886 self.active = active
887 self.selected = 0
888 self.cwd = path
889
890 self._display = []
891 self._vindex = 0
892 self._from = 0
893 self._to = 0
894 self._force_reload = False
895 self.entries = BlockEntries(self.xyz, [])
896 self._dir = None
897 self._vfsobj = None
898 self._title = u""
899 self._tagged = []
900 self._tab_data = []
901 self._is_xterm = os.getenv("TERM", None) == "xterm"
902
903 self._cursor_attr = None
904 self._custom_info = None
905 self._keys = libxyz.ui.Keys()
906 self._cmd = self.xyz.pm.load(":sys:cmd")
907 self._filter = self.xyz.pm.from_load(":sys:panel", "filter")
908 self._sort = self.xyz.pm.from_load(":sys:panel", "sort")
909
910 self._pending = libxyz.core.Queue(20)
911 self._re_raw = r".*"
912 self._rule_raw = ""
913 self._enc = enc
914 self.vfs_driver = None
915
916 self.tab_bar = TabBar(self.xyz, self.attr, self)
917
918 self._winfo = lowui.Text(u"")
919 self._sep = libxyz.ui.Separator()
920
921 _info = self._make_info()
922 _title_attr = self._get_title_attr()
923
924 self.frame = lowui.Frame(lowui.Filler(lowui.Text("")), footer=_info)
925
926 self.border = libxyz.ui.Border(self.frame, self._title,
927 _title_attr, self.attr(u"border"))
928 self.block = lowui.Frame(
929 lowui.AttrWrap(self.border, self.attr(u"panel")),
930 header=self.tab_bar)
931
932 self.xyz.hm.register(Panel.EVENT_BEFORE_SWITCH_TAB,
933 self._before_switch_tab_hook)
934 self.xyz.hm.register(Panel.EVENT_SWITCH_TAB, self._switch_tab_hook)
935 self.xyz.hm.register(Panel.EVENT_NEW_TAB, self._new_tab_hook)
936 self.xyz.hm.register(Panel.EVENT_DEL_TAB, self._del_tab_hook)
937
938 self.tab_bar.new_tab()
939
940 self._setup(self.xyz.vfs.dispatch(path, self._enc))
941
942 super(Block, self).__init__()
943
944
945
946 - def rows(self, (maxcol,), focus=False):
949
950
951
954
955
956
958 _parent, _dir, _entries = vfsobj.walk()
959
960 self._dir = _dir
961
962 _entries = self._filter(_entries)
963 _entries = self._sort(_entries)
964 _entries.insert(0, _parent)
965
966 self._title = truncate(_dir.full_path, self.size.cols - 4,
967 self._enc, True)
968
969 if hasattr(self, "border"):
970 self.border.set_title(self._title)
971
972 self._tagged = []
973
974 self.entries = _entries
975 self._vfsobj = vfsobj
976 self.vfs_driver = vfsobj.driver
977 self._force_reload = True
978
979
980
983
984
985
986 - def render(self, (maxcol,), focus=False):
987 """
988 Render block
989 """
990
991 w = self.display_widget((maxcol,), focus)
992 maxrow = w.rows((maxcol,), focus)
993
994
995 maxcol_orig, maxcol = maxcol, maxcol - 2
996 maxrow_orig, maxrow = maxrow, maxrow - 5
997
998
999 while True:
1000 try:
1001 _act = self._pending.pop()
1002 except IndexError:
1003 break
1004 else:
1005 _act(maxcol, maxrow)
1006
1007 if self._custom_info is not None:
1008 self._set_custom_info(self._custom_info, maxcol)
1009 else:
1010 self._set_info(self.entries[self.selected], maxcol)
1011
1012 _tlen = len(self._tagged)
1013
1014 if _tlen > 0:
1015 _text = _(u"%s bytes (%d)") % (
1016 self._make_number_readable(
1017 reduce(lambda x, y: x + y,
1018 [self.entries[x].size for x in self._tagged
1019 if isinstance(self.entries[x].ftype, VFSTypeFile)
1020 ], 0)), _tlen)
1021
1022 self._sep.set_text(bstring(_text, self._enc),
1023 self.attr(u"tagged"))
1024 else:
1025 self._sep.clear_text()
1026
1027 self._display = self._get_visible(maxrow, maxcol, self._force_reload)
1028 self._force_reload = False
1029
1030 _len = len(self._display)
1031
1032 canvases = []
1033
1034 for i in xrange(0, _len):
1035 _text = self._display[i]
1036 _own_attr = None
1037 _abs_i = self._from + i
1038
1039 if self._cursor_attr is not None and i == self._vindex:
1040 _own_attr = self._cursor_attr
1041 elif self.active and i == self._vindex:
1042 _own_attr = self.attr(u"cursor")
1043 elif _abs_i in self._tagged:
1044 _own_attr = self.attr(u"tagged")
1045 elif _abs_i in self.entries.palettes:
1046 _own_attr = self.entries.palettes[_abs_i]
1047
1048 if _own_attr is not None:
1049 x = lowui.AttrWrap(lowui.Text(bstring(_text, self._enc)),
1050 _own_attr).render((maxcol,))
1051 canvases.append((x, i, False))
1052 else:
1053 canvases.append((lowui.Text(_text).render((maxcol,)),
1054 i, False))
1055
1056 if _len < maxrow:
1057 _pad = lowui.AttrWrap(lowui.Text(" "), self.attr(u"panel"))
1058 canvases.append((_pad.render((maxcol,), focus), 0, False))
1059
1060 _info = self._make_info()
1061 self.frame.set_footer(_info)
1062
1063 combined = lowui.CanvasCombine(canvases)
1064 border = self.block.render((maxcol_orig, maxrow_orig), focus)
1065
1066 if _len > maxrow:
1067 combined.trim_end(_len - maxrow)
1068
1069 return lowui.CanvasOverlay(combined, border, 1, 2)
1070
1071
1072
1078
1079
1080
1082 _res = []
1083
1084 i = 0
1085 _sep = False
1086
1087 for x in reversed(unicode(num)):
1088 if _sep:
1089 _res.append(u"_")
1090 _sep = False
1091
1092 _res.append(x)
1093
1094 if i > 0 and (i + 1) % 3 == 0:
1095 _sep = True
1096
1097 i += 1
1098
1099 _res.reverse()
1100
1101 return u"".join(_res)
1102
1103
1104
1106 """
1107 Get currently visible piece of entries
1108 """
1109
1110 _from, _to, self._vindex = self._update_vindex(rows)
1111
1112 if reload or ((_from, _to) != (self._from, self._to)):
1113 self._from, self._to = _from, _to
1114 self._display = []
1115
1116 for _obj in self.entries[self._from:self._to]:
1117 _text = "%s%s "% (_obj.vtype, _obj.name)
1118 _text = truncate(_text, cols, self._enc)
1119 self._display.append(_text)
1120
1121 return self._display
1122
1123
1124
1126 """
1127 Return title attr
1128 """
1129
1130 if self.active:
1131 return self.attr(u"cwdtitle")
1132 else:
1133 return self.attr(u"cwdtitleinact")
1134
1135
1136
1138 """
1139 Set info text
1140 """
1141
1142 _part2 = vfsobj.info
1143 _part1 = truncate(vfsobj.visual, cols - len(_part2) - 2, self._enc)
1144
1145 _text = u"%s%s%s" % (_part1,
1146 u" " * (cols - (self.term_width(_part1) +
1147 self.term_width(_part2)) -
1148 1), _part2)
1149
1150 self._winfo.set_text(bstring(_text, self._enc))
1151
1152
1153
1155 """
1156 Set custom info text
1157 """
1158
1159 _text = truncate(custom_text, cols, self._enc, True)
1160 self._winfo.set_text(bstring(_text, self._enc))
1161
1162
1163
1165 """
1166 Calculate vindex according to selected position
1167 """
1168
1169 pos = self.selected
1170
1171 _from = pos / rows * rows
1172 _to = _from + rows
1173 _vindex = pos - (rows * (pos / rows))
1174
1175 return (_from, _to, _vindex)
1176
1177
1178
1179 @refresh
1187
1188
1189
1190 @refresh
1199
1200
1201
1202 @refresh
1204 """
1205 Next entry
1206 """
1207
1208 if self.selected < self.entries.length - 1:
1209 self.selected += 1
1210
1211
1212
1213 @refresh
1215 """
1216 Previous entry
1217 """
1218
1219 if self.selected > 0:
1220 self.selected -= 1
1221
1222
1223
1224 @refresh
1226 """
1227 Top entry
1228 """
1229
1230 self.selected = 0
1231
1232
1233
1234 @refresh
1236 """
1237 Bottom entry
1238 """
1239
1240 self.selected = self.entries.length - 1
1241
1242
1243
1244 @refresh
1246 """
1247 One block down
1248 """
1249
1250 def _do_next_block(cols, rows):
1251 if self.selected + rows >= self.entries.length:
1252 return self.bottom()
1253 else:
1254 self.selected += rows
1255
1256
1257
1258
1259
1260
1261 self._pending.push(_do_next_block)
1262
1263
1264
1265 @refresh
1267 """
1268 One block up
1269 """
1270
1271 def _do_prev_block(cols, rows):
1272 if self.selected - rows < 0:
1273 return self.top()
1274 else:
1275 self.selected -= rows
1276
1277
1278
1279 self._pending.push(_do_prev_block)
1280
1281
1282
1284 """
1285 Get selected VFSObject instance
1286 """
1287
1288 return self.entries[self.selected]
1289
1290
1291
1293 """
1294 Get VFSObject instance of current directory
1295 """
1296
1297 return self._vfsobj
1298
1299
1300
1302 """
1303 Return list of VFSObject instances in panel
1304 """
1305
1306 return self.entries
1307
1308
1309
1311 """
1312 Return list of tagged VFSObject instances
1313 """
1314
1315 return [self.entries[x] for x in self._tagged]
1316
1317
1318
1320 """
1321 Return list of not tagged VFSObject instances
1322 """
1323
1324 return [self.entries[x] for x in xrange(self.entries.length)
1325 if x not in self._tagged]
1326
1327
1328
1330 """
1331 Toggle tagged selected file
1332 """
1333
1334 if self.selected in self._tagged:
1335 self._tagged.remove(self.selected)
1336 else:
1337 self._tagged.append(self.selected)
1338
1339 self.next()
1340
1341
1342
1343 @refresh
1345 """
1346 Tag files by combined rule
1347 """
1348
1349 self._tag_rule(tag=True)
1350
1351
1352
1353 @refresh
1355 """
1356 Untag files by combined rule
1357 """
1358
1359 self._tag_rule(tag=False)
1360
1361
1362
1364 """
1365 Tag engine
1366 """
1367
1368 if tag:
1369 _title = _(u"Tag group")
1370 else:
1371 _title = _(u"Untag group")
1372
1373 _input = libxyz.ui.InputBox(self.xyz, self.xyz.top,
1374 _("Type FS Rule"),
1375 title=_title, text=self._rule_raw)
1376
1377 _raw = _input.show()
1378
1379 if _raw is None:
1380 return
1381 else:
1382 self._rule_raw = _raw
1383
1384 try:
1385 _rule = libxyz.core.FSRule(ustring(_raw, self._enc))
1386 except libxyz.exceptions.ParseError, e:
1387 xyzlog.error(unicode(e))
1388 return
1389
1390 try:
1391 if tag:
1392 self._tagged = [i for i in xrange(self.entries.length) if
1393 _rule.match(self.entries[i])]
1394 else:
1395 self._tagged = [i for i in self._tagged if not
1396 _rule.match(self.entries[i])]
1397 except libxyz.exceptions.FSRuleError, e:
1398 self._tagged = []
1399
1400 xyzlog.error(unicode(e))
1401 return
1402
1403
1404
1405 @refresh
1407 """
1408 Invert currently tagged files
1409 """
1410
1411 self._tagged = [i for i in xrange(self.entries.length)
1412 if i not in self._tagged]
1413
1414
1415
1416 @refresh
1418 """
1419 Tag every single object in current dir
1420 """
1421
1422 self._tagged = [i for i in xrange(self.entries.length) if
1423 self.entries[i].name != ".."]
1424
1425
1426
1427 @refresh
1429 """
1430 Untag every single object in current dir
1431 """
1432
1433 self._tagged = []
1434
1435
1436
1437 @refresh
1439 """
1440 Tag all the objects in active panel which are missing from the inactive
1441 one
1442 """
1443
1444 inactive_names = [x.name for x in
1445 self.xyz.pm.from_load(":sys:panel", "get_all")(
1446 active=False)]
1447
1448 self._tagged = [i for i in xrange(self.entries.length) if
1449 self.entries[i].name not in inactive_names]
1450
1451
1452
1453 @refresh
1455 """
1456 Reload contents
1457 """
1458
1459 _selected = self.entries[self.selected]
1460
1461 self._setup(self._vfsobj)
1462
1463 if self.selected >= self.entries.length:
1464 self.selected = self.entries.length - 1
1465
1466
1467 if self.entries[self.selected].name != _selected.name:
1468 self.select(_selected.name)
1469
1470
1471
1472 @refresh
1474 """
1475 Select VFS object by given name in current directory
1476 """
1477
1478 for i in xrange(self.entries.length):
1479 if self.entries[i].name == name:
1480 self.selected = i
1481 break
1482
1483
1484
1486 """
1487 Perform action on selected file
1488 """
1489
1490 _selected = self.entries[self.selected]
1491
1492 _action = self.xyz.am.match(_selected)
1493
1494 if _action is not None:
1495 try:
1496 _action(_selected)
1497 except Exception, e:
1498 xyzlog.error(_(u"Action error: %s") % (unicode(e)))
1499
1500
1501
1502 @refresh
1503 - def chdir(self, path, reload=True, active=True):
1504 """
1505 Change directory
1506 If reload is not True only execute os.chdir, without reloading
1507 directory contents
1508 If active is False do not call os.chdir
1509 """
1510
1511 try:
1512 old_selected = self.entries[self.selected].name
1513 except IndexError:
1514 old_selected = None
1515
1516 if reload:
1517 _path = os.path.normpath(path)
1518 _parent = None
1519 _old_vfs = None
1520
1521 if self.entries:
1522 _parent = os.path.normpath(self.entries[0].full_path)
1523 _old = self._dir.name
1524 _old_vfs = self._vfsobj
1525
1526 try:
1527 _vfsobj = self.xyz.vfs.dispatch(path, self._enc)
1528 except libxyz.exceptions.VFSError, e:
1529 xyzlog.error(_(u"Unable to chdir to %s: %s") %
1530 (ustring(path), ustring(e)))
1531 return
1532
1533 try:
1534 self._setup(_vfsobj)
1535 except libxyz.exceptions.XYZRuntimeError, e:
1536 xyzlog.info(_(u"Unable to chdir to %s: %s") %
1537 (ustring(path), ustring(e)))
1538 return
1539
1540 self.selected = 0
1541
1542
1543 if _parent == _path:
1544 for x in xrange(self.entries.length):
1545 if self.entries[x].name == _old:
1546 self.selected = x
1547 break
1548
1549 if _old_vfs:
1550 del(_old_vfs)
1551
1552 self.cwd = path
1553 self._tab_data[self.tab_bar.active_tab] = (path, old_selected)
1554
1555 if path == os.path.sep:
1556 new_tab_name = path
1557 else:
1558 new_tab_name = os.path.basename(path)
1559
1560 self.tab_bar.rename_tab(self.tab_bar.active_tab,
1561 truncate(new_tab_name, 15, self._enc))
1562
1563
1564 if isinstance(self._vfsobj, LocalVFSObject) and active:
1565 os.chdir(path)
1566
1567 self._update_xterm_title(path)
1568
1569
1570
1571 @refresh
1573 """
1574 Search forward for matching object while user types
1575 """
1576
1577 return self._search_engine(lambda x: (xrange(x, self.entries.length)))
1578
1579
1580
1581 @refresh
1583 """
1584 Search backward for matching object while user types
1585 """
1586
1587 return self._search_engine(lambda x: (xrange(x, 0, -1)))
1588
1589
1590
1592 """
1593 Search from current position downwards and then from top to
1594 currently selected
1595 """
1596
1597 return self._search_engine(lambda x: range(x, self.entries.length) +
1598 range(0, x))
1599
1600
1601
1602 @refresh
1604 """
1605 Show only tagged entries
1606 """
1607
1608 if not self._tagged:
1609 return
1610
1611 self.entries = BlockEntries(self.xyz,
1612 [self.entries[x] for x in self._tagged])
1613 self.selected = 0
1614 self._tagged = []
1615
1616 _tagged = _(u"TAGGED")
1617
1618 if not self._title.endswith(_tagged):
1619 self._title = truncate(u"%s:%s" % (self._title, _tagged),
1620 self.size.cols - 4, self._enc, True)
1621
1622 if hasattr(self, "border"):
1623 self.border.set_title(self._title)
1624
1625 self._force_reload = True
1626
1627
1628
1630 """
1631 Search for matching filenames while user types
1632 @param order: A function that returns generator for search order
1633 @param pattern: A search type pattern
1634 """
1635
1636 self._cursor_attr = self.attr(u"search")
1637
1638 if pattern is None:
1639
1640 pattern = lambda pat, obj: ustring(pat) in ustring(obj)
1641
1642
1643
1644 _dim = self.xyz.screen.get_cols_rows()
1645 _collected = []
1646
1647 _current_pos = self.selected
1648 _current_pos_orig = self.selected
1649 _skip = False
1650
1651
1652 while True:
1653 self._custom_info = u"".join(_collected)
1654
1655 self._invalidate()
1656 self.xyz.screen.draw_screen(_dim, self.xyz.top.render(_dim, True))
1657
1658 try:
1659 _raw = self.xyz.input.get()
1660
1661 if self.xyz.input.WIN_RESIZE in _raw:
1662 _dim = self.xyz.screen.get_cols_rows()
1663 continue
1664
1665 if self._keys.ESCAPE in _raw or self._keys.ENTER in _raw:
1666 self._invalidate()
1667 break
1668 elif self._keys.BACKSPACE in _raw:
1669 _current_pos = _current_pos_orig
1670 if _collected:
1671 _collected.pop()
1672
1673 elif self._keys.DOWN in _raw:
1674 _skip = True
1675
1676 _tmp = _collected[:]
1677 _tmp.extend([ustring(x, self._enc) for x in _raw
1678 if len(x) == 1])
1679 _pattern = u"".join(_tmp)
1680 except Exception:
1681 break
1682
1683
1684 for i in order(_current_pos):
1685 if pattern(_pattern, self.entries[i].name):
1686 if _skip:
1687 _skip = False
1688 _current_pos = i + 1
1689 continue
1690
1691 self.selected = i
1692 _collected = _tmp
1693 break
1694
1695 self._cursor_attr = None
1696 self._custom_info = None
1697
1698
1699
1701 """
1702 Before switch tab hook
1703 """
1704
1705
1706 if block is not self:
1707 return
1708
1709 try:
1710
1711 path = self._tab_data[index][0]
1712 self._tab_data[index] = (path, self.entries[self.selected].name)
1713
1714 self.chdir(path)
1715 except IndexError:
1716 pass
1717
1718
1719
1721 """
1722 Switch tab hook
1723 """
1724
1725
1726 if block is not self:
1727 return
1728
1729 try:
1730 path, name = self._tab_data[index]
1731
1732 self.chdir(path)
1733
1734 if name is not None:
1735 self.select(name)
1736 except IndexError:
1737 pass
1738
1739
1740
1742 """
1743 New tab hook
1744 """
1745
1746
1747 if block is not self:
1748 return
1749
1750 try:
1751 selected = self.entries[self.selected].name
1752 except IndexError:
1753 selected = None
1754
1755 self._tab_data.append((self.cwd, selected))
1756
1757
1758
1760 """
1761 Delete tab hook
1762 """
1763
1764
1765 if block is not self:
1766 return
1767
1768 try:
1769 del(self._tab_data[index])
1770 except IndexError:
1771 pass
1772
1773
1774
1776 """
1777 Return list of open tabs
1778 """
1779
1780 return self._tab_data
1781
1782
1783
1785 """
1786 Update xterm title string
1787 """
1788
1789 if self._is_xterm:
1790 sys.stdout.write("\33]2;%s\7" % title)
1791
1792
1793
1794 -class TabBar(lowui.FlowWidget):
1795 """
1796 Tabs bar
1797 """
1798
1808
1809
1810
1811 active_tab = property(lambda self: self._active_tab)
1812
1813
1814
1815 @refresh
1817 """
1818 Add new tab
1819 """
1820
1821 if tabname is None:
1822 tabname = "Tab"
1823
1824 self._tabs.append(tabname)
1825 newidx = len(self._tabs) - 1
1826
1827 self.xyz.hm.dispatch(Panel.EVENT_NEW_TAB, self.block, newidx)
1828 self.switch_tab(newidx)
1829
1830
1831
1832 @refresh
1834 """
1835 Delete tab by index
1836 """
1837
1838 if index is None:
1839 index = self._active_tab
1840
1841 _len = len(self._tabs)
1842
1843 if _len > 1 and index < _len:
1844 del(self._tabs[index])
1845
1846 if self._active_tab >= len(self._tabs):
1847 self._active_tab -= 1
1848
1849 self.xyz.hm.dispatch(Panel.EVENT_DEL_TAB, self.block, index)
1850
1851 self.switch_tab(self._active_tab)
1852
1853
1854
1855 @refresh
1867
1868
1869
1870 @refresh
1872 """
1873 Switch to the next tab
1874 """
1875
1876 index = self._active_tab + 1
1877
1878 if index >= len(self._tabs):
1879 index = 0
1880
1881 self.switch_tab(index)
1882
1883
1884
1885 @refresh
1887 """
1888 Switch to the previous tab
1889 """
1890
1891 index = self._active_tab - 1
1892
1893 if index < 0:
1894 index = len(self._tabs) - 1
1895
1896 self.switch_tab(index)
1897
1898
1899
1900 @refresh
1902 """
1903 Rename tab at index
1904 """
1905
1906 if index >= len(self._tabs):
1907 return
1908 else:
1909 self._tabs[index] = new_name
1910
1911
1912
1913 - def render(self, (maxcol,), focus=False):
1914 """
1915 Render the tab bar
1916 """
1917
1918 make_c = lambda text, at: lowui.AttrWrap(
1919 lowui.Text(text), self._attr(at)).render((maxcol,))
1920
1921 canvases = []
1922
1923 length = 0
1924
1925 for idx in xrange(len(self._tabs)):
1926 tabname = self._gen_tab_name(self._tabs[idx], idx)
1927 length += len(tabname)
1928
1929 if idx == self._active_tab:
1930 canv = make_c(tabname, "tabact")
1931 else:
1932 canv = make_c(tabname, "tabbar")
1933
1934 canvases.append((canv, None, False, len(tabname)))
1935
1936
1937 if length < maxcol:
1938 canvases.append((make_c("", "tabbar"), None, False,
1939 maxcol - length))
1940
1941 combined = lowui.CanvasJoin(canvases)
1942
1943 if length > maxcol:
1944 more = lowui.AttrWrap(
1945 lowui.Text(" >>"),
1946 self._attr("tabbar")).render((3,))
1947
1948 combined.pad_trim_left_right(0, -(length - maxcol))
1949 combined.overlay(more, maxcol - 3, 0)
1950
1951 return combined
1952
1953
1954
1955 - def rows(self, (maxcol,), focus=False):
1956 """
1957 Return the number of lines that will be rendered
1958 """
1959
1960 return 1
1961
1962
1963
1965 return "{%d %s} " % (idx, tab)
1966