diff options
author | hut <hut@lavabit.com> | 2010-10-08 19:46:41 +0200 |
---|---|---|
committer | hut <hut@lavabit.com> | 2010-10-08 19:46:41 +0200 |
commit | 8e0d73b53f164f949b55e268d9c76f0531b8c7d8 (patch) | |
tree | bf4f8ce8545f69d208b2b962ee2daa0c18c78947 | |
parent | d56375f95f91b9670a2353ee8d8883eca5c35f1e (diff) | |
parent | 3668fb2c70524a4f3e988cc4760e319500f9a4fb (diff) | |
download | ranger-8e0d73b53f164f949b55e268d9c76f0531b8c7d8.tar.gz |
Merge branch 'master' into preview
Conflicts: ranger/__init__.py ranger/core/helper.py ranger/gui/curses_shortcuts.py
44 files changed, 503 insertions, 418 deletions
diff --git a/README b/README index 2b44015e..44c03513 100644 --- a/README +++ b/README @@ -1,4 +1,4 @@ -Ranger v.1.2.1 +Ranger v.1.3.0 ============== Ranger is a free console file manager that gives you greater flexibility diff --git a/doc/ranger.1 b/doc/ranger.1 index 03cc3d56..968e601b 100644 --- a/doc/ranger.1 +++ b/doc/ranger.1 @@ -1,4 +1,4 @@ -.TH RANGER 1 ranger-1.2.1 +.TH RANGER 1 ranger-1.3.0 .SH NAME ranger - visual file manager .\"----------------------------------------- diff --git a/ranger.py b/ranger.py index 5652ba69..fbceab23 100755 --- a/ranger.py +++ b/ranger.py @@ -16,18 +16,17 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -# -# ---------------------------------------------------------------------------- -# -# An embedded shell script. It allows you to change the directory -# after you exit ranger by starting it with: source ranger ranger + +# Embed a script which allows you to change the directory of the parent shell +# after you exit ranger. Run it with the command: source ranger ranger """": if [ $1 ]; then + $@ --fail-unless-cd && if [ -z "$XDG_CONFIG_HOME" ]; then - "$@" --fail-unless-cd && cd "$(grep \^\' ~/.config/ranger/bookmarks | cut -b3-)" + cd "$(grep \^\' ~/.config/ranger/bookmarks | cut -b3-)" else - "$@" --fail-unless-cd && cd "$(grep \^\' "$XDG_CONFIG_HOME"/ranger/bookmarks | cut -b3-)" - fi + cd "$(grep \^\' "$XDG_CONFIG_HOME"/ranger/bookmarks | cut -b3-)" + fi && return 0 else echo "usage: source path/to/ranger.py path/to/ranger.py" fi @@ -36,21 +35,17 @@ return 1 import sys -# Redefine the docstring, since the previous one was hijacked to -# embed a shellscript. +# When using the --clean option, not even bytecode should be written. +# Need to find out if --clean is used as soon as possible. +try: + argv = sys.argv[0:sys.argv.index('--')] +except: + argv = sys.argv +sys.dont_write_bytecode = '-c' in argv or '--clean' in argv + +# Set the actual docstring __doc__ = """Ranger - file browser for the unix terminal""" -# Importing the main method may fail if the ranger directory -# is neither in the same directory as this file, nor in one of -# pythons global import paths. -try: - from ranger.__main__ import main -except ImportError: - if '-d' not in sys.argv and '--debug' not in sys.argv: - print("Can't import the main module.") - print("To run an uninstalled copy of ranger,") - print("launch ranger.py in the top directory.") - else: - raise -else: - sys.exit(main()) +# Start ranger +import ranger +sys.exit(ranger.main()) diff --git a/ranger/__init__.py b/ranger/__init__.py index 7a09dbe3..324cea55 100644 --- a/ranger/__init__.py +++ b/ranger/__init__.py @@ -13,69 +13,25 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -"""Ranger - file browser for the unix terminal""" +""" +Console-based visual file manager. + +Ranger is a file manager with an ncurses frontend written in Python. +It is designed to give you a broader overview of the file system by +displaying previews and backviews, dividing the screen into columns. + +The keybindings are similar to those of other console programs like +vim, mutt or ncmpcpp so the usage will be intuitive and efficient. +""" import os -import sys -from ranger.ext.openstruct import OpenStruct +from ranger.core.main import main +# Information __license__ = 'GPL3' -__version__ = '1.2.1' -__credits__ = 'Roman Zimbelmann' -__author__ = 'Roman Zimbelmann' -__maintainer__ = 'Roman Zimbelmann' +__version__ = '1.3.0' +__author__ = __maintainer__ = 'Roman Zimbelmann' __email__ = 'romanz@lavabit.com' -__copyright__ = """ -Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> -""" - -USAGE = '%prog [options] [path/filename]' -if 'XDG_CONFIG_HOME' in os.environ and os.environ['XDG_CONFIG_HOME']: - DEFAULT_CONFDIR = os.environ['XDG_CONFIG_HOME'] + '/ranger' -else: - DEFAULT_CONFDIR = '~/.config/ranger' +# Constants RANGERDIR = os.path.dirname(__file__) -LOGFILE = '/tmp/errorlog' -arg = OpenStruct( - debug=False, clean=False, confdir=DEFAULT_CONFDIR, - mode=0, flags='', targets=[]) - -#for python3-only versions, this could be replaced with: -#def log(*objects, start='ranger:', sep=' ', end='\n'): -# print(start, *objects, end=end, sep=sep, file=open(LOGFILE, 'a')) -def log(*objects, **keywords): - """ - Writes objects to a logfile (for the purpose of debugging only.) - Has the same arguments as print() in python3. - """ - if LOGFILE is None or arg.clean: - return - start = 'start' in keywords and keywords['start'] or 'ranger:' - sep = 'sep' in keywords and keywords['sep'] or ' ' - _file = 'file' in keywords and keywords['file'] or open(LOGFILE, 'a') - end = 'end' in keywords and keywords['end'] or '\n' - _file.write(sep.join(map(str, (start, ) + objects)) + end) - -def relpath_conf(*paths): - """returns the path relative to rangers configuration directory""" - if arg.clean: - assert 0, "Should not access relpath_conf in clean mode!" - else: - return os.path.join(arg.confdir, *paths) - -def relpath_script(*paths): - """ - Returns the path relative to where scripts are stored. - - It's relpath('data', *paths) with the --clean flag and - relpath_conf(*paths) without --clean. - """ - if arg.clean: - return relpath('data', *paths) - else: - return relpath_conf(*paths) - -def relpath(*paths): - """returns the path relative to rangers library directory""" - return os.path.join(RANGERDIR, *paths) diff --git a/ranger/api/apps.py b/ranger/api/apps.py index 91aae357..45432705 100644 --- a/ranger/api/apps.py +++ b/ranger/api/apps.py @@ -17,7 +17,7 @@ import os, sys, re from ranger.api import * from ranger.ext.iter_tools import flatten from ranger.ext.get_executables import get_executables -from ranger.shared import FileManagerAware +from ranger.core.shared import FileManagerAware class Applications(FileManagerAware): diff --git a/ranger/api/commands.py b/ranger/api/commands.py index f4e2ca76..a491c927 100644 --- a/ranger/api/commands.py +++ b/ranger/api/commands.py @@ -16,9 +16,12 @@ import os from collections import deque from ranger.api import * -from ranger.shared import FileManagerAware +from ranger.core.shared import FileManagerAware from ranger.ext.command_parser import LazyParser as parse +# A dummy that allows the generation of docstrings in ranger.defaults.commands +def alias(*_): + pass class CommandContainer(object): def __init__(self): diff --git a/ranger/api/keys.py b/ranger/api/keys.py index 5812de39..7ba05c73 100644 --- a/ranger/api/keys.py +++ b/ranger/api/keys.py @@ -23,6 +23,16 @@ from ranger.api import * from ranger.container.bookmarks import ALLOWED_KEYS as ALLOWED_BOOKMARK_KEYS from ranger.container.keymap import KeyMap, Direction, KeyMapWithDirections +# A dummy that allows the generation of docstrings in ranger.defaults.keys +class DummyKeyManager(object): + def get_context(self, _): + class Dummy(object): + def __getattr__(self, *_, **__): + return Dummy() + __call__ = __getattr__ + return Dummy() +keymanager = DummyKeyManager() + class Wrapper(object): def __init__(self, firstattr): self.__firstattr__ = firstattr diff --git a/ranger/api/options.py b/ranger/api/options.py index 93da4df1..ee947b39 100644 --- a/ranger/api/options.py +++ b/ranger/api/options.py @@ -17,4 +17,3 @@ import re from re import compile as regexp from ranger.api import * from ranger.gui import color -from ranger import relpath, relpath_conf, relpath_script diff --git a/ranger/colorschemes/jungle.py b/ranger/colorschemes/jungle.py index af10a404..f5e03c06 100644 --- a/ranger/colorschemes/jungle.py +++ b/ranger/colorschemes/jungle.py @@ -20,7 +20,7 @@ class Scheme(Default): def use(self, context): fg, bg, attr = Default.use(self, context) - if context.directory and not context.marked: + if context.directory and not context.marked and not context.link: fg = green if context.in_titlebar and context.hostname: diff --git a/ranger/colorschemes/snow.py b/ranger/colorschemes/snow.py index e89359c4..a449db87 100644 --- a/ranger/colorschemes/snow.py +++ b/ranger/colorschemes/snow.py @@ -32,7 +32,10 @@ class Snow(ColorScheme): if context.directory: attr |= bold - if context.highlight: + elif context.highlight: + attr |= reverse + + elif context.in_titlebar and context.tab and context.good: attr |= reverse return fg, bg, attr diff --git a/ranger/shared/settings.py b/ranger/container/settingobject.py index 41334ada..c8bd8b49 100644 --- a/ranger/shared/settings.py +++ b/ranger/container/settingobject.py @@ -13,11 +13,9 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -import sys from inspect import isfunction -import ranger from ranger.ext.signal_dispatcher import SignalDispatcher -from ranger.ext.openstruct import OpenStruct +from ranger.core.shared import FileManagerAware ALLOWED_SETTINGS = { 'autosave_bookmarks': bool, @@ -54,7 +52,7 @@ ALLOWED_SETTINGS = { } -class SettingObject(SignalDispatcher): +class SettingObject(SignalDispatcher, FileManagerAware): def __init__(self): SignalDispatcher.__init__(self) self.__dict__['_settings'] = dict() @@ -72,7 +70,7 @@ class SettingObject(SignalDispatcher): getattr(self, name) assert self._check_type(name, value) kws = dict(setting=name, value=value, - previous=self._settings[name]) + previous=self._settings[name], fm=self.fm) self.signal_emit('setopt', **kws) self.signal_emit('setopt.'+name, **kws) @@ -129,35 +127,3 @@ class SettingObject(SignalDispatcher): def _raw_set_with_signal(self, signal): self._settings[signal.setting] = signal.value - - -# -- globalize the settings -- -class SettingsAware(object): - settings = OpenStruct() - - @staticmethod - def _setup(): - settings = SettingObject() - - from ranger.gui.colorscheme import _colorscheme_name_to_class - settings.signal_bind('setopt.colorscheme', - _colorscheme_name_to_class, priority=1) - - if not ranger.arg.clean: - # overwrite single default options with custom options - sys.path[0:0] = [ranger.arg.confdir] - try: - import options as my_options - except ImportError: - pass - else: - settings._setting_sources.append(my_options) - del sys.path[0] - - from ranger.defaults import options as default_options - settings._setting_sources.append(default_options) - assert all(hasattr(default_options, setting) \ - for setting in ALLOWED_SETTINGS), \ - "Ensure that all options are defined in the defaults!" - - SettingsAware.settings = settings diff --git a/ranger/core/actions.py b/ranger/core/actions.py index b379d341..4b6a4ff4 100644 --- a/ranger/core/actions.py +++ b/ranger/core/actions.py @@ -26,7 +26,8 @@ from ranger.ext.direction import Direction from ranger.ext.relative_symlink import relative_symlink from ranger.ext.shell_escape import shell_quote from ranger import fsobject -from ranger.shared import FileManagerAware, EnvironmentAware, SettingsAware +from ranger.core.shared import FileManagerAware, EnvironmentAware, \ + SettingsAware from ranger.fsobject import File from ranger.ext import shutil_generatorized as shutil_g from ranger.core.loader import LoadableObject @@ -216,6 +217,8 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware): def move_parent(self, n): parent = self.env.at_level(-1) + if parent.pointer + n < 0: + n = 0 - parent.pointer try: self.env.enter_dir(parent.files[parent.pointer+n]) except IndexError: @@ -469,6 +472,7 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware): def enter_bookmark(self, key): """Enter the bookmark with the name <key>""" try: + self.bookmarks.update_if_outdated() destination = self.bookmarks[key] cwd = self.env.cwd if destination.path != cwd.path: @@ -479,10 +483,12 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware): def set_bookmark(self, key): """Set the bookmark with the name <key> to the current directory""" + self.bookmarks.update_if_outdated() self.bookmarks[key] = self.env.cwd def unset_bookmark(self, key): """Delete the bookmark with the name <key>""" + self.bookmarks.update_if_outdated() self.bookmarks.delete(key) def draw_bookmarks(self): diff --git a/ranger/core/environment.py b/ranger/core/environment.py index 61db8694..655054d7 100644 --- a/ranger/core/environment.py +++ b/ranger/core/environment.py @@ -22,7 +22,7 @@ from os.path import abspath, normpath, join, expanduser, isdir from ranger.fsobject import Directory from ranger.container import KeyBuffer, KeyManager, History from ranger.ext.signal_dispatcher import SignalDispatcher -from ranger.shared import SettingsAware +from ranger.core.shared import SettingsAware ALLOWED_CONTEXTS = ('browser', 'pager', 'embedded_pager', 'taskview', 'console') @@ -124,7 +124,7 @@ class Environment(SettingsAware, SignalDispatcher): """Delete unused directory objects""" for key in tuple(self.directories): value = self.directories[key] - if value.is_older_than(age): # and not value in self.pathway: + if value.is_older_than(age) and not value in self.pathway: del self.directories[key] if value.is_directory: value.files = None diff --git a/ranger/core/fm.py b/ranger/core/fm.py index dfad3425..e468e58c 100644 --- a/ranger/core/fm.py +++ b/ranger/core/fm.py @@ -19,6 +19,7 @@ The File Manager, putting the pieces together from time import time from collections import deque +import mimetypes import os import sys @@ -28,14 +29,12 @@ from ranger.container.tags import Tags from ranger.gui.defaultui import DefaultUI from ranger.container import Bookmarks from ranger.core.runner import Runner -from ranger import relpath_conf from ranger.ext.get_executables import get_executables from ranger.fsobject import Directory from ranger.ext.signal_dispatcher import SignalDispatcher from ranger import __version__ from ranger.core.loader import Loader -CTRL_C = 3 TICKS_BEFORE_COLLECTING_GARBAGE = 100 TIME_BEFORE_FILE_BECOMES_GARBAGE = 1200 @@ -70,7 +69,7 @@ class FM(Actions, SignalDispatcher): if ranger.arg.clean: bookmarkfile = None else: - bookmarkfile = relpath_conf('bookmarks') + bookmarkfile = self.confpath('bookmarks') self.bookmarks = Bookmarks( bookmarkfile=bookmarkfile, bookmarktype=Directory, @@ -81,7 +80,7 @@ class FM(Actions, SignalDispatcher): self.bookmarks = bookmarks if not ranger.arg.clean and self.tags is None: - self.tags = Tags(relpath_conf('tagged')) + self.tags = Tags(self.confpath('tagged')) if self.ui is None: self.ui = DefaultUI() @@ -94,6 +93,10 @@ class FM(Actions, SignalDispatcher): self.env.signal_bind('cd', self._update_current_tab) + mimetypes.knownfiles.append(os.path.expanduser('~/.mime.types')) + mimetypes.knownfiles.append(self.relpath('data/mime.types')) + self.mimetypes = mimetypes.MimeTypes() + def block_input(self, sec=0): self.input_blocked = sec != 0 self.input_blocked_until = time() + sec @@ -103,6 +106,23 @@ class FM(Actions, SignalDispatcher): self.input_blocked = False return self.input_blocked + def copy_config_files(self): + if not (ranger.arg.clean or os.path.exists(self.confpath('scope.sh'))): + import shutil + shutil.copy(self.relpath('data/scope.sh'), + self.confpath('scope.sh')) + + def confpath(self, *paths): + """returns the path relative to rangers configuration directory""" + if ranger.arg.clean: + assert 0, "Should not access relpath_conf in clean mode!" + else: + return os.path.join(ranger.arg.confdir, *paths) + + def relpath(self, *paths): + """returns the path relative to rangers library directory""" + return os.path.join(ranger.RANGERDIR, *paths) + def loop(self): """ The main loop consists of: @@ -120,14 +140,12 @@ class FM(Actions, SignalDispatcher): # for faster lookup: ui = self.ui throbber = ui.throbber - bookmarks = self.bookmarks loader = self.loader env = self.env has_throbber = hasattr(ui, 'throbber') try: while True: - bookmarks.update_if_outdated() loader.work() if has_throbber: if loader.has_work(): @@ -152,5 +170,5 @@ class FM(Actions, SignalDispatcher): raise SystemExit finally: - bookmarks.remember(env.cwd) - bookmarks.save() + self.bookmarks.remember(env.cwd) + self.bookmarks.save() diff --git a/ranger/__main__.py b/ranger/core/helper.py index 7559b43d..0ef0fc27 100644 --- a/ranger/__main__.py +++ b/ranger/core/helper.py @@ -1,6 +1,3 @@ -#!/usr/bin/env python -# coding=utf-8 -# # Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> # # This program is free software: you can redistribute it and/or modify @@ -16,20 +13,26 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -# Most import statements in this module are inside the functions. -# This enables more convenient exception handling in ranger.py -# (ImportError will imply that this module can't be found) -# convenient exception handling in ranger.py (ImportError) +"""Helper functions""" -import locale import os.path import sys +from ranger import * + +LOGFILE = '/tmp/errorlog' def parse_arguments(): """Parse the program arguments""" from optparse import OptionParser, SUPPRESS_HELP - from ranger import __version__, USAGE, DEFAULT_CONFDIR + from ranger import __version__ from ranger.ext.openstruct import OpenStruct + from os.path import expanduser + + if 'XDG_CONFIG_HOME' in os.environ and os.environ['XDG_CONFIG_HOME']: + default_confdir = os.environ['XDG_CONFIG_HOME'] + '/ranger' + else: + default_confdir = '~/.config/ranger' + usage = '%prog [options] [path/filename]' minor_version = __version__[2:] # assumes major version number is <10 if '.' in minor_version: @@ -40,7 +43,7 @@ def parse_arguments(): else: version_string = 'ranger ' + __version__ + version_tag - parser = OptionParser(usage=USAGE, version=version_string) + parser = OptionParser(usage=usage, version=version_string) parser.add_option('-d', '--debug', action='store_true', help="activate debug mode") @@ -52,7 +55,7 @@ def parse_arguments(): help="experimental: return the exit code 1 if ranger is" \ "used to run a file (with `ranger filename`)") parser.add_option('-r', '--confdir', type='string', - metavar='dir', default=DEFAULT_CONFDIR, + metavar='dir', default=default_confdir, help="the configuration directory. (%default)") parser.add_option('-m', '--mode', type='int', default=0, metavar='n', help="if a filename is supplied, run it with this mode") @@ -62,7 +65,7 @@ def parse_arguments(): options, positional = parser.parse_args() arg = OpenStruct(options.__dict__, targets=positional) - arg.confdir = os.path.expanduser(arg.confdir) + arg.confdir = expanduser(arg.confdir) if arg.fail_if_run: arg.fail_unless_cd = arg.fail_if_run del arg['fail_if_run'] @@ -70,33 +73,8 @@ def parse_arguments(): return arg -def copy_config_files(): - import shutil - from ranger import relpath, relpath_conf - if not os.path.exists(relpath_conf('scope.sh')): - shutil.copy(relpath('data', 'scope.sh'), relpath_conf('scope.sh')) - - -def allow_access_to_confdir(confdir, allow): - if allow: - try: - os.makedirs(confdir) - except OSError as err: - if err.errno != 17: # 17 means it already exists - print("This configuration directory could not be created:") - print(confdir) - print("To run ranger without the need for configuration") - print("files, use the --clean option.") - raise SystemExit() - if not confdir in sys.path: - sys.path[0:0] = [confdir] - else: - if sys.path[0] == confdir: - del sys.path[0] - - def load_settings(fm, clean): - import ranger.shared + import ranger.core.shared import ranger.api.commands import ranger.api.keys if not clean: @@ -121,24 +99,19 @@ def load_settings(fm, clean): from ranger.defaults import apps # Load keys - keymanager = ranger.shared.EnvironmentAware.env.keymanager + keymanager = ranger.core.shared.EnvironmentAware.env.keymanager ranger.api.keys.keymanager = keymanager from ranger.defaults import keys try: import keys except ImportError: pass - # COMPAT WARNING - if hasattr(keys, 'initialize_commands'): - print("Warning: the syntax for ~/.config/ranger/keys.py has changed.") - print("Your custom keys are not loaded."\ - " Please update your configuration.") allow_access_to_confdir(ranger.arg.confdir, False) else: comcont = ranger.api.commands.CommandContainer() ranger.api.commands.alias = comcont.alias from ranger.api import keys - keymanager = ranger.shared.EnvironmentAware.env.keymanager + keymanager = ranger.core.shared.EnvironmentAware.env.keymanager ranger.api.keys.keymanager = keymanager from ranger.defaults import commands, keys, apps comcont.load_commands_from_module(commands) @@ -162,99 +135,41 @@ def load_apps(fm, clean): fm.apps = apps.CustomApplications() -def main(): - """initialize objects and run the filemanager""" - try: - import curses - except ImportError as errormessage: - print(errormessage) - print('ranger requires the python curses module. Aborting.') - sys.exit(1) - - try: locale.setlocale(locale.LC_ALL, '') - except: print("Warning: Unable to set locale. Expect encoding problems.") - - if not 'SHELL' in os.environ: - os.environ['SHELL'] = 'bash' - - arg = parse_arguments() - if arg.clean: - sys.dont_write_bytecode = True - - # Need to decide whether to write bytecode or not before importing. - import ranger - from ranger.ext import curses_interrupt_handler - from ranger.core.runner import Runner - from ranger.core.fm import FM - from ranger.core.environment import Environment - from ranger.gui.defaultui import DefaultUI as UI - from ranger.fsobject import File - from ranger.shared import (EnvironmentAware, FileManagerAware, - SettingsAware) - - if not arg.debug: - curses_interrupt_handler.install_interrupt_handler() - ranger.arg = arg - - SettingsAware._setup() - - targets = arg.targets or ['.'] - target = targets[0] - if arg.targets: - if target.startswith('file://'): - target = target[7:] - if not os.access(target, os.F_OK): - print("File or directory doesn't exist: %s" % target) - sys.exit(1) - elif os.path.isfile(target): - def print_function(string): - print(string) - runner = Runner(logfunc=print_function) - load_apps(runner, ranger.arg.clean) - runner(files=[File(target)], mode=arg.mode, flags=arg.flags) - sys.exit(1 if arg.fail_unless_cd else 0) - - if not ranger.arg.clean: - copy_config_files() - - crash_traceback = None - try: - # Initialize objects - EnvironmentAware._assign(Environment(target)) - fm = FM() - fm.tabs = dict((n+1, os.path.abspath(path)) for n, path \ - in enumerate(targets[:9])) - load_settings(fm, ranger.arg.clean) - if fm.env.username == 'root': - fm.settings.preview_files = False - FileManagerAware._assign(fm) - fm.ui = UI() - - # Run the file manager - fm.initialize() - fm.ui.initialize() - fm.loop() - except Exception: - import traceback - crash_traceback = traceback.format_exc() - except SystemExit as error: - return error.args[0] - finally: +def allow_access_to_confdir(confdir, allow): + if allow: try: - fm.ui.destroy() - except (AttributeError, NameError): - pass - if crash_traceback: - print(crash_traceback) - print("Ranger crashed. " \ - "Please report this (including the traceback) at:") - print("http://savannah.nongnu.org/bugs/?group=ranger&func=additem") - return 1 - return 0 + os.makedirs(confdir) + except OSError as err: + if err.errno != 17: # 17 means it already exists + print("This configuration directory could not be created:") + print(confdir) + print("To run ranger without the need for configuration") + print("files, use the --clean option.") + raise SystemExit() + if not confdir in sys.path: + sys.path[0:0] = [confdir] + else: + if sys.path[0] == confdir: + del sys.path[0] -if __name__ == '__main__': - # The ranger directory can be executed directly, for example by typing - # python /usr/lib/python2.6/site-packages/ranger - sys.path.insert(0, os.path.dirname(sys.path[0])) - main() +# Debugging functions. These will be activated when run with --debug. +# Example usage in the code: +# import ranger; ranger.log("hello world") +def log(*objects, **keywords): + """ + Writes objects to a logfile (for the purpose of debugging only.) + Has the same arguments as print() in python3. + """ + if LOGFILE is None or not arg.debug or arg.clean: return + start = 'start' in keywords and keywords['start'] or 'ranger:' + sep = 'sep' in keywords and keywords['sep'] or ' ' + _file = 'file' in keywords and keywords['file'] or open(LOGFILE, 'a') + end = 'end' in keywords and keywords['end'] or '\n' + _file.write(sep.join(map(str, (start, ) + objects)) + end) + + +def log_traceback(): + if LOGFILE is None or not arg.debug or arg.clean: return + import traceback + traceback.print_stack(file=open(LOGFILE, 'a')) diff --git a/ranger/core/loader.py b/ranger/core/loader.py index 4f4424e4..7b074d60 100644 --- a/ranger/core/loader.py +++ b/ranger/core/loader.py @@ -15,17 +15,9 @@ from collections import deque from time import time -from ranger.shared import FileManagerAware +from ranger.core.shared import FileManagerAware import math -def status_generator(): - """Generate a rotating line which can be used as a throbber""" - while True: - yield '/' - yield '-' - yield '\\' - yield '|' - class LoadableObject(object): def __init__(self, gen, descr): self.load_generator = gen @@ -37,19 +29,22 @@ class LoadableObject(object): class Loader(FileManagerAware): seconds_of_work_time = 0.03 + throbber_chars = r'/-\|' def __init__(self): self.queue = deque() self.item = None self.load_generator = None - self.status_generator = status_generator() + self.throbber_status = 0 self.rotate() self.old_item = None def rotate(self): """Rotate the throbber""" # TODO: move all throbber logic to UI - self.status = next(self.status_generator) + self.throbber_status = \ + (self.throbber_status + 1) % len(self.throbber_chars) + self.status = self.throbber_chars[self.throbber_status] def add(self, obj): """ diff --git a/ranger/core/main.py b/ranger/core/main.py new file mode 100644 index 00000000..ed555a8d --- /dev/null +++ b/ranger/core/main.py @@ -0,0 +1,99 @@ +# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +""" +The main function responsible to initialize the FM object and stuff. +""" + +from ranger.core.helper import * + +def main(): + """initialize objects and run the filemanager""" + import locale + import os.path + import ranger + from ranger.ext import curses_interrupt_handler + from ranger.core.runner import Runner + from ranger.core.fm import FM + from ranger.core.environment import Environment + from ranger.gui.defaultui import DefaultUI as UI + from ranger.fsobject import File + from ranger.core.shared import (EnvironmentAware, FileManagerAware, + SettingsAware) + + try: + locale.setlocale(locale.LC_ALL, '') + except: + print("Warning: Unable to set locale. Expect encoding problems.") + + if not 'SHELL' in os.environ: + os.environ['SHELL'] = 'bash' + + ranger.arg = arg = parse_arguments() + SettingsAware._setup(clean=arg.clean) + + targets = arg.targets or ['.'] + target = targets[0] + if arg.targets: + if target.startswith('file://'): + target = target[7:] + if not os.access(target, os.F_OK): + print("File or directory doesn't exist: %s" % target) + sys.exit(1) + elif os.path.isfile(target): + def print_function(string): + print(string) + runner = Runner(logfunc=print_function) + load_apps(runner, arg.clean) + runner(files=[File(target)], mode=arg.mode, flags=arg.flags) + sys.exit(1 if arg.fail_unless_cd else 0) + + crash_traceback = None + try: + # Initialize objects + EnvironmentAware.env = Environment(target) + fm = FM() + fm.copy_config_files() + fm.tabs = dict((n+1, os.path.abspath(path)) for n, path \ + in enumerate(targets[:9])) + load_settings(fm, arg.clean) + if fm.env.username == 'root': + fm.settings.preview_files = False + FileManagerAware.fm = fm + fm.ui = UI() + if not arg.debug: + curses_interrupt_handler.install_interrupt_handler() + + # Run the file manager + fm.initialize() + fm.ui.initialize() + fm.loop() + except Exception: + import traceback + crash_traceback = traceback.format_exc() + except SystemExit as error: + return error.args[0] + finally: + try: + fm.ui.destroy() + except (AttributeError, NameError): + pass + if crash_traceback: + print(crash_traceback) + print("Ranger crashed. " \ + "Please report this (including the traceback) at:") + print("http://savannah.nongnu.org/bugs/?group=ranger&func=additem") + return 1 + return 0 diff --git a/ranger/core/shared.py b/ranger/core/shared.py new file mode 100644 index 00000000..175395ec --- /dev/null +++ b/ranger/core/shared.py @@ -0,0 +1,79 @@ +# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +"""Shared objects contain singleton variables which can be +inherited, essentially acting like global variables.""" + +from ranger.ext.lazy_property import lazy_property + +class Awareness(object): + pass + +class EnvironmentAware(Awareness): + # This creates an instance implicitly, mainly for unit tests + @lazy_property + def env(self): + from ranger.core.environment import Environment + return Environment(".") + +class FileManagerAware(Awareness): + # This creates an instance implicitly, mainly for unit tests + @lazy_property + def fm(self): + from ranger.core.fm import FM + return FM() + +class SettingsAware(Awareness): + # This creates an instance implicitly, mainly for unit tests + @lazy_property + def settings(self): + from ranger.ext.openstruct import OpenStruct + return OpenStruct() + + @staticmethod + def _setup(clean=True): + from ranger.container.settingobject import SettingObject, \ + ALLOWED_SETTINGS + import ranger + import sys + settings = SettingObject() + + from ranger.gui.colorscheme import _colorscheme_name_to_class + settings.signal_bind('setopt.colorscheme', + _colorscheme_name_to_class, priority=1) + + def postprocess_paths(signal): + import os + signal.value = os.path.expanduser(signal.value) + settings.signal_bind('setopt.preview_script', + postprocess_paths, priority=1) + + if not clean: + # add the custom options to the list of setting sources + sys.path[0:0] = [ranger.arg.confdir] + try: + import options as my_options + except ImportError: + pass + else: + settings._setting_sources.append(my_options) + del sys.path[0] + + from ranger.defaults import options as default_options + settings._setting_sources.append(default_options) + assert all(hasattr(default_options, setting) \ + for setting in ALLOWED_SETTINGS), \ + "Ensure that all options are defined in the defaults!" + SettingsAware.settings = settings diff --git a/ranger/defaults/apps.py b/ranger/defaults/apps.py index 47eff0c9..85abb2dc 100644 --- a/ranger/defaults/apps.py +++ b/ranger/defaults/apps.py @@ -87,9 +87,13 @@ class CustomApplications(Applications): if f.image: return self.either(c, 'feh', 'eog', 'mirage') - if f.document or f.filetype.startswith('text'): + if f.document or f.filetype.startswith('text') or f.size == 0: return self.either(c, 'editor') + # You can put this at the top of the function and mimeopen will + # always be used for every file. + return self.either(c, 'mimeopen') + # ----------------------------------------- application definitions # Note: Trivial applications are defined at the bottom @@ -181,6 +185,14 @@ class CustomApplications(Applications): if c.mode is 1: return tup("totem", "--fullscreen", *c) + @depends_on('mimeopen') + def app_mimeopen(self, c): + if c.mode is 0: + return tup("mimeopen", *c) + if c.mode is 1: + # Will ask user to select program + # aka "Open with..." + return tup("mimeopen", "--ask", *c) # Often a programs invocation is trivial. For example: # vim test.py readme.txt [...] diff --git a/ranger/defaults/commands.py b/ranger/defaults/commands.py index d3c05023..157b148c 100644 --- a/ranger/defaults/commands.py +++ b/ranger/defaults/commands.py @@ -73,9 +73,8 @@ class cd(Command): def execute(self): line = parse(self.line) - try: - destination = line.rest(1) - except IndexError: + destination = line.rest(1) + if not destination: destination = '~' if destination == '-': @@ -289,12 +288,13 @@ class find(Command): return self.count == 1 -class set(Command): +class _set(Command): """ :set <option name>=<python expression> Gives an option a new value. """ + name = 'set' # don't override the builtin set class def execute(self): line = parse(self.line) name = line.chunk(1) @@ -308,8 +308,6 @@ class set(Command): def tab(self): line = parse(self.line) - from ranger import log - log(line.parse_setting_line()) name, value, name_done = line.parse_setting_line() settings = self.fm.settings if not name: @@ -437,6 +435,42 @@ class mark(Command): self.fm.ui.need_redraw = True +class load_copy_buffer(Command): + """ + :load_copy_buffer + + Load the copy buffer from confdir/copy_buffer + """ + copy_buffer_filename = 'copy_buffer' + def execute(self): + from ranger.fsobject import File + from os.path import exists + try: + f = open(self.fm.confpath(self.copy_buffer_filename), 'r') + except: + return self.fm.notify("Cannot open file %s" % fname, bad=True) + self.fm.env.copy = set(File(g) \ + for g in f.read().split("\n") if exists(g)) + f.close() + self.fm.ui.redraw_main_column() + + +class save_copy_buffer(Command): + """ + :save_copy_buffer + + Save the copy buffer to confdir/copy_buffer + """ + copy_buffer_filename = 'copy_buffer' + def execute(self): + try: + f = open(self.fm.confpath(self.copy_buffer_filename), 'w') + except: + return self.fm.notify("Cannot open file %s" % fname, bad=True) + f.write("\n".join(f.path for f in self.fm.env.copy)) + f.close() + + class unmark(mark): """ :unmark <regexp> diff --git a/ranger/defaults/keys.py b/ranger/defaults/keys.py index 9f0c78cb..e62ae9ae 100644 --- a/ranger/defaults/keys.py +++ b/ranger/defaults/keys.py @@ -202,12 +202,12 @@ 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*ase*n*ame *m*time" \ - " *t*ype *r*everse")) +map('o<bg>', 'O<bg>', fm.hint("*s*ize *b*asename *m*time" \ + " *t*ype *r*everse *n*atural")) sort_dict = { 's': 'size', 'b': 'basename', - 'n': 'basename', + 'n': 'natural', 'm': 'mtime', 't': 'type', } diff --git a/ranger/defaults/options.py b/ranger/defaults/options.py index 5ce617bc..3c20c6fb 100644 --- a/ranger/defaults/options.py +++ b/ranger/defaults/options.py @@ -44,7 +44,7 @@ show_hidden = False # Ranger ships with scope.sh, a script that calls external programs (see # README for dependencies) to preview images, archives, etc. -preview_script = relpath_script('scope.sh') +preview_script = '~/.config/ranger/scope.sh' # Show dotfiles in the bookmark preview box? show_hidden_bookmarks = True diff --git a/ranger/fsobject/directory.py b/ranger/fsobject/directory.py index 9bdb4caa..e52b84d7 100644 --- a/ranger/fsobject/directory.py +++ b/ranger/fsobject/directory.py @@ -23,7 +23,7 @@ from time import time from ranger.ext.mount_path import mount_path from ranger.fsobject import BAD_INFO, File, FileSystemObject -from ranger.shared import SettingsAware +from ranger.core.shared import SettingsAware from ranger.ext.accumulator import Accumulator import ranger.fsobject @@ -39,6 +39,12 @@ def sort_by_directory(path): """returns 0 if path is a directory, otherwise 1 (for sorting)""" return 1 - path.is_directory +def sort_naturally(path): + return path.basename_natural + +def sort_naturally_icase(path): + return path.basename_natural_lower + def accept_file(fname, hidden_filter, name_filter): if hidden_filter: try: @@ -76,6 +82,7 @@ class Directory(FileSystemObject, Accumulator, SettingsAware): sort_dict = { 'basename': sort_by_basename, + 'natural': sort_naturally, 'size': lambda path: -path.size, 'mtime': lambda path: -(path.stat and path.stat.st_mtime or 1), 'type': lambda path: path.mimetype, @@ -295,6 +302,10 @@ class Directory(FileSystemObject, Accumulator, SettingsAware): 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 + self.files.sort(key = sort_func) if self.settings.sort_reverse: diff --git a/ranger/fsobject/file.py b/ranger/fsobject/file.py index fad22a1f..57d82b31 100644 --- a/ranger/fsobject/file.py +++ b/ranger/fsobject/file.py @@ -17,7 +17,6 @@ import re from ranger.fsobject import FileSystemObject from subprocess import Popen, PIPE from ranger.core.runner import devnull -from ranger import relpath N_FIRST_BYTES = 20 control_characters = set(chr(n) for n in diff --git a/ranger/fsobject/fsobject.py b/ranger/fsobject/fsobject.py index 4ca5a6c8..fd886275 100644 --- a/ranger/fsobject/fsobject.py +++ b/ranger/fsobject/fsobject.py @@ -17,17 +17,20 @@ CONTAINER_EXTENSIONS = ('7z', 'ace', 'ar', 'arc', 'bz', 'bz2', 'cab', 'cpio', 'cpt', 'dgc', 'dmg', 'gz', 'iso', 'jar', 'msi', 'pkg', 'rar', 'shar', 'tar', 'tbz', 'tgz', 'xar', 'xz', 'zip') +import re from os import access, listdir, lstat, readlink, stat from time import time from os.path import abspath, basename, dirname, realpath, splitext, extsep from . import BAD_INFO -from ranger.shared import MimeTypeAware, FileManagerAware +from ranger.core.shared import FileManagerAware from ranger.ext.shell_escape import shell_escape from ranger.ext.spawn import spawn from ranger.ext.lazy_property import lazy_property from ranger.ext.human_readable import human_readable -class FileSystemObject(MimeTypeAware, FileManagerAware): +_extract_number_re = re.compile(r'([^0-9]?)(\d*)') + +class FileSystemObject(FileManagerAware): (basename, basename_lower, dirname, @@ -67,8 +70,6 @@ class FileSystemObject(MimeTypeAware, FileManagerAware): def __init__(self, path, preload=None, path_is_abs=False): - MimeTypeAware.__init__(self) - if not path_is_abs: path = abspath(path) self.path = path @@ -98,6 +99,16 @@ class FileSystemObject(MimeTypeAware, FileManagerAware): except OSError: return "" + @lazy_property + def basename_natural(self): + return [int(c) if c.isdigit() else c or 0 \ + for c in _extract_number_re.split(self.basename)] + + @lazy_property + def basename_natural_lower(self): + return [int(c) if c.isdigit() else c or 0 \ + for c in _extract_number_re.split(self.basename_lower)] + def __str__(self): """returns a string containing the absolute path""" return str(self.path) @@ -110,7 +121,7 @@ class FileSystemObject(MimeTypeAware, FileManagerAware): basename = self.basename if self.extension == 'part': basename = basename[0:-5] - self._mimetype = self.mimetypes.guess_type(basename, False)[0] + self._mimetype = self.fm.mimetypes.guess_type(basename, False)[0] if self._mimetype is None: self._mimetype = '' diff --git a/ranger/gui/bar.py b/ranger/gui/bar.py index 03ed2f78..41cc8133 100644 --- a/ranger/gui/bar.py +++ b/ranger/gui/bar.py @@ -61,23 +61,27 @@ class Bar(object): if sumsize < wid: self.fill_gap(' ', (wid - sumsize), gapwidth=True) - def shrink_by_cutting(self, wid): + def shrink_from_the_left(self, wid): fixedsize = self.fixedsize() if wid < fixedsize: raise ValueError("Cannot shrink down to that size by cutting") - leftsize = self.left.sumsize() rightsize = self.right.sumsize() + oversize = leftsize + rightsize - wid + if oversize <= 0: + return self.fill_gap(' ', wid, gapwidth=False) nonfixed_items = self.left.nonfixed_items() - itemsize = int(float(wid - rightsize - fixedsize) / \ - (nonfixed_items + 1)) + 1 - + # Shrink items to a minimum size of 1 until there is enough room. for item in self.left: if not item.fixed: - item.cut_off_to(itemsize) - - self.fill_gap(' ', wid, gapwidth=False) + itemlen = len(item) + if oversize > itemlen - 1: + item.cut_off_to(1) + oversize -= (itemlen - 1) + else: + item.cut_off(oversize) + break def fill_gap(self, char, wid, gapwidth=False): del self.gap[:] @@ -127,8 +131,8 @@ class ColoredString(object): self.fixed = False def cut_off(self, n): - n = max(n, min(len(self.string), 1)) - self.string = self.string[:-n] + if n >= 1: + self.string = self.string[:-n] def cut_off_to(self, n): self.string = self.string[:n] diff --git a/ranger/gui/color.py b/ranger/gui/color.py index 69f67eba..58f0b38a 100644 --- a/ranger/gui/color.py +++ b/ranger/gui/color.py @@ -62,7 +62,7 @@ invisible = curses.A_INVIS default_colors = (default, default, normal) -def remove_attr(integer, attr): +def remove_attr(integer, attribute): """Remove an attribute from an integer""" if integer & attribute: return integer ^ attribute diff --git a/ranger/gui/colorscheme.py b/ranger/gui/colorscheme.py index 5b317acb..ae6aac0b 100644 --- a/ranger/gui/colorscheme.py +++ b/ranger/gui/colorscheme.py @@ -47,8 +47,8 @@ from curses import color_pair import ranger from ranger.gui.color import get_color from ranger.gui.context import Context -from ranger.__main__ import allow_access_to_confdir -from ranger.shared.settings import SettingsAware +from ranger.core.helper import allow_access_to_confdir +from ranger.core.shared import SettingsAware # ColorScheme is not SettingsAware but it will gain access # to the settings during the initialization. We can't import @@ -141,15 +141,15 @@ def _colorscheme_name_to_class(signal): # create ~/.config/ranger/colorschemes/__init__.py if it doesn't exist if usecustom: - if os.path.exists(ranger.relpath_conf('colorschemes')): - initpy = ranger.relpath_conf('colorschemes', '__init__.py') + if os.path.exists(signal.fm.confpath('colorschemes')): + initpy = signal.fm.confpath('colorschemes', '__init__.py') if not os.path.exists(initpy): open(initpy, 'a').close() if usecustom and \ - exists(ranger.relpath_conf('colorschemes', scheme_name)): + exists(signal.fm.confpath('colorschemes', scheme_name)): scheme_supermodule = 'colorschemes' - elif exists(ranger.relpath('colorschemes', scheme_name)): + elif exists(signal.fm.relpath('colorschemes', scheme_name)): scheme_supermodule = 'ranger.colorschemes' usecustom = False else: diff --git a/ranger/gui/context.py b/ranger/gui/context.py index 1e127a2e..20ce2817 100644 --- a/ranger/gui/context.py +++ b/ranger/gui/context.py @@ -22,7 +22,7 @@ CONTEXT_KEYS = ['reset', 'error', 'badinfo', 'selected', 'empty', 'main_column', 'message', 'background', 'good', 'bad', 'space', 'permissions', 'owner', 'group', 'mtime', 'nlink', - 'scroll', 'all', 'bot', 'top', 'percentage', + 'scroll', 'all', 'bot', 'top', 'percentage', 'filter', 'marked', 'tagged', 'tag_marker', 'cut', 'copied', 'help_markup', 'seperator', 'key', 'special', 'border', diff --git a/ranger/gui/curses_shortcuts.py b/ranger/gui/curses_shortcuts.py index 42f9dada..bae03adc 100644 --- a/ranger/gui/curses_shortcuts.py +++ b/ranger/gui/curses_shortcuts.py @@ -19,7 +19,7 @@ import _curses from ranger.ext.iter_tools import flatten from ranger.gui.color import get_color -from ranger.shared import SettingsAware +from ranger.core.shared import SettingsAware def ascii_only(string): # Some python versions have problems with invalid unicode strings. diff --git a/ranger/gui/displayable.py b/ranger/gui/displayable.py index 9ca72b13..70455b35 100644 --- a/ranger/gui/displayable.py +++ b/ranger/gui/displayable.py @@ -15,7 +15,7 @@ import _curses -from ranger.shared import FileManagerAware, EnvironmentAware +from ranger.core.shared import FileManagerAware, EnvironmentAware from ranger.gui.curses_shortcuts import CursesShortcuts class Displayable(EnvironmentAware, FileManagerAware, CursesShortcuts): diff --git a/ranger/gui/ui.py b/ranger/gui/ui.py index b0c1a352..2f27f11e 100644 --- a/ranger/gui/ui.py +++ b/ranger/gui/ui.py @@ -123,8 +123,8 @@ class UI(DisplayableContainer): event = MouseEvent(curses.getmouse()) except _curses.error: return - - DisplayableContainer.click(self, event) + if not self.console.visible: + DisplayableContainer.click(self, event) def handle_key(self, key): """Handles key input""" diff --git a/ranger/gui/widgets/browsercolumn.py b/ranger/gui/widgets/browsercolumn.py index 5585bccc..63323f65 100644 --- a/ranger/gui/widgets/browsercolumn.py +++ b/ranger/gui/widgets/browsercolumn.py @@ -28,6 +28,7 @@ class BrowserColumn(Pager): target = None tagged_marker = '*' last_redraw_time = -1 + ellipsis = "~" old_dir = None old_cf = None @@ -198,6 +199,8 @@ class BrowserColumn(Pager): self._set_scroll_begin() + copied = [f.path for f in self.env.copy] + selected_i = self.target.pointer for line in range(self.hei): i = line + self.scroll_begin @@ -207,11 +210,24 @@ class BrowserColumn(Pager): except IndexError: break + if self.display_infostring and drawn.infostring \ + and self.settings.display_size_in_main_column: + infostring = str(drawn.infostring) + " " + else: + infostring = "" + bad_info_color = None this_color = base_color + list(drawn.mimetype_tuple) text = drawn.basename tagged = self.fm.tags and drawn.realpath in self.fm.tags + space = self.wid - len(infostring) + if self.main_column: + space -= 2 + + if len(text) > space: + text = text[:space-1] + self.ellipsis + if i == selected_i: this_color.append('selected') @@ -241,7 +257,7 @@ class BrowserColumn(Pager): if drawn.is_device: this_color.append('device') - if self.env.copy and drawn in self.env.copy: + if drawn.path in copied: this_color.append('cut' if self.env.cut else 'copied') if drawn.is_link: @@ -257,14 +273,12 @@ class BrowserColumn(Pager): else: self.addnstr(line, 0, text, self.wid) - if self.display_infostring and drawn.infostring \ - and self.settings.display_size_in_main_column: - info = drawn.infostring - x = self.wid - 1 - len(info) - if info is BAD_INFO: - bad_info_color = (x, len(str(info))) + if infostring: + x = self.wid - 1 - len(infostring) + if infostring is BAD_INFO: + bad_info_color = (x, len(infostring)) if x > 0: - self.addstr(line, x, str(info) + ' ') + self.addstr(line, x, infostring) self.color_at(line, 0, self.wid, this_color) if bad_info_color: diff --git a/ranger/gui/widgets/browserview.py b/ranger/gui/widgets/browserview.py index c80e4885..142e7985 100644 --- a/ranger/gui/widgets/browserview.py +++ b/ranger/gui/widgets/browserview.py @@ -117,6 +117,7 @@ class BrowserView(Widget, DisplayableContainer): pass def _draw_bookmarks(self): + self.fm.bookmarks.update_if_outdated() self.color_reset() self.need_clear = True diff --git a/ranger/gui/widgets/console.py b/ranger/gui/widgets/console.py index 57264292..9f7d2405 100644 --- a/ranger/gui/widgets/console.py +++ b/ranger/gui/widgets/console.py @@ -23,7 +23,6 @@ import re from collections import deque from . import Widget -from ranger import log, relpath_conf from ranger.container.keymap import CommandArgs from ranger.ext.direction import Direction from ranger.ext.utfwidth import uwid, uchars @@ -50,7 +49,7 @@ class Console(Widget): self.history = History(self.settings.max_console_history_size) # load history from files if not ranger.arg.clean: - self.historypath = relpath_conf('history') + self.historypath = self.fm.confpath('history') try: f = open(self.historypath, 'r') except: diff --git a/ranger/gui/widgets/statusbar.py b/ranger/gui/widgets/statusbar.py index 3019930b..fb3e6b21 100644 --- a/ranger/gui/widgets/statusbar.py +++ b/ranger/gui/widgets/statusbar.py @@ -217,6 +217,11 @@ class StatusBar(Widget): max_pos = len(target) - self.column.hei base = 'scroll' + if self.env.cwd.filter: + right.add(" f=", base, 'filter') + right.add(repr(self.env.cwd.filter), base, 'filter') + right.add(", ", "space") + if target.marked_items: if len(target.marked_items) == len(target.files): right.add(human_readable(target.disk_usage, seperator='')) @@ -224,20 +229,24 @@ class StatusBar(Widget): right.add(human_readable(sum(f.size \ for f in target.marked_items \ if f.is_file), seperator='')) - right.add(" / " + str(len(target.marked_items))) + right.add("/" + str(len(target.marked_items))) else: - right.add(human_readable(target.disk_usage, seperator='')) - right.add(", ", "space") + right.add(human_readable(target.disk_usage, seperator='') + + " sum, ") right.add(human_readable(self.env.get_free_space( \ - target.mount_path), seperator='')) + target.mount_path), seperator='') + " free") right.add(" ", "space") if target.marked_items: # Indicate that there are marked files. Useful if you scroll # away and don't see them anymore. right.add('Mrk', base, 'marked') - elif max_pos > 0: - if pos == 0: + elif len(target.files): + right.add(str(target.pointer + 1) + '/' + + str(len(target.files)) + ' ', base) + if max_pos == 0: + right.add('All', base, 'all') + elif pos == 0: right.add('Top', base, 'top') elif pos >= max_pos: right.add('Bot', base, 'bot') @@ -245,7 +254,7 @@ class StatusBar(Widget): right.add('{0:0>.0f}%'.format(100.0 * pos / max_pos), base, 'percentage') else: - right.add('All', base, 'all') + right.add('0/0 All', base, 'all') def _print_result(self, result): self.win.move(0, 0) diff --git a/ranger/gui/widgets/titlebar.py b/ranger/gui/widgets/titlebar.py index 35e2e3d9..d87a0803 100644 --- a/ranger/gui/widgets/titlebar.py +++ b/ranger/gui/widgets/titlebar.py @@ -14,7 +14,7 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. """ -The titlebar is the widget at the top, giving you broad orientation. +The titlebar is the widget at the top, giving you broad overview. It displays the current path among other things. """ @@ -96,7 +96,7 @@ class TitleBar(Widget): self._get_left_part(bar) self._get_right_part(bar) try: - bar.shrink_by_cutting(self.wid) + bar.shrink_from_the_left(self.wid) except ValueError: bar.shrink_by_removing(self.wid) self.result = bar.combine() @@ -128,7 +128,7 @@ class TitleBar(Widget): bar.add('/', clr, fixed=True, directory=path) if self.env.cf is not None: - bar.add(self.env.cf.basename, 'file', fixed=True) + bar.add(self.env.cf.basename, 'file') def _get_right_part(self, bar): kb = str(self.env.keybuffer) @@ -144,10 +144,16 @@ class TitleBar(Widget): bar.addright(tabtext, 'tab', clr, fixed=True) def _get_tab_text(self, tabname): + result = ' ' + str(tabname) if self.settings.dirname_in_tabs: - return ' ' + str(tabname) + ":" + (basename(self.fm.tabs[tabname]) or '/') - else: - return ' ' + str(tabname) + dirname = basename(self.fm.tabs[tabname]) + if not dirname: + result += ":/" + elif len(dirname) > 15: + result += ":" + dirname[:14] + "~" + else: + result += ":" + dirname + return result def _print_result(self, result): self.win.move(0, 0) diff --git a/ranger/help/console.py b/ranger/help/console.py index f03491db..716740b9 100644 --- a/ranger/help/console.py +++ b/ranger/help/console.py @@ -12,6 +12,7 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. + """ 3. The Console diff --git a/ranger/help/invocation.py b/ranger/help/invocation.py index 26cffd4a..27ab5a67 100644 --- a/ranger/help/invocation.py +++ b/ranger/help/invocation.py @@ -12,6 +12,7 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. + """ 5. Ranger invocation diff --git a/ranger/shared/__init__.py b/ranger/shared/__init__.py deleted file mode 100644 index 048b9e7a..00000000 --- a/ranger/shared/__init__.py +++ /dev/null @@ -1,35 +0,0 @@ -# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. - -"""Shared objects contain singleton variables which can be -inherited, essentially acting like global variables.""" -class Awareness(object): - pass - -class EnvironmentAware(Awareness): - env = None - @staticmethod - def _assign(instance): - EnvironmentAware.env = instance - - -class FileManagerAware(Awareness): - fm = None - @staticmethod - def _assign(instance): - FileManagerAware.fm = instance - -from .mimetype import MimeTypeAware -from .settings import SettingsAware diff --git a/ranger/shared/mimetype.py b/ranger/shared/mimetype.py deleted file mode 100644 index da6fcd10..00000000 --- a/ranger/shared/mimetype.py +++ /dev/null @@ -1,26 +0,0 @@ -# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. - -from ranger import relpath -import mimetypes -import os.path - -class MimeTypeAware(object): - mimetypes = {} - def __init__(self): - MimeTypeAware.__init__ = lambda _: None # refuse multiple inits - mimetypes.knownfiles.append(os.path.expanduser('~/.mime.types')) - MimeTypeAware.mimetypes = mimetypes.MimeTypes() - MimeTypeAware.mimetypes.read(relpath('data/mime.types')) diff --git a/setup.py b/setup.py index 587b52c0..e63e28d2 100755 --- a/setup.py +++ b/setup.py @@ -21,6 +21,7 @@ if __name__ == '__main__': distutils.core.setup( name='ranger', description='Vim-like file manager', + long_description=ranger.__doc__, version=ranger.__version__, author=ranger.__author__, author_email=ranger.__email__, @@ -39,5 +40,4 @@ if __name__ == '__main__': 'ranger.fsobject', 'ranger.gui', 'ranger.gui.widgets', - 'ranger.help', - 'ranger.shared')) + 'ranger.help')) diff --git a/test/tc_directory.py b/test/tc_directory.py index 754253b3..a43ac89d 100644 --- a/test/tc_directory.py +++ b/test/tc_directory.py @@ -24,7 +24,7 @@ from os.path import realpath, join, dirname from ranger import fsobject from ranger.fsobject.file import File from ranger.fsobject.directory import Directory -from ranger.shared.settings import SettingsAware +from ranger.core.shared import SettingsAware SettingsAware._setup() @@ -49,7 +49,7 @@ class Test1(unittest.TestCase): import os # Check whether the directory has the correct list of filenames. dir = Directory(TESTDIR) - dir.load_content() + dir.load_content(schedule=False) self.assertTrue(dir.exists) self.assertEqual(type(dir.filenames), list) @@ -78,8 +78,8 @@ class Test1(unittest.TestCase): def test_nonexistant_dir(self): dir = Directory(NONEXISTANT_DIR) - dir.load_content() - + dir.load_content(schedule=False) + self.assertTrue(dir.content_loaded) self.assertFalse(dir.exists) self.assertFalse(dir.accessible) diff --git a/test/tc_loader.py b/test/tc_loader.py index 5a2e5a68..a679a629 100644 --- a/test/tc_loader.py +++ b/test/tc_loader.py @@ -24,7 +24,7 @@ import os from os.path import realpath, join, dirname from testlib import Fake -from ranger.shared import FileManagerAware, SettingsAware +from ranger.core.shared import FileManagerAware, SettingsAware from ranger.core.loader import Loader from ranger.fsobject import Directory, File from ranger.ext.openstruct import OpenStruct |