diff options
author | hut <hut@lavabit.com> | 2011-10-11 22:34:21 +0200 |
---|---|---|
committer | hut <hut@lavabit.com> | 2011-10-11 22:34:21 +0200 |
commit | 30a35e6a7b95a06709590dc56afb96ea7ccecb62 (patch) | |
tree | c44a71f9502dd08e5f1917ceb0f0f33fe3efe57f | |
parent | 6524d00c5ce0d1169645a879fe581ee29d91b33c (diff) | |
parent | 6f695ffeafd1d2f1cb7c0c201b395582a1f7ce4e (diff) | |
download | ranger-30a35e6a7b95a06709590dc56afb96ea7ccecb62.tar.gz |
Merge branch 'master' into stable
Conflicts: ranger/defaults/options.py
119 files changed, 4552 insertions, 6410 deletions
diff --git a/CHANGELOG b/CHANGELOG index 5152c73e..fc2a3dfd 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,18 @@ This log documents changes between stable versions. +2011-10-10: Version 1.5.0 +* Change in commands.py syntax: + * Using parse(self.line) to parse the line is unnecessary now. + In most cases you can write self.foo() instead of parse(self.line).foo(). + For example, parse(self.line).rest(n) is now self.rest(n). + However, parse(self.line).chunk(n) has been renamed to self.arg(n). + * parse(self.line) + X is now self.firstpart + X + * New special attribute "resolve_macros" which decides whether strings + like %f should be expanded to the name of the current file, etc. + * New special attribute "escape_macros_for_shell" to toggle whether or + not macros should be escaped, so you can use them in other commands + than :shell, for example :edit %f + 2011-10-02: Version 1.4.4 * Added keys for chmod (like +ow for "chmod o+w", etc) * Added "c" flag for running files diff --git a/INSTALL b/INSTALL deleted file mode 100644 index 8ba89921..00000000 --- a/INSTALL +++ /dev/null @@ -1,23 +0,0 @@ -Installing -========== - -Use the package manager of your operating system to install ranger. - -To install ranger manually, use either: -sudo ./setup.py install --optimize=1 --record=install_log.txt - -or for short: -sudo make install - - -Uninstalling -============ - -Again, use your package manager to uninstall ranger. No other way for -automatically removing ranger is supported! - -However, if you installed ranger with the command above, all installed files -have been recorded to "install_log.txt". This information can be used to remove -ranger by hand, e.g.: - -cat install_log.txt | sed s/\^/\\// | xargs -d "\n" sudo rm -- diff --git a/Makefile b/Makefile index fd525721..8adae6a0 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> +# Copyright (C) 2009, 2010, 2011 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 @@ -18,16 +18,16 @@ VERSION = $(shell grep -m 1 -o '[0-9][0-9.]\+' README) SNAPSHOT_NAME ?= $(NAME)-$(VERSION)-$(shell git rev-parse HEAD | cut -b 1-8).tar.gz # Find suitable python version (need python >= 2.6 or 3.1): PYTHON ?= $(shell python -c 'import sys; sys.exit(sys.version < "2.6")' && \ - which python || which python3.1 || which python3 || which python2.6) + which python || which python3.2 || which python3.1 || which python3 || \ + which python2.6) SETUPOPTS ?= '--record=install_log.txt' DOCDIR ?= doc/pydoc DESTDIR ?= / PYOPTIMIZE ?= 1 -BMCOUNT ?= 5 # how often to run the benchmarks? CWD = $(shell pwd) -default: compile +default: test compile @echo 'Run `make options` for a list of all options' options: help @@ -38,12 +38,16 @@ options: help @echo 'DOCDIR = $(DOCDIR)' help: - @echo 'make install: Install $(NAME)' - @echo 'make doc: Create the pydoc documentation' - @echo 'make clean: Remove the compiled files (*.pyc, *.pyo)' + @echo 'make: Test and compile ranger.' + @echo 'make install: Install $(NAME)' + @echo 'make clean: Remove the compiled files (*.pyc, *.pyo)' + @echo 'make doc: Create the pydoc documentation' @echo 'make cleandoc: Remove the pydoc documentation' + @echo 'make man: Compile the manpage with "pod2man"' + @echo 'make manhtml: Compile the html manpage with "pod2html"' @echo 'make snapshot: Create a tar.gz of the current git revision' - @echo 'make test: Run all unittests.' + @echo 'make test: Test all testable modules of ranger' + @echo 'make todo: Look for TODO and XXX markers in the source code' install: $(PYTHON) setup.py install $(SETUPOPTS) \ @@ -53,7 +57,8 @@ compile: clean PYTHONOPTIMIZE=$(PYOPTIMIZE) $(PYTHON) -m compileall -q ranger clean: - find . -regex .\*.py[co]\$$ -exec rm -f -- {} \; + find ranger -regex .\*.py[co]\$$ -delete + find ranger -depth -name __pycache__ -type d -exec rm -rf -- {} \; doc: cleandoc mkdir -p $(DOCDIR) @@ -63,16 +68,26 @@ doc: cleandoc pydoc.writedocs("$(CWD)")' find . -name \*.html -exec sed -i 's|'$(CWD)'|../..|g' -- {} \; -cleandoc: - test -d $(DOCDIR) && rm -f -- $(DOCDIR)/*.html || true - test: - @$(PYTHON) test/all_tests.py 1 + @for FILE in $(shell grep -IHm 1 doctest -r ranger | cut -d: -f1); do \ + echo "Testing $$FILE..."; \ + PYTHONPATH=".:"$$PYTHONPATH ${PYTHON} $$FILE; \ + done + +man: + pod2man --stderr --center='ranger manual' --date='$(NAME)-$(VERSION)' \ + --release=$(shell date +%x) doc/ranger.pod doc/ranger.1 -bm: - @$(PYTHON) test/all_benchmarks.py $(BMCOUNT) +manhtml: + pod2html doc/ranger.pod --outfile=doc/ranger.1.html + +cleandoc: + test -d $(DOCDIR) && rm -f -- $(DOCDIR)/*.html || true snapshot: git archive --prefix='$(NAME)-$(VERSION)/' --format=tar HEAD | gzip > $(SNAPSHOT_NAME) -.PHONY: default options compile clean doc cleandoc test bm snapshot install +todo: + @grep --color -Ion '\(TODO\|XXX\).*' -r ranger + +.PHONY: default options compile clean doc cleandoc snapshot install man todo diff --git a/README b/README index e11a7b99..98efc97d 100644 --- a/README +++ b/README @@ -1,39 +1,33 @@ -Ranger v.1.4.4 +ranger v.1.5.0 ============== +ranger is a console file manager with VI key bindings. It provides a +minimalistic and nice curses interface with a view on the directory hierarchy. +The secondary task of ranger is to figure out which program you want to use to +open your files with. -Ranger is a free console file manager that gives you greater flexibility -and a good overview of your files without having to leave your *nix console. -It visualizes the directory tree in two dimensions: the directory hierarchy -on one, lists of files on the other, with a preview to the right so you know -where you'll be going. +This file describes ranger and how to get it to run. For instructions on the +usage, please read the man page. See doc/HACKING for development specific +information. For configuration, check the files in ranger/defaults/. They +are usually installed to /usr/lib/python*/site-packages/ranger/defaults/ +and can be obtained with ranger's --copy-config option. -The default keys are similar to those of Vim, Emacs and Midnight Commander, -though Ranger is easily controllable with just the arrow keys or the mouse. - -The program is written in Python (2.6 or 3.1) and uses curses for the -text-based user interface. +A note to packagers: Versions meant for packaging are listed in the changelog +on the website. About ----- +* Authors: Check the copyright notices in each source file +* License: GNU General Public License Version 3 -* Authors: Check the copyright notices in each source file -* Website: http://ranger.nongnu.org/ -* License: GNU General Public License Version 3 - -* Download URL of the newest stable version: -http://ranger.nongnu.org/ranger-stable.tar.gz - -* Git Clone URL: -git clone http://git.sv.gnu.org/r/ranger.git - -* Bug report: -https://savannah.nongnu.org/bugs/?group=ranger&func=additem +* Website: http://ranger.nongnu.org/ +* Download: http://ranger.nongnu.org/ranger-stable.tar.gz +* Bug reports: https://savannah.nongnu.org/bugs/?group=ranger&func=additem +* git clone http://git.sv.gnu.org/r/ranger.git Design Goals ------------ - * An easily maintainable file manager in a high level language * A quick way to switch directories and browse the file system * Keep it small but useful, do one thing and do it well @@ -42,10 +36,11 @@ Design Goals Features -------- - -* Multi-column display (Miller Columns) +* UTF-8 Support (if your python copy supports it) +* Multi-column display * Preview of the selected file/directory * Common file operations (create/chmod/copy/delete/...) +* Renaming multiple files at once * VIM-like console and hotkeys * Automatically determine file types and run them with correct programs * Change the directory of your shell after exiting ranger @@ -54,103 +49,47 @@ Features Dependencies ------------ - -* A *nix-like operating system -* Python 2.6 or Python 3.1 with the curses module +* Python (tested with version 2.6, 2.7, 3.1, 3.2) with support for ncurses + and (optionally) wide-unicode. +* A pager ("less" by default) Optional: -* The "file" program -* A pager ("less" by default) +* The "file" program for determining file types * The python module "chardet", in case of encoding detection problems -For scope.sh: (enhanced file previews) +Optional, for enhanced file previews (with "scope.sh"): * img2txt (from caca-utils) for previewing images * highlight for syntax highlighting of code * atool for previews of archives -* lynx or elinks for previews of html pages - - -Getting Started ---------------- - -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. - -Follow the instructions in the INSTALL file for installing ranger. - -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. +* lynx, w3m or elinks for previews of html pages +* pdftotext for pdf previews +* transmission-show for viewing bit-torrent information +* mediainfo for viewing information about media files -To customize ranger, copy the files from ranger/defaults/ to ~/.config/ranger/ -and modify them according to your wishes. +Installing +---------- +Use the package manager of your operating system to install ranger. +Note that ranger can be started without installing by simply running ranger.py. -Combine Ranger With Other Applications --------------------------------------- +To install ranger manually: + sudo make install -1. bash: +This translates roughly to: + sudo python setup.py install --optimize=1 --record=install_log.txt -Add this to your ~/.bashrc to use ranger as a directory switcher: +This also saves a list of all installed files to install_log.txt, which you can +use to uninstall ranger. -function ranger-cd { - ranger --choosedir=/tmp/chosen - if [ -f /tmp/chosen -a "$(cat /tmp/chosen)" != "$(pwd | tr -d "\n")" ]; then - cd "$(cat /tmp/chosen)" - fi - rm -f /tmp/chosen -} -bind '"\C-o":"ranger-cd\C-m"' -Now when you run ranger-cd, browse and quit, the directory of the bash process -you started ranger in will change to the last directroy in ranger. -To change back to the previous directory, you can type: cd - -Also, the line with "bind" will map the key <CTRL-O> to start ranger. - -2. vim: - -Add this function to your ~/.vimrc: - -fun Ranger() - silent !ranger --choosefile=/tmp/chosen - if filereadable('/tmp/chosen') - exec 'edit ' . system('cat /tmp/chosen') - call system('rm /tmp/chosen') - endif - redraw! -endfun -map <leader>r :call Ranger() - -This starts ranger when you type <leader>r (usually \r) and if you open a file -in ranger it will be opened in the original vim process. - - -Troubleshooting, Getting Help ------------------------------ - -If you encounter an error, try running ranger with --debug. This will -sometimes display more detailed information about the error. Also, try -deactivating optimization: - -PYTHONOPTIMIZE="" ranger --debug - -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 - - -Further Reading +Getting Started --------------- - -Check the man page for information on common features and hotkeys. - -The most detailed manual is accessible by pressing "?" from inside ranger. -It is also available at ranger/help/, contained in the *.py files. - -The file ranger/defaults/keys.py contains all key combinations, so that's -another place you may want to check out. +After starting ranger, you can use the Arrow Keys (or hjkl) to navigate, Enter +to open a file or type Q to quit. The third column shows a preview of the +current file. The second is the main column and the first shows the parent +directory. + +Ranger will automatically copy simple configuration files to ~/.config/ranger. +If you mess them up, just delete them and ranger will copy them again. Run +ranger with --dont-copy-config to disable this. Also check ranger/defaults/ +for the default configuration. diff --git a/doc/HACKING b/doc/HACKING index dd384758..f6d5d064 100644 --- a/doc/HACKING +++ b/doc/HACKING @@ -10,7 +10,7 @@ Coding Style http://www.python.org/dev/peps/pep-0008/ * Although this guide suggests otherwise, tabs are used for indentation of code and docstrings. In other documents (readme, etc), use spaces. -* Test the code with unit tests where it makes sense +* Test the code with "doctest" where it makes sense Patches @@ -35,29 +35,25 @@ ranger/fsobject/fsobject.py About the UI: ranger/gui/widgets/browsercolumn.py ranger/gui/widgets/browserview.py -ranger/gui/defaultui.py +ranger/gui/ui.py Common Changes -------------- * Change which files are previewed in the auto preview: -In ranger/gui/widget/browsercolumn.py +In ranger/fsobject/file.py the constant PREVIEW_BLACKLIST * Adding options: In ranger/defaults/options.py add the default value, like: my_option = True -In ranger/shared/settings.py +In ranger/container/settingobject.py add the name of your option to the constant ALLOWED_SETTINGS The setting is now accessible at self.settings.my_option, assuming <self> is a "SettingsAware" object. -* Changing commands, adding aliases: -ranger/defaults/commands.py -or ~/.config/ranger/commands.py - * Adding colorschemes: Copy ranger/colorschemes/default.py to ranger/colorschemes/myscheme.py and modify it according to your needs. Alternatively, mimic the jungle @@ -66,15 +62,6 @@ In ranger/defaults/options.py (or ~/.config/ranger/options.py), change colorscheme = 'default' to: colorscheme = 'myscheme' -* Change which files are considered to be "hidden": -In ranger/defaults/options.py -change the hidden_filter regular expression. - -* Change the key map: -Modify ranger/defaults/keys.py. This should be self-explanatory. -Check out ranger/core/actions.py for the most common actions, of course -you can also use your own functions. - * Change the file type => application associations: In ranger/defaults/apps.py modify the method app_default. @@ -88,8 +75,5 @@ Modify ranger/data/mime.types Version Numbering ----------------- -X.Y.Z, where: - -* X: Major version, milestone -* Y: Minor version, even number => stable version -* Z: Revision, may be omitted if zero +Three numbers; The first changes on a rewrite, the second changes when major +configuration incompatibilities occur and the third changes with each release. diff --git a/doc/TODO b/doc/TODO deleted file mode 100644 index 1577f97a..00000000 --- a/doc/TODO +++ /dev/null @@ -1,119 +0,0 @@ -Console - - (X) #0 09/12/06 console commands - (X) #1 09/12/06 quick find - (X) #2 09/12/06 open with - (X) #4 09/12/06 history for console - (X) #13 09/12/27 display docstring of a command - - -General - - (X) #5 09/12/06 move code from fm into objects - (X) #6 09/12/06 move main to __init__ - (X) #7 09/12/06 cooler titlebar - (X) #8 09/12/17 Add operations to modify files/directories - (X) #9 09/12/24 add a widget for managing running operations - (X) #10 09/12/24 sorting - (X) #11 09/12/27 filter - (X) #12 09/12/27 jump through the list in a specific order - (X) #14 09/12/29 make filelists inherit from pagers - (X) #15 09/12/29 better way of running processes!!~ - (X) #16 10/01/01 list of bookmarks - (X) #21 10/01/01 write help! - (X) #22 10/01/03 add getopt options to change flags/mode - (X) #29 10/01/06 add chmod command - (X) #30 10/01/06 add a way to create symlinks - (X) #32 10/01/08 place the (hidden) cursor to a meaningful position - (X) #34 10/01/09 display free disk space - (X) #35 10/01/09 display disk usage of files in current directory - ( ) #36 10/01/11 help coloring is terribly inefficient - (X) #37 10/01/13 better tab completion for OpenConsole - ( ) #38 10/01/16 searching in pager - (X) #39 10/01/17 flushinput not always good - (X) #42 10/01/17 memorize directory for `` when using :cd - (X) #43 10/01/18 internally treat the bookmarks ` and ' the same - ( ) #44 10/01/18 more error messages :P - (X) #47 10/01/19 less restricive auto preview - (X) #48 10/01/19 abbreviate commands with first unambiguous substring - ( ) #50 10/01/19 add more unit tests - ( ) #51 10/01/21 remove directory.marked_items ? - (X) #55 10/01/24 allow change of filename when pasting - you're given the choice between overwriting or appending a "_" - ( ) #56 10/01/30 warn before deleting mount points - ( ) #57 10/01/30 warn before deleting unseen marked files - (X) #58 10/02/04 change the title of the terminal - (X) #61 10/02/09 show sum of size of marked files - (X) #63 10/02/15 limit filesize in previews - ( ) #64 10/02/25 scroll in previews - (X) #66 10/02/28 explain how colorschemes work - (X) #70 10/03/14 mouse handler for titlebar - (X) #71 10/03/21 previews: black/whitelist + read file - (X) #79 10/04/08 tab number zero - ( ) #80 10/04/08 when closing tabs, avoid gaps? - (X) #81 10/04/15 system crash when previewing /proc/kcore with root permissions - (X) #83 10/04/19 better ways to mark files. eg by regexp, filetype,.. - ( ) #86 10/04/21 narg for move_parent - ( ) #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 - - (X) #17 10/01/01 why do bookmarks disappear sometimes? - (X) #18 10/01/01 fix notify widget (by adding a LogView?) - (X) #19 10/01/01 resizing after pressing g - (X) #23 10/01/04 stop dir loading with ^C -> wont load anymore - (X) #25 10/01/06 directories sometimes dont reload correctly - (X) #26 10/01/06 :delete on symlinks of directories fails - (X) #31 10/01/06 ^C breaks cd-after-exit by stopping sourced shell script - (X) #40 10/01/17 freeze with unavailable sshfs - Not rangers fault (?) - (X) #41 10/01/17 capital file extensions are not recognized - (X) #46 10/01/19 old username displayed after using su - (X) #49 10/01/19 fix unit tests :'( - (X) #52 10/01/23 special characters in tab completion - (X) #54 10/01/23 max_dirsize_for_autopreview not working - (X) #62 10/02/15 curs_set can raise an exception - (X) #65 10/02/16 "source ranger ranger some/file.txt" shouldn't cd after exit - (X) #67 10/03/08 terminal title in tty - (X) #69 10/03/11 tab-completion breaks with Apps subclass - (X) #73 10/03/21 when clicking on the first column, it goes 1x down - (X) #74 10/03/21 console doesn't scroll - (X) #78 10/03/31 broken preview when deleting all files in a directory - (X) #85 10/04/26 no automatic reload of directory after using :filter - (X) #87 10/05/10 files are not properly closed after previewing - ( ) #88 10/05/10 race conditions for data loading from FS - (X) #90 10/05/11 no link target for broken links - ( ) #94 10/05/26 "pressed keys" text cut off when chaining ctrl-XYZ keys - - -Ideas - - ( ) #20 10/01/01 use inotify to monitor filesystem changes - ( ) #24 10/01/06 progress bar - (X) #27 10/01/06 hide bookmarks in list which contain hidden dir - (X) #28 10/01/06 use regexp instead of string for searching - (X) #33 10/01/08 accelerate mousewheel speed - won't do this - (X) #45 10/01/18 hooks for events like setting changes - (X) #53 10/01/23 merge fm and environment - won't do this - (X) #68 10/03/10 threads, to seperate ui and loading - won't do this - ( ) #72 10/03/21 ranger daemon which does the slow io tasks - ( ) #75 10/03/28 navigate in history - (X) #76 10/03/28 save history between sessions - (X) #77 10/03/28 colorscheme overlay in options.py - ( ) #82 10/04/19 :s command for batch renaming - ( ) #84 10/04/25 use pygments for syntax highlighting - ( ) #89 10/05/10 branch view - - -Blocking - - ( ) #60 10/02/05 utf support improvable - (X) #81 10/04/15 system crash when previewing /proc/kcore with root permissions - diff --git a/doc/help b/doc/help deleted file mode 120000 index 09ad0c73..00000000 --- a/doc/help +++ /dev/null @@ -1 +0,0 @@ -../ranger/help \ No newline at end of file diff --git a/doc/ranger.1 b/doc/ranger.1 index 08776fc7..da98f718 100644 --- a/doc/ranger.1 +++ b/doc/ranger.1 @@ -1,235 +1,934 @@ -.TH RANGER 1 ranger-1.4.4 -.SH NAME -ranger - visual file manager -.\"----------------------------------------- -.SH SYNOPSIS -.B ranger -.R [OPTIONS] [FILE] -.\"----------------------------------------- -.SH DESCRIPTION -Ranger is a file manager with an ncurses frontend written in Python. -.P -It is designed to give you a broader overview of the file system by displaying -previews and backviews, dividing the screen into several columns. -The keybindings are similar to those of other console programs like -.BR vim ", " mutt " or " ncmpcpp -so the usage will be intuitive and efficient. -.\"----------------------------------------- -.SH OPTIONS -.TP ---version -Print the version and exit. -.TP --h, --help -Print a list of options and exit. -.TP --d, --debug -Activate the debug mode: Whenever an error occurs, ranger will exit and -print a full backtrace. The default behaviour is to merely print the -name of the exception in the statusbar/log and to try to keep running. -.TP --c, --clean -Activate the clean mode: Ranger will not access or create any configuration -files nor will it leave any traces on your system. This is useful when -your configuration is broken, when you want to avoid clutter, etc. -.TP ---choosefile=\fItargetfile\fR +.\" Automatically generated by Pod::Man 2.23 (Pod::Simple 3.14) +.\" +.\" Standard preamble: +.\" ======================================================================== +.de Sp \" Vertical space (when we can't use .PP) +.if t .sp .5v +.if n .sp +.. +.de Vb \" Begin verbatim text +.ft CW +.nf +.ne \\$1 +.. +.de Ve \" End verbatim text +.ft R +.fi +.. +.\" Set up some character translations and predefined strings. \*(-- will +.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left +.\" double quote, and \*(R" will give a right double quote. \*(C+ will +.\" give a nicer C++. Capital omega is used to do unbreakable dashes and +.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff, +.\" nothing in troff, for use with C<>. +.tr \(*W- +.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p' +.ie n \{\ +. ds -- \(*W- +. ds PI pi +. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch +. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch +. ds L" "" +. ds R" "" +. ds C` "" +. ds C' "" +'br\} +.el\{\ +. ds -- \|\(em\| +. ds PI \(*p +. ds L" `` +. ds R" '' +'br\} +.\" +.\" Escape single quotes in literal strings from groff's Unicode transform. +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.\" +.\" If the F register is turned on, we'll generate index entries on stderr for +.\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index +.\" entries marked with X<> in POD. Of course, you'll have to process the +.\" output yourself in some meaningful fashion. +.ie \nF \{\ +. de IX +. tm Index:\\$1\t\\n%\t"\\$2" +.. +. nr % 0 +. rr F +.\} +.el \{\ +. de IX +.. +.\} +.\" +.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2). +.\" Fear. Run. Save yourself. No user-serviceable parts. +. \" fudge factors for nroff and troff +.if n \{\ +. ds #H 0 +. ds #V .8m +. ds #F .3m +. ds #[ \f1 +. ds #] \fP +.\} +.if t \{\ +. ds #H ((1u-(\\\\n(.fu%2u))*.13m) +. ds #V .6m +. ds #F 0 +. ds #[ \& +. ds #] \& +.\} +. \" simple accents for nroff and troff +.if n \{\ +. ds ' \& +. ds ` \& +. ds ^ \& +. ds , \& +. ds ~ ~ +. ds / +.\} +.if t \{\ +. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u" +. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u' +. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u' +. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u' +. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u' +. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u' +.\} +. \" troff and (daisy-wheel) nroff accents +.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V' +.ds 8 \h'\*(#H'\(*b\h'-\*(#H' +.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#] +.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H' +.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u' +.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#] +.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#] +.ds ae a\h'-(\w'a'u*4/10)'e +.ds Ae A\h'-(\w'A'u*4/10)'E +. \" corrections for vroff +.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u' +.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u' +. \" for low resolution devices (crt and lpr) +.if \n(.H>23 .if \n(.V>19 \ +\{\ +. ds : e +. ds 8 ss +. ds o a +. ds d- d\h'-1'\(ga +. ds D- D\h'-1'\(hy +. ds th \o'bp' +. ds Th \o'LP' +. ds ae ae +. ds Ae AE +.\} +.rm #[ #] #H #V #F C +.\" ======================================================================== +.\" +.IX Title "RANGER 1" +.TH RANGER 1 "ranger-1.5.0" "10/11/2011" "ranger manual" +.\" For nroff, turn off justification. Always turn off hyphenation; it makes +.\" way too many mistakes in technical documents. +.if n .ad l +.nh +.SH "NAME" +ranger \- visual file manager +.SH "SYNOPSIS" +.IX Header "SYNOPSIS" +\&\fBranger\fR [\fB\-\-help\fR] [\fB\-\-version\fR] [\fB\-\-debug\fR] [\fB\-\-clean\fR] +[\fB\-\-list\-unused\-keys\fR] [\fB\-\-fail\-unless\-cd\fR] [\fB\-\-choosedir\fR=\fItargetfile\fR] +[\fB\-\-choosefile\fR=\fItargetfile\fR] [\fB\-\-copy\-config\fR=\fIfile\fR] [\fB\-\-mode\fR=\fImode\fR] +[\fB\-\-flags\fR=\fIflags\fR] [\fIpath/filename\fR] +.SH "DESCRIPTION" +.IX Header "DESCRIPTION" +ranger is a console file manager with \s-1VI\s0 key bindings. It provides a +minimalistic and nice curses interface with a view on the directory hierarchy. +The secondary task of ranger is to figure out which program you want to use to +open your files with. +.PP +This manual mainly contains information on the usage of ranger. Refer to the +\&\fI\s-1README\s0\fR for install instructions and to \fIdoc/HACKING\fR for development +specific information. For configuration, see the files in \fIranger/defaults\fR. +They are usually installed to \fI/usr/lib/python*/site\-packages/ranger/defaults\fR +and can be obtained with ranger's \-\-copy\-config option. +.PP +Inside ranger, you can press \fI1?\fR for a list of key bindings, \fI2?\fR for a list +of commands and \fI3?\fR for a list of settings. +.SH "OPTIONS" +.IX Header "OPTIONS" +.IP "\fB\-d\fR, \fB\-\-debug\fR" 14 +.IX Item "-d, --debug" +Activate the debug mode: Whenever an error occurs, ranger will exit and print a +full traceback. The default behavior is to merely print the name of the +exception in the statusbar/log and try to keep running. +.IP "\fB\-c\fR, \fB\-\-clean\fR" 14 +.IX Item "-c, --clean" +Activate the clean mode: ranger will not access or create any configuration +files nor will it leave any traces on your system. This is useful when your +configuration is broken, when you want to avoid clutter, etc. +.IP "\fB\-\-choosefile\fR=\fItargetfile\fR" 14 +.IX Item "--choosefile=targetfile" Allows you to pick a file with ranger. This changes the behavior so that when you open a file, ranger will exit and write the name of that file into -\fItargetfile\fR -.TP ---choosedir=\fItargetfile\fR +\&\fItargetfile\fR. +.IP "\fB\-\-choosedir\fR=\fItargetfile\fR" 14 +.IX Item "--choosedir=targetfile" Allows you to pick a directory with ranger. When you exit ranger, it will -write the last visited directory into \fItargetfile\fR -.TP ---copy-config=\fIwhich\fR +write the last visited directory into \fItargetfile\fR. +.IP "\fB\-\-copy\-config\fR=\fIfile\fR" 14 +.IX Item "--copy-config=file" Create copies of the default configuration files in your local configuration -directory. Existing ones will not be overwritten. Possible values: -all, apps, commands, keys, options, scope. -.TP ---fail-unless-cd -Return the exit code 1 if ranger is used to run a file, for example with -`ranger --fail-unless-cd filename`. This can be useful for scripts. -.TP --r \fIdir\fR, --confdir=\fIdir\fR -Define a different configuration directory. The default is -$XDG_CONFIG_HOME/ranger (which defaults to ~/.config/ranger) -.TP --m \fIn\fR, --mode=\fIn\fR -When a filename is supplied, make it run in mode \fIn\fR. Check the -documentation for more information on modes. -.TP --f \fIflags\fR, --flags=\fIflags\fR -When a filename is supplied, make it run with the flags \fIflags\fR. Check the -documentation for more information on flags. -.\"----------------------------------------- -.SH USAGE -.\"----------------------------------------- -.SS General Keybindings -Many keybindings take an additional numeric argument. Type \fI5j\fR to move -down 5 lines, \fI10<Space>\fR to mark 10 files or \fI3?\fR to read the -third chapter of the documentation. -.TP -h, j, k, l -Move left, down, up, right -.TP -^D or J, ^U or K +directory. Existing ones will not be overwritten. Possible values: \fIall\fR, +\&\fIrc\fR, \fIapps\fR, \fIcommands\fR, \fIoptions\fR, \fIscope\fR. +.IP "\fB\-\-list\-unused\-keys\fR" 14 +.IX Item "--list-unused-keys" +List common keys which are not bound to any action in the \*(L"browser\*(R" context. +This list is not complete, you can bind any key that is supported by curses: +use the key code returned by \f(CW\*(C`getch()\*(C'\fR. +.IP "\fB\-\-fail\-unless\-cd\fR" 14 +.IX Item "--fail-unless-cd" +Return the exit code 1 if ranger is used to run a file instead of used for file +browsing. (For example, \*(L"ranger \-\-fail\-unless\-cd test.txt\*(R" returns 1.) +.IP "\fB\-m\fR \fIn\fR, \fB\-\-mode\fR=\fIn\fR" 14 +.IX Item "-m n, --mode=n" +When a filename is supplied, run it in mode \fIn\fR. This has no effect unless +the execution of this file type is explicitly handled in the configuration. +.IP "\fB\-f\fR \fIflags\fR, \fB\-\-flags\fR=\fIflags\fR" 14 +.IX Item "-f flags, --flags=flags" +When a filename is supplied, run it with the given \fIflags\fR to modify +behavior. The execution of this file type is explicitly handled in the +configuration. +.IP "\fB\-\-version\fR" 14 +.IX Item "--version" +Print the version and exit. +.IP "\fB\-h\fR, \fB\-\-help\fR" 14 +.IX Item "-h, --help" +Print a list of options and exit. +.SH "CONCEPTS" +.IX Header "CONCEPTS" +.SS "\s-1TAGS\s0" +.IX Subsection "TAGS" +Tags are single characters which are displayed left of a filename. You can use +tags however you want. Press \*(L"t\*(R" to toggle tags and \*(L"T\*(R" to remove any tags of +the selection. The default tag is an Asterisk (\*(L"*\*(R"), but you can use any tag by +typing \fI"<tagname>\fR. +.SS "\s-1PREVIEWS\s0" +.IX Subsection "PREVIEWS" +By default, only text files are previewed, but you can enable external preview +scripts by setting the option \f(CW\*(C`use_preview_script\*(C'\fR and \f(CW\*(C`preview_files\*(C'\fR to True. +.PP +This default script is \fI~/.config/ranger/scope.sh\fR. It contains more +documentation and calls to the programs \fIlynx\fR and \fIelinks\fR for html, +\&\fIhighlight\fR for text/code, \fIimg2txt\fR for images, \fIatool\fR for archives, +\&\fIpdftotext\fR for PDFs and \fImediainfo\fR for video and audio files. +.PP +Install these programs (just the ones you need) and scope.sh will automatically +use them. +.SS "\s-1SELECTION\s0" +.IX Subsection "SELECTION" +The \fIselection\fR is defined as \*(L"All marked files \s-1IF\s0 \s-1THERE\s0 \s-1ARE\s0 \s-1ANY\s0, otherwise +the current file.\*(R" Be aware of this when using the :delete command, which +deletes all files in the selection. +.PP +You can mark files by pressing <Space>, v, etc. A yellow \fBMrk\fR symbol at the +bottom right indicates that there are marked files in this directory. +.SS "\s-1MACROS\s0" +.IX Subsection "MACROS" +Macros can be used in commands to abbreviate things. +.PP +.Vb 5 +\& %f the highlighted file +\& %d the path of the current directory +\& %s the selected files in the current directory. +\& %t all tagged files in the current directory +\& %c the full paths of the currently copied/cut files +.Ve +.PP +The macros \f(CW%f\fR, \f(CW%d\fR and \f(CW%s\fR also have upper case variants, \f(CW%F\fR, \f(CW%D\fR and \f(CW%S\fR, +which refer to the next tab. To refer to specific tabs, add a number in +between. (%7s = selection of the seventh tab.) +.PP +\&\f(CW%c\fR is the only macro which ranges out of the current directory. So you may +\&\*(L"abuse\*(R" the copying function for other purposes, like diffing two files which +are in different directories: +.PP +.Vb 2 +\& Yank the file A (type yy), move to the file B, then type +\& @diff %c %f +.Ve +.PP +Macros for file paths are generally shell-escaped so they can be used in the +:shell command. +.SS "\s-1BOOKMARKS\s0" +.IX Subsection "BOOKMARKS" +Type \fBm<key>\fR to bookmark the current directory. You can re-enter this +directory by typing \fB`<key>\fR. <key> can be any letter or digit. Unlike vim, +both lowercase and uppercase bookmarks are persistent. +.PP +Each time you jump to a bookmark, the special bookmark at key ` will be set +to the last directory. So typing \*(L"``\*(R" gets you back to where you were before. +.PP +Bookmarks are selectable when tabbing in the :cd command. +.PP +Note: The bookmarks ' (Apostrophe) and ` (Backtick) are the same. +.SS "\s-1FLAGS\s0" +.IX Subsection "FLAGS" +Flags give you a way to modify the behavior of the spawned process. They are +used in the commands :open_with (key \*(L"r\*(R") and :shell (key \*(L"!\*(R"). +.PP +.Vb 5 +\& s Silent mode. Output will be discarded. +\& d Detach the process. (Run in background) +\& p Redirect output to the pager +\& w Wait for an Enter\-press when the process is done +\& c Run the current file only, instead of the selection +.Ve +.PP +By default, all the flags are off unless specified otherwise in the \fIapps.py\fR +configuration file. You can specify as many flags as you want. An uppercase +flag negates the effect: \*(L"ddcccDs\*(R" is equivalent to \*(L"cs\*(R". +.PP +Examples: \f(CW\*(C`:open_with p\*(C'\fR will pipe the output of that process into +the pager. \f(CW\*(C`:shell \-w df\*(C'\fR will run \*(L"df\*(R" and wait for you to press Enter before +switching back to ranger. +.SS "\s-1MODES\s0" +.IX Subsection "MODES" +By specifying a mode (a positive integer), you can tell ranger what to do with +a file when running it. You can specify which mode to use by typing <mode>l or +<mode><Enter> or :open_with <mode>. The default mode is 0. +.PP +Examples: \f(CW\*(C`l\*(C'\fR (mode zero) to list the contents of an archive, \f(CW\*(C`1l\*(C'\fR (mode one) +to extract an archive. See the \fIapps.py\fR configuration file for all programs +and modes. +.SH "KEY BINDINGS" +.IX Header "KEY BINDINGS" +Key bindings are defined in the file \fIranger/defaults/rc.conf\fR. Check this +file for a list of all key bindings. You can copy it to your local +configuration directory with the \-\-copy\-config=rc option. +.PP +Many key bindings take an additional numeric argument. Type \fI5j\fR to move +down 5 lines, \fI2l\fR to open a file in mode 2, \fI10<Space>\fR to mark 10 files. +.PP +This list contains the most useful bindings: +.SS "\s-1MAIN\s0 \s-1BINDINGS\s0" +.IX Subsection "MAIN BINDINGS" +.IP "h, j, k, l" 14 +.IX Item "h, j, k, l" +Move left, down, up or right +.IP "^D or J, ^U or K" 14 +.IX Item "^D or J, ^U or K" Move a half page down, up -.TP -H, L +.IP "H, L" 14 +.IX Item "H, L" Move back and forward in the history -.TP -gg +.IP "gg" 14 +.IX Item "gg" Move to the top -.TP -G +.IP "G" 14 +.IX Item "G" Move to the bottom -.TP -^R +.IP "^R" 14 +.IX Item "^R" Reload everything -.TP -^L +.IP "^L" 14 +.IX Item "^L" Redraw the screen -.TP -S +.IP "S" 14 +.IX Item "S" Open a shell in the current directory -.TP -yy -Yank the selection to the "copy" buffer and mark them as to be copied -.TP -dd -Cut the selection to the "copy" buffer and mark them as to be moved -.TP -pp -Paste the files from the "copy" buffer here (by moving or copying, depending -on how they are marked.) By default, this will not overwrite existing files. -To overwrite them, use \fBpo\fR. -.TP -m\fIX\fR +.IP "?" 14 +Opens this man page +.IP "yy" 14 +.IX Item "yy" +Yank the selection to the \*(L"copy\*(R" buffer and mark them as to be copied +.IP "dd" 14 +.IX Item "dd" +Cut the selection to the \*(L"copy\*(R" buffer and mark them as to be moved +.IP "pp" 14 +.IX Item "pp" +Paste the files from the \*(L"copy\*(R" buffer here (by moving or copying, depending on +how they are marked.) By default, this will not overwrite existing files. To +overwrite them, use \fIpo\fR. +.IP "m\fIX\fR" 14 +.IX Item "mX" Create a bookmark with the name \fIX\fR -.TP -`\fIX\fR +.IP "`\fIX\fR" 14 +.IX Item "`X" Move to the bookmark with the name \fIX\fR -.TP -n, N -Find the next file, the previous file. You can define what to look for -by typing c\fIX\fR. If nothing is specified, pressing n will get you to -the newest file in the directory. -.TP -o\fIX\fR +.IP "n, N" 14 +.IX Item "n, N" +Find the next file. By default, this gets you to the newest file in the +directory, but if you search something using the keys /, cm, ct, ..., it will +get you to the next found entry. +.IP "N" 14 +.IX Item "N" +Find the previous file. +.IP "o\fIX\fR" 14 +.IX Item "oX" Change the sort method (like in mutt) -.TP -z\fIX\fR -Change settings -.TP -f -Quickly navigate by entering a part of the filename -.TP -Space -Mark a file -.TP -v, V -Toggle the mark-status of all files, unmark all files -.TP -/ -Open the search console -.TP -: -Open the command console -.TP -? -Opens the help screen with more keybindings and documentation -.\"----------------------------------------- -.SS Keybindings for using Tabs -Tabs are used to work in different directories in the same Ranger instance. -.TP -g\fIN\fR -Open a tab. N has to be a number from 0 to 9. If the tab doesn't exist yet, -it will be created. -.TP -gn, ^N +.IP "z\fIX\fR" 14 +.IX Item "zX" +Change settings. See the settings section for a list of settings and their +hotkey. +.IP "f" 14 +.IX Item "f" +Quickly navigate by entering a part of the filename. +.IP "Space" 14 +.IX Item "Space" +Mark a file. +.IP "v" 14 +.IX Item "v" +Toggle the mark-status of all files, unmark all files. +.IP "V, uv" 14 +.IX Item "V, uv" +Unmark all files +.IP "/" 14 +Search for files in the current directory. +.IP ":" 14 +Open the console. +.IP "Alt\-\fIN\fR" 14 +.IX Item "Alt-N" +Open a tab. N has to be a number from 0 to 9. If the tab doesn't exist yet, it +will be created. +.IP "gn, ^N" 14 +.IX Item "gn, ^N" Create a new tab. -.TP -gt, gT -Go to the next or previous tab. You can also use TAB and SHIFT+TAB. -.TP -gc, ^W -Close the current tab. The last tab cannot be closed. -.P -.\"----------------------------------------- -.SS Mouse Usage -.TP -Left Mouse Button -Click on something and you'll move there. -To run a file, "enter" it, like a directory, by clicking on the preview. -.TP -Right Mouse Button -Enter a directory -.TP -Scroll Wheel -Scroll -.\"----------------------------------------- -.SS Commands -.TP -:delete -Destroy all files in the selection with a roundhouse kick. Ranger will -ask for a confirmation if you attempt to delete multiple (marked) files or -non-empty directories. -.TP -:rename \fInewname\fR -Rename the current file. Also try the keybinding A for appending something -to a file name. -.TP -:quit -Quit ranger. The current directory will be bookmarked as ' so you can -re-enter it by typing `` or '' the next time you start ranger. -.\"----------------------------------------- -.SH TIPS -.SS -Change the directory after exit -A script like this in your bashrc would make you change the directory -of your parent shell after exiting ranger: -.nf - -ranger() { - command ranger --fail-unless-cd $@ && - cd "$(grep \\^\\' ~/.config/ranger/bookmarks | cut -b3-)" -} -.\"----------------------------------------- -.SH CONFIGURATION -The files in -.B ranger/defaults/ -can be copied into your configuration directory (by default, this is -~/.config/ranger) and customized according to your wishes. -Most files don't have to be copied completely though: Just define those -settings you want to add or change and they will override the defauls. -Colorschemes can be placed in ~/.config/ranger/colorschemes. -.P -All configuration is done in Python. -Each configuration file should contain sufficient documentation. -.\"----------------------------------------- -.SH COPYRIGHT -Copyright \(co -2009, 2010 -Roman Zimbelmann -.P -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. - -There is NO warranty; -not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -.\"----------------------------------------- -.SH SEE ALSO -The project page: -.RB < http://ranger.nongnu.org/ > -.P -The mailing list: -.RB < http://savannah.nongnu.org/mail/?group=ranger > -.\"----------------------------------------- -.SH BUGS -Please report them here and include as much relevant information -as possible: -.P -.RB < http://savannah.nongnu.org/bugs/?group=ranger > +.IP "gt, gT" 14 +.IX Item "gt, gT" +Go to the next or previous tab. You can also use \s-1TAB\s0 and \s-1SHIFT+TAB\s0 instead. +.IP "gc, ^W" 14 +.IX Item "gc, ^W" +Close the current tab. The last tab cannot be closed this way. +.SS "\s-1MIDNIGHT\s0 COMMANDER-LIKE \s-1BINDINGS\s0" +.IX Subsection "MIDNIGHT COMMANDER-LIKE BINDINGS" +.IP "<F1>" 14 +.IX Item "<F1>" +Display Help. +.IP "<F3>" 14 +.IX Item "<F3>" +Display the file. +.IP "<F4>" 14 +.IX Item "<F4>" +Edit the file. +.IP "<F5>" 14 +.IX Item "<F5>" +Copy the file. +.IP "<F6>" 14 +.IX Item "<F6>" +Cut the file. +.IP "<F7>" 14 +.IX Item "<F7>" +Open the console with \*(L":mkdir \*(R". +.IP "<F8>" 14 +.IX Item "<F8>" +Prompt for deletion of the selected files. +.IP "<F10>" 14 +.IX Item "<F10>" +Exit ranger. +.SS "READLINE-LIKE \s-1BINDINGS\s0 \s-1IN\s0 \s-1THE\s0 \s-1CONSOLE\s0" +.IX Subsection "READLINE-LIKE BINDINGS IN THE CONSOLE" +.IP "^B, ^F" 14 +.IX Item "^B, ^F" +Move left and right (B for back, F for forward) +.IP "^P, ^N" 14 +.IX Item "^P, ^N" +Move up and down (P for previous, N for Next) +.IP "^A, ^E" 14 +.IX Item "^A, ^E" +Move to the start or to the end +.IP "^D" 14 +.IX Item "^D" +Delete the current character. +.IP "^H" 14 +.IX Item "^H" +Backspace. +.SH "MOUSE BUTTONS" +.IX Header "MOUSE BUTTONS" +.IP "Left Mouse Button" 4 +.IX Item "Left Mouse Button" +Click on something and you'll move there. To run a file, \*(L"enter\*(R" it, like a +directory, by clicking on the preview. +.IP "Right Mouse Button" 4 +.IX Item "Right Mouse Button" +Enter a directory or run a file. +.IP "Scroll Wheel" 4 +.IX Item "Scroll Wheel" +Scrolls up or down. You can point at the column of the parent directory to +switch directories. +.SH "SETTINGS" +.IX Header "SETTINGS" +This section lists all built-in settings of ranger. The valid types for the +value are in [brackets]. The hotkey to toggle the setting is in <brokets>, if +a hotkey exists. +.PP +Settings can be changed in the file \fI~/.config/ranger/options.py\fR or on the +fly with the command \fB:set option value\fR. Examples: + :set column_ratios (1,2,3) + :set show_hidden=True +.IP "autosave_bookmarks [bool]" 4 +.IX Item "autosave_bookmarks [bool]" +Save bookmarks (used with mX and `X) instantly? This helps to synchronize +bookmarks between multiple ranger instances but leads to *slight* performance +loss. When false, bookmarks are saved when ranger is exited. +.IP "collapse_preview [bool] <zc>" 4 +.IX Item "collapse_preview [bool] <zc>" +When no preview is visible, should the last column be squeezed to make use of +the whitespace? +.IP "colorscheme_overlay [function, None]" 4 +.IX Item "colorscheme_overlay [function, None]" +An overlay function for colorschemes. See the default options.py for an +explanation and an example. +.IP "colorscheme [string]" 4 +.IX Item "colorscheme [string]" +Which colorscheme to use? These colorschemes are available by default: +\&\fBdefault\fR, \fBdefault88\fR, \fBtexas\fR, \fBjungle\fR, \fBsnow\fR. Snow is monochrome, +texas and default88 use 88 colors. +.IP "column_ratios [tuple, list]" 4 +.IX Item "column_ratios [tuple, list]" +How many columns are there, and what are their relative widths? For example, a +value of (1, 1, 1) would mean 3 even sized columns. (1, 1, 1, 1, 4) means 5 columns +with the preview column being as large as the other columns combined. +.IP "dirname_in_tabs [bool]" 4 +.IX Item "dirname_in_tabs [bool]" +Display the directory name in tabs? +.IP "display_size_in_main_column [bool]" 4 +.IX Item "display_size_in_main_column [bool]" +Display the file size in the main column? +.IP "display_size_in_status_bar [bool]" 4 +.IX Item "display_size_in_status_bar [bool]" +Display the file size in the status bar? +.IP "display_tags_in_all_columns [bool]" 4 +.IX Item "display_tags_in_all_columns [bool]" +Display tags in all columns? +.IP "draw_bookmark_borders [bool]" 4 +.IX Item "draw_bookmark_borders [bool]" +Draw borders around the bookmark window? +.IP "draw_borders [bool]" 4 +.IX Item "draw_borders [bool]" +Draw borders around columns? +.IP "flushinput [bool] <zi>" 4 +.IX Item "flushinput [bool] <zi>" +Flush the input after each key hit? One advantage is that when scrolling down +with \*(L"j\*(R", ranger stops scrolling instantly when you release the key. One +disadvantage is that when you type commands blindly, some keys might get lost. +.IP "hidden_filter [regexp]" 4 +.IX Item "hidden_filter [regexp]" +A regular expression pattern for files which should be hidden. +.IP "max_console_history_size [integer, None]" 4 +.IX Item "max_console_history_size [integer, None]" +How many console commands should be kept in history? +.IP "max_history_size [integer, None]" 4 +.IX Item "max_history_size [integer, None]" +How many directory changes should be kept in history? +.IP "mouse_enabled [bool] <zm>" 4 +.IX Item "mouse_enabled [bool] <zm>" +Enable mouse input? +.IP "padding_right [bool]" 4 +.IX Item "padding_right [bool]" +When collapse_preview is on and there is no preview, should there remain a +little padding on the right? This allows you to click into that space to run +the file. +.IP "preview_directories [bool] <zP>" 4 +.IX Item "preview_directories [bool] <zP>" +Preview directories in the preview column? +.IP "preview_files [bool] <zp>" 4 +.IX Item "preview_files [bool] <zp>" +Preview files in the preview column? +.IP "preview_script [string, None]" 4 +.IX Item "preview_script [string, None]" +Which script should handle generating previews? If the file doesn't exist, or +use_preview_script is off, ranger will handle previews itself by just printing +the content. +.IP "save_console_history [bool]" 4 +.IX Item "save_console_history [bool]" +Should the console history be saved on exit? If disabled, the console history +is reset when you restart ranger. +.IP "scroll_offset [integer]" 4 +.IX Item "scroll_offset [integer]" +Try to keep this much space between the top/bottom border when scrolling. +.IP "shorten_title [integer, bool]" 4 +.IX Item "shorten_title [integer, bool]" +Trim the title of the window if it gets long? The number defines how many +directories are displayed at once, False turns off this feature. +.IP "show_cursor [bool]" 4 +.IX Item "show_cursor [bool]" +Always show the terminal cursor? +.IP "show_hidden_bookmarks [bool]" 4 +.IX Item "show_hidden_bookmarks [bool]" +Show dotfiles in the bookmark preview window? (Type ') +.IP "show_hidden [bool] <zh>, <^H>" 4 +.IX Item "show_hidden [bool] <zh>, <^H>" +Show hidden files? +.IP "sort_case_insensitive [bool] <zc>" 4 +.IX Item "sort_case_insensitive [bool] <zc>" +Sort case-insensitively? If true, \*(L"a\*(R" will be listed before \*(L"B\*(R" even though +its \s-1ASCII\s0 value is higher. +.IP "sort_directories_first [bool] <zd>" 4 +.IX Item "sort_directories_first [bool] <zd>" +Sort directories first? +.IP "sort_reverse [bool] <or>" 4 +.IX Item "sort_reverse [bool] <or>" +Sort reversed? +.IP "sort [string] <oa>, <ob>, <oc>, <om>, <on>, <ot>, <os>" 4 +.IX Item "sort [string] <oa>, <ob>, <oc>, <om>, <on>, <ot>, <os>" +Which sorting mechanism should be used? Choose one of \fBatime\fR, \fBbasename\fR, +\&\fBctime\fR, \fBmtime\fR, \fBnatural\fR, \fBtype\fR, \fBsize\fR +.Sp +Note: You can reverse the order by using an uppercase O in the key combination. +.IP "tilde_in_titlebar [bool]" 4 +.IX Item "tilde_in_titlebar [bool]" +Abbreviate \f(CW$HOME\fR with ~ in the title bar (first line) of ranger? +.IP "unicode_ellipsis [bool]" 4 +.IX Item "unicode_ellipsis [bool]" +Use a unicode \*(L"...\*(R" character instead of \*(L"~\*(R" to mark cut-off filenames? +.IP "update_title [bool]" 4 +.IX Item "update_title [bool]" +Set a window title? +.IP "use_preview_script [bool] <zv>" 4 +.IX Item "use_preview_script [bool] <zv>" +Use the preview script defined in the setting \fIpreview_script\fR? +.IP "xterm_alt_key [bool]" 4 +.IX Item "xterm_alt_key [bool]" +Enable this if key combinations with the Alt Key don't work for you. +(Especially on xterm) +.SH "COMMANDS" +.IX Header "COMMANDS" +You can enter the commands in the console which is opened by pressing \*(L":\*(R". +.PP +There are additional commands which are directly translated to python +functions, one for every method in the ranger.core.actions.Actions class. +They are not documented here, since they are mostly for key bindings, not to be +typed in by a user. Read the source if you are interested in them. +.IP "bulkrename" 2 +.IX Item "bulkrename" +This command opens a list of selected files in an external editor. After you +edit and save the file, it will generate a shell script which does bulk +renaming according to the changes you did in the file. +.Sp +This shell script is opened in an editor for you to review. After you close +it, it will be executed. +.IP "cd [\fIdirectory\fR]" 2 +.IX Item "cd [directory]" +The cd command changes the directory. The command \f(CW\*(C`:cd \-\*(C'\fR is equivalent to +typing ``. +.IP "chain \fIcommand1\fR[; \fIcommand2\fR[; \fIcommand3\fR...]]" 2 +.IX Item "chain command1[; command2[; command3...]]" +Combines multiple commands into one, separated by columns. +.IP "chmod \fIoctal_number\fR" 2 +.IX Item "chmod octal_number" +Sets the permissions of the selection to the octal number. +.Sp +The octal number is between 000 and 777. The digits specify the permissions for +the user, the group and others. A 1 permits execution, a 2 permits writing, a +4 permits reading. Add those numbers to combine them. So a 7 permits +everything. +.Sp +Key bindings in the form of [\-+]<who><what> and =<octal> also exist. For +example, \fB+ar\fR allows reading for everyone, \-ow forbids others to write and +=777 allows everything. +.Sp +See also: man 1 chmod +.IP "cmap \fIkey\fR \fIcommand\fR" 2 +.IX Item "cmap key command" +Binds keys for the console. Works like the \f(CW\*(C`map\*(C'\fR command. +.IP "console [\-p\fIN\fR] \fIcommand\fR" 2 +.IX Item "console [-pN] command" +Opens the console with the command already typed in. The cursor is placed at +\&\fIN\fR. +.IP "copycmap \fIkey\fR \fInewkey\fR [\fInewkey2\fR ...]" 2 +.IX Item "copycmap key newkey [newkey2 ...]" +See \f(CW\*(C`copymap\*(C'\fR +.IP "copymap \fIkey\fR \fInewkey\fR [\fInewkey2\fR ...]" 2 +.IX Item "copymap key newkey [newkey2 ...]" +Copies the keybinding \fIkey\fR to \fInewkey\fR in the \*(L"browser\*(R" context. This is a +deep copy, so if you change the new binding (or parts of it) later, the old one +is not modified. +.Sp +To copy key bindings of the console, taskview, or pager use \*(L"copycmap\*(R", +\&\*(L"copytmap\*(R" or \*(L"copypmap\*(R". +.IP "copypmap \fIkey\fR \fInewkey\fR [\fInewkey2\fR ...]" 2 +.IX Item "copypmap key newkey [newkey2 ...]" +See \f(CW\*(C`copymap\*(C'\fR +.IP "copytmap \fIkey\fR \fInewkey\fR [\fInewkey2\fR ...]" 2 +.IX Item "copytmap key newkey [newkey2 ...]" +See \f(CW\*(C`copymap\*(C'\fR +.IP "cunmap \fIkey\fR \fIcommand\fR" 2 +.IX Item "cunmap key command" +Removes key mappings of the console. Works like the \f(CW\*(C`unmap\*(C'\fR command. +.IP "delete [\fIconfirmation\fR]" 2 +.IX Item "delete [confirmation]" +Destroy all files in the selection with a roundhouse kick. ranger will ask for +a confirmation if you attempt to delete multiple (marked) files or non-empty +directories. +.Sp +When asking for confirmation, this command will only proceed if the last given +word starts with a `y'. +.IP "edit [\fIfilename\fR]" 2 +.IX Item "edit [filename]" +Edit the current file or the file in the argument. +.IP "eval [\fI\-q\fR] \fIpython_code\fR" 2 +.IX Item "eval [-q] python_code" +Evaluates the python code. `fm' is a reference to the \s-1FM\s0 instance. To display +text, use the function `p'. The result is displayed on the screen unless you +use the \*(L"\-q\*(R" option. +.Sp +Examples: + :eval fm + :eval len(fm.env.directories) + :eval p(\*(L"Hello World!\*(R") +.IP "filter [\fIstring\fR]" 2 +.IX Item "filter [string]" +Displays only the files which contain the \fIstring\fR in their basename. +.IP "find \fIpattern\fR" 2 +.IX Item "find pattern" +Search files in the current directory that match the given (case-insensitive) +regular expression pattern as you type. Once there is an unambiguous result, +it will be run immediately. (Or entered, if it's a directory.) +.IP "grep \fIpattern\fR" 2 +.IX Item "grep pattern" +Looks for a string in all marked files or directories. +.IP "load_copy_buffer" 2 +.IX Item "load_copy_buffer" +Load the copy buffer from \fI~/.config/ranger/copy_buffer\fR. This can be used to +pass the list of copied files to another ranger instance. +.IP "map \fIkey\fR \fIcommand\fR" 2 +.IX Item "map key command" +Assign the key combination to the given command. Whenever you type the +key/keys, the command will be executed. Additionally, if you use a quantifier +when typing the key, like 5j, it will be passed to the command as the attribute +\&\*(L"self.quantifier\*(R". +.Sp +The keys you bind with this command are accessible in the file browser only, +not in the console, task view or pager. To bind keys there, use the commands +\&\*(L"cmap\*(R", \*(L"tmap\*(R" or \*(L"pmap\*(R". +.IP "mark \fIpattern\fR" 2 +.IX Item "mark pattern" +Mark all files matching the regular expression pattern. +.IP "mkdir \fIdirname\fR" 2 +.IX Item "mkdir dirname" +Creates a directory with the name \fIdirname\fR. +.IP "open_with [\fIapplication\fR] [\fIflags\fR] [\fImode\fR]" 2 +.IX Item "open_with [application] [flags] [mode]" +Open the selected files with the given application, unless it is omitted, in +which case the default application is used. \fIflags\fR are characters out of +\&\*(L"sdpcwSDPCW\*(R" and \fImode\fR is any positive integer. Their meanings are discussed +in their own sections. +.IP "pmap \fIkey\fR \fIcommand\fR" 2 +.IX Item "pmap key command" +Binds keys for the pager. Works like the \f(CW\*(C`map\*(C'\fR command. +.IP "punmap \fIkey\fR \fIcommand\fR" 2 +.IX Item "punmap key command" +Removes key mappings of the pager. Works like the \f(CW\*(C`unmap\*(C'\fR command. +.IP "quit" 2 +.IX Item "quit" +Like quit!, but closes only this tab if multiple tabs are open. +.IP "quit!" 2 +.IX Item "quit!" +Quit ranger. The current directory will be bookmarked as ' so you can re-enter +it by typing `` or '' the next time you start ranger. +.IP "rename \fInewname\fR" 2 +.IX Item "rename newname" +Rename the current file. If a file with that name already exists, the renaming +will fail. Also try the key binding A for appending something to a file name. +.IP "save_copy_buffer" 2 +.IX Item "save_copy_buffer" +Save the copy buffer from \fI~/.config/ranger/copy_buffer\fR. This can be used to +pass the list of copied files to another ranger instance. +.IP "search \fIpattern\fR" 2 +.IX Item "search pattern" +Search files in the current directory that match the given (case insensitive) +regular expression pattern. +.IP "search_inc \fIpattern\fR" 2 +.IX Item "search_inc pattern" +Search files in the current directory that match the given (case insensitive) +regular expression pattern. This command gets you to matching files as you +type. +.IP "set \fIoption\fR=\fIvalue\fR" 2 +.IX Item "set option=value" +Assigns a new value to an option. Valid options are listed in the settings +section. Use tab completion to get the current value of an option, though this +doesn't work for functions and regular expressions. Valid values are: +.Sp +.Vb 8 +\& None None +\& bool True or False +\& integer 0 or 1 or \-1 or 2 etc. +\& list [1, 2, 3] +\& tuple 1, 2, 3 or (1, 2, 3) +\& function lambda <arguments>: <expression> +\& regexp regexp(\*(Aq<pattern>\*(Aq) +\& string Anything +.Ve +.IP "shell [\-\fIflags\fR] \fIcommand\fR" 2 +.IX Item "shell [-flags] command" +Run a shell command. \fIflags\fR are discussed in their own section. +.IP "terminal" 2 +.IX Item "terminal" +Spawns the \fIx\-terminal-emulator\fR starting in the current directory. +.IP "touch \fIfilename\fR" 2 +.IX Item "touch filename" +Creates an empty file with the name \fIfilename\fR, unless it already exists. +.IP "tmap \fIkey\fR \fIcommand\fR" 2 +.IX Item "tmap key command" +Binds keys for the taskview. Works like the \f(CW\*(C`map\*(C'\fR command. +.IP "tunmap \fIkey\fR \fIcommand\fR" 2 +.IX Item "tunmap key command" +Removes key mappings of the taskview. Works like the \f(CW\*(C`unmap\*(C'\fR command. +.IP "unmap [\fIkeys\fR ...]" 2 +.IX Item "unmap [keys ...]" +Removes the given key mappings in the \*(L"browser\*(R" context. To unmap key bindings +in the console, taskview, or pager use \*(L"cunmap\*(R", \*(L"tunmap\*(R" or \*(L"punmap\*(R". +.IP "unmark \fIpattern\fR" 2 +.IX Item "unmark pattern" +Unmark all files matching a regular expression pattern. +.SH "FILES" +.IX Header "FILES" +ranger reads several configuration files which are located in +\&\fI\f(CI$HOME\fI/.config/ranger\fR or \fI\f(CI$XDG_CONFIG_HOME\fI/ranger\fR if \f(CW$XDG_CONFIG_HOME\fR is +defined. The configuration is done mostly in python. When removing a +configuration file, remove its compiled version too. (Python automatically +compiles modules. Since python3 they are saved in the _\|_pycache_\|_ directory, +earlier versions store them with the .pyc extension in the same directory.) +.PP +Use the \-\-copy\-config option to obtain the default configuration files. They +include further documentation and it's too much to put here. +.PP +You don't need to copy the whole file though, most configuration files are +overlaid on top of the defaults (\fIoptions.py\fR, \fIcommand.py\fR, \fIrc.conf\fR) or +can be sub-classed (\fIapps.py\fR, \fIcolorschemes\fR). +.PP +When starting ranger with the \fB\-\-clean\fR option, it will not access or create +any of these files. +.SS "\s-1CONFIGURATION\s0" +.IX Subsection "CONFIGURATION" +.IP "apps.py" 10 +.IX Item "apps.py" +Controls which applications are used to open files. +.IP "commands.py" 10 +.IX Item "commands.py" +Defines commands which can be used by typing \*(L":\*(R". +.IP "rc.conf" 10 +.IX Item "rc.conf" +Contains a list of commands which are executed on startup. Mostly key bindings +are defined here. +.IP "options.py" 10 +.IX Item "options.py" +Sets a handful of basic options. +.IP "scope.sh" 10 +.IX Item "scope.sh" +This is a script that handles file previews. When the options +\&\fIuse_preview_script\fR and \fIpreview_files\fR or, respectively, +\&\fIpreview_directories\fR are set, the program specified in the option +\&\fIpreview_script\fR is run and its output and/or exit code determines rangers +reaction. +.IP "colorschemes/" 10 +.IX Item "colorschemes/" +Colorschemes can be placed here. +.SS "\s-1STORAGE\s0" +.IX Subsection "STORAGE" +.IP "bookmarks" 10 +.IX Item "bookmarks" +This file contains a list of bookmarks. The syntax is /^(.):(.*)$/. The first +character is the bookmark key and the rest after the colon is the path to the +file. In ranger, bookmarks can be set by typing m<key>, accessed by typing +\&'<key> and deleted by typing um<key>. +.IP "copy_buffer" 10 +.IX Item "copy_buffer" +When running the command :save_copy_buffer, the paths of all currently copied +files are saved in this file. You can later run :load_copy_buffer to copy the +same files again, pass them to another ranger instance or process them in a +script. +.IP "history" 10 +.IX Item "history" +Contains a list of commands that have been previously typed in. +.IP "tagged" 10 +.IX Item "tagged" +Contains a list of tagged files. The syntax is /^(.:)?(.*)$/ where the first +letter is the optional name of the tag and the rest after the optional colon is +the path to the file. In ranger, tags can be set by pressing t and removed +with T. To assign a named tag, type "<tagname>. +.SH "ENVIRONMENT" +.IX Header "ENVIRONMENT" +These environment variables have an effect on ranger: +.IP "\s-1EDITOR\s0" 8 +.IX Item "EDITOR" +Defines the editor to be used for the \*(L"E\*(R" key. Defaults to the first installed +program out of \*(L"vim\*(R", \*(L"emacs\*(R" and \*(L"nano\*(R". +.IP "\s-1SHELL\s0" 8 +.IX Item "SHELL" +Defines the shell that ranger is going to use with the :shell command and +the \*(L"S\*(R" key. Defaults to \*(L"bash\*(R". +.IP "\s-1XDG_CONFIG_HOME\s0" 8 +.IX Item "XDG_CONFIG_HOME" +Specifies the directory for configuration files. Defaults to \fI\f(CI$HOME\fI/.config\fR. +.IP "\s-1PYTHONOPTIMIZE\s0" 8 +.IX Item "PYTHONOPTIMIZE" +This variable determines the optimize level of python. +.Sp +Using PYTHONOPTIMIZE=1 (like python \-O) will make python discard assertion +statements. You will gain efficiency at the cost of losing some debug info. +.Sp +Using PYTHONOPTIMIZE=2 (like python \-OO) will additionally discard any +docstrings. Using this will disable the <F1> key on commands. +.SH "EXAMPLES" +.IX Header "EXAMPLES" +.SS "\s-1VIM:\s0 File Chooser" +.IX Subsection "VIM: File Chooser" +This is a vim function which allows you to use ranger to select a file for +opening in your current vim session. +.PP +.Vb 9 +\& fun! RangerChooser() +\& silent !ranger \-\-choosefile=/tmp/chosenfile \`[ \-z \*(Aq%\*(Aq ] && echo \-n . || dirname %\` +\& if filereadable(\*(Aq/tmp/chosenfile\*(Aq) +\& exec \*(Aqedit \*(Aq . system(\*(Aqcat /tmp/chosenfile\*(Aq) +\& call system(\*(Aqrm /tmp/chosenfile\*(Aq) +\& endif +\& redraw! +\& endfun +\& map ,r :call RangerChooser()<CR> +.Ve +.SS "Bash: cd to last path after exit" +.IX Subsection "Bash: cd to last path after exit" +This is a bash function (for \fI~/.bashrc\fR) to change the directory to the last +visited one after ranger quits. You can always type \f(CW\*(C`cd \-\*(C'\fR to go back to the +original one. +.PP +.Vb 9 +\& function ranger\-cd { +\& tempfile=\*(Aq/tmp/chosendir\*(Aq +\& /usr/bin/ranger \-\-choosedir="$tempfile" "${@:\-$(pwd)}" +\& test \-f "$tempfile" && +\& if [ "$(cat \-\- "$tempfile")" != "$(echo \-n \`pwd\`)" ]; then +\& cd \-\- "$(cat "$tempfile")" +\& fi +\& rm \-f \-\- "$tempfile" +\& } +\& +\& # This binds Ctrl\-O to ranger\-cd: +\& bind \*(Aq"\eC\-o":"ranger\-cd\eC\-m"\*(Aq +.Ve +.SH "LICENSE" +.IX Header "LICENSE" +\&\s-1GNU\s0 General Public License 3 or (at your option) any later version. +.SH "LINKS" +.IX Header "LINKS" +.IP "Download: http://ranger.nongnu.org/ranger\-stable.tar.gz <http://ranger.nongnu.org/ranger-stable.tar.gz>" 4 +.IX Item "Download: http://ranger.nongnu.org/ranger-stable.tar.gz <http://ranger.nongnu.org/ranger-stable.tar.gz>" +.PD 0 +.IP "The project page: <http://ranger.nongnu.org/>" 4 +.IX Item "The project page: <http://ranger.nongnu.org/>" +.IP "The mailing list: <http://savannah.nongnu.org/mail/?group=ranger>" 4 +.IX Item "The mailing list: <http://savannah.nongnu.org/mail/?group=ranger>" +.PD +.PP +ranger is maintained with the git version control system. To fetch a fresh +copy, run: +.PP +.Vb 1 +\& git clone git://git.savannah.nongnu.org/ranger.git +.Ve +.SH "BUGS" +.IX Header "BUGS" +Report bugs here: <http://savannah.nongnu.org/bugs/?group=ranger> +.PP +Please include as much relevant information as possible. For the most +diagnostic output, run ranger like this: \f(CW\*(C`PYTHONOPTIMIZE= ranger \-\-debug\*(C'\fR diff --git a/doc/ranger.pod b/doc/ranger.pod new file mode 100644 index 00000000..ec272de1 --- /dev/null +++ b/doc/ranger.pod @@ -0,0 +1,1034 @@ +=head1 NAME + +ranger - visual file manager + + + + +=head1 SYNOPSIS + +B<ranger> [B<--help>] [B<--version>] [B<--debug>] [B<--clean>] +[B<--list-unused-keys>] [B<--fail-unless-cd>] [B<--choosedir>=I<targetfile>] +[B<--choosefile>=I<targetfile>] [B<--copy-config>=I<file>] [B<--mode>=I<mode>] +[B<--flags>=I<flags>] [I<path/filename>] + + + + +=head1 DESCRIPTION + +ranger is a console file manager with VI key bindings. It provides a +minimalistic and nice curses interface with a view on the directory hierarchy. +The secondary task of ranger is to figure out which program you want to use to +open your files with. + +This manual mainly contains information on the usage of ranger. Refer to the +F<README> for install instructions and to F<doc/HACKING> for development +specific information. For configuration, see the files in F<ranger/defaults>. +They are usually installed to F</usr/lib/python*/site-packages/ranger/defaults> +and can be obtained with ranger's --copy-config option. + +Inside ranger, you can press I<1?> for a list of key bindings, I<2?> for a list +of commands and I<3?> for a list of settings. + + + + +=head1 OPTIONS + +=over 14 + +=item B<-d>, B<--debug> + +Activate the debug mode: Whenever an error occurs, ranger will exit and print a +full traceback. The default behavior is to merely print the name of the +exception in the statusbar/log and try to keep running. + +=item B<-c>, B<--clean> + +Activate the clean mode: ranger will not access or create any configuration +files nor will it leave any traces on your system. This is useful when your +configuration is broken, when you want to avoid clutter, etc. + +=item B<--choosefile>=I<targetfile> + +Allows you to pick a file with ranger. This changes the behavior so that when +you open a file, ranger will exit and write the name of that file into +I<targetfile>. + +=item B<--choosedir>=I<targetfile> + +Allows you to pick a directory with ranger. When you exit ranger, it will +write the last visited directory into I<targetfile>. + +=item B<--copy-config>=I<file> + +Create copies of the default configuration files in your local configuration +directory. Existing ones will not be overwritten. Possible values: I<all>, +I<rc>, I<apps>, I<commands>, I<options>, I<scope>. + +=item B<--list-unused-keys> + +List common keys which are not bound to any action in the "browser" context. +This list is not complete, you can bind any key that is supported by curses: +use the key code returned by C<getch()>. + +=item B<--fail-unless-cd> + +Return the exit code 1 if ranger is used to run a file instead of used for file +browsing. (For example, "ranger --fail-unless-cd test.txt" returns 1.) + +=item B<-m> I<n>, B<--mode>=I<n> + +When a filename is supplied, run it in mode I<n>. This has no effect unless +the execution of this file type is explicitly handled in the configuration. + +=item B<-f> I<flags>, B<--flags>=I<flags> + +When a filename is supplied, run it with the given I<flags> to modify +behavior. The execution of this file type is explicitly handled in the +configuration. + +=item B<--version> + +Print the version and exit. + +=item B<-h>, B<--help> + +Print a list of options and exit. + +=back + + + + +=head1 CONCEPTS + +=head2 TAGS + +Tags are single characters which are displayed left of a filename. You can use +tags however you want. Press "t" to toggle tags and "T" to remove any tags of +the selection. The default tag is an Asterisk ("*"), but you can use any tag by +typing I<"<tagnameE<gt>>. + +=head2 PREVIEWS + +By default, only text files are previewed, but you can enable external preview +scripts by setting the option C<use_preview_script> and C<preview_files> to True. + +This default script is F<~/.config/ranger/scope.sh>. It contains more +documentation and calls to the programs I<lynx> and I<elinks> for html, +I<highlight> for text/code, I<img2txt> for images, I<atool> for archives, +I<pdftotext> for PDFs and I<mediainfo> for video and audio files. + +Install these programs (just the ones you need) and scope.sh will automatically +use them. + +=head2 SELECTION + +The I<selection> is defined as "All marked files IF THERE ARE ANY, otherwise +the current file." Be aware of this when using the :delete command, which +deletes all files in the selection. + +You can mark files by pressing <Space>, v, etc. A yellow B<Mrk> symbol at the +bottom right indicates that there are marked files in this directory. + +=head2 MACROS + +Macros can be used in commands to abbreviate things. + + %f the highlighted file + %d the path of the current directory + %s the selected files in the current directory. + %t all tagged files in the current directory + %c the full paths of the currently copied/cut files + +The macros %f, %d and %s also have upper case variants, %F, %D and %S, +which refer to the next tab. To refer to specific tabs, add a number in +between. (%7s = selection of the seventh tab.) + +%c is the only macro which ranges out of the current directory. So you may +"abuse" the copying function for other purposes, like diffing two files which +are in different directories: + + Yank the file A (type yy), move to the file B, then type + @diff %c %f + +Macros for file paths are generally shell-escaped so they can be used in the +:shell command. + +=head2 BOOKMARKS + +Type B<m<keyE<gt>> to bookmark the current directory. You can re-enter this +directory by typing B<`<keyE<gt>>. <key> can be any letter or digit. Unlike vim, +both lowercase and uppercase bookmarks are persistent. + +Each time you jump to a bookmark, the special bookmark at key ` will be set +to the last directory. So typing "``" gets you back to where you were before. + +Bookmarks are selectable when tabbing in the :cd command. + +Note: The bookmarks ' (Apostrophe) and ` (Backtick) are the same. + +=head2 FLAGS + +Flags give you a way to modify the behavior of the spawned process. They are +used in the commands :open_with (key "r") and :shell (key "!"). + + s Silent mode. Output will be discarded. + d Detach the process. (Run in background) + p Redirect output to the pager + w Wait for an Enter-press when the process is done + c Run the current file only, instead of the selection + +By default, all the flags are off unless specified otherwise in the F<apps.py> +configuration file. You can specify as many flags as you want. An uppercase +flag negates the effect: "ddcccDs" is equivalent to "cs". + +Examples: C<:open_with p> will pipe the output of that process into +the pager. C<:shell -w df> will run "df" and wait for you to press Enter before +switching back to ranger. + +=head2 MODES + +By specifying a mode (a positive integer), you can tell ranger what to do with +a file when running it. You can specify which mode to use by typing <mode>l or +<mode><Enter> or :open_with <mode>. The default mode is 0. + +Examples: C<l> (mode zero) to list the contents of an archive, C<1l> (mode one) +to extract an archive. See the F<apps.py> configuration file for all programs +and modes. + + + + +=head1 KEY BINDINGS + +Key bindings are defined in the file F<ranger/defaults/rc.conf>. Check this +file for a list of all key bindings. You can copy it to your local +configuration directory with the --copy-config=rc option. + +Many key bindings take an additional numeric argument. Type I<5j> to move +down 5 lines, I<2l> to open a file in mode 2, I<10<SpaceE<gt>> to mark 10 files. + +This list contains the most useful bindings: + +=head2 MAIN BINDINGS + +=over 14 + +=item h, j, k, l + +Move left, down, up or right + +=item ^D or J, ^U or K + +Move a half page down, up + +=item H, L + +Move back and forward in the history + +=item gg + +Move to the top + +=item G + +Move to the bottom + +=item ^R + +Reload everything + +=item ^L + +Redraw the screen + +=item S + +Open a shell in the current directory + +=item ? + +Opens this man page + +=item yy + +Yank the selection to the "copy" buffer and mark them as to be copied + +=item dd + +Cut the selection to the "copy" buffer and mark them as to be moved + +=item pp + +Paste the files from the "copy" buffer here (by moving or copying, depending on +how they are marked.) By default, this will not overwrite existing files. To +overwrite them, use I<po>. + +=item mI<X> + +Create a bookmark with the name I<X> + +=item `I<X> + +Move to the bookmark with the name I<X> + +=item n, N + +Find the next file. By default, this gets you to the newest file in the +directory, but if you search something using the keys /, cm, ct, ..., it will +get you to the next found entry. + +=item N + +Find the previous file. + +=item oI<X> + +Change the sort method (like in mutt) + +=item zI<X> + +Change settings. See the settings section for a list of settings and their +hotkey. + +=item f + +Quickly navigate by entering a part of the filename. + +=item Space + +Mark a file. + +=item v + +Toggle the mark-status of all files, unmark all files. + +=item V, uv + +Unmark all files + +=item / + +Search for files in the current directory. + +=item : + +Open the console. + + +=item Alt-I<N> + +Open a tab. N has to be a number from 0 to 9. If the tab doesn't exist yet, it +will be created. + +=item gn, ^N + +Create a new tab. + +=item gt, gT + +Go to the next or previous tab. You can also use TAB and SHIFT+TAB instead. + +=item gc, ^W + +Close the current tab. The last tab cannot be closed this way. + +=back + +=head2 MIDNIGHT COMMANDER-LIKE BINDINGS + +=over 14 + +=item <F1> + +Display Help. + +=item <F3> + +Display the file. + +=item <F4> + +Edit the file. + +=item <F5> + +Copy the file. + +=item <F6> + +Cut the file. + +=item <F7> + +Open the console with ":mkdir ". + +=item <F8> + +Prompt for deletion of the selected files. + +=item <F10> + +Exit ranger. + +=back + +=head2 READLINE-LIKE BINDINGS IN THE CONSOLE + +=over 14 + +=item ^B, ^F + +Move left and right (B for back, F for forward) + +=item ^P, ^N + +Move up and down (P for previous, N for Next) + +=item ^A, ^E + +Move to the start or to the end + +=item ^D + +Delete the current character. + +=item ^H + +Backspace. + +=back + + +=head1 MOUSE BUTTONS + +=over + +=item Left Mouse Button + +Click on something and you'll move there. To run a file, "enter" it, like a +directory, by clicking on the preview. + +=item Right Mouse Button + +Enter a directory or run a file. + +=item Scroll Wheel + +Scrolls up or down. You can point at the column of the parent directory to +switch directories. + +=back + + + + +=head1 SETTINGS + +This section lists all built-in settings of ranger. The valid types for the +value are in [brackets]. The hotkey to toggle the setting is in <brokets>, if +a hotkey exists. + +Settings can be changed in the file F<~/.config/ranger/options.py> or on the +fly with the command B<:set option value>. Examples: + :set column_ratios (1,2,3) + :set show_hidden=True + +=over + +=item autosave_bookmarks [bool] + +Save bookmarks (used with mX and `X) instantly? This helps to synchronize +bookmarks between multiple ranger instances but leads to *slight* performance +loss. When false, bookmarks are saved when ranger is exited. + +=item collapse_preview [bool] <zc> + +When no preview is visible, should the last column be squeezed to make use of +the whitespace? + +=item colorscheme_overlay [function, None] + +An overlay function for colorschemes. See the default options.py for an +explanation and an example. + +=item colorscheme [string] + +Which colorscheme to use? These colorschemes are available by default: +B<default>, B<default88>, B<texas>, B<jungle>, B<snow>. Snow is monochrome, +texas and default88 use 88 colors. + +=item column_ratios [tuple, list] + +How many columns are there, and what are their relative widths? For example, a +value of (1, 1, 1) would mean 3 even sized columns. (1, 1, 1, 1, 4) means 5 columns +with the preview column being as large as the other columns combined. + +=item dirname_in_tabs [bool] + +Display the directory name in tabs? + +=item display_size_in_main_column [bool] + +Display the file size in the main column? + +=item display_size_in_status_bar [bool] + +Display the file size in the status bar? + +=item display_tags_in_all_columns [bool] + +Display tags in all columns? + +=item draw_bookmark_borders [bool] + +Draw borders around the bookmark window? + +=item draw_borders [bool] + +Draw borders around columns? + +=item flushinput [bool] <zi> + +Flush the input after each key hit? One advantage is that when scrolling down +with "j", ranger stops scrolling instantly when you release the key. One +disadvantage is that when you type commands blindly, some keys might get lost. + +=item hidden_filter [regexp] + +A regular expression pattern for files which should be hidden. + +=item max_console_history_size [integer, None] + +How many console commands should be kept in history? + +=item max_history_size [integer, None] + +How many directory changes should be kept in history? + +=item mouse_enabled [bool] <zm> + +Enable mouse input? + +=item padding_right [bool] + +When collapse_preview is on and there is no preview, should there remain a +little padding on the right? This allows you to click into that space to run +the file. + +=item preview_directories [bool] <zP> + +Preview directories in the preview column? + +=item preview_files [bool] <zp> + +Preview files in the preview column? + +=item preview_script [string, None] + +Which script should handle generating previews? If the file doesn't exist, or +use_preview_script is off, ranger will handle previews itself by just printing +the content. + +=item save_console_history [bool] + +Should the console history be saved on exit? If disabled, the console history +is reset when you restart ranger. + +=item scroll_offset [integer] + +Try to keep this much space between the top/bottom border when scrolling. + +=item shorten_title [integer, bool] + +Trim the title of the window if it gets long? The number defines how many +directories are displayed at once, False turns off this feature. + +=item show_cursor [bool] + +Always show the terminal cursor? + +=item show_hidden_bookmarks [bool] + +Show dotfiles in the bookmark preview window? (Type ') + +=item show_hidden [bool] <zh>, <^H> + +Show hidden files? + +=item sort_case_insensitive [bool] <zc> + +Sort case-insensitively? If true, "a" will be listed before "B" even though +its ASCII value is higher. + +=item sort_directories_first [bool] <zd> + +Sort directories first? + +=item sort_reverse [bool] <or> + +Sort reversed? + +=item sort [string] <oa>, <ob>, <oc>, <om>, <on>, <ot>, <os> + +Which sorting mechanism should be used? Choose one of B<atime>, B<basename>, +B<ctime>, B<mtime>, B<natural>, B<type>, B<size> + +Note: You can reverse the order by using an uppercase O in the key combination. + +=item tilde_in_titlebar [bool] + +Abbreviate $HOME with ~ in the title bar (first line) of ranger? + +=item unicode_ellipsis [bool] + +Use a unicode "..." character instead of "~" to mark cut-off filenames? + +=item update_title [bool] + +Set a window title? + +=item use_preview_script [bool] <zv> + +Use the preview script defined in the setting I<preview_script>? + +=item xterm_alt_key [bool] + +Enable this if key combinations with the Alt Key don't work for you. +(Especially on xterm) + +=back + + +=head1 COMMANDS + +You can enter the commands in the console which is opened by pressing ":". + +There are additional commands which are directly translated to python +functions, one for every method in the ranger.core.actions.Actions class. +They are not documented here, since they are mostly for key bindings, not to be +typed in by a user. Read the source if you are interested in them. + +=over 2 + +=item bulkrename + +This command opens a list of selected files in an external editor. After you +edit and save the file, it will generate a shell script which does bulk +renaming according to the changes you did in the file. + +This shell script is opened in an editor for you to review. After you close +it, it will be executed. + +=item cd [I<directory>] + +The cd command changes the directory. The command C<:cd -> is equivalent to +typing ``. + +=item chain I<command1>[; I<command2>[; I<command3>...]] + +Combines multiple commands into one, separated by columns. + +=item chmod I<octal_number> + +Sets the permissions of the selection to the octal number. + +The octal number is between 000 and 777. The digits specify the permissions for +the user, the group and others. A 1 permits execution, a 2 permits writing, a +4 permits reading. Add those numbers to combine them. So a 7 permits +everything. + +Key bindings in the form of [-+]<who><what> and =<octal> also exist. For +example, B<+ar> allows reading for everyone, -ow forbids others to write and +=777 allows everything. + +See also: man 1 chmod + +=item cmap I<key> I<command> + +Binds keys for the console. Works like the C<map> command. + +=item console [-pI<N>] I<command> + +Opens the console with the command already typed in. The cursor is placed at +I<N>. + +=item copycmap I<key> I<newkey> [I<newkey2> ...] + +See C<copymap> + +=item copymap I<key> I<newkey> [I<newkey2> ...] + +Copies the keybinding I<key> to I<newkey> in the "browser" context. This is a +deep copy, so if you change the new binding (or parts of it) later, the old one +is not modified. + +To copy key bindings of the console, taskview, or pager use "copycmap", +"copytmap" or "copypmap". + +=item copypmap I<key> I<newkey> [I<newkey2> ...] + +See C<copymap> + +=item copytmap I<key> I<newkey> [I<newkey2> ...] + +See C<copymap> + +=item cunmap I<key> I<command> + +Removes key mappings of the console. Works like the C<unmap> command. + +=item delete [I<confirmation>] + +Destroy all files in the selection with a roundhouse kick. ranger will ask for +a confirmation if you attempt to delete multiple (marked) files or non-empty +directories. + +When asking for confirmation, this command will only proceed if the last given +word starts with a `y'. + +=item edit [I<filename>] + +Edit the current file or the file in the argument. + +=item eval [I<-q>] I<python_code> + +Evaluates the python code. `fm' is a reference to the FM instance. To display +text, use the function `p'. The result is displayed on the screen unless you +use the "-q" option. + +Examples: + :eval fm + :eval len(fm.env.directories) + :eval p("Hello World!") + +=item filter [I<string>] + +Displays only the files which contain the I<string> in their basename. + +=item find I<pattern> + +Search files in the current directory that match the given (case-insensitive) +regular expression pattern as you type. Once there is an unambiguous result, +it will be run immediately. (Or entered, if it's a directory.) + +=item grep I<pattern> + +Looks for a string in all marked files or directories. + +=item load_copy_buffer + +Load the copy buffer from F<~/.config/ranger/copy_buffer>. This can be used to +pass the list of copied files to another ranger instance. + +=item map I<key> I<command> + +Assign the key combination to the given command. Whenever you type the +key/keys, the command will be executed. Additionally, if you use a quantifier +when typing the key, like 5j, it will be passed to the command as the attribute +"self.quantifier". + +The keys you bind with this command are accessible in the file browser only, +not in the console, task view or pager. To bind keys there, use the commands +"cmap", "tmap" or "pmap". + +=item mark I<pattern> + +Mark all files matching the regular expression pattern. + +=item mkdir I<dirname> + +Creates a directory with the name I<dirname>. + +=item open_with [I<application>] [I<flags>] [I<mode>] + +Open the selected files with the given application, unless it is omitted, in +which case the default application is used. I<flags> are characters out of +"sdpcwSDPCW" and I<mode> is any positive integer. Their meanings are discussed +in their own sections. + +=item pmap I<key> I<command> + +Binds keys for the pager. Works like the C<map> command. + +=item punmap I<key> I<command> + +Removes key mappings of the pager. Works like the C<unmap> command. + +=item quit + +Like quit!, but closes only this tab if multiple tabs are open. + +=item quit! + +Quit ranger. The current directory will be bookmarked as ' so you can re-enter +it by typing `` or '' the next time you start ranger. + +=item rename I<newname> + +Rename the current file. If a file with that name already exists, the renaming +will fail. Also try the key binding A for appending something to a file name. + +=item save_copy_buffer + +Save the copy buffer from I<~/.config/ranger/copy_buffer>. This can be used to +pass the list of copied files to another ranger instance. + +=item search I<pattern> + +Search files in the current directory that match the given (case insensitive) +regular expression pattern. + +=item search_inc I<pattern> + +Search files in the current directory that match the given (case insensitive) +regular expression pattern. This command gets you to matching files as you +type. + +=item set I<option>=I<value> + +Assigns a new value to an option. Valid options are listed in the settings +section. Use tab completion to get the current value of an option, though this +doesn't work for functions and regular expressions. Valid values are: + + None None + bool True or False + integer 0 or 1 or -1 or 2 etc. + list [1, 2, 3] + tuple 1, 2, 3 or (1, 2, 3) + function lambda <arguments>: <expression> + regexp regexp('<pattern>') + string Anything + +=item shell [-I<flags>] I<command> + +Run a shell command. I<flags> are discussed in their own section. + +=item terminal + +Spawns the I<x-terminal-emulator> starting in the current directory. + +=item touch I<filename> + +Creates an empty file with the name I<filename>, unless it already exists. + +=item tmap I<key> I<command> + +Binds keys for the taskview. Works like the C<map> command. + +=item tunmap I<key> I<command> + +Removes key mappings of the taskview. Works like the C<unmap> command. + +=item unmap [I<keys> ...] + +Removes the given key mappings in the "browser" context. To unmap key bindings +in the console, taskview, or pager use "cunmap", "tunmap" or "punmap". + +=item unmark I<pattern> + +Unmark all files matching a regular expression pattern. + +=back + + + + +=head1 FILES + +ranger reads several configuration files which are located in +F<$HOME/.config/ranger> or F<$XDG_CONFIG_HOME/ranger> if $XDG_CONFIG_HOME is +defined. The configuration is done mostly in python. When removing a +configuration file, remove its compiled version too. (Python automatically +compiles modules. Since python3 they are saved in the __pycache__ directory, +earlier versions store them with the .pyc extension in the same directory.) + +Use the --copy-config option to obtain the default configuration files. They +include further documentation and it's too much to put here. + +You don't need to copy the whole file though, most configuration files are +overlaid on top of the defaults (F<options.py>, F<command.py>, F<rc.conf>) or +can be sub-classed (F<apps.py>, F<colorschemes>). + +When starting ranger with the B<--clean> option, it will not access or create +any of these files. + +=head2 CONFIGURATION + +=over 10 + +=item apps.py + +Controls which applications are used to open files. + +=item commands.py + +Defines commands which can be used by typing ":". + +=item rc.conf + +Contains a list of commands which are executed on startup. Mostly key bindings +are defined here. + +=item options.py + +Sets a handful of basic options. + +=item scope.sh + +This is a script that handles file previews. When the options +I<use_preview_script> and I<preview_files> or, respectively, +I<preview_directories> are set, the program specified in the option +I<preview_script> is run and its output and/or exit code determines rangers +reaction. + +=item colorschemes/ + +Colorschemes can be placed here. + +=back + +=head2 STORAGE + +=over 10 + +=item bookmarks + +This file contains a list of bookmarks. The syntax is /^(.):(.*)$/. The first +character is the bookmark key and the rest after the colon is the path to the +file. In ranger, bookmarks can be set by typing m<key>, accessed by typing +'<key> and deleted by typing um<key>. + +=item copy_buffer + +When running the command :save_copy_buffer, the paths of all currently copied +files are saved in this file. You can later run :load_copy_buffer to copy the +same files again, pass them to another ranger instance or process them in a +script. + +=item history + +Contains a list of commands that have been previously typed in. + +=item tagged + +Contains a list of tagged files. The syntax is /^(.:)?(.*)$/ where the first +letter is the optional name of the tag and the rest after the optional colon is +the path to the file. In ranger, tags can be set by pressing t and removed +with T. To assign a named tag, type "<tagname>. + +=back + + + + +=head1 ENVIRONMENT + +These environment variables have an effect on ranger: + +=over 8 + +=item EDITOR + +Defines the editor to be used for the "E" key. Defaults to the first installed +program out of "vim", "emacs" and "nano". + +=item SHELL + +Defines the shell that ranger is going to use with the :shell command and +the "S" key. Defaults to "bash". + +=item XDG_CONFIG_HOME + +Specifies the directory for configuration files. Defaults to F<$HOME/.config>. + +=item PYTHONOPTIMIZE + +This variable determines the optimize level of python. + +Using PYTHONOPTIMIZE=1 (like python -O) will make python discard assertion +statements. You will gain efficiency at the cost of losing some debug info. + +Using PYTHONOPTIMIZE=2 (like python -OO) will additionally discard any +docstrings. Using this will disable the <F1> key on commands. + +=back + + + + +=head1 EXAMPLES + +=head2 VIM: File Chooser + +This is a vim function which allows you to use ranger to select a file for +opening in your current vim session. + + fun! RangerChooser() + silent !ranger --choosefile=/tmp/chosenfile `[ -z '%' ] && echo -n . || dirname %` + if filereadable('/tmp/chosenfile') + exec 'edit ' . system('cat /tmp/chosenfile') + call system('rm /tmp/chosenfile') + endif + redraw! + endfun + map ,r :call RangerChooser()<CR> + +=head2 Bash: cd to last path after exit + +This is a bash function (for F<~/.bashrc>) to change the directory to the last +visited one after ranger quits. You can always type C<cd -> to go back to the +original one. + + function ranger-cd { + tempfile='/tmp/chosendir' + /usr/bin/ranger --choosedir="$tempfile" "${@:-$(pwd)}" + test -f "$tempfile" && + if [ "$(cat -- "$tempfile")" != "$(echo -n `pwd`)" ]; then + cd -- "$(cat "$tempfile")" + fi + rm -f -- "$tempfile" + } + + # This binds Ctrl-O to ranger-cd: + bind '"\C-o":"ranger-cd\C-m"' + + + + +=head1 LICENSE + +GNU General Public License 3 or (at your option) any later version. + + + + +=head1 LINKS + +=over + +=item Download: L<http://ranger.nongnu.org/ranger-stable.tar.gz> + +=item The project page: L<http://ranger.nongnu.org/> + +=item The mailing list: L<http://savannah.nongnu.org/mail/?group=ranger> + +=back + +ranger is maintained with the git version control system. To fetch a fresh +copy, run: + + git clone git://git.savannah.nongnu.org/ranger.git + + + + +=head1 BUGS + +Report bugs here: L<http://savannah.nongnu.org/bugs/?group=ranger> + +Please include as much relevant information as possible. For the most +diagnostic output, run ranger like this: C<PYTHONOPTIMIZE= ranger --debug> diff --git a/ranger.py b/ranger.py index 53fd8bdb..c763a8d9 100755 --- a/ranger.py +++ b/ranger.py @@ -1,8 +1,6 @@ #!/usr/bin/python -O -# -*- coding: utf-8 -*- -# -# Ranger: Explore your forest of files from inside your terminal -# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> +# ranger - a vim-inspired file manager for the console (coding: utf-8) +# Copyright (C) 2009, 2010, 2011 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 @@ -17,42 +15,37 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -# 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 +# ===================== +# This embedded bash script can be executed by sourcing this file. +# It will cd to ranger's last location after you exit it. +# The first argument specifies the command to run ranger, the +# default is simply "ranger". (Not this file itself!) +# The other arguments are passed to ranger. """": -if [ ! -z "$1" ]; then - $@ --fail-unless-cd && - if [ -z "$XDG_CONFIG_HOME" ]; then - cd "$(grep \^\' ~/.config/ranger/bookmarks | cut -b3-)" - else - cd "$(grep \^\' "$XDG_CONFIG_HOME"/ranger/bookmarks | cut -b3-)" - fi && return 0 -else - echo "usage: source path/to/ranger.py path/to/ranger.py" +tempfile='/tmp/chosendir' +ranger="${1:-ranger}" +test -z "$1" || shift +"$ranger" --choosedir="$tempfile" "${@:-$(pwd)}" +returnvalue=$? +test -f "$tempfile" && +if [ "$(cat -- "$tempfile")" != "$(echo -n `pwd`)" ]; then + cd "$(cat "$tempfile")" + rm -f -- "$tempfile" fi -return 1 -""" +return $returnvalue +""" and None import sys -import os.path +from os.path import exists, abspath # Need to find out whether or not the flag --clean was used ASAP, # because --clean is supposed to disable bytecode compilation -try: - argv = sys.argv[0:sys.argv.index('--')] -except: - argv = sys.argv +argv = sys.argv[1:sys.argv.index('--')] if '--' in sys.argv else sys.argv[1:] sys.dont_write_bytecode = '-c' in argv or '--clean' in argv -# Set the actual docstring -__doc__ = """Ranger - file browser for the unix terminal""" - -# Don't import ./ranger when running an installed binary at /usr/bin/ranger -if os.path.exists('ranger') and '/' in os.path.normpath(__file__): - try: - sys.path.remove(os.path.abspath('.')) - except: - pass +# Don't import ./ranger when running an installed binary at /usr/.../ranger +if __file__[:4] == '/usr' and exists('ranger') and abspath('.') in sys.path: + sys.path.remove(abspath('.')) # Start ranger import ranger diff --git a/ranger/__init__.py b/ranger/__init__.py index 3d2633cd..486320bb 100644 --- a/ranger/__init__.py +++ b/ranger/__init__.py @@ -1,4 +1,4 @@ -# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> +# Copyright (C) 2009, 2010, 2011 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 @@ -14,24 +14,31 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. """ -Console-based visual file manager. +A console file manager with VI key bindings. -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. +It provides a minimalistic and nice curses interface with a view on the +directory hierarchy. The secondary task of ranger is to figure out which +program you want to use to open your files with. """ import os -from ranger.core.main import main # Information __license__ = 'GPL3' -__version__ = '1.4.4' +__version__ = '1.5.0' __author__ = __maintainer__ = 'Roman Zimbelmann' __email__ = 'romanz@lavabit.com' # Constants RANGERDIR = os.path.dirname(__file__) +TICKS_BEFORE_COLLECTING_GARBAGE = 100 +TIME_BEFORE_FILE_BECOMES_GARBAGE = 1200 +MACRO_DELIMITER = '%' +LOGFILE = '/tmp/ranger_errorlog' +USAGE = '%prog [options] [path/filename]' + +# If the environment variable XDG_CONFIG_HOME is non-empty, CONFDIR is ignored +# and the configuration directory will be $XDG_CONFIG_HOME/ranger instead. +CONFDIR = '~/.config/ranger' + +from ranger.core.main import main diff --git a/ranger/api/__init__.py b/ranger/api/__init__.py index 8780fd15..cc64a7c0 100644 --- a/ranger/api/__init__.py +++ b/ranger/api/__init__.py @@ -2,5 +2,3 @@ Files in this module contain helper functions used in configuration files. """ - -DELETE_WARNING = 'delete seriously? ' diff --git a/ranger/api/apps.py b/ranger/api/apps.py index 45432705..1af3167b 100644 --- a/ranger/api/apps.py +++ b/ranger/api/apps.py @@ -1,4 +1,4 @@ -# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> +# Copyright (C) 2009, 2010, 2011 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 @@ -17,6 +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.core.runner import Context from ranger.core.shared import FileManagerAware @@ -49,10 +50,10 @@ class Applications(FileManagerAware): return self.app_editor(context) def app_pager(self, context): - return ('less', ) + tuple(context) + return 'less', context def app_editor(self, context): - return ('vim', ) + tuple(context) + return ('vim', context) """ def _meets_dependencies(self, fnc): @@ -62,6 +63,10 @@ class Applications(FileManagerAware): return True for dep in deps: + if dep == 'X': + if 'DISPLAY' not in os.environ or not os.environ['DISPLAY']: + return False + continue if hasattr(dep, 'dependencies') \ and not self._meets_dependencies(dep): return False @@ -99,9 +104,21 @@ class Applications(FileManagerAware): handler = getattr(self, 'app_' + app) except AttributeError: if app in get_executables(): - return _generic_app(app, context) + return [app] + list(context) handler = self.app_default - return handler(context) + arguments = handler(context) + # flatten + if isinstance(arguments, str): + return (arguments, ) + if arguments is None: + return None + result = [] + for obj in arguments: + if isinstance(obj, (tuple, list, Context)): + result.extend(obj) + else: + result.append(obj) + return result def has(self, app): """Returns whether an application is defined""" @@ -119,10 +136,13 @@ class Applications(FileManagerAware): @classmethod def generic(cls, *args, **keywords): flags = 'flags' in keywords and keywords['flags'] or "" + deps = 'deps' in keywords and keywords['deps'] or () for name in args: assert isinstance(name, str) if not hasattr(cls, "app_" + name): - setattr(cls, "app_" + name, _generic_wrapper(name, flags=flags)) + fnc = _generic_wrapper(name, flags=flags) + fnc = depends_on(*deps)(fnc) + setattr(cls, "app_" + name, fnc) def tup(*args): @@ -139,7 +159,10 @@ def tup(*args): def depends_on(*args): args = tuple(flatten(args)) def decorator(fnc): - fnc.dependencies = args + try: + fnc.dependencies += args + except: + fnc.dependencies = args return fnc return decorator @@ -147,7 +170,7 @@ def depends_on(*args): def _generic_app(name, context, flags=''): """Use this function when no other information is given""" context.flags += flags - return tup(name, *context) + return name, context def _generic_wrapper(name, flags=''): diff --git a/ranger/api/commands.py b/ranger/api/commands.py index 9a353eef..1479e1ce 100644 --- a/ranger/api/commands.py +++ b/ranger/api/commands.py @@ -1,4 +1,4 @@ -# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> +# Copyright (C) 2009, 2010, 2011 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 @@ -14,38 +14,51 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. import os +import ranger +import re from collections import deque from ranger.api import * from ranger.core.shared import FileManagerAware -from ranger.ext.command_parser import LazyParser as parse +from ranger.ext.lazy_property import lazy_property -# A dummy that allows the generation of docstrings in ranger.defaults.commands -def alias(*_): - pass +SETTINGS_RE = re.compile(r'^([^\s]+?)=(.*)$') +DELETE_WARNING = 'delete seriously? ' + +def alias(*_): pass # COMPAT class CommandContainer(object): def __init__(self): - self.aliases = {} self.commands = {} def __getitem__(self, key): return self.commands[key] def alias(self, new, old): - self.aliases[new] = old + try: + self.commands[new] = self.commands[old] + except: + pass def load_commands_from_module(self, module): - for varname, var in vars(module).items(): + for var in vars(module).values(): try: - if issubclass(var, Command) and var != Command: - self.commands[var.name or varname] = var + if issubclass(var, Command) and var != Command \ + and var != FunctionCommand: + self.commands[var.get_name()] = var except TypeError: pass - for new, old in self.aliases.items(): - try: - self.commands[new] = self.commands[old] - except: - pass + + def load_commands_from_object(self, obj, filtr): + for attribute_name in dir(obj): + if attribute_name[0] == '_' or attribute_name not in filtr: + continue + attribute = getattr(obj, attribute_name) + if hasattr(attribute, '__call__'): + cmd = type(attribute_name, (FunctionCommand, ), dict()) + cmd._based_function = attribute + cmd._function_name = attribute.__name__ + cmd._object_name = obj.__class__.__name__ + self.commands[attribute_name] = cmd def get_command(self, name, abbrev=True): if abbrev: @@ -73,8 +86,28 @@ class Command(FileManagerAware): """Abstract command class""" name = None allow_abbrev = True - def __init__(self, line): + resolve_macros = True + escape_macros_for_shell = False + quantifier = None + _shifted = 0 + _setting_line = None + + def __init__(self, line, quantifier=None): self.line = line + self.args = line.split() + self.quantifier = quantifier + try: + self.firstpart = line[:line.rindex(' ') + 1] + except ValueError: + self.firstpart = '' + + @classmethod + def get_name(self): + classdict = self.__mro__[0].__dict__ + if 'name' in classdict and classdict['name']: + return self.name + else: + return self.__name__ def execute(self): """Override this""" @@ -88,17 +121,71 @@ class Command(FileManagerAware): def cancel(self): """Override this""" + # Easy ways to get information + def arg(self, n): + """Returns the nth space separated word""" + try: + return self.args[n] + except IndexError: + return "" + + def rest(self, n): + """Returns everything from and after arg(n)""" + got_space = False + word_count = 0 + for i in range(len(self.line)): + if self.line[i] == " ": + if not got_space: + got_space = True + word_count += 1 + elif got_space: + got_space = False + if word_count == n + self._shifted: + return self.line[i:] + return "" + + def start(self, n): + """Returns everything until (inclusively) arg(n)""" + return ' '.join(self.args[:n]) + " " # XXX + + def shift(self): + del self.args[0] + self._shifted += 1 + + def tabinsert(self, word): + return ''.join([self._tabinsert_left, word, self._tabinsert_right]) + + def parse_setting_line(self): + if self._setting_line is not None: + return self._setting_line + match = SETTINGS_RE.match(self.rest(1)) + if match: + self.firstpart += match.group(1) + '=' + result = [match.group(1), match.group(2), True] + else: + result = [self.arg(1), self.rest(2), ' ' in self.rest(1)] + self._setting_line = result + return result + + # XXX: Lazy properties? Not so smart? self.line can change after all! + @lazy_property + def _tabinsert_left(self): + try: + return self.line[:self.line[0:self.pos].rindex(' ') + 1] + except ValueError: + return '' + + @lazy_property + def _tabinsert_right(self): + return self.line[self.pos:] + # COMPAT: this is still used in old commands.py configs def _tab_only_directories(self): - from os.path import dirname, basename, expanduser, join, isdir + from os.path import dirname, basename, expanduser, join - line = parse(self.line) cwd = self.fm.env.cwd.path - try: - rel_dest = line.rest(1) - except IndexError: - rel_dest = '' + rel_dest = self.rest(1) # expand the tilde into the user directory if rel_dest.startswith('~'): @@ -132,22 +219,19 @@ class Command(FileManagerAware): # one result. since it must be a directory, append a slash. if len(dirnames) == 1: - return line.start(1) + join(rel_dirname, dirnames[0]) + '/' + return self.start(1) + join(rel_dirname, dirnames[0]) + '/' # more than one result. append no slash, so the user can # manually type in the slash to advance into that directory - return (line.start(1) + join(rel_dirname, dirname) for dirname in dirnames) + return (self.start(1) + join(rel_dirname, dirname) + for dirname in dirnames) def _tab_directory_content(self): - from os.path import dirname, basename, expanduser, join, isdir + from os.path import dirname, basename, expanduser, join - line = parse(self.line) cwd = self.fm.env.cwd.path - try: - rel_dest = line.rest(1) - except IndexError: - rel_dest = '' + rel_dest = self.rest(1) # expand the tilde into the user directory if rel_dest.startswith('~'): @@ -182,8 +266,62 @@ class Command(FileManagerAware): # one result. since it must be a directory, append a slash. if len(names) == 1: - return line.start(1) + join(rel_dirname, names[0]) + '/' + return self.start(1) + join(rel_dirname, names[0]) + '/' # more than one result. append no slash, so the user can # manually type in the slash to advance into that directory - return (line.start(1) + join(rel_dirname, name) for name in names) + return (self.start(1) + join(rel_dirname, name) for name in names) + + +class FunctionCommand(Command): + _based_function = None + _object_name = "" + _function_name = "unknown" + def execute(self): + if not self._based_function: + return + if len(self.args) == 1: + try: + return self._based_function(**{'narg':self.quantifier}) + except TypeError: + return self._based_function() + + args, keywords = list(), dict() + for arg in self.args[1:]: + equal_sign = arg.find("=") + value = arg if (equal_sign is -1) else arg[equal_sign + 1:] + try: + value = int(value) + except: + if value in ('True', 'False'): + value = (value == 'True') + else: + try: + value = float(value) + except: + pass + + if equal_sign == -1: + args.append(value) + else: + keywords[arg[:equal_sign]] = value + + if self.quantifier is not None: + keywords['narg'] = self.quantifier + + try: + if self.quantifier is None: + return self._based_function(*args, **keywords) + else: + try: + return self._based_function(*args, **keywords) + except TypeError: + del keywords['narg'] + return self._based_function(*args, **keywords) + except TypeError: + if ranger.arg.debug: + raise + else: + self.fm.notify("Bad arguments for %s.%s: %s, %s" % + (self._object_name, self._function_name, + repr(args), repr(keywords)), bad=True) diff --git a/ranger/api/keys.py b/ranger/api/keys.py deleted file mode 100644 index 75de6237..00000000 --- a/ranger/api/keys.py +++ /dev/null @@ -1,131 +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/>. - -import os -from curses import * -from curses.ascii import * -from inspect import getargspec, ismethod - -from ranger import RANGERDIR -from ranger.api import * -from ranger.container.bookmarks import ALLOWED_KEYS as ALLOWED_BOOKMARK_KEYS -from ranger.container.tags import ALLOWED_KEYS as ALLOWED_TAGS_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 - - def __getattr__(self, attr): - if attr.startswith('_'): - raise AttributeError - def wrapper(*real_args, **real_keywords): - def function(command_argument): - args, kws = real_args, real_keywords - number = command_argument.n - direction = command_argument.direction - obj = getattr(command_argument, self.__firstattr__) - fnc = getattr(obj, attr) - if number is not None or direction is not None: - args, kws = replace_narg(number, direction, fnc, args, kws) - return fnc(*args, **kws) - return function - return wrapper - -# fm.enter_dir('~') is translated into lambda arg: arg.fm.enter_dir('~') -# this makes things like this possible: -# bind('gh', fm.enter_dir('~')) -# -# but NOT: (note the 2 dots) -# bind('H', fm.history.go(-1)) -# -# for something like that, use the long version: -# bind('H', lambda arg: arg.fm.history.go(-1)) -# -# If the method has an argument named "narg", pressing a number before -# the key will pass that number as the narg argument. If you want the -# same behaviour in a custom lambda function, you can write: -# bind('gg', fm.move(to=0)) -# as: -# bind('gg', lambda arg: narg(arg.n, arg.fm.move, to=0)) - -fm = Wrapper('fm') -wdg = Wrapper('wdg') - - -DIRARG_KEYWORD = 'dirarg' -NARG_KEYWORD = 'narg' - -def narg(number_, function_, *args_, **keywords_): - """ - This applies the replace_narg function to the arguments and keywords - and directly runs this function. - - Example: - def foo(xyz, narg): return hash((xyz, narg)) - - narg(50, foo, 123) == foo(123, narg=50) - """ - args, keywords = replace_narg(number_, function_, args_, keywords_) - return function_(*args, **keywords) - -def replace_narg(number, direction, function, args, keywords): - """ - This function returns (args, keywords) with one little change: - if <function> has a named argument called "narg", args and keywords - will be modified so that the value of "narg" will be <number>. - - def foo(xyz, narg): pass - - replace_narg(666, foo, (), {'narg': 10, 'xyz': 5}) - => (), {'narg': 666, 'xyz': 5} - - replace_narg(666, foo, (1, 2), {}) - => (1, 666), {} - """ - argspec = getargspec(function).args - args = list(args) - if number is not None and NARG_KEYWORD in argspec: - try: - # is narg in args? - index = argspec.index(NARG_KEYWORD) - if ismethod(function): - index -= 1 # because of 'self' - args[index] = number - except (ValueError, IndexError): - # is narg in keywords? - keywords = dict(keywords) - keywords[NARG_KEYWORD] = number - if direction is not None and DIRARG_KEYWORD in argspec: - try: - index = argspec.index(DIRARG_KEYWORD) - if ismethod(function): - index -= 1 # because of 'self' - args[index] = direction - except (ValueError, IndexError): - # is narg in keywords? - keywords = dict(keywords) - keywords[DIRARG_KEYWORD] = direction - return args, keywords diff --git a/ranger/api/options.py b/ranger/api/options.py index ee947b39..e2558ffb 100644 --- a/ranger/api/options.py +++ b/ranger/api/options.py @@ -1,4 +1,4 @@ -# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> +# Copyright (C) 2009, 2010, 2011 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 diff --git a/ranger/colorschemes/__init__.py b/ranger/colorschemes/__init__.py index 8b7a21a9..a442d9c7 100644 --- a/ranger/colorschemes/__init__.py +++ b/ranger/colorschemes/__init__.py @@ -1,18 +1,3 @@ -# 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/>. - """ Colorschemes are required to be located here or in CONFDIR/colorschemes/ """ diff --git a/ranger/colorschemes/default.py b/ranger/colorschemes/default.py index 317e8258..fb46dd43 100644 --- a/ranger/colorschemes/default.py +++ b/ranger/colorschemes/default.py @@ -1,4 +1,4 @@ -# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> +# Copyright (C) 2009, 2010, 2011 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 @@ -103,21 +103,6 @@ class Default(ColorScheme): attr |= bold fg = red - if context.in_pager or context.help_markup: - if context.seperator: - fg = red - elif context.link: - fg = cyan - elif context.bars: - fg = black - attr |= bold - elif context.key: - fg = green - elif context.special: - fg = cyan - elif context.title: - attr |= bold - if context.text: if context.highlight: attr |= reverse diff --git a/ranger/colorschemes/default88.py b/ranger/colorschemes/default88.py index 9c00d9ff..8bf33807 100644 --- a/ranger/colorschemes/default88.py +++ b/ranger/colorschemes/default88.py @@ -1,4 +1,4 @@ -# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> +# Copyright (C) 2009, 2010, 2011 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 diff --git a/ranger/colorschemes/jungle.py b/ranger/colorschemes/jungle.py index f5e03c06..ea2e0d94 100644 --- a/ranger/colorschemes/jungle.py +++ b/ranger/colorschemes/jungle.py @@ -1,4 +1,4 @@ -# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> +# Copyright (C) 2009, 2010, 2011 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 diff --git a/ranger/colorschemes/snow.py b/ranger/colorschemes/snow.py index a449db87..a8125ee6 100644 --- a/ranger/colorschemes/snow.py +++ b/ranger/colorschemes/snow.py @@ -1,4 +1,4 @@ -# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> +# Copyright (C) 2009, 2010, 2011 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 diff --git a/ranger/colorschemes/texas.py b/ranger/colorschemes/texas.py deleted file mode 100644 index 26e8916d..00000000 --- a/ranger/colorschemes/texas.py +++ /dev/null @@ -1,71 +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/>. - -""" -Some experimental colorscheme. -""" - -from ranger.gui.color import * -from ranger.colorschemes.default import Default -import curses - -class Scheme(Default): - def use(self, context): - fg, bg, attr = Default.use(self, context) - - if curses.COLORS < 88: - return fg, bg, attr - - dircolor = 77 - dircolor_selected = {True: 79, False: 78} - linkcolor = {True: 21, False: 48} - - if context.in_browser: - if context.media: - if context.image: - fg = 20 - elif context.video: - fg = 22 - elif context.audio: - fg = 23 - - if context.container: - fg = 32 - if context.directory: - fg = dircolor - if context.selected: - fg = dircolor_selected[context.main_column] - elif context.executable and not \ - any((context.media, context.container)): - fg = 82 - if context.link: - fg = linkcolor[context.good] - - if context.main_column: - if context.selected: - attr |= bold - if context.marked: - attr |= bold - fg = 53 - - if context.in_titlebar: - if context.hostname: - fg = context.bad and 48 or 82 - elif context.directory: - fg = dircolor - elif context.link: - fg = linkcolor[True] - - return fg, bg, attr diff --git a/ranger/container/__init__.py b/ranger/container/__init__.py index 3351cc63..62ddc67a 100644 --- a/ranger/container/__init__.py +++ b/ranger/container/__init__.py @@ -1,22 +1,4 @@ -# 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/>. - -"""This package includes container-objects which are +""" +This package includes container-objects which are used to manage stored data """ -from ranger.container.history import History -from .keymap import KeyMap, KeyManager -from .keybuffer import KeyBuffer -from .bookmarks import Bookmarks diff --git a/ranger/container/bookmarks.py b/ranger/container/bookmarks.py index f115c753..750515c5 100644 --- a/ranger/container/bookmarks.py +++ b/ranger/container/bookmarks.py @@ -1,4 +1,4 @@ -# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> +# Copyright (C) 2009, 2010, 2011 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 @@ -89,7 +89,7 @@ class Bookmarks(object): if key in self.dct: return self.dct[key] else: - raise KeyError("Nonexistant Bookmark!") + raise KeyError("Nonexistant Bookmark: `%s'!" % key) def __setitem__(self, key, value): """Bookmark <value> to the key <key>. diff --git a/ranger/container/history.py b/ranger/container/history.py index d7a45500..dd511d0e 100644 --- a/ranger/container/history.py +++ b/ranger/container/history.py @@ -1,4 +1,4 @@ -# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> +# Copyright (C) 2009, 2010, 2011 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 @@ -18,10 +18,16 @@ class HistoryEmptyException(Exception): class History(object): def __init__(self, maxlen=None, unique=True): - self._history = [] - self._index = 0 - self.maxlen = maxlen - self.unique = unique + if isinstance(maxlen, History): + self._history = list(maxlen._history) + self._index = maxlen._index + self.maxlen = maxlen.maxlen + self.unique = maxlen.unique + else: + self._history = [] + self._index = 0 + self.maxlen = maxlen + self.unique = unique def add(self, item): # Remove everything after index diff --git a/ranger/container/keybuffer.py b/ranger/container/keybuffer.py deleted file mode 100644 index 23b82a16..00000000 --- a/ranger/container/keybuffer.py +++ /dev/null @@ -1,180 +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/>. - -import curses.ascii -from collections import deque -from string import digits -from ranger.ext.keybinding_parser import parse_keybinding, \ - DIRKEY, ANYKEY, PASSIVE_ACTION -from ranger.container.keymap import Binding, KeyMap # mainly for assertions - -MAX_ALIAS_RECURSION = 20 -digitlist = set(ord(n) for n in digits) - -class KeyBuffer(object): - """The evaluator and storage for pressed keys""" - def __init__(self, keymap, direction_keys): - self.assign(keymap, direction_keys) - - def assign(self, keymap, direction_keys): - """Change the keymap and direction keys of the keybuffer""" - self.keymap = keymap - self.direction_keys = direction_keys - - def add(self, key): - """Add a key and evaluate it""" - assert isinstance(key, int) - assert key >= 0 - self.all_keys.append(key) - self.key_queue.append(key) - while self.key_queue: - key = self.key_queue.popleft() - - # evaluate quantifiers - if self.eval_quantifier and self._do_eval_quantifier(key): - return - - # evaluate the command - if self.eval_command and self._do_eval_command(key): - return - - # evaluate (the first number of) the direction-quantifier - if self.eval_quantifier and self._do_eval_quantifier(key): - return - - # evaluate direction keys {j,k,gg,pagedown,...} - if not self.eval_command: - self._do_eval_direction(key) - - def _do_eval_direction(self, key): - try: - assert isinstance(self.dir_tree_pointer, dict) - self.dir_tree_pointer = self.dir_tree_pointer[key] - except KeyError: - self.failure = True - else: - self._direction_try_to_finish() - - def _direction_try_to_finish(self): - if self.max_alias_recursion <= 0: - self.failure = True - return None - match = self.dir_tree_pointer - assert isinstance(match, (Binding, dict, KeyMap)) - if isinstance(match, KeyMap): - self.dir_tree_pointer = self.dir_tree_pointer._tree - match = self.dir_tree_pointer - if isinstance(self.dir_tree_pointer, Binding): - if match.alias: - self.key_queue.extend(parse_keybinding(match.alias)) - self.dir_tree_pointer = self.direction_keys._tree - self.max_alias_recursion -= 1 - else: - direction = match.actions['dir'].copy() - if self.direction_quant is not None: - direction.multiply(self.direction_quant) - self.directions.append(direction) - self.direction_quant = None - self.eval_command = True - self._try_to_finish() - - def _do_eval_quantifier(self, key): - if self.eval_command: - tree = self.tree_pointer - else: - tree = self.dir_tree_pointer - if key in digitlist and ANYKEY not in tree: - attr = self.eval_command and 'quant' or 'direction_quant' - if getattr(self, attr) is None: - setattr(self, attr, 0) - setattr(self, attr, getattr(self, attr) * 10 + key - 48) - else: - self.eval_quantifier = False - return None - return True - - def _do_eval_command(self, key): - assert isinstance(self.tree_pointer, dict), self.tree_pointer - try: - self.tree_pointer = self.tree_pointer[key] - except TypeError: - self.failure = True - return None - except KeyError: - try: - key in digitlist or self.direction_keys._tree[key] - self.tree_pointer = self.tree_pointer[DIRKEY] - except KeyError: - try: - self.tree_pointer = self.tree_pointer[ANYKEY] - except KeyError: - self.failure = True - return None - else: - self.matches.append(key) - assert isinstance(self.tree_pointer, (Binding, dict)) - self._try_to_finish() - else: - assert isinstance(self.tree_pointer, (Binding, dict)) - self.eval_command = False - self.eval_quantifier = True - self.dir_tree_pointer = self.direction_keys._tree - else: - if isinstance(self.tree_pointer, dict): - try: - self.command = self.tree_pointer[PASSIVE_ACTION] - except (KeyError, TypeError): - self.command = None - self._try_to_finish() - - def _try_to_finish(self): - if self.max_alias_recursion <= 0: - self.failure = True - return None - assert isinstance(self.tree_pointer, (Binding, dict, KeyMap)) - if isinstance(self.tree_pointer, KeyMap): - self.tree_pointer = self.tree_pointer._tree - if isinstance(self.tree_pointer, Binding): - if self.tree_pointer.alias: - keys = parse_keybinding(self.tree_pointer.alias) - self.key_queue.extend(keys) - self.tree_pointer = self.keymap._tree - self.max_alias_recursion -= 1 - else: - self.command = self.tree_pointer - self.done = True - - def clear(self): - """Reset the keybuffer. Do this once before the first usage.""" - self.max_alias_recursion = MAX_ALIAS_RECURSION - self.failure = False - self.done = False - self.quant = None - self.matches = [] - self.command = None - self.direction_quant = None - self.directions = [] - self.all_keys = [] - self.tree_pointer = self.keymap._tree - self.dir_tree_pointer = self.direction_keys._tree - - self.key_queue = deque() - - self.eval_quantifier = True - self.eval_command = True - - def __str__(self): - """returns a concatenation of all characters""" - return "".join("{0:c}".format(c) for c in self.all_keys) diff --git a/ranger/container/keymap.py b/ranger/container/keymap.py deleted file mode 100644 index d52a5215..00000000 --- a/ranger/container/keymap.py +++ /dev/null @@ -1,164 +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.ext.tree import Tree -from ranger.ext.direction import Direction -from ranger.ext.keybinding_parser import parse_keybinding, DIRKEY, ANYKEY - -FUNC = 'func' -DIRARG = 'dir' -ALIASARG = 'alias' - -class CommandArgs(object): - """ - A CommandArgs object is passed to the keybinding function. - - This object simply aggregates information about the pressed keys - and the current environment. - - Attributes: - fm: the FM instance - wdg: the currently focused widget (or fm, if none is focused) - keybuffer: the keybuffer object - n: the prefixed number, eg 5 in the command "5yy" - directions: a list of directions which are entered for "<dir>" - direction: the first direction object from that list - keys: a string representation of the keybuffer - matches: all keys which are entered for "<any>" - match: the first match - binding: the used Binding object - """ - def __init__(self, fm, widget, keybuf): - self.fm = fm - self.wdg = widget - self.keybuffer = keybuf - self.n = keybuf.quant - self.direction = keybuf.directions and keybuf.directions[0] or None - self.directions = keybuf.directions - self.keys = str(keybuf) - self.matches = keybuf.matches - self.match = keybuf.matches and keybuf.matches[0] or None - self.binding = keybuf.command - - @staticmethod - def from_widget(widget): - return CommandArgs(widget.fm, \ - widget, widget.env.keybuffer) - - -class KeyMap(Tree): - """Contains a tree with all the keybindings""" - def map(self, *args, **keywords): - if keywords: - return self._add_binding(*args, **keywords) - firstarg = args[-1] - if hasattr(firstarg, '__call__'): - keywords[FUNC] = firstarg - return self._add_binding(*args[:-1], **keywords) - def decorator_function(func): - keywords = {FUNC:func} - self.map(*args, **keywords) - return func - return decorator_function - - __call__ = map - - def _add_binding(self, *keys, **actions): - assert keys - bind = Binding(keys, actions) - for key in keys: - self.set(parse_keybinding(key), bind) - - def unmap(self, *keys): - for key in keys: - self.unset(parse_keybinding(key)) - - def __getitem__(self, key): - return self.traverse(parse_keybinding(key)) - - -class KeyMapWithDirections(KeyMap): - def __init__(self, *args, **keywords): - Tree.__init__(self, *args, **keywords) - self.directions = KeyMap() - - def merge(self, other): - assert hasattr(other, 'directions'), 'Merging with wrong type?' - Tree.merge(self, other) - Tree.merge(self.directions, other.directions) - - def dir(self, *args, **keywords): - if ALIASARG in keywords: - self.directions.map(*args, **keywords) - else: - self.directions.map(*args, dir=Direction(**keywords)) - - -class KeyManager(object): - def __init__(self, keybuffer, contexts): - self._keybuffer = keybuffer - self._list_of_contexts = contexts - self.clear() - - def clear(self): - self.contexts = dict() - for context in self._list_of_contexts: - self.contexts[context] = KeyMapWithDirections() - - def map(self, context, *args, **keywords): - self.get_context(context).map(*args, **keywords) - - def dir(self, context, *args, **keywords): - self.get_context(context).dir(*args, **keywords) - - def unmap(self, context, *args, **keywords): - self.get_context(context).unmap(*args, **keywords) - - def merge_all(self, keymapwithdirection): - for context, keymap in self.contexts.items(): - keymap.merge(keymapwithdirection) - - def get_context(self, context): - assert isinstance(context, str) - assert context in self.contexts, "no such context: " + context - return self.contexts[context] - - def use_context(self, context): - context = self.get_context(context) - if self._keybuffer.keymap is not context: - self._keybuffer.assign(context, context.directions) - self._keybuffer.clear() - - -class Binding(object): - """The keybinding object""" - def __init__(self, keys, actions): - assert hasattr(keys, '__iter__') - assert isinstance(actions, dict) - self.actions = actions - try: - self.function = self.actions[FUNC] - except KeyError: - self.function = None - try: - self.direction = self.actions[DIRARG] - except KeyError: - self.direction = None - try: - alias = self.actions[ALIASARG] - except KeyError: - self.alias = None - else: - self.alias = tuple(parse_keybinding(alias)) diff --git a/ranger/container/settingobject.py b/ranger/container/settingobject.py index d036245f..5c24d663 100644 --- a/ranger/container/settingobject.py +++ b/ranger/container/settingobject.py @@ -1,4 +1,4 @@ -# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> +# Copyright (C) 2009, 2010, 2011 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 @@ -22,7 +22,7 @@ ALLOWED_SETTINGS = { 'collapse_preview': bool, 'colorscheme_overlay': (type(None), type(lambda:0)), 'colorscheme': str, - 'column_ratios': (tuple, list, set), + 'column_ratios': (tuple, list), 'dirname_in_tabs': bool, 'display_size_in_main_column': bool, 'display_size_in_status_bar': bool, @@ -31,6 +31,7 @@ ALLOWED_SETTINGS = { 'draw_borders': bool, 'flushinput': bool, 'hidden_filter': lambda x: isinstance(x, str) or hasattr(x, 'match'), + 'load_default_rc': (bool, type(None)), 'max_console_history_size': (int, type(None)), 'max_history_size': (int, type(None)), 'mouse_enabled': bool, @@ -81,6 +82,8 @@ class SettingObject(SignalDispatcher, FileManagerAware): def __getattr__(self, name): assert name in ALLOWED_SETTINGS or name in self._settings, \ "No such setting: {0}!".format(name) + if name.startswith('_'): + return self.__dict__[name] try: return self._settings[name] except: diff --git a/ranger/container/tags.py b/ranger/container/tags.py index c2fe3067..9c1c68a0 100644 --- a/ranger/container/tags.py +++ b/ranger/container/tags.py @@ -1,4 +1,4 @@ -# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> +# Copyright (C) 2009, 2010, 2011 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 @@ -57,6 +57,9 @@ class Tags(object): tag = others['tag'] else: tag = self.default_tag + tag = str(tag) + if tag not in ALLOWED_KEYS: + return self.sync() for item in items: try: diff --git a/ranger/core/actions.py b/ranger/core/actions.py index 163cc3d6..e9d89061 100644 --- a/ranger/core/actions.py +++ b/ranger/core/actions.py @@ -1,4 +1,4 @@ -# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> +# Copyright (C) 2009, 2010, 2011 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 @@ -13,10 +13,12 @@ # 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 codecs import os import re import shutil import string +import tempfile from os.path import join, isdir, realpath from os import link, symlink, getcwd from inspect import cleandoc @@ -24,21 +26,21 @@ from inspect import cleandoc import ranger from ranger.ext.direction import Direction from ranger.ext.relative_symlink import relative_symlink +from ranger.ext.keybinding_parser import key_to_string, construct_keybinding from ranger.ext.shell_escape import shell_quote -from ranger import fsobject from ranger.core.shared import FileManagerAware, EnvironmentAware, \ SettingsAware from ranger.fsobject import File from ranger.core.loader import CommandLoader +MACRO_FAIL = "<\x01\x01MACRO_HAS_NO_VALUE\x01\01>" + class _MacroTemplate(string.Template): """A template for substituting macros in commands""" - delimiter = '%' - idpattern = '\d?[a-z]' + delimiter = ranger.MACRO_DELIMITER class Actions(FileManagerAware, EnvironmentAware, SettingsAware): search_method = 'ctime' - search_forward = False # -------------------------- # -- Basic Commands @@ -52,7 +54,7 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware): """Reset the filemanager, clearing the directory buffer""" old_path = self.env.cwd.path self.previews = {} - self.env.garbage_collect(-1) + self.env.garbage_collect(-1, self.tabs) self.enter_dir(old_path) def reload_cwd(self): @@ -70,8 +72,20 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware): bad = True text = str(text) self.log.appendleft(text) - if hasattr(self.ui, 'notify'): - self.ui.notify(text, duration=duration, bad=bad) + if self.ui and self.ui.is_on: + self.ui.status.notify(" ".join(text.split("\n")), + duration=duration, bad=bad) + else: + print(text) + + def abort(self): + try: + item = self.loader.queue[0] + except: + self.notify("Type Q or :quit<Enter> to exit ranger") + else: + self.notify("Aborting: " + item.get_description()) + self.loader.remove(index=0) def redraw_window(self): """Redraw the window""" @@ -79,41 +93,78 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware): def open_console(self, string='', prompt=None, position=None): """Open the console if the current UI supports that""" - if hasattr(self.ui, 'open_console'): - self.ui.open_console(string, prompt=prompt, position=position) + self.ui.open_console(string, prompt=prompt, position=position) - def execute_console(self, string=''): + def execute_console(self, string='', wildcards=[], quantifier=None): """Execute a command for the console""" - self.open_console(string=string) - self.ui.console.line = string - self.ui.console.execute() - - def substitute_macros(self, string): - return _MacroTemplate(string).safe_substitute(self._get_macros()) + command_name = string.split()[0] + cmd_class = self.commands.get_command(command_name, abbrev=False) + if cmd_class is None: + self.notify("Command not found: `%s'" % command_name, bad=True) + return + cmd = cmd_class(string) + if cmd.resolve_macros and _MacroTemplate.delimiter in string: + macros = dict(('any%d'%i, key_to_string(char)) \ + for i, char in enumerate(wildcards)) + if 'any0' in macros: + macros['any'] = macros['any0'] + try: + string = self.substitute_macros(string, additional=macros, + escape=cmd.escape_macros_for_shell) + except ValueError as e: + if ranger.arg.debug: + raise + else: + return self.notify(e) + try: + cmd_class(string, quantifier=quantifier).execute() + except Exception as e: + if ranger.arg.debug: + raise + else: + self.notify(e) + + def substitute_macros(self, string, additional=dict(), escape=False): + macros = self._get_macros() + macros.update(additional) + if escape: + for key, value in macros.items(): + if isinstance(value, list): + macros[key] = " ".join(shell_quote(s) for s in value) + elif value != MACRO_FAIL: + macros[key] = shell_quote(value) + else: + for key, value in macros.items(): + if isinstance(value, list): + macros[key] = " ".join(value) + result = _MacroTemplate(string).safe_substitute(macros) + if MACRO_FAIL in result: + raise ValueError("Could not apply macros to `%s'" % string) + return result def _get_macros(self): macros = {} + macros['rangerdir'] = ranger.RANGERDIR + if self.fm.env.cf: - macros['f'] = shell_quote(self.fm.env.cf.basename) + macros['f'] = self.fm.env.cf.basename else: - macros['f'] = '' + macros['f'] = MACRO_FAIL - macros['s'] = ' '.join(shell_quote(fl.basename) \ - for fl in self.fm.env.get_selection()) + macros['s'] = [fl.basename for fl in self.fm.env.get_selection()] - macros['c'] = ' '.join(shell_quote(fl.path) - for fl in self.fm.env.copy) + macros['c'] = [fl.path for fl in self.fm.env.copy] - macros['t'] = ' '.join(shell_quote(fl.basename) - for fl in self.fm.env.cwd.files - if fl.realpath in self.fm.tags) + macros['t'] = [fl.basename for fl in self.fm.env.cwd.files + if fl.realpath in (self.fm.tags or [])] if self.fm.env.cwd: - macros['d'] = shell_quote(self.fm.env.cwd.path) + macros['d'] = self.fm.env.cwd.path else: macros['d'] = '.' + # define d/f/s macros for each tab for i in range(1,10): try: @@ -122,10 +173,12 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware): continue tab_dir = self.fm.env.get_directory(tab_dir_path) i = str(i) - macros[i + 'd'] = shell_quote(tab_dir_path) - macros[i + 'f'] = shell_quote(tab_dir.pointed_obj.path) - macros[i + 's'] = ' '.join(shell_quote(fl.path) - for fl in tab_dir.get_selection()) + macros[i + 'd'] = tab_dir_path + macros[i + 's'] = [fl.path for fl in tab_dir.get_selection()] + if tab_dir.pointed_obj: + macros[i + 'f'] = tab_dir.pointed_obj.path + else: + macros[i + 'f'] = MACRO_FAIL # define D/F/S for the next tab found_current_tab = False @@ -143,13 +196,29 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware): next_tab_path = self.fm.tabs[first_tab] next_tab = self.fm.env.get_directory(next_tab_path) - macros['D'] = shell_quote(next_tab) - macros['F'] = shell_quote(next_tab.pointed_obj.path) - macros['S'] = ' '.join(shell_quote(fl.path) - for fl in next_tab.get_selection()) + macros['D'] = next_tab + if next_tab.pointed_obj: + macros['F'] = next_tab.pointed_obj.path + else: + macros['F'] = MACRO_FAIL + macros['S'] = [fl.path for fl in next_tab.get_selection()] return macros + def source(self, filename): + filename = os.path.expanduser(filename) + for line in open(filename, 'r'): + line = line.rstrip("\r\n") + if line.startswith("#") or not line.strip(): + continue + try: + self.execute_console(line) + except Exception as e: + if ranger.arg.debug: + raise + else: + self.notify('Error in line `%s\':\n %s' % + (line, str(e)), bad=True) def execute_file(self, files, **kw): """Execute a file. @@ -158,6 +227,11 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware): mode is a positive integer. Both flags and mode specify how the program is run.""" + # ranger can act as a file chooser when running with --choosefile=... + if ranger.arg.choosefile: + open(ranger.arg.choosefile, 'w').write(self.fm.env.cf.path) + raise SystemExit() + if isinstance(files, set): files = list(files) elif type(files) not in (list, tuple): @@ -226,23 +300,26 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware): pagesize=self.ui.browser.hei) cwd.move(to=newpos) - def move_parent(self, n): + def move_parent(self, n, narg=None): + if narg is not None: + n *= narg 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: - pass + if parent is not None: + if parent.pointer + n < 0: + n = 0 - parent.pointer + try: + self.env.enter_dir(parent.files[parent.pointer+n]) + except IndexError: + pass def history_go(self, relative): """Move back and forth in the history""" - self.env.history_go(relative) + self.env.history_go(int(relative)) def scroll(self, relative): """Scroll down by <relative> lines""" - if hasattr(self.ui, 'scroll'): - self.ui.scroll(relative) + if self.ui.browser and self.ui.browser.main_column: + self.ui.browser.main_column.scroll(relative) self.env.cf = self.env.cwd.pointed_obj def enter_dir(self, path, remember=False, history=True): @@ -281,6 +358,24 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware): # -- Shortcuts / Wrappers # -------------------------- + def pager_move(self, narg=None, **kw): + self.ui.browser.pager.move(narg=narg, **kw) + + def taskview_move(self, narg=None, **kw): + self.ui.taskview.move(narg=narg, **kw) + + def pager_close(self): + if self.ui.pager.visible: + self.ui.close_pager() + if self.ui.browser.pager.visible: + self.ui.close_embedded_pager() + + def taskview_open(self): + self.ui.open_taskview() + + def taskview_close(self): + self.ui.close_taskview() + def execute_command(self, cmd, **kw): return self.run(cmd, **kw) @@ -294,10 +389,7 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware): return self.execute_file(file, app = 'editor') - def hint(self, text): - self.ui.hint(text) - - def toggle_boolean_option(self, string): + def toggle_option(self, string): """Toggle a boolean option named <string>""" if isinstance(self.env.settings[string], bool): self.env.settings[string] ^= True @@ -319,7 +411,7 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware): except: pass - def mark(self, all=False, toggle=False, val=None, movedown=None, narg=1): + def mark_files(self, all=False, toggle=False, val=None, movedown=None, narg=1): """ A wrapper for the directory.mark_xyz functions. @@ -360,10 +452,8 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware): if movedown: self.move(down=narg) - if hasattr(self.ui, 'redraw_main_column'): - self.ui.redraw_main_column() - if hasattr(self.ui, 'status'): - self.ui.status.need_redraw = True + self.ui.redraw_main_column() + self.ui.status.need_redraw = True def mark_in_direction(self, val=True, dirarg=None): cwd = self.env.cwd @@ -386,14 +476,10 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware): except: return False self.env.last_search = text - self.search(order='search', offset=offset) + self.search_next(order='search', offset=offset) - def search(self, order=None, offset=1, forward=True): + def search_next(self, order=None, offset=1, forward=True): original_order = order - if self.search_forward: - direction = bool(forward) - else: - direction = not bool(forward) if order is None: order = self.search_method @@ -437,7 +523,6 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware): def set_search_method(self, order, forward=True): if order in ('search', 'tag', 'size', 'mimetype', 'ctime'): self.search_method = order - self.search_forward = forward # -------------------------- # -- Tags @@ -464,8 +549,7 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware): if movedown: self.move(down=1) - if hasattr(self.ui, 'redraw_main_column'): - self.ui.redraw_main_column() + self.ui.redraw_main_column() def tag_remove(self, paths=None, movedown=None): self.tag_toggle(paths=paths, value=False, movedown=movedown) @@ -482,10 +566,10 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware): """Enter the bookmark with the name <key>""" try: self.bookmarks.update_if_outdated() - destination = self.bookmarks[key] + destination = self.bookmarks[str(key)] cwd = self.env.cwd if destination.path != cwd.path: - self.bookmarks.enter(key) + self.bookmarks.enter(str(key)) self.bookmarks.remember(cwd) except KeyError: pass @@ -493,12 +577,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 + self.bookmarks[str(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) + self.bookmarks.delete(str(key)) def draw_bookmarks(self): self.ui.browser.draw_bookmarks = True @@ -512,9 +596,6 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware): # These commands open the built-in pager and set specific sources. def display_command_help(self, console_widget): - if not hasattr(self.ui, 'open_pager'): - return - try: command = console_widget._get_cmd_class() except: @@ -534,40 +615,17 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware): lines = cleandoc(command.__doc__).split('\n') pager.set_source(lines) - def display_help(self, topic='index', narg=None): - if not hasattr(self.ui, 'open_pager'): - return - - from ranger.help import get_help, get_help_by_index - - scroll_to_line = 0 - if narg is not None: - chapter, subchapter = int(str(narg)[0]), str(narg)[1:] - help_text = get_help_by_index(chapter) - lines = help_text.split('\n') - if chapter: - chapternumber = str(chapter) + '.' + subchapter + '. ' - skip_to_content = True - for line_number, line in enumerate(lines): - if skip_to_content: - if line[:10] == '==========': - skip_to_content = False - else: - if line.startswith(chapternumber): - scroll_to_line = line_number - else: - help_text = get_help(topic) - lines = help_text.split('\n') - - pager = self.ui.open_pager() - pager.set_source(lines) - pager.markup = 'help' - pager.move(down=scroll_to_line) + def display_help(self): + manualpath = self.relpath('../doc/ranger.1') + if os.path.exists(manualpath): + process = self.run(['man', manualpath]) + if process.poll() != 16: + return + process = self.run(['man', 'ranger']) + if process.poll() == 16: + self.notify("Could not find manpage.", bad=True) def display_log(self): - if not hasattr(self.ui, 'open_pager'): - return - pager = self.ui.open_pager() if self.log: pager.set_source(["Message Log:"] + list(self.log)) @@ -575,8 +633,6 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware): pager.set_source(["Message Log:", "No messages!"]) def display_file(self): - if not hasattr(self.ui, 'open_embedded_pager'): - return if not self.env.cf or not self.env.cf.is_file: return @@ -631,7 +687,15 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware): data[(-1, -1)] = None data['foundpreview'] = False elif exit == 2: - data[(-1, -1)] = open(path, 'r').read(1024 * 32) + f = codecs.open(path, 'r', errors='ignore') + try: + data[(-1, -1)] = f.read(1024 * 32) + except UnicodeDecodeError: + f.close() + f = codecs.open(path, 'r', encoding='latin-1', + errors='ignore') + data[(-1, -1)] = f.read(1024 * 32) + f.close() else: data[(-1, -1)] = None if self.env.cf.realpath == path: @@ -654,7 +718,7 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware): return found else: try: - return open(path, 'r') + return codecs.open(path, 'r', errors='ignore') except: return None @@ -694,10 +758,10 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware): if newtab != self.current_tab: self.tab_open(newtab) - def tab_new(self): + def tab_new(self, path=None): for i in range(1, 10): if not i in self.tabs: - self.tab_open(i) + self.tab_open(i, path) break def _get_tab_list(self): @@ -708,6 +772,71 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware): self.tabs[self.current_tab] = self.env.cwd.path # -------------------------- + # -- Overview of internals + # -------------------------- + + def dump_keybindings(self, *contexts): + if not contexts: + contexts = 'browser', 'console', 'pager', 'taskview' + + temporary_file = tempfile.NamedTemporaryFile() + def write(string): + temporary_file.write(string.encode('utf-8')) + + def recurse(before, pointer): + for key, value in pointer.items(): + keys = before + [key] + if isinstance(value, dict): + recurse(keys, value) + else: + write("%12s %s\n" % (construct_keybinding(keys), value)) + + for context in contexts: + write("Keybindings in `%s'\n" % context) + if context in self.env.keymaps: + recurse([], self.env.keymaps[context]) + else: + write(" None\n") + write("\n") + + temporary_file.flush() + self.run(app='pager', files=[File(temporary_file.name)]) + + def dump_commands(self): + temporary_file = tempfile.NamedTemporaryFile() + def write(string): + temporary_file.write(string.encode('utf-8')) + + undocumented = [] + for cmd_name in sorted(self.commands.commands): + cmd = self.commands.commands[cmd_name] + if hasattr(cmd, '__doc__') and cmd.__doc__: + write(cleandoc(cmd.__doc__)) + write("\n\n" + "-" * 60 + "\n") + else: + undocumented.append(cmd) + + if undocumented: + write("Undocumented commands:\n\n") + for cmd in undocumented: + write(" :%s\n" % cmd.get_name()) + + temporary_file.flush() + self.run(app='pager', files=[File(temporary_file.name)]) + + def dump_settings(self): + from ranger.container.settingobject import ALLOWED_SETTINGS + temporary_file = tempfile.NamedTemporaryFile() + def write(string): + temporary_file.write(string.encode('utf-8')) + + for setting in sorted(ALLOWED_SETTINGS): + write("%30s = %s\n" % (setting, getattr(self.settings, setting))) + + temporary_file.flush() + self.run(app='pager', files=[File(temporary_file.name)]) + + # -------------------------- # -- File System Operations # -------------------------- @@ -817,6 +946,7 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware): self.loader.add(obj) def delete(self): + # XXX: warn when deleting mount points/unseen marked files? self.notify("Deleting!") selected = self.env.get_selection() self.env.copy -= set(selected) @@ -845,6 +975,6 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware): src = src.path try: - os.rename(src, dest) + os.renames(src, dest) except OSError as err: self.notify(err) diff --git a/ranger/core/environment.py b/ranger/core/environment.py index cf140410..b5ab223d 100644 --- a/ranger/core/environment.py +++ b/ranger/core/environment.py @@ -1,4 +1,4 @@ -# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> +# Copyright (C) 2009, 2010, 2011 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 @@ -20,13 +20,11 @@ import socket from os.path import abspath, normpath, join, expanduser, isdir from ranger.fsobject import Directory -from ranger.container import KeyBuffer, KeyManager, History +from ranger.ext.keybinding_parser import KeyBuffer, KeyMaps +from ranger.container.history import History from ranger.ext.signals import SignalDispatcher from ranger.core.shared import SettingsAware -ALLOWED_CONTEXTS = ('browser', 'pager', 'embedded_pager', 'taskview', - 'console') - class Environment(SettingsAware, SignalDispatcher): """ A collection of data which is relevant for more than one class. @@ -42,8 +40,6 @@ class Environment(SettingsAware, SignalDispatcher): last_search = None pathway = None path = None - keybuffer = None - keymanager = None def __init__(self, path): SignalDispatcher.__init__(self) @@ -51,8 +47,8 @@ class Environment(SettingsAware, SignalDispatcher): self._cf = None self.pathway = () self.directories = {} - self.keybuffer = KeyBuffer(None, None) - self.keymanager = KeyManager(self.keybuffer, ALLOWED_CONTEXTS) + self.keybuffer = KeyBuffer() + self.keymaps = KeyMaps(self.keybuffer) self.copy = set() self.history = History(self.settings.max_history_size, unique=False) @@ -120,15 +116,18 @@ class Environment(SettingsAware, SignalDispatcher): except KeyError: return directory - def garbage_collect(self, age): + def garbage_collect(self, age, tabs): """Delete unused directory objects""" for key in tuple(self.directories): value = self.directories[key] - if age == -1 or \ - (value.is_older_than(age) and not value in self.pathway): - del self.directories[key] - if value.is_directory: - value.files = None + if age != -1: + if not value.is_older_than(age) or value in self.pathway: + continue + if value in tabs.values(): + continue + del self.directories[key] + if value.is_directory: + value.files = None self.settings.signal_garbage_collect() self.signal_garbage_collect() diff --git a/ranger/core/fm.py b/ranger/core/fm.py index fa972b50..93ac793b 100644 --- a/ranger/core/fm.py +++ b/ranger/core/fm.py @@ -1,4 +1,4 @@ -# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> +# Copyright (C) 2009, 2010, 2011 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 @@ -25,10 +25,11 @@ import stat import sys import ranger +from ranger import * from ranger.core.actions import Actions from ranger.container.tags import Tags -from ranger.gui.defaultui import DefaultUI -from ranger.container import Bookmarks +from ranger.gui.ui import UI +from ranger.container.bookmarks import Bookmarks from ranger.core.runner import Runner from ranger.ext.get_executables import get_executables from ranger.fsobject import Directory @@ -36,9 +37,6 @@ from ranger.ext.signals import SignalDispatcher from ranger import __version__ from ranger.core.loader import Loader -TICKS_BEFORE_COLLECTING_GARBAGE = 100 -TIME_BEFORE_FILE_BECOMES_GARBAGE = 1200 - class FM(Actions, SignalDispatcher): input_blocked = False input_blocked_until = 0 @@ -56,7 +54,7 @@ class FM(Actions, SignalDispatcher): self.current_tab = 1 self.loader = Loader() - self.log.append('Ranger {0} started! Process ID is {1}.' \ + self.log.append('ranger {0} started! Process ID is {1}.' \ .format(__version__, os.getpid())) self.log.append('Running on Python ' + sys.version.replace('\n','')) @@ -64,12 +62,6 @@ class FM(Actions, SignalDispatcher): mimetypes.knownfiles.append(self.relpath('data/mime.types')) self.mimetypes = mimetypes.MimeTypes() - # COMPAT - @property - def executables(self): - """For compatibility. Calls get_executables()""" - return get_executables() - def initialize(self): """If ui/bookmarks are None, they will be initialized here.""" if self.bookmarks is None: @@ -83,14 +75,11 @@ class FM(Actions, SignalDispatcher): autosave=self.settings.autosave_bookmarks) self.bookmarks.load() - else: - self.bookmarks = bookmarks - if not ranger.arg.clean and self.tags is None: self.tags = Tags(self.confpath('tagged')) if self.ui is None: - self.ui = DefaultUI() + self.ui = UI() self.ui.initialize() def mylogfunc(text): @@ -142,8 +131,8 @@ class FM(Actions, SignalDispatcher): copy('defaults/apps.py', 'apps.py') if which == 'commands' or which == 'all': copy('defaults/commands.py', 'commands.py') - if which == 'keys' or which == 'all': - copy('defaults/keys.py', 'keys.py') + if which == 'rc' or which == 'all': + copy('defaults/rc.conf', 'rc.conf') if which == 'options' or which == 'all': copy('defaults/options.py', 'options.py') if which == 'scope' or which == 'all': @@ -151,7 +140,7 @@ class FM(Actions, SignalDispatcher): os.chmod(self.confpath('scope.sh'), os.stat(self.confpath('scope.sh')).st_mode | stat.S_IXUSR) if which not in \ - ('all', 'apps', 'scope', 'commands', 'keys', 'options'): + ('all', 'apps', 'scope', 'commands', 'rc', 'options'): sys.stderr.write("Unknown config file `%s'\n" % which) def confpath(self, *paths): @@ -204,7 +193,8 @@ class FM(Actions, SignalDispatcher): gc_tick += 1 if gc_tick > TICKS_BEFORE_COLLECTING_GARBAGE: gc_tick = 0 - env.garbage_collect(TIME_BEFORE_FILE_BECOMES_GARBAGE) + env.garbage_collect(TIME_BEFORE_FILE_BECOMES_GARBAGE, + self.tabs) except KeyboardInterrupt: # this only happens in --debug mode. By default, interrupts diff --git a/ranger/core/helper.py b/ranger/core/helper.py index ad5541f5..995be7c5 100644 --- a/ranger/core/helper.py +++ b/ranger/core/helper.py @@ -1,4 +1,4 @@ -# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> +# Copyright (C) 2009, 2010, 2011 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 @@ -20,11 +20,9 @@ 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 optparse import OptionParser from ranger import __version__ from ranger.ext.openstruct import OpenStruct from os.path import expanduser @@ -32,29 +30,17 @@ def parse_arguments(): 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 + default_confdir = CONFDIR - parser = OptionParser(usage=usage, version=version_string) + parser = OptionParser(usage=USAGE, version='ranger '+__version__) 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('--copy-config', type='string', metavar='which', help="copy the default configs to the local config directory. " - "Possible values: all, apps, commands, keys, options, scope") + "Possible values: all, rc, apps, commands, options, scope") 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`)") @@ -73,63 +59,85 @@ def parse_arguments(): parser.add_option('--choosedir', type='string', metavar='TARGET', help="Makes ranger act like a directory chooser. When ranger quits" ", it will write the name of the last visited directory to TARGET") + parser.add_option('--list-unused-keys', action='store_true', + help="List common keys which are not bound to any action.") 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): + from ranger.core.actions import Actions import ranger.core.shared import ranger.api.commands - import ranger.api.keys + from ranger.defaults import commands + + # Load default commands + fm.commands = ranger.api.commands.CommandContainer() + exclude = ['settings'] + include = [name for name in dir(Actions) if name not in exclude] + fm.commands.load_commands_from_object(fm, include) + fm.commands.load_commands_from_module(commands) + if not clean: allow_access_to_confdir(ranger.arg.confdir, True) - # Load commands - comcont = ranger.api.commands.CommandContainer() - ranger.api.commands.alias = comcont.alias + # Load custom commands try: import commands - comcont.load_commands_from_module(commands) + fm.commands.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 + fm.apps = apps.CustomApplications() + + # Load rc.conf + custom_conf = fm.confpath('rc.conf') + default_conf = fm.relpath('defaults', 'rc.conf') + load_default_rc = fm.settings.load_default_rc - # Load keys - keymanager = ranger.core.shared.EnvironmentAware.env.keymanager - ranger.api.keys.keymanager = keymanager - from ranger.defaults import keys + if load_default_rc: + fm.source(default_conf) + if os.access(custom_conf, os.R_OK): + fm.source(custom_conf) + + # XXX Load plugins (experimental) try: - import keys - except ImportError: + plugindir = fm.confpath('plugins') + plugins = [p[:-3] for p in os.listdir(plugindir) \ + if p.endswith('.py') and not p.startswith('_')] + except: pass + else: + if not os.path.exists(fm.confpath('plugins', '__init__.py')): + f = open(fm.confpath('plugins', '__init__.py'), 'w') + f.close() + + ranger.fm = fm + for plugin in sorted(plugins): + try: + module = __import__('plugins', fromlist=[plugin]) + fm.log.append("Loaded plugin '%s'." % module) + except Exception as e: + fm.log.append("Error in plugin '%s'" % plugin) + import traceback + for line in traceback.format_exception_only(type(e), e): + fm.log.append(line) + ranger.fm = None + 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() + from ranger.defaults import apps + fm.apps = apps.CustomApplications() + fm.source(fm.relpath('defaults', 'rc.conf')) def load_apps(fm, clean): diff --git a/ranger/core/loader.py b/ranger/core/loader.py index d063148b..e1262718 100644 --- a/ranger/core/loader.py +++ b/ranger/core/loader.py @@ -1,4 +1,4 @@ -# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> +# Copyright (C) 2009, 2010, 2011 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 @@ -16,10 +16,8 @@ from collections import deque from time import time, sleep from subprocess import Popen, PIPE -from time import time from ranger.core.shared import FileManagerAware from ranger.ext.signals import SignalDispatcher -import math import os import sys import select diff --git a/ranger/core/main.py b/ranger/core/main.py index e6392387..c87a4660 100644 --- a/ranger/core/main.py +++ b/ranger/core/main.py @@ -1,4 +1,4 @@ -# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> +# Copyright (C) 2009, 2010, 2011 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 @@ -77,6 +77,19 @@ def main(): fm.tabs = dict((n+1, os.path.abspath(path)) for n, path \ in enumerate(targets[:9])) load_settings(fm, arg.clean) + + if arg.list_unused_keys: + from ranger.ext.keybinding_parser import (special_keys, + reversed_special_keys) + maps = fm.env.keymaps['browser'] + for key in sorted(special_keys.values(), key=lambda x: str(x)): + if key not in maps: + print("<%s>" % reversed_special_keys[key]) + for key in range(33, 127): + if key not in maps: + print(chr(key)) + return 1 if arg.fail_unless_cd else 0 + if fm.env.username == 'root': fm.settings.preview_files = False fm.settings.use_preview_script = False @@ -95,18 +108,21 @@ def main(): return error.args[0] finally: if crash_traceback: - filepath = fm.env.cf.path if fm.env.cf else "None" + try: + filepath = fm.env.cf.path if fm.env.cf else "None" + except: + filepath = "None" try: fm.ui.destroy() except (AttributeError, NameError): pass if crash_traceback: - print("Ranger version: %s, executed with python %s" % + print("ranger version: %s, executed with python %s" % (ranger.__version__, sys.version.split()[0])) print("Locale: %s" % '.'.join(str(s) for s in locale.getlocale())) print("Current file: %s" % filepath) print(crash_traceback) - print("Ranger crashed. " \ + print("ranger crashed. " \ "Please report this traceback at:") print("http://savannah.nongnu.org/bugs/?group=ranger&func=additem") return 1 diff --git a/ranger/core/runner.py b/ranger/core/runner.py index 53bede29..2773d2bd 100644 --- a/ranger/core/runner.py +++ b/ranger/core/runner.py @@ -1,4 +1,4 @@ -# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> +# Copyright (C) 2009, 2010, 2011 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 @@ -28,13 +28,14 @@ List of allowed flags: s: silent mode. output will be discarded. d: detach the process. p: redirect output to the pager -(An uppercase key ensures that a certain flag will not be used.) +c: run only the current file (not handled here) +w: wait for enter-press afterwards +(An uppercase key negates the respective lower case flag) """ import os import sys from subprocess import Popen, PIPE -from ranger.ext.waitpid_no_intr import waitpid_no_intr ALLOWED_FLAGS = 'sdpwcSDPWC' @@ -196,10 +197,10 @@ class Runner(object): try: process = Popen(**popen_kws) except Exception as e: - self._log("Failed to run: " + str(action)) + self._log("Failed to run: %s\n%s" % (str(action), str(e))) else: if context.wait: - waitpid_no_intr(process.pid) + process.wait() if wait_for_enter: press_enter() finally: diff --git a/ranger/core/shared.py b/ranger/core/shared.py index 38dc7cdd..e1aa5e65 100644 --- a/ranger/core/shared.py +++ b/ranger/core/shared.py @@ -1,4 +1,4 @@ -# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> +# Copyright (C) 2009, 2010, 2011 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 @@ -63,7 +63,8 @@ class SettingsAware(Awareness): settings.signal_bind('setopt.preview_script', after_setting_preview_script, priority=1) def after_setting_use_preview_script(signal): - if signal.fm.settings.preview_script is None and signal.value: + if signal.fm.settings.preview_script is None and signal.value \ + and not signal.previous: signal.fm.notify("Preview script undefined or not found!", bad=True) settings.signal_bind('setopt.use_preview_script', diff --git a/ranger/data/ranger b/ranger/data/ranger new file mode 120000 index 00000000..99d43c63 --- /dev/null +++ b/ranger/data/ranger @@ -0,0 +1 @@ +../../ranger.py \ No newline at end of file diff --git a/ranger/data/scope.sh b/ranger/data/scope.sh index ca1f7e67..aeb47a13 100755 --- a/ranger/data/scope.sh +++ b/ranger/data/scope.sh @@ -1,6 +1,7 @@ #!/bin/bash -# This script is called whenever you preview a file. -# Its output is used as the preview. ANSI color codes are supported. +# ranger supports enhanced previews. If the option "use_preview_script" +# is set to True (by default it's False), this script will be called +# and its output is displayed in ranger. ANSI color codes are supported. # NOTES: This script is considered a configuration file. If you upgrade # ranger, it will be left untouched. (You must update it yourself.) diff --git a/ranger/defaults/apps.py b/ranger/defaults/apps.py index ffa828c0..3ec6bff2 100644 --- a/ranger/defaults/apps.py +++ b/ranger/defaults/apps.py @@ -1,50 +1,86 @@ -# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> +# -*- coding: utf-8 -*- +# Copyright (C) 2009, 2010, 2011 Roman Zimbelmann <romanz@lavabit.com> +# This configuration file is licensed under the same terms as ranger. +# =================================================================== +# This is the configuration file for file type detection and application +# handling. It's all in python; lines beginning with # are comments. # -# 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. +# You can customize this in the file ~/.config/ranger/apps.py. +# It has the same syntax as this file. In fact, you can just copy this +# file there with `ranger --copy-config=apps' and make your modifications. +# But make sure you update your configs when you update ranger. # -# 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. +# In order to add application definitions "on top of" the default ones +# in your ~/.config/ranger/apps.py, you should subclass the class defined +# here like this: # -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. - -""" -This is the default ranger configuration file for filetype detection -and application handling. - -You can place this file in your ~/.config/ranger/ directory and it will be used -instead of this one. Though, to minimize your effort when upgrading ranger, -you may want to subclass CustomApplications rather than making a full copy. - -This example modifies the behaviour of "feh" and adds a custom media player: - -#### start of the ~/.config/ranger/apps.py example - from ranger.defaults.apps import CustomApplications as DefaultApps - from ranger.api.apps import * - - class CustomApplications(DefaultApps): - def app_kaffeine(self, c): - return tup('kaffeine', *c) - - def app_feh_fullscreen_by_default(self, c): - return tup('feh', '-F', *c) - - def app_default(self, c): - f = c.file #shortcut - if f.video or f.audio: - return self.app_kaffeine(c) - - if f.image and c.mode == 0: - return self.app_feh_fullscreen_by_default(c) - - return DefaultApps.app_default(self, c) -#### end of the example -""" +# from ranger.defaults.apps import CustomApplications as DefaultApps +# class CustomApplications(DeafultApps): +# <your definitions here> +# +# =================================================================== +# This system is based on things called MODES and FLAGS. You can read +# in the man page about them. To remind you, here's a list of all flags. +# An uppercase flag inverts previous flags of the same name. +# s Silent mode. Output will be discarded. +# d Detach the process. (Run in background) +# p Redirect output to the pager +# w Wait for an Enter-press when the process is done +# c Run the current file only, instead of the selection +# +# To implement flags in this file, you could do this: +# context.flags += "d" +# Another example: +# context.flags += "Dw" +# +# To implement modes in this file, you can do something like: +# if context.mode == 1: +# <run in one way> +# elif context.mode == 2: +# <run in another way> +# +# =================================================================== +# The methods are called with a "context" object which provides some +# attributes that transfer information. Relevant attributes are: +# +# mode -- a number, mainly used in determining the action in app_xyz() +# flags -- a string with flags which change the way programs are run +# files -- a list containing files, mainly used in app_xyz +# filepaths -- a list of the paths of each file +# file -- an arbitrary file from that list (or None) +# fm -- the filemanager instance +# popen_kws -- keyword arguments which are directly passed to Popen +# +# =================================================================== +# The return value of the functions should be either: +# 1. A reference to another app, like: +# return self.app_editor(context) +# +# 2. A call to the "either" method, which uses the first program that +# is installed on your system. If none are installed, None is returned. +# return self.either(context, "libreoffice", "soffice", "ooffice") +# +# 3. A tuple of arguments that should be run. +# return "mplayer", "-fs", context.file.path +# If you use lists instead of strings, they will be flattened: +# args = ["-fs", "-shuf"] +# return "mplayer", args, context.filepaths +# "context.filepaths" can, and will often be abbreviated with just "context": +# return "mplayer", context +# +# 4. "None" to indicate that no action was found. +# return None +# +# =================================================================== +# When using the "either" method, ranger determines which program to +# pick by looking at its dependencies. You can set dependencies by +# adding the decorator "depends_on": +# @depends_on("vim") +# def app_vim(self, context): +# .... +# There is a special keyword which you can use as a dependence: "X" +# This ensures that the program will only run when X is running. +# =================================================================== import ranger from ranger.api.apps import * @@ -55,13 +91,9 @@ class CustomApplications(Applications): """How to determine the default application?""" f = c.file - # ranger can act as a file chooser when running with --choosefile=... - if ranger.arg.choosefile: - open(ranger.arg.choosefile, 'w').write(f.path) - raise SystemExit() - - if f.basename.lower() == 'makefile': - return self.either(c, 'make') + if f.basename.lower() == 'makefile' and c.mode == 1: + made = self.either(c, 'make') + if made: return made if f.extension is not None: if f.extension in ('pdf', ): @@ -77,7 +109,7 @@ class CustomApplications(Applications): return self.either(c, 'firefox', 'opera', 'jumanji', 'luakit') if f.extension == 'nes': return self.either(c, 'fceux') - if f.extension in ('swc', 'smc'): + if f.extension in ('swc', 'smc', 'sfc'): return self.either(c, 'zsnes') if f.extension in ('odt', 'ods', 'odp', 'odf', 'odg', 'doc', 'xls'): @@ -93,10 +125,14 @@ class CustomApplications(Applications): if f.video or f.audio: if f.video: c.flags += 'd' - return self.either(c, 'mplayer', 'smplayer', 'vlc', 'totem') + return self.either(c, 'mplayer2', 'mplayer', 'smplayer', 'vlc', + 'totem') if f.image: - return self.either(c, 'feh', 'eog', 'mirage') + if c.mode in (11, 12, 13, 14): + return self.either(c, 'set_bg_with_feh') + else: + return self.either(c, 'sxiv', 'feh', 'eog', 'mirage') if f.document or f.filetype.startswith('text') or f.size == 0: return self.either(c, 'editor') @@ -109,7 +145,7 @@ class CustomApplications(Applications): # ----------------------------------------- application definitions # Note: Trivial application definitions are at the bottom def app_pager(self, c): - return tup('less', '-R', *c) + return 'less', '-R', c def app_editor(self, c): try: @@ -132,53 +168,76 @@ class CustomApplications(Applications): @depends_on('mplayer') def app_mplayer(self, c): if c.mode is 1: - return tup('mplayer', '-fs', *c) + return 'mplayer', '-fs', c elif c.mode is 2: args = "mplayer -fs -sid 0 -vfm ffmpeg -lavdopts " \ "lowres=1:fast:skiploopfilter=all:threads=8".split() args.extend(c) - return tup(*args) + return args elif c.mode is 3: - return tup('mplayer', '-mixer', 'software', *c) + return 'mplayer', '-mixer', 'software', c else: - return tup('mplayer', *c) + return 'mplayer', c - @depends_on('feh') - def app_feh(self, c): - arg = {1: '--bg-scale', 2: '--bg-tile', 3: '--bg-center'} + @depends_on('mplayer2') + def app_mplayer2(self, c): + args = list(self.app_mplayer(c)) + args[0] += '2' + return args + # A dependence on "X" means: this programs requires a running X server! + @depends_on('feh', 'X') + def app_set_bg_with_feh(self, c): c.flags += 'd' + arg = {11: '--bg-scale', 12: '--bg-tile', 13: '--bg-center', + 14: '--bg-fill'} + if c.mode in arg: + return 'feh', arg[c.mode], c.file.path + return 'feh', arg[11], c.file.path - if c.mode in arg: # mode 1, 2 and 3 will set the image as the background - return tup('feh', arg[c.mode], c.file.path) - if c.mode is 11 and len(c.files) is 1: # view all files in the cwd - images = (f.basename for f in self.fm.env.cwd.files if f.image) - return tup('feh', '--start-at', c.file.basename, *images) - return tup('feh', *c) + @depends_on('feh', 'X') + def app_feh(self, c): + c.flags += 'd' + if c.mode is 0 and len(c.files) is 1: # view all files in the cwd + images = [f.basename for f in self.fm.env.cwd.files if f.image] + return 'feh', '--start-at', c.file.basename, images + return 'feh', c + + @depends_on('sxiv', 'X') + def app_sxiv(self, c): + c.flags = 'd' + c.flags + if len(c.files) is 1: + images = [f.basename for f in self.fm.env.cwd.files if f.image] + try: + position = images.index(c.file.basename) + 1 + except: + return None + return 'sxiv', '-n', str(position), images + return 'sxiv', c @depends_on('aunpack') def app_aunpack(self, c): if c.mode is 0: c.flags += 'p' - return tup('aunpack', '-l', c.file.path) - return tup('aunpack', c.file.path) + return 'aunpack', '-l', c.file.path + return 'aunpack', c.file.path - @depends_on('file-roller') + @depends_on('file-roller', 'X') def app_file_roller(self, c): c.flags += 'd' - return tup('file-roller', c.file.path) + return 'file-roller', c.file.path @depends_on('make') def app_make(self, c): if c.mode is 0: - return tup("make") + return "make" if c.mode is 1: - return tup("make", "install") + return "make", "install" if c.mode is 2: - return tup("make", "clear") + return "make", "clear" @depends_on('java') def app_java(self, c): @@ -187,38 +246,44 @@ class CustomApplications(Applications): return file.path[:file.path.index('.')] return file.path files_without_extensions = map(strip_extensions, c.files) - return tup("java", files_without_extensions) + return "java", files_without_extensions - @depends_on('totem') + @depends_on('totem', 'X') def app_totem(self, c): if c.mode is 0: - return tup("totem", *c) + return "totem", c if c.mode is 1: - return tup("totem", "--fullscreen", *c) + return "totem", "--fullscreen", c @depends_on('mimeopen') def app_mimeopen(self, c): if c.mode is 0: - return tup("mimeopen", *c) + return "mimeopen", c if c.mode is 1: # Will ask user to select program # aka "Open with..." - return tup("mimeopen", "--ask", *c) + return "mimeopen", "--ask", c + # Often a programs invocation is trivial. For example: # vim test.py readme.txt [...] +# # This could be implemented like: # @depends_on("vim") -# def app_vim(self, c): -# return tup("vim", *c.files) -# Instead of creating such a generic function for each program, just add -# its name here and it will be automatically done for you. -CustomApplications.generic('vim', 'fceux', 'elinks', 'wine', - 'zsnes', 'javac') - -# By setting flags='d', this programs will not block ranger's terminal: +# def app_vim(self, context): +# return "vim", context +# +# But this is redundant and ranger does this automatically. However, sometimes +# you want to change some properties like flags or dependencies. +# The method "generic" defines a generic method for the given programs which +# looks like the one above, but you can add dependencies and flags here. +# Add programs (that are not defined yet) here if they should only run in X: +CustomApplications.generic('fceux', 'wine', 'zsnes', deps=['X']) + +# Add those which should only run in X AND should be detached/forked here: CustomApplications.generic('opera', 'firefox', 'apvlv', 'evince', - 'zathura', 'gimp', 'mirage', 'eog', flags='d') + 'zathura', 'gimp', 'mirage', 'eog', 'jumanji', + flags='d', deps=['X']) # What filetypes are recognized as scripts for interpreted languages? # This regular expression is used in app_default() diff --git a/ranger/defaults/commands.py b/ranger/defaults/commands.py index ad3fa0e5..0b472378 100644 --- a/ranger/defaults/commands.py +++ b/ranger/defaults/commands.py @@ -1,80 +1,116 @@ -# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> +# -*- coding: utf-8 -*- +# Copyright (C) 2009, 2010, 2011 Roman Zimbelmann <romanz@lavabit.com> +# This configuration file is licensed under the same terms as ranger. +# =================================================================== +# This file contains ranger's commands. +# It's all in python; lines beginning with # are comments. # -# 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. +# Note that additional commands are automatically generated from the methods +# of the class ranger.core.actions.Actions. # -# 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 can customize commands in the file ~/.config/ranger/commands.py. +# It has the same syntax as this file. In fact, you can just copy this +# file there with `ranger --copy-config=commands' and make your modifications. +# But make sure you update your configs when you update ranger. # -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. - -''' -This is the default file for command definitions. - -Each command is a subclass of `Command'. Several methods are defined -to interface with the console: - execute: call this method when the command is executed. - cancel: call this method when closing the console without executing. - tab: call this method when tab is pressed. - quick: call this method after each keypress. - -The return values for tab() can be either: - None: There is no tab completion - A string: Change the console to this string - A list/tuple/generator: cycle through every item in it -The return value for quick() can be: - False: Nothing happens - True: Execute the command afterwards -The return value for execute() doesn't matter. - -If you want to add custom commands, you can create a file -~/.config/ranger/commands.py, add the line: - from ranger.api.commands import * - -and write some command definitions, for example: - - class tabnew(Command): - def execute(self): - self.fm.tab_new() - - class tabgo(Command): - """ - :tabgo <n> - - Go to the nth tab. - """ - def execute(self): - num = self.line.split()[1] - self.fm.tab_open(int(num)) - -For a list of all actions, check /ranger/core/actions.py. -''' +# =================================================================== +# Every class defined here which is a subclass of `Command' will be used as a +# command in ranger. Several methods are defined to interface with ranger: +# execute(): called when the command is executed. +# cancel(): called when closing the console. +# tab(): called when <TAB> is pressed. +# quick(): called after each keypress. +# +# The return values for tab() can be either: +# None: There is no tab completion +# A string: Change the console to this string +# A list/tuple/generator: cycle through every item in it +# +# The return value for quick() can be: +# False: Nothing happens +# True: Execute the command afterwards +# +# The return value for execute() and cancel() doesn't matter. +# +# =================================================================== +# Commands have certain attributes and methods that facilitate parsing of +# the arguments: +# +# self.line: The whole line that was written in the console. +# self.args: A list of all (space-separated) arguments to the command. +# self.quantifier: If this command was mapped to the key "X" and +# the user pressed 6X, self.quantifier will be 6. +# self.arg(n): The n-th argument, or an empty string if it doesn't exist. +# self.rest(n): The n-th argument plus everything that followed. For example, +# If the command was "search foo bar a b c", rest(2) will be "bar a b c" +# self.start(n): The n-th argument and anything before it. For example, +# If the command was "search foo bar a b c", rest(2) will be "bar a b c" +# +# =================================================================== +# And this is a little reference for common ranger functions and objects: +# +# self.fm: A reference to the "fm" object which contains most information +# about ranger. +# self.fm.notify(string): Print the given string on the screen. +# self.fm.notify(string, bad=True): Print the given string in RED. +# self.fm.reload_cwd(): Reload the current working directory. +# self.fm.env.cwd: The current working directory. (A File object.) +# self.fm.env.cf: The current file. (A File object too.) +# self.fm.env.cwd.get_selection(): A list of all selected files. +# self.fm.execute_console(string): Execute the string as a ranger command. +# self.fm.open_console(string): Open the console with the given string +# already typed in for you. +# self.fm.move(direction): Moves the cursor in the given direction, which +# can be something like down=3, up=5, right=1, left=1, to=6, ... +# +# File objects (for example self.fm.env.cf) have these useful attributes and +# methods: +# +# cf.path: The path to the file. +# cf.basename: The base name only. +# cf.load_content(): Force a loading of the directories content (which +# obviously works with directories only) +# cf.is_directory: True/False depending on whether it's a directory. +# +# For advanced commands it is unavoidable to dive a bit into the source code +# of ranger. +# =================================================================== from ranger.api.commands import * from ranger.ext.get_executables import get_executables from ranger.core.runner import ALLOWED_FLAGS -alias('e', 'edit') -alias('q', 'quit') -alias('q!', 'quitall') -alias('qall', 'quitall') +class alias(Command): + """ + :alias <newcommand> <oldcommand> + + Copies the oldcommand as newcommand. + """ + def execute(self): + if not self.arg(1) or not self.arg(2): + self.fm.notify('Syntax: alias <newcommand> <oldcommand>', bad=True) + else: + self.fm.commands.alias(self.arg(1), self.arg(2)) class cd(Command): """ - :cd <dirname> + :cd [-r] <dirname> The cd command changes the directory. The command 'cd -' is equivalent to typing ``. + Using the option "-r" will get you to the real path. """ def execute(self): - line = parse(self.line) - destination = line.rest(1) + if self.arg(1) == '-r': + import os.path + self.shift() + destination = os.path.realpath(self.rest(1)) + if os.path.isfile(destination): + destination = os.path.dirname(destination) + else: + destination = self.rest(1) + if not destination: destination = '~' @@ -84,15 +120,10 @@ class cd(Command): self.fm.cd(destination) def tab(self): - from os.path import dirname, basename, expanduser, join, isdir + from os.path import dirname, basename, expanduser, join - line = parse(self.line) cwd = self.fm.env.cwd.path - - try: - rel_dest = line.rest(1) - except IndexError: - rel_dest = '' + rel_dest = self.rest(1) bookmarks = [v.path for v in self.fm.bookmarks.dct.values() if rel_dest in v.path ] @@ -130,32 +161,43 @@ class cd(Command): # one result. since it must be a directory, append a slash. if len(dirnames) == 1: - return line.start(1) + join(rel_dirname, dirnames[0]) + '/' + return self.start(1) + join(rel_dirname, dirnames[0]) + '/' # more than one result. append no slash, so the user can # manually type in the slash to advance into that directory - return (line.start(1) + join(rel_dirname, dirname) for dirname in dirnames) + return (self.start(1) + join(rel_dirname, dirname) for dirname in dirnames) + + +class chain(Command): + """ + :chain <command1>; <command2>; ... + Calls multiple commands at once, separated by semicolons. + """ + def execute(self): + for command in self.rest(1).split(";"): + self.fm.execute_console(command) class search(Command): def execute(self): - self.fm.search_file(parse(self.line).rest(1), regexp=True) + self.fm.search_file(self.rest(1), regexp=True) class search_inc(Command): def quick(self): - self.fm.search_file(parse(self.line).rest(1), regexp=True, offset=0) + self.fm.search_file(self.rest(1), regexp=True, offset=0) class shell(Command): + escape_macros_for_shell = True + def execute(self): - line = parse(self.line) - if line.chunk(1) and line.chunk(1)[0] == '-': - flags = line.chunk(1)[1:] - command = line.rest(2) + if self.arg(1) and self.arg(1)[0] == '-': + flags = self.arg(1)[1:] + command = self.rest(2) else: flags = '' - command = line.rest(1) + command = self.rest(1) if not command and 'p' in flags: command = 'cat %f' if command: @@ -164,13 +206,10 @@ class shell(Command): self.fm.execute_command(command, flags=flags) def tab(self): - line = parse(self.line) - if line.chunk(1) and line.chunk(1)[0] == '-': - flags = line.chunk(1)[1:] - command = line.rest(2) + if self.arg(1) and line.arg(1)[0] == '-': + command = self.rest(2) else: - flags = '' - command = line.rest(1) + command = self.rest(1) start = self.line[0:len(self.line) - len(command)] try: @@ -188,8 +227,7 @@ class shell(Command): class open_with(Command): def execute(self): - line = parse(self.line) - app, flags, mode = self._get_app_flags_mode(line.rest(1)) + app, flags, mode = self._get_app_flags_mode(self.rest(1)) self.fm.execute_file( files = [f for f in self.fm.env.cwd.get_selection()], app = app, @@ -270,8 +308,7 @@ class open_with(Command): return app, flags, int(mode) def _get_tab(self): - line = parse(self.line) - data = line.rest(1) + data = self.rest(1) if ' ' not in data: all_apps = self.fm.apps.all() if all_apps: @@ -303,19 +340,17 @@ class find(Command): tab = Command._tab_directory_content def execute(self): - if self.count == 1: + if self.quick(): self.fm.move(right=1) self.fm.block_input(0.5) else: - self.fm.cd(parse(self.line).rest(1)) + self.fm.cd(self.rest(1)) def quick(self): self.count = 0 - line = parse(self.line) cwd = self.fm.env.cwd - try: - arg = line.rest(1) - except IndexError: + arg = self.rest(1) + if not arg: return False if arg == '.': @@ -352,10 +387,10 @@ class set_(Command): """ name = 'set' # don't override the builtin set class def execute(self): - line = parse(self.line) - name = line.chunk(1) - name, value, _ = line.parse_setting_line() + name = self.arg(1) + name, value, _ = self.parse_setting_line() if name and value: + from re import compile as regexp try: value = eval(value) except: @@ -363,21 +398,20 @@ class set_(Command): self.fm.settings[name] = value def tab(self): - line = parse(self.line) - name, value, name_done = line.parse_setting_line() + name, value, name_done = self.parse_setting_line() settings = self.fm.settings if not name: - return (line + setting for setting in settings) + return (self.firstpart + setting for setting in settings) if not value and not name_done: - return (line + setting for setting in settings \ + return (self.firstpart + setting for setting in settings \ if setting.startswith(name)) if not value: - return line + repr(settings[name]) + return self.firstpart + repr(settings[name]) if bool in settings.types_of(name): if 'true'.startswith(value.lower()): - return line + 'True' + return self.firstpart + 'True' if 'false'.startswith(value.lower()): - return line + 'False' + return self.firstpart + 'False' class quit(Command): @@ -444,8 +478,7 @@ class delete(Command): allow_abbrev = False def execute(self): - line = parse(self.line) - lastword = line.chunk(-1) + lastword = self.arg(-1) if lastword.startswith('y'): # user confirmed deletion! @@ -478,8 +511,7 @@ class mark(Command): def execute(self): import re cwd = self.fm.env.cwd - line = parse(self.line) - input = line.rest(1) + input = self.rest(1) searchflags = re.UNICODE if input.lower() == input: # "smartcase" searchflags |= re.IGNORECASE @@ -491,6 +523,23 @@ class mark(Command): self.fm.ui.need_redraw = True +class console(Command): + """ + :console <command> + + Open the console with the given command. + """ + def execute(self): + position = None + if self.arg(1)[0:2] == '-p': + try: + position = int(self.arg(1)[2:]) + self.shift() + except: + pass + self.fm.open_console(self.rest(1), position=position) + + class load_copy_buffer(Command): """ :load_copy_buffer @@ -502,9 +551,11 @@ class load_copy_buffer(Command): from ranger.fsobject import File from os.path import exists try: - f = open(self.fm.confpath(self.copy_buffer_filename), 'r') + fname = self.fm.confpath(self.copy_buffer_filename) + f = open(fname, 'r') except: - return self.fm.notify("Cannot open file %s" % fname, bad=True) + return self.fm.notify("Cannot open %s" % \ + (fname or self.copy_buffer_filename), bad=True) self.fm.env.copy = set(File(g) \ for g in f.read().split("\n") if exists(g)) f.close() @@ -519,10 +570,13 @@ class save_copy_buffer(Command): """ copy_buffer_filename = 'copy_buffer' def execute(self): + fname = None try: - f = open(self.fm.confpath(self.copy_buffer_filename), 'w') + fname = self.fm.confpath(self.copy_buffer_filename) + f = open(fname, 'w') except: - return self.fm.notify("Cannot open file %s" % fname, bad=True) + return self.fm.notify("Cannot open %s" % \ + (fname or self.copy_buffer_filename), bad=True) f.write("\n".join(f.path for f in self.fm.env.copy)) f.close() @@ -547,8 +601,7 @@ class mkdir(Command): from os.path import join, expanduser, lexists from os import mkdir - line = parse(self.line) - dirname = join(self.fm.env.cwd.path, expanduser(line.rest(1))) + dirname = join(self.fm.env.cwd.path, expanduser(self.rest(1))) if not lexists(dirname): mkdir(dirname) else: @@ -564,12 +617,10 @@ class touch(Command): def execute(self): from os.path import join, expanduser, lexists - from os import mkdir - line = parse(self.line) - fname = join(self.fm.env.cwd.path, expanduser(line.rest(1))) + fname = join(self.fm.env.cwd.path, expanduser(self.rest(1))) if not lexists(fname): - open(fname, 'a') + open(fname, 'a').close() else: self.fm.notify("file/directory exists!", bad=True) @@ -582,11 +633,10 @@ class edit(Command): """ def execute(self): - line = parse(self.line) - if not line.chunk(1): + if not self.arg(1): self.fm.edit_file(self.fm.env.cf.path) else: - self.fm.edit_file(line.rest(1)) + self.fm.edit_file(self.rest(1)) def tab(self): return self._tab_directory_content() @@ -594,7 +644,7 @@ class edit(Command): class eval_(Command): """ - :eval <python code> + :eval [-q] <python code> Evaluates the python code. `fm' is a reference to the FM instance. @@ -606,18 +656,28 @@ class eval_(Command): :eval p("Hello World!") """ name = 'eval' + resolve_macros = False def execute(self): - code = parse(self.line).rest(1) + if self.arg(1) == '-q': + code = self.rest(2) + quiet = True + else: + code = self.rest(1) + quiet = False + import ranger + global cmd, fm, p, quantifier fm = self.fm + cmd = self.fm.execute_console p = fm.notify + quantifier = self.quantifier try: try: result = eval(code) except SyntaxError: exec(code) else: - if result: + if result and not quiet: p(result) except Exception as err: p(err) @@ -632,11 +692,21 @@ class rename(Command): def execute(self): from ranger.fsobject import File - line = parse(self.line) - if not line.rest(1): + from os import access + + new_name = self.rest(1) + + if not new_name: return self.fm.notify('Syntax: rename <newname>', bad=True) - self.fm.rename(self.fm.env.cf, line.rest(1)) - f = File(line.rest(1)) + + if new_name == self.fm.env.cf.basename: + return + + if access(new_name, os.F_OK): + return self.fm.notify("Can't rename: file already exists!", bad=True) + + self.fm.rename(self.fm.env.cf, new_name) + f = File(new_name) self.fm.env.cwd.pointed_obj = f self.fm.env.cf = f @@ -658,8 +728,9 @@ class chmod(Command): """ def execute(self): - line = parse(self.line) - mode = line.rest(1) + mode = self.rest(1) + if not mode: + mode = str(self.quantifier) try: mode = int(mode, 8) @@ -683,6 +754,196 @@ class chmod(Command): pass +class bulkrename(Command): + """ + :bulkrename + + This command opens a list of selected files in an external editor. + After you edit and save the file, it will generate a shell script + which does bulk renaming according to the changes you did in the file. + + This shell script is opened in an editor for you to review. + After you close it, it will be executed. + """ + def execute(self): + import sys + import tempfile + from ranger.fsobject.file import File + from ranger.ext.shell_escape import shell_escape as esc + py3 = sys.version > "3" + + # Create and edit the file list + filenames = [f.basename for f in self.fm.env.get_selection()] + listfile = tempfile.NamedTemporaryFile() + + if py3: + listfile.write("\n".join(filenames).encode("utf-8")) + else: + listfile.write("\n".join(filenames)) + listfile.flush() + self.fm.execute_file([File(listfile.name)], app='editor') + listfile.seek(0) + if py3: + new_filenames = listfile.read().decode("utf-8").split("\n") + else: + new_filenames = listfile.read().split("\n") + listfile.close() + if all(a == b for a, b in zip(filenames, new_filenames)): + self.fm.notify("No renaming to be done!") + return + + # Generate and execute script + cmdfile = tempfile.NamedTemporaryFile() + cmdfile.write(b"# This file will be executed when you close the editor.\n") + cmdfile.write(b"# Please double-check everything, clear the file to abort.\n") + if py3: + cmdfile.write("\n".join("mv -vi " + esc(old) + " " + esc(new) \ + for old, new in zip(filenames, new_filenames) \ + if old != new).encode("utf-8")) + else: + cmdfile.write("\n".join("mv -vi " + esc(old) + " " + esc(new) \ + for old, new in zip(filenames, new_filenames) if old != new)) + cmdfile.flush() + self.fm.execute_file([File(cmdfile.name)], app='editor') + self.fm.run(['/bin/sh', cmdfile.name], flags='w') + cmdfile.close() + + +class help_(Command): + """ + :help + + Display ranger's manual page. + """ + name = 'help' + def execute(self): + if self.quantifier == 1: + self.fm.dump_keybindings() + elif self.quantifier == 2: + self.fm.dump_commands() + elif self.quantifier == 3: + self.fm.dump_settings() + else: + self.fm.display_help() + + +class copymap(Command): + """ + :copymap <keys> <newkeys1> [<newkeys2>...] + Copies a "browser" keybinding from <keys> to <newkeys> + """ + context = 'browser' + + def execute(self): + if not self.arg(1) or not self.arg(2): + return self.notify("Not enough arguments", bad=True) + + for arg in self.args[2:]: + self.fm.env.keymaps.copy(self.context, self.arg(1), arg) + + +class copypmap(copymap): + """ + :copypmap <keys> <newkeys1> [<newkeys2>...] + Copies a "pager" keybinding from <keys> to <newkeys> + """ + context = 'pager' + + +class copycmap(copymap): + """ + :copycmap <keys> <newkeys1> [<newkeys2>...] + Copies a "console" keybinding from <keys> to <newkeys> + """ + context = 'console' + + +class copytmap(copymap): + """ + :copycmap <keys> <newkeys1> [<newkeys2>...] + Copies a "taskview" keybinding from <keys> to <newkeys> + """ + context = 'taskview' + + +class unmap(Command): + """ + :unmap <keys> [<keys2>, ...] + Remove the given "browser" mappings + """ + context = 'browser' + + def execute(self): + for arg in self.args[1:]: + self.fm.env.keymaps.unbind(self.context, arg) + + +class cunmap(unmap): + """ + :cunmap <keys> [<keys2>, ...] + Remove the given "console" mappings + """ + context = 'browser' + + +class punmap(unmap): + """ + :punmap <keys> [<keys2>, ...] + Remove the given "pager" mappings + """ + context = 'pager' + + +class tunmap(unmap): + """ + :tunmap <keys> [<keys2>, ...] + Remove the given "taskview" mappings + """ + context = 'taskview' + + +class map_(Command): + """ + :map <keysequence> <command> + Maps a command to a keysequence in the "browser" context. + + Example: + map j move down + map J move down 10 + """ + name = 'map' + context = 'browser' + resolve_macros = False + + def execute(self): + self.fm.env.keymaps.bind(self.context, self.arg(1), self.rest(2)) + + +class cmap(map_): + """:cmap <keysequence> <command> + Maps a command to a keysequence in the "console" context. + + Example: + cmap <ESC> console_close + cmap <C-x> console_type test + """ + context = 'console' + + +class tmap(map_): + """:tmap <keysequence> <command> + Maps a command to a keysequence in the "taskview" context. + """ + context = 'taskview' + + +class pmap(map_): + """:pmap <keysequence> <command> + Maps a command to a keysequence in the "pager" context. + """ + context = 'pager' + + class filter(Command): """ :filter <string> @@ -691,8 +952,7 @@ class filter(Command): """ def execute(self): - line = parse(self.line) - self.fm.set_filter(line.rest(1)) + self.fm.set_filter(self.rest(1)) self.fm.reload_cwd() @@ -704,9 +964,8 @@ class grep(Command): """ def execute(self): - line = parse(self.line) - if line.rest(1): + if self.rest(1): action = ['grep', '--color=always', '--line-number'] - action.extend(['-e', line.rest(1), '-r']) + action.extend(['-e', self.rest(1), '-r']) action.extend(f.path for f in self.fm.env.get_selection()) self.fm.execute_command(action, flags='p') diff --git a/ranger/defaults/keys.py b/ranger/defaults/keys.py deleted file mode 100644 index 07591691..00000000 --- a/ranger/defaults/keys.py +++ /dev/null @@ -1,439 +0,0 @@ -# -*- coding: utf-8 -*- -# 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/>. - -""" -This is the default key configuration file of ranger. -Syntax for binding keys: map(*keys, fnc) - -Examples for keys: "x", "gg", "<C-J><A-4>", "<tab>", "<down><up><right>" - -fnc is a function which is called with the CommandArgs object. - -The CommandArgs object has these attributes: -arg.fm: the file manager instance -arg.wdg: the current widget -arg.n: the number typed before the key combination (if allowed) -arg.direction: the direction object (if applicable) -arg.keys: the string representation of the used key combination -arg.keybuffer: the keybuffer instance - -Direction keys are special. They must be mapped with: map.dir(*keys, **args) -where args is a dict of values such as up, down, to, absolute, relative... -Example: map.dir('gg', to=0) -Direction keys can be accessed in a mapping that contians "<dir>". -Other special keys are "<any>" which matches any single key and "<bg>" -which will run the function passively, without clearing the keybuffer. - -Additionally, there are shortcuts for accessing methods of the current -file manager and widget instance: -map('xyz', fm.method(foo=bar)) -will be translated to: -map('xyz', lamdba arg: arg.fm.method(foo=bar)) -If possible, arg.n and arg.direction are automatically inserted. - - -Example scenario ----------------- -If this keys are defined: -map("dd", "d<dir>", fm.cut(foo=bar)) -map.dir("gg", to=0) - -Type in the keys on the left and the function on the right will be executed: -dd => fm.cut(foo=bar) -5dd => fm.cut(foo=bar, narg=5) -dgg => fm.cut(foo=bar, dirarg=Direction(to=0)) -5dgg => fm.cut(foo=bar, narg=5, dirarg=Direction(to=0)) -5d3gg => fm.cut(foo=bar, narg=5, dirarg=Direction(to=3)) - -Example ~/.config/ranger/keys.py -------------------------- -from ranger.api.keys import * - -keymanager.map("browser", "d", fm.move(down=0.5, pages=True)) - -# Add less-like d/u keys to the "browser" context: -map = keymanager.get_context('browser') -map("d", fm.move(down=0.5, pages=True)) -map("u", fm.move(up=0.5, pages=True)) - -# Add keys to all contexts -map = KeyMapWithDirections() # create new empty keymap. -map("q", fm.exit()) -map.dir("<down>", down=3) # I'm quick, I want to move 3 at once! -keymanager.merge_all(map) # merge the new map into all existing ones. -""" - -from ranger.api.keys import * - -# =================================================================== -# == Define keys for everywhere: -# =================================================================== -map = global_keys = KeyMapWithDirections() -map('Q', fm.exit()) -map('<C-L>', fm.redraw_window()) -map('<backspace2>', alias='<backspace>') # Backspace is bugged sometimes - -#map('<dir>', wdg.move()) -@map('<dir>') # move around with direction keys -def move(arg): - arg.wdg.move(narg=arg.n, **arg.direction) - -# -------------------------------------------------- direction keys -map.dir('<down>', down=1) -map.dir('<up>', up=1) -map.dir('<left>', left=1) -map.dir('<right>', right=1) -map.dir('<home>', down=0, absolute=True) -map.dir('<end>', down=-1, absolute=True) -map.dir('<pagedown>', down=1, pages=True) -map.dir('<pageup>', down=-1, pages=True) -map.dir('%', down=50, percentage=True, absolute=True) - - -# =================================================================== -# == Define aliases -# =================================================================== -map = vim_aliases = KeyMapWithDirections() -map.dir('j', alias='<down>') -map.dir('k', alias='<up>') -map.dir('h', alias='<left>') -map.dir('l', alias='<right>') -map.dir('gg', alias='<home>') -map.dir('G', alias='<end>') -map.dir('<C-F>', alias='<pagedown>') -map.dir('<C-B>', alias='<pageup>') - -map = readline_aliases = KeyMapWithDirections() -map.dir('<C-B>', alias='<left>') -map.dir('<C-F>', alias='<right>') -map.dir('<C-A>', alias='<home>') -map.dir('<C-E>', alias='<end>') -map.dir('<C-D>', alias='<delete>') -map.dir('<C-H>', alias='<backspace>') - -map = midnight_commander_fkeys = KeyMapWithDirections() -map('<F1>', fm.display_help()) -map('<F3>', fm.display_file()) -map('<F4>', fm.edit_file()) -map('<F5>', fm.copy()) -map('<F6>', fm.cut()) -map('<F7>', fm.open_console('mkdir ')) -map('<F8>', fm.open_console(DELETE_WARNING)) -map('<F10>', fm.exit()) - -# =================================================================== -# == Define keys in "browser" context: -# =================================================================== -map = keymanager.get_context('browser') -map.merge(global_keys) -map.merge(vim_aliases) -map.merge(midnight_commander_fkeys) - -# -------------------------------------------------------- movement -map('gg', fm.move(to=0)) -map('<enter>', wdg.move(right=0)) # run with mode=0 -map('<C-D>', 'J', fm.move(down=0.5, pages=True)) -map('<C-U>', 'K', fm.move(up=0.5, pages=True)) -map(']', fm.move_parent(1)) -map('[', fm.move_parent(-1)) -map('}', fm.traverse()) -map('{', fm.history_go(-1)) - -# --------------------------------------------------------- history -map('H', fm.history_go(-1)) -map('L', fm.history_go(1)) - -# ----------------------------------------------- tagging / marking -map('t', fm.tag_toggle()) -map('T', fm.tag_remove()) -for key in ALLOWED_TAGS_KEYS: - map('"' + key, fm.tag_toggle(tag=key)) - -map(' ', fm.mark(toggle=True)) -map('v', fm.mark(all=True, toggle=True)) -map('V', 'uv', fm.mark(all=True, val=False)) -map('<C-V><dir>', fm.mark_in_direction(val=True)) -map('u<C-V><dir>', fm.mark_in_direction(val=False)) - -# ------------------------------------------ file system operations -map('y<bg>', fm.hint('*copy:* cop*y* *a*dd *r*emove ' \ - '*p*ath_to_xsel *d*irpath_to_xsel base*n*ame_to_xsel')) -map('yy', 'y<dir>', fm.copy()) -map('ya', fm.copy(mode='add')) -map('yr', fm.copy(mode='remove')) -map('yp', fm.execute_console('shell -d echo -n %d/%f | xsel -i')) -map('yd', fm.execute_console('shell -d echo -n %d | xsel -i')) -map('yn', fm.execute_console('shell -d echo -n %f | xsel -i')) -map('d<bg>', fm.hint('disk_*u*sage *cut:* *d*:cut *a*dd *r*emove')) -map('dd', 'd<dir>', fm.cut()) -map('da', fm.cut(mode='add')) -map('dr', fm.cut(mode='remove')) -map('p<bg>', fm.hint('*paste:* *p*aste *o*verwrite sym*l*inks ' \ - '*h*ardlinks relative_sym*L*inks')) -map('pp', fm.paste()) -map('po', fm.paste(overwrite=True)) -map('pl', fm.paste_symlink(relative=False)) -map('pL', fm.paste_symlink(relative=True)) -map('ph<bg>', fm.hint('*paste:* hard*l*inks')) -map('phl', fm.paste_hardlink()) - -map('u<bg>', fm.hint("un*y*ank, unbook*m*ark, unselect:*v*")) -map('ud', 'uy', fm.uncut()) - -# ------------------------------------ changing of file permissions -# type "+ow" for "chmod o+w %s" and so on -from itertools import product -for mode in product('ugoa', 'rwxXst'): - map('-%s%s' % mode, fm.execute_console('shell chmod %s-%s %%s' % mode)) - map('+%s%s' % mode, fm.execute_console('shell chmod %s+%s %%s' % mode)) - map('=%s%s' % mode, fm.execute_console('shell chmod %s+%s %%s' % mode)) - -# hints: -template = '%s %s to *r*ead, *w*rite, e*x*ecute' -for who, name in zip('ugoa', ('user', 'group', 'others', 'all')): - map('-%s<bg>' % who, fm.hint(template % ('forbid', name))) - map('+%s<bg>' % who, fm.hint(template % ('allow', name))) - map('=%s<bg>' % who, fm.hint(template % ('allow', name))) -map('-<bg>', '+<bg>', '=<bg>', fm.hint('change permission for *u*ser, ' - '*g*roup, *o*thers, *a*ll')) - -# ---------------------------------------------------- run programs -map('S', fm.execute_command(os.environ['SHELL'])) -map('E', fm.edit_file()) -map('du', fm.execute_console('shell -p du --max-depth=1 -h --apparent-size')) - -# -------------------------------------------------- toggle options -map('z<bg>', fm.hint('*f*ilter *options:* *d*irectories_first *c*ollape_preview ' \ - '*s*ort_case_insensitive show_*h*idden *p*review_files '\ - '*P*review_dirs use_pre*v*iew_script flush*i*nput *m*ouse')) -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('zv', fm.toggle_boolean_option('use_preview_script')) -map('zi', fm.toggle_boolean_option('flushinput')) -map('zd', fm.toggle_boolean_option('sort_directories_first')) -map('zc', fm.toggle_boolean_option('collapse_preview')) -map('zs', fm.toggle_boolean_option('sort_case_insensitive')) -map('zm', fm.toggle_boolean_option('mouse_enabled')) -map('zf', fm.open_console('filter ')) - -# ------------------------------------------------------------ sort -map('o<bg>', 'O<bg>', fm.hint('*sort by:* *s*ize *b*asename *m*time' \ - ' *c*time *a*time *t*ype *r*everse *n*atural')) -sort_dict = { - 's': 'size', - 'b': 'basename', - 'n': 'natural', - 'm': 'mtime', - 'c': 'ctime', - 'a': 'atime', - 't': 'type', -} - -for key, val in sort_dict.items(): - for key, is_capital in ((key, False), (key.upper(), True)): - # reverse if any of the two letters is capital - map('o' + key, fm.sort(func=val, reverse=is_capital)) - map('O' + key, fm.sort(func=val, reverse=True)) - -map('or', 'Or', 'oR', 'OR', lambda arg: \ - arg.fm.sort(reverse=not arg.fm.settings.sort_reverse)) - -# ----------------------------------------------- console shortcuts -@map("A") -def append_to_filename(arg): - command = 'rename ' + arg.fm.env.cf.basename - arg.fm.open_console(command) - -@map("I") -def insert_before_filename(arg): - command = 'rename ' + arg.fm.env.cf.basename - arg.fm.open_console(command, position=len('rename ')) - -map('cw', fm.open_console('rename ')) -map('cd', fm.open_console('cd ')) -map('f', fm.open_console('find ')) -map('@', fm.open_console('shell %s', position=len('shell '))) -map('#', fm.open_console('shell -p ')) - -# --------------------------------------------- jump to directories -map('g<bg>', fm.hint('*goto:* */* *d*ev *e*tc *h*:~ *m*edia ' \ - '*M*nt *o*pt *s*rv *u*sr *v*ar *R*anger *link:* *l*:target_path ' \ - 'rea*L*_path *tab:* *c*lose *n*ew nex*t* *T*:prev')) -map('gh', fm.cd('~')) -map('ge', fm.cd('/etc')) -map('gu', fm.cd('/usr')) -map('gd', fm.cd('/dev')) -map('gl', lambda arg: arg.fm.cd(os.path.realpath(arg.fm.env.cwd.path))) -map('gL', lambda arg: arg.fm.cd( - os.path.dirname(os.path.realpath(arg.fm.env.cf.path)))) -map('go', fm.cd('/opt')) -map('gv', fm.cd('/var')) -map('gr', 'g/', fm.cd('/')) -map('gm', fm.cd('/media')) -map('gM', fm.cd('/mnt')) -map('gs', fm.cd('/srv')) -map('gR', fm.cd(RANGERDIR)) - -# ------------------------------------------------------------ tabs -map('gc', '<C-W>', fm.tab_close()) -map('gt', '<TAB>', '<A-Right>', fm.tab_move(1)) -map('gT', '<S-TAB>', '<A-Left>', fm.tab_move(-1)) -@map('gn', '<C-N>') -def newtab_and_gohome(arg): - arg.fm.tab_new() - arg.fm.cd('~') # To return to the original directory, type `` -for n in range(1, 10): - map('g' + str(n), fm.tab_open(n)) - map('<A-' + str(n) + '>', fm.tab_open(n)) - -# ------------------------------------------------------- searching -map('/', fm.open_console('search ')) - -map('n', fm.search()) -map('N', fm.search(forward=False)) - -map('c<bg>', fm.hint('*w*:rename ch*d*ir *search order:* ' \ - '*a*time *c*time *M*time *m*imetype *s*ize *t*ag')) -map('ct', fm.search(order='tag')) -map('cc', fm.search(order='ctime')) -map('ca', fm.search(order='atime')) -map('cM', fm.search(order='mtime')) -map('cm', fm.search(order='mimetype')) -map('cs', fm.search(order='size')) - -# ------------------------------------------------------- bookmarks -for key in ALLOWED_BOOKMARK_KEYS: - map("`" + key, "'" + key, fm.enter_bookmark(key)) - map("m" + key, fm.set_bookmark(key)) - map("um" + key, fm.unset_bookmark(key)) -map("`<bg>", "'<bg>", "m<bg>", fm.draw_bookmarks()) -map('um<bg>', lambda arg: (arg.fm.draw_bookmarks(), - arg.fm.hint("delete which bookmark?"))) - -# ---------------------------------------------------- change views -map('i', fm.display_file()) -map('W', fm.display_log()) -map('?', fm.display_help()) -map('w', lambda arg: arg.fm.ui.open_taskview()) - -# ------------------------------------------------ system functions -map('ZZ', 'ZQ', fm.exit()) -map('<C-R>', fm.reset()) -map('R', fm.reload_cwd()) -@map('<C-C>') -def ctrl_c(arg): - try: - item = arg.fm.loader.queue[0] - except: - arg.fm.notify("Type Q or :quit<Enter> to exit Ranger") - else: - arg.fm.notify("Aborting: " + item.get_description()) - arg.fm.loader.remove(index=0) - -map(':', ';', fm.open_console('')) -map('!', fm.open_console('shell ')) -map('s', fm.open_console('shell ')) -map('r', fm.open_console('open_with ')) - - -# =================================================================== -# == Define keys for the pager -# =================================================================== -map = pager_keys = KeyMapWithDirections() -map.merge(global_keys) -map.merge(vim_aliases) - -# -------------------------------------------------------- movement -map('<left>', 'h', wdg.move(left=4)) -map('<right>', 'l', wdg.move(right=4)) -map('<C-D>', 'd', wdg.move(down=0.5, pages=True)) -map('<C-U>', 'u', wdg.move(up=0.5, pages=True)) -map('<C-F>', 'f', '<pagedown>', wdg.move(down=1, pages=True)) -map('<C-B>', 'b', '<pageup>', wdg.move(up=1, pages=True)) -map('<space>', wdg.move(down=0.8, pages=True)) -map('<cr>', wdg.move(down=1)) - -# ---------------------------------------------------------- others -map('E', fm.edit_file()) -map('?', fm.display_help()) - -# --------------------------------------------------- bind the keys -# There are two different kinds of pagers, each have a different -# method for exiting: - -map = keymanager.get_context('pager') -map.merge(pager_keys) -map('q', 'i', '<esc>', '<F3>', lambda arg: arg.fm.ui.close_pager()) - -map = keymanager.get_context('embedded_pager') -map.merge(pager_keys) -map('q', 'i', '<esc>', '<F3>', lambda arg: arg.fm.ui.close_embedded_pager()) - - -# =================================================================== -# == Define keys for the taskview -# =================================================================== -map = keymanager.get_context('taskview') -map.merge(global_keys) -map.merge(vim_aliases) -map('K', wdg.task_move(0)) -map('J', wdg.task_move(-1)) -map('dd', wdg.task_remove()) - -map('?', fm.display_help()) -map('w', 'q', ESC, ctrl('d'), ctrl('c'), - lambda arg: arg.fm.ui.close_taskview()) - - -# =================================================================== -# == Define keys for the console -# =================================================================== -map = keymanager.get_context('console') -map.merge(global_keys) -map.merge(readline_aliases) - -map('<up>', '<C-P>', wdg.history_move(-1)) -map('<down>', '<C-N>', wdg.history_move(1)) -map('<home>', '<C-A>', wdg.move(right=0, absolute=True)) -map('<end>', '<C-E>', wdg.move(right=-1, absolute=True)) -map('<tab>', wdg.tab()) -map('<s-tab>', wdg.tab(-1)) -map('<C-C>', '<C-D>', '<ESC>', wdg.close()) -map('<CR>', '<c-j>', wdg.execute()) -map('<F1>', lambda arg: arg.fm.display_command_help(arg.wdg)) - -map('<backspace>', '<C-H>', wdg.delete(-1)) -map('<delete>', '<C-D>', wdg.delete(0)) -map('<C-W>', wdg.delete_word()) -map('<C-K>', wdg.delete_rest(1)) -map('<C-U>', wdg.delete_rest(-1)) -map('<C-Y>', wdg.paste()) - -# Any key which is still undefined will simply be typed in. -@map('<any>') -def type_key(arg): - arg.wdg.type_key(arg.match) - -# Allow typing in numbers: -def type_chr(n): - return lambda arg: arg.wdg.type_key(str(n)) -for number in range(10): - map(str(number), type_chr(number)) - -# Unmap some global keys so we can type them: -map.unmap('Q') -map.directions.unmap('%') diff --git a/ranger/defaults/options.py b/ranger/defaults/options.py index df478989..3b44d4f6 100644 --- a/ranger/defaults/options.py +++ b/ranger/defaults/options.py @@ -1,37 +1,27 @@ -# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> +# -*- coding: utf-8 -*- +# Copyright (C) 2009, 2010, 2011 Roman Zimbelmann <romanz@lavabit.com> +# This configuration file is licensed under the same terms as ranger. +# =================================================================== +# This is the main configuration file of ranger. It consists of python +# code, but fear not, you don't need any python knowledge for changing +# the settings. # -# 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. +# Lines beginning with # are comments. To enable a line, remove the #. # -# 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/>. - -""" -This is the default configuration file of ranger. - -There are two ways of customizing ranger. The first and recommended -method is creating a file at ~/.config/ranger/options.py and adding -those lines you want to change. It might look like this: +# You can customize ranger in the file ~/.config/ranger/options.py. +# It has the same syntax as this file. In fact, you can just copy this +# file there with `ranger --copy-config=options' and make your modifications. +# But make sure you update your configs when you update ranger. +# =================================================================== from ranger.api.options import * -preview_files = False # I hate previews! -max_history_size = 2000 # I can afford it. -The other way is directly editing this file. This will make upgrades -of ranger more complicated though. +# Load the deault rc.conf file? If you've copied it to your configuration +# direcory, then you should deactivate this option. +load_default_rc = True -Whatever you do, make sure the import-line stays intact and the type -of the values stay the same. -""" - -from ranger.api.options import * +# How many columns are there, and what are their relative widths? +column_ratios = (1, 3, 4) # Which files should be hidden? Toggle this by typing `zh' or # changing the setting `show_hidden' @@ -40,7 +30,7 @@ hidden_filter = regexp( show_hidden = False # Which script is used to generate file previews? -# Ranger ships with scope.sh, a script that calls external programs (see +# ranger ships with scope.sh, a script that calls external programs (see # README for dependencies) to preview images, archives, etc. preview_script = '~/.config/ranger/scope.sh' @@ -54,8 +44,8 @@ unicode_ellipsis = False show_hidden_bookmarks = True # Which colorscheme to use? These colorschemes are available by default: -# default, default88, texas, jungle, snow -# Snow is monochrome, texas and default88 use 88 colors. +# default, default88, jungle, snow +# Snow is monochrome and default88 uses 88 colors. colorscheme = 'default' # Preview files on the rightmost column? @@ -74,9 +64,6 @@ draw_bookmark_borders = True # Display the directory name in tabs? dirname_in_tabs = False -# How many columns are there, and what are their relative widths? -column_ratios = (1, 3, 4) - # Enable the mouse support? mouse_enabled = True @@ -130,22 +117,83 @@ sort_directories_first = True # (Especially on xterm) xterm_alt_key = False +# The color scheme overlay. Explained below. +colorscheme_overlay = None + +## Apply an overlay function to the colorscheme. It will be called with +## 4 arguments: the context and the 3 values (fg, bg, attr) returned by +## the original use() function of your colorscheme. The return value +## must be a 3-tuple of (fg, bg, attr). +## Note: Here, the colors/attributes aren't directly imported into +## the namespace but have to be accessed with color.xyz. + +#from ranger.gui import color +#def colorscheme_overlay(context, fg, bg, attr): +# if context.directory and attr & color.bold and \ +# not any((context.marked, context.selected)): +# attr ^= color.bold # I don't like bold directories! +# +# if context.main_column and context.selected: +# fg, bg = color.red, color.default # To highlight the main column! +# +# return fg, bg, attr -# Apply an overlay function to the colorscheme. It will be called with -# 4 arguments: the context and the 3 values (fg, bg, attr) returned by -# the original use() function of your colorscheme. The return value -# must be a 3-tuple of (fg, bg, attr). -# Note: Here, the colors/attributes aren't directly imported into -# the namespace but have to be accessed with color.xyz. -def colorscheme_overlay(context, fg, bg, attr): - if context.directory and attr & color.bold and \ - not any((context.marked, context.selected)): - attr ^= color.bold # I don't like bold directories! - if context.main_column and context.selected: - fg, bg = color.red, color.default # To highlight the main column! +# =================================================================== +# Beware: from here on, you are on your own. This part requires python +# knowledge. +# +# Since python is a dynamic language, it gives you the power to replace any +# part of ranger without touching the code. This is commonly referred to as +# Monkey Patching and can be helpful if you, for some reason, don't want to +# modify rangers code directly. Just remember: the more you mess around, the +# more likely it is to break when you switch to another version. +# +# Here are some practical examples of monkey patching. +# +# Technical information: This file is imported as a python module. If a +# variable has the name of a setting, ranger will attempt to use it to change +# that setting. You can write "del <variable-name>" to avoid that. +# =================================================================== +# Add a new sorting algorithm: Random sort. +# Enable this with :set sort=random + +#from ranger.fsobject.directory import Directory +#from random import random +#Directory.sort_dict['random'] = lambda path: random() + +# =================================================================== +# A function that changes which files are displayed. This is more powerful +# than the hidden_filter setting since this function has more information. + +## Save the original filter function +#import ranger.fsobject.directory +#old_accept_file = ranger.fsobject.directory.accept_file +# +## Define a new one +#def accept_file_MOD(fname, mypath, hidden_filter, name_filter): +# if hidden_filter and mypath == '/' and fname in ('boot', 'sbin', 'proc', 'sys'): +# return False +# else: +# return old_accept_file(fname, mypath, hidden_filter, name_filter) +# +## Overwrite the old function +#import ranger.fsobject.directory +#ranger.fsobject.directory.accept_file = accept_file_MOD - return fg, bg, attr +# =================================================================== +# A function that adds an additional macro. Test this with :shell -p echo %date -# The above function was just an example, let's set it back to None -colorscheme_overlay = None +## Save the original macro function +#import ranger.core.actions +#old_get_macros = ranger.core.actions.Actions._get_macros +# +## Define a new macro function +#import time +#def get_macros_MOD(self): +# macros = old_get_macros(self) +# macros['date'] = time.strftime('%m/%d/%Y') +# return macros +# +## Overwrite the old one +#ranger.core.actions.Actions._get_macros = get_macros_MOD diff --git a/ranger/defaults/rc.conf b/ranger/defaults/rc.conf new file mode 100644 index 00000000..bf29335e --- /dev/null +++ b/ranger/defaults/rc.conf @@ -0,0 +1,361 @@ +# =================================================================== +# This file contains the default startup commands for ranger. +# To change them, it is recommended to create the file +# ~/.config/ranger/rc.conf and add your custom commands there. +# +# If you copy this whole file there, add this line to your options.py +# so it is not loaded twice: +# +# load_default_rc = False +# +# The purpose of this file is mainly to define keybindings. For +# changing settings or running more complex python code, use the +# configuation file "options.py" or define commands in "commands.py". +# +# Each line is a command that will be run before the user interface +# is initialized. As a result, you can not use commands which rely +# on the UI such as :delete or :mark. +# =================================================================== + +# =================================================================== +# == Command Aliases in the Console +# =================================================================== + +alias e edit +alias q quit +alias q! quitall +alias qall quitall + +# =================================================================== +# == Define keys for the browser +# =================================================================== + +# Basic +map Q quit! +map q quit +copymap q ZZ ZQ + +map R reload_cwd +map <C-r> reset +map <C-l> redraw_window +map <C-c> abort + +map i display_file +map ? help +map W display_log +map w taskview_open +map S shell $SHELL + +map : console +map ; console +map ! console shell +map @ console -p6 shell %%s +map # console shell -p +map s console shell +map r console open_with +map f console find +map cd console cd + +# Tagging / Marking +map t tag_toggle +map T tag_remove +map "<any> tag_toggle tag=%any +map <Space> mark_files toggle=True +map v mark_files all=True toggle=True +map V mark_files all=True val=False +map uv mark_files all=True val=False + +# For the nostalgics: Midnight Commander bindings +map <F1> help +map <F3> display_file +map <F4> edit +map <F5> copy +map <F6> cut +map <F7> console mkdir +map <F8> console delete seriously? +map <F10> exit + +# In case you work on a keyboard with dvorak layout +map <UP> move up=1 +map <DOWN> move down=1 +map <LEFT> move left=1 +map <RIGHT> move right=1 +map <HOME> move to=0 +map <END> move to=-1 +map <PAGEDOWN> move down=1 pages=True +map <PAGEUP> move up=1 pages=True +map <CR> move right=1 +map <DELETE> console delete +map <INSERT> console touch + +# VIM-like +copymap <UP> k +copymap <DOWN> j +copymap <LEFT> h +copymap <RIGHT> l +copymap <HOME> gg +copymap <END> G +copymap <PAGEDOWN> <C-F> +copymap <PAGEUP> <C-B> + +map J move down=0.5 pages=True +map K move up=0.5 pages=True +copymap J <C-D> +copymap K <C-U> + +# Jumping around +map H history_go -1 +map L history_go 1 +map ] move_parent 1 +map [ move_parent -1 +map } traverse + +map gh cd ~ +map ge cd /etc +map gu cd /usr +map gd cd /dev +map gl cd -r . +map gL cd -r %f +map go cd /opt +map gv cd /var +map gm cd /media +map gM cd /mnt +map gs cd /srv +map gr cd / +map gR eval fm.cd(ranger.RANGERDIR) +map g/ cd / + +# External Programs +map E edit +map du shell -p du --max-depth=1 -h --apparent-size +map dU shell -p du --max-depth=1 -h --apparent-size | sort -rh +map yp shell -d echo -n %d/%f | xsel -i +map yd shell -d echo -n %d | xsel -i +map yn shell -d echo -n %f | xsel -i + +# Filesystem Operations +map = chmod + +map cw console rename +map A eval fm.open_console('rename ' + fm.env.cf.basename) +map I eval fm.open_console('rename ' + fm.env.cf.basename, position=7) + +map pp paste +map po paste overwrite=True +map pl paste_symlink relative=False +map pL paste_symlink relative=True +map phl paste_hardlink + +map dd cut +map ud uncut +map da cut mode=add +map dr cut mode=remove + +map yy copy +map uy uncut +map ya copy mode=add +map yr copy mode=remove + +# Temporary workarounds +map dgg eval fm.cut(dirarg=dict(to=0), narg=quantifier) +map dG eval fm.cut(dirarg=dict(to=-1), narg=quantifier) +map dj eval fm.cut(dirarg=dict(down=1), narg=quantifier) +map dk eval fm.cut(dirarg=dict(up=1), narg=quantifier) +map ygg eval fm.copy(dirarg=dict(to=0), narg=quantifier) +map yG eval fm.copy(dirarg=dict(to=-1), narg=quantifier) +map yj eval fm.copy(dirarg=dict(down=1), narg=quantifier) +map yk eval fm.copy(dirarg=dict(up=1), narg=quantifier) + +# Searching +map / console search +map n search_next +map N search_next forward=False +map ct search_next order=tag +map cs search_next order=size +map ci search_next order=mimetype +map cc search_next order=ctime +map cm search_next order=mtime +map ca search_next order=atime + +# Tabs +map <C-n> tab_new ~ +map <C-w> tab_close +map <TAB> tab_move 1 +map <S-TAB> tab_move -1 +map <A-Right> tab_move 1 +map <A-Left> tab_move -1 +map gt tab_move 1 +map gT tab_move -1 +map gn tab_new ~ +map gc tab_close +map <a-1> tab_open 1 +map <a-2> tab_open 2 +map <a-3> tab_open 3 +map <a-4> tab_open 4 +map <a-5> tab_open 5 +map <a-6> tab_open 6 +map <a-7> tab_open 7 +map <a-8> tab_open 8 +map <a-9> tab_open 9 + +# Sorting +map or eval fm.settings.sort_reverse ^= True +map os chain set sort=size; set sort_reverse=False +map ob chain set sort=basename; set sort_reverse=False +map on chain set sort=natural; set sort_reverse=False +map om chain set sort=mtime; set sort_reverse=False +map oc chain set sort=ctime; set sort_reverse=False +map oa chain set sort=atime; set sort_reverse=False +map ot chain set sort=type; set sort_reverse=False + +map oS chain set sort=size; set sort_reverse=True +map oB chain set sort=basename; set sort_reverse=True +map oN chain set sort=natural; set sort_reverse=True +map oM chain set sort=mtime; set sort_reverse=True +map oC chain set sort=ctime; set sort_reverse=True +map oA chain set sort=atime; set sort_reverse=True +map oT chain set sort=type; set sort_reverse=True + +# Settings +map zc toggle_option collapse_preview +map zd toggle_option sort_directories_first +map zh toggle_option show_hidden +map <C-h> toggle_option show_hidden +map zi toggle_option flushinput +map zm toggle_option mouse_enabled +map zp toggle_option preview_files +map zP toggle_option preview_directories +map zs toggle_option sort_case_insensitive +map zv toggle_option use_preview_script +map zf console filter + +# Bookmarks +map `<any> enter_bookmark %any +map '<any> enter_bookmark %any +map m<any> set_bookmark %any +map um<any> unset_bookmark %any + +map m<bg> draw_bookmarks +copymap m<bg> um<bg> `<bg> '<bg> + +# Beware. I haven't figured out how to make these keybindings pretty yet: + +# map +ow shell -d chmod o+w (one mapping for each combination) +eval import itertools; [cmd("map +%s%s shell -d chmod %s+%s %%s" % (mode+mode)) for mode in itertools.product("ugoa", "rwxXst")] + +# map -ow shell -d chmod o+w (one mapping for each combination) +eval import itertools; [cmd("map -%s%s shell -d chmod %s-%s %%s" % (mode+mode)) for mode in itertools.product("ugoa", "rwxXst")] + +# =================================================================== +# == Define keys for the console +# =================================================================== +# Note: Unmapped keys are passed directly to the console. + +# Basic +cmap <tab> eval fm.ui.console.tab() +cmap <s-tab> eval fm.ui.console.tab(-1) +cmap <ESC> eval fm.ui.console.close() +cmap <CR> eval fm.ui.console.execute() +cmap <C-l> redraw_window + +copycmap <ESC> <C-c> +copycmap <CR> <C-j> + +# Move around +cmap <up> eval fm.ui.console.history_move(-1) +cmap <down> eval fm.ui.console.history_move(1) +cmap <left> eval fm.ui.console.move(left=1) +cmap <right> eval fm.ui.console.move(right=1) +cmap <home> eval fm.ui.console.move(right=0, absolute=True) +cmap <end> eval fm.ui.console.move(right=-1, absolute=True) + +# Line Editing +cmap <backspace> eval fm.ui.console.delete(-1) +cmap <delete> eval fm.ui.console.delete(0) +cmap <C-w> eval fm.ui.console.delete_word() +cmap <C-k> eval fm.ui.console.delete_rest(1) +cmap <C-u> eval fm.ui.console.delete_rest(-1) +cmap <C-y> eval fm.ui.console.paste() + +# And of course the emacs way +copycmap <up> <C-p> +copycmap <down> <C-n> +copycmap <left> <C-b> +copycmap <right> <C-f> +copycmap <home> <C-a> +copycmap <end> <C-e> +copycmap <delete> <C-d> +copycmap <backspace> <C-h> + +# Note: There are multiple ways to express backspaces. <backspace> (code 263) +# and <backspace2> (code 127). To be sure, use both. +copycmap <backspace> <backspace2> + +# This special expression allows typing in numerals: +cmap <allow_quantifiers> false + +# =================================================================== +# == Pager Keybindings +# =================================================================== + +# Movement +pmap <down> pager_move down=1 +pmap <up> pager_move up=1 +pmap <left> pager_move left=4 +pmap <right> pager_move right=4 +pmap <home> pager_move to=0 +pmap <end> pager_move to=-1 +pmap <pagedown> pager_move down=1.0 pages=True +pmap <pageup> pager_move up=1.0 pages=True +pmap <C-d> pager_move down=0.5 pages=True +pmap <C-u> pager_move up=0.5 pages=True + +copypmap <UP> k <C-p> +copypmap <DOWN> j <C-n> <CR> +copypmap <LEFT> h +copypmap <RIGHT> l +copypmap <HOME> g +copypmap <END> G +copypmap <C-d> d +copypmap <C-u> u +copypmap <PAGEDOWN> n f <C-F> <Space> +copypmap <PAGEUP> p b <C-B> + +# Basic +pmap <ESC> pager_close +copypmap <ESC> q Q i <F3> +pmap E edit_file + +# =================================================================== +# == Taskview Keybindings +# =================================================================== + +# Movement +tmap <up> taskview_move up=1 +tmap <down> taskview_move down=1 +tmap <home> taskview_move to=0 +tmap <end> taskview_move to=-1 +tmap <pagedown> taskview_move down=1.0 pages=True +tmap <pageup> taskview_move up=1.0 pages=True +tmap <C-d> taskview_move down=0.5 pages=True +tmap <C-u> taskview_move up=0.5 pages=True + +copytmap <UP> k <C-p> +copytmap <DOWN> j <C-n> <CR> +copytmap <HOME> g +copytmap <END> G +copytmap <C-u> u +copytmap <PAGEDOWN> n f <C-F> <Space> +copytmap <PAGEUP> p b <C-B> + +# Changing priority and deleting tasks +tmap J eval -q fm.ui.taskview.task_move(-1) +tmap K eval -q fm.ui.taskview.task_move(0) +tmap dd eval -q fm.ui.taskview.task_remove() +tmap <pagedown> eval -q fm.ui.taskview.task_move(-1) +tmap <pageup> eval -q fm.ui.taskview.task_move(0) +tmap <delete> eval -q fm.ui.taskview.task_remove() + +# Basic +tmap <ESC> taskview_close +copytmap <ESC> q Q w <C-c> diff --git a/ranger/ext/accumulator.py b/ranger/ext/accumulator.py index 75f58ad7..d68bc656 100644 --- a/ranger/ext/accumulator.py +++ b/ranger/ext/accumulator.py @@ -1,4 +1,4 @@ -# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> +# Copyright (C) 2009, 2010, 2011 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 diff --git a/ranger/ext/command_parser.py b/ranger/ext/command_parser.py deleted file mode 100644 index 3b65067c..00000000 --- a/ranger/ext/command_parser.py +++ /dev/null @@ -1,104 +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/>. - -import re -SETTINGS_RE = re.compile(r'^([^\s]+?)=(.*)$') - -class LazyParser(object): - """Parse commands and extract information""" - def __init__(self, line): - self.line = line - self._chunks = None - self._rests = None - self._setting_line = None - self._rests_loaded = 0 - self._rests_gen_instance = None - self._starts = None - - try: - self.firstpart = line[:line.rindex(' ') + 1] - except ValueError: - self.firstpart = '' - - def chunk(self, n, otherwise=''): - """Chunks are pieces of the command seperated by spaces""" - if self._chunks is None: - self._chunks = self.line.split() - - if len(self._chunks) > n: - return self._chunks[n] - else: - return otherwise - - def rest(self, n, otherwise=''): - """Rests are the strings which come after each word.""" - if self._rests is None: - self._rests = list(self._rest_generator()) - # TODO: Don't calculate all the rest elements if not needed - - if len(self._rests) > n: - return self._rests[n] - else: - return otherwise - - def start(self, n): - if self._starts is None: - self._starts = [''] - line = self.line - result = "" - while True: - try: - index = line.index(' ') + 1 - except: - break - if index == 1: - line = line[1:] - continue - result = line[:index] - self._starts.append(result) - line = line[index:] - try: - return self._starts[n] - except: - return self._starts[-1] - - def _rest_generator(self): - lastrest = self.line - n = 0 - while n < len(lastrest): - if lastrest[n] == ' ': - n += 1 - else: - yield lastrest[n:] - n = lastrest.find(' ', n) + 1 - if n <= 0: - break - lastrest = lastrest[n:] - n = 0 - - def parse_setting_line(self): - if self._setting_line is not None: - return self._setting_line - match = SETTINGS_RE.match(self.rest(1)) - if match: - self.firstpart += match.group(1) + '=' - result = [match.group(1), match.group(2), True] - else: - result = [self.chunk(1), self.rest(2), ' ' in self.rest(1)] - self._setting_line = result - return result - - def __add__(self, newpart): - return self.firstpart + newpart diff --git a/ranger/ext/curses_interrupt_handler.py b/ranger/ext/curses_interrupt_handler.py index 4e2845cc..7c5b153e 100644 --- a/ranger/ext/curses_interrupt_handler.py +++ b/ranger/ext/curses_interrupt_handler.py @@ -1,4 +1,4 @@ -# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> +# Copyright (C) 2009, 2010, 2011 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 diff --git a/ranger/ext/direction.py b/ranger/ext/direction.py index f36e22a6..8a37c54a 100644 --- a/ranger/ext/direction.py +++ b/ranger/ext/direction.py @@ -1,4 +1,4 @@ -# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> +# Copyright (C) 2009, 2010, 2011 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 @@ -14,22 +14,24 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. """ -Directions provide convenience methods for movement operations. +Directions provide convenient methods for movement operations. Direction objects are handled just like dicts but provide methods like up() and down() which give you the correct value for the vertical direction, even if only the "up" or "down" key has been defined. -Example application: -d = Direction(down=5) -print(d.up()) # prints -5 -print(bool(d.horizontal())) # False, since no horizontal direction is defined + +>>> d = Direction(down=5) +>>> d.down() +5 +>>> d.up() +-5 +>>> bool(d.horizontal()) +False """ class Direction(dict): - __doc__ = __doc__ # for nicer pydoc - def __init__(self, dictionary=None, **keywords): if dictionary is not None: dict.__init__(self, dictionary) @@ -110,12 +112,18 @@ class Direction(dict): Calculates the new position in a given boundary. Example: - d = Direction(pages=True) - d.move(direction=3) # = 3 - d.move(direction=3, current=2) # = 5 - d.move(direction=3, pagesize=5) # = 15 - d.move(direction=3, pagesize=5, maximum=10) # = 10 - d.move(direction=9, override=2) # = 18 + >>> d = Direction(pages=True) + >>> d.move(direction=3) + 3 + >>> d.move(direction=3, current=2) + 5 + >>> d.move(direction=3, pagesize=5) + 15 + >>> # Note: we start to count at zero. + >>> d.move(direction=3, pagesize=5, maximum=10) + 9 + >>> d.move(direction=9, override=2) + 18 """ pos = direction if override is not None: @@ -139,3 +147,7 @@ class Direction(dict): current=current, pagesize=pagesize, minimum=0, maximum=len(lst)) selection = lst[min(current, dest):max(current, dest) + offset] return dest + offset - 1, selection + +if __name__ == '__main__': + import doctest + doctest.testmod() diff --git a/ranger/ext/get_executables.py b/ranger/ext/get_executables.py index 22c08eb9..c19798a6 100644 --- a/ranger/ext/get_executables.py +++ b/ranger/ext/get_executables.py @@ -1,4 +1,4 @@ -# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> +# Copyright (C) 2009, 2010, 2011 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 diff --git a/ranger/ext/human_readable.py b/ranger/ext/human_readable.py index 3ce6498a..9cdce409 100644 --- a/ranger/ext/human_readable.py +++ b/ranger/ext/human_readable.py @@ -1,4 +1,4 @@ -# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> +# Copyright (C) 2009, 2010, 2011 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 diff --git a/ranger/ext/iter_tools.py b/ranger/ext/iter_tools.py index 462c0c22..e515fa07 100644 --- a/ranger/ext/iter_tools.py +++ b/ranger/ext/iter_tools.py @@ -1,4 +1,4 @@ -# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> +# Copyright (C) 2009, 2010, 2011 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 @@ -21,6 +21,12 @@ def flatten(lst): All contained tuples, lists, deques and sets are replaced by their elements and flattened as well. + + >>> l = [1, 2, [3, [4], [5, 6]], 7] + >>> list(flatten(l)) + [1, 2, 3, 4, 5, 6, 7] + >>> list(flatten(())) + [] """ for elem in lst: if isinstance(elem, (tuple, list, set, deque)): @@ -36,9 +42,18 @@ def unique(iterable): This function assumes that: type(iterable)(list(iterable)) == iterable which is true for tuples, lists and deques (but not for strings) + + >>> unique([1, 2, 3, 1, 2, 3, 4, 2, 3, 4, 1, 1, 2]) + [1, 2, 3, 4] + >>> unique(('w', 't', 't', 'f', 't', 'w')) + ('w', 't', 'f') """ already_seen = [] for item in iterable: if item not in already_seen: already_seen.append(item) return type(iterable)(already_seen) + +if __name__ == '__main__': + import doctest + doctest.testmod() diff --git a/ranger/ext/keybinding_parser.py b/ranger/ext/keybinding_parser.py index bfd1b6d4..d03d6ed1 100644 --- a/ranger/ext/keybinding_parser.py +++ b/ranger/ext/keybinding_parser.py @@ -1,4 +1,4 @@ -# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> +# Copyright (C) 2009, 2010, 2011 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 @@ -13,15 +13,70 @@ # 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 +import copy import curses.ascii -from string import ascii_lowercase + +PY3 = sys.version > '3' +digits = set(range(ord('0'), ord('9')+1)) + +# Arbitrary numbers which are not used with curses.KEY_XYZ +ANYKEY, PASSIVE_ACTION, ALT_KEY, QUANT_KEY = range(9001, 9005) + +special_keys = { + 'bs': curses.KEY_BACKSPACE, + 'backspace': curses.KEY_BACKSPACE, + 'backspace2': curses.ascii.DEL, + 'delete': curses.KEY_DC, + 'insert': curses.KEY_IC, + 'cr': ord("\n"), + 'enter': ord("\n"), + 'return': ord("\n"), + 'space': ord(" "), + 'esc': curses.ascii.ESC, + 'escape': curses.ascii.ESC, + 'down': curses.KEY_DOWN, + 'up': curses.KEY_UP, + 'left': curses.KEY_LEFT, + 'right': curses.KEY_RIGHT, + 'pagedown': curses.KEY_NPAGE, + 'pageup': curses.KEY_PPAGE, + 'home': curses.KEY_HOME, + 'end': curses.KEY_END, + 'tab': ord('\t'), + 's-tab': curses.KEY_BTAB, +} + +very_special_keys = { + 'any': ANYKEY, + 'alt': ALT_KEY, + 'bg': PASSIVE_ACTION, + 'allow_quantifiers': QUANT_KEY, +} + +for key, val in tuple(special_keys.items()): + special_keys['a-' + key] = (ALT_KEY, val) + +for char in 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789': + special_keys['a-' + char] = (ALT_KEY, ord(char)) + +for char in 'abcdefghijklmnopqrstuvwxyz': + special_keys['c-' + char] = ord(char) - 96 + +for n in range(64): + special_keys['f' + str(n)] = curses.KEY_F0 + n + +special_keys.update(very_special_keys) +del very_special_keys +reverse_special_keys = dict((v, k) for k, v in special_keys.items()) + def parse_keybinding(obj): """ Translate a keybinding to a sequence of integers Example: - lol<CR> => (ord('l'), ord('o'), ord('l'), ord('\n')) + lol<CR> => (ord('l'), ord('o'), ord('l'), ord('\\n')) => (108, 111, 108, 10) x<A-Left> => (120, (27, curses.KEY_LEFT)) """ @@ -63,51 +118,145 @@ def parse_keybinding(obj): for c in bracket_content: yield ord(c) -# Arbitrary numbers which are not used with curses.KEY_XYZ -DIRKEY = 9001 -ANYKEY = 9002 -PASSIVE_ACTION = 9003 -ALT_KEY = 9004 -very_special_keys = { - 'dir': DIRKEY, - 'any': ANYKEY, - 'bg': PASSIVE_ACTION, -} +def construct_keybinding(iterable): + """ + Does the reverse of parse_keybinding + """ + return ''.join(key_to_string(c) for c in iterable) -special_keys = { - 'bs': curses.KEY_BACKSPACE, - 'backspace': curses.KEY_BACKSPACE, - 'backspace2': curses.ascii.DEL, - 'delete': curses.KEY_DC, - 'cr': ord("\n"), - 'enter': ord("\n"), - 'return': ord("\n"), - 'space': ord(" "), - 'esc': curses.ascii.ESC, - 'escape': curses.ascii.ESC, - 'down': curses.KEY_DOWN, - 'up': curses.KEY_UP, - 'left': curses.KEY_LEFT, - 'right': curses.KEY_RIGHT, - 'pagedown': curses.KEY_NPAGE, - 'pageup': curses.KEY_PPAGE, - 'home': curses.KEY_HOME, - 'end': curses.KEY_END, - 'tab': ord('\t'), - 's-tab': curses.KEY_BTAB, -} -for key, val in tuple(special_keys.items()): - special_keys['a-' + key] = (ALT_KEY, val) +def key_to_string(key): + if key in range(33, 127): + return chr(key) + if key in reverse_special_keys: + return "<%s>" % reverse_special_keys[key] + return "<%s>" % str(key) -for char in ascii_lowercase + '0123456789': - special_keys['a-' + char] = (ALT_KEY, ord(char)) -for char in ascii_lowercase: - special_keys['c-' + char] = ord(char) - 96 +def _unbind_traverse(pointer, keys, pos=0): + if keys[pos] not in pointer: + return + if len(keys) > pos+1 and isinstance(pointer, dict): + _unbind_traverse(pointer[keys[pos]], keys, pos=pos+1) + if not pointer[keys[pos]]: + del pointer[keys[pos]] + elif len(keys) == pos+1: + try: + del pointer[keys[pos]] + keys.pop() + except: + pass -for n in range(64): - special_keys['f' + str(n)] = curses.KEY_F0 + n +class KeyMaps(dict): + def __init__(self, keybuffer=None): + dict.__init__(self) + self.keybuffer = keybuffer + self.used_keymap = None -special_keys.update(very_special_keys) + def use_keymap(self, keymap_name): + self.keybuffer.keymap = self.get(keymap_name, dict()) + if self.used_keymap != keymap_name: + self.used_keymap = keymap_name + self.keybuffer.clear() + + def _clean_input(self, context, keys): + try: + pointer = self[context] + except: + self[context] = pointer = dict() + if PY3: + keys = keys.encode('utf-8').decode('latin-1') + return list(parse_keybinding(keys)), pointer + + def bind(self, context, keys, leaf): + keys, pointer = self._clean_input(context, keys) + if not keys: + return + last_key = keys[-1] + for key in keys[:-1]: + try: + if isinstance(pointer[key], dict): + pointer = pointer[key] + else: + pointer[key] = pointer = dict() + except: + pointer[key] = pointer = dict() + pointer[last_key] = leaf + + def copy(self, context, source, target): + clean_source, pointer = self._clean_input(context, source) + if not source: + return + for key in clean_source: + try: + pointer = pointer[key] + except: + raise KeyError("Tried to copy the keybinding `%s'," + " but it was not found." % source) + self.bind(context, target, copy.deepcopy(pointer)) + + def unbind(self, context, keys): + keys, pointer = self._clean_input(context, keys) + if not keys: + return + _unbind_traverse(pointer, keys) + + +class KeyBuffer(object): + any_key = ANYKEY + passive_key = PASSIVE_ACTION + quantifier_key = QUANT_KEY + exclude_from_anykey = [27] + + def __init__(self, keymap=None): + self.keymap = keymap + self.clear() + + def clear(self): + self.keys = [] + self.wildcards = [] + self.pointer = self.keymap + self.result = None + self.quantifier = None + self.finished_parsing_quantifier = False + self.finished_parsing = False + self.parse_error = False + + if self.keymap and self.quantifier_key in self.keymap: + if self.keymap[self.quantifier_key] == 'false': + self.finished_parsing_quantifier = True + + def add(self, key): + self.keys.append(key) + self.result = None + if not self.finished_parsing_quantifier and key in digits: + if self.quantifier is None: + self.quantifier = 0 + self.quantifier = self.quantifier * 10 + key - 48 # (48 = ord(0)) + else: + self.finished_parsing_quantifier = True + + moved = True + if key in self.pointer: + self.pointer = self.pointer[key] + elif self.any_key in self.pointer and \ + key not in self.exclude_from_anykey: + self.wildcards.append(key) + self.pointer = self.pointer[self.any_key] + else: + moved = False + + if moved: + if isinstance(self.pointer, dict): + if self.passive_key in self.pointer: + self.result = self.pointer[self.passive_key] + else: + self.result = self.pointer + self.finished_parsing = True + else: + self.finished_parsing = True + self.parse_error = True + + def __str__(self): + return "".join(key_to_string(c) for c in self.keys) diff --git a/ranger/ext/lazy_property.py b/ranger/ext/lazy_property.py index 320a1993..734d9616 100644 --- a/ranger/ext/lazy_property.py +++ b/ranger/ext/lazy_property.py @@ -4,12 +4,17 @@ class lazy_property(object): """ A @property-like decorator with lazy evaluation - Example: - class Foo: - @lazy_property - def bar(self): - result = [...] - return result + >>> class Foo: + ... @lazy_property + ... def answer(self): + ... print("calculating answer...") + ... return 2*3*7 + >>> foo = Foo() + >>> foo.answer + calculating answer... + 42 + >>> foo.answer + 42 """ def __init__(self, method): @@ -23,3 +28,7 @@ class lazy_property(object): result = self._method(obj) obj.__dict__[self.__name__] = result return result + +if __name__ == '__main__': + import doctest + doctest.testmod() diff --git a/ranger/ext/mount_path.py b/ranger/ext/mount_path.py index ba7dd052..31d6c602 100644 --- a/ranger/ext/mount_path.py +++ b/ranger/ext/mount_path.py @@ -1,4 +1,4 @@ -# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> +# Copyright (C) 2009, 2010, 2011 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 diff --git a/ranger/ext/openstruct.py b/ranger/ext/openstruct.py index a94c3031..b538d384 100644 --- a/ranger/ext/openstruct.py +++ b/ranger/ext/openstruct.py @@ -1,4 +1,4 @@ -# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> +# Copyright (C) 2009, 2010, 2011 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 diff --git a/ranger/ext/relative_symlink.py b/ranger/ext/relative_symlink.py index bba00e39..420f186c 100644 --- a/ranger/ext/relative_symlink.py +++ b/ranger/ext/relative_symlink.py @@ -1,4 +1,4 @@ -# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> +# Copyright (C) 2009, 2010, 2011 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 @@ -14,7 +14,6 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. from os import symlink, sep -from os.path import dirname, join def relative_symlink(src, dst): common_base = get_common_base(src, dst) diff --git a/ranger/ext/shell_escape.py b/ranger/ext/shell_escape.py index ec9e4b12..28a502bf 100644 --- a/ranger/ext/shell_escape.py +++ b/ranger/ext/shell_escape.py @@ -1,4 +1,4 @@ -# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> +# Copyright (C) 2009, 2010, 2011 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 diff --git a/ranger/ext/signals.py b/ranger/ext/signals.py index 5d80590a..ecb48de3 100644 --- a/ranger/ext/signals.py +++ b/ranger/ext/signals.py @@ -1,4 +1,4 @@ -# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> +# Copyright (C) 2009, 2010, 2011 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 @@ -13,82 +13,234 @@ # 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 efficient and minimalistic signaling/hook module. + +To use this in a class, subclass SignalDispatcher and call +SignalDispatcher.__init__(self) in the __init__ function. Now you can bind +functions to a signal name (string) by using signal_bind or remove it with +signal_unbind. Now whenever signal_emit is called with that signal name, +the bound functions are executed in order of priority. + +This module supports weak referencing. This means that if you bind a function +which is later deleted everywhere except in this binding, Python's garbage +collector will remove it from memory. Activate it with +signal_bind(..., weak=True). The handlers for such functions are automatically +deleted when trying to call them (in signal_emit), but if they are never +called, they accumulate and should be manually deleted with +signal_garbage_collect(). + +>>> def test_function(signal): +... if 'display' in signal: +... print(signal.display) +... else: +... signal.stop() +>>> def temporary_function(): +... print("A temporary function") + +>>> sig = SignalDispatcher() + +>>> # Test binding and unbinding +>>> handler1 = sig.signal_bind('test', test_function, priority=2) +>>> handler2 = sig.signal_bind('test', temporary_function, priority=1) +>>> sig.signal_emit('test', display="It works!") +It works! +A temporary function +True +>>> # Note that test_function stops the signal when there's no display keyword +>>> sig.signal_emit('test') +False +>>> sig.signal_unbind(handler1) +>>> sig.signal_emit('test') +A temporary function +True +>>> sig.signal_clear() +>>> sig.signal_emit('test') +True + +>>> # Bind temporary_function with a weak reference +>>> handler = sig.signal_bind('test', temporary_function, weak=True) +>>> sig.signal_emit('test') +A temporary function +True +>>> # Delete temporary_function. Its handler is removed too, since it +>>> # was weakly referenced. +>>> del temporary_function +>>> sig.signal_emit('test') +True +""" + import weakref from types import MethodType class Signal(dict): + """ + Signals are passed to the bound functions as an argument. + + They contain the attributes "origin", which is a reference to the + signal dispatcher, and "name", the name of the signal that was emitted. + You can call signal_emit with any keyword arguments, which will be + turned into attributes of this object as well. + + To delete a signal handler from inside a signal, raise a ReferenceError. + """ stopped = False def __init__(self, **keywords): dict.__init__(self, keywords) self.__dict__ = self def stop(self): + """ Stop the propagation of the signal to the next handlers. """ self.stopped = True -class SignalHandler(object): +class SignalHandler: + """ + Signal Handlers contain information about a signal binding. + + They are returned by signal_bind() and have to be passed to signal_unbind() + in order to remove the handler again. + + You can disable a handler without removing it by setting the attribute + "active" to False. + """ active = True def __init__(self, signal_name, function, priority, pass_signal): - self.priority = max(0, min(1, priority)) - self.signal_name = signal_name - self.function = function - self.pass_signal = pass_signal + self._priority = max(0, min(1, priority)) + self._signal_name = signal_name + self._function = function + self._pass_signal = pass_signal class SignalDispatcher(object): + """ + This abstract class handles the binding and emitting of signals. + """ def __init__(self): self._signals = dict() - signal_clear = __init__ + def signal_clear(self): + """ Remove all signals. """ + for handler_list in self._signals.values(): + for handler in handler_list: + handler._function = None + self._signals = dict() def signal_bind(self, signal_name, function, priority=0.5, weak=False): + """ + Bind a function to the signal. + + signal_name: Any string to name the signal + function: Any function with either one or zero arguments which will be + called when the signal is emitted. If it takes one argument, a + Signal object will be passed to it. + priority: Optional, any number. When signals are emitted, handlers will + be called in order of priority. (highest priority first) + weak: Use a weak reference of "function" so it can be garbage collected + properly when it's deleted. + + Returns a SignalHandler which can be used to remove this binding by + passing it to signal_unbind(). + """ assert isinstance(signal_name, str) + assert hasattr(function, '__call__') + assert hasattr(function, '__code__') + assert isinstance(priority, (int, float)) + assert isinstance(weak, bool) try: handlers = self._signals[signal_name] except: handlers = self._signals[signal_name] = [] nargs = function.__code__.co_argcount - try: - instance = function.__self__ - except: - if weak: - function = weakref.proxy(function) - else: + if getattr(function, '__self__', None): nargs -= 1 if weak: function = (function.__func__, weakref.proxy(function.__self__)) + elif weak: + function = weakref.proxy(function) + handler = SignalHandler(signal_name, function, priority, nargs > 0) handlers.append(handler) - handlers.sort(key=lambda handler: -handler.priority) + handlers.sort(key=lambda handler: -handler._priority) return handler def signal_unbind(self, signal_handler): + """ + Removes a signal binding. + + This requires the SignalHandler that has been originally returned by + signal_bind(). + """ try: - handlers = self._signals[signal_handler.signal_name] + handlers = self._signals[signal_handler._signal_name] except: pass else: try: + signal_handler._function = None handlers.remove(signal_handler) except: pass def signal_garbage_collect(self): + """ + Remove all handlers with deleted weak references. + + Usually this is not needed; every time you emit a signal, its handlers + are automatically checked in this way. However, if you can't be sure + that a signal is ever emitted AND you keep binding weakly referenced + functions to the signal, this method should be regularly called to + avoid memory leaks in self._signals. + + >>> sig = SignalDispatcher() + + >>> # lambda:None is an anonymous function which has no references + >>> # so it should get deleted immediately + >>> handler = sig.signal_bind('test', lambda: None, weak=True) + >>> len(sig._signals['test']) + 1 + >>> # need to call garbage collect so that it's removed from the list. + >>> sig.signal_garbage_collect() + >>> len(sig._signals['test']) + 0 + >>> # This demonstrates that garbage collecting is not necessary + >>> # when using signal_emit(). + >>> handler = sig.signal_bind('test', lambda: None, weak=True) + >>> sig.signal_emit('another_signal') + True + >>> len(sig._signals['test']) + 1 + >>> sig.signal_emit('test') + True + >>> len(sig._signals['test']) + 0 + """ for handler_list in self._signals.values(): i = len(handler_list) while i: i -= 1 handler = handler_list[i] try: - if isinstance(handler.function, tuple): - handler.function[1].__class__ + if isinstance(handler._function, tuple): + handler._function[1].__class__ else: - handler.function.__class__ + handler._function.__class__ except ReferenceError: + handler._function = None del handler_list[i] def signal_emit(self, signal_name, **kw): + """ + Emits a signal and call every function that was bound to that signal. + + You can call this method with any key words. They will be turned into + attributes of the Signal object that is passed to the functions. + If a function calls signal.stop(), no further functions will be called. + If a function raises a ReferenceError, the handler will be deleted. + + Returns False if signal.stop() was called and True otherwise. + """ assert isinstance(signal_name, str) if signal_name not in self._signals: return True @@ -101,17 +253,23 @@ class SignalDispatcher(object): # propagate for handler in tuple(handlers): if handler.active: - if isinstance(handler.function, tuple): - fnc = MethodType(*handler.function) - else: - fnc = handler.function try: - if handler.pass_signal: + if isinstance(handler._function, tuple): + fnc = MethodType(*handler._function) + else: + fnc = handler._function + if handler._pass_signal: fnc(signal) else: fnc() except ReferenceError: + handler._function = None handlers.remove(handler) if signal.stopped: return False return True + + +if __name__ == '__main__': + import doctest + doctest.testmod() diff --git a/ranger/ext/spawn.py b/ranger/ext/spawn.py index 9723c1ed..b1f04cf6 100644 --- a/ranger/ext/spawn.py +++ b/ranger/ext/spawn.py @@ -1,4 +1,4 @@ -# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> +# Copyright (C) 2009, 2010, 2011 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 diff --git a/ranger/ext/tree.py b/ranger/ext/tree.py deleted file mode 100644 index a954136b..00000000 --- a/ranger/ext/tree.py +++ /dev/null @@ -1,136 +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/>. - -class Tree(object): - def __init__(self, dictionary=None, parent=None, key=None): - if dictionary is None: - self._tree = dict() - else: - self._tree = dictionary - self.key = key - self.parent = parent - - def copy(self): - """Create a deep copy""" - def deep_copy_dict(dct): - dct = dct.copy() - for key, val in dct.items(): - if isinstance(val, dict): - dct[key] = deep_copy_dict(val) - return dct - newtree = Tree() - if isinstance(self._tree, dict): - newtree._tree = deep_copy_dict(self._tree) - else: - newtree._tree = self._tree - return newtree - - def merge(self, other, copy=False): - """Merge another Tree into a copy of self""" - def deep_merge(branch, otherbranch): - assert isinstance(otherbranch, dict) - if not isinstance(branch, dict): - branch = dict() - elif copy: - branch = branch.copy() - for key, val in otherbranch.items(): - if isinstance(val, dict): - if key not in branch: - branch[key] = None - branch[key] = deep_merge(branch[key], val) - else: - branch[key] = val - return branch - - if isinstance(self._tree, dict) and isinstance(other._tree, dict): - content = deep_merge(self._tree, other._tree) - elif copy and hasattr(other._tree, 'copy'): - content = other._tree.copy() - else: - content = other._tree - return type(self)(content) - - def set(self, keys, value, force=True): - """Sets the element at the end of the path to <value>.""" - if not isinstance(keys, (list, tuple)): - keys = tuple(keys) - if len(keys) == 0: - self.replace(value) - else: - fnc = force and self.plow or self.traverse - subtree = fnc(keys) - subtree.replace(value) - - def unset(self, iterable): - chars = list(iterable) - first = True - - while chars: - if first or isinstance(subtree, Tree) and subtree.empty(): - top = chars.pop() - subtree = self.traverse(chars) - assert top in subtree._tree, "no such key: " + chr(top) - del subtree._tree[top] - else: - break - first = False - - def empty(self): - return len(self._tree) == 0 - - def replace(self, value): - if self.parent: - self.parent[self.key] = value - self._tree = value - - def plow(self, iterable): - """Move along a path, creating nonexistant subtrees""" - tree = self._tree - last_tree = None - char = None - for char in iterable: - try: - newtree = tree[char] - if not isinstance(newtree, dict): - raise KeyError() - except KeyError: - newtree = dict() - tree[char] = newtree - last_tree = tree - tree = newtree - if isinstance(tree, dict): - return type(self)(tree, parent=last_tree, key=char) - else: - return tree - - def traverse(self, iterable): - """Move along a path, raising exceptions when failed""" - tree = self._tree - last_tree = tree - char = None - for char in iterable: - last_tree = tree - try: - tree = tree[char] - except TypeError: - raise KeyError("trying to enter leaf") - except KeyError: - raise KeyError(repr(char) + " not in tree " + str(tree)) - if isinstance(tree, dict): - return type(self)(tree, parent=last_tree, key=char) - else: - return tree - - __getitem__ = traverse diff --git a/ranger/ext/utfwidth.py b/ranger/ext/utfwidth.py deleted file mode 100644 index 0976fee1..00000000 --- a/ranger/ext/utfwidth.py +++ /dev/null @@ -1,113 +0,0 @@ -# -*- encoding: utf8 -*- -# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> -# Copyright (C) 2004, 2005 Timo Hirvonen -# -# 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/>. -# -# ---- -# This file contains portions of code from cmus (uchar.c). - -NARROW = 1 -WIDE = 2 - -def uwid(string): - """Return the width of a string""" - end = len(string) - i = 0 - width = 0 - while i < end: - bytelen = utf_byte_length(string[i:]) - width += utf_char_width(string[i:i+bytelen]) - i += bytelen - return width - -def uchars(string): - """Return a list with one string for each character""" - end = len(string) - i = 0 - result = [] - while i < end: - bytelen = utf_byte_length(string[i:]) - result.append(string[i:i+bytelen]) - i += bytelen - return result - -def utf_byte_length(string): - """Return the byte length of one utf character""" - firstord = ord(string[0]) - if firstord < 0b01111111: - return 1 - if firstord < 0b10111111: - return 1 # invalid - if firstord < 0b11011111: - return 2 - if firstord < 0b11101111: - return 3 - if firstord < 0b11110100: - return 4 - return 1 # invalid - - -def utf_char_width(string): - """Return the width of a single character""" - u = _utf_char_to_int(string) - return utf_char_width_(u) - -def utf_char_width_(u): - if u < 0x1100: - return NARROW - # Hangul Jamo init. constonants - if u <= 0x115F: - return WIDE - # Angle Brackets - if u == 0x2329 or u == 0x232A: - return WIDE - if u < 0x2e80: - return NARROW - # CJK ... Yi - if u < 0x302A: - return WIDE - if u <= 0x302F: - return NARROW - if u == 0x303F or u == 0x3099 or u == 0x309a: - return NARROW - # CJK ... Yi - if u <= 0xA4CF: - return WIDE - # Hangul Syllables - if u >= 0xAC00 and u <= 0xD7A3: - return WIDE - # CJK Compatibility Ideographs - if u >= 0xF900 and u <= 0xFAFF: - return WIDE - # CJK Compatibility Forms - if u >= 0xFE30 and u <= 0xFE6F: - return WIDE - # Fullwidth Forms - if u >= 0xFF00 and u <= 0xFF60 or u >= 0xFFE0 and u <= 0xFFE6: - return WIDE - # CJK Extra Stuff - if u >= 0x20000 and u <= 0x2FFFD: - return WIDE - # ? - if u >= 0x30000 and u <= 0x3FFFD: - return WIDE - return NARROW # invalid (?) - -def _utf_char_to_int(string): - # Squash the last 6 bits of each byte together to an integer - u = 0 - for c in string: - u = (u << 6) | (ord(c) & 0b00111111) - return u diff --git a/ranger/ext/waitpid_no_intr.py b/ranger/ext/waitpid_no_intr.py deleted file mode 100644 index 12fbcbce..00000000 --- a/ranger/ext/waitpid_no_intr.py +++ /dev/null @@ -1,30 +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 errno import EINTR -from os import waitpid - -def waitpid_no_intr(pid): - """catch interrupts which occur while using os.waitpid""" - while True: - try: - return waitpid(pid, 0) - except KeyboardInterrupt: - continue - except OSError as e: - if e.errno == EINTR: - continue - else: - raise diff --git a/ranger/ext/widestring.py b/ranger/ext/widestring.py index c7230806..525e1bc1 100644 --- a/ranger/ext/widestring.py +++ b/ranger/ext/widestring.py @@ -1,6 +1,5 @@ # -*- encoding: utf8 -*- -# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> -# Copyright (C) 2004, 2005 Timo Hirvonen +# Copyright (C) 2009, 2010, 2011 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 @@ -14,135 +13,46 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -# -# ---- -# This file contains portions of code from cmus (uchar.c). import sys +from unicodedata import east_asian_width +PY3 = sys.version > '3' ASCIIONLY = set(chr(c) for c in range(1, 128)) NARROW = 1 WIDE = 2 +WIDE_SYMBOLS = set('WF') -def _utf_char_to_int(string): - # Squash the last 6 bits of each byte together to an integer - if sys.version > '3': - return ord(string) - else: - # THIS CODE IS INCORRECT - u = 0 - for c in string: - u = (u << 6) | (ord(c) & 0b00111111) - return u - -def utf_char_width_(u): - if u < 0x1100: - return NARROW - # Hangul Jamo init. constonants - if u <= 0x115F: - return WIDE - # Angle Brackets - if u == 0x2329 or u == 0x232A: - return WIDE - if u < 0x2e80: - return NARROW - # CJK ... Yi - if u < 0x302A: - return WIDE - if u <= 0x302F: - return NARROW - if u == 0x303F or u == 0x3099 or u == 0x309a: - return NARROW - # CJK ... Yi - if u <= 0xA4CF: - return WIDE - # Hangul Syllables - if u >= 0xAC00 and u <= 0xD7A3: - return WIDE - # CJK Compatibility Ideographs - if u >= 0xF900 and u <= 0xFAFF: - return WIDE - # CJK Compatibility Forms - if u >= 0xFE30 and u <= 0xFE6F: - return WIDE - # Fullwidth Forms - if u >= 0xFF00 and u <= 0xFF60 or u >= 0xFFE0 and u <= 0xFFE6: - return WIDE - # CJK Extra Stuff - if u >= 0x20000 and u <= 0x2FFFD: - return WIDE - # ? - if u >= 0x30000 and u <= 0x3FFFD: - return WIDE - return NARROW # invalid (?) - -def uchars(string): - if sys.version >= '3': - return list(string) - end = len(string) - i = 0 - result = [] - while i < end: - bytelen = utf_byte_length(string[i:]) - result.append(string[i:i+bytelen]) - i += bytelen - return result - - -def utf_byte_length(string): - """Return the byte length of one utf character""" - if sys.version >= '3': - firstord = string.encode("utf-8")[0] - else: - firstord = ord(string[0]) - if firstord < 0b01111111: - return 1 - if firstord < 0b10111111: - return 1 # invalid - if firstord < 0b11011111: - return 2 - if firstord < 0b11101111: - return 3 - if firstord < 0b11110100: - return 4 - return 1 # invalid +def uwid(string): + """Return the width of a string""" + if not PY3: + string = string.decode('utf-8', 'ignore') + return sum(utf_char_width(c) for c in string) def utf_char_width(string): """Return the width of a single character""" - u = _utf_char_to_int(string) - return utf_char_width_(u) - - -def width(string): - """Return the width of a string""" - end = len(string) - i = 0 - width = 0 - while i < end: - bytelen = utf_byte_length(string[i:]) - width += utf_char_width(string[i:i+bytelen]) - i += bytelen - return width + if east_asian_width(string) in WIDE_SYMBOLS: + return WIDE + return NARROW def string_to_charlist(string): + """Return a list of characters with extra empty strings after wide chars""" if not set(string) - ASCIIONLY: return list(string) - end = len(string) - i = 0 result = [] - py3 = sys.version > '3' - while i < end: - if py3: - result.append(string[i:i+1]) - i += 1 - else: - bytelen = utf_byte_length(string[i:]) - result.append(string[i:i+bytelen]) - i += bytelen - if utf_char_width_(_utf_char_to_int(result[-1])) == WIDE: - result.append('') + if PY3: + for c in string: + result.append(c) + if east_asian_width(c) in WIDE_SYMBOLS: + result.append('') + else: + string = string.decode('utf-8', 'ignore') + for c in string: + result.append(c.encode('utf-8')) + if east_asian_width(c) in WIDE_SYMBOLS: + result.append('') return result @@ -186,37 +96,6 @@ class WideString(object): def __repr__(self): return '<' + self.__class__.__name__ + " '" + self.string + "'>" - #def __getslice__(self, a, z): - #""" - #>>> WideString("asdf")[1:3] - #<WideString 'sd'> - #>>> WideString("モヒカン")[2:4] - #<WideString 'ヒ'> - #>>> WideString("モヒカン")[2:5] - #<WideString 'ヒ '> - #>>> WideString("モヒカン")[1:5] - #<WideString ' ヒ '> - #>>> WideString("モヒカン")[:] - #<WideString 'モヒカン'> - #>>> WideString("asdfモ")[0:6] - #<WideString 'asdfモ'> - #>>> WideString("asdfモ")[0:5] - #<WideString 'asdf '> - #>>> WideString("asdfモ")[0:4] - #<WideString 'asdf'> - #""" - #if z is None or z >= len(self.chars): - #z = len(self.chars) - 1 - #if a is None or a < 0: - #a = 0 - #if z < len(self.chars) - 1 and self.chars[z] == '': - #if self.chars[a] == '': - #return WideString(' ' + ''.join(self.chars[a:z - 1]) + ' ') - #return WideString(''.join(self.chars[a:z - 1]) + ' ') - #if self.chars[a] == '': - #return WideString(' ' + ''.join(self.chars[a:z - 1])) - #return WideString(''.join(self.chars[a:z])) - def __getslice__(self, a, z): """ >>> WideString("asdf")[1:3] diff --git a/ranger/fsobject/__init__.py b/ranger/fsobject/__init__.py index 5fb4b877..f3de0fcf 100644 --- a/ranger/fsobject/__init__.py +++ b/ranger/fsobject/__init__.py @@ -1,20 +1,7 @@ -# 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/>. - -"""FileSystemObjects are representation of files and directories -with fast access to their properties through caching""" +""" +FileSystemObjects are representation of files and directories +with fast access to their properties through caching +""" BAD_INFO = '?' diff --git a/ranger/fsobject/directory.py b/ranger/fsobject/directory.py index 6d34b02d..9cfbb6c0 100644 --- a/ranger/fsobject/directory.py +++ b/ranger/fsobject/directory.py @@ -1,4 +1,4 @@ -# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> +# Copyright (C) 2009, 2010, 2011 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 @@ -14,20 +14,17 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. import os.path -import stat -from stat import S_ISLNK, S_ISDIR from os import stat as os_stat, lstat as os_lstat -from os.path import join, isdir, basename from collections import deque from time import time +from ranger.fsobject import BAD_INFO from ranger.core.loader import Loadable from ranger.ext.mount_path import mount_path -from ranger.fsobject import BAD_INFO, File, FileSystemObject +from ranger.fsobject import File, FileSystemObject from ranger.core.shared import SettingsAware from ranger.ext.accumulator import Accumulator from ranger.ext.lazy_property import lazy_property -import ranger.fsobject def sort_by_basename(path): """returns path.basename (for sorting)""" @@ -146,6 +143,8 @@ class Directory(FileSystemObject, Accumulator, Loadable, SettingsAware): del self.marked_items[:] self._clear_marked_items() + # XXX: Is it really necessary to have the marked items in a list? + # Can't we just recalculate them with [f for f in self.files if f.marked]? def _gc_marked_items(self): for item in list(self.marked_items): if item.path not in self.filenames: @@ -166,6 +165,7 @@ class Directory(FileSystemObject, Accumulator, Loadable, SettingsAware): else: return [] + # XXX: Check for possible race conditions def load_bit_by_bit(self): """ Returns a generator which load a part of the directory @@ -184,8 +184,11 @@ class Directory(FileSystemObject, Accumulator, Loadable, SettingsAware): hidden_filter = not self.settings.show_hidden \ and self.settings.hidden_filter + filelist = os.listdir(mypath) + self.size = len(filelist) + self.infostring = ' %d' % self.size filenames = [mypath + (mypath == '/' and fname or '/' + fname)\ - for fname in os.listdir(mypath) if accept_file( + for fname in filelist if accept_file( fname, mypath, hidden_filter, self.filter)] yield @@ -328,8 +331,9 @@ class Directory(FileSystemObject, Accumulator, Loadable, SettingsAware): try: size = len(os.listdir(self.path)) # bite me except OSError: - self.infostring = '?' + self.infostring = BAD_INFO self.accessible = False + self.runnable = False return 0 else: self.infostring = ' %d' % size diff --git a/ranger/fsobject/file.py b/ranger/fsobject/file.py index c24b560f..66bc549b 100644 --- a/ranger/fsobject/file.py +++ b/ranger/fsobject/file.py @@ -1,4 +1,4 @@ -# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> +# Copyright (C) 2009, 2010, 2011 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 @@ -15,8 +15,6 @@ import re from ranger.fsobject import FileSystemObject -from subprocess import Popen, PIPE -from ranger.core.loader import CommandLoader N_FIRST_BYTES = 256 control_characters = set(chr(n) for n in diff --git a/ranger/fsobject/fsobject.py b/ranger/fsobject/fsobject.py index 647b7604..d74b21c1 100644 --- a/ranger/fsobject/fsobject.py +++ b/ranger/fsobject/fsobject.py @@ -1,4 +1,4 @@ -# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> +# Copyright (C) 2009, 2010, 2011 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 @@ -18,10 +18,8 @@ CONTAINER_EXTENSIONS = ('7z', 'ace', 'ar', 'arc', 'bz', 'bz2', 'cab', 'cpio', 'shar', 'tar', 'tbz', 'tgz', 'xar', 'xpi', 'xz', 'zip') import re -from os import access, listdir, lstat, readlink, stat -from time import time +from os import lstat, stat from os.path import abspath, basename, dirname, realpath, splitext, extsep -from . import BAD_INFO from ranger.core.shared import FileManagerAware from ranger.ext.shell_escape import shell_escape from ranger.ext.spawn import spawn @@ -65,7 +63,6 @@ class FileSystemObject(FileManagerAware): media, video) = (False,) * 21 - mimetype_tuple = () size = 0 diff --git a/ranger/gui/ansi.py b/ranger/gui/ansi.py index ea024977..65038120 100644 --- a/ranger/gui/ansi.py +++ b/ranger/gui/ansi.py @@ -1,5 +1,5 @@ # Copyright (C) 2010 David Barnett <davidbarnett2@gmail.com> -# Copyright (C) 2010 Roman Zimbelmann <romanz@lavabit.com> +# Copyright (C) 2010, 2011 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 @@ -14,18 +14,22 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. +""" +A library to help to convert ANSI codes to curses instructions. +""" + from ranger.gui import color import re -ansi_re = re.compile('(\033' + r'\[\d*(?:;\d+)*?[a-zA-Z])') -reset = '\033[0m' +ansi_re = re.compile('(\x1b' + r'\[\d*(?:;\d+)*?[a-zA-Z])') +reset = '\x1b[0m' def split_ansi_from_text(ansi_text): return ansi_re.split(ansi_text) def text_with_fg_bg_attr(ansi_text): for chunk in split_ansi_from_text(ansi_text): - if chunk and chunk[0] == '\033': + if chunk and chunk[0] == '\x1b': if chunk[-1] != 'm': continue match = re.match(r'^.\[(.*).$', chunk) @@ -66,31 +70,72 @@ def text_with_fg_bg_attr(ansi_text): yield chunk def char_len(ansi_text): + """ + Count the number of visible characters. + + >>> char_len("\x1b[0;30;40mX\x1b[0m") + 1 + >>> char_len("\x1b[0;30;40mXY\x1b[0m") + 2 + >>> char_len("\x1b[0;30;40mX\x1b[0mY") + 2 + >>> char_len("hello") + 5 + >>> char_len("") + 0 + """ return len(ansi_re.sub('', ansi_text)) -def char_slice(ansi_text, start, end): - slice_chunks = [] - # skip to start - last_color = None - skip_len_left = start - len_left = end - start - for chunk in split_ansi_from_text(ansi_text): - m = ansi_re.match(chunk) - if m: - if chunk[-1] == 'm': - last_color = chunk +def char_slice(ansi_text, start, length): + """ + Slices a string with respect to ansi code sequences + + Acts as if the ansi codes aren't there, slices the text from the + given start point to the given length and adds the codes back in. + + >>> test_string = "abcde\x1b[30mfoo\x1b[31mbar\x1b[0mnormal" + >>> split_ansi_from_text(test_string) + ['abcde', '\\x1b[30m', 'foo', '\\x1b[31m', 'bar', '\\x1b[0m', 'normal'] + >>> char_slice(test_string, 1, 3) + 'bcd' + >>> char_slice(test_string, 5, 6) + '\\x1b[30mfoo\\x1b[31mbar' + >>> char_slice(test_string, 0, 8) + 'abcde\\x1b[30mfoo' + >>> char_slice(test_string, 4, 4) + 'e\\x1b[30mfoo' + >>> char_slice(test_string, 11, 100) + '\\x1b[0mnormal' + >>> char_slice(test_string, 9, 100) + '\\x1b[31mar\\x1b[0mnormal' + >>> char_slice(test_string, 9, 4) + '\\x1b[31mar\\x1b[0mno' + """ + chunks = [] + last_color = "" + pos = old_pos = 0 + for i, chunk in enumerate(split_ansi_from_text(ansi_text)): + if i % 2 == 1: + last_color = chunk + continue + + old_pos = pos + pos += len(chunk) + if pos <= start: + pass # seek + elif old_pos < start and pos >= start: + chunks.append(last_color) + chunks.append(chunk[start-old_pos:start-old_pos+length]) + elif pos > length + start: + chunks.append(last_color) + chunks.append(chunk[:start-old_pos+length]) else: - if skip_len_left > len(chunk): - skip_len_left -= len(chunk) - else: # finished skipping to start - if skip_len_left > 0: - chunk = chunk[skip_len_left:] - chunk_left = chunk[:len_left] - if len(chunk_left): - if last_color is not None: - slice_chunks.append(last_color) - slice_chunks.append(chunk_left) - len_left -= len(chunk_left) - if len_left == 0: - break - return ''.join(slice_chunks) + chunks.append(last_color) + chunks.append(chunk) + if pos - start >= length: + break + return ''.join(chunks) + +if __name__ == '__main__': + import doctest + doctest.testmod() diff --git a/ranger/gui/bar.py b/ranger/gui/bar.py index aa5c9ab4..40218fb1 100644 --- a/ranger/gui/bar.py +++ b/ranger/gui/bar.py @@ -1,4 +1,4 @@ -# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> +# Copyright (C) 2009, 2010, 2011 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 @@ -13,7 +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/>. -from ranger.ext.utfwidth import uwid +from ranger.ext.widestring import WideString, utf_char_width +import sys +PY3 = sys.version > '3' class Bar(object): left = None @@ -45,7 +47,7 @@ class Bar(object): # remove elemets from the left until it fits if sumsize > wid: while len(self.left) > 0: - leftsize -= len(self.left.pop(-1).string) + leftsize -= len(self.left.pop(-1)) if leftsize + rightsize <= wid: break sumsize = leftsize + rightsize @@ -53,7 +55,7 @@ class Bar(object): # remove elemets from the right until it fits if sumsize > wid: while len(self.right) > 0: - rightsize -= len(self.right.pop(0).string) + rightsize -= len(self.right.pop(0)) if leftsize + rightsize <= wid: break sumsize = leftsize + rightsize @@ -67,18 +69,17 @@ class Bar(object): raise ValueError("Cannot shrink down to that size by cutting") leftsize = self.left.sumsize() rightsize = self.right.sumsize() - oversize = leftsize + rightsize - wid - 1 + oversize = leftsize + rightsize - wid if oversize <= 0: return self.fill_gap(' ', wid, gapwidth=False) - nonfixed_items = self.left.nonfixed_items() - # Shrink items to a minimum size of 1 until there is enough room. + # Shrink items to a minimum size until there is enough room. for item in self.left: if not item.fixed: itemlen = len(item) - if oversize > itemlen - 1: - item.cut_off_to(1) - oversize -= (itemlen - 1) + if oversize > itemlen - item.min_size: + item.cut_off_to(item.min_size) + oversize -= (itemlen - item.min_size) else: item.cut_off(oversize) break @@ -117,28 +118,34 @@ class BarSide(list): if item.fixed: n += len(item) else: - n += 1 + n += item.min_size return n - def nonfixed_items(self): - return sum(1 for item in self if not item.fixed) - class ColoredString(object): def __init__(self, string, *lst): - self.string = string + self.string = WideString(string) self.lst = lst self.fixed = False + if not len(string): + self.min_size = 0 + elif PY3: + self.min_size = utf_char_width(string[0]) + else: + self.min_size = utf_char_width(self.string.chars[0].decode('utf-8')) def cut_off(self, n): if n >= 1: self.string = self.string[:-n] def cut_off_to(self, n): - self.string = self.string[:n] + if n < self.min_size: + self.string = self.string[:self.min_size] + elif n < len(self.string): + self.string = self.string[:n] def __len__(self): - return uwid(self.string) + return len(self.string) def __str__(self): - return self.string + return str(self.string) diff --git a/ranger/gui/color.py b/ranger/gui/color.py index 67be30f7..889f9e9a 100644 --- a/ranger/gui/color.py +++ b/ranger/gui/color.py @@ -1,4 +1,4 @@ -# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> +# Copyright (C) 2009, 2010, 2011 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 diff --git a/ranger/gui/colorscheme.py b/ranger/gui/colorscheme.py index 5a2a97ef..bc5a67a5 100644 --- a/ranger/gui/colorscheme.py +++ b/ranger/gui/colorscheme.py @@ -1,4 +1,4 @@ -# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> +# Copyright (C) 2009, 2010, 2011 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 @@ -156,7 +156,7 @@ def _colorscheme_name_to_class(signal): and is_scheme(scheme_module.Scheme): signal.value = scheme_module.Scheme() else: - for name, var in scheme_module.__dict__.items(): + for var in scheme_module.__dict__.values(): if var != ColorScheme and is_scheme(var): signal.value = var() break diff --git a/ranger/gui/context.py b/ranger/gui/context.py index 20ce2817..3e7ca505 100644 --- a/ranger/gui/context.py +++ b/ranger/gui/context.py @@ -1,4 +1,4 @@ -# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> +# Copyright (C) 2009, 2010, 2011 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 @@ -24,8 +24,8 @@ CONTEXT_KEYS = ['reset', 'error', 'badinfo', 'space', 'permissions', 'owner', 'group', 'mtime', 'nlink', 'scroll', 'all', 'bot', 'top', 'percentage', 'filter', 'marked', 'tagged', 'tag_marker', 'cut', 'copied', - 'help_markup', - 'seperator', 'key', 'special', 'border', + 'help_markup', # COMPAT + 'seperator', 'key', 'special', 'border', # COMPAT 'title', 'text', 'highlight', 'bars', 'quotes', 'tab', 'keybuffer'] diff --git a/ranger/gui/curses_shortcuts.py b/ranger/gui/curses_shortcuts.py index bae03adc..a977beda 100644 --- a/ranger/gui/curses_shortcuts.py +++ b/ranger/gui/curses_shortcuts.py @@ -1,4 +1,4 @@ -# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> +# Copyright (C) 2009, 2010, 2011 Roman Zimbelmann <romanz@lavabit.com> # Copyright (C) 2010 David Barnett <davidbarnett2@gmail.com> # # This program is free software: you can redistribute it and/or modify @@ -21,22 +21,6 @@ from ranger.ext.iter_tools import flatten from ranger.gui.color import get_color from ranger.core.shared import SettingsAware -def ascii_only(string): - # Some python versions have problems with invalid unicode strings. - # I think this exception is rare enough that this naive hack is enough. - # It simply removes all non-ascii chars from a string. - def validate_char(char): - try: - if ord(char) > 127: - return '?' - except: - return '?' - return char - if isinstance(string, str): - return ''.join(validate_char(c) for c in string) - return string - - class CursesShortcuts(SettingsAware): """ This class defines shortcuts to faciliate operations with curses. @@ -50,35 +34,20 @@ class CursesShortcuts(SettingsAware): def addstr(self, *args): try: self.win.addstr(*args) - except (_curses.error, TypeError): + except: pass - except UnicodeEncodeError: - try: - self.win.addstr(*(ascii_only(obj) for obj in args)) - except (_curses.error, TypeError): - pass def addnstr(self, *args): try: self.win.addnstr(*args) - except (_curses.error, TypeError): + except: pass - except UnicodeEncodeError: - try: - self.win.addnstr(*(ascii_only(obj) for obj in args)) - except (_curses.error, TypeError): - pass def addch(self, *args): try: self.win.addch(*args) - except (_curses.error, TypeError): + except: pass - except UnicodeEncodeError: - try: - self.win.addch(*(ascii_only(obj) for obj in args)) - except (_curses.error, TypeError): - pass def color(self, *keys): """Change the colors from now on.""" diff --git a/ranger/gui/defaultui.py b/ranger/gui/defaultui.py deleted file mode 100644 index 933b56f7..00000000 --- a/ranger/gui/defaultui.py +++ /dev/null @@ -1,136 +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.gui.ui import UI -from ranger.gui.widgets.browserview import BrowserView -from ranger.gui.widgets.titlebar import TitleBar -from ranger.gui.widgets.console import Console -from ranger.gui.widgets.statusbar import StatusBar -from ranger.gui.widgets.taskview import TaskView -from ranger.gui.widgets.pager import Pager - -class DefaultUI(UI): - def setup(self): - """Build up the UI by initializing widgets.""" - # Create a title bar - self.titlebar = TitleBar(self.win) - self.add_child(self.titlebar) - - # Create the browser view - self.browser = BrowserView(self.win, self.settings.column_ratios) - self.settings.signal_bind('setopt.column_ratios', - self.browser.change_ratios) - self.add_child(self.browser) - - # Create the process manager - self.taskview = TaskView(self.win) - self.taskview.visible = False - self.add_child(self.taskview) - - # Create the status bar - self.status = StatusBar(self.win, self.browser.main_column) - self.add_child(self.status) - - # Create the console - self.console = Console(self.win) - self.add_child(self.console) - self.console.visible = False - - # Create the pager - self.pager = Pager(self.win) - self.pager.visible = False - self.add_child(self.pager) - - def update_size(self): - """resize all widgets""" - UI.update_size(self) - y, x = self.env.termsize - - self.browser.resize(1, 0, y - 2, x) - self.taskview.resize(1, 0, y - 2, x) - self.pager.resize(1, 0, y - 2, x) - self.titlebar.resize(0, 0, 1, x) - self.status.resize(y - 1, 0, 1, x) - self.console.resize(y - 1, 0, 1, x) - - def notify(self, *a, **k): - return self.status.notify(*a, **k) - - def close_pager(self): - if self.console.visible: - self.console.focused = True - self.pager.close() - self.pager.visible = False - self.pager.focused = False - self.browser.visible = True - - def open_pager(self): - if self.console.focused: - self.console.focused = False - self.pager.open() - self.pager.visible = True - self.pager.focused = True - self.browser.visible = False - return self.pager - - def open_embedded_pager(self): - self.browser.open_pager() - return self.browser.pager - - def close_embedded_pager(self): - self.browser.close_pager() - - def open_console(self, string='', prompt=None, position=None): - if self.console.open(string, prompt=prompt, position=position): - self.status.msg = None - self.console.on_close = self.close_console - self.console.visible = True - self.status.visible = False - - def close_console(self): - self.console.visible = False - self.status.visible = True - self.close_pager() - - def open_taskview(self): - self.pager.close() - self.pager.visible = False - self.pager.focused = False - self.console.visible = False - self.browser.visible = False - self.taskview.visible = True - self.taskview.focused = True - self.fm.hint('*tasks:* *dd*:remove *J*:move_down *H*:move_up') - - def redraw_main_column(self): - self.browser.main_column.need_redraw = True - - def close_taskview(self): - self.taskview.visible = False - self.browser.visible = True - self.taskview.focused = False - - def scroll(self, relative): - if self.browser and self.browser.main_column: - self.browser.main_column.scroll(relative) - - def throbber(self, string='.', remove=False): - if remove: - self.titlebar.throbber = type(self.titlebar).throbber - else: - self.titlebar.throbber = string - - def hint(self, text=None): - self.status.hint = text diff --git a/ranger/gui/displayable.py b/ranger/gui/displayable.py index 70455b35..5e9562a7 100644 --- a/ranger/gui/displayable.py +++ b/ranger/gui/displayable.py @@ -1,4 +1,4 @@ -# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> +# Copyright (C) 2009, 2010, 2011 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 @@ -13,8 +13,6 @@ # 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 _curses - from ranger.core.shared import FileManagerAware, EnvironmentAware from ranger.gui.curses_shortcuts import CursesShortcuts diff --git a/ranger/gui/mouse_event.py b/ranger/gui/mouse_event.py index 4a2860b8..cb697d8d 100644 --- a/ranger/gui/mouse_event.py +++ b/ranger/gui/mouse_event.py @@ -1,4 +1,4 @@ -# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> +# Copyright (C) 2009, 2010, 2011 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 diff --git a/ranger/gui/ui.py b/ranger/gui/ui.py index cc2871af..91d0d774 100644 --- a/ranger/gui/ui.py +++ b/ranger/gui/ui.py @@ -1,4 +1,4 @@ -# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> +# Copyright (C) 2009, 2010, 2011 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 @@ -19,8 +19,6 @@ import curses import _curses from .displayable import DisplayableContainer -from ranger.gui.curses_shortcuts import ascii_only -from ranger.container.keymap import CommandArgs from .mouse_event import MouseEvent from ranger.ext.keybinding_parser import ALT_KEY @@ -30,6 +28,10 @@ TERMINALS_WITH_TITLE = ("xterm", "xterm-256color", "rxvt", MOUSEMASK = curses.ALL_MOUSE_EVENTS | curses.REPORT_MOUSE_POSITION +_ASCII = ''.join(chr(c) for c in range(32, 127)) +def ascii_only(string): + return ''.join(c if c in _ASCII else '?' for c in string) + def _setup_mouse(signal): if signal['value']: curses.mousemask(MOUSEMASK) @@ -45,9 +47,12 @@ def _setup_mouse(signal): else: curses.mousemask(0) +# TODO: progress bar +# TODO: branch view class UI(DisplayableContainer): is_set_up = False load_mode = False + is_on = False def __init__(self, env=None, fm=None): self._draw_title = os.environ["TERM"] in TERMINALS_WITH_TITLE os.environ['ESCDELAY'] = '25' # don't know a cleaner way @@ -57,9 +62,13 @@ class UI(DisplayableContainer): if fm is not None: self.fm = fm - self.win = curses.initscr() - self.env.keymanager.use_context('browser') - self.env.keybuffer.clear() + try: + self.win = curses.initscr() + except _curses.error as e: + if e.args[0] == "setupterm: could not find terminal": + os.environ['TERM'] = 'linux' + self.win = curses.initscr() + self.env.keymaps.use_keymap('browser') DisplayableContainer.__init__(self, None) @@ -85,7 +94,10 @@ class UI(DisplayableContainer): if not self.is_set_up: self.is_set_up = True self.setup() + self.win.addstr("loading...") + self.win.refresh() self.update_size() + self.is_on = True def suspend(self): """Turn off curses""" @@ -99,6 +111,7 @@ class UI(DisplayableContainer): if self.settings.mouse_enabled: _setup_mouse(dict(value=False)) curses.endwin() + self.is_on = False def set_load_mode(self, boolean): boolean = bool(boolean) @@ -135,37 +148,32 @@ class UI(DisplayableContainer): if key < 0: self.env.keybuffer.clear() - return - if DisplayableContainer.press(self, key): - return + elif not DisplayableContainer.press(self, key): + self.env.keymaps.use_keymap('browser') + self.press(key) + def press(self, key): + keybuffer = self.env.keybuffer self.status.clear_message() - self.env.keymanager.use_context('browser') - self.env.key_append(key) - kbuf = self.env.keybuffer - cmd = kbuf.command - + keybuffer.add(key) self.fm.hide_bookmarks() + self.browser.draw_hints = not keybuffer.finished_parsing \ + and keybuffer.finished_parsing_quantifier - if kbuf.failure: - kbuf.clear() - return - elif not cmd: - return - - self.env.cmd = cmd - - if cmd.function: + if keybuffer.result is not None: try: - cmd.function(CommandArgs.from_widget(self.fm)) - except Exception as error: - self.fm.notify(error) - if kbuf.done: - kbuf.clear() - else: - kbuf.clear() + self.fm.execute_console(keybuffer.result, + wildcards=keybuffer.wildcards, + quantifier=keybuffer.quantifier) + finally: + if keybuffer.finished_parsing: + keybuffer.clear() + elif keybuffer.finished_parsing: + keybuffer.clear() + return False + return True def handle_keys(self, *keys): for key in keys: @@ -210,10 +218,42 @@ class UI(DisplayableContainer): self.handle_key(key) def setup(self): - """ - Called after an initialize() call. - Override this! - """ + """Build up the UI by initializing widgets.""" + from ranger.gui.widgets.browserview import BrowserView + from ranger.gui.widgets.titlebar import TitleBar + from ranger.gui.widgets.console import Console + from ranger.gui.widgets.statusbar import StatusBar + from ranger.gui.widgets.taskview import TaskView + from ranger.gui.widgets.pager import Pager + + # Create a title bar + self.titlebar = TitleBar(self.win) + self.add_child(self.titlebar) + + # Create the browser view + self.browser = BrowserView(self.win, self.settings.column_ratios) + self.settings.signal_bind('setopt.column_ratios', + self.browser.change_ratios) + self.add_child(self.browser) + + # Create the process manager + self.taskview = TaskView(self.win) + self.taskview.visible = False + self.add_child(self.taskview) + + # Create the status bar + self.status = StatusBar(self.win, self.browser.main_column) + self.add_child(self.status) + + # Create the console + self.console = Console(self.win) + self.add_child(self.console) + self.console.visible = False + + # Create the pager + self.pager = Pager(self.win) + self.pager.visible = False + self.add_child(self.pager) def redraw(self): """Redraw all widgets""" @@ -230,11 +270,16 @@ class UI(DisplayableContainer): self.need_redraw = True def update_size(self): - """ - Update self.env.termsize. - Extend this method to resize all widgets! - """ + """resize all widgets""" self.env.termsize = self.win.getmaxyx() + y, x = self.env.termsize + + self.browser.resize(1, 0, y - 2, x) + self.taskview.resize(1, 0, y - 2, x) + self.pager.resize(1, 0, y - 2, x) + self.titlebar.resize(0, 0, 1, x) + self.status.resize(y - 1, 0, 1, x) + self.console.resize(y - 1, 0, 1, x) def draw(self): """Draw all objects in the container""" @@ -258,3 +303,65 @@ class UI(DisplayableContainer): """Finalize every object in container and refresh the window""" DisplayableContainer.finalize(self) self.win.refresh() + + def close_pager(self): + if self.console.visible: + self.console.focused = True + self.pager.close() + self.pager.visible = False + self.pager.focused = False + self.browser.visible = True + + def open_pager(self): + if self.console.focused: + self.console.focused = False + self.pager.open() + self.pager.visible = True + self.pager.focused = True + self.browser.visible = False + return self.pager + + def open_embedded_pager(self): + self.browser.open_pager() + return self.browser.pager + + def close_embedded_pager(self): + self.browser.close_pager() + + def open_console(self, string='', prompt=None, position=None): + if self.console.open(string, prompt=prompt, position=position): + self.status.msg = None + self.console.on_close = self.close_console + self.console.visible = True + self.status.visible = False + + def close_console(self): + self.console.visible = False + self.status.visible = True + self.close_pager() + + def open_taskview(self): + self.pager.close() + self.pager.visible = False + self.pager.focused = False + self.console.visible = False + self.browser.visible = False + self.taskview.visible = True + self.taskview.focused = True + + def redraw_main_column(self): + self.browser.main_column.need_redraw = True + + def close_taskview(self): + self.taskview.visible = False + self.browser.visible = True + self.taskview.focused = False + + def throbber(self, string='.', remove=False): + if remove: + self.titlebar.throbber = type(self.titlebar).throbber + else: + self.titlebar.throbber = string + + def hint(self, text=None): + self.status.hint = text diff --git a/ranger/gui/widgets/__init__.py b/ranger/gui/widgets/__init__.py index 950b670a..82e592ee 100644 --- a/ranger/gui/widgets/__init__.py +++ b/ranger/gui/widgets/__init__.py @@ -1,18 +1,3 @@ -# 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.gui.displayable import Displayable class Widget(Displayable): diff --git a/ranger/gui/widgets/browsercolumn.py b/ranger/gui/widgets/browsercolumn.py index 801b79fd..b6b745aa 100644 --- a/ranger/gui/widgets/browsercolumn.py +++ b/ranger/gui/widgets/browsercolumn.py @@ -1,5 +1,5 @@ # -*- encoding: utf8 -*- -# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> +# Copyright (C) 2009, 2010, 2011 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 @@ -21,6 +21,7 @@ from time import time from . import Widget from .pager import Pager from ranger.fsobject import BAD_INFO +from ranger.ext.widestring import WideString class BrowserColumn(Pager): main_column = False @@ -159,17 +160,12 @@ class BrowserColumn(Pager): Pager.close(self) return - try: - f = self.target.get_preview_source(self.wid, self.hei) - except: - raise # XXX + f = self.target.get_preview_source(self.wid, self.hei) + if f is None: Pager.close(self) else: - if f is None: - Pager.close(self) - else: - self.set_source(f) - Pager.draw(self) + self.set_source(f) + Pager.draw(self) def _draw_directory(self): """Draw the contents of a directory""" @@ -275,8 +271,6 @@ class BrowserColumn(Pager): this_color.append('link') this_color.append(drawn.exists and 'good' or 'bad') - string = drawn.basename - from ranger.ext.widestring import WideString wtext = WideString(text) if len(wtext) > space: wtext = wtext[:space - 1] + ellipsis diff --git a/ranger/gui/widgets/browserview.py b/ranger/gui/widgets/browserview.py index 09944108..d386d389 100644 --- a/ranger/gui/widgets/browserview.py +++ b/ranger/gui/widgets/browserview.py @@ -1,4 +1,4 @@ -# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> +# Copyright (C) 2009, 2010, 2011 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 @@ -16,6 +16,7 @@ """The BrowserView manages a set of BrowserColumns.""" import curses from ranger.ext.signals import Signal +from ranger.ext.keybinding_parser import key_to_string from . import Widget from .browsercolumn import BrowserColumn from .pager import Pager @@ -29,6 +30,7 @@ class BrowserView(Widget, DisplayableContainer): stretch_ratios = None need_clear = False old_collapse = False + draw_hints = False def __init__(self, win, ratios, preview = True): DisplayableContainer.__init__(self, win) @@ -39,7 +41,7 @@ class BrowserView(Widget, DisplayableContainer): self.pager.visible = False self.add_child(self.pager) - self.change_ratios(ratios, resize=False) + self.change_ratios(ratios) for option in ('preview_directories', 'preview_files'): self.settings.signal_bind('setopt.' + option, @@ -48,7 +50,7 @@ class BrowserView(Widget, DisplayableContainer): self.fm.env.signal_bind('move', self.request_clear) self.settings.signal_bind('setopt.column_ratios', self.request_clear) - def change_ratios(self, ratios, resize=True): + def change_ratios(self, ratios): if isinstance(ratios, Signal): ratios = ratios.value @@ -92,16 +94,22 @@ class BrowserView(Widget, DisplayableContainer): self.need_clear = True def draw(self): + if self.need_clear: + self.win.erase() + self.need_redraw = True + self.need_clear = False + for path in self.fm.tabs.values(): + if path is not None: + directory = self.env.get_directory(path) + directory.load_content_if_outdated() + directory.use() + DisplayableContainer.draw(self) + if self.settings.draw_borders: + self._draw_borders() if self.draw_bookmarks: self._draw_bookmarks() - else: - if self.need_clear: - self.win.erase() - self.need_redraw = True - self.need_clear = False - DisplayableContainer.draw(self) - if self.settings.draw_borders: - self._draw_borders() + elif self.draw_hints: + self._draw_hints() def finalize(self): if self.pager.visible: @@ -118,37 +126,6 @@ class BrowserView(Widget, DisplayableContainer): except: pass - def _draw_bookmarks(self): - self.fm.bookmarks.update_if_outdated() - self.color_reset() - self.need_clear = True - - sorted_bookmarks = sorted(item for item in self.fm.bookmarks \ - if self.settings.show_hidden_bookmarks or '/.' not in item[1].path) - - def generator(): - return zip(range(self.hei-1), sorted_bookmarks) - - try: - maxlen = max(len(item[1].path) for i, item in generator()) - except ValueError: - return - maxlen = min(maxlen + 5, self.wid) - - whitespace = " " * maxlen - for line, items in generator(): - key, mark = items - string = " " + key + ": " + mark.path - self.addstr(line, 0, whitespace) - self.addnstr(line, 0, string, self.wid) - - if self.settings.draw_bookmark_borders: - self.win.hline(line+1, 0, curses.ACS_HLINE, maxlen) - - if maxlen < self.wid: - self.win.vline(0, maxlen, curses.ACS_VLINE, line+1) - self.addch(line+1, maxlen, curses.ACS_LRCORNER) - def _draw_borders(self): win = self.win self.color('in_browser', 'border') @@ -196,6 +173,57 @@ class BrowserView(Widget, DisplayableContainer): self.addch(0, right_end, curses.ACS_URCORNER) self.addch(self.hei - 1, right_end, curses.ACS_LRCORNER) + def _draw_bookmarks(self): + self.fm.bookmarks.update_if_outdated() + self.color_reset() + self.need_clear = True + + sorted_bookmarks = sorted((item for item in self.fm.bookmarks \ + if self.fm.settings.show_hidden_bookmarks or \ + '/.' not in item[1].path), key=lambda t: t[0].lower()) + + hei = min(self.hei - 1, len(sorted_bookmarks)) + ystart = self.hei - hei + + maxlen = self.wid + self.addnstr(ystart - 1, 0, "mark path".ljust(self.wid), self.wid) + + whitespace = " " * maxlen + for line, items in zip(range(self.hei-1), sorted_bookmarks): + key, mark = items + string = " " + key + " " + mark.path + self.addstr(ystart + line, 0, whitespace) + self.addnstr(ystart + line, 0, string, self.wid) + + self.win.chgat(ystart - 1, 0, curses.A_UNDERLINE) + + def _draw_hints(self): + self.need_clear = True + hints = [] + for k, v in self.fm.env.keybuffer.pointer.items(): + k = key_to_string(k) + if isinstance(v, dict): + text = '...' + else: + text = v + if text.startswith('hint') or text.startswith('chain hint'): + continue + hints.append((k, text)) + hints.sort(key=lambda t: t[1]) + + hei = min(self.hei - 1, len(hints)) + ystart = self.hei - hei + self.addnstr(ystart - 1, 0, "key command".ljust(self.wid), + self.wid) + self.win.chgat(ystart - 1, 0, curses.A_UNDERLINE) + whitespace = " " * self.wid + i = ystart + for key, cmd in hints: + string = " " + key.ljust(11) + " " + cmd + self.addstr(i, 0, whitespace) + self.addnstr(i, 0, string, self.wid) + i += 1 + def _collapse(self): # Should the last column be cut off? (Because there is no preview) if not self.settings.collapse_preview or not self.preview \ diff --git a/ranger/gui/widgets/console.py b/ranger/gui/widgets/console.py index 00d6828b..406a2fe9 100644 --- a/ranger/gui/widgets/console.py +++ b/ranger/gui/widgets/console.py @@ -1,4 +1,4 @@ -# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> +# Copyright (C) 2009, 2010, 2011 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 @@ -23,11 +23,9 @@ import re from collections import deque from . import Widget -from ranger.container.keymap import CommandArgs from ranger.ext.direction import Direction -from ranger.ext.utfwidth import uwid, uchars, utf_char_width_ -from ranger.container import History -from ranger.container.history import HistoryEmptyException +from ranger.ext.widestring import uwid, WideString +from ranger.container.history import History, HistoryEmptyException import ranger class Console(Widget): @@ -39,6 +37,7 @@ class Console(Widget): tab_deque = None original_line = None history = None + history_backup = None override = None allow_close = False historypath = None @@ -58,6 +57,7 @@ class Console(Widget): for line in f: self.history.add(line[:-1]) f.close() + self.history_backup = History(self.history) def destroy(self): # save history to files @@ -69,31 +69,24 @@ class Console(Widget): except: pass else: - for entry in self.history: + for entry in self.history_backup: f.write(entry + '\n') f.close() def draw(self): self.win.erase() self.addstr(0, 0, self.prompt) - if self.fm.py3: - overflow = -self.wid + len(self.prompt) + len(self.line) + 1 - else: - overflow = -self.wid + len(self.prompt) + uwid(self.line) + 1 + line = WideString(self.line) + overflow = -self.wid + len(self.prompt) + len(line) + 1 if overflow > 0: - #XXX: cut uft-char-wise, consider width - self.addstr(self.line[overflow:]) + self.addstr(str(line[overflow:])) else: self.addstr(self.line) def finalize(self): try: - if self.fm.py3: - xpos = sum(utf_char_width_(ord(c)) for c in self.line[0:self.pos]) \ - + len(self.prompt) - else: - xpos = uwid(self.line[0:self.pos]) + len(self.prompt) - self.fm.ui.win.move(self.y, self.x + min(self.wid-1, xpos)) + pos = uwid(self.line[0:self.pos]) + len(self.prompt) + self.fm.ui.win.move(self.y, self.x + min(self.wid-1, pos)) except: pass @@ -119,7 +112,8 @@ class Console(Widget): self.pos = len(string) if position is not None: self.pos = min(self.pos, position) - self.history.fast_forward() + self.history_backup.fast_forward() + self.history = History(self.history_backup) self.history.add('') return True @@ -151,28 +145,9 @@ class Console(Widget): self.line = '' def press(self, key): - self.env.keymanager.use_context('console') - self.env.key_append(key) - kbuf = self.env.keybuffer - cmd = kbuf.command - - if kbuf.failure: - kbuf.clear() - return - elif not cmd: - return - - self.env.cmd = cmd - - if cmd.function: - try: - cmd.function(CommandArgs.from_widget(self)) - except Exception as error: - self.fm.notify(error) - if kbuf.done: - kbuf.clear() - else: - kbuf.clear() + self.env.keymaps.use_keymap('console') + if not self.fm.ui.press(key): + self.type_key(key) def type_key(self, key): self.tab_deque = None @@ -188,7 +163,9 @@ class Console(Widget): try: decoded = self.unicode_buffer.encode("latin-1").decode("utf-8") except UnicodeDecodeError: - pass + return + except UnicodeEncodeError: + return else: self.unicode_buffer = "" if self.pos == len(self.line): @@ -224,8 +201,9 @@ class Console(Widget): self.pos = len(self.line) def add_to_history(self): - self.history.fast_forward() - self.history.modify(self.line, unique=True) + self.history_backup.fast_forward() + self.history_backup.add(self.line) + self.history = History(self.history_backup) def move(self, **keywords): direction = Direction(keywords) @@ -238,14 +216,18 @@ class Console(Widget): maximum=len(self.line) + 1, current=self.pos) else: - uc = uchars(self.line) - upos = len(uchars(self.line[:self.pos])) + if self.fm.py3: + uc = list(self.line) + upos = len(self.line[:self.pos]) + else: + uc = list(self.line.decode('utf-8', 'ignore')) + upos = len(self.line[:self.pos].decode('utf-8', 'ignore')) newupos = direction.move( direction=direction.right(), minimum=0, maximum=len(uc) + 1, current=upos) - self.pos = len(''.join(uc[:newupos])) + self.pos = len(''.join(uc[:newupos]).encode('utf-8', 'ignore')) def delete_rest(self, direction): self.tab_deque = None @@ -303,24 +285,16 @@ class Console(Widget): self.pos = len(left_part) self.line = left_part + self.line[self.pos + 1:] else: - uc = uchars(self.line) - upos = len(uchars(self.line[:self.pos])) + mod - left_part = ''.join(uc[:upos]) + uc = list(self.line.decode('utf-8', 'ignore')) + upos = len(self.line[:self.pos].decode('utf-8', 'ignore')) + mod + left_part = ''.join(uc[:upos]).encode('utf-8', 'ignore') self.pos = len(left_part) - self.line = left_part + ''.join(uc[upos+1:]) + self.line = left_part + ''.join(uc[upos+1:]).encode('utf-8', 'ignore') self.on_line_change() def execute(self, cmd=None): self.allow_close = True - if cmd is None: - cmd = self._get_cmd() - - if cmd: - try: - cmd.execute() - except Exception as error: - self.fm.notify(error) - + self.fm.execute_console(self.line) if self.allow_close: self.close(trigger_cancel_function=False) @@ -329,7 +303,8 @@ class Console(Widget): command_class = self._get_cmd_class() except KeyError: if not quiet: - self.fm.notify("Invalid command! Press ? for help.", bad=True) + error = "Command not found: `%s'" % self.line.split()[0] + self.fm.notify(error, bad=True) except: return None else: diff --git a/ranger/gui/widgets/pager.py b/ranger/gui/widgets/pager.py index d1bf5918..38451781 100644 --- a/ranger/gui/widgets/pager.py +++ b/ranger/gui/widgets/pager.py @@ -1,4 +1,4 @@ -# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> +# Copyright (C) 2009, 2010, 2011 Roman Zimbelmann <romanz@lavabit.com> # Copyright (C) 2010 David Barnett <davidbarnett2@gmail.com> # # This program is free software: you can redistribute it and/or modify @@ -17,17 +17,11 @@ """ The pager displays text and allows you to scroll inside it. """ -import re from . import Widget from ranger.gui import ansi from ranger.ext.direction import Direction -from ranger.container.keymap import CommandArgs - -BAR_REGEXP = re.compile(r'\|\d+\?\|') -QUOTES_REGEXP = re.compile(r'"[^"]+?"') -SPECIAL_CHARS_REGEXP = re.compile(r'<\w+>|\^[A-Z]') -TITLE_REGEXP = re.compile(r'^\d+\.') +# TODO: Scrolling in embedded pager class Pager(Widget): source = None source_is_stream = False @@ -81,35 +75,6 @@ class Pager(Widget): def _draw_line(self, i, line): if self.markup is None: self.addstr(i, 0, line) - elif self.markup is 'help': - self.addstr(i, 0, line) - - baseclr = ('in_pager', 'help_markup') - - if line.startswith('===='): - self.color_at(i, 0, len(line), 'seperator', *baseclr) - return - - if line.startswith(' ') and \ - len(line) >= 16 and line[15] == ' ': - self.color_at(i, 0, 16, 'key', *baseclr) - - for m in BAR_REGEXP.finditer(line): - start, length = m.start(), m.end() - m.start() - self.color_at(i, start, length, 'bars', *baseclr) - self.color_at(i, start + 1, length - 2, 'link', *baseclr) - - for m in QUOTES_REGEXP.finditer(line): - start, length = m.start(), m.end() - m.start() - self.color_at(i, start, length, 'quotes', *baseclr) - self.color_at(i, start + 1, length - 2, 'text', *baseclr) - - for m in SPECIAL_CHARS_REGEXP.finditer(line): - start, length = m.start(), m.end() - m.start() - self.color_at(i, start, length, 'special', *baseclr) - - if TITLE_REGEXP.match(line): - self.color_at(i, 0, -1, 'title', *baseclr) elif self.markup == 'ansi': try: self.win.move(i, 0) @@ -144,28 +109,8 @@ class Pager(Widget): offset=-self.hei + 1) def press(self, key): - self.env.keymanager.use_context(self.embedded and 'embedded_pager' or 'pager') - self.env.key_append(key) - kbuf = self.env.keybuffer - cmd = kbuf.command - - if kbuf.failure: - kbuf.clear() - return - elif not cmd: - return - - self.env.cmd = cmd - - if cmd.function: - try: - cmd.function(CommandArgs.from_widget(self)) - except Exception as error: - self.fm.notify(error) - if kbuf.done: - kbuf.clear() - else: - kbuf.clear() + self.env.keymaps.use_keymap('pager') + self.fm.ui.press(key) def set_source(self, source, strip=False): if self.source and self.source_is_stream: @@ -230,8 +175,7 @@ class Pager(Widget): try: line = self._get_line(i).expandtabs(4) if self.markup is 'ansi': - line = ansi.char_slice(line, startx, self.wid + startx) \ - + ansi.reset + line = ansi.char_slice(line, startx, self.wid) + ansi.reset else: line = line[startx:self.wid + startx] yield line.rstrip() diff --git a/ranger/gui/widgets/statusbar.py b/ranger/gui/widgets/statusbar.py index b7ab123c..1e2e2520 100644 --- a/ranger/gui/widgets/statusbar.py +++ b/ranger/gui/widgets/statusbar.py @@ -1,4 +1,4 @@ -# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> +# Copyright (C) 2009, 2010, 2011 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 @@ -183,7 +183,7 @@ class StatusBar(Widget): left.add_space() left.add(strftime(self.timeformat, - localtime(stat.st_ctime)), 'mtime') + localtime(stat.st_mtime)), 'mtime') def _get_owner(self, target): uid = target.stat.st_uid @@ -267,7 +267,7 @@ class StatusBar(Widget): self.win.move(0, 0) for part in result: self.color(*part.lst) - self.addstr(part.string) + self.addstr(str(part)) self.color_reset() class Message(object): diff --git a/ranger/gui/widgets/taskview.py b/ranger/gui/widgets/taskview.py index e988b08c..c4476b9c 100644 --- a/ranger/gui/widgets/taskview.py +++ b/ranger/gui/widgets/taskview.py @@ -1,4 +1,4 @@ -# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> +# Copyright (C) 2009, 2010, 2011 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 @@ -17,12 +17,10 @@ The TaskView allows you to modify what the loader is doing. """ -import curses from collections import deque from . import Widget from ranger.ext.accumulator import Accumulator -from ranger.container.keymap import CommandArgs class TaskView(Widget, Accumulator): old_lst = None @@ -96,28 +94,8 @@ class TaskView(Widget, Accumulator): self.fm.loader.move(_from=i, to=to) def press(self, key): - self.env.keymanager.use_context('taskview') - self.env.key_append(key) - kbuf = self.env.keybuffer - cmd = kbuf.command - - if kbuf.failure: - kbuf.clear() - return - elif not cmd: - return - - self.env.cmd = cmd - - if cmd.function: - try: - cmd.function(CommandArgs.from_widget(self)) - except Exception as error: - self.fm.notify(error) - if kbuf.done: - kbuf.clear() - else: - kbuf.clear() + self.env.keymaps.use_keymap('taskview') + self.fm.ui.press(key) def get_list(self): return self.fm.loader.queue diff --git a/ranger/gui/widgets/titlebar.py b/ranger/gui/widgets/titlebar.py index d87a0803..6b92ccfa 100644 --- a/ranger/gui/widgets/titlebar.py +++ b/ranger/gui/widgets/titlebar.py @@ -1,4 +1,4 @@ -# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> +# Copyright (C) 2009, 2010, 2011 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 @@ -77,7 +77,7 @@ class TitleBar(Widget): pos = 0 for i, part in enumerate(self.result): - pos += len(part.string) + pos += len(part) if event.x < pos: if i < 2: self.fm.enter_dir("~") @@ -131,6 +131,7 @@ class TitleBar(Widget): bar.add(self.env.cf.basename, 'file') def _get_right_part(self, bar): + # TODO: fix that pressed keys are cut off when chaining CTRL keys kb = str(self.env.keybuffer) self.old_keybuffer = kb bar.addright(kb, 'keybuffer', fixed=True) @@ -159,5 +160,5 @@ class TitleBar(Widget): self.win.move(0, 0) for part in result: self.color(*part.lst) - self.addstr(part.string) + self.addstr(str(part)) self.color_reset() diff --git a/ranger/help/__init__.py b/ranger/help/__init__.py deleted file mode 100644 index f304c7bc..00000000 --- a/ranger/help/__init__.py +++ /dev/null @@ -1,47 +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/>. - -"""Help files are located here.""" - -from inspect import cleandoc - -NO_TOPIC = """The help topic was not found.""" - -NO_HELP = """No help was found. - -Possibly the program was invoked with "python -OO" which -discards all documentation.""" - -HELP_TOPICS = ('index', 'movement', 'starting', 'console', 'fileop', - 'invocation') - -def get_docstring_of_module(path, module_name): - imported = __import__(path, fromlist=[module_name]) - return getattr(imported, module_name).__doc__ - -def get_help(topic): - try: - doc = get_docstring_of_module('ranger.help', topic) - except (ImportError, AttributeError): - return NO_TOPIC - if isinstance(doc, str): - return cleandoc(doc) - return NO_HELP - -def get_help_by_index(i): - try: - return get_help(HELP_TOPICS[i]) - except IndexError: - return NO_TOPIC diff --git a/ranger/help/console.py b/ranger/help/console.py deleted file mode 100644 index 2f3a75c8..00000000 --- a/ranger/help/console.py +++ /dev/null @@ -1,157 +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/>. - -""" -3. The Console - -3.1. General Information -3.2. List of Commands -3.3. Macros -3.4. The more complicated Commands in Detail - -============================================================================== -3.1. General Information - -The console is opened by pressing ":". Press <TAB> to cycle through all -available commands and press <F1> to view help about the current command. - -All commands are defined in the file ranger/defaults/commands.py, which -also contains a detailed specification. - - -============================================================================== -3.2. List of Commands - -All commands except for ":delete" can be abbreviated with the shortest -unambiguous name, e.g. ":chmod" can be written as ":ch" but not as ":c" since -it conflicts with ":cd". - - -:cd <dirname> - Changes the directory to <dirname> - -:chmod <octal_number> - Sets the permissions of the selection to the octal number. - -:delete - Deletes the current selection. - "Selection" is defined as all the "marked files" (by default, you - can mark files with space or v). If there are no marked files, - use the "current file" (where the cursor is) - -:edit <filename> - Opens the specified file in the text editor. - -:eval <python_code> - Evaluates the given code inside ranger. `fm' is a reference to - the filemanager instance, `p' is a function to print text. - -:filter <string> - Displays only files which contain <string> in their basename. - -:find <regexp> - Quickly find files that match the regexp and execute the first - unambiguous match. - -:grep <string> - Looks for a string in all marked files or directory. - (equivalent to "!grep [some options] -e <string> -r %s | less") - -:mark <regexp> - Mark all files matching a regular expression. - -:unmark <regexp> - Unmark all files matching a regular expression. - -:mkdir <dirname> - Creates a directory with the name <dirname> - -:open_with [<program>] [<flags>] [<mode>] - Open the current file with the program, flags and mode. |24?| |25?| - All arguments are optional. If none is given, its equivalent to - pressing <Enter> - -:quit - Exits ranger - -:rename <newname> - Changes the name of the currently highlighted file to <newname> - -:search <regexp> - Search for a regexp in all file names, like the / key in vim. - -:shell [-<flags>] <command> - Run the command, optionally with some flags. |25?| - Example: shell -d firefox -safe-mode %s - opens (detached from ranger) the selection in firefox' safe-mode - -:terminal - Spawns "x-terminal-emulator" starting in the current directory. - -:touch <filename> - Creates a file with the name <filename> - - -============================================================================== -3.3. Macros - -Like in similar filemanagers there are some macros. Use them in -commands and they will be replaced with a list of files. - %f the highlighted file - %d the path of the current directory - %s the selected files in the current directory. If no files are - selected, it defaults to the same as %f - %t all tagged files in the current directory - %c the full paths of the currently copied/cut files - -The macros %f, %d and %s also have upper case variants, %F, %D and %S, -which refer to the next tab. To refer to specific tabs, add a number in -between. Examples: - %D The path of the directory in the next tab - %7s The selection of the seventh tab - -%c is the only macro which ranges out of the current directory. So you may -"abuse" the copying function for other purposes, like diffing two files which -are in different directories: - - Yank the file A (type yy), move to the file B and use: - :shell -p diff %c %f - - -============================================================================== -3.4. The more complicated Commands in Detail - -3.4.1. "find" -The find command is different than others: it doesn't require you to -press <RETURN>. To speed things up, it tries to guess when you're -done typing and executes the command right away. -The key "f" opens the console with ":find " - -3.4.2. "shell" -The shell command accepts flags |25?| as the first argument. This example -will use the "p"-flag, which pipes the output to the pager: - :shell -p cat somefile.txt - -There are some shortcuts which open the console with the shell command: - "!" opens ":shell " - "@" opens ":shell %s" - "#" opens ":shell -p " - -3.4.3. "open_with" -The open_with command is explained in detail in chapter 2.2. |22?| - -============================================================================== -""" -# vim:tw=78:sw=8:sts=8:ts=8:ft=help diff --git a/ranger/help/fileop.py b/ranger/help/fileop.py deleted file mode 100644 index ac23c6d4..00000000 --- a/ranger/help/fileop.py +++ /dev/null @@ -1,107 +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/>. - -""" -4. File Operations - -4.1. Destructive Operations -4.2. The Selection -4.3. Copying and Pasting -4.4. Task View - - -============================================================================== -4.1. Destructive Operations - -These are all the operations which can change, and with misuse, possibly -harm your files: - -:chmod <number> Change the rights of the selection -:delete DELETES ALL FILES IN THE SELECTION -:rename <newname> Change the name of the current file -pp, pl, pL, po Pastes the copied files in different ways - -Think twice before using these commands or key combinations. - - -============================================================================== -4.2. The Selection - -Many commands operate on the selection, so it's important to know what -it is: - -If there are marked files: - The selection contains all the marked files. -Otherwise: - The selection contains only the highlighted file. - -"Marked files" are the files which are slightly indented and marked in -yellow (in the default color scheme.) You can mark files by typing "v" or -<space>. - -The "highlighted file", or the "current file", is the one below the cursor. - - -============================================================================== -4.3. Copying and Pasting - - yy copy the selection - dd cut the selection - - ya, da add the selection to the copied/cut files - yr, dr remove the selection from the copied/cut files - - pp paste the copied/cut files. No file will be overwritten. - Instead, a "_" character will be appended to the new filename. - po paste the copied/cut files. Existing files are overwritten. - pl create symbolic links to the copied/cut files. - pL create relative symbolic links to the copied/cut files. - -The difference between copying and cutting should be intuitive: - -When pasting files which are copied, the original file remains unchanged -in any case. - -When pasting files which are cut, the original file will be renamed. -If renaming is not possible because the source and the destination are -on separate devices, it will be copied and eventually the source is deleted. -This implies that a file can only be cut + pasted once. - -The files are either copied or cut, never mixed even if you mix "da" and "ya" -keys (in which case the last command is decisive about whether they are copied -or cut.) - -============================================================================== -4.4. Task View - -The task view lets you manage IO tasks like copying, moving and -loading directories by changing their priority or stop them. - - w open or close the task view - dd stop the task - J decrease the priority of the task - K increase the priority of the task - -The execution of tasks is not parallel but sequential. Only the -topmost task is executed. Ranger constantly switches between -handling GUI and executing tasks. One movement of the throbber at -the top right represents such a switch, so while the throbber is -standing still, ranger is locked by a Input/Output operation and -you will not be able to input any commands. - - -============================================================================== -""" -# vim:tw=78:sw=4:sts=8:ts=8:ft=help diff --git a/ranger/help/index.py b/ranger/help/index.py deleted file mode 100644 index a10a8406..00000000 --- a/ranger/help/index.py +++ /dev/null @@ -1,80 +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/>. - -""" - ranger %s - main help file - k - Move around: Use the cursor keys, or "h" to go left, h l - "j" to go down, "k" to go up, "l" to go right. j - Close Ranger: Type "Q" - Specific help: Type "?", prepended with a number: - - |0?| This index - |1?| Basic movement and browsing - |2?| Running Files - |3?| The console - |4?| File operations - |5?| Ranger invocation - - -============================================================================== -0.1. About ranger - -Ranger is a free console file manager that gives you greater flexibility -and a good overview of your files without having to leave your *nix console. -It visualizes the directory tree in two dimensions: the directory hierarchy -on one, lists of files on the other, with a preview to the right so you know -where you'll be going. - -The default keys are similar to those of Vim, Emacs and Midnight Commander, -though Ranger is easily controllable with just the arrow keys or the mouse. - -The program is written in Python (2.6 or 3.1) and uses curses for the -text-based user interface. - - -============================================================================== -0.2. About these help pages - -Annotations like |1?| indicate that the topic is explained in more -detail in chapter 1. You can type 1? to view it. -You can type 16? to open chapter 1, paragraph 6. - - -============================================================================== -0.3. Copying - -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/>. - - -============================================================================== -""" - -import ranger -__doc__ %= ranger.__version__ -# vim:tw=78:sw=4:sts=8:ts=8:ft=help diff --git a/ranger/help/invocation.py b/ranger/help/invocation.py deleted file mode 100644 index afb1cd27..00000000 --- a/ranger/help/invocation.py +++ /dev/null @@ -1,125 +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/>. - -""" -5. Ranger invocation - -5.1. Command Line Arguments -5.2. Python Options - - -============================================================================== -5.1. Command Line Arguments - -These options can be passed to ranger when starting it from the -command line. - ---version - Print the version and exit. - --h, --help - Print a list of options and exit. - --d, --debug - Activate the debug mode: Whenever an error occurs, ranger - will exit and print a full backtrace. The default behaviour - is to merely print the name of the exception in the statusbar/log - and to try to keep running. - --c, --clean - Activate the clean mode: Ranger will not access or create any - configuration files nor will it leave any traces on your system. - This is useful when your configuration is broken, when you want - to avoid clutter, etc. - ---copy-config - Create copies of the default configuration files in your local - configuration directory. Existing ones will not be overwritten. - Possible values: all, apps, commands, keys, options, scope. - ---fail-unless-cd - Return the exit code 1 if ranger is used to run a file, for example - with `ranger --fail-unless-cd filename`. This can be useful for scripts. - (This option used to be called --fail-if-run) - --r <dir>, --confdir=<dir> - Define a different configuration directory. The default is - $HOME/.ranger. - --m <n>, --mode=<n> - When a filename is supplied, make it run in mode <n> |2| - --f <flags>, --flags=<flags> - When a filename is supplied, run it with the flags <flags> |2| - ---choosefile=<target> - Makes ranger act like a file chooser. When opneing a file, it will - quit and write the name of the selected file to the filename specified - as <target>. This file can be read in a script and used to open a - certain file which has been chosen with ranger. - ---choosedir=<target> - Makes ranger act like a directory chooser. When ranger quits, it will - write the name of the last visited directory to <target> - -(Optional) Positional Argument - The positional argument should be a path to the directory you - want ranger to start in, or the file which you want to run. - Only one positional argument is accepted as of now. - --- - Stop looking for options. All following arguments are treated as - positional arguments. - -Examples: - ranger episode1.avi - ranger --debug /usr/bin - ranger --confdir=~/.config/ranger --fail-unless-cd - -See the README on how to integrate ranger with various external programs. - - -============================================================================== -5.2. Python Options - -Ranger makes use of python optimize flags. To use them, run ranger like this: - PYTHONOPTIMIZE=1 ranger -An alternative is: - python -O `which ranger` -Or you could change the first line of the ranger script and add -O/-OO. -The first way is the recommended one. Of course you can make an alias or -a shell fuction to save typing. - -Using PYTHONOPTIMIZE=1 (-O) will make python discard assertion statements. -Assertions are little pieces of code which are helpful for finding errors, -but unless you're touching sensitive parts of ranger, you may want to -disable them to save some computing power. - -Using PYTHONOPTIMIZE=2 (-OO) will additionally discard any docstrings. -In ranger, most built-in documentation (F1/? keys) is implemented with -docstrings. Use this option if you don't need the documentation. - -Examples: - PYTHONOPTIMIZE=1 ranger episode1.avi - PYTHONOPTIMIZE=2 ranger --debug /usr/bin - python -OO `which ranger` --confdir=~/.config/ranger --fail-unless-cd - -Note: The author expected "-OO" to reduce the memory usage, but that -doesn't seem to happen. - - -============================================================================== -""" -# vim:tw=78:sw=8:sts=8:ts=8:ft=help diff --git a/ranger/help/movement.py b/ranger/help/movement.py deleted file mode 100644 index e7c3a87c..00000000 --- a/ranger/help/movement.py +++ /dev/null @@ -1,232 +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/>. - -""" -1. Basic movement and browsing - -1.1. Move around -1.2. Browser control -1.3. Searching -1.4. Sorting -1.5. Bookmarks -1.6. Tabs -1.7. Mouse usage -1.8. Misc keys -1.9. Previews - - -============================================================================== -1.1. Ranger has similar movement keys as vim: - -Note: A ^ stands for the Ctrl key. - - k move up - j move down - h move left (in browser: move one directory up) - l move right (in browser: enter this directory, or run this file) - - ^U move half the screen up - ^D move half the screen down - H in browser: move back in history - L in browser: move forward in history - - gg move to the top - G move to the bottom - % move to the middle - -By prefixing a number, you can give more precise commands, eg: - - 2^D move 2 pages down - 5gg move to the 5th line - 3h move 3 characters to the left, or move 3 directories up - 30% move to 30% of the screen - -Using arrow keys is equivalent of using h/j/k/l in most cases. -An exception to this is the console, where you can move around with -arrow keys and pressing letters will insert the letter into the console. - -Special keys like Home, Page Up,.. work as expected. - -These keys work like in vim: - - ^U move half the screen up - ^D move half the screen down - ^B move up by one screen - ^F move down by one screen - -This keys can be used to make movements beyond the current directory - - ] move down in the parent directory - [ move up in the parent directory - - } traverse the directory tree, visiting each directory - { traverse in the other direction. (not implemented yet, - currently this only moves back in history) - - gl move to the real path of the current directory (resolving symlinks) - gL move to the real path of the selected file or directory - - -============================================================================== -1.2. Browser control - - ? view the help screen - R reload the current directory - ^R clear the cache and reload the view - ^L redraw the window - : open the console |3?| - z toggle options - u undo certain things (unyank, unmark,...) - - i inspect the content of the file - E edit the file - S open a shell, starting in the current directory - -Marking files allows you to use operations on multiple files at once. -If there are any marked files in this directory, "yy" will copy them instead -of the file you're pointing at. - - <Space> mark a file - v toggle all marks - V, uv remove all marks - ^V mark files in a specific direction - e.g. ^Vgg marks all files from the current to the top - u^V unmark files in a specific direction - -By "tagging" files, you can highlight them and mark them to be -special in whatever context you want. Tags are persistent across sessions. - - t tag/untag the selection - T untag the selection - -Midnight Commander lovers will find that the function keys work similarly. -There is no menu or drop down though. - - <F1> view the help screen - <F3> view the file - <F4> edit the file - <F5> copy the selection - <F6> cut the selection - <F7> create a directory - <F8> delete the selection - <F10> exit ranger - - -============================================================================== -1.3. Searching - -Use "/" to open the search console. |3?| -Enter a string and press <Enter> to search for it in all currently -visible files. Pressing "n" will move you to the next occurance, -"N" to the previous one. - -You can search for more than just strings: - cc cycle through all files by their ctime (last inode change) - cm cycle by mime type, connecting similar files - cs cycle by size, large items first - ct search tagged files - - -============================================================================== -1.4. Sorting - -To sort files, type "o" suffixed with a key that stands for a certain -sorting mode. By typing any of those keys in upper case, the order will -be reversed. - - os sort by size - ob, on sort by basename - om sort by mtime (last modification) - ot sort by mime type - or reverse order - - -============================================================================== -1.5. Bookmarks - -Type "m<key>" to bookmark the current directory. You can re-enter this -directory by typing "`<key>". <key> can be any letter or digit. Unlike vim, -both lowercase and uppercase bookmarks are persistent. - -Each time you jump to a bookmark, the special bookmark at key ` will be set -to the last directory. So typing "``" gets you back to where you were before. - -Note: The ' key is equivalent to `. - - -============================================================================== -1.6. Tabs - -Tabs are used to work in different directories in the same Ranger instance. -In Ranger, tabs are very simple though and only store the directory path. - - gt Go to the next tab. (also TAB) - gT Go to the previous tab. (also Shift+TAB) - gn, ^N Create a new tab - g<N> Open a tab. N has to be a number from 1 to 9. - If the tab doesn't exist yet, it will be created. - On most terminals, Alt-1, Alt-2, etc., also work. - gc, ^W Close the current tab. The last tab cannot be closed. - - -============================================================================== -1.7. Mouse usage - -The mouse can be used to quickly enter directories which you point at, -or to scroll around with the mouse wheel. The implementation of the mouse -wheel is not stable due to problems with the ncurses library, but "it works -on my machine". - -Clicking into the preview window will usually run the file. |2?| - - -============================================================================== -1.8. Misc keys - - 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 " - A Open the console with ":rename <current filename>" - I Same as A, put the cursor at the beginning of the filename - yp Copy the path of the file (with xsel) - yn Copy the base name of the file (with xsel) - yd Copy the directory name of the file (with xsel) - - -============================================================================== -1.9. Previews - -By default, only text files are previewed, but you can enable external -preview scripts by creating ~/.config/ranger/scope.sh (see preview_script -option.) This script will then be executed each time you attempt to -preview a file. - -Fetch the default scope.sh (from ranger/data/scope.sh) by running - ranger --copy-config=scope - -This default script contains more documentation and calls to the -programs "lynx" and "elinks" for html, "highlight" for text/code, -"img2txt" for images, "atool" for archives, "pdftotext" for PDFs and -"mediainfo" for video and audio files. - -Install these programs (just the ones you need) and scope.sh will -automatically use them. Make sure to also have the options -"use_preview_script" and "preview_files" turned on. - - -============================================================================== -""" -# vim:tw=78:sw=4:sts=8:ts=8:ft=help diff --git a/ranger/help/starting.py b/ranger/help/starting.py deleted file mode 100644 index dbc6b6b5..00000000 --- a/ranger/help/starting.py +++ /dev/null @@ -1,121 +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/>. - -""" -2. Running Files - -2.1. How to run files -2.2. The "open_with" command -2.2. Programs -2.4. Modes -2.5. Flags - - -============================================================================== -2.1. How to run files - -While highlighting a file, press the "l" key to fire up the automatic -filetype detection mechanism and attempt to start the file. - - l run the selection - r open the console with ":open_with" - -Note: The selection means, if there are marked files in this directory, -use them. Otherwise use the file under the cursor. - - -============================================================================== -2.2. The "open_with" command - -If the automatic filetype detection fails or starts the file in a wrong -way, you can press "r" to manually tell ranger how to run it. - -The programs and modes can be defined in the apps.py, giving you a -high level interface for running files. - -Syntax: :open_with <program> <flags> <mode> -You can leave out parameters or change the order. - -Examples: -Open this file with vim: - :open_with vim -Run this file like with "./file": - :open_with self -Open this file as usual but pipe the output to "less" - :open_with p -Open this file with mplayer with the "detached" flag: - :open_with mplayer d -Open this file with totem in mode 1, will not detach the process (flag D) -but discard the output (flag s). - :open_with totem 1 Ds - -The parameters <program>, <flags> and <mode> are explained in the -following paragraphs - - -============================================================================== -2.3. Programs - -Programs have to be defined in ranger/defaults/apps.py. Each function -in the class CustomApplications which starts with "app_" can be used -as a program in the "open_with" command. - -You're encouraged to add your own program definitions to the list. Refer to -the existing examples in the apps.py, it should be easy to adapt it for your -purposes. - - -============================================================================== -2.4. Modes - -Sometimes there are multiple variants to open a file. For example, ranger -gives you 2 ways of opening a video (by default): - - 0 windowed - 1 fullscreen - -By specifying a mode, you can select one of those. The "l" key will -start a file in mode 0. "4l" will start the file in mode 4 etc. -You can specify a mode in the "open_with" command by simply adding -the number. Eg: ":open_with mplayer 1" or ":open_with 1" - -For a list of all programs and modes, see ranger/defaults/apps.py - - -============================================================================== -2.5. Flags - -Flags give you a way to modify the behaviour of the spawned process. - - s Silent mode. Output will be discarded. - d Detach the process. (Run in background) - p Redirect output to the pager - w Wait for an enter-press when the process is done - c Run the current file only, even when more files are marked - -For example, ":open_with p" will pipe the output of that process into -the pager. - -An uppercase flag has the opposite effect. If a program will be detached by -default, use ":open_with D" to not detach it. - -Note: Some combinations don't make sense, eg: "vim d" would open the file in -vim and detach it. Since vim is a console application, you loose grip -of that process when you detach it. It's up to you to do such sanity checks. - - -============================================================================== -""" -# vim:tw=78:sw=4:sts=8:ts=8:ft=help diff --git a/scripts/ranger b/scripts/ranger deleted file mode 120000 index 21b7d3ee..00000000 --- a/scripts/ranger +++ /dev/null @@ -1 +0,0 @@ -../ranger.py \ No newline at end of file diff --git a/setup.py b/setup.py index e63e28d2..ec782794 100755 --- a/setup.py +++ b/setup.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> +# Copyright (C) 2009, 2010, 2011 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 @@ -27,9 +27,9 @@ if __name__ == '__main__': author_email=ranger.__email__, license=ranger.__license__, url='http://savannah.nongnu.org/projects/ranger', - scripts=['scripts/ranger'], + scripts=['ranger/data/ranger'], data_files=[('share/man/man1', ['doc/ranger.1'])], - package_data={'ranger': ['data/*']}, + package_data={'ranger': ['data/*', 'defaults/rc.conf']}, packages=('ranger', 'ranger.api', 'ranger.colorschemes', @@ -39,5 +39,4 @@ if __name__ == '__main__': 'ranger.ext', 'ranger.fsobject', 'ranger.gui', - 'ranger.gui.widgets', - 'ranger.help')) + 'ranger.gui.widgets')) diff --git a/test/all_benchmarks.py b/test/all_benchmarks.py deleted file mode 100755 index a3612701..00000000 --- a/test/all_benchmarks.py +++ /dev/null @@ -1,56 +0,0 @@ -#!/usr/bin/env python -# 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/>. - -""" -Run all the benchmarks inside this directory. -Usage: ./all_benchmarks.py [count] [regexp-filters...] -""" - -import os.path -import sys -rangerpath = os.path.join(os.path.dirname(__file__), '..') -if sys.path[1] != rangerpath: - sys.path[1:1] = [rangerpath] - -import re -import time - -if __name__ == '__main__': - count = int(sys.argv[1]) if len(sys.argv) > 1 else 10 - regexes = [re.compile(fltr) for fltr in sys.argv[2:]] - modules = (fname[:-3] for fname in os.listdir(sys.path[0]) \ - if fname[:3] == 'bm_' and fname[-3:] == '.py') - - def run_benchmark(cls, methodname): - full_method_name = "{0}.{1}".format(cls.__name__, methodname) - if all(re.search(full_method_name) for re in regexes): - method = getattr(cls(), methodname) - t1 = time.time() - try: - method(count) - except: - print("{0} failed!".format(full_method_name)) - raise - else: - t2 = time.time() - print("{0:60}: {1:10}s".format(full_method_name, t2 - t1)) - - for val in [__import__(module) for module in modules]: - for cls in vars(val).values(): - if type(cls) == type: - for methodname in vars(cls): - if methodname.startswith('bm_'): - run_benchmark(cls, methodname) diff --git a/test/all_tests.py b/test/all_tests.py deleted file mode 100755 index 0c184df5..00000000 --- a/test/all_tests.py +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/env python -# 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/>. - -""" -Run all the tests inside this directory as a test suite. -Usage: ./all_tests.py [verbosity] -""" - -import os.path -import sys -rangerpath = os.path.join(os.path.dirname(__file__), '..') -if sys.path[1] != rangerpath: - sys.path[1:1] = [rangerpath] - -import unittest - -if __name__ == '__main__': - verbosity = int(sys.argv[1]) if len(sys.argv) > 1 else 1 - tests = (fname[:-3] for fname in os.listdir(sys.path[0]) \ - if fname[:3] == 'tc_' and fname[-3:] == '.py') - suite = unittest.TestLoader().loadTestsFromNames(tests) - result = unittest.TextTestRunner(verbosity=verbosity).run(suite) - if len(result.errors + result.failures) > 0: - sys.exit(1) diff --git a/test/bm_human_readable.py b/test/bm_human_readable.py deleted file mode 100644 index ef400774..00000000 --- a/test/bm_human_readable.py +++ /dev/null @@ -1,51 +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/>. - -import os.path -import sys -rangerpath = os.path.join(os.path.dirname(__file__), '..') -if sys.path[1] != rangerpath: - sys.path[1:1] = [rangerpath] - -from ranger.ext.human_readable import * - -# The version before 2010/06/24: -import math -UNITS = 'BKMGTP' -MAX_EXPONENT = len(UNITS) - 1 -def human_readable_old(byte, seperator=' '): - if not byte: - return '0' - - exponent = int(math.log(byte, 2) / 10) - flt = round(float(byte) / (1 << (10 * exponent)), 2) - - if exponent > MAX_EXPONENT: - return '>9000' # off scale - - if int(flt) == flt: - return '%.0f%s%s' % (flt, seperator, UNITS[exponent]) - - else: - return '%.2f%s%s' % (flt, seperator, UNITS[exponent]) - -class benchmark_human_readable(object): - def bm_current(self, n): - for i in range(n): - human_readable((128 * i) % 2**50) - - def bm_old(self, n): - for i in range(n): - human_readable_old((128 * i) % 2**50) diff --git a/test/bm_loader.py b/test/bm_loader.py deleted file mode 100644 index 552954a7..00000000 --- a/test/bm_loader.py +++ /dev/null @@ -1,174 +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/>. - -import os.path -import sys -rangerpath = os.path.join(os.path.dirname(__file__), '..') -if sys.path[1] != rangerpath: - sys.path[1:1] = [rangerpath] - -from ranger.core.loader import Loader -from ranger.fsobject import Directory, File -from ranger.ext.openstruct import OpenStruct -import os.path -from ranger.shared import FileManagerAware, SettingsAware -from testlib import Fake -from os.path import realpath, join, dirname -from subprocess import Popen, PIPE -TESTDIR = realpath(join(dirname(__file__), '/usr/include')) - -def skip(x): - return - -def raw_load_content(self): - """ - The method which is used in a Directory object to load stuff. - Keep this up to date! - """ - - from os.path import join, isdir, basename - from os import listdir - import ranger.ext.mount_path - - self.loading = True - self.load_if_outdated() - - try: - if self.exists and self.runnable: - # 0.003s: - self.mount_path = ranger.ext.mount_path.mount_path(self.path) - - # 0.1s: - filenames = [] - for fname in listdir(self.path): - if not self.settings.show_hidden: - hfilter = self.settings.hidden_filter - if hfilter: - if isinstance(hfilter, str) and hfilter in fname: - continue - if hasattr(hfilter, 'search') and \ - hfilter.search(fname): - continue - if isinstance(self.filter, str) and self.filter \ - and self.filter not in fname: - continue - filenames.append(join(self.path, fname)) - # --- - - self.load_content_mtime = os.stat(self.path).st_mtime - - marked_paths = [obj.path for obj in self.marked_items] - - # 2.85s: - files = [] - for name in filenames: - if isdir(name): - try: - item = self.fm.env.get_directory(name) - except: - item = Directory(name) - else: - item = File(name) - item.load_if_outdated() - files.append(item) - - # 0.2s - self.disk_usage = sum(f.size for f in files if f.is_file) - - self.scroll_offset = 0 - self.filenames = filenames - self.files = files - - self._clear_marked_items() - for item in self.files: - if item.path in marked_paths: - self.mark_item(item, True) - else: - self.mark_item(item, False) - - self.sort() - - if len(self.files) > 0: - if self.pointed_obj is not None: - self.sync_index() - else: - self.move(to=0) - else: - self.filenames = None - self.files = None - - self.cycle_list = None - self.content_loaded = True - self.determine_infostring() - self.correct_pointer() - - finally: - self.loading = False - - -class benchmark_load(object): - def __init__(self): - self.loader = Loader() - fm = OpenStruct(loader=self.loader) - SettingsAware.settings = Fake() - FileManagerAware.fm = fm - self.dir = Directory(TESTDIR) - - def bm_run(self, n): - for _ in range(n): - self.dir.load_content(schedule=True) - while self.loader.has_work(): - self.loader.work() - - -@skip -class benchmark_raw_load(object): - def __init__(self): - SettingsAware.settings = Fake() - self.dir = Directory(TESTDIR) - - def bm_run(self, n): - generator = self.dir.load_bit_by_bit() - for _ in range(n): - raw_load_content(self.dir) - -def bm_loader(n): - """Do some random calculation""" - tloader = benchmark_load(N) - traw = benchmark_raw_load(N) - -class benchmark_load_varieties(object): - def bm_ls(self, n): - for _ in range(n): - Popen(["ls", '-l', TESTDIR], stdout=open(os.devnull, 'w')).wait() - - def bm_os_listdir_stat(self, n): - for _ in range(n): - for f in os.listdir(TESTDIR): - path = os.path.join(TESTDIR, f) - os.stat(path) - - def bm_os_listdir(self, n): - for _ in range(n): - for f in os.listdir(TESTDIR): - path = os.path.join(TESTDIR, f) - - def bm_os_listdir_stat_listdir(self, n): - for _ in range(n): - for f in os.listdir(TESTDIR): - path = os.path.join(TESTDIR, f) - os.stat(path) - if os.path.isdir(path): - os.listdir(path) diff --git a/test/tc_ansi.py b/test/tc_ansi.py deleted file mode 100644 index 0a6ad8b1..00000000 --- a/test/tc_ansi.py +++ /dev/null @@ -1,40 +0,0 @@ -# Copyright (C) 2010 David Barnett <davidbarnett2@gmail.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/>. - -if __name__ == '__main__': from __init__ import init; init() - -import unittest -from ranger.gui import ansi - -class TestDisplayable(unittest.TestCase): - def test_char_len(self): - ansi_string = "[0;30;40mX[0m" - self.assertEqual(ansi.char_len(ansi_string), 1) - - def test_char_len2(self): - ansi_string = "[0;30;40mXY[0m" - self.assertEqual(ansi.char_len(ansi_string), 2) - - def test_char_len3(self): - ansi_string = "[0;30;40mX[0;31;41mY" - self.assertEqual(ansi.char_len(ansi_string), 2) - - def test_char_slice(self): - ansi_string = "[0;30;40mX[0;31;41mY[0m" - expected = "[0;30;40mX" - self.assertEqual(ansi.char_slice(ansi_string, 0, 1), expected) - -if __name__ == '__main__': - unittest.main() diff --git a/test/tc_bookmarks.py b/test/tc_bookmarks.py deleted file mode 100644 index 59435f06..00000000 --- a/test/tc_bookmarks.py +++ /dev/null @@ -1,92 +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/>. - -import os.path -import sys -rangerpath = os.path.join(os.path.dirname(__file__), '..') -if sys.path[1] != rangerpath: - sys.path[1:1] = [rangerpath] - -from os.path import realpath, join, dirname -import unittest -import os -import time - -from ranger.container.bookmarks import Bookmarks - -TESTDIR = realpath(join(dirname(__file__), 'testdir')) -BMFILE = join(TESTDIR, 'bookmarks') - -class TestDisplayable(unittest.TestCase): - def setUp(self): - try: - os.remove(BMFILE) - except: - pass - - def tearDown(self): - try: - os.remove(BMFILE) - except: - pass - - def test_adding_bookmarks(self): - bm = Bookmarks(BMFILE, str, autosave=False) - bm.load() - bm['a'] = 'fooo' - self.assertEqual(bm['a'], 'fooo') - - def test_sharing_bookmarks_between_instances(self): - bm = Bookmarks(BMFILE, str, autosave=True) - bm2 = Bookmarks(BMFILE, str, autosave=True) - - bm.load() - bm2.load() - bm['a'] = 'fooo' - self.assertRaises(KeyError, bm2.__getitem__, 'a') - - bm.save() - bm2.load() - self.assertEqual(bm['a'], bm2['a']) - - bm2['a'] = 'bar' - - bm.save() - bm2.save() - bm.load() - bm2.load() - - self.assertEqual(bm['a'], bm2['a']) - - def test_messing_around(self): - bm = Bookmarks(BMFILE, str, autosave=False) - bm2 = Bookmarks(BMFILE, str, autosave=False) - - bm.load() - bm['a'] = 'car' - - bm2.load() - self.assertRaises(KeyError, bm2.__getitem__, 'a') - - bm2.save() - bm.update() - bm.save() - bm.load() - bm2.load() - - self.assertEqual(bm['a'], bm2['a']) - -if __name__ == '__main__': - unittest.main() diff --git a/test/tc_colorscheme.py b/test/tc_colorscheme.py deleted file mode 100644 index eefb1e4f..00000000 --- a/test/tc_colorscheme.py +++ /dev/null @@ -1,53 +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/>. - -import os.path -import sys -rangerpath = os.path.join(os.path.dirname(__file__), '..') -if sys.path[1] != rangerpath: - sys.path[1:1] = [rangerpath] - -from unittest import TestCase, main -import random -import ranger.colorschemes -from ranger.gui.colorscheme import ColorScheme -from ranger.gui.context import CONTEXT_KEYS - -class Test(TestCase): - def setUp(self): - import random - import curses - curses.COLORS = 88 - schemes = [] - for key, mod in vars(ranger.colorschemes).items(): - if type(mod) == type(random): - for key, var in vars(mod).items(): - if type(var) == type and issubclass(var, ColorScheme) \ - and var != ColorScheme: - schemes.append(var) - self.schemes = set(schemes) - - def test_colorschemes(self): - def test(scheme): - scheme.get() # test with no arguments - - for i in range(300): # test with a bunch of random (valid) arguments - sample = random.sample(CONTEXT_KEYS, random.randint(2, 9)) - scheme.get(*sample) - - for scheme in self.schemes: - test(scheme()) - -if __name__ == '__main__': main() diff --git a/test/tc_direction.py b/test/tc_direction.py deleted file mode 100644 index 16c26dab..00000000 --- a/test/tc_direction.py +++ /dev/null @@ -1,92 +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/>. - -import os.path -import sys -rangerpath = os.path.join(os.path.dirname(__file__), '..') -if sys.path[1] != rangerpath: - sys.path[1:1] = [rangerpath] - -import unittest -from ranger.ext.direction import Direction -from ranger.ext.openstruct import OpenStruct - -class TestDirections(unittest.TestCase): - def test_symmetry(self): - d1 = Direction(right=4, down=7, relative=True) - d2 = Direction(left=-4, up=-7, absolute=False) - - def subtest(d): - self.assertEqual(4, d.right()) - self.assertEqual(7, d.down()) - self.assertEqual(-4, d.left()) - self.assertEqual(-7, d.up()) - self.assertEqual(True, d.relative()) - self.assertEqual(False, d.absolute()) - - self.assertTrue(d.horizontal()) - self.assertTrue(d.vertical()) - - subtest(d1) - subtest(d2) - - def test_conflicts(self): - d3 = Direction(right=5, left=2, up=3, down=6, - absolute=True, relative=True) - self.assertEqual(d3.right(), -d3.left()) - self.assertEqual(d3.left(), -d3.right()) - self.assertEqual(d3.up(), -d3.down()) - self.assertEqual(d3.down(), -d3.up()) - self.assertEqual(d3.absolute(), not d3.relative()) - self.assertEqual(d3.relative(), not d3.absolute()) - - def test_copy(self): - d = Direction(right=5) - c = d.copy() - self.assertEqual(c.right(), d.right()) - d['right'] += 3 - self.assertNotEqual(c.right(), d.right()) - c['right'] += 3 - self.assertEqual(c.right(), d.right()) - - self.assertFalse(d.vertical()) - self.assertTrue(d.horizontal()) - -# Doesn't work in python2? -# def test_duck_typing(self): -# dct = dict(right=7, down=-3) -# self.assertEqual(-7, Direction.left(dct)) -# self.assertEqual(3, Direction.up(dct)) - - def test_move(self): - d = Direction(pages=True) - self.assertEqual(3, d.move(direction=3)) - self.assertEqual(5, d.move(direction=3, current=2)) - self.assertEqual(15, d.move(direction=3, pagesize=5)) - self.assertEqual(9, d.move(direction=3, pagesize=5, maximum=10)) - self.assertEqual(18, d.move(direction=9, override=2)) - d2 = Direction(absolute=True) - self.assertEqual(5, d2.move(direction=9, override=5)) - - def test_select(self): - d = Direction(down=3) - lst = list(range(100)) - self.assertEqual((6, [3,4,5,6]), d.select(current=3, pagesize=10, override=None, lst=lst)) - d = Direction(down=3, pages=True) - self.assertEqual((9, [3,4,5,6,7,8,9]), d.select(current=3, pagesize=2, override=None, lst=lst)) - -if __name__ == '__main__': - unittest.main() - diff --git a/test/tc_directory.py b/test/tc_directory.py deleted file mode 100644 index a43ac89d..00000000 --- a/test/tc_directory.py +++ /dev/null @@ -1,124 +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/>. - -import os.path -import sys -rangerpath = os.path.join(os.path.dirname(__file__), '..') -if sys.path[1] != rangerpath: - sys.path[1:1] = [rangerpath] - -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.core.shared import SettingsAware - -SettingsAware._setup() - -TESTDIR = realpath(join(dirname(__file__), 'testdir')) -TESTFILE = join(TESTDIR, 'testfile5234148') -NONEXISTANT_DIR = join(TESTDIR, 'nonexistant') - -import unittest -class Test1(unittest.TestCase): - def test_initial_condition(self): - # Check for the expected initial condition - dir = Directory(TESTDIR) - - self.assertEqual(dir.path, TESTDIR) - self.assertFalse(dir.content_loaded) - self.assertEqual(dir.filenames, None) - self.assertEqual(dir.files, None) - if not sys.flags.optimize: # asserts are ignored with python -O - self.assertRaises(AssertionError, len, dir) - - def test_after_content_loaded(self): - import os - # Check whether the directory has the correct list of filenames. - dir = Directory(TESTDIR) - dir.load_content(schedule=False) - - self.assertTrue(dir.exists) - self.assertEqual(type(dir.filenames), list) - - # Get the filenames you expect it to have and sort both before - # comparing. I don't expect any order after only loading the filenames. - assumed_filenames = os.listdir(TESTDIR) - assumed_filenames = list(map(lambda str: os.path.join(TESTDIR, str), - assumed_filenames)) - assumed_filenames.sort() - dir.filenames.sort() - - self.assertTrue(len(dir) > 0) - self.assertEqual(dir.filenames, assumed_filenames) - - # build a file object for each file in the list assumed_filenames - # and find exactly one equivalent in dir.files - for name in assumed_filenames: - f = File(name) - f.load() - for dirfile in dir.files: - if (f.path == dirfile.path and f.stat == dirfile.stat): - break - else: - self.fail("couldn't find file {0}".format(name)) - - def test_nonexistant_dir(self): - dir = Directory(NONEXISTANT_DIR) - dir.load_content(schedule=False) - - self.assertTrue(dir.content_loaded) - self.assertFalse(dir.exists) - self.assertFalse(dir.accessible) - self.assertEqual(dir.filenames, None) - if not sys.flags.optimize: # asserts are ignored with python -O - self.assertRaises(AssertionError, len, dir) - - def test_load_if_outdated(self): - import os - import time - # modify the directory. If the time between the last modification - # was within the filesystems resolution of mtime, we should have a reload - - def modify_dir(): - open(TESTFILE, 'w').close() - os.unlink(TESTFILE) - - def mtime(): - return os.stat(TESTDIR).st_mtime - - dir = Directory(TESTDIR) - dir.load() - - # If the modification happens to be in the same second as the - # last modification, it will result in mtime having the same - # integer value. So we wait until the resolution is exceeded - # and mtime differs. - old_mtime = mtime() - for i in range(50): - modify_dir() - if old_mtime != mtime(): break - time.sleep(0.1) - else: - # fail after 5 seconds of trying - self.fail( - "Cannot perform test: mtime of TESTDIR is not being updated.") - - self.assertTrue(dir.load_if_outdated()) - -if __name__ == '__main__': - unittest.main() - diff --git a/test/tc_displayable.py b/test/tc_displayable.py deleted file mode 100644 index 72e0507d..00000000 --- a/test/tc_displayable.py +++ /dev/null @@ -1,167 +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/>. - -import os.path -import sys -rangerpath = os.path.join(os.path.dirname(__file__), '..') -if sys.path[1] != rangerpath: - sys.path[1:1] = [rangerpath] - -import unittest -import curses -from random import randint - -from ranger.gui.displayable import Displayable, DisplayableContainer -from testlib import Fake, OK, raise_ok, TODO - -class TestWithFakeCurses(unittest.TestCase): - def setUp(self): - self.win = Fake() - self.fm = Fake() - self.env = Fake() - self.settings = Fake() - self.initdict = {'win': self.win, 'settings': self.settings, - 'fm': self.fm, 'env': self.env} - - self.disp = Displayable(**self.initdict) - self.disc = DisplayableContainer(**self.initdict) - self.disc.add_child(self.disp) - - hei, wid = 100, 100 - self.env.termsize = (hei, wid) - - def tearDown(self): - self.disp.destroy() - self.disc.destroy() - - def test_colorscheme(self): - # Using a color method implies change of window attributes - disp = self.disp - - disp.win.chgat = raise_ok - disp.win.attrset = raise_ok - - self.assertRaises(OK, disp.color, 'a', 'b') - self.assertRaises(OK, disp.color_at, 0, 0, 0, 'a', 'b') - self.assertRaises(OK, disp.color_reset) - - def test_focused_object(self): - d1 = Displayable(**self.initdict) - d2 = DisplayableContainer(**self.initdict) - for obj in (Displayable(**self.initdict) for x in range(5)): - d2.add_child(obj) - d3 = DisplayableContainer(**self.initdict) - for obj in (Displayable(**self.initdict) for x in range(5)): - d3.add_child(obj) - - for obj in (d1, d2, d3): - self.disc.add_child(obj) - - d3.container[3].focused = True - - self.assertEqual(self.disc._get_focused_obj(), d3.container[3]) - - d3.container[3].focused = False - d2.container[0].focused = True - - self.assertEqual(self.disc._get_focused_obj(), d2.container[0]) - -gWin = None - -class TestDisplayableWithCurses(unittest.TestCase): - def setUp(self): - global gWin - if not gWin: - gWin = curses.initscr() - self.win = gWin - curses.cbreak() - curses.noecho() - curses.start_color() - curses.use_default_colors() - - self.fm = Fake() - self.env = Fake() - self.settings = Fake() - self.initdict = {'win': self.win, 'settings': self.settings, - 'fm': self.fm, 'env': self.env} - self.disp = Displayable(**self.initdict) - self.disc = DisplayableContainer(**self.initdict) - self.disc.add_child(self.disp) - - self.env.termsize = self.win.getmaxyx() - - def tearDown(self): - self.disp.destroy() - curses.nocbreak() - curses.echo() - curses.endwin() - - @TODO - def test_boundaries(self): - disp = self.disp - hei, wid = self.env.termsize - - self.assertRaises(ValueError, disp.resize, 0, 0, hei + 1, wid) - self.assertRaises(ValueError, disp.resize, 0, 0, hei, wid + 1) - self.assertRaises(ValueError, disp.resize, -1, 0, hei, wid) - self.assertRaises(ValueError, disp.resize, 0, -1, hei, wid) - - for i in range(1000): - box = [int(randint(0, hei) * 0.2), int(randint(0, wid) * 0.2)] - box.append(randint(0, hei - box[0])) - box.append(randint(0, wid - box[1])) - - def in_box(y, x): - return (y >= box[1] and y < box[1] + box[3]) and \ - (x >= box[0] and x < box[0] + box[2]) - - disp.resize(*box) - self.assertEqual(box, [disp.y, disp.x, disp.hei, disp.wid], - "Resizing failed for some reason on loop " + str(i)) - - for y, x in zip(range(10), range(10)): - is_in_box = in_box(y, x) - - point1 = (y, x) - self.assertEqual(is_in_box, point1 in disp) - - point2 = Fake() - point2.x = x - point2.y = y - self.assertEqual(is_in_box, point2 in disp) - - def test_click(self): - self.disp.click = raise_ok - - hei, wid = self.env.termsize - - for i in range(50): - winwid = randint(2, wid-1) - winhei = randint(2, hei-1) - self.disc.resize(0, 0, hei, wid) - self.disp.resize(0, 0, winhei, winwid) - fakepos = Fake() - - fakepos.x = winwid - 2 - fakepos.y = winhei - 2 - self.assertRaises(OK, self.disc.click, fakepos) - - fakepos.x = winwid - fakepos.y = winhei - self.disc.click(fakepos) - - -if __name__ == '__main__': - unittest.main() diff --git a/test/tc_ext.py b/test/tc_ext.py deleted file mode 100644 index 495591a1..00000000 --- a/test/tc_ext.py +++ /dev/null @@ -1,150 +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/>. - -import os.path -import sys -rangerpath = os.path.join(os.path.dirname(__file__), '..') -if sys.path[1] != rangerpath: - sys.path[1:1] = [rangerpath] - -import unittest -from collections import deque - -from ranger.ext.iter_tools import * - -class TestCases(unittest.TestCase): - def test_flatten(self): - def f(x): - return list(flatten(x)) - - self.assertEqual( - [1,2,3,4,5], - f([1,2,3,4,5])) - self.assertEqual( - [1,2,3,4,5], - f([1,[2,3],4,5])) - self.assertEqual( - [1,2,3,4,5], - f([[1,[2,3]],4,5])) - self.assertEqual( - [], - f([[[[]]]])) - self.assertEqual( - ['a', 'b', 'fskldfjl'], - f(['a', ('b', 'fskldfjl')])) - self.assertEqual( - ['a', 'b', 'fskldfjl'], - f(['a', deque(['b', 'fskldfjl'])])) - self.assertEqual( - set([3.5, 4.3, 5.2, 6.0]), - set(f([6.0, set((3.5, 4.3)), (5.2, )]))) - - def test_unique(self): - def u(x): - return list(unique(x)) - - self.assertEqual( - [1,2,3], - u([1,2,3])) - self.assertEqual( - [1,2,3], - u([1,2,3,2,1])) - self.assertEqual( - [1,2,3], - u([1,2,3,1,2,3,2,2,3,1,2,3,1,2,3,2,3,2,1])) - self.assertEqual( - [1,[2,3]], - u([1,[2,3],1,[2,3],[2,3],1,[2,3],1,[2,3],[2,3],1])) - - def test_unique_keeps_type(self): - def u(x): - return unique(x) - - self.assertEqual( - [1,2,3], - u([1,2,3,1])) - self.assertEqual( - (1,2,3), - u((1,2,3,1))) - self.assertEqual( - set((1,2,3)), - u(set((1,2,3,1)))) - self.assertEqual( - deque((1,2,3)), - u(deque((1,2,3,1)))) - - def test_mount_path(self): - # assuming ismount() is used - - def my_ismount(path): - depth = path.count('/') - if path.startswith('/media'): - return depth == 0 or depth == 2 - return depth <= 1 - - from ranger.ext import mount_path - original_ismount = mount_path.ismount - mount_path.ismount = my_ismount - try: - mp = mount_path.mount_path - - self.assertEqual('/home', mp('/home/hut/porn/bondage')) - self.assertEqual('/', mp('/')) - self.assertEqual('/media/sdb1', mp('/media/sdb1/foo/bar')) - self.assertEqual('/media/sdc2', mp('/media/sdc2/a/b/c/d/e')) - finally: - mount_path.ismount = original_ismount - - # TODO: links are not tested but I don't see how its possible - # without messing around with mounts. - # self.assertEqual('/media/foo', - # mount_path('/media/bar/some_link_to_a_foo_subdirectory')) - - def test_openstruct(self): - from ranger.ext.openstruct import OpenStruct - from random import randint, choice - from string import ascii_letters - - os = OpenStruct(a='a') - self.assertEqual(os.a, 'a') - self.assertRaises(AttributeError, getattr, os, 'b') - - dictionary = {'foo': 'bar', 'zoo': 'zar'} - os = OpenStruct(dictionary) - self.assertEqual(os.foo, 'bar') - self.assertEqual(os.zoo, 'zar') - self.assertRaises(AttributeError, getattr, os, 'sdklfj') - - for i in range(100): - attr_name = ''.join(choice(ascii_letters) \ - for x in range(randint(3,9))) - value = randint(100,999) - if not attr_name in os: - self.assertRaises(AttributeError, getattr, os, attr_name) - setattr(os, attr_name, value) - value2 = randint(100,999) - setattr(os, attr_name, value2) - self.assertEqual(value2, getattr(os, attr_name)) - - def test_shell_escape(self): - from ranger.ext.shell_escape import shell_escape, shell_quote - self.assertEqual(r"'luigi'\''s pizza'", shell_quote("luigi's pizza")) - self.assertEqual(r"luigi\'s\ pizza", shell_escape("luigi's pizza")) - self.assertEqual(r"\$lol/foo\\xyz\|\>\<\]\[", - shell_escape(r"$lol/foo\xyz|><][")) - - -if __name__ == '__main__': - unittest.main() diff --git a/test/tc_history.py b/test/tc_history.py deleted file mode 100644 index 02a8bb9f..00000000 --- a/test/tc_history.py +++ /dev/null @@ -1,82 +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/>. - -import os.path -import sys -rangerpath = os.path.join(os.path.dirname(__file__), '..') -if sys.path[1] != rangerpath: - sys.path[1:1] = [rangerpath] - -from ranger.container import History -from unittest import TestCase, main -import unittest - -class Test(TestCase): - def test_history(self): - hist = History(3) - for i in range(6): - hist.add(i) - self.assertEqual([3,4,5], list(hist)) - - hist.back() - - self.assertEqual(4, hist.current()) - self.assertEqual([3,4], list(hist._left())) - - self.assertEqual(5, hist.top()) - - hist.back() - self.assertEqual(3, hist.current()) - self.assertEqual([3], list(hist._left())) - - # no change if current == bottom - self.assertEqual(hist.current(), hist.bottom()) - last = hist.current() - hist.back() - self.assertEqual(hist.current(), last) - - self.assertEqual(5, hist.top()) - - hist.forward() - hist.forward() - self.assertEqual(5, hist.current()) - self.assertEqual([3,4,5], list(hist._left())) - - - self.assertEqual(3, hist.bottom()) - hist.add(6) - self.assertEqual(4, hist.bottom()) - self.assertEqual([4,5,6], list(hist._left())) - - hist.back() - hist.fast_forward() - self.assertEqual([4,5,6], list(hist._left())) - hist.back() - hist.back() - hist.fast_forward() - self.assertEqual([4,5,6], list(hist._left())) - hist.back() - hist.back() - hist.back() - hist.fast_forward() - self.assertEqual([4,5,6], list(hist._left())) - hist.back() - hist.back() - hist.back() - hist.back() - hist.fast_forward() - self.assertEqual([4,5,6], list(hist._left())) - -if __name__ == '__main__': main() diff --git a/test/tc_human_readable.py b/test/tc_human_readable.py deleted file mode 100644 index 493e6d3a..00000000 --- a/test/tc_human_readable.py +++ /dev/null @@ -1,51 +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/>. - -import os.path -import sys -rangerpath = os.path.join(os.path.dirname(__file__), '..') -if sys.path[1] != rangerpath: - sys.path[1:1] = [rangerpath] - -import unittest -from ranger.ext.human_readable import human_readable as hr - -class HumanReadableTest(unittest.TestCase): - def test_basic(self): - self.assertEqual("0", hr(0)) - self.assertEqual("1 B", hr(1)) - self.assertEqual("1 K", hr(2 ** 10)) - self.assertEqual("1 M", hr(2 ** 20)) - self.assertEqual("1 G", hr(2 ** 30)) - self.assertEqual(">9000", hr(2 ** 100)) - - def test_big(self): - self.assertEqual("1023 G", hr(2 ** 30 * 1023)) - self.assertEqual("1024 G", hr(2 ** 40 - 1)) - self.assertEqual("1 T", hr(2 ** 40)) - - def test_small(self): - self.assertEqual("1000 B", hr(1000)) - self.assertEqual("1.66 M", hr(1.66 * 2 ** 20)) - self.assertEqual("1.46 K", hr(1500)) - self.assertEqual("1.5 K", hr(2 ** 10 + 2 ** 9)) - self.assertEqual("1.5 K", hr(2 ** 10 + 2 ** 9 - 1)) - - def test_no_exponent(self): - for i in range(2 ** 10, 2 ** 20, 512): - self.assertTrue('e' not in hr(i), "%d => %s" % (i, hr(i))) - -if __name__ == '__main__': - unittest.main() diff --git a/test/tc_keyapi.py b/test/tc_keyapi.py deleted file mode 100644 index 79d89fa5..00000000 --- a/test/tc_keyapi.py +++ /dev/null @@ -1,45 +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/>. - -import os.path -import sys -rangerpath = os.path.join(os.path.dirname(__file__), '..') -if sys.path[1] != rangerpath: - sys.path[1:1] = [rangerpath] - -from unittest import TestCase, main - -class Test(TestCase): - def test_wrapper(self): - from ranger.api.keys import Wrapper - - class dummyfm(object): - def move(self, relative): - return "I move down by {0}".format(relative) - - class commandarg(object): - def __init__(self): - self.fm = dummyfm() - self.n = None - self.direction = None - - arg = commandarg() - - do = Wrapper('fm') - command = do.move(relative=4) - - self.assertEqual(command(arg), 'I move down by 4') - -if __name__ == '__main__': main() diff --git a/test/tc_loader.py b/test/tc_loader.py deleted file mode 100644 index a679a629..00000000 --- a/test/tc_loader.py +++ /dev/null @@ -1,79 +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/>. - -import os.path -import sys -rangerpath = os.path.join(os.path.dirname(__file__), '..') -if sys.path[1] != rangerpath: - sys.path[1:1] = [rangerpath] - -import unittest -import os -from os.path import realpath, join, dirname - -from testlib import Fake -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 - -TESTDIR = realpath(join(dirname(__file__), 'testdir')) -#TESTDIR = "/usr/sbin" - -class Test1(unittest.TestCase): - def test_loader(self): - loader = Loader() - fm = OpenStruct(loader=loader) - SettingsAware.settings = Fake() - FileManagerAware.fm = fm - - # initially, the loader has nothing to do - self.assertFalse(loader.has_work()) - - dir = Directory(TESTDIR) - self.assertEqual(None, dir.files) - self.assertFalse(loader.has_work()) - - # Calling load_content() will enqueue the loading operation. - # dir is not loaded yet, but the loader has work - dir.load_content(schedule=True) - self.assertEqual(None, dir.files) - self.assertTrue(loader.has_work()) - - iterations = 0 - while loader.has_work(): - iterations += 1 - loader.work() - #print(iterations) - self.assertNotEqual(None, dir.files) - self.assertFalse(loader.has_work()) -# -# def test_get_overhead_of_loader(self): -# N = 5 -# tloader = benchmark_load(N) -# traw = benchmark_raw_load(N) -# #traw1k = 250.0 -# #traw = traw1k * N / 1000.0 -# #print("Loader: {0}s".format(tloader)) -# #print("Raw: {0}s".format(traw)) -# self.assertTrue(tloader > traw) -# overhead = tloader * 100 / traw - 100 -# self.assertTrue(overhead < 2, "overhead of loader too high: {0}" \ -# .format(overhead)) -# #print("Overhead: {0:.5}%".format(overhead)) - - -if __name__ == '__main__': - unittest.main() diff --git a/test/tc_newkeys.py b/test/tc_newkeys.py deleted file mode 100644 index c9597201..00000000 --- a/test/tc_newkeys.py +++ /dev/null @@ -1,620 +0,0 @@ -# coding=utf-8 -# 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 - -import os.path -import sys -rangerpath = os.path.join(os.path.dirname(__file__), '..') -if sys.path[1] != rangerpath: - sys.path[1:1] = [rangerpath] -sys.path[1:1] = ['..'] - -from unittest import TestCase, main - -from testlib import TODO -from ranger.ext.tree import Tree -from ranger.container.keymap import * -from ranger.container.keybuffer import KeyBuffer -from ranger.ext.keybinding_parser import parse_keybinding - -def simulate_press(self, string): - for char in parse_keybinding(string): - self.add(char) - if self.done: - return self.command - if self.failure: - break - return self.command - -class PressTestCase(TestCase): - """Some useful methods for the actual test""" - def _mkpress(self, keybuffer, _=0): - def press(keys): - keybuffer.clear() - match = simulate_press(keybuffer, keys) - self.assertFalse(keybuffer.failure, - "parsing keys '"+keys+"' did fail!") - self.assertTrue(keybuffer.done, - "parsing keys '"+keys+"' did not complete!") - arg = CommandArgs(None, None, keybuffer) - self.assert_(match.function, "No function found! " + \ - str(match.__dict__)) - return match.function(arg) - return press - - def assertPressFails(self, kb, keys): - kb.clear() - simulate_press(kb, keys) - self.assertTrue(kb.failure, "Keypress did not fail as expected") - kb.clear() - - def assertPressIncomplete(self, kb, keys): - kb.clear() - simulate_press(kb, keys) - self.assertFalse(kb.failure, "Keypress failed, expected incomplete") - self.assertFalse(kb.done, "Keypress done which was unexpected") - kb.clear() - -class Test(PressTestCase): - """The test cases""" - def test_passive_action(self): - km = KeyMap() - directions = KeyMap() - kb = KeyBuffer(km, directions) - def n(value): - """return n or value""" - def fnc(arg=None): - if arg is None or arg.n is None: - return value - return arg.n - return fnc - - km.map('ppp', n(5)) - km.map('pp<bg>', n(8)) - km.map('pp<dir>', n(2)) - directions.map('j', dir=Direction(down=1)) - - press = self._mkpress(kb, km) - self.assertEqual(5, press('ppp')) - self.assertEqual(3, press('3ppp')) - - self.assertEqual(2, press('ppj')) - - kb.clear() - match = simulate_press(kb, 'pp') - args = CommandArgs(0, 0, kb) - self.assert_(match) - self.assert_(match.function) - self.assertEqual(8, match.function(args)) - - def test_translate_keys(self): - def test(string, *args): - if not args: - args = (string, ) - self.assertEqual(ordtuple(*args), tuple(parse_keybinding(string))) - - def ordtuple(*args): - lst = [] - for arg in args: - if isinstance(arg, str): - lst.extend(ord(c) for c in arg) - else: - lst.append(arg) - return tuple(lst) - - # 1 argument means: assume nothing is translated. - test('k') - test('kj') - test('k<dir>', 'k', DIRKEY) - test('k<ANY>z<any>', 'k', ANYKEY, 'z', ANYKEY) - test('k<anY>z<dir>', 'k', ANYKEY, 'z', DIRKEY) - test('<cr>', "\n") - test('<tab><tab><cr>', "\t\t\n") - test('<') - test('>') - test('<C-a>', 1) - test('<C-b>', 2) - for i in range(1, 26): - test('<C-' + chr(i+ord('a')-1) + '>', i) - test('<A-x>', 27, ord('x')) - test('<a-o>', 27, ord('o')) - test('k<a') - test('k<anz>') - test('k<a<nz>') - test('k<a<nz>') - test('k<a<>nz>') - test('>nz>') - - def test_alias(self): - def add_dirs(arg): - return sum(dir.down() for dir in arg.directions) - def return5(_): - return 5 - - directions = KeyMap() - directions.map('j', dir=Direction(down=1)) - directions.map('k', dir=Direction(down=-1)) - directions.map('<CR>', alias='j') - directions.map('@', alias='<CR>') - - base = KeyMap() - base.map('a<dir>', add_dirs) - base.map('b<dir>', add_dirs) - base.map('x<dir>x<dir>', add_dirs) - base.map('f', return5) - base.map('yy', alias='y') - base.map('!', alias='!') - - other = KeyMap() - other.map('b<dir>b<dir>', alias='x<dir>x<dir>') - other.map('c<dir>', add_dirs) - other.map('g', alias='f') - - km = base.merge(other, copy=True) - kb = KeyBuffer(km, directions) - - press = self._mkpress(kb, km) - - self.assertEqual(1, press('aj')) - self.assertEqual(2, press('bjbj')) - self.assertEqual(1, press('cj')) - self.assertEqual(1, press('c<CR>')) - - self.assertEqual(5, press('f')) - self.assertEqual(5, press('g')) - self.assertEqual(press('c<CR>'), press('c@')) - self.assertEqual(press('c<CR>'), press('c@')) - self.assertEqual(press('c<CR>'), press('c@')) - - for n in range(1, 10): - self.assertPressIncomplete(kb, 'y' * n) - - for n in range(1, 5): - self.assertPressFails(kb, '!' * n) - - def test_tree(self): - t = Tree() - t.set('abcd', "Yes") - self.assertEqual("Yes", t.traverse('abcd')) - self.assertRaises(KeyError, t.traverse, 'abcde') - self.assertRaises(KeyError, t.traverse, 'xyz') - self.assert_(isinstance(t.traverse('abc'), Tree)) - - t2 = Tree() - self.assertRaises(KeyError, t2.set, 'axy', "Lol", force=False) - t2.set('axx', 'ololol') - t2.set('axyy', "Lol") - self.assertEqual("Yes", t.traverse('abcd')) - self.assertRaises(KeyError, t2.traverse, 'abcd') - self.assertEqual("Lol", t2.traverse('axyy')) - self.assertEqual("ololol", t2.traverse('axx')) - - t2.unset('axyy') - self.assertEqual("ololol", t2.traverse('axx')) - self.assertRaises(KeyError, t2.traverse, 'axyy') - self.assertRaises(KeyError, t2.traverse, 'axy') - - t2.unset('a') - self.assertRaises(KeyError, t2.traverse, 'abcd') - self.assertRaises(KeyError, t2.traverse, 'a') - self.assert_(t2.empty()) - - def test_merge_trees(self): - def makeTreeA(): - t = Tree() - t.set('aaaX', 1) - t.set('aaaY', 2) - t.set('aaaZ', 3) - t.set('bbbA', 11) - t.set('bbbB', 12) - t.set('bbbC', 13) - t.set('bbbD', 14) - t.set('bP', 21) - t.set('bQ', 22) - return t - - def makeTreeB(): - u = Tree() - u.set('aaaX', 0) - u.set('bbbC', 'Yes') - u.set('bbbD', None) - u.set('bbbE', 15) - u.set('bbbF', 16) - u.set('bQ', 22) - u.set('bR', 23) - u.set('ffff', 1337) - return u - - # test 1 - t = Tree('a') - u = Tree('b') - merged = t.merge(u, copy=True) - self.assertEqual('b', merged._tree) - - # test 2 - t = Tree('a') - u = makeTreeA() - merged = t.merge(u, copy=True) - self.assertEqual(u._tree, merged._tree) - - # test 3 - t = makeTreeA() - u = makeTreeB() - v = t.merge(u, copy=True) - - self.assertEqual(0, v['aaaX']) - self.assertEqual(2, v['aaaY']) - self.assertEqual(3, v['aaaZ']) - self.assertEqual(11, v['bbbA']) - self.assertEqual('Yes', v['bbbC']) - self.assertEqual(None, v['bbbD']) - self.assertEqual(15, v['bbbE']) - self.assertEqual(16, v['bbbF']) - self.assertRaises(KeyError, t.__getitem__, 'bbbG') - self.assertEqual(21, v['bP']) - self.assertEqual(22, v['bQ']) - self.assertEqual(23, v['bR']) - self.assertEqual(1337, v['ffff']) - - # merge shouldn't be destructive - self.assertEqual(makeTreeA()._tree, t._tree) - self.assertEqual(makeTreeB()._tree, u._tree) - - v['fff'].replace('Lolz') - self.assertEqual('Lolz', v['fff']) - - v['aaa'].replace('Very bad') - v.plow('qqqqqqq').replace('eww.') - - self.assertEqual(makeTreeA()._tree, t._tree) - self.assertEqual(makeTreeB()._tree, u._tree) - - def test_add(self): - c = KeyMap() - c.map('aa', 'b', lambda *_: 'lolz') - self.assert_(c['aa'].function(), 'lolz') - @c.map('a', 'c') - def test(): - return 5 - self.assert_(c['b'].function(), 'lolz') - self.assert_(c['c'].function(), 5) - self.assert_(c['a'].function(), 5) - - def test_quantifier(self): - km = KeyMap() - directions = KeyMap() - kb = KeyBuffer(km, directions) - def n(value): - """return n or value""" - def fnc(arg=None): - if arg is None or arg.n is None: - return value - return arg.n - return fnc - km.map('p', n(5)) - press = self._mkpress(kb, km) - self.assertEqual(5, press('p')) - self.assertEqual(3, press('3p')) - self.assertEqual(6223, press('6223p')) - - def test_direction(self): - km = KeyMap() - directions = KeyMap() - kb = KeyBuffer(km, directions) - directions.map('j', dir=Direction(down=1)) - directions.map('k', dir=Direction(down=-1)) - def nd(arg): - """ n * direction """ - n = arg.n is None and 1 or arg.n - dir = arg.direction is None and Direction(down=1) \ - or arg.direction - return n * dir.down() - km.map('d<dir>', nd) - km.map('dd', func=nd) - - press = self._mkpress(kb, km) - - self.assertPressIncomplete(kb, 'd') - self.assertEqual( 1, press('dj')) - self.assertEqual( 3, press('3ddj')) - self.assertEqual( 15, press('3d5j')) - self.assertEqual(-15, press('3d5k')) - # supporting this kind of key combination would be too confusing: - # self.assertEqual( 15, press('3d5d')) - self.assertEqual( 3, press('3dd')) - self.assertEqual( 33, press('33dd')) - self.assertEqual( 1, press('dd')) - - km.map('x<dir>', nd) - km.map('xxxx', func=nd) - - self.assertEqual(1, press('xxxxj')) - self.assertEqual(1, press('xxxxjsomeinvalitchars')) - - # these combinations should break: - self.assertPressFails(kb, 'xxxj') - self.assertPressFails(kb, 'xxj') - self.assertPressFails(kb, 'xxkldfjalksdjklsfsldkj') - self.assertPressFails(kb, 'xyj') - self.assertPressIncomplete(kb, 'x') # direction missing - - def test_any_key(self): - km = KeyMap() - directions = KeyMap() - kb = KeyBuffer(km, directions) - directions.map('j', dir=Direction(down=1)) - directions.map('k', dir=Direction(down=-1)) - - directions.map('g<any>', dir=Direction(down=-1)) - - def cat(arg): - n = arg.n is None and 1 or arg.n - return ''.join(chr(c) for c in arg.matches) * n - - km.map('return<any>', cat) - km.map('cat4<any><any><any><any>', cat) - km.map('foo<dir><any>', cat) - - press = self._mkpress(kb, km) - - self.assertEqual('x', press('returnx')) - self.assertEqual('abcd', press('cat4abcd')) - self.assertEqual('abcdabcd', press('2cat4abcd')) - self.assertEqual('55555', press('5return5')) - - self.assertEqual('x', press('foojx')) - self.assertPressFails(kb, 'fooggx') # ANYKEY forbidden in DIRECTION - - km.map('<any>', lambda _: Ellipsis) - self.assertEqual('x', press('returnx')) - self.assertEqual('abcd', press('cat4abcd')) - self.assertEqual(Ellipsis, press('2cat4abcd')) - self.assertEqual(Ellipsis, press('5return5')) - self.assertEqual(Ellipsis, press('g')) - self.assertEqual(Ellipsis, press('ß')) - self.assertEqual(Ellipsis, press('ア')) - self.assertEqual(Ellipsis, press('9')) - - def test_multiple_directions(self): - km = KeyMap() - directions = KeyMap() - kb = KeyBuffer(km, directions) - directions.map('j', dir=Direction(down=1)) - directions.map('k', dir=Direction(down=-1)) - - def add_dirs(arg): - return sum(dir.down() for dir in arg.directions) - - km.map('x<dir>y<dir>', add_dirs) - km.map('four<dir><dir><dir><dir>', add_dirs) - - press = self._mkpress(kb, km) - - self.assertEqual(2, press('xjyj')) - self.assertEqual(0, press('fourjkkj')) - self.assertEqual(2, press('four2j4k2j2j')) - self.assertEqual(10, press('four1j2j3j4j')) - self.assertEqual(10, press('four1j2j3j4jafslkdfjkldj')) - - def test_corruptions(self): - km = KeyMap() - directions = KeyMap() - kb = KeyBuffer(km, directions) - press = self._mkpress(kb, km) - directions.map('j', dir=Direction(down=1)) - directions.map('k', dir=Direction(down=-1)) - km.map('xxx', lambda _: 1) - - self.assertEqual(1, press('xxx')) - - # corrupt the tree - tup = tuple(parse_keybinding('xxx')) - x = ord('x') - km._tree[x][x][x] = "Boo" - - self.assertPressFails(kb, 'xxy') - self.assertPressFails(kb, 'xzy') - self.assertPressIncomplete(kb, 'xx') - self.assertPressIncomplete(kb, 'x') - if not sys.flags.optimize: # asserts are ignored with python -O - self.assertRaises(AssertionError, simulate_press, kb, 'xxx') - kb.clear() - - def test_directions_as_functions(self): - km = KeyMap() - directions = KeyMap() - kb = KeyBuffer(km, directions) - press = self._mkpress(kb, km) - - def move(arg): - return arg.direction.down() - - directions.map('j', dir=Direction(down=1)) - directions.map('s', alias='j') - directions.map('k', dir=Direction(down=-1)) - km.map('<dir>', func=move) - - self.assertEqual(1, press('j')) - self.assertEqual(1, press('j')) - self.assertEqual(1, press('j')) - self.assertEqual(1, press('j')) - self.assertEqual(1, press('j')) - self.assertEqual(1, press('s')) - self.assertEqual(1, press('s')) - self.assertEqual(1, press('s')) - self.assertEqual(1, press('s')) - self.assertEqual(1, press('s')) - self.assertEqual(-1, press('k')) - self.assertEqual(-1, press('k')) - self.assertEqual(-1, press('k')) - - km.map('k', func=lambda _: 'love') - - self.assertEqual(1, press('j')) - self.assertEqual('love', press('k')) - - self.assertEqual(1, press('40j')) - self.assertEqual(40, kb.quant) - - km.map('<dir><dir><any><any>', func=move) - - self.assertEqual(1, press('40jkhl')) - self.assertEqual(40, kb.quant) - - def test_tree_deep_copy(self): - t = Tree() - s = t.plow('abcd') - s.replace('X') - u = t.copy() - self.assertEqual(t._tree, u._tree) - s = t.traverse('abc') - s.replace('Y') - self.assertNotEqual(t._tree, u._tree) - - def test_keymanager(self): - def func(arg): - return 5 - def getdown(arg): - return arg.direction.down() - - buffer = KeyBuffer(None, None) - press = self._mkpress(buffer) - keymanager = KeyManager(buffer, ['foo', 'bar']) - - map = keymanager.get_context('foo') - map('a', func) - map('b', func) - map = keymanager.get_context('bar') - map('c', func) - map('<dir>', getdown) - - keymanager.dir('foo', 'j', down=1) - keymanager.dir('bar', 'j', down=1) - - keymanager.use_context('foo') - self.assertEqual(5, press('a')) - self.assertEqual(5, press('b')) - self.assertPressFails(buffer, 'c') - - keymanager.use_context('bar') - self.assertPressFails(buffer, 'a') - self.assertPressFails(buffer, 'b') - self.assertEqual(5, press('c')) - self.assertEqual(1, press('j')) - keymanager.use_context('foo') - keymanager.use_context('foo') - keymanager.use_context('foo') - keymanager.use_context('bar') - keymanager.use_context('foo') - keymanager.use_context('bar') - keymanager.use_context('bar') - self.assertEqual(1, press('j')) - - def test_alias_to_direction(self): - def func(arg): - return arg.direction.down() - - km = KeyMapWithDirections() - kb = KeyBuffer(km, km.directions) - press = self._mkpress(kb) - - km.map('<dir>', func) - km.map('d<dir>', func) - km.dir('j', down=42) - km.dir('k', alias='j') - self.assertEqual(42, press('j')) - - km.dir('o', alias='j') - km.dir('ick', alias='j') - self.assertEqual(42, press('o')) - self.assertEqual(42, press('dj')) - self.assertEqual(42, press('dk')) - self.assertEqual(42, press('do')) - self.assertEqual(42, press('dick')) - self.assertPressFails(kb, 'dioo') - - def test_both_directory_and_any_key(self): - def func(arg): - return arg.direction.down() - def func2(arg): - return "yay" - - km = KeyMap() - directions = KeyMap() - kb = KeyBuffer(km, directions) - press = self._mkpress(kb) - - km.map('abc<dir>', func) - directions.map('j', dir=Direction(down=42)) - self.assertEqual(42, press('abcj')) - - km.unmap('abc<dir>') - - km.map('abc<any>', func2) - self.assertEqual("yay", press('abcd')) - - km.map('abc<dir>', func) - - km.map('abc<any>', func2) - self.assertEqual("yay", press('abcd')) - - def test_map_collision(self): - def add_dirs(arg): - return sum(dir.down() for dir in arg.directions) - def return5(_): - return 5 - - - directions = KeyMap() - directions.map('gg', dir=Direction(down=1)) - - - km = KeyMap() - km.map('gh', return5) - km.map('agh', return5) - km.map('a<dir>', add_dirs) - - kb = KeyBuffer(km, directions) - press = self._mkpress(kb, km) - - self.assertEqual(5, press('gh')) - self.assertEqual(5, press('agh')) -# self.assertPressFails(kb, 'agh') - - @TODO - def test_map_collision2(self): - directions = KeyMap() - directions.map('gg', dir=Direction(down=1)) - km = KeyMap() - km.map('agh', lambda _: 1) - km.map('a<dir>', lambda _: 2) - kb = KeyBuffer(km, directions) - press = self._mkpress(kb, km) - self.assertEqual(1, press('agh')) - self.assertEqual(2, press('agg')) - - def test_keymap_with_dir(self): - def func(arg): - return arg.direction.down() - - km = KeyMapWithDirections() - kb = KeyBuffer(km, km.directions) - - press = self._mkpress(kb) - - km.map('abc<dir>', func) - km.dir('j', down=42) - self.assertEqual(42, press('abcj')) - -if __name__ == '__main__': main() diff --git a/test/tc_relative_symlink.py b/test/tc_relative_symlink.py deleted file mode 100644 index a202513d..00000000 --- a/test/tc_relative_symlink.py +++ /dev/null @@ -1,47 +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/>. - -import os.path -import sys -rangerpath = os.path.join(os.path.dirname(__file__), '..') -if sys.path[1] != rangerpath: - sys.path[1:1] = [rangerpath] - -import unittest -from ranger.ext.relative_symlink import * -rel = get_relative_source_file - -class Test(unittest.TestCase): - def test_foo(self): - self.assertEqual('../foo', rel('/foo', '/x/bar')) - self.assertEqual('../../foo', rel('/foo', '/x/y/bar')) - self.assertEqual('../../a/b/foo', rel('/a/b/foo', '/x/y/bar')) - self.assertEqual('../../x/b/foo', rel('/x/b/foo', '/x/y/bar', - common_base='/')) - self.assertEqual('../b/foo', rel('/x/b/foo', '/x/y/bar')) - self.assertEqual('../b/foo', rel('/x/b/foo', '/x/y/bar')) - - def test_get_common_base(self): - self.assertEqual('/', get_common_base('', '')) - self.assertEqual('/', get_common_base('', '/')) - self.assertEqual('/', get_common_base('/', '')) - self.assertEqual('/', get_common_base('/', '/')) - self.assertEqual('/', get_common_base('/bla/bar/x', '/foo/bar/a')) - self.assertEqual('/foo/bar/', get_common_base('/foo/bar/x', '/foo/bar/a')) - self.assertEqual('/foo/', get_common_base('/foo/bar/x', '/foo/baz/a')) - self.assertEqual('/foo/', get_common_base('/foo/bar/x', '/foo/baz/a')) - self.assertEqual('/', get_common_base('//foo/bar/x', '/foo/baz/a')) - -if __name__ == '__main__': unittest.main() diff --git a/test/tc_signal.py b/test/tc_signal.py deleted file mode 100644 index 6547bbc3..00000000 --- a/test/tc_signal.py +++ /dev/null @@ -1,139 +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/>. - -import os.path -import sys -rangerpath = os.path.join(os.path.dirname(__file__), '..') -if sys.path[1] != rangerpath: - sys.path[1:1] = [rangerpath] - -import unittest -import gc -from ranger.ext.signals import * - -class TestSignal(unittest.TestCase): - def setUp(self): - self.sd = SignalDispatcher() - - def test_signal_register_emit(self): - sd = self.sd - def poo(sig): - self.assert_('works' in sig) - self.assertEqual('yes', sig.works) - handler = sd.signal_bind('x', poo) - - sd.signal_emit('x', works='yes') - sd.signal_unbind(handler) - sd.signal_emit('x') - - def test_signal_order(self): - sd = self.sd - lst = [] - def addn(n): - return lambda _: lst.append(n) - - sd.signal_bind('x', addn(6)) - sd.signal_bind('x', addn(3), priority=1) - sd.signal_bind('x', addn(2), priority=1) - sd.signal_bind('x', addn(9), priority=0) - sd.signal_bind('x', addn(1337), priority=0.7) - sd.signal_emit('x') - - self.assert_(lst.index(3) < lst.index(6)) - self.assert_(lst.index(2) < lst.index(6)) - self.assert_(lst.index(6) < lst.index(9)) - self.assert_(lst.index(1337) < lst.index(6)) - self.assert_(lst.index(1337) < lst.index(9)) - self.assert_(lst.index(1337) > lst.index(2)) - - def test_modifying_arguments(self): - sd = self.sd - lst = [] - def modify(s): - s.number = 5 - def set_number(s): - lst.append(s.number) - def stopit(s): - s.stop() - - sd.signal_bind('setnumber', set_number) - sd.signal_emit('setnumber', number=100) - self.assertEqual(100, lst[-1]) - - sd.signal_bind('setnumber', modify, priority=1) - sd.signal_emit('setnumber', number=100) - self.assertEqual(5, lst[-1]) - - lst.append(None) - sd.signal_bind('setnumber', stopit, priority=1) - sd.signal_emit('setnumber', number=100) - self.assertEqual(None, lst[-1]) - - def test_weak_refs(self): - sd = self.sd - is_deleted = [False] - - class Foo(object): - def __init__(self): - self.alphabet = ['a'] - def calc(self, signal): - self.alphabet.append(chr(ord(self.alphabet[-1]) + 1)) - def __del__(self): - is_deleted[0] = True - - foo = Foo() - alphabet = foo.alphabet - calc = foo.calc - - del foo - self.assertEqual('a', ''.join(alphabet)) - sd.signal_bind('mysignal', calc, weak=True) - sd.signal_emit('mysignal') - self.assertEqual('ab', ''.join(alphabet)) - self.assertFalse(is_deleted[0]) - - del calc - self.assertTrue(is_deleted[0]) - - def test_weak_refs_dead_on_arrival(self): - sd = self.sd - is_deleted = [False] - - class Foo(object): - def __init__(self): - self.alphabet = ['a'] - def calc(self, signal): - self.alphabet.append(chr(ord(self.alphabet[-1]) + 1)) - def __del__(self): - is_deleted[0] = True - - foo = Foo() - alphabet = foo.alphabet - - self.assertEqual('a', ''.join(alphabet)) - sd.signal_bind('mysignal', foo.calc, weak=True) - - sd.signal_emit('mysignal') - self.assertEqual('ab', ''.join(alphabet)) - self.assertFalse(is_deleted[0]) - - del foo - - sd.signal_emit('mysignal') - self.assertEqual('ab', ''.join(alphabet)) - self.assertTrue(is_deleted[0]) - -if __name__ == '__main__': - unittest.main() diff --git a/test/tc_ui.py b/test/tc_ui.py deleted file mode 100644 index fa2bdcac..00000000 --- a/test/tc_ui.py +++ /dev/null @@ -1,69 +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/>. - -import os.path -import sys -rangerpath = os.path.join(os.path.dirname(__file__), '..') -if sys.path[1] != rangerpath: - sys.path[1:1] = [rangerpath] - -import unittest -import curses - -from ranger.gui import ui - -from testlib import Fake, OK, raise_ok - -ui.curses = Fake() - -class Test(unittest.TestCase): - def setUp(self): - - self.fm = Fake() - self.ui = ui.UI(env=Fake(), fm=self.fm) - - def fakesetup(): - self.ui.widget = Fake() - self.ui.add_child(self.ui.widget) - self.ui.setup = fakesetup - - self.ui.initialize() - - def tearDown(self): - self.ui.destroy() - - def test_passing(self): - # Test whether certain method calls are passed to widgets - widget = self.ui.widget - - widget.draw = raise_ok - self.assertRaises(OK, self.ui.draw) - widget.__clear__() - - widget.finalize = raise_ok - self.assertRaises(OK, self.ui.finalize) - widget.__clear__() - - widget.press = raise_ok - random_key = 123 - self.assertRaises(OK, self.ui.handle_key, random_key) - widget.__clear__() - - widget.destroy = raise_ok - self.assertRaises(OK, self.ui.destroy) - widget.__clear__() - -if __name__ == '__main__': - unittest.main() diff --git a/test/tc_utfwidth.py b/test/tc_utfwidth.py deleted file mode 100644 index 0288c17b..00000000 --- a/test/tc_utfwidth.py +++ /dev/null @@ -1,46 +0,0 @@ -# -*- encoding: utf8 -*- -# 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 - -import os.path -import sys -rangerpath = os.path.join(os.path.dirname(__file__), '..') -if sys.path[1] != rangerpath: - sys.path[1:1] = [rangerpath] -sys.path[1:1] = ['..'] - -from unittest import TestCase, main -from ranger.ext.utfwidth import * - -a_ascii = "a" # width = 1, bytes = 1 -a_umlaut = "ä" # width = 1, bytes = 2 -a_katakana = "ア" # width = 2, bytes = 3 -# need one with width = 1 & bytes = 3 - -class Test(TestCase): - def test_utf_byte_length(self): - self.assertEqual(1, utf_byte_length(a_ascii)) - self.assertEqual(2, utf_byte_length(a_umlaut)) - self.assertEqual(3, utf_byte_length(a_katakana)) - - def test_uwid(self): - self.assertEqual(1, uwid(a_ascii)) - self.assertEqual(1, uwid(a_umlaut)) - self.assertEqual(2, uwid(a_katakana)) - self.assertEqual(3, uwid(a_katakana + a_umlaut)) - self.assertEqual(4, uwid("asdf")) - self.assertEqual(5, uwid("löööl")) - self.assertEqual(6, uwid("バババ")) - -if __name__ == '__main__': main() diff --git a/test/testdir/largefile.txt b/test/testdir/largefile.txt deleted file mode 100644 index 0eb8c64f..00000000 --- a/test/testdir/largefile.txt +++ /dev/null @@ -1 +0,0 @@ -XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX diff --git a/test/testdir/symlink b/test/testdir/symlink deleted file mode 120000 index 5cbc1596..00000000 --- a/test/testdir/symlink +++ /dev/null @@ -1 +0,0 @@ -textfile.txt \ No newline at end of file diff --git a/test/testdir/textfile.txt b/test/testdir/textfile.txt deleted file mode 100644 index 45a23497..00000000 --- a/test/testdir/textfile.txt +++ /dev/null @@ -1,4 +0,0 @@ -Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. -Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. -Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. -Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. diff --git a/test/testdir/zerobytes b/test/testdir/zerobytes deleted file mode 100644 index e69de29b..00000000 --- a/test/testdir/zerobytes +++ /dev/null diff --git a/test/testlib.py b/test/testlib.py deleted file mode 100644 index 29dd9e07..00000000 --- a/test/testlib.py +++ /dev/null @@ -1,43 +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/>. - -def TODO(fnc): - def result(*arg, **kw): - try: - fnc(*arg, **kw) - except: - pass # failure expected - return result - -class Fake(object): - def __getattr__(self, attrname): - val = Fake() - self.__dict__[attrname] = val - return val - - def __call__(self, *_, **__): - return Fake() - - def __clear__(self): - self.__dict__.clear() - - def __iter__(self): - return iter(()) - -class OK(Exception): - pass - -def raise_ok(*_, **__): - raise OK() |