comparison config/ranger/commands.py @ 245:ca66a7fd5cc9

Add ranger configs
author zegervdv <zegervdv@me.com>
date Wed, 18 Feb 2015 22:41:39 +0100
parents
children
comparison
equal deleted inserted replaced
244:50379acb4815 245:ca66a7fd5cc9
1 # -*- coding: utf-8 -*-
2 # Copyright (C) 2009-2013 Roman Zimbelmann <[email protected]>
3 # This configuration file is licensed under the same terms as ranger.
4 # ===================================================================
5 # This file contains ranger's commands.
6 # It's all in python; lines beginning with # are comments.
7 #
8 # Note that additional commands are automatically generated from the methods
9 # of the class ranger.core.actions.Actions.
10 #
11 # You can customize commands in the file ~/.config/ranger/commands.py.
12 # It has the same syntax as this file. In fact, you can just copy this
13 # file there with `ranger --copy-config=commands' and make your modifications.
14 # But make sure you update your configs when you update ranger.
15 #
16 # ===================================================================
17 # Every class defined here which is a subclass of `Command' will be used as a
18 # command in ranger. Several methods are defined to interface with ranger:
19 # execute(): called when the command is executed.
20 # cancel(): called when closing the console.
21 # tab(): called when <TAB> is pressed.
22 # quick(): called after each keypress.
23 #
24 # The return values for tab() can be either:
25 # None: There is no tab completion
26 # A string: Change the console to this string
27 # A list/tuple/generator: cycle through every item in it
28 #
29 # The return value for quick() can be:
30 # False: Nothing happens
31 # True: Execute the command afterwards
32 #
33 # The return value for execute() and cancel() doesn't matter.
34 #
35 # ===================================================================
36 # Commands have certain attributes and methods that facilitate parsing of
37 # the arguments:
38 #
39 # self.line: The whole line that was written in the console.
40 # self.args: A list of all (space-separated) arguments to the command.
41 # self.quantifier: If this command was mapped to the key "X" and
42 # the user pressed 6X, self.quantifier will be 6.
43 # self.arg(n): The n-th argument, or an empty string if it doesn't exist.
44 # self.rest(n): The n-th argument plus everything that followed. For example,
45 # If the command was "search foo bar a b c", rest(2) will be "bar a b c"
46 # self.start(n): The n-th argument and anything before it. For example,
47 # If the command was "search foo bar a b c", rest(2) will be "bar a b c"
48 #
49 # ===================================================================
50 # And this is a little reference for common ranger functions and objects:
51 #
52 # self.fm: A reference to the "fm" object which contains most information
53 # about ranger.
54 # self.fm.notify(string): Print the given string on the screen.
55 # self.fm.notify(string, bad=True): Print the given string in RED.
56 # self.fm.reload_cwd(): Reload the current working directory.
57 # self.fm.thisdir: The current working directory. (A File object.)
58 # self.fm.thisfile: The current file. (A File object too.)
59 # self.fm.thistab.get_selection(): A list of all selected files.
60 # self.fm.execute_console(string): Execute the string as a ranger command.
61 # self.fm.open_console(string): Open the console with the given string
62 # already typed in for you.
63 # self.fm.move(direction): Moves the cursor in the given direction, which
64 # can be something like down=3, up=5, right=1, left=1, to=6, ...
65 #
66 # File objects (for example self.fm.thisfile) have these useful attributes and
67 # methods:
68 #
69 # cf.path: The path to the file.
70 # cf.basename: The base name only.
71 # cf.load_content(): Force a loading of the directories content (which
72 # obviously works with directories only)
73 # cf.is_directory: True/False depending on whether it's a directory.
74 #
75 # For advanced commands it is unavoidable to dive a bit into the source code
76 # of ranger.
77 # ===================================================================
78
79 import os
80 from ranger.api.commands import *
81 from ranger.core.loader import CommandLoader
82
83 class alias(Command):
84 """:alias <newcommand> <oldcommand>
85
86 Copies the oldcommand as newcommand.
87 """
88
89 context = 'browser'
90 resolve_macros = False
91
92 def execute(self):
93 if not self.arg(1) or not self.arg(2):
94 self.fm.notify('Syntax: alias <newcommand> <oldcommand>', bad=True)
95 else:
96 self.fm.commands.alias(self.arg(1), self.rest(2))
97
98 class cd(Command):
99 """:cd [-r] <dirname>
100
101 The cd command changes the directory.
102 The command 'cd -' is equivalent to typing ``.
103 Using the option "-r" will get you to the real path.
104 """
105
106 def execute(self):
107 import os.path
108 if self.arg(1) == '-r':
109 self.shift()
110 destination = os.path.realpath(self.rest(1))
111 if os.path.isfile(destination):
112 destination = os.path.dirname(destination)
113 else:
114 destination = self.rest(1)
115
116 if not destination:
117 destination = '~'
118
119 if destination == '-':
120 self.fm.enter_bookmark('`')
121 else:
122 self.fm.cd(destination)
123
124 def tab(self):
125 import os
126 from os.path import dirname, basename, expanduser, join
127
128 cwd = self.fm.thisdir.path
129 rel_dest = self.rest(1)
130
131 bookmarks = [v.path for v in self.fm.bookmarks.dct.values()
132 if rel_dest in v.path ]
133
134 # expand the tilde into the user directory
135 if rel_dest.startswith('~'):
136 rel_dest = expanduser(rel_dest)
137
138 # define some shortcuts
139 abs_dest = join(cwd, rel_dest)
140 abs_dirname = dirname(abs_dest)
141 rel_basename = basename(rel_dest)
142 rel_dirname = dirname(rel_dest)
143
144 try:
145 # are we at the end of a directory?
146 if rel_dest.endswith('/') or rel_dest == '':
147 _, dirnames, _ = next(os.walk(abs_dest))
148
149 # are we in the middle of the filename?
150 else:
151 _, dirnames, _ = next(os.walk(abs_dirname))
152 dirnames = [dn for dn in dirnames \
153 if dn.startswith(rel_basename)]
154 except (OSError, StopIteration):
155 # os.walk found nothing
156 pass
157 else:
158 dirnames.sort()
159 dirnames = bookmarks + dirnames
160
161 # no results, return None
162 if len(dirnames) == 0:
163 return
164
165 # one result. since it must be a directory, append a slash.
166 if len(dirnames) == 1:
167 return self.start(1) + join(rel_dirname, dirnames[0]) + '/'
168
169 # more than one result. append no slash, so the user can
170 # manually type in the slash to advance into that directory
171 return (self.start(1) + join(rel_dirname, dirname) for dirname in dirnames)
172
173
174 class chain(Command):
175 """:chain <command1>; <command2>; ...
176
177 Calls multiple commands at once, separated by semicolons.
178 """
179 def execute(self):
180 for command in self.rest(1).split(";"):
181 self.fm.execute_console(command)
182
183
184 class shell(Command):
185 escape_macros_for_shell = True
186
187 def execute(self):
188 if self.arg(1) and self.arg(1)[0] == '-':
189 flags = self.arg(1)[1:]
190 command = self.rest(2)
191 else:
192 flags = ''
193 command = self.rest(1)
194
195 if not command and 'p' in flags:
196 command = 'cat %f'
197 if command:
198 if '%' in command:
199 command = self.fm.substitute_macros(command, escape=True)
200 self.fm.execute_command(command, flags=flags)
201
202 def tab(self):
203 from ranger.ext.get_executables import get_executables
204 if self.arg(1) and self.arg(1)[0] == '-':
205 command = self.rest(2)
206 else:
207 command = self.rest(1)
208 start = self.line[0:len(self.line) - len(command)]
209
210 try:
211 position_of_last_space = command.rindex(" ")
212 except ValueError:
213 return (start + program + ' ' for program \
214 in get_executables() if program.startswith(command))
215 if position_of_last_space == len(command) - 1:
216 selection = self.fm.thistab.get_selection()
217 if len(selection) == 1:
218 return self.line + selection[0].shell_escaped_basename + ' '
219 else:
220 return self.line + '%s '
221 else:
222 before_word, start_of_word = self.line.rsplit(' ', 1)
223 return (before_word + ' ' + file.shell_escaped_basename \
224 for file in self.fm.thisdir.files \
225 if file.shell_escaped_basename.startswith(start_of_word))
226
227 class open_with(Command):
228 def execute(self):
229 app, flags, mode = self._get_app_flags_mode(self.rest(1))
230 self.fm.execute_file(
231 files = [f for f in self.fm.thistab.get_selection()],
232 app = app,
233 flags = flags,
234 mode = mode)
235
236 def tab(self):
237 return self._tab_through_executables()
238
239 def _get_app_flags_mode(self, string):
240 """Extracts the application, flags and mode from a string.
241
242 examples:
243 "mplayer f 1" => ("mplayer", "f", 1)
244 "aunpack 4" => ("aunpack", "", 4)
245 "p" => ("", "p", 0)
246 "" => None
247 """
248
249 app = ''
250 flags = ''
251 mode = 0
252 split = string.split()
253
254 if len(split) == 0:
255 pass
256
257 elif len(split) == 1:
258 part = split[0]
259 if self._is_app(part):
260 app = part
261 elif self._is_flags(part):
262 flags = part
263 elif self._is_mode(part):
264 mode = part
265
266 elif len(split) == 2:
267 part0 = split[0]
268 part1 = split[1]
269
270 if self._is_app(part0):
271 app = part0
272 if self._is_flags(part1):
273 flags = part1
274 elif self._is_mode(part1):
275 mode = part1
276 elif self._is_flags(part0):
277 flags = part0
278 if self._is_mode(part1):
279 mode = part1
280 elif self._is_mode(part0):
281 mode = part0
282 if self._is_flags(part1):
283 flags = part1
284
285 elif len(split) >= 3:
286 part0 = split[0]
287 part1 = split[1]
288 part2 = split[2]
289
290 if self._is_app(part0):
291 app = part0
292 if self._is_flags(part1):
293 flags = part1
294 if self._is_mode(part2):
295 mode = part2
296 elif self._is_mode(part1):
297 mode = part1
298 if self._is_flags(part2):
299 flags = part2
300 elif self._is_flags(part0):
301 flags = part0
302 if self._is_mode(part1):
303 mode = part1
304 elif self._is_mode(part0):
305 mode = part0
306 if self._is_flags(part1):
307 flags = part1
308
309 return app, flags, int(mode)
310
311 def _is_app(self, arg):
312 return not self._is_flags(arg) and not arg.isdigit()
313
314 def _is_flags(self, arg):
315 from ranger.core.runner import ALLOWED_FLAGS
316 return all(x in ALLOWED_FLAGS for x in arg)
317
318 def _is_mode(self, arg):
319 return all(x in '0123456789' for x in arg)
320
321
322 class set_(Command):
323 """:set <option name>=<python expression>
324
325 Gives an option a new value.
326 """
327 name = 'set' # don't override the builtin set class
328 def execute(self):
329 name = self.arg(1)
330 name, value, _ = self.parse_setting_line()
331 self.fm.set_option_from_string(name, value)
332
333 def tab(self):
334 name, value, name_done = self.parse_setting_line()
335 settings = self.fm.settings
336 if not name:
337 return sorted(self.firstpart + setting for setting in settings)
338 if not value and not name_done:
339 return (self.firstpart + setting for setting in settings \
340 if setting.startswith(name))
341 if not value:
342 return self.firstpart + str(settings[name])
343 if bool in settings.types_of(name):
344 if 'true'.startswith(value.lower()):
345 return self.firstpart + 'True'
346 if 'false'.startswith(value.lower()):
347 return self.firstpart + 'False'
348
349
350 class setlocal(set_):
351 """:setlocal path=<python string> <option name>=<python expression>
352
353 Gives an option a new value.
354 """
355 PATH_RE = re.compile(r'^\s*path="?(.*?)"?\s*$')
356 def execute(self):
357 import os.path
358 match = self.PATH_RE.match(self.arg(1))
359 if match:
360 path = os.path.normpath(os.path.expanduser(match.group(1)))
361 self.shift()
362 elif self.fm.thisdir:
363 path = self.fm.thisdir.path
364 else:
365 path = None
366
367 if path:
368 name = self.arg(1)
369 name, value, _ = self.parse_setting_line()
370 self.fm.set_option_from_string(name, value, localpath=path)
371
372
373 class setintag(setlocal):
374 """:setintag <tag or tags> <option name>=<option value>
375
376 Sets an option for directories that are tagged with a specific tag.
377 """
378 def execute(self):
379 tags = self.arg(1)
380 self.shift()
381 name, value, _ = self.parse_setting_line()
382 self.fm.set_option_from_string(name, value, tags=tags)
383
384
385 class quit(Command):
386 """:quit
387
388 Closes the current tab. If there is only one tab, quit the program.
389 """
390
391 def execute(self):
392 if len(self.fm.tabs) <= 1:
393 self.fm.exit()
394 self.fm.tab_close()
395
396
397 class quitall(Command):
398 """:quitall
399
400 Quits the program immediately.
401 """
402
403 def execute(self):
404 self.fm.exit()
405
406
407 class quit_bang(quitall):
408 """:quit!
409
410 Quits the program immediately.
411 """
412 name = 'quit!'
413 allow_abbrev = False
414
415
416 class terminal(Command):
417 """:terminal
418
419 Spawns an "x-terminal-emulator" starting in the current directory.
420 """
421 def execute(self):
422 import os
423 from ranger.ext.get_executables import get_executables
424 command = os.environ.get('TERMCMD', os.environ.get('TERM'))
425 if command not in get_executables():
426 command = 'x-terminal-emulator'
427 if command not in get_executables():
428 command = 'xterm'
429 self.fm.run(command, flags='f')
430
431
432 class delete(Command):
433 """:delete
434
435 Tries to delete the selection.
436
437 "Selection" is defined as all the "marked files" (by default, you
438 can mark files with space or v). If there are no marked files,
439 use the "current file" (where the cursor is)
440
441 When attempting to delete non-empty directories or multiple
442 marked files, it will require a confirmation.
443 """
444
445 allow_abbrev = False
446
447 def execute(self):
448 import os
449 if self.rest(1):
450 self.fm.notify("Error: delete takes no arguments! It deletes "
451 "the selected file(s).", bad=True)
452 return
453
454 cwd = self.fm.thisdir
455 cf = self.fm.thisfile
456 if not cwd or not cf:
457 self.fm.notify("Error: no file selected for deletion!", bad=True)
458 return
459
460 confirm = self.fm.settings.confirm_on_delete
461 many_files = (cwd.marked_items or (cf.is_directory and not cf.is_link \
462 and len(os.listdir(cf.path)) > 0))
463
464 if confirm != 'never' and (confirm != 'multiple' or many_files):
465 self.fm.ui.console.ask("Confirm deletion of: %s (y/N)" %
466 ', '.join(f.basename for f in self.fm.thistab.get_selection()),
467 self._question_callback, ('n', 'N', 'y', 'Y'))
468 else:
469 # no need for a confirmation, just delete
470 self.fm.delete()
471
472 def _question_callback(self, answer):
473 if answer == 'y' or answer == 'Y':
474 self.fm.delete()
475
476
477 class mark_tag(Command):
478 """:mark_tag [<tags>]
479
480 Mark all tags that are tagged with either of the given tags.
481 When leaving out the tag argument, all tagged files are marked.
482 """
483 do_mark = True
484
485 def execute(self):
486 cwd = self.fm.thisdir
487 tags = self.rest(1).replace(" ","")
488 if not self.fm.tags:
489 return
490 for fileobj in cwd.files:
491 try:
492 tag = self.fm.tags.tags[fileobj.realpath]
493 except KeyError:
494 continue
495 if not tags or tag in tags:
496 cwd.mark_item(fileobj, val=self.do_mark)
497 self.fm.ui.status.need_redraw = True
498 self.fm.ui.need_redraw = True
499
500
501 class console(Command):
502 """:console <command>
503
504 Open the console with the given command.
505 """
506 def execute(self):
507 position = None
508 if self.arg(1)[0:2] == '-p':
509 try:
510 position = int(self.arg(1)[2:])
511 self.shift()
512 except:
513 pass
514 self.fm.open_console(self.rest(1), position=position)
515
516
517 class load_copy_buffer(Command):
518 """:load_copy_buffer
519
520 Load the copy buffer from confdir/copy_buffer
521 """
522 copy_buffer_filename = 'copy_buffer'
523 def execute(self):
524 from ranger.container.file import File
525 from os.path import exists
526 try:
527 fname = self.fm.confpath(self.copy_buffer_filename)
528 f = open(fname, 'r')
529 except:
530 return self.fm.notify("Cannot open %s" % \
531 (fname or self.copy_buffer_filename), bad=True)
532 self.fm.copy_buffer = set(File(g) \
533 for g in f.read().split("\n") if exists(g))
534 f.close()
535 self.fm.ui.redraw_main_column()
536
537
538 class save_copy_buffer(Command):
539 """:save_copy_buffer
540
541 Save the copy buffer to confdir/copy_buffer
542 """
543 copy_buffer_filename = 'copy_buffer'
544 def execute(self):
545 fname = None
546 try:
547 fname = self.fm.confpath(self.copy_buffer_filename)
548 f = open(fname, 'w')
549 except:
550 return self.fm.notify("Cannot open %s" % \
551 (fname or self.copy_buffer_filename), bad=True)
552 f.write("\n".join(f.path for f in self.fm.copy_buffer))
553 f.close()
554
555
556 class unmark_tag(mark_tag):
557 """:unmark_tag [<tags>]
558
559 Unmark all tags that are tagged with either of the given tags.
560 When leaving out the tag argument, all tagged files are unmarked.
561 """
562 do_mark = False
563
564
565 class mkdir(Command):
566 """:mkdir <dirname>
567
568 Creates a directory with the name <dirname>.
569 """
570
571 def execute(self):
572 from os.path import join, expanduser, lexists
573 from os import mkdir
574
575 dirname = join(self.fm.thisdir.path, expanduser(self.rest(1)))
576 if not lexists(dirname):
577 mkdir(dirname)
578 else:
579 self.fm.notify("file/directory exists!", bad=True)
580
581 def tab(self):
582 return self._tab_directory_content()
583
584
585 class touch(Command):
586 """:touch <fname>
587
588 Creates a file with the name <fname>.
589 """
590
591 def execute(self):
592 from os.path import join, expanduser, lexists
593
594 fname = join(self.fm.thisdir.path, expanduser(self.rest(1)))
595 if not lexists(fname):
596 open(fname, 'a').close()
597 else:
598 self.fm.notify("file/directory exists!", bad=True)
599
600 def tab(self):
601 return self._tab_directory_content()
602
603
604 class edit(Command):
605 """:edit <filename>
606
607 Opens the specified file in vim
608 """
609
610 def execute(self):
611 if not self.arg(1):
612 self.fm.edit_file(self.fm.thisfile.path)
613 else:
614 self.fm.edit_file(self.rest(1))
615
616 def tab(self):
617 return self._tab_directory_content()
618
619
620 class eval_(Command):
621 """:eval [-q] <python code>
622
623 Evaluates the python code.
624 `fm' is a reference to the FM instance.
625 To display text, use the function `p'.
626
627 Examples:
628 :eval fm
629 :eval len(fm.directories)
630 :eval p("Hello World!")
631 """
632 name = 'eval'
633 resolve_macros = False
634
635 def execute(self):
636 if self.arg(1) == '-q':
637 code = self.rest(2)
638 quiet = True
639 else:
640 code = self.rest(1)
641 quiet = False
642 import ranger
643 global cmd, fm, p, quantifier
644 fm = self.fm
645 cmd = self.fm.execute_console
646 p = fm.notify
647 quantifier = self.quantifier
648 try:
649 try:
650 result = eval(code)
651 except SyntaxError:
652 exec(code)
653 else:
654 if result and not quiet:
655 p(result)
656 except Exception as err:
657 p(err)
658
659
660 class rename(Command):
661 """:rename <newname>
662
663 Changes the name of the currently highlighted file to <newname>
664 """
665
666 def execute(self):
667 from ranger.container.file import File
668 from os import access
669
670 new_name = self.rest(1)
671
672 if not new_name:
673 return self.fm.notify('Syntax: rename <newname>', bad=True)
674
675 if new_name == self.fm.thisfile.basename:
676 return
677
678 if access(new_name, os.F_OK):
679 return self.fm.notify("Can't rename: file already exists!", bad=True)
680
681 self.fm.rename(self.fm.thisfile, new_name)
682 f = File(new_name)
683 self.fm.thisdir.pointed_obj = f
684 self.fm.thisfile = f
685
686 def tab(self):
687 return self._tab_directory_content()
688
689
690 class chmod(Command):
691 """:chmod <octal number>
692
693 Sets the permissions of the selection to the octal number.
694
695 The octal number is between 0 and 777. The digits specify the
696 permissions for the user, the group and others.
697
698 A 1 permits execution, a 2 permits writing, a 4 permits reading.
699 Add those numbers to combine them. So a 7 permits everything.
700 """
701
702 def execute(self):
703 mode = self.rest(1)
704 if not mode:
705 mode = str(self.quantifier)
706
707 try:
708 mode = int(mode, 8)
709 if mode < 0 or mode > 0o777:
710 raise ValueError
711 except ValueError:
712 self.fm.notify("Need an octal number between 0 and 777!", bad=True)
713 return
714
715 for file in self.fm.thistab.get_selection():
716 try:
717 os.chmod(file.path, mode)
718 except Exception as ex:
719 self.fm.notify(ex)
720
721 try:
722 # reloading directory. maybe its better to reload the selected
723 # files only.
724 self.fm.thisdir.load_content()
725 except:
726 pass
727
728
729 class bulkrename(Command):
730 """:bulkrename
731
732 This command opens a list of selected files in an external editor.
733 After you edit and save the file, it will generate a shell script
734 which does bulk renaming according to the changes you did in the file.
735
736 This shell script is opened in an editor for you to review.
737 After you close it, it will be executed.
738 """
739 def execute(self):
740 import sys
741 import tempfile
742 from ranger.container.file import File
743 from ranger.ext.shell_escape import shell_escape as esc
744 py3 = sys.version > "3"
745
746 # Create and edit the file list
747 filenames = [f.basename for f in self.fm.thistab.get_selection()]
748 listfile = tempfile.NamedTemporaryFile()
749
750 if py3:
751 listfile.write("\n".join(filenames).encode("utf-8"))
752 else:
753 listfile.write("\n".join(filenames))
754 listfile.flush()
755 self.fm.execute_file([File(listfile.name)], app='editor')
756 listfile.seek(0)
757 if py3:
758 new_filenames = listfile.read().decode("utf-8").split("\n")
759 else:
760 new_filenames = listfile.read().split("\n")
761 listfile.close()
762 if all(a == b for a, b in zip(filenames, new_filenames)):
763 self.fm.notify("No renaming to be done!")
764 return
765
766 # Generate and execute script
767 cmdfile = tempfile.NamedTemporaryFile()
768 cmdfile.write(b"# This file will be executed when you close the editor.\n")
769 cmdfile.write(b"# Please double-check everything, clear the file to abort.\n")
770 if py3:
771 cmdfile.write("\n".join("mv -vi -- " + esc(old) + " " + esc(new) \
772 for old, new in zip(filenames, new_filenames) \
773 if old != new).encode("utf-8"))
774 else:
775 cmdfile.write("\n".join("mv -vi -- " + esc(old) + " " + esc(new) \
776 for old, new in zip(filenames, new_filenames) if old != new))
777 cmdfile.flush()
778 self.fm.execute_file([File(cmdfile.name)], app='editor')
779 self.fm.run(['/bin/sh', cmdfile.name], flags='w')
780 cmdfile.close()
781
782
783 class relink(Command):
784 """:relink <newpath>
785
786 Changes the linked path of the currently highlighted symlink to <newpath>
787 """
788
789 def execute(self):
790 from ranger.container.file import File
791
792 new_path = self.rest(1)
793 cf = self.fm.thisfile
794
795 if not new_path:
796 return self.fm.notify('Syntax: relink <newpath>', bad=True)
797
798 if not cf.is_link:
799 return self.fm.notify('%s is not a symlink!' % cf.basename, bad=True)
800
801 if new_path == os.readlink(cf.path):
802 return
803
804 try:
805 os.remove(cf.path)
806 os.symlink(new_path, cf.path)
807 except OSError as err:
808 self.fm.notify(err)
809
810 self.fm.reset()
811 self.fm.thisdir.pointed_obj = cf
812 self.fm.thisfile = cf
813
814 def tab(self):
815 if not self.rest(1):
816 return self.line+os.readlink(self.fm.thisfile.path)
817 else:
818 return self._tab_directory_content()
819
820
821 class help_(Command):
822 """:help
823
824 Display ranger's manual page.
825 """
826 name = 'help'
827 def execute(self):
828 if self.quantifier == 1:
829 self.fm.dump_keybindings()
830 elif self.quantifier == 2:
831 self.fm.dump_commands()
832 elif self.quantifier == 3:
833 self.fm.dump_settings()
834 else:
835 self.fm.display_help()
836
837
838 class copymap(Command):
839 """:copymap <keys> <newkeys1> [<newkeys2>...]
840
841 Copies a "browser" keybinding from <keys> to <newkeys>
842 """
843 context = 'browser'
844
845 def execute(self):
846 if not self.arg(1) or not self.arg(2):
847 return self.fm.notify("Not enough arguments", bad=True)
848
849 for arg in self.args[2:]:
850 self.fm.ui.keymaps.copy(self.context, self.arg(1), arg)
851
852
853 class copypmap(copymap):
854 """:copypmap <keys> <newkeys1> [<newkeys2>...]
855
856 Copies a "pager" keybinding from <keys> to <newkeys>
857 """
858 context = 'pager'
859
860
861 class copycmap(copymap):
862 """:copycmap <keys> <newkeys1> [<newkeys2>...]
863
864 Copies a "console" keybinding from <keys> to <newkeys>
865 """
866 context = 'console'
867
868
869 class copytmap(copymap):
870 """:copycmap <keys> <newkeys1> [<newkeys2>...]
871
872 Copies a "taskview" keybinding from <keys> to <newkeys>
873 """
874 context = 'taskview'
875
876
877 class unmap(Command):
878 """:unmap <keys> [<keys2>, ...]
879
880 Remove the given "browser" mappings
881 """
882 context = 'browser'
883
884 def execute(self):
885 for arg in self.args[1:]:
886 self.fm.ui.keymaps.unbind(self.context, arg)
887
888
889 class cunmap(unmap):
890 """:cunmap <keys> [<keys2>, ...]
891
892 Remove the given "console" mappings
893 """
894 context = 'browser'
895
896
897 class punmap(unmap):
898 """:punmap <keys> [<keys2>, ...]
899
900 Remove the given "pager" mappings
901 """
902 context = 'pager'
903
904
905 class tunmap(unmap):
906 """:tunmap <keys> [<keys2>, ...]
907
908 Remove the given "taskview" mappings
909 """
910 context = 'taskview'
911
912
913 class map_(Command):
914 """:map <keysequence> <command>
915
916 Maps a command to a keysequence in the "browser" context.
917
918 Example:
919 map j move down
920 map J move down 10
921 """
922 name = 'map'
923 context = 'browser'
924 resolve_macros = False
925
926 def execute(self):
927 self.fm.ui.keymaps.bind(self.context, self.arg(1), self.rest(2))
928
929
930 class cmap(map_):
931 """:cmap <keysequence> <command>
932
933 Maps a command to a keysequence in the "console" context.
934
935 Example:
936 cmap <ESC> console_close
937 cmap <C-x> console_type test
938 """
939 context = 'console'
940
941
942 class tmap(map_):
943 """:tmap <keysequence> <command>
944
945 Maps a command to a keysequence in the "taskview" context.
946 """
947 context = 'taskview'
948
949
950 class pmap(map_):
951 """:pmap <keysequence> <command>
952
953 Maps a command to a keysequence in the "pager" context.
954 """
955 context = 'pager'
956
957
958 class scout(Command):
959 """:scout [-FLAGS] <pattern>
960
961 Swiss army knife command for searching, traveling and filtering files.
962 The command takes various flags as arguments which can be used to
963 influence its behaviour:
964
965 -a = automatically open a file on unambiguous match
966 -e = open the selected file when pressing enter
967 -f = filter files that match the current search pattern
968 -g = interpret pattern as a glob pattern
969 -i = ignore the letter case of the files
970 -k = keep the console open when changing a directory with the command
971 -l = letter skipping; e.g. allow "rdme" to match the file "readme"
972 -m = mark the matching files after pressing enter
973 -M = unmark the matching files after pressing enter
974 -p = permanent filter: hide non-matching files after pressing enter
975 -s = smart case; like -i unless pattern contains upper case letters
976 -t = apply filter and search pattern as you type
977 -v = inverts the match
978
979 Multiple flags can be combined. For example, ":scout -gpt" would create
980 a :filter-like command using globbing.
981 """
982 AUTO_OPEN = 'a'
983 OPEN_ON_ENTER = 'e'
984 FILTER = 'f'
985 SM_GLOB = 'g'
986 IGNORE_CASE = 'i'
987 KEEP_OPEN = 'k'
988 SM_LETTERSKIP = 'l'
989 MARK = 'm'
990 UNMARK = 'M'
991 PERM_FILTER = 'p'
992 SM_REGEX = 'r'
993 SMART_CASE = 's'
994 AS_YOU_TYPE = 't'
995 INVERT = 'v'
996
997 def __init__(self, *args, **kws):
998 Command.__init__(self, *args, **kws)
999 self._regex = None
1000 self.flags, self.pattern = self.parse_flags()
1001
1002 def execute(self):
1003 thisdir = self.fm.thisdir
1004 flags = self.flags
1005 pattern = self.pattern
1006 regex = self._build_regex()
1007 count = self._count(move=True)
1008
1009 self.fm.thistab.last_search = regex
1010 self.fm.set_search_method(order="search")
1011
1012 if self.MARK in flags or self.UNMARK in flags:
1013 value = flags.find(self.MARK) > flags.find(self.UNMARK)
1014 if self.FILTER in flags:
1015 for f in thisdir.files:
1016 thisdir.mark_item(f, value)
1017 else:
1018 for f in thisdir.files:
1019 if regex.search(f.basename):
1020 thisdir.mark_item(f, value)
1021
1022 if self.PERM_FILTER in flags:
1023 thisdir.filter = regex if pattern else None
1024
1025 # clean up:
1026 self.cancel()
1027
1028 if self.OPEN_ON_ENTER in flags or \
1029 self.AUTO_OPEN in flags and count == 1:
1030 if os.path.exists(pattern):
1031 self.fm.cd(pattern)
1032 else:
1033 self.fm.move(right=1)
1034
1035 if self.KEEP_OPEN in flags and thisdir != self.fm.thisdir:
1036 # reopen the console:
1037 self.fm.open_console(self.line[0:-len(pattern)])
1038
1039 if thisdir != self.fm.thisdir and pattern != "..":
1040 self.fm.block_input(0.5)
1041
1042 def cancel(self):
1043 self.fm.thisdir.temporary_filter = None
1044 self.fm.thisdir.refilter()
1045
1046 def quick(self):
1047 asyoutype = self.AS_YOU_TYPE in self.flags
1048 if self.FILTER in self.flags:
1049 self.fm.thisdir.temporary_filter = self._build_regex()
1050 if self.PERM_FILTER in self.flags and asyoutype:
1051 self.fm.thisdir.filter = self._build_regex()
1052 if self.FILTER in self.flags or self.PERM_FILTER in self.flags:
1053 self.fm.thisdir.refilter()
1054 if self._count(move=asyoutype) == 1 and self.AUTO_OPEN in self.flags:
1055 return True
1056 return False
1057
1058 def tab(self):
1059 self._count(move=True, offset=1)
1060
1061 def _build_regex(self):
1062 if self._regex is not None:
1063 return self._regex
1064
1065 frmat = "%s"
1066 flags = self.flags
1067 pattern = self.pattern
1068
1069 if pattern == ".":
1070 return re.compile("")
1071
1072 # Handle carets at start and dollar signs at end separately
1073 if pattern.startswith('^'):
1074 pattern = pattern[1:]
1075 frmat = "^" + frmat
1076 if pattern.endswith('$'):
1077 pattern = pattern[:-1]
1078 frmat += "$"
1079
1080 # Apply one of the search methods
1081 if self.SM_REGEX in flags:
1082 regex = pattern
1083 elif self.SM_GLOB in flags:
1084 regex = re.escape(pattern).replace("\\*", ".*").replace("\\?", ".")
1085 elif self.SM_LETTERSKIP in flags:
1086 regex = ".*".join(re.escape(c) for c in pattern)
1087 else:
1088 regex = re.escape(pattern)
1089
1090 regex = frmat % regex
1091
1092 # Invert regular expression if necessary
1093 if self.INVERT in flags:
1094 regex = "^(?:(?!%s).)*$" % regex
1095
1096 # Compile Regular Expression
1097 options = re.LOCALE | re.UNICODE
1098 if self.IGNORE_CASE in flags or self.SMART_CASE in flags and \
1099 pattern.islower():
1100 options |= re.IGNORECASE
1101 try:
1102 self._regex = re.compile(regex, options)
1103 except:
1104 self._regex = re.compile("")
1105 return self._regex
1106
1107 def _count(self, move=False, offset=0):
1108 count = 0
1109 cwd = self.fm.thisdir
1110 pattern = self.pattern
1111
1112 if not pattern:
1113 return 0
1114 if pattern == '.':
1115 return 0
1116 if pattern == '..':
1117 return 1
1118
1119 deq = deque(cwd.files)
1120 deq.rotate(-cwd.pointer - offset)
1121 i = offset
1122 regex = self._build_regex()
1123 for fsobj in deq:
1124 if regex.search(fsobj.basename):
1125 count += 1
1126 if move and count == 1:
1127 cwd.move(to=(cwd.pointer + i) % len(cwd.files))
1128 self.fm.thisfile = cwd.pointed_obj
1129 if count > 1:
1130 return count
1131 i += 1
1132
1133 return count == 1
1134
1135
1136 class grep(Command):
1137 """:grep <string>
1138
1139 Looks for a string in all marked files or directories
1140 """
1141
1142 def execute(self):
1143 if self.rest(1):
1144 action = ['grep', '--line-number']
1145 action.extend(['-e', self.rest(1), '-r'])
1146 action.extend(f.path for f in self.fm.thistab.get_selection())
1147 self.fm.execute_command(action, flags='p')
1148
1149
1150 # Version control commands
1151 # --------------------------------
1152 class stage(Command):
1153 """
1154 :stage
1155
1156 Stage selected files for the corresponding version control system
1157 """
1158 def execute(self):
1159 from ranger.ext.vcs import VcsError
1160
1161 filelist = [f.path for f in self.fm.thistab.get_selection()]
1162 self.fm.thisdir.vcs_outdated = True
1163 # for f in self.fm.thistab.get_selection():
1164 # f.vcs_outdated = True
1165
1166 try:
1167 self.fm.thisdir.vcs.add(filelist)
1168 except VcsError:
1169 self.fm.notify("Could not stage files.")
1170
1171 self.fm.reload_cwd()
1172
1173
1174 class unstage(Command):
1175 """
1176 :unstage
1177
1178 Unstage selected files for the corresponding version control system
1179 """
1180 def execute(self):
1181 from ranger.ext.vcs import VcsError
1182
1183 filelist = [f.path for f in self.fm.thistab.get_selection()]
1184 self.fm.thisdir.vcs_outdated = True
1185 # for f in self.fm.thistab.get_selection():
1186 # f.vcs_outdated = True
1187
1188 try:
1189 self.fm.thisdir.vcs.reset(filelist)
1190 except VcsError:
1191 self.fm.notify("Could not unstage files.")
1192
1193 self.fm.reload_cwd()
1194
1195
1196 class diff(Command):
1197 """
1198 :diff
1199
1200 Displays a diff of selected files against last last commited version
1201 """
1202 def execute(self):
1203 from ranger.ext.vcs import VcsError
1204 import tempfile
1205
1206 L = self.fm.thistab.get_selection()
1207 if len(L) == 0: return
1208
1209 filelist = [f.path for f in L]
1210 vcs = L[0].vcs
1211
1212 diff = vcs.get_raw_diff(filelist=filelist)
1213 if len(diff.strip()) > 0:
1214 tmp = tempfile.NamedTemporaryFile()
1215 tmp.write(diff.encode('utf-8'))
1216 tmp.flush()
1217
1218 pager = os.environ.get('PAGER', ranger.DEFAULT_PAGER)
1219 self.fm.run([pager, tmp.name])
1220 else:
1221 raise Exception("diff is empty")
1222
1223
1224 class log(Command):
1225 """
1226 :log
1227
1228 Displays the log of the current repo or files
1229 """
1230 def execute(self):
1231 from ranger.ext.vcs import VcsError
1232 import tempfile
1233
1234 L = self.fm.thistab.get_selection()
1235 if len(L) == 0: return
1236
1237 filelist = [f.path for f in L]
1238 vcs = L[0].vcs
1239
1240 log = vcs.get_raw_log(filelist=filelist)
1241 tmp = tempfile.NamedTemporaryFile()
1242 tmp.write(log.encode('utf-8'))
1243 tmp.flush()
1244
1245 pager = os.environ.get('PAGER', ranger.DEFAULT_PAGER)
1246 self.fm.run([pager, tmp.name])
1247
1248 class extracthere(Command):
1249 def execute(self):
1250 """ Extract copied files to current directory """
1251 copied_files = tuple(self.fm.env.copy)
1252
1253 if not copied_files:
1254 return
1255
1256 def refresh(_):
1257 cwd = self.fm.env.get_directory(original_path)
1258 cwd.load_content()
1259
1260 one_file = copied_files[0]
1261 cwd = self.fm.env.cwd
1262 original_path = cwd.path
1263 au_flags = ['-X', cwd.path]
1264 au_flags += self.line.split()[1:]
1265 au_flags += ['-e']
1266
1267 self.fm.env.copy.clear()
1268 self.fm.env.cut = False
1269 if len(copied_files) == 1:
1270 descr = "extracting: " + os.path.basename(one_file.path)
1271 else:
1272 descr = "extracting files from: " + os.path.basename(one_file.dirname)
1273 obj = CommandLoader(args=['aunpack'] + au_flags \
1274 + [f.path for f in copied_files], descr=descr)
1275
1276 obj.signal_bind('after', refresh)
1277 self.fm.loader.add(obj)
1278
1279 class compress(Command):
1280 def execute(self):
1281 """ Compress marked files to current directory """
1282 cwd = self.fm.env.cwd
1283 marked_files = cwd.get_selection()
1284
1285 if not marked_files:
1286 return
1287
1288 def refresh(_):
1289 cwd = self.fm.env.get_directory(original_path)
1290 cwd.load_content()
1291
1292 original_path = cwd.path
1293 parts = self.line.split()
1294 au_flags = parts[1:]
1295
1296 descr = "compressing files in: " + os.path.basename(parts[1])
1297 obj = CommandLoader(args=['apack'] + au_flags + \
1298 [os.path.relpath(f.path, cwd.path) for f in marked_files], descr=descr)
1299
1300 obj.signal_bind('after', refresh)
1301 self.fm.loader.add(obj)
1302
1303 def tab(self):
1304 """ Complete with current folder name """
1305
1306 extension = ['.zip', '.tar.gz', '.rar', '.7z']
1307 return ['compress ' + os.path.basename(self.fm.env.cwd.path) + ext for ext in extension]