diff options
-rwxr-xr-x | ranger.py | 2 | ||||
-rw-r--r-- | ranger/api/commands.py | 3 | ||||
-rw-r--r-- | ranger/api/keys.py | 1 | ||||
-rw-r--r-- | ranger/container/settingobject.py | 1 | ||||
-rw-r--r-- | ranger/container/tags.py | 60 | ||||
-rw-r--r-- | ranger/core/actions.py | 6 | ||||
-rw-r--r-- | ranger/core/main.py | 5 | ||||
-rw-r--r-- | ranger/data/mime.types | 2 | ||||
-rwxr-xr-x | ranger/data/scope.sh | 9 | ||||
-rw-r--r-- | ranger/defaults/commands.py | 1 | ||||
-rw-r--r-- | ranger/defaults/keys.py | 27 | ||||
-rw-r--r-- | ranger/defaults/options.py | 3 | ||||
-rw-r--r-- | ranger/ext/keybinding_parser.py | 5 | ||||
-rw-r--r-- | ranger/fsobject/directory.py | 1 | ||||
-rw-r--r-- | ranger/gui/defaultui.py | 1 | ||||
-rw-r--r-- | ranger/gui/ui.py | 8 | ||||
-rw-r--r-- | ranger/gui/widgets/browsercolumn.py | 19 | ||||
-rw-r--r-- | ranger/gui/widgets/console.py | 13 |
18 files changed, 125 insertions, 42 deletions
diff --git a/ranger.py b/ranger.py index ffdf13aa..53fd8bdb 100755 --- a/ranger.py +++ b/ranger.py @@ -48,7 +48,7 @@ sys.dont_write_bytecode = '-c' in argv or '--clean' in argv __doc__ = """Ranger - file browser for the unix terminal""" # Don't import ./ranger when running an installed binary at /usr/bin/ranger -if os.path.isdir('ranger'): +if os.path.exists('ranger') and '/' in os.path.normpath(__file__): try: sys.path.remove(os.path.abspath('.')) except: diff --git a/ranger/api/commands.py b/ranger/api/commands.py index 80827c4f..9a353eef 100644 --- a/ranger/api/commands.py +++ b/ranger/api/commands.py @@ -85,6 +85,9 @@ class Command(FileManagerAware): def quick(self): """Override this""" + def cancel(self): + """Override this""" + # COMPAT: this is still used in old commands.py configs def _tab_only_directories(self): from os.path import dirname, basename, expanduser, join, isdir diff --git a/ranger/api/keys.py b/ranger/api/keys.py index 7ba05c73..75de6237 100644 --- a/ranger/api/keys.py +++ b/ranger/api/keys.py @@ -21,6 +21,7 @@ from inspect import getargspec, ismethod from ranger import RANGERDIR from ranger.api import * from ranger.container.bookmarks import ALLOWED_KEYS as ALLOWED_BOOKMARK_KEYS +from ranger.container.tags import ALLOWED_KEYS as ALLOWED_TAGS_KEYS from ranger.container.keymap import KeyMap, Direction, KeyMapWithDirections # A dummy that allows the generation of docstrings in ranger.defaults.keys diff --git a/ranger/container/settingobject.py b/ranger/container/settingobject.py index c56a18b5..d036245f 100644 --- a/ranger/container/settingobject.py +++ b/ranger/container/settingobject.py @@ -26,6 +26,7 @@ ALLOWED_SETTINGS = { 'dirname_in_tabs': bool, 'display_size_in_main_column': bool, 'display_size_in_status_bar': bool, + 'display_tags_in_all_columns': bool, 'draw_bookmark_borders': bool, 'draw_borders': bool, 'flushinput': bool, diff --git a/ranger/container/tags.py b/ranger/container/tags.py index 9ef8a1b2..c2fe3067 100644 --- a/ranger/container/tags.py +++ b/ranger/container/tags.py @@ -14,8 +14,13 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. from os.path import isdir, exists, dirname, abspath, realpath, expanduser +import string + +ALLOWED_KEYS = string.ascii_letters + string.digits + string.punctuation class Tags(object): + default_tag = '*' + def __init__(self, filename): self._filename = realpath(abspath(expanduser(filename))) @@ -28,33 +33,47 @@ class Tags(object): def __contains__(self, item): return item in self.tags - def add(self, *items): + def add(self, *items, **others): + if 'tag' in others: + tag = others['tag'] + else: + tag = self.defautag self.sync() for item in items: - self.tags.add(item) + self.tags[item] = tag self.dump() def remove(self, *items): self.sync() for item in items: try: - self.tags.remove(item) + del(self.tags[item]) except KeyError: pass self.dump() - def toggle(self, *items): + def toggle(self, *items, **others): + if 'tag' in others: + tag = others['tag'] + else: + tag = self.default_tag self.sync() for item in items: - if item in self: - try: - self.tags.remove(item) - except KeyError: - pass - else: - self.tags.add(item) + try: + if item in self and tag in (self.tags[item], self.default_tag): + del(self.tags[item]) + else: + self.tags[item] = tag + except KeyError: + pass self.dump() + def marker(self, item): + if item in self.tags: + return self.tags[item] + else: + return self.default_tag + def sync(self): try: f = open(self._filename, 'r') @@ -74,13 +93,24 @@ class Tags(object): f.close() def _compile(self, f): - for line in self.tags: - f.write(line + '\n') + for path, tag in self.tags.items(): + if tag == self.default_tag: + # COMPAT: keep the old format if the default tag is used + f.write(path + '\n') + elif tag in ALLOWED_KEYS: + f.write('{0}:{1}\n'.format(tag, path)) def _parse(self, f): - result = set() + result = dict() for line in f: - result.add(line.strip()) + line = line.strip() + if len(line) > 2 and line[1] == ':': + tag, path = line[0], line[2:] + if tag in ALLOWED_KEYS: + result[path] = tag + else: + result[line] = self.default_tag + return result def __nonzero__(self): diff --git a/ranger/core/actions.py b/ranger/core/actions.py index 89bd9389..cb692d4c 100644 --- a/ranger/core/actions.py +++ b/ranger/core/actions.py @@ -430,7 +430,7 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware): # Tags are saved in ~/.config/ranger/tagged and simply mark if a # file is important to you in any context. - def tag_toggle(self, paths=None, value=None, movedown=None): + def tag_toggle(self, paths=None, value=None, movedown=None, tag=None): if not self.tags: return if paths is None: @@ -438,11 +438,11 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware): else: tags = [realpath(path) for path in paths] if value is True: - self.tags.add(*tags) + self.tags.add(*tags, tag=tag or self.tags.default_tag) elif value is False: self.tags.remove(*tags) else: - self.tags.toggle(*tags) + self.tags.toggle(*tags, tag=tag or self.tags.default_tag) if movedown is None: movedown = len(tags) == 1 and paths is None diff --git a/ranger/core/main.py b/ranger/core/main.py index ade3bab5..e6392387 100644 --- a/ranger/core/main.py +++ b/ranger/core/main.py @@ -24,10 +24,15 @@ def main(): import locale import os.path import ranger + import sys from ranger.core.shared import (EnvironmentAware, 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: diff --git a/ranger/data/mime.types b/ranger/data/mime.types index 35e34fdc..14c35a9a 100644 --- a/ranger/data/mime.types +++ b/ranger/data/mime.types @@ -13,8 +13,10 @@ audio/flac flac audio/musepack mpc mpp mp+ audio/ogg oga ogg spx +audio/wavpack wv wvc video/mkv mkv +video/webm webm video/flash flv video/ogg ogv ogm video/divx div divx diff --git a/ranger/data/scope.sh b/ranger/data/scope.sh index 4d778486..ca1f7e67 100755 --- a/ranger/data/scope.sh +++ b/ranger/data/scope.sh @@ -30,19 +30,24 @@ extension=$(echo "$path" | grep '\.' | grep -o '[^.]\+$') # Functions: # "have $1" succeeds if $1 is an existing command/installed program function have { type -P "$1" > /dev/null; } -# "sucess" returns the exit code of the first program in the last pipe chain +# "success" returns the exit code of the first program in the last pipe chain function success { test ${PIPESTATUS[0]} = 0; } case "$extension" in # Archive extensions: 7z|a|ace|alz|arc|arj|bz|bz2|cab|cpio|deb|gz|jar|lha|lz|lzh|lzma|lzo|\ rar|rpm|rz|t7z|tar|tbz|tbz2|tgz|tlz|txz|tZ|tzo|war|xpi|xz|Z|zip) - atool -l "$path" | head -n $maxln && exit 3 + als "$path" | head -n $maxln + success && exit 0 || acat "$path" | head -n $maxln && exit 3 exit 1;; # PDF documents: pdf) pdftotext -l 10 -nopgbrk -q "$path" - | head -n $maxln | fmt -s -w $width success && exit 0 || exit 1;; + # BitTorrent Files + torrent) + transmission-show "$path" | head -n $maxln && exit 3 + success && exit 5 || exit 1;; # HTML Pages: htm|html|xhtml) have w3m && w3m -dump "$path" | head -n $maxln | fmt -s -w $width && exit 4 diff --git a/ranger/defaults/commands.py b/ranger/defaults/commands.py index e9572d3e..ad3fa0e5 100644 --- a/ranger/defaults/commands.py +++ b/ranger/defaults/commands.py @@ -19,6 +19,7 @@ This is the default file for command definitions. Each command is a subclass of `Command'. Several methods are defined to interface with the console: execute: call this method when the command is executed. + cancel: call this method when closing the console without executing. tab: call this method when tab is pressed. quick: call this method after each keypress. diff --git a/ranger/defaults/keys.py b/ranger/defaults/keys.py index ea98e4c5..cd1c5d07 100644 --- a/ranger/defaults/keys.py +++ b/ranger/defaults/keys.py @@ -159,6 +159,8 @@ map('L', fm.history_go(1)) # ----------------------------------------------- tagging / marking map('t', fm.tag_toggle()) map('T', fm.tag_remove()) +for key in ALLOWED_TAGS_KEYS: + map('"' + key, fm.tag_toggle(tag=key)) map(' ', fm.mark(toggle=True)) map('v', fm.mark(all=True, toggle=True)) @@ -167,22 +169,26 @@ map('<C-V><dir>', fm.mark_in_direction(val=True)) map('u<C-V><dir>', fm.mark_in_direction(val=False)) # ------------------------------------------ file system operations +map('y<bg>', fm.hint('*copy:* cop*y* *a*dd *r*emove ' \ + '*p*ath_to_xsel *d*irpath_to_xsel base*n*ame_to_xsel')) map('yy', 'y<dir>', fm.copy()) map('ya', fm.copy(mode='add')) map('yr', fm.copy(mode='remove')) map('yp', fm.execute_console('shell -d echo -n %d/%f | xsel -i')) map('yd', fm.execute_console('shell -d echo -n %d | xsel -i')) map('yn', fm.execute_console('shell -d echo -n %f | xsel -i')) +map('d<bg>', fm.hint('disk_*u*sage *cut:* *d*:cut *a*dd *r*emove')) map('dd', 'd<dir>', fm.cut()) map('da', fm.cut(mode='add')) map('dr', fm.cut(mode='remove')) +map('p<bg>', fm.hint('*paste:* *p*aste *o*verwrite sym*l*inks ' \ + '*h*ardlinks relative_sym*L*inks')) map('pp', fm.paste()) map('po', fm.paste(overwrite=True)) map('pl', fm.paste_symlink(relative=False)) map('pL', fm.paste_symlink(relative=True)) +map('ph<bg>', fm.hint('*paste:* hard*l*inks')) map('phl', fm.paste_hardlink()) -map('p<bg>', fm.hint('press *p* to confirm pasting' \ - ', *o*verwrite, create sym*l*inks, relative sym*L*inks')) map('u<bg>', fm.hint("un*y*ank, unbook*m*ark, unselect:*v*")) map('ud', 'uy', fm.uncut()) @@ -193,8 +199,9 @@ map('E', fm.edit_file()) map('du', fm.execute_console('shell -p du --max-depth=1 -h --apparent-size')) # -------------------------------------------------- toggle options -map('z<bg>', fm.hint("[*cdfhimpPsv*] show_*h*idden *p*review_files "\ - "*P*review_dirs *f*ilter flush*i*nput *m*ouse")) +map('z<bg>', fm.hint('*f*ilter *options:* *d*irectories_first *c*ollape_preview ' \ + '*s*ort_case_insensitive show_*h*idden *p*review_files '\ + '*P*review_dirs use_pre*v*iew_script flush*i*nput *m*ouse')) map('zh', '<C-h>', fm.toggle_boolean_option('show_hidden')) map('zp', fm.toggle_boolean_option('preview_files')) map('zP', fm.toggle_boolean_option('preview_directories')) @@ -207,13 +214,14 @@ map('zm', fm.toggle_boolean_option('mouse_enabled')) map('zf', fm.open_console('filter ')) # ------------------------------------------------------------ sort -map('o<bg>', 'O<bg>', fm.hint("*s*ize *b*asename *m*time" \ - " *t*ype *r*everse *n*atural")) +map('o<bg>', 'O<bg>', fm.hint('*sort by:* *s*ize *b*asename *m*time' \ + ' *t*ype *r*everse *n*atural')) sort_dict = { 's': 'size', 'b': 'basename', 'n': 'natural', 'm': 'mtime', + 'c': 'ctime', 't': 'type', } @@ -240,11 +248,13 @@ def insert_before_filename(arg): map('cw', fm.open_console('rename ')) map('cd', fm.open_console('cd ')) map('f', fm.open_console('find ')) -map('d<bg>', fm.hint('d*u* (disk usage) d*d* (cut)')) map('@', fm.open_console('shell %s', position=len('shell '))) map('#', fm.open_console('shell -p ')) # --------------------------------------------- jump to directories +map('g<bg>', fm.hint('*goto:* */* *d*ev *e*tc *h*:~ *m*edia ' \ + '*M*nt *o*pt *s*rv *u*sr *v*ar *R*anger *link:* *l*:target_path ' \ + 'rea*L*_path *tab:* *c*lose *n*ew nex*t* *T*:prev')) map('gh', fm.cd('~')) map('ge', fm.cd('/etc')) map('gu', fm.cd('/usr')) @@ -278,11 +288,12 @@ map('/', fm.open_console('search ')) map('n', fm.search()) map('N', fm.search(forward=False)) +map('c<bg>', fm.hint('*w*:rename ch*d*ir *search order:* ' \ + '*c*time *m*imetype *s*ize *t*ag')) map('ct', fm.search(order='tag')) map('cc', fm.search(order='ctime')) map('cm', fm.search(order='mimetype')) map('cs', fm.search(order='size')) -map('c<bg>', fm.hint('*c*time *m*imetype *s*ize *t*ag *w*:rename')) # ------------------------------------------------------- bookmarks for key in ALLOWED_BOOKMARK_KEYS: diff --git a/ranger/defaults/options.py b/ranger/defaults/options.py index a2289729..c9cbf8ad 100644 --- a/ranger/defaults/options.py +++ b/ranger/defaults/options.py @@ -84,6 +84,9 @@ mouse_enabled = True display_size_in_main_column = True display_size_in_status_bar = False +# Display files tags in all columns or only in main column? +display_tags_in_all_columns = True + # Set a title for the window? update_title = True diff --git a/ranger/ext/keybinding_parser.py b/ranger/ext/keybinding_parser.py index 93119bce..bfd1b6d4 100644 --- a/ranger/ext/keybinding_parser.py +++ b/ranger/ext/keybinding_parser.py @@ -67,6 +67,7 @@ def parse_keybinding(obj): DIRKEY = 9001 ANYKEY = 9002 PASSIVE_ACTION = 9003 +ALT_KEY = 9004 very_special_keys = { 'dir': DIRKEY, @@ -98,10 +99,10 @@ special_keys = { } for key, val in tuple(special_keys.items()): - special_keys['a-' + key] = (27, val) + special_keys['a-' + key] = (ALT_KEY, val) for char in ascii_lowercase + '0123456789': - special_keys['a-' + char] = (27, ord(char)) + special_keys['a-' + char] = (ALT_KEY, ord(char)) for char in ascii_lowercase: special_keys['c-' + char] = ord(char) - 96 diff --git a/ranger/fsobject/directory.py b/ranger/fsobject/directory.py index 7b8a7563..e0623e23 100644 --- a/ranger/fsobject/directory.py +++ b/ranger/fsobject/directory.py @@ -87,6 +87,7 @@ class Directory(FileSystemObject, Accumulator, Loadable, SettingsAware): 'natural': sort_naturally, 'size': lambda path: -path.size, 'mtime': lambda path: -(path.stat and path.stat.st_mtime or 1), + 'ctime': lambda path: -(path.stat and path.stat.st_ctime or 1), 'type': lambda path: path.mimetype or '', } diff --git a/ranger/gui/defaultui.py b/ranger/gui/defaultui.py index 434e6d45..933b56f7 100644 --- a/ranger/gui/defaultui.py +++ b/ranger/gui/defaultui.py @@ -112,6 +112,7 @@ class DefaultUI(UI): self.browser.visible = False self.taskview.visible = True self.taskview.focused = True + self.fm.hint('*tasks:* *dd*:remove *J*:move_down *H*:move_up') def redraw_main_column(self): self.browser.main_column.need_redraw = True diff --git a/ranger/gui/ui.py b/ranger/gui/ui.py index a2babed8..cc2871af 100644 --- a/ranger/gui/ui.py +++ b/ranger/gui/ui.py @@ -22,6 +22,7 @@ from .displayable import DisplayableContainer from ranger.gui.curses_shortcuts import ascii_only from ranger.container.keymap import CommandArgs from .mouse_event import MouseEvent +from ranger.ext.keybinding_parser import ALT_KEY TERMINALS_WITH_TITLE = ("xterm", "xterm-256color", "rxvt", "rxvt-256color", "rxvt-unicode", "aterm", "Eterm", @@ -183,9 +184,14 @@ class UI(DisplayableContainer): keys.append(getkey) if len(keys) == 1: keys.append(-1) + elif keys[0] == 27: + keys[0] = ALT_KEY if self.settings.xterm_alt_key: if len(keys) == 2 and keys[1] in range(127, 256): - keys = [27, keys[1] - 128] + if keys[0] == 195: + keys = [ALT_KEY, keys[1] - 64] + elif keys[0] == 194: + keys = [ALT_KEY, keys[1] - 128] self.handle_keys(*keys) self.set_load_mode(previous_load_mode) if self.settings.flushinput and not self.console.visible: diff --git a/ranger/gui/widgets/browsercolumn.py b/ranger/gui/widgets/browsercolumn.py index b8277748..801b79fd 100644 --- a/ranger/gui/widgets/browsercolumn.py +++ b/ranger/gui/widgets/browsercolumn.py @@ -27,7 +27,6 @@ class BrowserColumn(Pager): display_infostring = False scroll_begin = 0 target = None - tagged_marker = '*' last_redraw_time = -1 ellipsis = { False: '~', True: '…' } @@ -228,9 +227,14 @@ class BrowserColumn(Pager): text = drawn.basename tagged = self.fm.tags and drawn.realpath in self.fm.tags + if tagged: + tagged_marker = self.fm.tags.marker(drawn.realpath) + space = self.wid - len(infostring) if self.main_column: space -= 2 + elif self.settings.display_tags_in_all_columns: + space -= 1 # if len(text) > space: # text = text[:space-1] + self.ellipsis @@ -240,13 +244,13 @@ class BrowserColumn(Pager): if drawn.marked: this_color.append('marked') - if self.main_column: + if self.main_column or self.settings.display_tags_in_all_columns: text = " " + text if tagged: this_color.append('tagged') - if self.main_column: - text = self.tagged_marker + text + if self.main_column or self.settings.display_tags_in_all_columns: + text = tagged_marker + text if drawn.is_directory: this_color.append('directory') @@ -276,7 +280,7 @@ class BrowserColumn(Pager): wtext = WideString(text) if len(wtext) > space: wtext = wtext[:space - 1] + ellipsis - if self.main_column: + if self.main_column or self.settings.display_tags_in_all_columns: if tagged: self.addstr(line, 0, str(wtext)) elif self.wid > 1: @@ -296,9 +300,10 @@ class BrowserColumn(Pager): start, wid = bad_info_color self.color_at(line, start, wid, this_color, 'badinfo') - if self.main_column and tagged and self.wid > 2: + if (self.main_column or self.settings.display_tags_in_all_columns) \ + and tagged and self.wid > 2: this_color.append('tag_marker') - self.color_at(line, 0, len(self.tagged_marker), this_color) + self.color_at(line, 0, len(tagged_marker), this_color) self.color_reset() diff --git a/ranger/gui/widgets/console.py b/ranger/gui/widgets/console.py index 12f685d4..a575405d 100644 --- a/ranger/gui/widgets/console.py +++ b/ranger/gui/widgets/console.py @@ -123,7 +123,14 @@ class Console(Widget): self.history.add('') return True - def close(self): + def close(self, trigger_cancel_function=True): + if trigger_cancel_function: + cmd = self._get_cmd() + if cmd: + try: + cmd.cancel() + except Exception as error: + self.fm.notify(error) if self.last_cursor_mode is not None: try: curses.curs_set(self.last_cursor_mode) @@ -288,7 +295,7 @@ class Console(Widget): self.tab_deque = None if mod == -1 and self.pos == 0: if not self.line: - self.close() + self.close(trigger_cancel_function=False) return # Delete utf-char-wise if self.fm.py3: @@ -315,7 +322,7 @@ class Console(Widget): self.fm.notify(error) if self.allow_close: - self.close() + self.close(trigger_cancel_function=False) def _get_cmd(self): try: |