diff options
-rw-r--r-- | README.md | 5 | ||||
-rw-r--r-- | doc/ranger.pod | 2 | ||||
-rw-r--r-- | ranger/config/rc.conf | 2 | ||||
-rwxr-xr-x | ranger/data/scope.sh | 3 | ||||
-rw-r--r-- | ranger/ext/shell_escape.py | 2 | ||||
-rw-r--r-- | ranger/ext/vcs/bzr.py | 22 | ||||
-rw-r--r-- | ranger/ext/vcs/git.py | 87 | ||||
-rw-r--r-- | ranger/ext/vcs/hg.py | 10 | ||||
-rw-r--r-- | ranger/ext/vcs/svn.py | 25 | ||||
-rw-r--r-- | ranger/ext/vcs/vcs.py | 11 | ||||
-rw-r--r-- | ranger/gui/displayable.py | 6 | ||||
-rw-r--r-- | ranger/gui/ui.py | 14 | ||||
-rw-r--r-- | ranger/gui/widgets/statusbar.py | 2 | ||||
-rw-r--r-- | ranger/gui/widgets/view_miller.py | 5 |
14 files changed, 105 insertions, 91 deletions
diff --git a/README.md b/README.md index b5fc03ea..5174f1c1 100644 --- a/README.md +++ b/README.md @@ -69,12 +69,13 @@ Optional: Optional, for enhanced file previews (with "scope.sh"): * img2txt (from caca-utils) for ASCII-art image previews -* highlight for syntax highlighting of code -* atool for previews of archives +* highlight or pygmentize for syntax highlighting of code +* atool, acat, bsdtar and/or unrar for previews of archives * lynx, w3m or elinks for previews of html pages * pdftotext for pdf previews * transmission-show for viewing bit-torrent information * mediainfo or exiftool for viewing information about media files +* odt2txt for OpenDocument text files (odt, ods, odp and sxw) Installing diff --git a/doc/ranger.pod b/doc/ranger.pod index 7c5a7768..6b0752bd 100644 --- a/doc/ranger.pod +++ b/doc/ranger.pod @@ -1063,7 +1063,7 @@ Examples: =item filter [I<string>] Displays only the files which contain the I<string> in their basename. Running -this command without any parameter will reset the fitler. +this command without any parameter will reset the filter. This command is based on the I<scout> command and supports all of its options. diff --git a/ranger/config/rc.conf b/ranger/config/rc.conf index 560fbe4d..cc7f5007 100644 --- a/ranger/config/rc.conf +++ b/ranger/config/rc.conf @@ -259,7 +259,7 @@ map ! console shell%space map @ console -p6 shell %%s map # console shell -p%space map s console shell%space -map r chain draw_possible_programs; console open_with%space +map r chain draw_possible_programs; console open_with%%space map f console find%space map cd console cd%space diff --git a/ranger/data/scope.sh b/ranger/data/scope.sh index 669d1e34..44fcec2b 100755 --- a/ranger/data/scope.sh +++ b/ranger/data/scope.sh @@ -80,6 +80,9 @@ case "$extension" in # BitTorrent Files torrent) try transmission-show "$path" && { dump | trim; exit 5; } || exit 1;; + # ODT Files + odt|ods|odp|sxw) + try odt2txt "$path" && { dump | trim; exit 5; } || exit 1;; # HTML Pages: htm|html|xhtml) try w3m -dump "$path" && { dump | trim | fmt -s -w $width; exit 4; } diff --git a/ranger/ext/shell_escape.py b/ranger/ext/shell_escape.py index fe542084..d57ff339 100644 --- a/ranger/ext/shell_escape.py +++ b/ranger/ext/shell_escape.py @@ -3,7 +3,7 @@ """Functions to escape metacharacters of arguments for shell commands.""" -META_CHARS = (' ', "'", '"', '`', '&', '|', ';', +META_CHARS = (' ', "'", '"', '`', '&', '|', ';', '#', '$', '!', '(', ')', '[', ']', '<', '>', '\t') UNESCAPABLE = set(map(chr, list(range(9)) + list(range(10, 32)) + list(range(127, 256)))) diff --git a/ranger/ext/vcs/bzr.py b/ranger/ext/vcs/bzr.py index 5decc8b1..d0db1faf 100644 --- a/ranger/ext/vcs/bzr.py +++ b/ranger/ext/vcs/bzr.py @@ -25,7 +25,7 @@ class Bzr(Vcs): def _remote_url(self): """Remote url""" try: - return self._run(['config', 'parent_location']).rstrip('\n') or None + return self._run(['config', 'parent_location']) or None except VcsError: return None @@ -87,29 +87,29 @@ class Bzr(Vcs): statuses = set() # Paths with status - output = self._run(['status', '--short', '--no-classify']).rstrip('\n') - if not output: + lines = self._run(['status', '--short', '--no-classify']).split('\n') + if not lines: return 'sync' - for line in output.split('\n'): + for line in lines: statuses.add(self._status_translate(line[:2])) for status in self.DIRSTATUSES: if status in statuses: return status + return 'sync' def data_status_subpaths(self): statuses = {} # Ignored - output = self._run(['ls', '--null', '--ignored']).rstrip('\x00') - if output: - for path in output.split('\x00'): - statuses[path] = 'ignored' + paths = self._run(['ls', '--null', '--ignored']).split('\0')[:-1] + for path in paths: + statuses[path] = 'ignored' # Paths with status - output = self._run(['status', '--short', '--no-classify']).rstrip('\n') - for line in output.split('\n'): + lines = self._run(['status', '--short', '--no-classify']).split('\n') + for line in lines: statuses[os.path.normpath(line[4:])] = self._status_translate(line[:2]) return statuses @@ -121,7 +121,7 @@ class Bzr(Vcs): def data_branch(self): try: - return self._run(['nick']).rstrip('\n') or None + return self._run(['nick']) or None except VcsError: return None diff --git a/ranger/ext/vcs/git.py b/ranger/ext/vcs/git.py index 8f4d9ff8..06e066d2 100644 --- a/ranger/ext/vcs/git.py +++ b/ranger/ext/vcs/git.py @@ -4,13 +4,19 @@ """Git module""" from datetime import datetime -import json import os import re +import unicodedata from .vcs import Vcs, VcsError +def string_control_replace(string, replacement): + """Replace all unicode control characters with replacement""" + return ''.join( + (replacement if unicodedata.category(char)[0] == 'C' else char for char in string)) + + class Git(Vcs): """VCS implementation for Git""" _status_translations = ( @@ -30,26 +36,17 @@ class Git(Vcs): def _head_ref(self): """Returns HEAD reference""" - return self._run(['symbolic-ref', self.HEAD]).rstrip('\n') or None + return self._run(['symbolic-ref', self.HEAD]) or None def _remote_ref(self, ref): """Returns remote reference associated to given ref""" if ref is None: return None - return self._run(['for-each-ref', '--format=%(upstream)', ref]).rstrip('\n') or None + return self._run(['for-each-ref', '--format=%(upstream)', ref]) or None def _log(self, refspec=None, maxres=None, filelist=None): """Returns an array of dicts containing revision info for refspec""" - args = [ - '--no-pager', 'log', - '--pretty={' - '%x00short%x00:%x00%h%x00,' - '%x00revid%x00:%x00%H%x00,' - '%x00author%x00:%x00%an <%ae>%x00,' - '%x00date%x00:%ct,' - '%x00summary%x00:%x00%s%x00' - '}' - ] + args = ['--no-pager', 'log', '--pretty=%h%x00%H%x00%an <%ae>%x00%ct%x00%s%x00%x00'] if refspec: args += ['-1', refspec] elif maxres: @@ -58,18 +55,22 @@ class Git(Vcs): args += ['--'] + filelist try: - output = self._run(args).rstrip('\n') + output = self._run(args) except VcsError: return None if not output: return None log = [] - for line in output\ - .replace('\\', '\\\\').replace('"', '\\"').replace('\x00', '"').split('\n'): - line = json.loads(line) - line['date'] = datetime.fromtimestamp(line['date']) - log.append(line) + for line in output[:-2].split('\0\0\n'): + commit_hash_abbrev, commit_hash, author, timestamp, subject = line.split('\0') + log.append({ + 'short': commit_hash_abbrev, + 'revid': commit_hash, + 'author': string_control_replace(author, ' '), + 'date': datetime.fromtimestamp(int(timestamp)), + 'summary': string_control_replace(subject, ' '), + }) return log def _status_translate(self, code): @@ -100,10 +101,10 @@ class Git(Vcs): # Paths with status skip = False - output = self._run(['status', '--porcelain', '-z']).rstrip('\x00') - if not output: + lines = self._run(['status', '--porcelain', '-z']).split('\0')[:-1] + if not lines: return 'sync' - for line in output.split('\x00'): + for line in lines: if skip: skip = False continue @@ -114,39 +115,37 @@ class Git(Vcs): for status in self.DIRSTATUSES: if status in statuses: return status + return 'sync' def data_status_subpaths(self): statuses = {} # Ignored directories - output = self._run([ + paths = self._run([ 'ls-files', '-z', '--others', '--directory', '--ignored', '--exclude-standard' - ]).rstrip('\x00') - if output: - for path in output.split('\x00'): - if path.endswith('/'): - statuses[os.path.normpath(path)] = 'ignored' + ]).split('\0')[:-1] + for path in paths: + if path.endswith('/'): + statuses[os.path.normpath(path)] = 'ignored' # Empty directories - output = self._run( - ['ls-files', '-z', '--others', '--directory', '--exclude-standard']).rstrip('\x00') - if output: - for path in output.split('\x00'): - if path.endswith('/'): - statuses[os.path.normpath(path)] = 'none' + paths = self._run( + ['ls-files', '-z', '--others', '--directory', '--exclude-standard']).split('\0')[:-1] + for path in paths: + if path.endswith('/'): + statuses[os.path.normpath(path)] = 'none' # Paths with status - output = self._run(['status', '--porcelain', '-z', '--ignored']).rstrip('\x00') - if output: - skip = False - for line in output.split('\x00'): - if skip: - skip = False - continue - statuses[os.path.normpath(line[3:])] = self._status_translate(line[:2]) - if line.startswith('R'): - skip = True + lines = self._run(['status', '--porcelain', '-z', '--ignored']).split('\0')[:-1] + skip = False + for line in lines: + if skip: + skip = False + continue + statuses[os.path.normpath(line[3:])] = self._status_translate(line[:2]) + if line.startswith('R'): + skip = True return statuses diff --git a/ranger/ext/vcs/hg.py b/ranger/ext/vcs/hg.py index cf15e35e..21460975 100644 --- a/ranger/ext/vcs/hg.py +++ b/ranger/ext/vcs/hg.py @@ -36,7 +36,7 @@ class Hg(Vcs): args += ['--'] + filelist try: - output = self._run(args).rstrip('\n') + output = self._run(args) except VcsError: return None if not output: @@ -56,13 +56,13 @@ class Hg(Vcs): def _remote_url(self): """Remote url""" try: - return self._run(['showconfig', 'paths.default']).rstrip('\n') or None + return self._run(['showconfig', 'paths.default']) or None except VcsError: return None def _status_translate(self, code): """Translate status code""" - for code_x, status in self._status_translations: # pylint: disable=invalid-name + for code_x, status in self._status_translations: if code in code_x: return status return 'unknown' @@ -80,7 +80,7 @@ class Hg(Vcs): if filelist: args += filelist else: - args += self.rootvcs.status_subpaths.keys() + args += self.rootvcs.status_subpaths.keys() # pylint: disable=no-member self._run(args, catchout=False) # Data interface @@ -117,7 +117,7 @@ class Hg(Vcs): return 'unknown' def data_branch(self): - return self._run(['branch']).rstrip('\n') or None + return self._run(['branch']) or None def data_info(self, rev=None): if rev is None: diff --git a/ranger/ext/vcs/svn.py b/ranger/ext/vcs/svn.py index 1813f857..1f09cd52 100644 --- a/ranger/ext/vcs/svn.py +++ b/ranger/ext/vcs/svn.py @@ -36,7 +36,7 @@ class SVN(Vcs): args += ['--'] + filelist try: - output = self._run(args).rstrip('\n') + output = self._run(args) except VcsError: return None if not output: @@ -66,7 +66,7 @@ class SVN(Vcs): def _remote_url(self): """Remote url""" try: - output = self._run(['info', '--xml']).rstrip('\n') + output = self._run(['info', '--xml']) except VcsError: return None if not output: @@ -86,7 +86,7 @@ class SVN(Vcs): if filelist: args += filelist else: - args += self.rootvcs.status_subpaths.keys() + args += self.rootvcs.status_subpaths.keys() # pylint: disable=no-member self._run(args, catchout=False) # Data Interface @@ -95,10 +95,10 @@ class SVN(Vcs): statuses = set() # Paths with status - output = self._run(['status']).rstrip('\n') - if not output: + lines = self._run(['status']).split('\n') + if not lines: return 'sync' - for line in output.split('\n'): + for line in lines: code = line[0] if code == ' ': continue @@ -113,13 +113,12 @@ class SVN(Vcs): statuses = {} # Paths with status - output = self._run(['status']).rstrip('\n') - if output: - for line in output.split('\n'): - code, path = line[0], line[8:] - if code == ' ': - continue - statuses[os.path.normpath(path)] = self._status_translate(code) + lines = self._run(['status']).split('\n') + for line in lines: + code, path = line[0], line[8:] + if code == ' ': + continue + statuses[os.path.normpath(path)] = self._status_translate(code) return statuses diff --git a/ranger/ext/vcs/vcs.py b/ranger/ext/vcs/vcs.py index 487c9405..9c7be653 100644 --- a/ranger/ext/vcs/vcs.py +++ b/ranger/ext/vcs/vcs.py @@ -109,7 +109,8 @@ class Vcs(object): # pylint: disable=too-many-instance-attributes # Generic - def _run(self, args, path=None, catchout=True, retbytes=False): + def _run(self, args, path=None, # pylint: disable=too-many-arguments + catchout=True, retbytes=False, rstrip_newline=True): """Run a command""" cmd = [self.repotype] + args if path is None: @@ -119,7 +120,13 @@ class Vcs(object): # pylint: disable=too-many-instance-attributes try: if catchout: output = subprocess.check_output(cmd, cwd=path, stderr=devnull) - return output if retbytes else output.decode('UTF-8') + if retbytes: + return output + else: + output = output.decode('UTF-8') + if rstrip_newline and output.endswith('\n'): + return output[:-1] + return output else: subprocess.check_call(cmd, cwd=path, stdout=devnull, stderr=devnull) except (subprocess.CalledProcessError, FileNotFoundError): diff --git a/ranger/gui/displayable.py b/ranger/gui/displayable.py index 7b5aa954..62eb5300 100644 --- a/ranger/gui/displayable.py +++ b/ranger/gui/displayable.py @@ -211,6 +211,7 @@ class DisplayableContainer(Displayable): New methods: add_child(object) -- add the object to the container. + replace_child(old_obj, new_obj) -- replaces old object with new object. remove_child(object) -- remove the object from the container. New attributes: @@ -290,6 +291,11 @@ class DisplayableContainer(Displayable): self.container.append(obj) obj.parent = self + def replace_child(self, old_obj, new_obj): + """Replace the old object with the new instance in the container.""" + self.container[self.container.index(old_obj)] = new_obj + new_obj.parent = self + def remove_child(self, obj): """Remove the object from the container.""" try: diff --git a/ranger/gui/ui.py b/ranger/gui/ui.py index 4c302e00..f10bc0f2 100644 --- a/ranger/gui/ui.py +++ b/ranger/gui/ui.py @@ -437,18 +437,18 @@ class UI(DisplayableContainer): if value in self.ALLOWED_VIEWMODES: if self._viewmode != value: self._viewmode = value - resize = False + new_browser = self._viewmode_to_class(value)(self.win) + if hasattr(self, 'browser'): old_size = self.browser.y, self.browser.x, self.browser.hei, self.browser.wid - self.remove_child(self.browser) + self.replace_child(self.browser, new_browser) self.browser.destroy() - resize = True + new_browser.resize(*old_size) + else: + self.add_child(new_browser) - self.browser = self._viewmode_to_class(value)(self.win) + self.browser = new_browser self.redraw_window() - self.add_child(self.browser) - if resize: - self.browser.resize(*old_size) else: raise ValueError("Attempting to set invalid viewmode `%s`, should " "be one of `%s`." % (value, "`, `".join(self.ALLOWED_VIEWMODES))) diff --git a/ranger/gui/widgets/statusbar.py b/ranger/gui/widgets/statusbar.py index 4eb06692..0828c372 100644 --- a/ranger/gui/widgets/statusbar.py +++ b/ranger/gui/widgets/statusbar.py @@ -258,7 +258,7 @@ class StatusBar(Widget): right.add("', ", "space") if target.marked_items: - if len(target.marked_items) == len(target.files): + if len(target.marked_items) == target.size: right.add(human_readable(target.disk_usage, separator='')) else: sumsize = sum(f.size for f in target.marked_items if not diff --git a/ranger/gui/widgets/view_miller.py b/ranger/gui/widgets/view_miller.py index 90046456..42013fe9 100644 --- a/ranger/gui/widgets/view_miller.py +++ b/ranger/gui/widgets/view_miller.py @@ -256,9 +256,8 @@ class ViewMiller(ViewBase): # Show the preview column when it has a preview but has # been hidden (e.g. because of padding_right = False) - if not self.pager.visible and not self.columns[-1].visible and \ - self.columns[-1].target and self.columns[-1].target.is_directory \ - or self.columns[-1].has_preview() and not self.pager.visible: + if not self.columns[-1].visible and self.columns[-1].has_preview() \ + and not self.pager.visible: self.columns[-1].visible = True if self.preview and self.is_collapsed != self._collapse(): |