diff options
-rw-r--r-- | README | 162 | ||||
-rw-r--r-- | TODO | 1 | ||||
-rw-r--r-- | ranger/__main__.py | 12 | ||||
-rw-r--r-- | ranger/core/actions.py | 40 | ||||
-rw-r--r-- | ranger/core/environment.py | 4 | ||||
-rw-r--r-- | ranger/defaults/keys.py | 4 | ||||
-rw-r--r-- | ranger/fsobject/directory.py | 4 | ||||
-rw-r--r-- | ranger/fsobject/fsobject.py | 225 | ||||
-rw-r--r-- | ranger/gui/widgets/browsercolumn.py | 2 | ||||
-rw-r--r-- | ranger/gui/widgets/statusbar.py | 7 | ||||
-rw-r--r-- | ranger/help/movement.py | 2 | ||||
-rw-r--r-- | test/bm_loader.py | 1 |
12 files changed, 196 insertions, 268 deletions
diff --git a/README b/README index 96b7e53e..6e386635 100644 --- a/README +++ b/README @@ -1,154 +1,86 @@ Ranger v.1.0.4 ============== -Ranger +Ranger is a console file manager with fast and straightforward navigation. +By efficiently using the screen space, it gives you a broad overview of +your file system. Ranger's hotkeys are similar to those of other common +unix programs such as VIM, Emacs and Midnight Commander, though it's +controllable with Arrow Keys just fine. - A keeper, guardian, or soldier who ranges over a region - to protect the area or enforce the law. - -This is the filemanager Chuck Norris the Texas Ranger would use -if he had a computer with a unix-like operating system. (He doesn't -trust computers though and prefers to do the calculations himself.) - -After all, as a professional ranger, he needs a broad overview of his -territory, and the multi-column display of ranger provides just that, -rather than restricting you to the current directory only. -You can preview the content of the selected file or directory, copy or move -files around with the VIM-like commands dd and yy, execute predefined -applications when opening a file, etc... - -Everything is fully customizable and written in Python (2.6 and 3.1 -compatible) using curses for the text-based user interface. +The program is written in Python (2.6 or 3.1) and uses curses for the +text-based user interface. About ----- -* Author: Roman Zimbelmann -* Email: romanz@lavabit.com +* Author: Roman Zimbelmann <romanz@lavabit.com> * Website: http://savannah.nongnu.org/projects/ranger -* Git repo: http://git.savannah.gnu.org/cgit/ranger.git +* Dependencies: Unix-like OS, Python 2.6 or 3.1 +* License: GNU General Public License Version 3 * Version: 1.0.4 +* Download URL of the newest stable version: +http://git.savannah.gnu.org/cgit/ranger.git/snapshot/ranger-stable.tar.gz + +* Git Clone URL: +git clone http://git.sv.gnu.org/r/ranger.git + Features -------- -* Multi-column display +* Multi-column display (Miller Columns) * Preview of the selected file/directory * Common file operations (create/chmod/copy/delete/...) -* Quickly find files or text inside files * VIM-like console and hotkeys -* Open files in external programs -* Mouse support +* Automatically determine file types and run them with correct programs * Change the directory of your shell after exiting ranger -* Bookmarks - - -Dependencies ------------- - -* A Unix-like Operating System -* Python 2.6 or 3.1 -* Python curses module (often but not always included with Python) - - -Bugs and Feature Requests -------------------------- - -Report bugs and feature requests on savannah: - https://savannah.nongnu.org/bugs/?func=additem&group=ranger - -Alternatively you can send an email to romanz@lavabit.com. - -Please include as much relevant information as possible. -Using ranger with the --debug option will abort the program and -print tracebacks rather than a red message in the statusbar. -If this applies to you, please include such a traceback in your report. +* Tabs, Bookmarks, Mouse support Getting Started --------------- -If you just want to check out ranger without installing it, type - - ./ranger.py --clean - -in the top directory of ranger. By using the --clean switch, it will -leave no trace on your system whatsoever. - -To properly install it, follow the instructions in the INSTALL file, -then type: +Ranger can be started without installing. Just run the executable (in +a terminal.) The switch "--clean" will prevent it from creating or +accessing configuration files. - ranger +Follow the instructions in the INSTALL file for installing ranger. -You should see 4 columns. The third is the directory where you are at -the moment. To the left, there are the directories above the current -working directory, and the column on the right is a preview of the selected -file/directory. +After starting ranger, you should see 4 columns. The third one is the main +column, the directory where you're currently at. To the left you see the +parent directories and to the right there's a preview of the object you're +pointing at. Now use the Arrow Keys to navigate, Enter to open a file +or type Q to quit. -Now use the arrow keys to navigate, press enter to open a file. +To customize ranger, copy the files from ranger/defaults/ to ~/.ranger/ +and modify them according to your wishes. -A list of commands with short descriptions can be viewed by -pressing "?" inside the program and following the instructions. -The file ranger/defaults/keys.py contains all key combinations, so that's -another place you may want to check out. - - -Opening Files with Ranger -------------------------- - -If you use the same applications like me, you'll be able to open -files by pressing the right arrow key. If not, you will have to -specify them in ranger/defaults/apps.py. It's explained -in the docstrings how exactly to do that. - -Once you've set up your applications, you can also use ranger to -open files from the shell: - ranger blabla.pdf - - -Customizing Ranger ------------------- -The file ranger/defaults/options.py contains most of the options. -apps.py defines how files are run, keys.py defines keybindings. +Troubleshooting, Getting Help +----------------------------- -The files in ranger/defaults/ can be copied into ~/.ranger/ for per-user -modifications. Colorschemes can be placed in ~/.ranger/colorschemes. +If you encounter an error, try running ranger with --debug. This will +sometimes display more detailed information about the error. Also, try +deactivating optimization: -The configuration files should be self-explanatory. If you need more -information, check out the source code. +PYTHONOPTIMIZE="" ranger --debug -Also, see the file HACKING for more detailed instructions on -modifying the program. +Report bugs on savannah: (please include as much information as possible) +http://savannah.nongnu.org/bugs/?func=additem&group=ranger +Ask questions on the mailing list: +http://lists.nongnu.org/mailman/listinfo/ranger-users -Roadmap -------- -Short term: - -* Performance improvements everywhere -* Simplification of the code - -Long term: - -* A plugin system -* Separate ranger into multiple programs: - 1. One daemon running in the background for slow IO operations - 2. A file launcher (ideally an already existing one) - 3. The actual program containing unseparable parts - - -Tips ----- +Further Reading +--------------- -Change the directory of your parent shell when you exit ranger: +Check the man page for information on common features and hotkeys. -ranger() { - command ranger --fail-if-run $@ && - cd "$(grep \^\' ~/.ranger/bookmarks | cut -b3-)" -} +The most detailed manual is accessible by pressing "?" from inside ranger. +It is also available at ranger/help/, contained in the *.py files. -This can be put into your ~/.bashrc or something similar. +The file ranger/defaults/keys.py contains all key combinations, so that's +another place you may want to check out. diff --git a/TODO b/TODO index 775605e9..84ecc811 100644 --- a/TODO +++ b/TODO @@ -57,6 +57,7 @@ General ( ) #60 10/02/05 utf support improvable ( ) #91 10/05/12 in keys.py, fm.move(right=N) should run with mode=N ( ) #92 10/05/14 allow to enter the realpath of a directory + ( ) #93 10/05/15 pause after running program Bugs diff --git a/ranger/__main__.py b/ranger/__main__.py index 6d574693..c232b489 100644 --- a/ranger/__main__.py +++ b/ranger/__main__.py @@ -73,8 +73,9 @@ def allow_access_to_confdir(confdir, allow): def load_settings(fm, clean): - import ranger + import ranger.shared import ranger.api.commands + import ranger.api.keys if not clean: allow_access_to_confdir(ranger.arg.confdir, True) @@ -97,10 +98,8 @@ def load_settings(fm, clean): from ranger.defaults import apps # Load keys - from ranger import shared, api - from ranger.api import keys - keymanager = shared.EnvironmentAware.env.keymanager - keys.keymanager = keymanager + keymanager = ranger.shared.EnvironmentAware.env.keymanager + ranger.api.keys.keymanager = keymanager from ranger.defaults import keys try: import keys @@ -115,6 +114,9 @@ def load_settings(fm, clean): else: comcont = ranger.api.commands.CommandContainer() ranger.api.commands.alias = comcont.alias + from ranger.api import keys + keymanager = ranger.shared.EnvironmentAware.env.keymanager + ranger.api.keys.keymanager = keymanager from ranger.defaults import commands, keys, apps comcont.load_commands_from_module(commands) commands = comcont diff --git a/ranger/core/actions.py b/ranger/core/actions.py index 86d37915..c61ef338 100644 --- a/ranger/core/actions.py +++ b/ranger/core/actions.py @@ -108,9 +108,6 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware): self.move(to=1, percentage=True) # moves to 80% """ cwd = self.env.cwd - if not cwd or not cwd.accessible or not cwd.content_loaded: - return - direction = Direction(kw) if 'left' in direction or direction.left() > 0: steps = direction.left() @@ -121,25 +118,24 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware): except: return self.env.enter_dir(directory) - - elif 'right' in direction: - mode = 0 - if narg is not None: - mode = narg - cf = self.env.cf - selection = self.env.get_selection() - if not self.env.enter_dir(cf) and selection: - if self.execute_file(selection, mode=mode) is False: - self.open_console(cmode.OPEN_QUICK) - - elif direction.vertical(): - newpos = direction.move( - direction=direction.down(), - override=narg, - maximum=len(cwd), - current=cwd.pointer, - pagesize=self.ui.browser.hei) - cwd.move(to=newpos) + if cwd and cwd.accessible and cwd.content_loaded: + if 'right' in direction: + mode = 0 + if narg is not None: + mode = narg + cf = self.env.cf + selection = self.env.get_selection() + if not self.env.enter_dir(cf) and selection: + if self.execute_file(selection, mode=mode) is False: + self.open_console(cmode.OPEN_QUICK) + elif direction.vertical(): + newpos = direction.move( + direction=direction.down(), + override=narg, + maximum=len(cwd), + current=cwd.pointer, + pagesize=self.ui.browser.hei) + cwd.move(to=newpos) def move_parent(self, n): self.enter_dir('..') diff --git a/ranger/core/environment.py b/ranger/core/environment.py index 296ba108..bb6abb36 100644 --- a/ranger/core/environment.py +++ b/ranger/core/environment.py @@ -190,10 +190,6 @@ class Environment(SettingsAware, SignalDispatcher): self.cwd = new_cwd self.cwd.load_content_if_outdated() - if self.cwd.files: - for dir in self.cwd.files: - if dir.is_directory and dir.infostring == 'unknown': - dir.determine_infostring() # build the pathway, a tuple of directory objects which lie # on the path to the current directory. diff --git a/ranger/defaults/keys.py b/ranger/defaults/keys.py index b17e5e8f..979d77e5 100644 --- a/ranger/defaults/keys.py +++ b/ranger/defaults/keys.py @@ -184,7 +184,7 @@ map('du', fm.execute_command('du --max-depth=1 -h | less')) # -------------------------------------------------- toggle options map('z<bg>', fm.hint("[*cdfhimpPs*] show_*h*idden *p*review_files "\ "*P*review_dirs *f*ilter flush*i*nput *m*ouse")) -map('zh', fm.toggle_boolean_option('show_hidden')) +map('zh', '<C-h>', fm.toggle_boolean_option('show_hidden')) map('zp', fm.toggle_boolean_option('preview_files')) map('zP', fm.toggle_boolean_option('preview_directories')) map('zi', fm.toggle_boolean_option('flushinput')) @@ -280,7 +280,7 @@ map('um<bg>', fm.hint("delete which bookmark?")) # ---------------------------------------------------- change views map('i', fm.display_file()) -map('<C-P>', fm.display_log()) +map('W', fm.display_log()) map('?', fm.display_help()) map('w', lambda arg: arg.fm.ui.open_taskview()) diff --git a/ranger/fsobject/directory.py b/ranger/fsobject/directory.py index 60387e7b..930ecb70 100644 --- a/ranger/fsobject/directory.py +++ b/ranger/fsobject/directory.py @@ -231,7 +231,6 @@ class Directory(FileSystemObject, Accumulator, SettingsAware): self.cycle_list = None self.content_loaded = True - self.determine_infostring() self.last_update_time = time() self.correct_pointer() @@ -250,7 +249,8 @@ class Directory(FileSystemObject, Accumulator, SettingsAware): self.content_outdated = False if not self.loading: - self.load_once() + if not self.loaded: + self.load() if not self.accessible: self.content_loaded = True diff --git a/ranger/fsobject/fsobject.py b/ranger/fsobject/fsobject.py index 6bb8b6ec..afef48e4 100644 --- a/ranger/fsobject/fsobject.py +++ b/ranger/fsobject/fsobject.py @@ -13,12 +13,11 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -CONTAINER_EXTENSIONS = 'rar zip tar gz bz bz2 tgz 7z iso cab'.split() +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 stat -import os -from stat import S_ISLNK, S_ISCHR, S_ISBLK, S_ISSOCK, S_ISFIFO, \ - S_ISDIR, S_IXUSR +from os import access, listdir, lstat, readlink, stat from time import time from os.path import abspath, basename, dirname, realpath from . import BAD_INFO @@ -28,44 +27,46 @@ from ranger.ext.spawn import spawn from ranger.ext.human_readable import human_readable class FileSystemObject(MimeTypeAware, FileManagerAware): - is_file = False - is_directory = False - content_loaded = False - force_load = False - path = None - basename = None - basename_lower = None - _shell_escaped_basename = None - _filetype = None - dirname = None - extension = None - exists = False - accessible = False - marked = False - tagged = False - loaded = False - runnable = False - is_link = False - is_device = False - is_socket = False - is_fifo = False - readlink = None - stat = None - infostring = None - permissions = None - size = 0 - - last_used = None + (_filetype, + _shell_escaped_basename, + basename, + basename_lower, + dirname, + extension, + infostring, + last_used, + path, + permissions, + stat) = (None,) * 11 + + (content_loaded, + force_load, + + is_device, + is_directory, + is_file, + is_fifo, + is_link, + is_socket, + + accessible, + exists, + loaded, + marked, + runnable, + stopped, + tagged, + + audio, + container, + document, + image, + media, + video) = (False,) * 21 - stopped = False - - video = False - image = False - audio = False - media = False - document = False - container = False mimetype_tuple = () + size = 0 + def __init__(self, path, preload=None, path_is_abs=False): MimeTypeAware.__init__(self) @@ -167,33 +168,6 @@ class FileSystemObject(MimeTypeAware, FileManagerAware): """Called by directory.mark_item() and similar functions""" self.marked = bool(boolean) - def determine_infostring(self): - self.size = 0 - if self.is_device: - self.infostring = 'dev' - elif self.is_fifo: - self.infostring = 'fifo' - elif self.is_socket: - self.infostring = 'sock' - elif self.is_directory: - try: - self.size = len(os.listdir(self.path)) # bite me - except OSError: - self.infostring = BAD_INFO - self.accessible = False - else: - self.infostring = ' %d' % self.size - self.accessible = True - self.runnable = True - elif self.is_file: - if self.stat: - self.size = self.stat.st_size - self.infostring = ' ' + human_readable(self.size) - else: - self.infostring = BAD_INFO - if self.is_link: - self.infostring = '->' + self.infostring - def load(self): """ reads useful information about the filesystem-object from the @@ -202,82 +176,104 @@ class FileSystemObject(MimeTypeAware, FileManagerAware): self.loaded = True - # Get the stat object, either from preload or from os.[l]stat - self.stat = None + # Get the stat object, either from preload or from [l]stat + new_stat = None + path = self.path if self.preload: - self.stat = self.preload[1] - self.is_link = S_ISLNK(self.stat.st_mode) - if self.is_link: - self.stat = self.preload[0] + new_stat = self.preload[1] + is_link = (new_stat.st_mode & 0o120000) == 0o120000 + if is_link: + new_stat = self.preload[0] self.preload = None else: try: - self.stat = os.lstat(self.path) + new_stat = lstat(path) + is_link = (new_stat.st_mode & 0o120000) == 0o120000 + if is_link: + new_stat = stat(path) except: pass - else: - self.is_link = S_ISLNK(self.stat.st_mode) - if self.is_link: - try: - self.stat = os.stat(self.path) - except: - pass # Set some attributes - if self.stat: - mode = self.stat.st_mode - self.is_device = bool(S_ISCHR(mode) or S_ISBLK(mode)) - self.is_socket = bool(S_ISSOCK(mode)) - self.is_fifo = bool(S_ISFIFO(mode)) + if new_stat: + mode = new_stat.st_mode self.accessible = True - if os.access(self.path, os.F_OK): + self.is_device = (mode & 0o060000) == 0o060000 + self.is_fifo = (mode & 0o010000) == 0o010000 + self.is_link = is_link + self.is_socket = (mode & 0o140000) == 0o140000 + if access(path, 0): self.exists = True - if S_ISDIR(mode): - self.runnable = bool(mode & S_IXUSR) + if self.is_directory: + self.runnable = (mode & 0o0100) == 0o0100 else: self.exists = False self.runnable = False - if self.is_link: - self.realpath = realpath(self.path) - self.readlink = os.readlink(self.path) + if is_link: + try: + self.realpath = realpath(path) + except OSError: + pass # it is impossible to get the link destination else: self.accessible = False - self.determine_infostring() + # Determine infostring + if self.is_file: + if new_stat: + self.size = new_stat.st_size + self.infostring = ' ' + human_readable(self.size) + else: + self.size = 0 + self.infostring = '?' + elif self.is_directory: + try: + self.size = len(listdir(path)) # bite me + except OSError: + self.size = 0 + self.infostring = '?' + self.accessible = False + else: + self.infostring = ' %d' % self.size + self.accessible = True + self.runnable = True + elif self.is_device: + self.size = 0 + self.infostring = 'dev' + elif self.is_fifo: + self.size = 0 + self.infostring = 'fifo' + elif self.is_socket: + self.size = 0 + self.infostring = 'sock' + if self.is_link: + self.infostring = '->' + self.infostring + + self.stat = new_stat def get_permission_string(self): if self.permissions is not None: return self.permissions - try: - mode = self.stat.st_mode - except: - return '----??----' - - if S_ISDIR(mode): + if self.is_directory: perms = ['d'] - elif S_ISLNK(mode): + elif self.is_link: perms = ['l'] else: perms = ['-'] - for who in ("USR", "GRP", "OTH"): - for what in "RWX": - if mode & getattr(stat, "S_I" + what + who): - perms.append(what.lower()) + mode = self.stat.st_mode + test = 0o0400 + while test: # will run 3 times because 0o400 >> 9 = 0 + for what in "rwx": + if mode & test: + perms.append(what) else: perms.append('-') + test >>= 1 self.permissions = ''.join(perms) return self.permissions - def load_once(self): - """calls load() if it has not been called at least once yet""" - if not self.loaded: - self.load() - return True - return False - def go(self): """enter the directory if the filemanager is running""" if self.fm: @@ -289,10 +285,11 @@ class FileSystemObject(MimeTypeAware, FileManagerAware): Calls load() if the currently cached information is outdated or nonexistant. """ - if self.load_once(): + if not self.loaded: + self.load() return True try: - real_mtime = os.lstat(self.path).st_mtime + real_mtime = lstat(self.path).st_mtime except OSError: real_mtime = None if self.stat: diff --git a/ranger/gui/widgets/browsercolumn.py b/ranger/gui/widgets/browsercolumn.py index 6e020e5f..fbacbccd 100644 --- a/ranger/gui/widgets/browsercolumn.py +++ b/ranger/gui/widgets/browsercolumn.py @@ -117,7 +117,7 @@ class BrowserColumn(Pager): pass else: - if self.level > 0: + if self.level > 0 and not direction: self.fm.move(right=0) return True diff --git a/ranger/gui/widgets/statusbar.py b/ranger/gui/widgets/statusbar.py index 752bfd6b..3019930b 100644 --- a/ranger/gui/widgets/statusbar.py +++ b/ranger/gui/widgets/statusbar.py @@ -23,7 +23,7 @@ such as the space used by all the files in this directory. from pwd import getpwuid from grp import getgrgid -from os import getuid +from os import getuid, readlink from time import time, strftime, localtime from ranger.ext.human_readable import human_readable @@ -164,7 +164,10 @@ class StatusBar(Widget): if target.is_link: how = target.exists and 'good' or 'bad' - dest = target.readlink if target.readlink is not None else '?' + try: + dest = readlink(target.path) + except: + dest = '?' left.add(' -> ' + dest, 'link', how) else: if self.settings.display_size_in_status_bar and target.infostring: diff --git a/ranger/help/movement.py b/ranger/help/movement.py index 194dd3a9..3287e9bb 100644 --- a/ranger/help/movement.py +++ b/ranger/help/movement.py @@ -188,7 +188,7 @@ Clicking into the preview window will usually run the file. |2?| ============================================================================== 1.8 Misc keys - ^P Display the message log + W Display the message log du Display the disk usage of the current directory cd Open the console with ":cd " cw Open the console with ":rename " diff --git a/test/bm_loader.py b/test/bm_loader.py index 4bfc2db9..745e6f3b 100644 --- a/test/bm_loader.py +++ b/test/bm_loader.py @@ -127,6 +127,7 @@ class benchmark_load(object): self.loader.work() +@skip class benchmark_raw_load(object): def __init__(self): SettingsAware.settings = Fake() |