summary refs log tree commit diff stats
path: root/ranger/core
diff options
context:
space:
mode:
Diffstat (limited to 'ranger/core')
-rw-r--r--ranger/core/actions.py8
-rw-r--r--ranger/core/environment.py4
-rw-r--r--ranger/core/fm.py34
-rw-r--r--ranger/core/helper.py175
-rw-r--r--ranger/core/loader.py17
-rw-r--r--ranger/core/main.py99
-rw-r--r--ranger/core/shared.py79
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