ARGV = sys.argv[1:sys.argv.index('--')] if '--' in sys.argv else sys.argv[1:] class Directory(  # pylint: disable=too-many-instance-attributes,too-many-public-methods
        FileSystemObject, Accumulator, Loadable):
    is_directory = True
    enterable = False
    load_generator = None
    cycle_list = None
    loading = False
    progressbar_supported = True
    flat = 0

    filenames = None
    files = None
    files_all = None
    filter = None
    temporary_filter = None
    narrow_filter = None
    inode_type_filter = None
    marked_items = None
    scroll_begin = 0

    mount_path = '/'
    disk_usage = 0

    last_update_time = -1
    load_content_mtime = -1

    order_outdated = False
    content_outdated = False
    content_loaded = False

    has_vcschild = False
    _vcs_signal_handler_installed = False

    cumulative_size_calculated = False

    sort_dict = {
        'basename': sort_by_basename,
        'natural': sort_naturally,
        'size': lambda path: -(path.size or 1),
        'mtime': lambda path: -(path.stat and path.stat.st_mtime or 1),
        'ctime': lambda path: -(path.stat and path.stat.st_ctime or 1),
        'atime': lambda path: -(path.stat and path.stat.st_atime or 1),
        'random': lambda path: random.random(),
        'type': lambda path: path.mimetype or '',
        'extension': lambda path: path.extension or '',
    }

    def __init__(self, path, **kw):
        assert not os.path.isfile(path), "No directory given!"

        Loadable.__init__(self, None, None)
        Accumulator.__init__(self)
        FileSystemObject.__init__(self, path, **kw)

        self.marked_items = []

        self.filter_stack = []

        self._signal_functions = []
        func = self.signal_function_factory(self.sort)
        self._signal_functions += [func]
        for opt in ('sort_directories_first', 'sort', 'sort_reverse', 'sort_case_insensitive'):
            self.settings.signal_bind('setopt.' + opt, func, weak=True, autosort=False)
        func = self.signal_function_factory(self.refilter)
        self._signal_functions += [func]
        for opt in ('hidden_filter', 'show_hidden'):
            self.settings.signal_bind('setopt.' + opt, func, weak=True, autosort=False)

        self.settings = LocalSettings(path, self.settings)

        self.use()

    @lazy_property
    def vcs(self):
        if not self._vcs_signal_handler_installed:
            self.settings.signal_bind(
                'setopt.vcs_aware', self.vcs__reset,  # pylint: disable=no-member
                weak=True, autosort=False,
            )
            self._vcs_signal_handler_installed = True
        if self.settings.vcs_aware:
            return Vcs(self)
        return None

    def signal_function_factory(self, function):
        def signal_function():
            self.load_if_outdated()
            if not self.exists:
                return
            function()
        return signal_function

    def request_resort(self):
        self.order_outdated = True

    def request_reload(self):
        self.content_outdated = True

    def get_list(self):
        return self.files

    def mark_item(self, item, val):
        item.mark_set(val)
        if val:
            if item in self.files and item not in self.marked_items:
                self.marked_items.append(item)
        else:
            while True:
                try:
                    self.marked_items.remove(item)
                except ValueError:
                    break

    def toggle_mark(self, item):
        self.mark_item(item, not item.marked)

    def toggle_all_marks(self):
        for item in self.files:
            self.toggle_mark(item)

    def mark_all(self, val):
        for item in self.files:
            self.mark_item(item, val)

        if not val:
            del self.marked_items[:]
            self._clear_marked_items()

    # XXX: Is it really necessary to have the marked items in a list?
    # Can't we just recalculate them with [f for f in self.files if f.marked]?
    def _gc_marked_items(self):
        for item in list(self.marked_items):
            if item.path not in self.filenames:
                self.marked_items.remove(item)

    def _clear_marked_items(self):
        for item in self.marked_items:
            item.mark_set(False)
        del self.marked_items[:]

    def get_selection(self):
        """READ ONLY"""
        self._gc_marked_items()
        if not self.files:
            return []

        if self.marked_items:
            return [item for item in self.files if item.marked]
        elif self.pointed_obj:
            return [self.pointed_obj]

        return []

    def refilter(self):
        if self.files_all is None:
            return  # probably not loaded yet

        self.last_update_time = time()

        filters = []

        if not self.settings.show_hidden and self.settings.hidden_filter:
            hidden_filter = re.compile(self.settings.hidden_filter)
            hidden_filter_search = hidden_filter.search

            def hidden_filter_func(fobj):
                for comp in fobj.relative_path.split(os.path.sep):
                    if hidden_filter_search(comp):
                        return False
                return True
            filters.append(hidden_filter_func)
        if self.narrow_filter:
            # pylint: disable=unsupported-membership-test

            # Pylint complains that self.narrow_filter is by default
            # None but the execution won't reach this line if it is
            # still None.
            filters.append(lambda fobj: fobj.basename in self.narrow_filter)
        if self.settings.global_inode_type_filter or self.inode_type_filter:
            def inode_filter_func(obj):
                # Use local inode_type_filter if present, global otherwise
                inode_filter = self.inode_type_filter or self.settings.global_inode_type_filter
                # Apply filter
                if InodeFilterConstants.DIRS in inode_filter and \
                        obj.is_directory:
                    return True
                elif InodeFilterConstants.FILES in inode_filter and \
                        obj.is_file and not obj.is_link:
                    return True
                elif InodeFilterConstants.LINKS in inode_filter and \
                        obj.is_link:
                    return True
                return False
            filters.append(inode_filter_func)
        if self.filter:
            filter_search = self.filter.search
            filters.append(lambda fobj: filter_search(fobj.basename))
        if self.temporary_filter:
            temporary_filter_search = self.temporary_filter.search
            filters.append(lambda fobj: temporary_filter_search(fobj.basename))
        filters.extend(self.filter_stack)

        self.files = [f for f in self.files_all if accept_file(f, filters)]

        # A fix for corner cases when the user invokes show_hidden on a
        # directory that contains only hidden directories and hidden files.
        if self.files and not self.pointed_obj:
            self.pointed_obj = self.files[0]
        elif not self.files:
            self.content_loaded = False
            self.pointed_obj = None

        self.move_to_obj(self.pointed_obj)

    # XXX: Check for possible race conditions
    # pylint: disable=too-many-locals,too-many-branches,too-many-statements
    def load_bit_by_bit(self):
        """An iterator that loads a part on every next() call

        Returns a generator which load a part of the directory
        in each iteration.
        """

        self.loading = True
        self.percent = 0
        self.load_if_outdated()

        basename_is_rel_to = self.path if self.flat else None

        try:  # pylint: disable=too-many-nested-blocks
            if self.runnable:
                yield
                mypath = self.path

                self.mount_path = mount_path(mypath)

                if self.flat:
                    filelist = []
                    for dirpath, dirnames, filenames in walklevel(mypath, self.flat):
                        dirlist = [
                            os.path.join("/", dirpath, d)
                            for d in dirnames
                            if self.flat == -1
                            or (dirpath.count(os.path.sep)
                                - mypath.count(os.path.sep)) <= self.flat
                        ]
                        filelist += dirlist
                        filelist += [os.path.join("/", dirpath, f) for f in filenames]
                    filenames = filelist
                    self.load_content_mtime = mtimelevel(mypath, self.flat)
                else:
                    filelist = os.listdir(mypath)
                    filenames = [mypath + (mypath == '/' and fname or '/' + fname)
                                 for fname in filelist]
                    self.load_content_mtime = os.stat(mypath).st_mtime

                if self.cumulative_size_calculated:
                    # If self.content_loaded is true, this is not the first
                    # time loading.  So I can't really be sure if the
                    # size has changed and I'll add a "?".
                    if self.content_loaded:
                        if self.fm.settings.autoupdate_cumulative_size:
                            self.look_up_cumulative_size()
                        else:
                            self.infostring = ' %s' % human_readable(
                                self.size, separator='? ')
                    else:
                        self.infostring = ' %s' % human_readable(self.size)
                else:
                    self.size = len(filelist)
                    self.infostring = ' %d' % self.size
                if self.is_link:
                    self.infostring = '->' + self.infostring

                yield

                marked_paths = [obj.path for obj in self.marked_items]

                files = []
                disk_usage = 0

                has_vcschild = False
                for name in filenames:
                    try:
                        file_lstat = os_lstat(name)
                        if file_lstat.st_mode & 0o170000 == 0o120000:
                            file_stat = os_stat(name)
                        else:
                            file_stat = file_lstat
                    except OSError:
                        file_lstat = None
                        file_stat = None
                    if file_lstat and file_stat:
                        stats = (file_stat, file_lstat)
                        is_a_dir = file_stat.st_mode & 0o170000 == 0o040000
                    else:
                        stats = None
                        is_a_dir = False

                    if is_a_dir:
                        item = self.fm.get_directory(name, preload=stats, path_is_abs=True,
                                                     basename_is_rel_to=basename_is_rel_to)
                        item.load_if_outdated()
                        if self.flat:
                            item.relative_path = os.path.relpath(item.path, self.path)
                        else:
                            item.relative_path = item.basename
                        item.relative_path_lower = item.relative_path.lower()
                        if item.vcs and item.vcs.track:
                            if item.vcs.is_root_pointer:
                                has_vcschild = True
                            else:
                                item.vcsstatus = \
                                    item.vcs.rootvcs.status_subpath(  # pylint: disable=no-member
                                        os.path.join(self.realpath, item.basename),
                                        is_directory=True,
                                    )
                    else:
                        item = File(name, preload=stats, path_is_abs=True,
                                    basename_is_rel_to=basename_is_rel_to)
                        item.load()
                        disk_usage += item.size
                        if self.vcs and self.vcs.track:
                            item.vcsstatus = \
                                self.vcs.rootvcs.status_subpath(  # pylint: disable=no-member
                                    os.path.join(self.realpath, item.basename))

                    files.append(item)
                    self.percent = 100 * len(files) // len(filenames)
                    yield
                self.has_vcschild = has_vcschild
                self.disk_usage = disk_usage

                self.filenames = filenames
                self.files_all = files

                self._clear_marked_items()
                for item in self.files_all:
                    if item.path in marked_paths:
                        item.mark_set(True)
                        self.marked_items.append(item)
                    else:
                        item.mark_set(False)

                self.sort()

                if files:
                    if self.pointed_obj is not None:
                        self.sync_index()
                    else:
                        self.move(to=0)
            else:
                self.filenames = None
                self.files_all = None
                self.files = None

            self.cycle_list = None
            self.content_loaded = True
            self.last_update_time = time()
            self.correct_pointer()

        finally:
            self.loading = False
            self.fm.signal_emit("finished_loading_dir", directory=self)
            if self.vcs:
                self.fm.ui.vcsthread.process(self)
    # pylint: enable=too-many-locals,too-many-branches,too-many-statements

    def unload(self):
        self.loading = False
        self.load_generator = None

    def load_content(self, schedule=None):
        """Loads the contents of the directory.

        Use this sparingly since it takes rather long.
        """
        self.content_outdated = False
        if self.settings.freeze_files:
            return

        if not self.loading:
            if not self.loaded:
                self.load()

            if not self.accessible:
                self.content_loaded = True
                return

            if schedule is None:
                schedule = True   # was: self.size > 30

            if self.load_generator is None:
                self.load_generator = self.load_bit_by_bit()

                if schedule and self.fm:
                    self.fm.loader.add(self)
                else:
                    for _ in self.load_generator:
                        pass
                    self.load_generator = None

            elif not schedule or not self.fm:
                for _ in self.load_generator:
                    pass
                self.load_generator = None

    def sort(self):
        """Sort the contained files"""
        # pylint: disable=comparison-with-callable
        if self.files_all is None:
            return

        try:
            sort_func = self.sort_dict[self.settings.sort]
        except KeyError:
            sort_func = sort_by_basename

        if self.settings.sort_case_insensitive and \
                sort_func == sort_by_basename:
            sort_func = sort_by_basename_icase

        if self.settings.sort_case_insensitive and \
                sort_func == sort_naturally:
            sort_func = sort_naturally_icase

        # XXX Does not work with usermade sorting functions :S
        if self.settings.sort_unicode:
            if sort_func in (sort_naturally, sort_naturally_icase):
                sort_func = sort_unicode_wrapper_list(sort_func)
            elif sort_func in (sort_by_basename, sort_by_basename_icase):
                sort_func = sort_unicode_wrapper_string(sort_func)

        self.files_all.sort(key=sort_func)

        if self.settings.sort_reverse:
            self.files_all.reverse()

        if self.settings.sort_directories_first:
            self.files_all.sort(key=sort_by_directory)

        self.refilter()

    def _get_cumulative_size(self):
        if self.size == 0:
            return 0
        cum = 0
        realpath = os.path.realpath
        for dirpath, _, filenames in os.walk(self.path, onerror=lambda _: None):
            for fname in filenames:
                try:
                    if dirpath == self.path:
                        stat = os_stat(realpath(dirpath + "/" + fname))
                    else:
                        stat = os_stat(dirpath + "/" + fname)
                except OSError:
                    continue
                cum += stat.st_size
        return cum

    def look_up_cumulative_size(self):
        self.cumulative_size_calculated = True
        self.size = self._get_cumulative_size()
        self.infostring = ('-> ' if self.is_link else ' ') + human_readable(self.size)

    @lazy_property
    def size(self):  # pylint: disable=method-hidden
        try:
            if self.fm.settings.automatically_count_files:
                size = len(os.listdir(self.path))
            else:
                size = None
        except OSError:
            self.infostring = BAD_INFO
            self.accessible = False
            self.runnable = False
            return 0
        else:
            if size is None:
                self.infostring = ''
            else:
                self.infostring = ' %d' % size
            self.accessible = True
            self.runnable = True
            return size

    @lazy_property
    def infostring(self):  # pylint: disable=method-hidden
        self.size  # trigger the lazy property initializer pylint: disable=pointless-statement
        if self.is_link:
            return '->' + self.infostring
        return self.infostring

    @lazy_property
    def runnable(self):  # pylint: disable=method-hidden
        self.size  # trigger the lazy property initializer pylint: disable=pointless-statement
        return self.runnable

    def sort_if_outdated(self):
        """Sort the containing files if they are outdated"""
        if self.order_outdated:
            self.order_outdated = False
            self.sort()
            return True
        return False

    def move_to_obj(self, arg, attr=None):
        try:
            arg = arg.path
        except AttributeError:
            pass
        self.load_content_once(schedule=False)
        if self.empty():
            return

        Accumulator.move_to_obj(self, arg, attr='path')

    def search_fnc(self, fnc, offset=1, forward=True):
        length = len(self)
        if forward:
            generator = ((self.pointer + (x + offset)) % length
                         for x in range(length - 1))
        else:
            generator = ((self.pointer - (x + offset)) % length
                         for x in range(length - 1))

        for i in generator:
            _file = self.files[i]
            if fnc(_file):
                self.pointer = i
                self.pointed_obj = _file
                self.correct_pointer()
                return True
        return False

    def set_cycle_list(self, lst):
        self.cycle_list = deque(lst)

    def cycle(self, forward=True):
        if self.cycle_list:
            if forward is True:
                self.cycle_list.rotate(-1)
            elif forward is False:
                self.cycle_list.rotate(1)

            self.move_to_obj(self.cycle_list[0])

    def correct_pointer(self):
        """Make sure the pointer is in the valid range"""
        Accumulator.correct_pointer(self)

        if self == self.fm.thisdir:
            try:
                self.fm.thisfile = self.pointed_obj
            except AttributeError:
                pass

    def load_content_once(self, *a, **k):
        """Load the contents of the directory if not done yet"""
        if not self.content_loaded:
            self.load_content(*a, **k)
            return True
        return False

    def load_content_if_outdated(self, *a, **k):
        """Load the contents of the directory if outdated"""

        if self.load_content_once(*a, **k):
            return True

        if self.files_all is None or self.content_outdated:
            self.load_content(*a, **k)
            return True

        try:
            if self.flat:
                real_mtime = mtimelevel(self.path, self.flat)
            else:
                real_mtime = os.stat(self.path).st_mtime
        except OSError:
            real_mtime = None
            return False
        if self.stat:
            cached_mtime = self.load_content_mtime
        else:
            cached_mtime = 0

        if real_mtime != cached_mtime:
            self.load_content(*a, **k)
            return True
        return False

    def get_description(self):
        return "Loading " + str(self)

    def use(self):
        """mark the filesystem-object as used at the current time"""
        self.last_used = time()

    def is_older_than(self, seconds):
        """returns whether this object wasn't use()d in the last n seconds"""
        if seconds < 0:
            return True
        return self.last_used + seconds < time()

    def go(self, history=True):  # pylint: disable=invalid-name
        """enter the directory if the filemanager is running"""
        if self.fm:
            return self.fm.enter_dir(self.path, history=history)
        return False

    def empty(self):
        """Is the directory empty?"""
        return not self.files

    def set_linemode_of_children(self, mode):
        for fobj in self.files:
            fobj.set_linemode(mode)

    def __nonzero__(self):
        """Always True"""
        return True
    __bool__ = __nonzero__

    def __len__(self):
        """The number of containing files"""
        assert self.accessible
        assert self.content_loaded
        assert self.files is not None
        return len(self.files)

    def __eq__(self, other):
        """Check for equality of the directories paths"""
        return isinstance(other, Directory) and self.path == other.path

    def __neq__(self, other):
        """Check for inequality of the directories paths"""
        return not self.__eq__(other)

    def __hash__(self):
        return hash(self.path) class File(FileSystemObject):
    is_file = True
    preview_data = None
    preview_known = False
    preview_loading = False
    _firstbytes = None

    @property
    def firstbytes(self):
        if self._firstbytes is not None:
            return self._firstbytes
        try:
            with open(self.path, 'rb') as fobj:
                self._firstbytes = set(fobj.read(N_FIRST_BYTES))
        # IOError for Python2, OSError for Python3
        except (IOError, OSError):
            return None
        return self._firstbytes

    def is_binary(self):
        if self.firstbytes and CONTROL_CHARACTERS & self.firstbytes:
            return True
        return False

    def has_preview(self):  # pylint: disable=too-many-return-statements
        if not self.fm.settings.preview_files:
            return False
        if self.is_socket or self.is_fifo or self.is_device:
            return False
        if not self.accessible:
            return False
        if self.fm.settings.preview_max_size and \
                self.size > self.fm.settings.preview_max_size:
            return False
        if self.fm.settings.preview_script and \
                self.fm.settings.use_preview_script:
            return True
        if self.container:
            return False
        if PREVIEW_WHITELIST.search(self.basename):
            return True
        if PREVIEW_BLACKLIST.search(self.basename):
            return False
        if self.path == '/dev/core' or self.path == '/proc/kcore':
            return False
        if self.is_binary():
            return False
        return True

    def get_preview_source(self, width, height):
        return self.fm.get_preview(self, width, height)

    def is_image_preview(self):
        try:
            return self.fm.previews[self.realpath]['imagepreview']
        except KeyError:
            return False

    def __eq__(self, other):
        return isinstance(other, File) and self.path == other.path

    def __neq__(self, other):
        return not self.__eq__(other)

    def __hash__(self):
        return hash(self.path) ALLOWED_SETTINGS = {
    'automatically_count_files': bool,
    'autosave_bookmarks': bool,
    'autoupdate_cumulative_size': bool,
    'bidi_support': bool,
    'cd_bookmarks': bool,
    'cd_tab_case': str,
    'cd_tab_fuzzy': bool,
    'clear_filters_on_dir_change': bool,
    'collapse_preview': bool,
    'colorscheme': str,
    'column_ratios': (tuple, list),
    'confirm_on_delete': str,
    'dirname_in_tabs': bool,
    'display_size_in_main_column': bool,
    'display_size_in_status_bar': bool,
    "display_free_space_in_status_bar": bool,
    'display_tags_in_all_columns': bool,
    'draw_borders': str,
    'draw_borders_multipane': str,
    'draw_progress_bar_in_status_bar': bool,
    'flushinput': bool,
    'freeze_files': bool,
    'global_inode_type_filter': str,
    'hidden_filter': str,
    'hint_collapse_threshold': int,
    'hostname_in_titlebar': bool,
    'size_in_bytes': bool,
    'idle_delay': int,
    'iterm2_font_width': int,
    'iterm2_font_height': int,
    'line_numbers': str,
    'max_console_history_size': (int, type(None)),
    'max_history_size': (int, type(None)),
    'metadata_deep_search': bool,
    'mouse_enabled': bool,
    'nested_ranger_warning': str,
    'one_indexed': bool,
    'open_all_images': bool,
    'padding_right': bool,
    'preview_directories': bool,
    'preview_files': bool,
    'preview_images': bool,
    'preview_images_method': str,
    'preview_max_size': int,
    'preview_script': (str, type(None)),
    'relative_current_zero': bool,
    'save_backtick_bookmark': bool,
    'save_console_history': bool,
    'save_tabs_on_exit': bool,
    'scroll_offset': int,
    'shorten_title': int,
    'show_cursor': bool,  # TODO: not working?
    'show_hidden_bookmarks': bool,
    'show_hidden': bool,
    'show_selection_in_titlebar': bool,
    'sort_case_insensitive': bool,
    'sort_directories_first': bool,
    'sort_reverse': bool,
    'sort': str,
    'sort_unicode': bool,
    'status_bar_on_top': bool,
    'tilde_in_titlebar': bool,
    'unicode_ellipsis': bool,
    'update_title': bool,
    'update_tmux_title': bool,
    'use_preview_script': bool,
    'vcs_aware': bool,
    'vcs_backend_bzr': str,
    'vcs_backend_git': str,
    'vcs_backend_hg': str,
    'vcs_backend_svn': str,
    'vcs_msg_length': int,
    'viewmode': str,
    'w3m_delay': float,
    'w3m_offset': int,
    'wrap_plaintext_previews': bool,
    'wrap_scroll': bool,
    'xterm_alt_key': bool,
} ALLOWED_VALUES = {
    'cd_tab_case': ['sensitive', 'insensitive', 'smart'],
    'confirm_on_delete': ['multiple', 'always', 'never'],
    'draw_borders': ['none', 'both', 'outline', 'separators'],
    'draw_borders_multipane': [None, 'none', 'both', 'outline',
                               'separators', 'active-pane'],
    'line_numbers': ['false', 'absolute', 'relative'],
    'nested_ranger_warning': ['true', 'false', 'error'],
    'one_indexed': [False, True],
    'preview_images_method': ['w3m', 'iterm2', 'terminology',
                              'urxvt', 'urxvt-full', 'kitty',
                              'ueberzug'],
    'vcs_backend_bzr': ['disabled', 'local', 'enabled'],
    'vcs_backend_git': ['enabled', 'disabled', 'local'],
    'vcs_backend_hg': ['disabled', 'local', 'enabled'],
    'vcs_backend_svn': ['disabled', 'local', 'enabled'],
    'viewmode': ['miller', 'multipane'],
} class CommandLoader(  # pylint: disable=too-many-instance-attributes
        Loadable, SignalDispatcher, FileManagerAware):
    """Run an external command with the loader.

    Output from stderr will be reported.  Ensure that the process doesn't
    ever ask for input, otherwise the loader will be blocked until this
    object is removed from the queue (type ^C in ranger)
    """
    finished = False
    process = None

    def __init__(self, args, descr,  # pylint: disable=too-many-arguments
                 silent=False, read=False, input=None,  # pylint: disable=redefined-builtin
                 kill_on_pause=False, popenArgs=None):
        SignalDispatcher.__init__(self)
        Loadable.__init__(self, self.generate(), descr)
        self.args = args
        self.silent = silent
        self.read = read
        self.stdout_buffer = ""
        self.input = input
        self.kill_on_pause = kill_on_pause
        self.popenArgs = popenArgs  # pylint: disable=invalid-name

    def generate(self):
        # pylint: disable=too-many-branches,too-many-statements
        # TODO: Check whether we can afford to wait for processes and use a
        #       with-statement for Popen.
        popenargs = {} if self.popenArgs is None else self.popenArgs
        popenargs['stdout'] = popenargs['stderr'] = PIPE
        popenargs['stdin'] = (
            PIPE if self.input else open(os.devnull, 'r', encoding="utf-8")
        )
        self.process = process = Popen(self.args, **popenargs)
        self.signal_emit('before', process=process, loader=self)
        if self.input:
            if PY3:
                import io
                stdin = io.TextIOWrapper(process.stdin)
            else:
                stdin = process.stdin
            try:
                stdin.write(self.input)
            except IOError as ex:
                if ex.errno != errno.EPIPE and ex.errno != errno.EINVAL:
                    raise
            stdin.close()
        if self.silent and not self.read:  # pylint: disable=too-many-nested-blocks
            while process.poll() is None:
                yield
                if self.finished:
                    break
                sleep(0.03)
        else:
            selectlist = []
            if self.read:
                selectlist.append(process.stdout)
            if not self.silent:
                selectlist.append(process.stderr)
            read_stdout = None
            while process.poll() is None:
                yield
                if self.finished:
                    break
                try:
                    robjs, _, _ = select.select(selectlist, [], [], 0.03)
                    if robjs:
                        robjs = robjs[0]
                        if robjs == process.stderr:
                            read = robjs.readline()
                            if PY3:
                                read = safe_decode(read)
                            if read:
                                self.fm.notify(read, bad=True)
                        elif robjs == process.stdout:
                            read = robjs.read(512)
                            if read:
                                if read_stdout is None:
                                    read_stdout = read
                                else:
                                    read_stdout += read
                except select.error:
                    sleep(0.03)
            if not self.silent:
                for line in process.stderr:
                    if PY3:
                        line = safe_decode(line)
                    self.fm.notify(line, bad=True)
            if self.read:
                read = process.stdout.read()
                if read:
                    read_stdout += read
            if read_stdout:
                if PY3:
                    read_stdout = safe_decode(read_stdout)
                self.stdout_buffer += read_stdout
        self.finished = True
        self.signal_emit('after', process=process, loader=self)

    def pause(self):
        if not self.finished and not self.paused:
            if self.kill_on_pause:
                self.finished = True
                try:
                    self.process.kill()
                except OSError:
                    # probably a race condition where the process finished
                    # between the last poll()ing and this point.
                    pass
                return
            try:
                self.process.send_signal(20)
            except OSError:
                pass
            Loadable.pause(self)
            self.signal_emit('pause', process=self.process, loader=self)

    def unpause(self):
        if not self.finished and self.paused:
            try:
                self.process.send_signal(18)
            except OSError:
                pass
            Loadable.unpause(self)
            self.signal_emit('unpause', process=self.process, loader=self)

    def destroy(self):
        self.signal_emit('destroy', process=self.process, loader=self)
        if self.process:
            try:
                self.process.kill()
            except OSError:
                pass class CopyLoader(Loadable, FileManagerAware):  # pylint: disable=too-many-instance-attributes
    progressbar_supported = True

    def __init__(self, copy_buffer, do_cut=False, overwrite=False, dest=None,
                 make_safe_path=get_safe_path):
        self.copy_buffer = tuple(copy_buffer)
        self.do_cut = do_cut
        self.original_copy_buffer = copy_buffer
        self.original_path = dest if dest is not None else self.fm.thistab.path
        self.overwrite = overwrite
        self.make_safe_path = make_safe_path
        self.percent = 0
        if self.copy_buffer:
            self.one_file = self.copy_buffer[0]
        Loadable.__init__(self, self.generate(), 'Calculating size...')

    def _calculate_size(self, step):
        from os.path import join
        size = 0
        stack = [fobj.path for fobj in self.copy_buffer]
        while stack:
            fname = stack.pop()
            if os.path.islink(fname):
                continue
            if os.path.isdir(fname):
                stack.extend([join(fname, item) for item in os.listdir(fname)])
            else:
                try:
                    fstat = os.stat(fname)
                except OSError:
                    continue
                size += max(step, math.ceil(fstat.st_size / step) * step)
        return size

    def generate(self):
        if not self.copy_buffer:
            return

        from ranger.ext import shutil_generatorized as shutil_g
        # TODO: Don't calculate size when renaming (needs detection)
        bytes_per_tick = shutil_g.BLOCK_SIZE
        size = max(1, self._calculate_size(bytes_per_tick))
        size_str = " (" + human_readable(self._calculate_size(1)) + ")"
        done = 0
        if self.do_cut:
            self.original_copy_buffer.clear()
            if len(self.copy_buffer) == 1:
                self.description = "moving: " + self.one_file.path + size_str
            else:
                self.description = "moving files from: " + self.one_file.dirname + size_str
            for fobj in self.copy_buffer:
                for path in self.fm.tags.tags:
                    if path == fobj.path or str(path).startswith(fobj.path):
                        tag = self.fm.tags.tags[path]
                        self.fm.tags.remove(path)
                        new_path = path.replace(
                            fobj.path,
                            os.path.join(self.original_path, fobj.basename))
                        self.fm.tags.tags[new_path] = tag
                        self.fm.tags.dump()
                n = 0
                for n in shutil_g.move(src=fobj.path, dst=self.original_path,
                                       overwrite=self.overwrite,
                                       make_safe_path=self.make_safe_path):
                    self.percent = ((done + n) / size) * 100.
                    yield
                done += n
        else:
            if len(self.copy_buffer) == 1:
                self.description = "copying: " + self.one_file.path + size_str
            else:
                self.description = "copying files from: " + self.one_file.dirname + size_str
            for fobj in self.copy_buffer:
                if os.path.isdir(fobj.path) and not os.path.islink(fobj.path):
                    n = 0
                    for n in shutil_g.copytree(
                            src=fobj.path,
                            dst=os.path.join(self.original_path, fobj.basename),
                            symlinks=True,
                            overwrite=self.overwrite,
                            make_safe_path=self.make_safe_path,
                    ):
                        self.percent = ((done + n) / size) * 100.
                        yield
                    done += n
                else:
                    n = 0
                    for n in shutil_g.copy2(fobj.path, self.original_path,
                                            symlinks=True, overwrite=self.overwrite,
                                            make_safe_path=self.make_safe_path):
                        self.percent = ((done + n) / size) * 100.
                        yield
                    done += n
        cwd = self.fm.get_directory(self.original_path)
        cwd.load_content() class Tab(FileManagerAware, SettingsAware):  # pylint: disable=too-many-instance-attributes

    def __init__(self, path):
        self.thisdir = None  # Current Working Directory
        self._thisfile = None  # Current File
        self.history = History(self.settings.max_history_size, unique=False)
        self.last_search = None
        self._pointer = 0
        self._pointed_obj = None
        self.pointed_obj = None
        self.path = abspath(expanduser(path))
        self.pathway = ()
        # NOTE: in the line below, weak=True works only in python3.  In python2,
        # weak references are not equal to the original object when tested with
        # "==", and this breaks _set_thisfile_from_signal and _on_tab_change.
        self.fm.signal_bind('move', self._set_thisfile_from_signal,
                            priority=settings.SIGNAL_PRIORITY_AFTER_SYNC,
                            weak=(PY3))
        self.fm.signal_bind('tab.change', self._on_tab_change,
                            weak=(PY3))

    def _set_thisfile_from_signal(self, signal):
        if self == signal.tab:
            self._thisfile = signal.new
            if self == self.fm.thistab:
                self.pointer = self.thisdir.pointer
                self.pointed_obj = self.thisdir.pointed_obj

    def _on_tab_change(self, signal):
        if self == signal.new and self.thisdir:
            # restore the pointer whenever this tab is reopened
            self.thisdir.pointer = self.pointer
            self.thisdir.correct_pointer()

    def _set_thisfile(self, value):
        if value is not self._thisfile:
            previous = self._thisfile
            self.fm.signal_emit('move', previous=previous, new=value, tab=self)

    def _get_thisfile(self):
        return self._thisfile

    thisfile = property(_get_thisfile, _set_thisfile)

    def _get_pointer(self):
        try:
            if self.thisdir.files[self._pointer] != self._pointed_obj:
                try:
                    self._pointer = self.thisdir.files.index(self._pointed_obj)
                except ValueError:
                    self._set_pointer(self._pointer)
        except (TypeError, IndexError):
            pass
        return self._pointer

    def _set_pointer(self, value):
        self._pointer = value
        try:
            self._pointed_obj = self.thisdir.files[self._pointer]
        except (TypeError, IndexError):
            pass

    pointer = property(_get_pointer, _set_pointer)

    def at_level(self, level):
        """Returns the FileSystemObject at the given level.

        level >0 => previews
        level 0 => current file/directory
        level <0 => parent directories
        """
        if level <= 0:
            try:
                return self.pathway[level - 1]
            except IndexError:
                return None
        else:
            directory = self.thisdir
            for _ in range(level):
                if directory is None:
                    return None
                if directory.is_directory:
                    directory = directory.pointed_obj
                else:
                    return None
            return directory

    def get_selection(self):
        if self.thisdir:
            if self.thisdir.marked_items:
                return self.thisdir.get_selection()
            elif self._thisfile:
                return [self._thisfile]
        return []

    def assign_cursor_positions_for_subdirs(self):  # pylint: disable=invalid-name
        """Assign correct cursor positions for subdirectories"""
        last_path = None
        for path in reversed(self.pathway):
            if last_path is None:
                last_path = path
                continue

            path.move_to_obj(last_path)
            last_path = path

    def ensure_correct_pointer(self):
        if self.thisdir:
            self.thisdir.correct_pointer()

    def history_go(self, relative):
        """Move relative in history"""
        if self.history:
            self.history.move(relative).go(history=False)

    def inherit_history(self, other_history):
        self.history.rebase(other_history)

    def enter_dir(self, path, history=True):
        """Enter given path"""
        # TODO: Ensure that there is always a self.thisdir
        if path is None:
            return None
        path = str(path)

        # clear filter in the folder we're leaving
        if self.fm.settings.clear_filters_on_dir_change and self.thisdir:
            self.thisdir.filter = None
            self.thisdir.refilter()

        previous = self.thisdir

        # get the absolute path
        path = normpath(join(self.path, expanduser(path)))
        selectfile = None

        if not isdir(path):
            selectfile = path
            path = dirname(path)
        new_thisdir = self.fm.get_directory(path)

        try:
            os.chdir(path)
        except OSError:
            return True
        self.path = path
        self.thisdir = new_thisdir

        self.thisdir.load_content_if_outdated()

        # build the pathway, a tuple of directory objects which lie
        # on the path to the current directory.
        if path == '/':
            self.pathway = (self.fm.get_directory('/'), )
        else:
            pathway = []
            currentpath = '/'
            for comp in path.split('/'):
                currentpath = join(currentpath, comp)
                pathway.append(self.fm.get_directory(currentpath))
            self.pathway = tuple(pathway)

        self.assign_cursor_positions_for_subdirs()

        # set the current file.
        self.thisdir.sort_directories_first = self.fm.settings.sort_directories_first
        self.thisdir.sort_reverse = self.fm.settings.sort_reverse
        self.thisdir.sort_if_outdated()
        if selectfile:
            self.thisdir.move_to_obj(selectfile)
        if previous and previous.path != path:
            self.thisfile = self.thisdir.pointed_obj
        else:
            # This avoids setting self.pointer (through the 'move' signal) and
            # is required so that you can use enter_dir when switching tabs
            # without messing up the pointer.
            self._thisfile = self.thisdir.pointed_obj

        if history:
            self.history.add(new_thisdir)

        self.fm.signal_emit('cd', previous=previous, new=self.thisdir)

        return True

    def __repr__(self):
        return "<Tab '%s'>" % self.thisdir def key_to_string(key):
    if key in range(33, 127):
        return chr(key)
    if key in reversed_special_keys:
        return "<%s>" % reversed_special_keys[key]
    return "<%s>" % str(key) def construct_keybinding(iterable):
    """Does the reverse of parse_keybinding"""
    return ''.join(key_to_string(c) for c in iterable) class MacroDict(MutableMapping):
    """Mapping that returns a fallback value when thunks error

    Macros can be used in scenarios where several attributes aren't
    initialized yet. To avoid errors in these cases we have to delay the
    evaluation of these attributes using ``lambda``s. This
    ``MutableMapping`` evaluates these thunks before returning them
    replacing them with a fallback value if necessary.

    For convenience it also catches ``TypeError`` so you can store
    non-callable values without thunking.

    >>> m = MacroDict()
    >>> o = type("", (object,), {})()
    >>> o.existing_attribute = "I exist!"

    >>> m['a'] = "plain value"
    >>> m['b'] = lambda: o.non_existent_attribute
    >>> m['c'] = lambda: o.existing_attribute

    >>> m['a']
    'plain value'
    >>> m['b']
    '<\\x01\\x01MACRO_HAS_NO_VALUE\\x01\\x01>'
    >>> m['c']
    'I exist!'
    """

    def __init__(self, *args, **kwargs):
        super(MacroDict, self).__init__()
        self.__dict__.update(*args, **kwargs)

    def __setitem__(self, key, value):
        try:
            real_val = value()
            if real_val is None:
                real_val = MACRO_FAIL
        except AttributeError:
            real_val = MACRO_FAIL
        except TypeError:
            real_val = value
        self.__dict__[key] = real_val

    def __getitem__(self, key):
        return self.__dict__[key]

    def __delitem__(self, key):
        del self.__dict__[key]

    def __iter__(self):
        return iter(self.__dict__)

    def __len__(self):
        return len(self.__dict__)

    def __str__(self):
        return str(self.__dict__) MACRO_FAIL = "<\x01\x01MACRO_HAS_NO_VALUE\x01\01>" def macro_val(thunk, fallback=MACRO_FAIL):
    try:
        return thunk()
    except AttributeError:
        return fallback def squash_flags(flags):
    """Remove lowercase flags if the respective uppercase flag exists

    >>> squash_flags('abc')
    'abc'
    >>> squash_flags('abcC')
    'ab'
    >>> squash_flags('CabcAd')
    'bd'
    """
    exclude = ''.join(f.upper() + f.lower() for f in flags if f == f.upper())
    return ''.join(f for f in flags if f not in exclude) ASK_COMMAND = 'ask'
import codecs
import os
import re
import shlex
import shutil
import string
import tempfile
import ranger
            import chardet
from hashlib import sha512
from inspect import cleandoc
from io import open
from logging import getLogger
from os import link, symlink, listdir, stat
from os.path import join, isdir, realpath, exists
from stat import S_IEXEC
from ranger import PY3
from ranger.container.directory import Directory
from ranger.container.file import File
from ranger.container.settings import ALLOWED_SETTINGS, ALLOWED_VALUES
from ranger.core.loader import CommandLoader, CopyLoader
from ranger.core.shared import FileManagerAware, SettingsAware
from ranger.core.tab import Tab
from ranger.ext.direction import Direction
from ranger.ext.get_executables import get_executables
from ranger.ext.keybinding_parser import key_to_string, construct_keybinding
from ranger.ext.macrodict import MacroDict, MACRO_FAIL, macro_val
from ranger.ext.next_available_filename import next_available_filename
from ranger.ext.relative_symlink import relative_symlink
from ranger.ext.rifle import squash_flags, ASK_COMMAND
from ranger.ext.safe_path import get_safe_path
from ranger.ext.shell_escape import shell_quote
        self.change_mode('normal')
        if narg is not None:
            n *= narg
        parent = self.thistab.at_level(-1)
        if parent is not None:
            if parent.pointer + n < 0:
                n = 0 - parent.pointer
            try:
                self.thistab.enter_dir(parent.files[parent.pointer + n])
            except IndexError:
                pass

    def select_file(self, path):
        path = path.strip()
        if self.enter_dir(os.path.dirname(path)):
            self.thisdir.move_to_obj(path)

    def history_go(self, relative):
        """Move back and forth in the history"""
        self.thistab.history_go(int(relative))

    # TODO: remove this method since it is not used?
    def scroll(self, relative):
        """Scroll down by <relative> lines"""
        if self.ui.browser and self.ui.browser.main_column:
            self.ui.browser.main_column.scroll(relative)
            self.thisfile = self.thisdir.pointed_obj

    def enter_dir(self, path, remember=False, history=True):
        """Enter the directory at the given path"""
        cwd = self.thisdir
        # csh variable is lowercase
        cdpath = os.environ.get('CDPATH', None) or os.environ.get('cdpath', None)
        result = self.thistab.enter_dir(path, history=history)
        if result is False and cdpath:
            for comp in cdpath.split(':'):
                curpath = os.path.join(comp, path)
                if os.path.isdir(curpath):
                    result = self.thistab.enter_dir(curpath, history=history)
                    break
        if cwd != self.thisdir:
            if remember:
                self.bookmarks.remember(cwd)
            self.change_mode('normal')
        return result

    def cd(self, path, remember=True):  # pylint: disable=invalid-name
        """enter the directory at the given path, remember=True"""
        self.enter_dir(path, remember=remember)

    def traverse(self):
        self.change_mode('normal')
        tfile = self.thisfile
        cwd = self.thisdir
        if tfile is not None and tfile.is_directory:
            self.enter_dir(tfile.path)
        elif cwd.pointer >= len(cwd) - 1:
            while True:
                self.move(left=1)
                cwd = self.thisdir
                if cwd.pointer < len(cwd) - 1:
                    break
                if cwd.path == '/':
                    break
            self.move(down=1)
            self.traverse()
        else:
            self.move(down=1)
            self.traverse()

    def traverse_backwards(self):
        self.change_mode('normal')
        if self.thisdir.pointer == 0:
            self.move(left=1)
            if self.thisdir.pointer != 0:
                self.traverse_backwards()
        else:
            self.move(up=1)
            while True:
                if self.thisfile is not None and self.thisfile.is_directory:
                    self.enter_dir(self.thisfile.path)
                    self.move(to=100, percentage=True)
                elif self.thisdir.pointer == 0:
                    break
                else:
                    self.move(up=1)

    # --------------------------
    # -- Shortcuts / Wrappers
    # --------------------------

    def pager_move(self, narg=None, **kw):
        self.ui.get_pager().move(narg=narg, **kw)

    def taskview_move(self, narg=None, **kw):
        self.ui.taskview.move(narg=narg, **kw)

    def pause_tasks(self):
        self.loader.pause(-1)

    def pager_close(self):
        if self.ui.pager.visible:
            self.ui.close_pager()
        if self.ui.browser.pager and self.ui.browser.pager.visible:
            self.ui.close_embedded_pager()

    def taskview_open(self):
        self.ui.open_taskview()

    def taskview_close(self):
        self.ui.close_taskview()

    def execute_command(self, cmd, **kw):
        return self.run(cmd, **kw)

    def edit_file(self, file=None):  # pylint: disable=redefined-builtin
        """Calls execute_file with the current file and label='editor'"""
        if file is None:
            file = self.thisfile
        elif isinstance(file, str):

            file = File(os.path.expanduser(file))