diff options
Diffstat (limited to 'ranger/core')
-rw-r--r-- | ranger/core/actions.py | 8 | ||||
-rw-r--r-- | ranger/core/environment.py | 4 | ||||
-rw-r--r-- | ranger/core/fm.py | 34 | ||||
-rw-r--r-- | ranger/core/helper.py | 175 | ||||
-rw-r--r-- | ranger/core/loader.py | 17 | ||||
-rw-r--r-- | ranger/core/main.py | 99 | ||||
-rw-r--r-- | ranger/core/shared.py | 79 |
7 files changed, 394 insertions, 22 deletions
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/core/helper.py b/ranger/core/helper.py new file mode 100644 index 00000000..0ef0fc27 --- /dev/null +++ b/ranger/core/helper.py @@ -0,0 +1,175 @@ +# 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/>. + +"""Helper functions""" + +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__ + 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: + minor_version = minor_version[:minor_version.find('.')] + version_tag = ' (stable)' if int(minor_version) % 2 == 0 else ' (testing)' + if __version__.endswith('.0'): + version_string = 'ranger ' + __version__[:-2] + version_tag + else: + version_string = 'ranger ' + __version__ + version_tag + + parser = OptionParser(usage=usage, version=version_string) + + parser.add_option('-d', '--debug', action='store_true', + help="activate debug mode") + parser.add_option('-c', '--clean', action='store_true', + help="don't touch/require any config files. ") + parser.add_option('--fail-if-run', action='store_true', # COMPAT + help=SUPPRESS_HELP) + parser.add_option('--fail-unless-cd', action='store_true', + 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, + 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") + parser.add_option('-f', '--flags', type='string', default='', + metavar='string', + help="if a filename is supplied, run it with these flags.") + + options, positional = parser.parse_args() + arg = OpenStruct(options.__dict__, targets=positional) + arg.confdir = expanduser(arg.confdir) + if arg.fail_if_run: + arg.fail_unless_cd = arg.fail_if_run + del arg['fail_if_run'] + + return arg + + +def load_settings(fm, clean): + import ranger.core.shared + import ranger.api.commands + import ranger.api.keys + if not clean: + allow_access_to_confdir(ranger.arg.confdir, True) + + # Load commands + comcont = ranger.api.commands.CommandContainer() + ranger.api.commands.alias = comcont.alias + try: + import commands + comcont.load_commands_from_module(commands) + except ImportError: + pass + from ranger.defaults import commands + comcont.load_commands_from_module(commands) + commands = comcont + + # Load apps + try: + import apps + except ImportError: + from ranger.defaults import apps + + # Load keys + keymanager = ranger.core.shared.EnvironmentAware.env.keymanager + ranger.api.keys.keymanager = keymanager + from ranger.defaults import keys + try: + import keys + except ImportError: + pass + 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.core.shared.EnvironmentAware.env.keymanager + ranger.api.keys.keymanager = keymanager + from ranger.defaults import commands, keys, apps + comcont.load_commands_from_module(commands) + commands = comcont + fm.commands = commands + fm.keys = keys + fm.apps = apps.CustomApplications() + + +def load_apps(fm, clean): + import ranger + if not clean: + allow_access_to_confdir(ranger.arg.confdir, True) + try: + import apps + except ImportError: + from ranger.defaults import apps + allow_access_to_confdir(ranger.arg.confdir, False) + else: + from ranger.defaults import apps + fm.apps = apps.CustomApplications() + + +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] + + +# 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 |