def initialize(self): """If ui/bookmarks are None, they will be initialized here.""" self.tabs = dict((n + 1, Tab(path)) for n, path in enumerate(self.start_paths)) tab_list = self._get_tab_list() if tab_list: self.current_tab = tab_list[0] self.thistab = self.tabs[self.current_tab] else: self.current_tab = 1 self.tabs[self.current_tab] = self.thistab = Tab(".") if not ranger.arg.clean and os.path.isfile(self.confpath("rifle.conf")): rifleconf = self.confpath("rifle.conf") else: rifleconf = self.relpath("config/rifle.conf") self.rifle = Rifle(rifleconf) self.rifle.reload_config() if self.bookmarks is None: if ranger.arg.clean: bookmarkfile = None else: bookmarkfile = self.confpath("bookmarks") self.bookmarks = Bookmarks( bookmarkfile=bookmarkfile, bookmarktype=Directory, autosave=self.settings.autosave_bookmarks ) self.bookmarks.load() if not ranger.arg.clean and self.tags is None: self.tags = Tags(self.confpath("tagged")) self.ui.setup_curses() self.ui.initialize() self.rifle.hook_before_executing = lambda a, b, flags: self.ui.suspend() if "f" not in flags else None self.rifle.hook_after_executing = lambda a, b, flags: self.ui.initialize() if "f" not in flags else None self.rifle.hook_logger = self.notify def mylogfunc(text): self.notify(text, bad=True) self.run = Runner(ui=self.ui, logfunc=mylogfunc, fm=self)
def initialize(self): """If ui/bookmarks are None, they will be initialized here.""" if not ranger.arg.clean and os.path.isfile(self.confpath("rifle.conf")): rifleconf = self.confpath("rifle.conf") else: rifleconf = self.relpath("defaults/rifle.conf") self.rifle = Rifle(rifleconf) self.rifle.reload_config() if self.bookmarks is None: if ranger.arg.clean: bookmarkfile = None else: bookmarkfile = self.confpath("bookmarks") self.bookmarks = Bookmarks( bookmarkfile=bookmarkfile, bookmarktype=Directory, autosave=self.settings.autosave_bookmarks ) self.bookmarks.load() if not ranger.arg.clean and self.tags is None: self.tags = Tags(self.confpath("tagged")) if self.ui is None: self.ui = UI() self.ui.initialize() self.rifle.hook_before_executing = lambda a, b, flags: self.ui.suspend() if "f" not in flags else None self.rifle.hook_after_executing = lambda a, b, flags: self.ui.initialize() if "f" not in flags else None self.rifle.hook_logger = self.notify def mylogfunc(text): self.notify(text, bad=True) self.run = Runner(ui=self.ui, logfunc=mylogfunc, fm=self) self.env.signal_bind("cd", self._update_current_tab) if self.settings.init_function: self.settings.init_function(self)
def initialize(self): """If ui/bookmarks are None, they will be initialized here.""" self.tabs = dict((n + 1, Tab(path)) for n, path in enumerate(self.start_paths)) tab_list = self.get_tab_list() if tab_list: self.current_tab = tab_list[0] self.thistab = self.tabs[self.current_tab] else: self.current_tab = 1 self.tabs[self.current_tab] = self.thistab = Tab('.') if not ranger.args.clean and os.path.isfile(self.confpath('rifle.conf')): rifleconf = self.confpath('rifle.conf') else: rifleconf = self.relpath('config/rifle.conf') self.rifle = Rifle(rifleconf) self.rifle.reload_config() def set_image_displayer(): self.image_displayer = self._get_image_displayer() set_image_displayer() self.settings.signal_bind('setopt.preview_images_method', set_image_displayer, priority=settings.SIGNAL_PRIORITY_AFTER_SYNC) self.settings.signal_bind( 'setopt.preview_images', lambda signal: signal.fm.previews.clear(), ) if ranger.args.clean: self.tags = TagsDummy("") elif self.tags is None: self.tags = Tags(self.datapath('tagged')) if self.bookmarks is None: if ranger.args.clean: bookmarkfile = None else: bookmarkfile = self.datapath('bookmarks') self.bookmarks = Bookmarks( bookmarkfile=bookmarkfile, bookmarktype=Directory, autosave=self.settings.autosave_bookmarks) self.bookmarks.load() self.ui.setup_curses() self.ui.initialize() self.rifle.hook_before_executing = lambda a, b, flags: \ self.ui.suspend() if 'f' not in flags else None self.rifle.hook_after_executing = lambda a, b, flags: \ self.ui.initialize() if 'f' not in flags else None self.rifle.hook_logger = self.notify old_preprocessing_hook = self.rifle.hook_command_preprocessing # This hook allows image viewers to open all images in the current # directory, keeping the order of files the same as in ranger. # The requirements to use it are: # 1. set open_all_images to true # 2. ensure no files are marked # 3. call rifle with a command that starts with "sxiv " or "feh " def sxiv_workaround_hook(command): import re from ranger.ext.shell_escape import shell_quote if self.settings.open_all_images and \ not self.thisdir.marked_items and \ re.match(r'^(feh|sxiv|imv|pqiv) ', command): images = [f.relative_path for f in self.thisdir.files if f.image] escaped_filenames = " ".join(shell_quote(f) for f in images if "\x00" not in f) if images and self.thisfile.relative_path in images and \ "$@" in command: new_command = None if command[0:5] == 'sxiv ': number = images.index(self.thisfile.relative_path) + 1 new_command = command.replace("sxiv ", "sxiv -n %d " % number, 1) if command[0:4] == 'feh ': new_command = command.replace( "feh ", "feh --start-at %s " % shell_quote(self.thisfile.relative_path), 1, ) if command[0:4] == 'imv ': number = images.index(self.thisfile.relative_path) + 1 new_command = command.replace("imv ", "imv -n %d " % number, 1) if command[0:5] == 'pqiv ': number = images.index(self.thisfile.relative_path) new_command = command.replace( "pqiv ", "pqiv --action \"goto_file_byindex(%d)\" " % number, 1) if new_command: command = "set -- %s; %s" % (escaped_filenames, new_command) return old_preprocessing_hook(command) self.rifle.hook_command_preprocessing = sxiv_workaround_hook def mylogfunc(text): self.notify(text, bad=True) self.run = Runner(ui=self.ui, logfunc=mylogfunc, fm=self) self.settings.signal_bind( 'setopt.metadata_deep_search', lambda signal: setattr(signal.fm.metadata, 'deep_search', signal.value) )
class FM(Actions, # pylint: disable=too-many-instance-attributes SignalDispatcher): input_blocked = False input_blocked_until = 0 mode = 'normal' # either 'normal' or 'visual'. search_method = 'ctime' _previous_selection = None _visual_reverse = False _visual_pos_start = None _visual_move_cycles = None def __init__(self, ui=None, bookmarks=None, tags=None, paths=None): """Initialize FM.""" Actions.__init__(self) SignalDispatcher.__init__(self) self.ui = ui if ui is not None else UI() self.start_paths = paths if paths is not None else ['.'] self.directories = dict() self.bookmarks = bookmarks self.current_tab = 1 self.tabs = {} self.tags = tags self.restorable_tabs = deque([], ranger.MAX_RESTORABLE_TABS) self.py3 = sys.version_info >= (3, ) self.previews = {} self.default_linemodes = deque() self.loader = Loader() self.copy_buffer = set() self.do_cut = False self.metadata = MetadataManager() self.image_displayer = None self.run = None self.rifle = None self.thistab = None try: self.username = pwd.getpwuid(os.geteuid()).pw_name except KeyError: self.username = '******' + str(os.geteuid()) self.hostname = socket.gethostname() self.home_path = os.path.expanduser('~') mimetypes.knownfiles.append(os.path.expanduser('~/.mime.types')) mimetypes.knownfiles.append(self.relpath('data/mime.types')) self.mimetypes = mimetypes.MimeTypes() def initialize(self): """If ui/bookmarks are None, they will be initialized here.""" self.tabs = dict((n + 1, Tab(path)) for n, path in enumerate(self.start_paths)) tab_list = self.get_tab_list() if tab_list: self.current_tab = tab_list[0] self.thistab = self.tabs[self.current_tab] else: self.current_tab = 1 self.tabs[self.current_tab] = self.thistab = Tab('.') if not ranger.args.clean and os.path.isfile(self.confpath('rifle.conf')): rifleconf = self.confpath('rifle.conf') else: rifleconf = self.relpath('config/rifle.conf') self.rifle = Rifle(rifleconf) self.rifle.reload_config() def set_image_displayer(): self.image_displayer = self._get_image_displayer() set_image_displayer() self.settings.signal_bind('setopt.preview_images_method', set_image_displayer, priority=settings.SIGNAL_PRIORITY_AFTER_SYNC) self.settings.signal_bind( 'setopt.preview_images', lambda signal: signal.fm.previews.clear(), ) if ranger.args.clean: self.tags = TagsDummy("") elif self.tags is None: self.tags = Tags(self.datapath('tagged')) if self.bookmarks is None: if ranger.args.clean: bookmarkfile = None else: bookmarkfile = self.datapath('bookmarks') self.bookmarks = Bookmarks( bookmarkfile=bookmarkfile, bookmarktype=Directory, autosave=self.settings.autosave_bookmarks) self.bookmarks.load() self.ui.setup_curses() self.ui.initialize() self.rifle.hook_before_executing = lambda a, b, flags: \ self.ui.suspend() if 'f' not in flags else None self.rifle.hook_after_executing = lambda a, b, flags: \ self.ui.initialize() if 'f' not in flags else None self.rifle.hook_logger = self.notify old_preprocessing_hook = self.rifle.hook_command_preprocessing # This hook allows image viewers to open all images in the current # directory, keeping the order of files the same as in ranger. # The requirements to use it are: # 1. set open_all_images to true # 2. ensure no files are marked # 3. call rifle with a command that starts with "sxiv " or "feh " def sxiv_workaround_hook(command): import re from ranger.ext.shell_escape import shell_quote if self.settings.open_all_images and \ not self.thisdir.marked_items and \ re.match(r'^(feh|sxiv|imv|pqiv) ', command): images = [f.relative_path for f in self.thisdir.files if f.image] escaped_filenames = " ".join(shell_quote(f) for f in images if "\x00" not in f) if images and self.thisfile.relative_path in images and \ "$@" in command: new_command = None if command[0:5] == 'sxiv ': number = images.index(self.thisfile.relative_path) + 1 new_command = command.replace("sxiv ", "sxiv -n %d " % number, 1) if command[0:4] == 'feh ': new_command = command.replace( "feh ", "feh --start-at %s " % shell_quote(self.thisfile.relative_path), 1, ) if command[0:4] == 'imv ': number = images.index(self.thisfile.relative_path) + 1 new_command = command.replace("imv ", "imv -n %d " % number, 1) if command[0:5] == 'pqiv ': number = images.index(self.thisfile.relative_path) new_command = command.replace( "pqiv ", "pqiv --action \"goto_file_byindex(%d)\" " % number, 1) if new_command: command = "set -- %s; %s" % (escaped_filenames, new_command) return old_preprocessing_hook(command) self.rifle.hook_command_preprocessing = sxiv_workaround_hook def mylogfunc(text): self.notify(text, bad=True) self.run = Runner(ui=self.ui, logfunc=mylogfunc, fm=self) self.settings.signal_bind( 'setopt.metadata_deep_search', lambda signal: setattr(signal.fm.metadata, 'deep_search', signal.value) ) def destroy(self): debug = ranger.args.debug if self.ui: try: self.ui.destroy() except Exception: # pylint: disable=broad-except if debug: raise if self.loader: try: self.loader.destroy() except Exception: # pylint: disable=broad-except if debug: raise @staticmethod def get_log(): """Return the current log The log is returned as a generator over its entries' lines """ for entry in logutils.QUEUE: for line in entry.splitlines(): yield line def _get_image_displayer(self): if self.settings.preview_images_method == "w3m": return W3MImageDisplayer() elif self.settings.preview_images_method == "iterm2": return ITerm2ImageDisplayer() elif self.settings.preview_images_method == "urxvt": return URXVTImageDisplayer() elif self.settings.preview_images_method == "urxvt-full": return URXVTImageFSDisplayer() return ImageDisplayer() def _get_thisfile(self): return self.thistab.thisfile def _set_thisfile(self, obj): self.thistab.thisfile = obj def _get_thisdir(self): return self.thistab.thisdir def _set_thisdir(self, obj): self.thistab.thisdir = obj thisfile = property(_get_thisfile, _set_thisfile) thisdir = property(_get_thisdir, _set_thisdir) def block_input(self, sec=0): self.input_blocked = sec != 0 self.input_blocked_until = time() + sec def input_is_blocked(self): if self.input_blocked and time() > self.input_blocked_until: self.input_blocked = False return self.input_blocked def copy_config_files(self, which): if ranger.args.clean: sys.stderr.write("refusing to copy config files in clean mode\n") return import shutil from errno import EEXIST def copy(src, dest): if os.path.exists(self.confpath(dest)): sys.stderr.write("already exists: %s\n" % self.confpath(dest)) else: sys.stderr.write("creating: %s\n" % self.confpath(dest)) try: os.makedirs(ranger.args.confdir) except OSError as err: if err.errno != EEXIST: # EEXIST means it already exists print("This configuration directory could not be created:") print(ranger.args.confdir) print("To run ranger without the need for configuration") print("files, use the --clean option.") raise SystemExit try: shutil.copy(self.relpath(src), self.confpath(dest)) except OSError as ex: sys.stderr.write(" ERROR: %s\n" % str(ex)) if which == 'rifle' or which == 'all': copy('config/rifle.conf', 'rifle.conf') if which == 'commands' or which == 'all': copy('config/commands_sample.py', 'commands.py') if which == 'commands_full' or which == 'all': copy('config/commands.py', 'commands_full.py') if which == 'rc' or which == 'all': copy('config/rc.conf', 'rc.conf') if which == 'scope' or which == 'all': copy('data/scope.sh', 'scope.sh') os.chmod(self.confpath('scope.sh'), os.stat(self.confpath('scope.sh')).st_mode | stat.S_IXUSR) if which in ('all', 'rifle', 'scope', 'commands', 'commands_full', 'rc'): sys.stderr.write("\n> Please note that configuration files may " "change as ranger evolves.\n It's completely up to you to " "keep them up to date.\n") if os.environ.get('RANGER_LOAD_DEFAULT_RC', 'TRUE').upper() != 'FALSE': sys.stderr.write("\n> To stop ranger from loading " "\033[1mboth\033[0m the default and your custom rc.conf,\n" " please set the environment variable " "\033[1mRANGER_LOAD_DEFAULT_RC\033[0m to " "\033[1mFALSE\033[0m.\n") else: sys.stderr.write("Unknown config file `%s'\n" % which) def confpath(self, *paths): """returns path to ranger's configuration directory""" if ranger.args.clean: self.notify("Accessed configuration directory in clean mode", bad=True) return None return os.path.join(ranger.args.confdir, *paths) def datapath(self, *paths): """returns path to ranger's data directory""" if ranger.args.clean: self.notify("Accessed data directory in clean mode", bad=True) return None path_compat = self.confpath(*paths) # COMPAT if os.path.exists(path_compat): return path_compat return os.path.join(ranger.args.datadir, *paths) @staticmethod def relpath(*paths): """returns the path relative to rangers library directory""" return os.path.join(ranger.RANGERDIR, *paths) def get_directory(self, path, **dir_kwargs): """Get the directory object at the given path""" path = os.path.abspath(path) try: return self.directories[path] except KeyError: obj = Directory(path, **dir_kwargs) self.directories[path] = obj return obj def garbage_collect( self, age, tabs=None): # tabs=None is for COMPATibility pylint: disable=unused-argument """Delete unused directory objects""" for key in tuple(self.directories): value = self.directories[key] if age != -1: if not value.is_older_than(age) \ or any(value in tab.pathway for tab in self.tabs.values()): continue del self.directories[key] if value.is_directory: value.files = None self.settings.signal_garbage_collect() self.signal_garbage_collect() def loop(self): """The main loop of ranger. It consists of: 1. reloading bookmarks if outdated 2. letting the loader work 3. drawing and finalizing ui 4. reading and handling user input 5. after X loops: collecting unused directory objects """ self.enter_dir(self.thistab.path) # for faster lookup: ui = self.ui throbber = ui.throbber loader = self.loader zombies = self.run.zombies ranger.api.hook_ready(self) try: # pylint: disable=too-many-nested-blocks while True: loader.work() if loader.has_work(): throbber(loader.status) else: throbber(remove=True) ui.redraw() ui.set_load_mode(not loader.paused and loader.has_work()) ui.draw_images() ui.handle_input() if zombies: for zombie in tuple(zombies): if zombie.poll() is not None: zombies.remove(zombie) # gc_tick += 1 # if gc_tick > ranger.TICKS_BEFORE_COLLECTING_GARBAGE: # gc_tick = 0 # self.garbage_collect(ranger.TIME_BEFORE_FILE_BECOMES_GARBAGE) except KeyboardInterrupt: # this only happens in --debug mode. By default, interrupts # are caught in curses_interrupt_handler raise SystemExit finally: self.image_displayer.quit() if ranger.args.choosedir and self.thisdir and self.thisdir.path: # XXX: UnicodeEncodeError: 'utf-8' codec can't encode character # '\udcf6' in position 42: surrogates not allowed with open(ranger.args.choosedir, 'w') as fobj: fobj.write(self.thisdir.path) self.bookmarks.remember(self.thisdir) self.bookmarks.save() # Save tabs if not ranger.args.clean and self.settings.save_tabs_on_exit and len(self.tabs) > 1: with open(self.datapath('tabs'), 'a') as fobj: # Don't save active tab since launching ranger changes the active tab fobj.write('\0'.join(v.path for t, v in self.tabs.items() if t != self.current_tab) + '\0\0')
class FM(Actions, SignalDispatcher): input_blocked = False input_blocked_until = 0 mode = "normal" # either 'normal' or 'visual'. search_method = "ctime" _previous_selection = None _visual_reverse = False _visual_start = None _visual_start_pos = None def __init__(self, ui=None, bookmarks=None, tags=None, paths=["."]): """Initialize FM.""" Actions.__init__(self) SignalDispatcher.__init__(self) if ui is None: self.ui = UI() else: self.ui = ui self.start_paths = paths self.directories = dict() self.log = deque(maxlen=20) self.bookmarks = bookmarks self.current_tab = 1 self.tabs = {} self.tags = tags self.restorable_tabs = deque([], ranger.MAX_RESTORABLE_TABS) self.py3 = sys.version_info >= (3,) self.previews = {} self.loader = Loader() self.copy_buffer = set() self.do_cut = False try: self.username = pwd.getpwuid(os.geteuid()).pw_name except: self.username = "******" + str(os.geteuid()) self.hostname = socket.gethostname() self.home_path = os.path.expanduser("~") self.log.append("ranger {0} started! Process ID is {1}.".format(__version__, os.getpid())) self.log.append("Running on Python " + sys.version.replace("\n", "")) mimetypes.knownfiles.append(os.path.expanduser("~/.mime.types")) mimetypes.knownfiles.append(self.relpath("data/mime.types")) self.mimetypes = mimetypes.MimeTypes() def initialize(self): """If ui/bookmarks are None, they will be initialized here.""" self.tabs = dict((n + 1, Tab(path)) for n, path in enumerate(self.start_paths)) tab_list = self._get_tab_list() if tab_list: self.current_tab = tab_list[0] self.thistab = self.tabs[self.current_tab] else: self.current_tab = 1 self.tabs[self.current_tab] = self.thistab = Tab(".") if not ranger.arg.clean and os.path.isfile(self.confpath("rifle.conf")): rifleconf = self.confpath("rifle.conf") else: rifleconf = self.relpath("config/rifle.conf") self.rifle = Rifle(rifleconf) self.rifle.reload_config() if self.bookmarks is None: if ranger.arg.clean: bookmarkfile = None else: bookmarkfile = self.confpath("bookmarks") self.bookmarks = Bookmarks( bookmarkfile=bookmarkfile, bookmarktype=Directory, autosave=self.settings.autosave_bookmarks ) self.bookmarks.load() if not ranger.arg.clean and self.tags is None: self.tags = Tags(self.confpath("tagged")) self.ui.setup_curses() self.ui.initialize() self.rifle.hook_before_executing = lambda a, b, flags: self.ui.suspend() if "f" not in flags else None self.rifle.hook_after_executing = lambda a, b, flags: self.ui.initialize() if "f" not in flags else None self.rifle.hook_logger = self.notify def mylogfunc(text): self.notify(text, bad=True) self.run = Runner(ui=self.ui, logfunc=mylogfunc, fm=self) def destroy(self): debug = ranger.arg.debug if self.ui: try: self.ui.destroy() except: if debug: raise if self.loader: try: self.loader.destroy() except: if debug: raise def _get_thisfile(self): return self.thistab.thisfile def _set_thisfile(self, obj): self.thistab.thisfile = obj def _get_thisdir(self): return self.thistab.thisdir def _set_thisdir(self, obj): self.thistab.thisdir = obj thisfile = property(_get_thisfile, _set_thisfile) thisdir = property(_get_thisdir, _set_thisdir) def block_input(self, sec=0): self.input_blocked = sec != 0 self.input_blocked_until = time() + sec def input_is_blocked(self): if self.input_blocked and time() > self.input_blocked_until: self.input_blocked = False return self.input_blocked def copy_config_files(self, which): if ranger.arg.clean: sys.stderr.write("refusing to copy config files in clean mode\n") return import shutil def copy(_from, to): if os.path.exists(self.confpath(to)): sys.stderr.write("already exists: %s\n" % self.confpath(to)) else: sys.stderr.write("creating: %s\n" % self.confpath(to)) try: shutil.copy(self.relpath(_from), self.confpath(to)) except Exception as e: sys.stderr.write(" ERROR: %s\n" % str(e)) if which == "rifle" or which == "all": copy("config/rifle.conf", "rifle.conf") if which == "commands" or which == "all": copy("config/commands.py", "commands.py") if which == "rc" or which == "all": copy("config/rc.conf", "rc.conf") if which == "scope" or which == "all": copy("data/scope.sh", "scope.sh") os.chmod(self.confpath("scope.sh"), os.stat(self.confpath("scope.sh")).st_mode | stat.S_IXUSR) if which not in ("all", "rifle", "scope", "commands", "rc"): sys.stderr.write("Unknown config file `%s'\n" % which) def confpath(self, *paths): """returns the path relative to rangers configuration directory""" if ranger.arg.clean: assert 0, "Should not access relpath_conf in clean mode!" else: return os.path.join(ranger.arg.confdir, *paths) def relpath(self, *paths): """returns the path relative to rangers library directory""" return os.path.join(ranger.RANGERDIR, *paths) def get_directory(self, path): """Get the directory object at the given path""" path = os.path.abspath(path) try: return self.directories[path] except KeyError: obj = Directory(path) self.directories[path] = obj return obj def garbage_collect(self, age, tabs=None): # tabs=None is for COMPATibility """Delete unused directory objects""" for key in tuple(self.directories): value = self.directories[key] if age != -1: if not value.is_older_than(age) or any(value in tab.pathway for tab in self.tabs.values()): continue del self.directories[key] if value.is_directory: value.files = None self.settings.signal_garbage_collect() self.signal_garbage_collect() def loop(self): """ The main loop consists of: 1. reloading bookmarks if outdated 2. letting the loader work 3. drawing and finalizing ui 4. reading and handling user input 5. after X loops: collecting unused directory objects """ self.enter_dir(self.thistab.path) gc_tick = 0 # for faster lookup: ui = self.ui throbber = ui.throbber loader = self.loader has_throbber = hasattr(ui, "throbber") zombies = self.run.zombies ranger.api.hook_ready(self) try: while True: loader.work() if has_throbber: if loader.has_work(): throbber(loader.status) else: throbber(remove=True) ui.redraw() ui.set_load_mode(not loader.paused and loader.has_work()) ui.handle_input() if zombies: for zombie in tuple(zombies): if zombie.poll() is not None: zombies.remove(zombie) gc_tick += 1 if gc_tick > ranger.TICKS_BEFORE_COLLECTING_GARBAGE: gc_tick = 0 self.garbage_collect(ranger.TIME_BEFORE_FILE_BECOMES_GARBAGE) except KeyboardInterrupt: # this only happens in --debug mode. By default, interrupts # are caught in curses_interrupt_handler raise SystemExit finally: if ranger.arg.choosedir and self.thisdir and self.thisdir.path: # XXX: UnicodeEncodeError: 'utf-8' codec can't encode character # '\udcf6' in position 42: surrogates not allowed open(ranger.arg.choosedir, "w").write(self.thisdir.path) self.bookmarks.remember(self.thisdir) self.bookmarks.save()
def main(): """initialize objects and run the filemanager""" import locale import ranger.api from ranger.container.settings import Settings from ranger.core.shared import FileManagerAware, SettingsAware from ranger.core.fm import FM if not sys.stdin.isatty(): sys.stderr.write("Error: Must run ranger from terminal\n") raise SystemExit(1) try: locale.setlocale(locale.LC_ALL, '') except: print("Warning: Unable to set locale. Expect encoding problems.") # so that programs can know that ranger spawned them: level = 'RANGER_LEVEL' if level in os.environ and os.environ[level].isdigit(): os.environ[level] = str(int(os.environ[level]) + 1) else: os.environ[level] = '1' if not 'SHELL' in os.environ: os.environ['SHELL'] = 'sh' ranger.arg = arg = parse_arguments() if arg.copy_config is not None: fm = FM() fm.copy_config_files(arg.copy_config) return 1 if arg.fail_unless_cd else 0 # COMPAT if arg.list_tagged_files: fm = FM() try: f = open(fm.confpath('tagged'), 'r') except: pass else: for line in f.readlines(): if len(line) > 2 and line[1] == ':': if line[0] in arg.list_tagged_files: sys.stdout.write(line[2:]) elif len(line) > 0 and '*' in arg.list_tagged_files: sys.stdout.write(line) return 1 if arg.fail_unless_cd else 0 # COMPAT SettingsAware._setup(Settings()) if arg.selectfile: arg.selectfile = os.path.abspath(arg.selectfile) arg.targets.insert(0, os.path.dirname(arg.selectfile)) targets = arg.targets or ['.'] target = targets[0] if arg.targets: # COMPAT if target.startswith('file://'): target = target[7:] if not os.access(target, os.F_OK): print("File or directory doesn't exist: %s" % target) return 1 elif os.path.isfile(target): sys.stderr.write("Warning: Using ranger as a file launcher is " "deprecated.\nPlease use the standalone file launcher " "'rifle' instead.\n") def print_function(string): print(string) from ranger.ext.rifle import Rifle fm = FM() if not arg.clean and os.path.isfile(fm.confpath('rifle.conf')): rifleconf = fm.confpath('rifle.conf') else: rifleconf = fm.relpath('config/rifle.conf') rifle = Rifle(rifleconf) rifle.reload_config() rifle.execute(targets, number=ranger.arg.mode, flags=ranger.arg.flags) return 1 if arg.fail_unless_cd else 0 # COMPAT crash_traceback = None try: # Initialize objects fm = FM(paths=targets) FileManagerAware._setup(fm) load_settings(fm, arg.clean) if arg.list_unused_keys: from ranger.ext.keybinding_parser import (special_keys, reversed_special_keys) maps = fm.ui.keymaps['browser'] for key in sorted(special_keys.values(), key=lambda x: str(x)): if key not in maps: print("<%s>" % reversed_special_keys[key]) for key in range(33, 127): if key not in maps: print(chr(key)) return 1 if arg.fail_unless_cd else 0 # COMPAT if fm.username == 'root': fm.settings.preview_files = False fm.settings.use_preview_script = False if not arg.debug: from ranger.ext import curses_interrupt_handler curses_interrupt_handler.install_interrupt_handler() # Run the file manager fm.initialize() ranger.api.hook_init(fm) fm.ui.initialize() if arg.selectfile: fm.select_file(arg.selectfile) if arg.cmd: for command in arg.cmd: fm.execute_console(command) if ranger.arg.profile: import cProfile import pstats profile = None ranger.__fm = fm cProfile.run('ranger.__fm.loop()', '/tmp/ranger_profile') profile = pstats.Stats('/tmp/ranger_profile', stream=sys.stderr) else: fm.loop() except Exception: import traceback crash_traceback = traceback.format_exc() except SystemExit as error: return error.args[0] finally: if crash_traceback: try: filepath = fm.thisfile.path if fm.thisfile else "None" except: filepath = "None" try: fm.ui.destroy() except (AttributeError, NameError): pass if ranger.arg.profile and profile: profile.strip_dirs().sort_stats('cumulative').print_callees() if crash_traceback: print("ranger version: %s, executed with python %s" % (ranger.__version__, sys.version.split()[0])) print("Locale: %s" % '.'.join(str(s) for s in locale.getlocale())) try: print("Current file: %s" % filepath) except: pass print(crash_traceback) print("ranger crashed. " \ "Please report this traceback at:") print("http://savannah.nongnu.org/bugs/?group=ranger&func=additem") return 1 return 0
def main(): """initialize objects and run the filemanager""" import locale import ranger.api from ranger.container.settings import Settings from ranger.core.shared import FileManagerAware, SettingsAware from ranger.core.fm import FM try: locale.setlocale(locale.LC_ALL, '') except: print("Warning: Unable to set locale. Expect encoding problems.") # so that programs can know that ranger spawned them: level = 'RANGER_LEVEL' if level in os.environ and os.environ[level].isdigit(): os.environ[level] = str(int(os.environ[level]) + 1) else: os.environ[level] = '1' if not 'SHELL' in os.environ: os.environ['SHELL'] = 'sh' ranger.arg = arg = parse_arguments() if arg.copy_config is not None: fm = FM() fm.copy_config_files(arg.copy_config) return 1 if arg.fail_unless_cd else 0 # COMPAT if arg.list_tagged_files: fm = FM() try: if sys.version_info[0] >= 3: f = open(fm.confpath('tagged'), 'r', errors='replace') else: f = open(fm.confpath('tagged'), 'r') except: pass else: for line in f.readlines(): if len(line) > 2 and line[1] == ':': if line[0] in arg.list_tagged_files: sys.stdout.write(line[2:]) elif len(line) > 0 and '*' in arg.list_tagged_files: sys.stdout.write(line) return 1 if arg.fail_unless_cd else 0 # COMPAT SettingsAware._setup(Settings()) if arg.selectfile: arg.selectfile = os.path.abspath(arg.selectfile) arg.targets.insert(0, os.path.dirname(arg.selectfile)) targets = arg.targets or ['.'] target = targets[0] if arg.targets: # COMPAT if target.startswith('file://'): target = target[7:] if not os.access(target, os.F_OK): print("File or directory doesn't exist: %s" % target) return 1 elif os.path.isfile(target): sys.stderr.write( "Warning: Using ranger as a file launcher is " "deprecated.\nPlease use the standalone file launcher " "'rifle' instead.\n") def print_function(string): print(string) from ranger.ext.rifle import Rifle fm = FM() if not arg.clean and os.path.isfile(fm.confpath('rifle.conf')): rifleconf = fm.confpath('rifle.conf') else: rifleconf = fm.relpath('config/rifle.conf') rifle = Rifle(rifleconf) rifle.reload_config() rifle.execute(targets, number=ranger.arg.mode, flags=ranger.arg.flags) return 1 if arg.fail_unless_cd else 0 # COMPAT crash_traceback = None try: # Initialize objects fm = FM(paths=targets) FileManagerAware._setup(fm) load_settings(fm, arg.clean) if arg.list_unused_keys: from ranger.ext.keybinding_parser import (special_keys, reversed_special_keys) maps = fm.ui.keymaps['browser'] for key in sorted(special_keys.values(), key=lambda x: str(x)): if key not in maps: print("<%s>" % reversed_special_keys[key]) for key in range(33, 127): if key not in maps: print(chr(key)) return 1 if arg.fail_unless_cd else 0 # COMPAT if not sys.stdin.isatty(): sys.stderr.write("Error: Must run ranger from terminal\n") raise SystemExit(1) #if fm.username == 'root': # fm.settings.preview_files = False # fm.settings.use_preview_script = False # fm.log.append("Running as root, disabling the file previews.") if not arg.debug: from ranger.ext import curses_interrupt_handler curses_interrupt_handler.install_interrupt_handler() # Create cache directory if fm.settings.preview_images and fm.settings.use_preview_script: if not os.path.exists(arg.cachedir): os.makedirs(arg.cachedir) # Run the file manager ranger.api.hook_init(fm) fm.start_paths = arg.targets fm.initialize() fm.ui.initialize() if arg.selectfile: fm.select_file(arg.selectfile) if arg.cmd: for command in arg.cmd: fm.execute_console(command) if ranger.arg.profile: import cProfile import pstats profile = None ranger.__fm = fm cProfile.run('ranger.__fm.loop()', tempfile.gettempdir() + '/ranger_profile') profile = pstats.Stats(tempfile.gettempdir() + '/ranger_profile', stream=sys.stderr) else: fm.loop() except Exception: import traceback crash_traceback = traceback.format_exc() except SystemExit as error: return error.args[0] finally: if crash_traceback: try: filepath = fm.thisfile.path if fm.thisfile else "None" except: filepath = "None" try: fm.ui.destroy() except (AttributeError, NameError): pass if ranger.arg.profile and profile: profile.strip_dirs().sort_stats('cumulative').print_callees() if crash_traceback: print("ranger version: %s, executed with python %s" % (ranger.__version__, sys.version.split()[0])) print("Locale: %s" % '.'.join(str(s) for s in locale.getlocale())) try: print("Current file: %s" % filepath) except: pass print(crash_traceback) print("ranger crashed. " \ "Please report this traceback at:") print("https://github.com/hut/ranger/issues") return 1 return 0
def initialize(self): """If ui/bookmarks are None, they will be initialized here.""" self.tabs = dict( (n + 1, Tab(path)) for n, path in enumerate(self.start_paths)) tab_list = self.get_tab_list() if tab_list: self.current_tab = tab_list[0] self.thistab = self.tabs[self.current_tab] else: self.current_tab = 1 self.tabs[self.current_tab] = self.thistab = Tab('.') if not ranger.args.clean and os.path.isfile( self.confpath('rifle.conf')): rifleconf = self.confpath('rifle.conf') else: rifleconf = self.relpath('config/rifle.conf') self.rifle = Rifle(rifleconf) self.rifle.reload_config() def set_image_displayer(): self.image_displayer = self._get_image_displayer() set_image_displayer() self.settings.signal_bind('setopt.preview_images_method', set_image_displayer, priority=settings.SIGNAL_PRIORITY_AFTER_SYNC) self.settings.signal_bind( 'setopt.preview_images', lambda signal: signal.fm.previews.clear(), ) if ranger.args.clean: self.tags = TagsDummy("") elif self.tags is None: self.tags = Tags(self.datapath('tagged')) if self.bookmarks is None: if ranger.args.clean: bookmarkfile = None else: bookmarkfile = self.datapath('bookmarks') self.bookmarks = Bookmarks( bookmarkfile=bookmarkfile, bookmarktype=Directory, autosave=self.settings.autosave_bookmarks) self.bookmarks.load() self.bookmarks.enable_saving_backtick_bookmark( self.settings.save_backtick_bookmark) self.ui.setup_curses() self.ui.initialize() self.rifle.hook_before_executing = lambda a, b, flags: \ self.ui.suspend() if 'f' not in flags else None self.rifle.hook_after_executing = lambda a, b, flags: \ self.ui.initialize() if 'f' not in flags else None self.rifle.hook_logger = self.notify old_preprocessing_hook = self.rifle.hook_command_preprocessing # This hook allows image viewers to open all images in the current # directory, keeping the order of files the same as in ranger. # The requirements to use it are: # 1. set open_all_images to true # 2. ensure no files are marked # 3. call rifle with a command that starts with "sxiv " or "feh " def sxiv_workaround_hook(command): import re from ranger.ext.shell_escape import shell_quote if self.settings.open_all_images and \ not self.thisdir.marked_items and \ re.match(r'^(feh|sxiv|imv|pqiv) ', command): images = [ f.relative_path for f in self.thisdir.files if f.image ] escaped_filenames = " ".join( shell_quote(f) for f in images if "\x00" not in f) if images and self.thisfile.relative_path in images and \ "$@" in command: new_command = None if command[0:5] == 'sxiv ': number = images.index(self.thisfile.relative_path) + 1 new_command = command.replace("sxiv ", "sxiv -n %d " % number, 1) if command[0:4] == 'feh ': new_command = command.replace( "feh ", "feh --start-at %s " % shell_quote(self.thisfile.relative_path), 1, ) if command[0:4] == 'imv ': number = images.index(self.thisfile.relative_path) + 1 new_command = command.replace("imv ", "imv -n %d " % number, 1) if command[0:5] == 'pqiv ': number = images.index(self.thisfile.relative_path) new_command = command.replace( "pqiv ", "pqiv --action \"goto_file_byindex(%d)\" " % number, 1) if new_command: command = "set -- %s; %s" % (escaped_filenames, new_command) return old_preprocessing_hook(command) self.rifle.hook_command_preprocessing = sxiv_workaround_hook def mylogfunc(text): self.notify(text, bad=True) self.run = Runner(ui=self.ui, logfunc=mylogfunc, fm=self) self.settings.signal_bind( 'setopt.metadata_deep_search', lambda signal: setattr( signal.fm.metadata, 'deep_search', signal.value)) self.settings.signal_bind( 'setopt.save_backtick_bookmark', lambda signal: signal.fm.bookmarks .enable_saving_backtick_bookmark(signal.value))
class FM( Actions, # pylint: disable=too-many-instance-attributes SignalDispatcher): input_blocked = False input_blocked_until = 0 mode = 'normal' # either 'normal' or 'visual'. search_method = 'ctime' _previous_selection = None _visual_reverse = False _visual_pos_start = None _visual_move_cycles = None def __init__(self, ui=None, bookmarks=None, tags=None, paths=None): """Initialize FM.""" Actions.__init__(self) SignalDispatcher.__init__(self) self.ui = ui if ui is not None else UI() self.start_paths = paths if paths is not None else ['.'] self.directories = dict() self.bookmarks = bookmarks self.current_tab = 1 self.tabs = {} self.tags = tags self.restorable_tabs = deque([], ranger.MAX_RESTORABLE_TABS) self.py3 = sys.version_info >= (3, ) self.previews = {} self.default_linemodes = deque() self.loader = Loader() self.copy_buffer = set() self.do_cut = False self.metadata = MetadataManager() self.image_displayer = None self.run = None self.rifle = None self.thistab = None try: self.username = pwd.getpwuid(os.geteuid()).pw_name except KeyError: self.username = '******' + str(os.geteuid()) self.hostname = socket.gethostname() self.home_path = os.path.expanduser('~') mimetypes.knownfiles.append(os.path.expanduser('~/.mime.types')) mimetypes.knownfiles.append(self.relpath('data/mime.types')) self.mimetypes = mimetypes.MimeTypes() def initialize(self): """If ui/bookmarks are None, they will be initialized here.""" self.tabs = dict( (n + 1, Tab(path)) for n, path in enumerate(self.start_paths)) tab_list = self.get_tab_list() if tab_list: self.current_tab = tab_list[0] self.thistab = self.tabs[self.current_tab] else: self.current_tab = 1 self.tabs[self.current_tab] = self.thistab = Tab('.') if not ranger.args.clean and os.path.isfile( self.confpath('rifle.conf')): rifleconf = self.confpath('rifle.conf') else: rifleconf = self.relpath('config/rifle.conf') self.rifle = Rifle(rifleconf) self.rifle.reload_config() def set_image_displayer(): self.image_displayer = self._get_image_displayer() set_image_displayer() self.settings.signal_bind('setopt.preview_images_method', set_image_displayer, priority=settings.SIGNAL_PRIORITY_AFTER_SYNC) self.settings.signal_bind( 'setopt.preview_images', lambda signal: signal.fm.previews.clear(), ) if ranger.args.clean: self.tags = TagsDummy("") elif self.tags is None: self.tags = Tags(self.datapath('tagged')) if self.bookmarks is None: if ranger.args.clean: bookmarkfile = None else: bookmarkfile = self.datapath('bookmarks') self.bookmarks = Bookmarks( bookmarkfile=bookmarkfile, bookmarktype=Directory, autosave=self.settings.autosave_bookmarks) self.bookmarks.load() self.bookmarks.enable_saving_backtick_bookmark( self.settings.save_backtick_bookmark) self.ui.setup_curses() self.ui.initialize() self.rifle.hook_before_executing = lambda a, b, flags: \ self.ui.suspend() if 'f' not in flags else None self.rifle.hook_after_executing = lambda a, b, flags: \ self.ui.initialize() if 'f' not in flags else None self.rifle.hook_logger = self.notify old_preprocessing_hook = self.rifle.hook_command_preprocessing # This hook allows image viewers to open all images in the current # directory, keeping the order of files the same as in ranger. # The requirements to use it are: # 1. set open_all_images to true # 2. ensure no files are marked # 3. call rifle with a command that starts with "sxiv " or "feh " def sxiv_workaround_hook(command): import re from ranger.ext.shell_escape import shell_quote if self.settings.open_all_images and \ not self.thisdir.marked_items and \ re.match(r'^(feh|sxiv|imv|pqiv) ', command): images = [ f.relative_path for f in self.thisdir.files if f.image ] escaped_filenames = " ".join( shell_quote(f) for f in images if "\x00" not in f) if images and self.thisfile.relative_path in images and \ "$@" in command: new_command = None if command[0:5] == 'sxiv ': number = images.index(self.thisfile.relative_path) + 1 new_command = command.replace("sxiv ", "sxiv -n %d " % number, 1) if command[0:4] == 'feh ': new_command = command.replace( "feh ", "feh --start-at %s " % shell_quote(self.thisfile.relative_path), 1, ) if command[0:4] == 'imv ': number = images.index(self.thisfile.relative_path) + 1 new_command = command.replace("imv ", "imv -n %d " % number, 1) if command[0:5] == 'pqiv ': number = images.index(self.thisfile.relative_path) new_command = command.replace( "pqiv ", "pqiv --action \"goto_file_byindex(%d)\" " % number, 1) if new_command: command = "set -- %s; %s" % (escaped_filenames, new_command) return old_preprocessing_hook(command) self.rifle.hook_command_preprocessing = sxiv_workaround_hook def mylogfunc(text): self.notify(text, bad=True) self.run = Runner(ui=self.ui, logfunc=mylogfunc, fm=self) self.settings.signal_bind( 'setopt.metadata_deep_search', lambda signal: setattr( signal.fm.metadata, 'deep_search', signal.value)) self.settings.signal_bind( 'setopt.save_backtick_bookmark', lambda signal: signal.fm.bookmarks .enable_saving_backtick_bookmark(signal.value)) def destroy(self): debug = ranger.args.debug if self.ui: try: self.ui.destroy() except Exception: # pylint: disable=broad-except if debug: raise if self.loader: try: self.loader.destroy() except Exception: # pylint: disable=broad-except if debug: raise @staticmethod def get_log(): """Return the current log The log is returned as a generator over its entries' lines """ for entry in logutils.QUEUE: for line in entry.splitlines(): yield line def _get_image_displayer(self): if self.settings.preview_images_method == "w3m": return W3MImageDisplayer() elif self.settings.preview_images_method == "iterm2": return ITerm2ImageDisplayer() elif self.settings.preview_images_method == "terminology": return TerminologyImageDisplayer() elif self.settings.preview_images_method == "urxvt": return URXVTImageDisplayer() elif self.settings.preview_images_method == "urxvt-full": return URXVTImageFSDisplayer() return ImageDisplayer() def _get_thisfile(self): return self.thistab.thisfile def _set_thisfile(self, obj): self.thistab.thisfile = obj def _get_thisdir(self): return self.thistab.thisdir def _set_thisdir(self, obj): self.thistab.thisdir = obj thisfile = property(_get_thisfile, _set_thisfile) thisdir = property(_get_thisdir, _set_thisdir) def block_input(self, sec=0): self.input_blocked = sec != 0 self.input_blocked_until = time() + sec def input_is_blocked(self): if self.input_blocked and time() > self.input_blocked_until: self.input_blocked = False return self.input_blocked def copy_config_files(self, which): if ranger.args.clean: sys.stderr.write("refusing to copy config files in clean mode\n") return import shutil from errno import EEXIST def copy(src, dest): if os.path.exists(self.confpath(dest)): sys.stderr.write("already exists: %s\n" % self.confpath(dest)) else: sys.stderr.write("creating: %s\n" % self.confpath(dest)) try: os.makedirs(ranger.args.confdir) except OSError as err: if err.errno != EEXIST: # EEXIST means it already exists print( "This configuration directory could not be created:" ) print(ranger.args.confdir) print( "To run ranger without the need for configuration") print("files, use the --clean option.") raise SystemExit try: shutil.copy(self.relpath(src), self.confpath(dest)) except OSError as ex: sys.stderr.write(" ERROR: %s\n" % str(ex)) if which == 'rifle' or which == 'all': copy('config/rifle.conf', 'rifle.conf') if which == 'commands' or which == 'all': copy('config/commands_sample.py', 'commands.py') if which == 'commands_full' or which == 'all': copy('config/commands.py', 'commands_full.py') if which == 'rc' or which == 'all': copy('config/rc.conf', 'rc.conf') if which == 'scope' or which == 'all': copy('data/scope.sh', 'scope.sh') os.chmod(self.confpath('scope.sh'), os.stat(self.confpath('scope.sh')).st_mode | stat.S_IXUSR) if which in ('all', 'rifle', 'scope', 'commands', 'commands_full', 'rc'): sys.stderr.write( "\n> Please note that configuration files may " "change as ranger evolves.\n It's completely up to you to " "keep them up to date.\n") if os.environ.get('RANGER_LOAD_DEFAULT_RC', 'TRUE').upper() != 'FALSE': sys.stderr.write( "\n> To stop ranger from loading " "\033[1mboth\033[0m the default and your custom rc.conf,\n" " please set the environment variable " "\033[1mRANGER_LOAD_DEFAULT_RC\033[0m to " "\033[1mFALSE\033[0m.\n") else: sys.stderr.write("Unknown config file `%s'\n" % which) def confpath(self, *paths): """returns path to ranger's configuration directory""" if ranger.args.clean: self.notify("Accessed configuration directory in clean mode", bad=True) return None return os.path.join(ranger.args.confdir, *paths) def datapath(self, *paths): """returns path to ranger's data directory""" if ranger.args.clean: self.notify("Accessed data directory in clean mode", bad=True) return None path_compat = self.confpath(*paths) # COMPAT if os.path.exists(path_compat): return path_compat return os.path.join(ranger.args.datadir, *paths) @staticmethod def relpath(*paths): """returns the path relative to rangers library directory""" return os.path.join(ranger.RANGERDIR, *paths) def get_directory(self, path, **dir_kwargs): """Get the directory object at the given path""" path = os.path.abspath(path) try: return self.directories[path] except KeyError: obj = Directory(path, **dir_kwargs) self.directories[path] = obj return obj def garbage_collect(self, age, tabs=None): # tabs=None is for COMPATibility pylint: disable=unused-argument """Delete unused directory objects""" for key in tuple(self.directories): value = self.directories[key] if age != -1: if not value.is_older_than(age) \ or any(value in tab.pathway for tab in self.tabs.values()): continue del self.directories[key] if value.is_directory: value.files = None self.settings.signal_garbage_collect() self.signal_garbage_collect() def loop(self): """The main loop of ranger. It consists of: 1. reloading bookmarks if outdated 2. letting the loader work 3. drawing and finalizing ui 4. reading and handling user input 5. after X loops: collecting unused directory objects """ self.enter_dir(self.thistab.path) # for faster lookup: ui = self.ui throbber = ui.throbber loader = self.loader zombies = self.run.zombies ranger.api.hook_ready(self) try: # pylint: disable=too-many-nested-blocks while True: loader.work() if loader.has_work(): throbber(loader.status) else: throbber(remove=True) ui.redraw() ui.set_load_mode(not loader.paused and loader.has_work()) ui.draw_images() ui.handle_input() if zombies: for zombie in tuple(zombies): if zombie.poll() is not None: zombies.remove(zombie) # gc_tick += 1 # if gc_tick > ranger.TICKS_BEFORE_COLLECTING_GARBAGE: # gc_tick = 0 # self.garbage_collect(ranger.TIME_BEFORE_FILE_BECOMES_GARBAGE) except KeyboardInterrupt: # this only happens in --debug mode. By default, interrupts # are caught in curses_interrupt_handler raise SystemExit finally: self.image_displayer.quit() if ranger.args.choosedir and self.thisdir and self.thisdir.path: # XXX: UnicodeEncodeError: 'utf-8' codec can't encode character # '\udcf6' in position 42: surrogates not allowed with open(ranger.args.choosedir, 'w') as fobj: fobj.write(self.thisdir.path) self.bookmarks.remember(self.thisdir) self.bookmarks.save() # Save tabs if not ranger.args.clean and self.settings.save_tabs_on_exit and len( self.tabs) > 1: with open(self.datapath('tabs'), 'a') as fobj: # Don't save active tab since launching ranger changes the active tab fobj.write('\0'.join(v.path for t, v in self.tabs.items()) + '\0\0')
def initialize(self): """If ui/bookmarks are None, they will be initialized here.""" self.tabs = dict((n+1, Tab(path)) for n, path in enumerate(self.start_paths)) tab_list = self._get_tab_list() if tab_list: self.current_tab = tab_list[0] self.thistab = self.tabs[self.current_tab] else: self.current_tab = 1 self.tabs[self.current_tab] = self.thistab = Tab('.') if not ranger.arg.clean and os.path.isfile(self.confpath('rifle.conf')): rifleconf = self.confpath('rifle.conf') else: rifleconf = self.relpath('config/rifle.conf') self.rifle = Rifle(rifleconf) self.rifle.reload_config() if self.bookmarks is None: if ranger.arg.clean: bookmarkfile = None else: bookmarkfile = self.confpath('bookmarks') self.bookmarks = Bookmarks( bookmarkfile=bookmarkfile, bookmarktype=Directory, autosave=self.settings.autosave_bookmarks) self.bookmarks.load() if not ranger.arg.clean and self.tags is None: self.tags = Tags(self.confpath('tagged')) self.ui.setup_curses() self.ui.initialize() self.rifle.hook_before_executing = lambda a, b, flags: \ self.ui.suspend() if 'f' not in flags else None self.rifle.hook_after_executing = lambda a, b, flags: \ self.ui.initialize() if 'f' not in flags else None self.rifle.hook_logger = self.notify # This hook allows image viewers to open all images in the current # directory, keeping the order of files the same as in ranger. # The requirements to use it are: # 1. set open_all_images to true # 2. ensure no files are marked # 3. call rifle with a command that starts with "sxiv " or "feh " def sxiv_workaround_hook(command): import re from ranger.ext.shell_escape import shell_quote if self.settings.open_all_images and \ len(self.thisdir.marked_items) == 0 and \ re.match(r'^(feh|sxiv) ', command): images = [f.basename for f in self.thisdir.files if f.image] escaped_filenames = " ".join(shell_quote(f) \ for f in images if "\x00" not in f) if images and self.thisfile.basename in images and \ "$@" in command: new_command = None if command[0:5] == 'sxiv ': number = images.index(self.thisfile.basename) + 1 new_command = command.replace("sxiv ", "sxiv -n %d " % number, 1) if command[0:4] == 'feh ': new_command = command.replace("feh ", "feh --start-at %s " % \ shell_quote(self.thisfile.basename), 1) if new_command: command = "set -- %s; %s" % (escaped_filenames, new_command) return command self.rifle.hook_command_preprocessing = sxiv_workaround_hook def mylogfunc(text): self.notify(text, bad=True) self.run = Runner(ui=self.ui, logfunc=mylogfunc, fm=self)
class FM(Actions, SignalDispatcher): input_blocked = False input_blocked_until = 0 mode = 'normal' # either 'normal' or 'visual'. search_method = 'ctime' _previous_selection = None _visual_reverse = False _visual_start = None _visual_start_pos = None def __init__(self, ui=None, bookmarks=None, tags=None, paths=['.']): """Initialize FM.""" Actions.__init__(self) SignalDispatcher.__init__(self) if ui is None: self.ui = UI() else: self.ui = ui self.start_paths = paths self.directories = dict() self.log = deque(maxlen=20) self.image_displayer = ImageDisplayer(self) self.bookmarks = bookmarks self.current_tab = 1 self.tabs = {} self.tags = tags self.restorable_tabs = deque([], ranger.MAX_RESTORABLE_TABS) self.py3 = sys.version_info >= (3, ) self.previews = {} self.loader = Loader() self.copy_buffer = set() self.do_cut = False self.control_server = RangerControlServer(self) try: self.username = pwd.getpwuid(os.geteuid()).pw_name except: self.username = '******' + str(os.geteuid()) self.hostname = socket.gethostname() self.home_path = os.path.expanduser('~') self.log.append('ranger {0} started! Process ID is {1}.' \ .format(__version__, os.getpid())) self.log.append('Running on Python ' + sys.version.replace('\n','')) mimetypes.knownfiles.append(os.path.expanduser('~/.mime.types')) mimetypes.knownfiles.append(self.relpath('data/mime.types')) self.mimetypes = mimetypes.MimeTypes() def initialize(self): """If ui/bookmarks are None, they will be initialized here.""" self.tabs = dict((n+1, Tab(path)) for n, path in enumerate(self.start_paths)) tab_list = self._get_tab_list() if tab_list: self.current_tab = tab_list[0] self.thistab = self.tabs[self.current_tab] else: self.current_tab = 1 self.tabs[self.current_tab] = self.thistab = Tab('.') if not ranger.arg.clean and os.path.isfile(self.confpath('rifle.conf')): rifleconf = self.confpath('rifle.conf') else: rifleconf = self.relpath('config/rifle.conf') self.rifle = Rifle(rifleconf) self.rifle.reload_config() if self.bookmarks is None: if ranger.arg.clean: bookmarkfile = None else: bookmarkfile = self.confpath('bookmarks') self.bookmarks = Bookmarks( bookmarkfile=bookmarkfile, bookmarktype=Directory, autosave=self.settings.autosave_bookmarks) self.bookmarks.load() if not ranger.arg.clean and self.tags is None: self.tags = Tags(self.confpath('tagged')) self.ui.setup_curses() self.ui.initialize() self.rifle.hook_before_executing = lambda a, b, flags: \ self.ui.suspend() if 'f' not in flags else None self.rifle.hook_after_executing = lambda a, b, flags: \ self.ui.initialize() if 'f' not in flags else None self.rifle.hook_logger = self.notify # This hook allows image viewers to open all images in the current # directory, keeping the order of files the same as in ranger. # The requirements to use it are: # 1. set open_all_images to true # 2. ensure no files are marked # 3. call rifle with a command that starts with "sxiv " or "feh " def sxiv_workaround_hook(command): import re from ranger.ext.shell_escape import shell_quote if self.settings.open_all_images and \ len(self.thisdir.marked_items) == 0 and \ re.match(r'^(feh|sxiv) ', command): images = [f.basename for f in self.thisdir.files if f.image] escaped_filenames = " ".join(shell_quote(f) \ for f in images if "\x00" not in f) if images and self.thisfile.basename in images and \ "$@" in command: new_command = None if command[0:5] == 'sxiv ': number = images.index(self.thisfile.basename) + 1 new_command = command.replace("sxiv ", "sxiv -n %d " % number, 1) if command[0:4] == 'feh ': new_command = command.replace("feh ", "feh --start-at %s " % \ shell_quote(self.thisfile.basename), 1) if new_command: command = "set -- %s; %s" % (escaped_filenames, new_command) return command self.rifle.hook_command_preprocessing = sxiv_workaround_hook def mylogfunc(text): self.notify(text, bad=True) self.run = Runner(ui=self.ui, logfunc=mylogfunc, fm=self) def destroy(self): debug = ranger.arg.debug if self.ui: try: self.ui.destroy() except: if debug: raise if self.loader: try: self.loader.destroy() except: if debug: raise def _get_thisfile(self): return self.thistab.thisfile def _set_thisfile(self, obj): self.thistab.thisfile = obj def _get_thisdir(self): return self.thistab.thisdir def _set_thisdir(self, obj): self.thistab.thisdir = obj thisfile = property(_get_thisfile, _set_thisfile) thisdir = property(_get_thisdir, _set_thisdir) def block_input(self, sec=0): self.input_blocked = sec != 0 self.input_blocked_until = time() + sec def input_is_blocked(self): if self.input_blocked and time() > self.input_blocked_until: self.input_blocked = False return self.input_blocked def copy_config_files(self, which): if ranger.arg.clean: sys.stderr.write("refusing to copy config files in clean mode\n") return import shutil from errno import EEXIST def copy(_from, to): if os.path.exists(self.confpath(to)): sys.stderr.write("already exists: %s\n" % self.confpath(to)) else: sys.stderr.write("creating: %s\n" % self.confpath(to)) try: os.makedirs(ranger.arg.confdir) except OSError as err: if err.errno != EEXIST: # EEXIST means it already exists print("This configuration directory could not be created:") print(ranger.arg.confdir) print("To run ranger without the need for configuration") print("files, use the --clean option.") raise SystemExit() try: shutil.copy(self.relpath(_from), self.confpath(to)) except Exception as e: sys.stderr.write(" ERROR: %s\n" % str(e)) if which == 'rifle' or which == 'all': copy('config/rifle.conf', 'rifle.conf') if which == 'commands' or which == 'all': copy('config/commands.py', 'commands.py') if which == 'rc' or which == 'all': copy('config/rc.conf', 'rc.conf') if which == 'scope' or which == 'all': copy('data/scope.sh', 'scope.sh') os.chmod(self.confpath('scope.sh'), os.stat(self.confpath('scope.sh')).st_mode | stat.S_IXUSR) if which in ('all', 'rifle', 'scope', 'commands', 'rc'): sys.stderr.write("\nPlease note that configuration files may " "change as ranger evolves.\nIt's completely up to you to keep " "them up to date.\n") else: sys.stderr.write("Unknown config file `%s'\n" % which) def confpath(self, *paths): """returns the path relative to rangers configuration directory""" if ranger.arg.clean: assert 0, "Should not access relpath_conf in clean mode!" else: return os.path.join(ranger.arg.confdir, *paths) def relpath(self, *paths): """returns the path relative to rangers library directory""" return os.path.join(ranger.RANGERDIR, *paths) def get_directory(self, path): """Get the directory object at the given path""" path = os.path.abspath(path) try: return self.directories[path] except KeyError: obj = Directory(path) self.directories[path] = obj return obj def garbage_collect(self, age, tabs=None): # tabs=None is for COMPATibility """Delete unused directory objects""" for key in tuple(self.directories): value = self.directories[key] if age != -1: if not value.is_older_than(age) \ or any(value in tab.pathway for tab in self.tabs.values()): continue del self.directories[key] if value.is_directory: value.files = None self.settings.signal_garbage_collect() self.signal_garbage_collect() def loop(self): """The main loop of ranger. It consists of: 1. reloading bookmarks if outdated 2. letting the loader work 3. drawing and finalizing ui 4. reading and handling user input 5. after X loops: collecting unused directory objects """ self.enter_dir(self.thistab.path) gc_tick = 0 # for faster lookup: ui = self.ui throbber = ui.throbber loader = self.loader has_throbber = hasattr(ui, 'throbber') zombies = self.run.zombies sleeping = False ranger.api.hook_ready(self) self.control_server.start() try: while True: loader.work() if has_throbber: if loader.has_work(): throbber(loader.status) else: throbber(remove=True) if not sleeping: ui.redraw() else: ui.sleep_update() ui.set_load_mode(not loader.paused and loader.has_work()) ui.draw_images() had_input = ui.handle_input() had_command = self.control_server.act_on_messages() sleeping = not (had_input or loader.has_work() or had_command) if zombies: for zombie in tuple(zombies): if zombie.poll() is not None: zombies.remove(zombie) #gc_tick += 1 #if gc_tick > ranger.TICKS_BEFORE_COLLECTING_GARBAGE: #gc_tick = 0 #self.garbage_collect(ranger.TIME_BEFORE_FILE_BECOMES_GARBAGE) except KeyboardInterrupt: # this only happens in --debug mode. By default, interrupts # are caught in curses_interrupt_handler raise SystemExit finally: self.image_displayer.quit() self.control_server.stop() if ranger.arg.choosedir and self.thisdir and self.thisdir.path: # XXX: UnicodeEncodeError: 'utf-8' codec can't encode character # '\udcf6' in position 42: surrogates not allowed open(ranger.arg.choosedir, 'w').write(self.thisdir.path) self.bookmarks.remember(self.thisdir) self.bookmarks.save()
def initialize(self): """If ui/bookmarks are None, they will be initialized here.""" self.tabs = dict( (n + 1, Tab(path)) for n, path in enumerate(self.start_paths)) tab_list = self._get_tab_list() if tab_list: self.current_tab = tab_list[0] self.thistab = self.tabs[self.current_tab] else: self.current_tab = 1 self.tabs[self.current_tab] = self.thistab = Tab('.') if not ranger.arg.clean and os.path.isfile( self.confpath('rifle.conf')): rifleconf = self.confpath('rifle.conf') else: rifleconf = self.relpath('config/rifle.conf') self.rifle = Rifle(rifleconf) self.rifle.reload_config() if self.bookmarks is None: if ranger.arg.clean: bookmarkfile = None else: bookmarkfile = self.confpath('bookmarks') self.bookmarks = Bookmarks( bookmarkfile=bookmarkfile, bookmarktype=Directory, autosave=self.settings.autosave_bookmarks) self.bookmarks.load() if not ranger.arg.clean and self.tags is None: self.tags = Tags(self.confpath('tagged')) self.ui.setup_curses() self.ui.initialize() self.rifle.hook_before_executing = lambda a, b, flags: \ self.ui.suspend() if 'f' not in flags else None self.rifle.hook_after_executing = lambda a, b, flags: \ self.ui.initialize() if 'f' not in flags else None self.rifle.hook_logger = self.notify # This hook allows image viewers to open all images in the current # directory, keeping the order of files the same as in ranger. # The requirements to use it are: # 1. set open_all_images to true # 2. ensure no files are marked # 3. call rifle with a command that starts with "sxiv " or "feh " def sxiv_workaround_hook(command): import re from ranger.ext.shell_escape import shell_quote if self.settings.open_all_images and \ len(self.thisdir.marked_items) == 0 and \ re.match(r'^(feh|sxiv) ', command): images = [f.basename for f in self.thisdir.files if f.image] escaped_filenames = " ".join(shell_quote(f) \ for f in images if "\x00" not in f) if images and self.thisfile.basename in images and \ "$@" in command: new_command = None if command[0:5] == 'sxiv ': number = images.index(self.thisfile.basename) + 1 new_command = command.replace("sxiv ", "sxiv -n %d " % number, 1) if command[0:4] == 'feh ': new_command = command.replace("feh ", "feh --start-at %s " % \ shell_quote(self.thisfile.basename), 1) if new_command: command = "set -- %s; %s" % (escaped_filenames, new_command) return command self.rifle.hook_command_preprocessing = sxiv_workaround_hook def mylogfunc(text): self.notify(text, bad=True) self.run = Runner(ui=self.ui, logfunc=mylogfunc, fm=self)
class FM(Actions, SignalDispatcher): input_blocked = False input_blocked_until = 0 mode = 'normal' # either 'normal' or 'visual'. search_method = 'ctime' _previous_selection = None _visual_reverse = False _visual_start = None _visual_start_pos = None def __init__(self, ui=None, bookmarks=None, tags=None, paths=['.']): """Initialize FM.""" Actions.__init__(self) SignalDispatcher.__init__(self) if ui is None: self.ui = UI() else: self.ui = ui self.start_paths = paths self.directories = dict() self.log = deque(maxlen=20) self.image_displayer = ImageDisplayer(self) self.bookmarks = bookmarks self.current_tab = 1 self.tabs = {} self.tags = tags self.restorable_tabs = deque([], ranger.MAX_RESTORABLE_TABS) self.py3 = sys.version_info >= (3, ) self.previews = {} self.loader = Loader() self.copy_buffer = set() self.do_cut = False self.control_server = RangerControlServer(self) try: self.username = pwd.getpwuid(os.geteuid()).pw_name except: self.username = '******' + str(os.geteuid()) self.hostname = socket.gethostname() self.home_path = os.path.expanduser('~') self.log.append('ranger {0} started! Process ID is {1}.' \ .format(__version__, os.getpid())) self.log.append('Running on Python ' + sys.version.replace('\n', '')) mimetypes.knownfiles.append(os.path.expanduser('~/.mime.types')) mimetypes.knownfiles.append(self.relpath('data/mime.types')) self.mimetypes = mimetypes.MimeTypes() def initialize(self): """If ui/bookmarks are None, they will be initialized here.""" self.tabs = dict( (n + 1, Tab(path)) for n, path in enumerate(self.start_paths)) tab_list = self._get_tab_list() if tab_list: self.current_tab = tab_list[0] self.thistab = self.tabs[self.current_tab] else: self.current_tab = 1 self.tabs[self.current_tab] = self.thistab = Tab('.') if not ranger.arg.clean and os.path.isfile( self.confpath('rifle.conf')): rifleconf = self.confpath('rifle.conf') else: rifleconf = self.relpath('config/rifle.conf') self.rifle = Rifle(rifleconf) self.rifle.reload_config() if self.bookmarks is None: if ranger.arg.clean: bookmarkfile = None else: bookmarkfile = self.confpath('bookmarks') self.bookmarks = Bookmarks( bookmarkfile=bookmarkfile, bookmarktype=Directory, autosave=self.settings.autosave_bookmarks) self.bookmarks.load() if not ranger.arg.clean and self.tags is None: self.tags = Tags(self.confpath('tagged')) self.ui.setup_curses() self.ui.initialize() self.rifle.hook_before_executing = lambda a, b, flags: \ self.ui.suspend() if 'f' not in flags else None self.rifle.hook_after_executing = lambda a, b, flags: \ self.ui.initialize() if 'f' not in flags else None self.rifle.hook_logger = self.notify # This hook allows image viewers to open all images in the current # directory, keeping the order of files the same as in ranger. # The requirements to use it are: # 1. set open_all_images to true # 2. ensure no files are marked # 3. call rifle with a command that starts with "sxiv " or "feh " def sxiv_workaround_hook(command): import re from ranger.ext.shell_escape import shell_quote if self.settings.open_all_images and \ len(self.thisdir.marked_items) == 0 and \ re.match(r'^(feh|sxiv) ', command): images = [f.basename for f in self.thisdir.files if f.image] escaped_filenames = " ".join(shell_quote(f) \ for f in images if "\x00" not in f) if images and self.thisfile.basename in images and \ "$@" in command: new_command = None if command[0:5] == 'sxiv ': number = images.index(self.thisfile.basename) + 1 new_command = command.replace("sxiv ", "sxiv -n %d " % number, 1) if command[0:4] == 'feh ': new_command = command.replace("feh ", "feh --start-at %s " % \ shell_quote(self.thisfile.basename), 1) if new_command: command = "set -- %s; %s" % (escaped_filenames, new_command) return command self.rifle.hook_command_preprocessing = sxiv_workaround_hook def mylogfunc(text): self.notify(text, bad=True) self.run = Runner(ui=self.ui, logfunc=mylogfunc, fm=self) def destroy(self): debug = ranger.arg.debug if self.ui: try: self.ui.destroy() except: if debug: raise if self.loader: try: self.loader.destroy() except: if debug: raise def _get_thisfile(self): return self.thistab.thisfile def _set_thisfile(self, obj): self.thistab.thisfile = obj def _get_thisdir(self): return self.thistab.thisdir def _set_thisdir(self, obj): self.thistab.thisdir = obj thisfile = property(_get_thisfile, _set_thisfile) thisdir = property(_get_thisdir, _set_thisdir) def block_input(self, sec=0): self.input_blocked = sec != 0 self.input_blocked_until = time() + sec def input_is_blocked(self): if self.input_blocked and time() > self.input_blocked_until: self.input_blocked = False return self.input_blocked def copy_config_files(self, which): if ranger.arg.clean: sys.stderr.write("refusing to copy config files in clean mode\n") return import shutil from errno import EEXIST def copy(_from, to): if os.path.exists(self.confpath(to)): sys.stderr.write("already exists: %s\n" % self.confpath(to)) else: sys.stderr.write("creating: %s\n" % self.confpath(to)) try: os.makedirs(ranger.arg.confdir) except OSError as err: if err.errno != EEXIST: # EEXIST means it already exists print( "This configuration directory could not be created:" ) print(ranger.arg.confdir) print( "To run ranger without the need for configuration") print("files, use the --clean option.") raise SystemExit() try: shutil.copy(self.relpath(_from), self.confpath(to)) except Exception as e: sys.stderr.write(" ERROR: %s\n" % str(e)) if which == 'rifle' or which == 'all': copy('config/rifle.conf', 'rifle.conf') if which == 'commands' or which == 'all': copy('config/commands.py', 'commands.py') if which == 'rc' or which == 'all': copy('config/rc.conf', 'rc.conf') if which == 'scope' or which == 'all': copy('data/scope.sh', 'scope.sh') os.chmod(self.confpath('scope.sh'), os.stat(self.confpath('scope.sh')).st_mode | stat.S_IXUSR) if which in ('all', 'rifle', 'scope', 'commands', 'rc'): sys.stderr.write( "\nPlease note that configuration files may " "change as ranger evolves.\nIt's completely up to you to keep " "them up to date.\n") else: sys.stderr.write("Unknown config file `%s'\n" % which) def confpath(self, *paths): """returns the path relative to rangers configuration directory""" if ranger.arg.clean: assert 0, "Should not access relpath_conf in clean mode!" else: return os.path.join(ranger.arg.confdir, *paths) def relpath(self, *paths): """returns the path relative to rangers library directory""" return os.path.join(ranger.RANGERDIR, *paths) def get_directory(self, path): """Get the directory object at the given path""" path = os.path.abspath(path) try: return self.directories[path] except KeyError: obj = Directory(path) self.directories[path] = obj return obj def garbage_collect(self, age, tabs=None): # tabs=None is for COMPATibility """Delete unused directory objects""" for key in tuple(self.directories): value = self.directories[key] if age != -1: if not value.is_older_than(age) \ or any(value in tab.pathway for tab in self.tabs.values()): continue del self.directories[key] if value.is_directory: value.files = None self.settings.signal_garbage_collect() self.signal_garbage_collect() def loop(self): """The main loop of ranger. It consists of: 1. reloading bookmarks if outdated 2. letting the loader work 3. drawing and finalizing ui 4. reading and handling user input 5. after X loops: collecting unused directory objects """ self.enter_dir(self.thistab.path) gc_tick = 0 # for faster lookup: ui = self.ui throbber = ui.throbber loader = self.loader has_throbber = hasattr(ui, 'throbber') zombies = self.run.zombies sleeping = False ranger.api.hook_ready(self) self.control_server.start() try: while True: loader.work() if has_throbber: if loader.has_work(): throbber(loader.status) else: throbber(remove=True) if not sleeping: ui.redraw() else: ui.sleep_update() ui.set_load_mode(not loader.paused and loader.has_work()) ui.draw_images() had_input = ui.handle_input() had_command = self.control_server.act_on_messages() sleeping = not (had_input or loader.has_work() or had_command) if zombies: for zombie in tuple(zombies): if zombie.poll() is not None: zombies.remove(zombie) #gc_tick += 1 #if gc_tick > ranger.TICKS_BEFORE_COLLECTING_GARBAGE: #gc_tick = 0 #self.garbage_collect(ranger.TIME_BEFORE_FILE_BECOMES_GARBAGE) except KeyboardInterrupt: # this only happens in --debug mode. By default, interrupts # are caught in curses_interrupt_handler raise SystemExit finally: self.image_displayer.quit() self.control_server.stop() if ranger.arg.choosedir and self.thisdir and self.thisdir.path: # XXX: UnicodeEncodeError: 'utf-8' codec can't encode character # '\udcf6' in position 42: surrogates not allowed open(ranger.arg.choosedir, 'w').write(self.thisdir.path) self.bookmarks.remember(self.thisdir) self.bookmarks.save()
def main( # pylint: disable=too-many-locals,too-many-return-statements # pylint: disable=too-many-branches,too-many-statements ): """initialize objects and run the filemanager""" import ranger.api from ranger.container.settings import Settings from ranger.core.shared import FileManagerAware, SettingsAware from ranger.core.fm import FM from ranger.ext.logutils import setup_logging from ranger.ext.openstruct import OpenStruct ranger.args = args = parse_arguments() ranger.arg = OpenStruct(args.__dict__) # COMPAT setup_logging(debug=args.debug, logfile=args.logfile) for line in VERSION_MSG: LOG.info(line) LOG.info('Process ID: %s', os.getpid()) try: locale.setlocale(locale.LC_ALL, '') except locale.Error: print("Warning: Unable to set locale. Expect encoding problems.") # so that programs can know that ranger spawned them: level = 'RANGER_LEVEL' if level in os.environ and os.environ[level].isdigit(): os.environ[level] = str(int(os.environ[level]) + 1) else: os.environ[level] = '1' if 'SHELL' not in os.environ: os.environ['SHELL'] = 'sh' LOG.debug("cache dir: '%s'", args.cachedir) LOG.debug("config dir: '%s'", args.confdir) LOG.debug("data dir: '%s'", args.datadir) if args.copy_config is not None: fm = FM() fm.copy_config_files(args.copy_config) return 0 if args.list_tagged_files: if args.clean: print("Can't access tag data in clean mode", file=sys.stderr) return 1 fm = FM() try: if sys.version_info[0] >= 3: fobj = open(fm.datapath('tagged'), 'r', errors='replace') else: fobj = open(fm.datapath('tagged'), 'r') except OSError as ex: print('Unable to open `tagged` data file: {0}'.format(ex), file=sys.stderr) return 1 for line in fobj.readlines(): if len(line) > 2 and line[1] == ':': if line[0] in args.list_tagged_files: sys.stdout.write(line[2:]) elif line and '*' in args.list_tagged_files: sys.stdout.write(line) return 0 SettingsAware.settings_set(Settings()) # TODO: deprecate --selectfile if args.selectfile: args.selectfile = os.path.abspath(args.selectfile) args.paths.insert(0, os.path.dirname(args.selectfile)) paths = get_paths(args) paths_inaccessible = [] paths_are_files = [] paths_abs = [] for path in paths: try: path_abs = os.path.abspath(path) except OSError: paths_inaccessible += [path] continue if os.path.isfile(path_abs): paths_are_files.append(path_abs) if not os.access(path_abs, os.F_OK): paths_inaccessible += [path] else: paths_abs.append(path) if paths_inaccessible: print('Inaccessible paths: {0}'.format(', '.join(paths_inaccessible)), file=sys.stderr) return 1 if paths_are_files: from ranger.ext.rifle import Rifle fm = FM() if not args.clean and os.path.isfile(fm.confpath('rifle.conf')): rifleconf = fm.confpath('rifle.conf') else: rifleconf = fm.relpath('config/rifle.conf') rifle = Rifle(rifleconf) rifle.reload_config() rifle.execute(paths_abs) return 0 profile = None exit_msg = '' exit_code = 0 try: # pylint: disable=too-many-nested-blocks # Initialize objects fm = FM(paths=paths) FileManagerAware.fm_set(fm) load_settings(fm, args.clean) if args.show_only_dirs: from ranger.container.directory import InodeFilterConstants fm.settings.global_inode_type_filter = InodeFilterConstants.DIRS if args.list_unused_keys: from ranger.ext.keybinding_parser import (special_keys, reversed_special_keys) maps = fm.ui.keymaps['browser'] for key in sorted(special_keys.values(), key=str): if key not in maps: print("<%s>" % reversed_special_keys[key]) for key in range(33, 127): if key not in maps: print(chr(key)) return 0 if not sys.stdin.isatty(): sys.stderr.write("Error: Must run ranger from terminal\n") raise SystemExit(1) if fm.username == 'root': fm.settings.preview_files = False fm.settings.use_preview_script = False LOG.info("Running as root, disabling the file previews.") if not args.debug: from ranger.ext import curses_interrupt_handler curses_interrupt_handler.install_interrupt_handler() if not args.clean: # Create data directory if not os.path.exists(args.datadir): os.makedirs(args.datadir) # Restore saved tabs tabs_datapath = fm.datapath('tabs') if fm.settings.save_tabs_on_exit and os.path.exists(tabs_datapath) and not args.paths: try: with open(tabs_datapath, 'r') as fobj: tabs_saved = fobj.read().partition('\0\0') fm.start_paths += tabs_saved[0].split('\0') if tabs_saved[-1]: with open(tabs_datapath, 'w') as fobj: fobj.write(tabs_saved[-1]) else: os.remove(tabs_datapath) except OSError as ex: LOG.error('Unable to restore saved tabs') LOG.exception(ex) # Run the file manager fm.initialize() ranger.api.hook_init(fm) fm.ui.initialize() if args.selectfile: fm.select_file(args.selectfile) if args.cmd: fm.enter_dir(fm.thistab.path) for command in args.cmd: fm.execute_console(command) if ranger.args.profile: import cProfile import pstats ranger.__fm = fm # pylint: disable=protected-access profile_file = tempfile.gettempdir() + '/ranger_profile' cProfile.run('ranger.__fm.loop()', profile_file) profile = pstats.Stats(profile_file, stream=sys.stderr) else: fm.loop() except Exception: # pylint: disable=broad-except import traceback ex_traceback = traceback.format_exc() exit_msg += '\n'.join(VERSION_MSG) + '\n' try: exit_msg += "Current file: {0}\n".format(repr(fm.thisfile.path)) except Exception: # pylint: disable=broad-except pass exit_msg += ''' {0} ranger crashed. Please report this traceback at: https://github.com/ranger/ranger/issues '''.format(ex_traceback) exit_code = 1 except SystemExit as ex: if ex.code is not None: if not isinstance(ex.code, int): exit_msg = ex.code exit_code = 1 else: exit_code = ex.code finally: if exit_msg: LOG.critical(exit_msg) try: fm.ui.destroy() except (AttributeError, NameError): pass # If profiler is enabled print the stats if ranger.args.profile and profile: profile.strip_dirs().sort_stats('cumulative').print_callees() # print the exit message if any if exit_msg: sys.stderr.write(exit_msg) return exit_code # pylint: disable=lost-exception
class FM(Actions, SignalDispatcher): input_blocked = False input_blocked_until = 0 def __init__(self, ui=None, bookmarks=None, tags=None): """Initialize FM.""" Actions.__init__(self) SignalDispatcher.__init__(self) self.ui = ui self.log = deque(maxlen=20) self.bookmarks = bookmarks self.tags = tags self.tabs = {} self.py3 = sys.version_info >= (3,) self.previews = {} self.current_tab = 1 self.loader = Loader() self.log.append("ranger {0} started! Process ID is {1}.".format(__version__, os.getpid())) self.log.append("Running on Python " + sys.version.replace("\n", "")) mimetypes.knownfiles.append(os.path.expanduser("~/.mime.types")) mimetypes.knownfiles.append(self.relpath("data/mime.types")) self.mimetypes = mimetypes.MimeTypes() def initialize(self): """If ui/bookmarks are None, they will be initialized here.""" if not ranger.arg.clean and os.path.isfile(self.confpath("rifle.conf")): rifleconf = self.confpath("rifle.conf") else: rifleconf = self.relpath("defaults/rifle.conf") self.rifle = Rifle(rifleconf) self.rifle.reload_config() if self.bookmarks is None: if ranger.arg.clean: bookmarkfile = None else: bookmarkfile = self.confpath("bookmarks") self.bookmarks = Bookmarks( bookmarkfile=bookmarkfile, bookmarktype=Directory, autosave=self.settings.autosave_bookmarks ) self.bookmarks.load() if not ranger.arg.clean and self.tags is None: self.tags = Tags(self.confpath("tagged")) if self.ui is None: self.ui = UI() self.ui.initialize() self.rifle.hook_before_executing = lambda a, b, flags: self.ui.suspend() if "f" not in flags else None self.rifle.hook_after_executing = lambda a, b, flags: self.ui.initialize() if "f" not in flags else None self.rifle.hook_logger = self.notify def mylogfunc(text): self.notify(text, bad=True) self.run = Runner(ui=self.ui, logfunc=mylogfunc, fm=self) self.env.signal_bind("cd", self._update_current_tab) if self.settings.init_function: self.settings.init_function(self) def destroy(self): debug = ranger.arg.debug if self.ui: try: self.ui.destroy() except: if debug: raise if self.loader: try: self.loader.destroy() except: if debug: raise def block_input(self, sec=0): self.input_blocked = sec != 0 self.input_blocked_until = time() + sec def input_is_blocked(self): if self.input_blocked and time() > self.input_blocked_until: self.input_blocked = False return self.input_blocked def copy_config_files(self, which): if ranger.arg.clean: sys.stderr.write("refusing to copy config files in clean mode\n") return import shutil def copy(_from, to): if os.path.exists(self.confpath(to)): sys.stderr.write("already exists: %s\n" % self.confpath(to)) else: sys.stderr.write("creating: %s\n" % self.confpath(to)) try: shutil.copy(self.relpath(_from), self.confpath(to)) except Exception as e: sys.stderr.write(" ERROR: %s\n" % str(e)) if which == "rifle" or which == "all": copy("defaults/rifle.conf", "rifle.conf") if which == "commands" or which == "all": copy("defaults/commands.py", "commands.py") if which == "rc" or which == "all": copy("defaults/rc.conf", "rc.conf") if which == "options" or which == "all": copy("defaults/options.py", "options.py") if which == "scope" or which == "all": copy("data/scope.sh", "scope.sh") os.chmod(self.confpath("scope.sh"), os.stat(self.confpath("scope.sh")).st_mode | stat.S_IXUSR) if which not in ("all", "rifle", "scope", "commands", "rc", "options"): sys.stderr.write("Unknown config file `%s'\n" % which) def confpath(self, *paths): """returns the path relative to rangers configuration directory""" if ranger.arg.clean: assert 0, "Should not access relpath_conf in clean mode!" else: return os.path.join(ranger.arg.confdir, *paths) def relpath(self, *paths): """returns the path relative to rangers library directory""" return os.path.join(ranger.RANGERDIR, *paths) def loop(self): """ The main loop consists of: 1. reloading bookmarks if outdated 2. letting the loader work 3. drawing and finalizing ui 4. reading and handling user input 5. after X loops: collecting unused directory objects """ self.env.enter_dir(self.env.path) gc_tick = 0 # for faster lookup: ui = self.ui throbber = ui.throbber loader = self.loader env = self.env has_throbber = hasattr(ui, "throbber") zombies = self.run.zombies try: while True: loader.work() if has_throbber: if loader.has_work(): throbber(loader.status) else: throbber(remove=True) ui.redraw() ui.set_load_mode(not loader.paused and loader.has_work()) ui.handle_input() if zombies: for zombie in tuple(zombies): if zombie.poll() is not None: zombies.remove(zombie) gc_tick += 1 if gc_tick > ranger.TICKS_BEFORE_COLLECTING_GARBAGE: gc_tick = 0 env.garbage_collect(ranger.TIME_BEFORE_FILE_BECOMES_GARBAGE, self.tabs) except KeyboardInterrupt: # this only happens in --debug mode. By default, interrupts # are caught in curses_interrupt_handler raise SystemExit finally: if ranger.arg.choosedir and self.env.cwd and self.env.cwd.path: # XXX: UnicodeEncodeError: 'utf-8' codec can't encode character # '\udcf6' in position 42: surrogates not allowed open(ranger.arg.choosedir, "w").write(self.env.cwd.path) self.bookmarks.remember(env.cwd) self.bookmarks.save()