diff options
-rw-r--r-- | doc/ranger.pod | 3 | ||||
-rw-r--r-- | ranger/api/__init__.py | 8 | ||||
-rw-r--r-- | ranger/config/commands.py | 41 | ||||
-rw-r--r-- | ranger/config/rc.conf | 8 | ||||
-rw-r--r-- | ranger/container/directory.py | 2 | ||||
-rw-r--r-- | ranger/container/fsobject.py | 18 | ||||
-rw-r--r-- | ranger/core/actions.py | 56 | ||||
-rw-r--r-- | ranger/core/linemode.py | 82 | ||||
-rw-r--r-- | ranger/gui/widgets/browsercolumn.py | 42 | ||||
-rw-r--r-- | ranger/gui/widgets/titlebar.py | 2 |
10 files changed, 165 insertions, 97 deletions
diff --git a/doc/ranger.pod b/doc/ranger.pod index 97a324a8..336a886c 100644 --- a/doc/ranger.pod +++ b/doc/ranger.pod @@ -1051,6 +1051,9 @@ Sets the linemode of all files in the current directory. The linemode may be: available, fall back to the "filename" linemode if no metadata was found. See :meta command. +The custom linemodes may be added by subclassing the I<LinemodeBase> class. +See the I<ranger.core.linemode> module for some examples. + =item load_copy_buffer Load the copy buffer from F<~/.config/ranger/copy_buffer>. This can be used to diff --git a/ranger/api/__init__.py b/ranger/api/__init__.py index a50f706f..572cf1a4 100644 --- a/ranger/api/__init__.py +++ b/ranger/api/__init__.py @@ -26,3 +26,11 @@ def hook_ready(fm): This hook is executed after the user interface is initialized. You should NOT print anything to stdout anymore from here on. Use fm.notify instead. """ + +from ranger.core.linemode import LinemodeBase + +def register_linemode(linemode_class): + """Add a custom linemode class. See ranger.core.linemode""" + from ranger.container.fsobject import FileSystemObject + FileSystemObject.linemode_dict[linemode_class.name] = linemode_class() + return linemode_class diff --git a/ranger/config/commands.py b/ranger/config/commands.py index 55f6442b..b47cb6ad 100644 --- a/ranger/config/commands.py +++ b/ranger/config/commands.py @@ -390,7 +390,7 @@ class setintag(setlocal): class default_linemode(Command): def execute(self): import re - from ranger.container.fsobject import POSSIBLE_LINEMODES + from ranger.container.fsobject import FileSystemObject if len(self.args) < 2: self.fm.notify("Usage: default_linemode [path=<regexp> | tag=<tag(s)>] <linemode>", bad=True) @@ -410,9 +410,9 @@ class default_linemode(Command): # Extract and validate the line mode from the command line linemode = self.rest(1) - if linemode not in POSSIBLE_LINEMODES: + if linemode not in FileSystemObject.linemode_dict: self.fm.notify("Invalid linemode: %s; should be %s" % - (linemode, "/".join(POSSIBLE_LINEMODES)), bad=True) + (linemode, "/".join(FileSystemObject.linemode_dict)), bad=True) # Add the prepared entry to the fm.default_linemodes entry = [method, argument, linemode] @@ -423,6 +423,11 @@ class default_linemode(Command): for col in self.fm.ui.browser.columns: col.need_redraw = True + def tab(self): + mode = self.arg(1) + return (self.arg(0) + " " + linemode + for linemode in self.fm.thisfile.linemode_dict.keys()) + class quit(Command): """:quit @@ -793,14 +798,12 @@ class bulkrename(Command): def execute(self): import sys import tempfile - from os.path import relpath from ranger.container.file import File from ranger.ext.shell_escape import shell_escape as esc py3 = sys.version_info[0] >= 3 # Create and edit the file list - filenames = [relpath(f.path, start=self.fm.thisdir.path) - for f in self.fm.thistab.get_selection()] + filenames = [f.relative_path for f in self.fm.thistab.get_selection()] listfile = tempfile.NamedTemporaryFile(delete=False) listpath = listfile.name @@ -1418,3 +1421,29 @@ class meta(prompt_metadata): metadata = self.fm.metadata.get_metadata(self.fm.thisfile.path) if key in metadata and metadata[key]: return self.arg(0) + " " + metadata[key] + +class linemode(default_linemode): + """ + :linemode <mode> + + Change what is displayed as a filename. + + - "mode" may be any of the defined linemodes (see: ranger.core.linemode). + "normal" is mapped to "filename". + """ + + def execute(self): + mode = self.arg(1) + + if mode == "normal": + mode = DEFAULT_LINEMODE + + if mode not in self.fm.thisfile.linemode_dict: + self.fm.notify("Unhandled linemode: `%s'" % mode, bad=True) + return + + self.fm.thisdir._set_linemode_of_children(mode) + + # Ask the browsercolumns to redraw + for col in self.fm.ui.browser.columns: + col.need_redraw = True diff --git a/ranger/config/rc.conf b/ranger/config/rc.conf index e55ab2be..ca67c389 100644 --- a/ranger/config/rc.conf +++ b/ranger/config/rc.conf @@ -29,7 +29,7 @@ set hidden_filter ^\.|\.(?:pyc|pyo|bak|swp)$|^lost\+found$|^__(py)?cache__$ set show_hidden false # Ask for a confirmation when running the "delete" command? -# Valid values are "always" (default), "never", "multiple" +# Valid values are "always", "never", "multiple" (default) # With "multiple", ranger will ask only if you delete multiple files at once. set confirm_on_delete multiple @@ -81,7 +81,7 @@ set unicode_ellipsis false set show_hidden_bookmarks true # Which colorscheme to use? These colorschemes are available by default: -# default, jungle, snow +# default, jungle, snow, solarized set colorscheme default # Preview files on the rightmost column? @@ -284,7 +284,7 @@ map <END> move to=-1 map <PAGEDOWN> move down=1 pages=True map <PAGEUP> move up=1 pages=True map <CR> move right=1 -map <DELETE> console delete +#map <DELETE> console delete map <INSERT> console touch # VIM-like @@ -348,6 +348,8 @@ map pL paste_symlink relative=True map phl paste_hardlink map pht paste_hardlinked_subtree +map dD console delete + map dd cut map ud uncut map da cut mode=add diff --git a/ranger/container/directory.py b/ranger/container/directory.py index 71eda763..256dd729 100644 --- a/ranger/container/directory.py +++ b/ranger/container/directory.py @@ -21,7 +21,7 @@ from ranger.container.settings import LocalSettings def sort_by_basename(path): """returns path.basename (for sorting)""" - return path.drawn_basename + return path.relative_path def sort_by_basename_icase(path): """returns case-insensitive path.basename (for sorting)""" diff --git a/ranger/container/fsobject.py b/ranger/container/fsobject.py index 08161c3c..6cdaa727 100644 --- a/ranger/container/fsobject.py +++ b/ranger/container/fsobject.py @@ -12,14 +12,12 @@ DOCUMENT_BASENAMES = ('bugs', 'bugs', 'changelog', 'copying', 'credits', BAD_INFO = '?' -POSSIBLE_LINEMODES = ("filename", "metatitle", "permissions") -DEFAULT_LINEMODE = "filename" - import re from grp import getgrgid from os import lstat, stat, getcwd from os.path import abspath, basename, dirname, realpath, splitext, extsep, relpath from pwd import getpwuid +from ranger.core.linemode import * from ranger.core.shared import FileManagerAware, SettingsAware from ranger.ext.shell_escape import shell_escape from ranger.ext.spawn import spawn @@ -86,6 +84,10 @@ class FileSystemObject(FileManagerAware, SettingsAware): basename_is_rel_to = None _linemode = DEFAULT_LINEMODE + linemode_dict = dict( + (linemode.name, linemode()) for linemode in + [DefaultLinemode, TitleLinemode, PermissionsLinemode] + ) def __init__(self, path, preload=None, path_is_abs=False, basename_is_rel_to=None): if not path_is_abs: @@ -94,11 +96,11 @@ class FileSystemObject(FileManagerAware, SettingsAware): self.basename_is_rel_to = basename_is_rel_to if basename_is_rel_to == None: self.basename = basename(path) - self.drawn_basename = self.basename + self.relative_path = self.basename else: self.basename = basename(path) - self.drawn_basename = relpath(path, basename_is_rel_to) - self.basename_lower = self.drawn_basename.lower() + self.relative_path = relpath(path, basename_is_rel_to) + self.basename_lower = self.relative_path.lower() self.extension = splitext(self.basename)[1].lstrip(extsep) or None self.dirname = dirname(path) self.preload = preload @@ -112,7 +114,7 @@ class FileSystemObject(FileManagerAware, SettingsAware): # Set the line mode from fm.default_linemodes for method, argument, linemode in self.fm.default_linemodes: - if linemode in POSSIBLE_LINEMODES: + if linemode in self.linemode_dict: if method == "always": self._linemode = linemode break @@ -142,7 +144,7 @@ class FileSystemObject(FileManagerAware, SettingsAware): @lazy_property def basename_natural(self): return [c if i % 3 == 1 else (int(c) if c else 0) for i, c in \ - enumerate(_extract_number_re.split(self.drawn_basename))] + enumerate(_extract_number_re.split(self.relative_path))] @lazy_property def basename_natural_lower(self): diff --git a/ranger/core/actions.py b/ranger/core/actions.py index 63c91bcc..d1e383ba 100644 --- a/ranger/core/actions.py +++ b/ranger/core/actions.py @@ -27,7 +27,7 @@ from ranger.core.tab import Tab from ranger.container.file import File from ranger.core.loader import CommandLoader, CopyLoader from ranger.container.settings import ALLOWED_SETTINGS -from ranger.container.fsobject import POSSIBLE_LINEMODES, DEFAULT_LINEMODE +from ranger.core.linemode import DEFAULT_LINEMODE MACRO_FAIL = "<\x01\x01MACRO_HAS_NO_VALUE\x01\01>" @@ -154,52 +154,6 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware): """Redraw the window""" self.ui.redraw_window() - def linemode(self, mode, directory=None, depth=0): - """ - Change what is displayed as a filename. - - - "mode" may be: "filename", "permissions", "metatitle", the mode - "normal" is mapped to "filename". - - "directory" specifies the directory. None means the current directory - - "depth" specifies the recursion depth - """ - - if mode == "normal": - mode = DEFAULT_LINEMODE - - if mode not in POSSIBLE_LINEMODES: - self.notify("Unhandled linemode: `%s'" % mode, bad=True) - return - - if directory is None: - directory = self.fm.thisdir - - directories = set([directory]) - bucket = set() - - current_depth = 0 - - while True: - if current_depth >= depth: - for direct in directories: - direct._set_linemode_of_children(mode) - break - - else: - for direct in directories: - direct._set_linemode_of_children(mode) - for file_ in direct.files: - if file_.is_directory: - bucket.add(file_) - - directories, bucket = bucket, directories - bucket.clear() - current_depth += 1 - - # Ask the browsercolumns to redraw - for col in self.ui.browser.columns: - col.need_redraw = True - def open_console(self, string='', prompt=None, position=None): """Open the console""" self.change_mode('normal') @@ -258,12 +212,12 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware): macros['rangerdir'] = ranger.RANGERDIR if self.fm.thisfile: - macros['f'] = self.fm.thisfile.basename + macros['f'] = self.fm.thisfile.relative_path else: macros['f'] = MACRO_FAIL if self.fm.thistab.get_selection: - macros['s'] = [fl.basename for fl in self.fm.thistab.get_selection()] + macros['s'] = [fl.relative_path for fl in self.fm.thistab.get_selection()] else: macros['s'] = MACRO_FAIL @@ -273,7 +227,7 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware): macros['c'] = MACRO_FAIL if self.fm.thisdir.files: - macros['t'] = [fl.basename for fl in self.fm.thisdir.files + macros['t'] = [fl.relative_path for fl in self.fm.thisdir.files if fl.realpath in (self.fm.tags or [])] else: macros['t'] = MACRO_FAIL @@ -1290,7 +1244,7 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware): def mkdir(self, name): try: - os.mkdirs(os.path.join(self.thisdir.path, name)) + os.makedirs(os.path.join(self.thisdir.path, name)) except OSError as err: self.notify(err) diff --git a/ranger/core/linemode.py b/ranger/core/linemode.py new file mode 100644 index 00000000..166829cf --- /dev/null +++ b/ranger/core/linemode.py @@ -0,0 +1,82 @@ +# -*- coding: utf-8 -*- + +from abc import * + +DEFAULT_LINEMODE = "filename" + + +class LinemodeBase(object): + """Supplies the file line contents for BrowserColumn. + + Attributes: + name (required!) - Name by which the linemode is referred to by the user + + uses_metadata - True if metadata should to be loaded for this linemode + + required_metadata - + If any of these metadata fields are absent, fall back to + the default linemode + """ + __metaclass__ = ABCMeta + + uses_metadata = False + required_metadata = [] + + name = abstractproperty() + + @abstractmethod + def filetitle(self, file, metadata): + """The left-aligned part of the line.""" + raise NotImplementedError + + def infostring(self, file, metadata): + """The right-aligned part of the line. + + If `NotImplementedError' is raised (e.g. this method is just + not implemented in the actual linemode), the caller should + provide its own implementation, which in this case means + displaying the hardlink count of the directories. Useful + because only the caller (BrowserColumn) possesses the data + necessary to display that information. + + """ + raise NotImplementedError + + +class DefaultLinemode(LinemodeBase): + name = "filename" + + def filetitle(self, file, metadata): + return file.relative_path + + +class TitleLinemode(LinemodeBase): + name = "metatitle" + uses_metadata = True + required_metadata = ["title"] + + def filetitle(self, file, metadata): + name = metadata.title or file.basename + if metadata.year: + return "%s - %s" % (metadata.year, name) + else: + return name + + def infostring(self, file, metadata): + if metadata.authors: + authorstring = metadata.authors + if ',' in authorstring: + authorstring = authorstring[0:authorstring.find(",")] + return authorstring + return "" + + +class PermissionsLinemode(LinemodeBase): + name = "permissions" + + def filetitle(self, file, metadata): + return "%s %s %s %s" % (file.get_permission_string(), + file.user, file.group, file.relative_path) + + def infostring(self, file, metadata): + return "" diff --git a/ranger/gui/widgets/browsercolumn.py b/ranger/gui/widgets/browsercolumn.py index 564e9a5f..0340b565 100644 --- a/ranger/gui/widgets/browsercolumn.py +++ b/ranger/gui/widgets/browsercolumn.py @@ -12,6 +12,8 @@ from . import Widget from .pager import Pager from ranger.ext.widestring import WideString +from ranger.core import linemode + from ranger.gui.color import * class BrowserColumn(Pager): @@ -250,38 +252,25 @@ class BrowserColumn(Pager): # Extract linemode-related information from the drawn object metadata = None - use_linemode = drawn._linemode - if use_linemode == "metatitle": + current_linemode = drawn.linemode_dict[drawn._linemode] + if current_linemode.uses_metadata: metadata = self.fm.metadata.get_metadata(drawn.path) - if not metadata.title: - use_linemode = "filename" + if not all(getattr(metadata, tag) + for tag in current_linemode.required_metadata): + current_linemode = drawn.linemode_dict[linemode.DEFAULT_LINEMODE] metakey = hash(repr(sorted(metadata.items()))) if metadata else 0 key = (self.wid, selected_i == i, drawn.marked, self.main_column, drawn.path in copied, tagged_marker, drawn.infostring, drawn.vcsfilestatus, drawn.vcsremotestatus, self.fm.do_cut, - use_linemode, metakey) + current_linemode.name, metakey) if key in drawn.display_data: self.execute_curses_batch(line, drawn.display_data[key]) self.color_reset() continue - - # Deal with the line mode - text = "" - if use_linemode == "metatitle": - assert metadata.title, "Ensure that metadata.title is set!" - if metadata.year: - text = "%s - %s" % (metadata.year, metadata.title) - else: - text = metadata.title - if use_linemode == "filename": - text = drawn.drawn_basename - elif use_linemode == "permissions": - text = "%s %s %s %s" % (drawn.get_permission_string(), - drawn.user, drawn.group, drawn.drawn_basename) - + text = current_linemode.filetitle(drawn, metadata) if drawn.marked and (self.main_column or \ self.settings.display_tags_in_all_columns): @@ -312,14 +301,13 @@ class BrowserColumn(Pager): # info string infostring = [] infostringlen = 0 - if use_linemode == "filename": + try: + infostringdata = current_linemode.infostring(drawn, metadata) + if infostringdata: + infostring.append([" " + infostringdata + " ", + ["infostring"]]) + except NotImplementedError: infostring = self._draw_infostring_display(drawn, space) - elif use_linemode == "metatitle": - if metadata.authors: - authorstring = metadata.authors - if ',' in authorstring: - authorstring = authorstring[0:authorstring.find(",")] - infostring.append([" " + authorstring + " ", ["infostring"]]) if infostring: infostringlen = self._total_len(infostring) if space - infostringlen > 2: diff --git a/ranger/gui/widgets/titlebar.py b/ranger/gui/widgets/titlebar.py index 55cc25e6..52726b1c 100644 --- a/ranger/gui/widgets/titlebar.py +++ b/ranger/gui/widgets/titlebar.py @@ -117,7 +117,7 @@ class TitleBar(Widget): if self.fm.thisfile is not None and \ self.settings.show_selection_in_titlebar: - bar.add(self.fm.thisfile.drawn_basename, 'file') + bar.add(self.fm.thisfile.relative_path, 'file') def _get_right_part(self, bar): # TODO: fix that pressed keys are cut off when chaining CTRL keys |