diff options
40 files changed, 480 insertions, 390 deletions
diff --git a/CHANGELOG b/CHANGELOG index f1417fee..8078305d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -3,6 +3,9 @@ This log only documents changes between stable versions. From 1.2 on, odd minor version numbers (1.3, 1.5, 1.7,..) are assigned to the fresh git snapshots while stable versions will have even minor numbers. +1.2 -> 1.2.1: +* Fixed yy/pp bug when yanking multiple directories + 1.1.2 -> 1.2: * !!! Changed the default configuration directory to ~/.config/ranger !!! * Removed "Console Modes", each old mode is now a simple command 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 87376500..324cea55 100644 --- a/ranger/__init__.py +++ b/ranger/__init__.py @@ -13,11 +13,19 @@ # 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. -from os import path, environ -from ranger.ext.openstruct import OpenStruct -from sys import argv +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 +from ranger.core.main import main # Information __license__ = 'GPL3' @@ -26,52 +34,4 @@ __author__ = __maintainer__ = 'Roman Zimbelmann' __email__ = 'romanz@lavabit.com' # Constants -USAGE = '%prog [options] [path/filename]' -RANGERDIR = path.dirname(__file__) -LOGFILE = '/tmp/errorlog' -if 'XDG_CONFIG_HOME' in environ and environ['XDG_CONFIG_HOME']: - DEFAULT_CONFDIR = environ['XDG_CONFIG_HOME'] + '/ranger' -else: - DEFAULT_CONFDIR = '~/.config/ranger' -DEBUG = ('-d' in argv or '--debug' in argv) and ('--' not in argv or - (('-d' in argv and argv.index('-d') < argv.index('--')) or - ('--debug' in argv and argv.index('--debug') < argv.index('--')))) - -# Get some valid arguments before actually parsing them in main() -arg = OpenStruct(debug=DEBUG, clean=False, confdir=DEFAULT_CONFDIR, - mode=0, flags='', targets=[]) - -# Debugging features. 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 not DEBUG: 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 not DEBUG: return - import traceback - traceback.print_stack(file=open(LOGFILE, 'a')) - -# Handy functions -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 path.join(arg.confdir, *paths) - -def relpath(*paths): - """returns the path relative to rangers library directory""" - return path.join(RANGERDIR, *paths) - -# Clean up -del path, environ, OpenStruct, argv +RANGERDIR = os.path.dirname(__file__) 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/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 7604af12..008b846d 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, @@ -53,7 +51,7 @@ ALLOWED_SETTINGS = { } -class SettingObject(SignalDispatcher): +class SettingObject(SignalDispatcher, FileManagerAware): def __init__(self): SignalDispatcher.__init__(self) self.__dict__['_settings'] = dict() @@ -71,7 +69,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) @@ -128,35 +126,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 1493d84d..2e775848 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.core.loader import CommandLoader @@ -215,6 +216,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: @@ -468,6 +471,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: @@ -478,10 +482,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 d2aa00ec..cd1c73b6 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 destroy(self): debug = ranger.arg.debug if self.ui: @@ -118,6 +121,17 @@ class FM(Actions, SignalDispatcher): self.input_blocked = False return self.input_blocked + 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: @@ -135,14 +149,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(): @@ -167,5 +179,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 a9a18537..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,26 +73,8 @@ def parse_arguments(): return arg -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: @@ -114,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) @@ -155,96 +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' +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] - 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) - - 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: - try: - fm.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 - - -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 e33c2e9e..8272d0e0 100644 --- a/ranger/core/loader.py +++ b/ranger/core/loader.py @@ -16,20 +16,12 @@ from collections import deque from time import time, sleep from subprocess import Popen, PIPE +from time import time +from ranger.shared import FileManagerAware import math import os import select -from ranger.shared import FileManagerAware - -def status_generator(): - """Generate a rotating line which can be used as a throbber""" - while True: - yield '/' - yield '-' - yield '\\' - yield '|' - class Loadable(object): paused = False @@ -106,11 +98,13 @@ class CommandLoader(Loadable, FileManagerAware): 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.throbber_status = 0 self.status_generator = status_generator() self.rotate() self.old_item = None @@ -118,7 +112,9 @@ class Loader(FileManagerAware): 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..42118516 --- /dev/null +++ b/ranger/core/main.py @@ -0,0 +1,98 @@ +# 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.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..b91445a3 --- /dev/null +++ b/ranger/core/shared.py @@ -0,0 +1,73 @@ +# 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) + + 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 b61d5a5a..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) @@ -435,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/fsobject/directory.py b/ranger/fsobject/directory.py index 2ac56120..7578ad5f 100644 --- a/ranger/fsobject/directory.py +++ b/ranger/fsobject/directory.py @@ -24,7 +24,7 @@ from time import time from ranger.core.loader import Loadable 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 @@ -40,6 +40,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: @@ -77,6 +83,7 @@ class Directory(FileSystemObject, Accumulator, Loadable, 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, @@ -296,6 +303,10 @@ class Directory(FileSystemObject, Accumulator, Loadable, 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/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 3df45700..4ed348fd 100644 --- a/ranger/gui/curses_shortcuts.py +++ b/ranger/gui/curses_shortcuts.py @@ -16,7 +16,7 @@ import _curses from ranger.ext.iter_tools import flatten -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 d617e64e..dacc3920 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 2a92f313..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='')) @@ -241,7 +246,7 @@ class StatusBar(Widget): + str(len(target.files)) + ' ', base) if max_pos == 0: right.add('All', base, 'all') - if pos == 0: + elif pos == 0: right.add('Top', base, 'top') elif pos >= max_pos: right.add('Bot', base, 'bot') 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 |