# -*- coding: utf-8 -*- # This file is part of ranger, the console file manager. # This configuration file is licensed under the same terms as ranger. # =================================================================== # # NOTE: If you copied this file to /etc/ranger/commands_full.py or # ~/.config/ranger/commands_full.py, then it will NOT be loaded by ranger, # and only serve as a reference. # # =================================================================== # This file contains ranger's commands. # It's all in python; lines beginning with # are comments. # # Note that additional commands are automatically generated from the methods # of the class ranger.core.actions.Actions. # # You can customize commands in the files /etc/ranger/commands.py (system-wide) # and ~/.config/ranger/commands.py (per user). # They have the same syntax as this file. In fact, you can just copy this # file to ~/.config/ranger/commands_full.py with # `ranger --copy-config=commands_full' and make your modifications, don't # forget to rename it to commands.py. You can also use # `ranger --copy-config=commands' to copy a short sample commands.py that # has everything you need to get started. # But make sure you update your configs when you update ranger. # # =================================================================== # Every class defined here which is a subclass of `Command' will be used as a # command in ranger. Several methods are defined to interface with ranger: # execute(): called when the command is executed. # cancel(): called when closing the console. # tab(tabnum): called when is pressed. # quick(): called after each keypress. # # tab() argument tabnum is 1 for and -1 for by default # # The return values for tab() can be either: # None: There is no tab completion # A string: Change the console to this string # A list/tuple/generator: cycle through every item in it # # The return value for quick() can be: # False: Nothing happens # True: Execute the command afterwards # # The return value for execute() and cancel() doesn't matter. # # =================================================================== # Commands have certain attributes and methods that facilitate parsing of # the arguments: # # self.line: The whole line that was written in the console. # self.args: A list of all (space-separated) arguments to the command. # self.quantifier: If this command was mapped to the key "X" and # the user pressed 6X, self.quantifier will be 6. # self.arg(n): The n-th argument, or an empty string if it doesn't exist. # self.rest(n): The n-th argument plus everything that followed. For example, # if the command was "search foo bar a b c", rest(2) will be "bar a b c" # self.start(n): Anything before the n-th argument. For example, if the # command was "search foo bar a b c", start(2) will be "search foo" # # =================================================================== # And this is a little reference for common ranger functions and objects: # # self.fm: A reference to the "fm" object which contains most information # about ranger. # self.fm.notify(string): Print the given string on the screen. # self.fm.notify(string, bad=True): Print the given string in RED. # self.fm.reload_cwd(): Reload the current working directory. # self.fm.thisdir: The current working directory. (A File object.) # self.fm.thisfile: The current file. (A File object too.) # self.fm.thistab.get_selection(): A list of all selected files. # self.fm.execute_console(string): Execute the string as a ranger command. # self.fm.open_console(string): Open the console with the given string # already typed in for you. # self.fm.move(direction): Moves the cursor in the given direction, which # can be something like down=3, up=5, right=1, left=1, to=6, ... # # File objects (for example self.fm.thisfile) have these useful attributes and # methods: # # tfile.path: The path to the file. # tfile.basename: The base name only. # tfile.load_content(): Force a loading of the directories content (which # obviously works with directories only) # tfile.is_directory: True/False depending on whether it's a directory. # # For advanced commands it is unavoidable to dive a bit into the source code # of ranger. # =================================================================== from __future__ import (absolute_import, division, print_function) from collections import deque import os import re from ranger.api.commands import Command class alias(Command): """:alias Copies the oldcommand as newcommand. """ context = 'browser' resolve_macros = False def execute(self): if not self.arg(1) or not self.arg(2): self.fm.notify('Syntax: alias ', bad=True) return self.fm.commands.alias(self.arg(1), self.rest(2)) class echo(Command): """:echo Display the text in the statusbar. """ def execute(self): self.fm.notify(self.rest(1)) class cd(Command): """:cd [-r] The cd command changes the directory. If the path is a file, selects that file. The command 'cd -' is equivalent to typing ``. Using the option "-r" will get you to the real path. """ def execute(self): if self.arg(1) == '-r': self.shift() destination = os.path.realpath(self.rest(1)) if os.path.isfile(destination): self.fm.select_file(destination) return else: destination = self.rest(1) if not destination: destination = '~' if destination == '-': self.fm.enter_bookmark('`') else: self.fm.cd(destination) def _tab_args(self): # dest must be rest because path could contain spaces if self.arg(1) == '-r': start = self.start(2) dest = self.rest(2) else: start = self.start(1) dest = self.rest(1) if dest: head, tail = os.path.split(os.path.expanduser(dest)) if head: dest_exp = os.path.join(os.path.normpath(head), tail) else: dest_exp = tail else: dest_exp = '' return (start, dest_exp, os.path.join(self.fm.thisdir.path, dest_exp), dest.endswith(os.path.sep)) @staticmethod def _tab_paths(dest, dest_abs, ends_with_sep): if not dest: try: return next(os.walk(dest_abs))[1], dest_abs except (OSError, StopIteration): return [], '' if ends_with_sep: try: return [os.path.join(dest, path) for path in next(os.walk(dest_abs))[1]], '' except (OSError, StopIteration): return [], '' return None, None def _tab_match(self, path_user, path_file): if self.fm.settings.cd_tab_case == 'insensitive': path_user = path_user.lower() path_file = path_file.lower() elif self.fm.settings.cd_tab_case == 'smart' and path_user.islower(): path_file = path_file.lower() return path_file.startswith(path_user) def _tab_normal(self, dest, dest_abs): dest_dir = os.path.dirname(dest) dest_base = os.path.basename(dest) try: dirnames = next(os.walk(os.path.dirname(dest_abs)))[1] except (OSError, StopIteration): return [], '' return [os.path.join(dest_dir, d) for d in dirnames if self._tab_match(dest_base, d)], '' def _tab_fuzzy_match(self, basepath, tokens): """ Find directories matching tokens recursively """ if not tokens: tokens = [''] paths = [basepath] while True: token = tokens.pop() matches = [] for path in paths: try: directories = next(os.walk(path))[1] except (OSError, StopIteration): continue matches += [os.path.join(path, d) for d in directories if self._tab_match(token, d)] if not tokens or not matches: return matches paths = matches return None def _tab_fuzzy(self, dest, dest_abs): tokens = [] basepath = dest_abs while True: basepath_old = basepath basepath, token = os.path.split(basepath) if basepath == basepath_old: break if os.path.isdir(basepath_old) and not token.startswith('.'): basepath = basepath_old break tokens.append(token) paths = self._tab_fuzzy_match(basepath, tokens) if not os.path.isabs(dest): paths_rel = self.fm.thisdir.path paths = [os.path.relpath(os.path.join(basepath, path), paths_rel) for path in paths] else: paths_rel = '' return paths, paths_rel def tab(self, tabnum): from os.path import sep start, dest, dest_abs, ends_with_sep = self._tab_args() paths, paths_rel = self._tab_paths(dest, dest_abs, ends_with_sep) if paths is None: if self.fm.settings.cd_tab_fuzzy: paths, paths_rel = self._tab_fuzzy(dest, dest_abs) else: paths, paths_rel = self._tab_normal(dest, dest_abs) paths.sort() if self.fm.settings.cd_bookmarks: paths[0:0] = [ os.path.relpath(v.path, paths_rel) if paths_rel else v.path for v in self.fm.bookmarks.dct.values() for path in paths if v.path.startswith(os.path.join(paths_rel, path) + sep) ] if not paths: return None if len(paths) == 1: return start + paths[0] + sep return [start + dirname + sep for dirname in paths] class chain(Command): """:chain ; ; ... Calls multiple commands at once, separated by semicolons. """ resolve_macros = False def execute(self): if not self.rest(1).strip(): self.fm.notify('Syntax: chain ; ; ...', bad=True) return for command in [s.strip() for s in self.rest(1).split(";")]: self.fm.execute_console(command) class shell(Command): escape_macros_for_shell = True def execute(self): if self.arg(1) and self.arg(1)[0] == '-': flags = self.arg(1)[1:] command = self.rest(2) else: flags = '' command = self.rest(1) if command: self.fm.execute_command(command, flags=flags) def tab(self, tabnum): from ranger.ext.get_executables import get_executables if self.arg(1) and self.arg(1)[0] == '-': command = self.rest(2) else: command = self.rest(1) start = self.line[0:len(self.line) - len(command)] try: position_of_last_space = command.rindex(" ") except ValueError: return (start + program + ' ' for program in get_executables() if program.startswith(command)) if position_of_last_space == len(command) - 1: selection = self.fm.thistab.get_selection() if len(selection) == 1: return self.line + selection[0].shell_escaped_basename + ' ' return self.line + '%s ' before_word, start_of_word = self.line.rsplit(' ', 1) return (before_word + ' ' + file.shell_escaped_basename for file in self.fm.thisdir.files or [] if file.shell_escaped_basename.startswith(start_of_word)) class open_with(Command): def execute(self): app, flags, mode = self._get_app_flags_mode(self.rest(1)) self.fm.execute_file( files=[f for f in self.fm.thistab.get_selection()], app=app, flags=flags, mode=mode) def tab(self, tabnum): return self._tab_through_executables() def _get_app_flags_mode(self, string): # pylint: disable=too-many-branches,too-many-statements """Extracts the application, flags and mode from a string. examples: "mplayer f 1" => ("mplayer", "f", 1) "atool 4" => ("atool", "", 4) "p" => ("", "p", 0) "" => None """ app = '' flags = '' mode = 0 split = string.split() if len(split) == 1: part = split[0] if self._is_app(part): app = part elif self._is_flags(part): flags = part elif self._is_mode(part): mode = part elif len(split) == 2: part0 = split[0] part1 = split[1] if self._is_app(part0): app = part0 if self._is_flags(part1): flags = part1 elif self._is_mode(part1): mode = part1 elif self._is_flags(part0): flags = part0 if self._is_mode(part1): mode = part1 elif self._is_mode(part0): mode = part0 if self._is_flags(part1): flags = part1 elif len(split) >= 3: part0 = split[0] part1 = split[1] part2 = split[2] if self._is_app(part0): app = part0 if self._is_flags(part1): flags = part1 if self._is_mode(part2): mode = part2 elif self._is_mode(part1): mode = part1 if self._is_flags(part2): flags = part2 elif self._is_flags(part0): flags = part0 if self._is_mode(part1): mode = part1 elif self._is_mode(part0): mode = part0 if self._is_flags(part1): flags = part1 return app, flags, int(mode) def _is_app(self, arg): return not self._is_flags(arg) and not arg.isdigit() @staticmethod def _is_flags(arg): from ranger.core.runner import ALLOWED_FLAGS return all(x in ALLOWED_FLAGS for x in arg) @staticmethod def _is_mode(arg): return all(x in '0123456789' for x in arg) class set_(Command): """:set