diff options
82 files changed, 3262 insertions, 5421 deletions
diff --git a/Makefile b/Makefile index fd525721..6c5ed88c 100644 --- a/Makefile +++ b/Makefile @@ -23,7 +23,6 @@ SETUPOPTS ?= '--record=install_log.txt' DOCDIR ?= doc/pydoc DESTDIR ?= / PYOPTIMIZE ?= 1 -BMCOUNT ?= 5 # how often to run the benchmarks? CWD = $(shell pwd) @@ -43,7 +42,6 @@ help: @echo 'make clean: Remove the compiled files (*.pyc, *.pyo)' @echo 'make cleandoc: Remove the pydoc documentation' @echo 'make snapshot: Create a tar.gz of the current git revision' - @echo 'make test: Run all unittests.' install: $(PYTHON) setup.py install $(SETUPOPTS) \ @@ -53,7 +51,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 +62,17 @@ doc: cleandoc pydoc.writedocs("$(CWD)")' find . -name \*.html -exec sed -i 's|'$(CWD)'|../..|g' -- {} \; -cleandoc: - test -d $(DOCDIR) && rm -f -- $(DOCDIR)/*.html || true +man: + pod2man --stderr --center='ranger manual' --date='$(NAME)-$(VERSION)' \ + --release=$(shell date +%x) doc/ranger.pod doc/ranger.1 -test: - @$(PYTHON) test/all_tests.py 1 +manhtml: + pod2html doc/ranger.pod --outfile=doc/ranger.1.html -bm: - @$(PYTHON) test/all_benchmarks.py $(BMCOUNT) +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 +.PHONY: default options compile clean doc cleandoc snapshot install man diff --git a/README b/README index e11a7b99..94aa19c6 100644 --- a/README +++ b/README @@ -14,6 +14,25 @@ The program is written in Python (2.6 or 3.1) and uses curses for the text-based user interface. +Getting Started +--------------- + +Ranger can be started without installing: Just run ranger.py. If you want to +install it anyway, the INSTALL file contains instructions. + +After starting ranger, you can use the Arrow Keys (or hjkl) to navigate, Enter +to open a file or type Q to quit. The column on the right shows a preview of +the current file. The second from the right is the main column and the others +show parent directories. + +The manual page of ranger contains a documentation of rangers functions, +keybindings, commands and options. You can access it by typing "?" in ranger, +running "man ranger" or view it online. + +An easy way to customize ranger is running "ranger --copy-config=all" +and editing the new files in ~/.config/ranger/. + + About ----- @@ -46,6 +65,7 @@ Features * Multi-column display (Miller Columns) * 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 @@ -55,8 +75,8 @@ Features Dependencies ------------ -* A *nix-like operating system -* Python 2.6 or Python 3.1 with the curses module +* Python 2.6, 2.7 or Python 3.1 with the curses module +(Later versions might work too, but 2.5 definitely won't.) Optional: * The "file" program @@ -68,89 +88,3 @@ For scope.sh: (enhanced file previews) * 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. - -To customize ranger, copy the files from ranger/defaults/ to ~/.config/ranger/ -and modify them according to your wishes. - - -Combine Ranger With Other Applications --------------------------------------- - -1. bash: - -Add this to your ~/.bashrc to use ranger as a directory switcher: - -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 ---------------- - -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. diff --git a/doc/TODO b/doc/TODO index 1577f97a..ce1fee6c 100644 --- a/doc/TODO +++ b/doc/TODO @@ -27,16 +27,18 @@ General (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) #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) #38 10/01/16 searching in pager + won't do this (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 + (X) #50 10/01/19 add more unit tests + won't do this ( ) #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 "_" @@ -50,14 +52,15 @@ General (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) #80 10/04/08 when closing tabs, avoid gaps? + won't do this (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 + (X) #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 + (X) #91 10/05/12 in keys.py, fm.move(right=N) should run with mode=N + (X) #92 10/05/14 allow to enter the realpath of a directory + (X) #93 10/05/15 pause after running program Bugs @@ -107,13 +110,9 @@ Ideas ( ) #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 + (X) #82 10/04/19 :s command for batch renaming + implemented with :bulkrename + (X) #84 10/04/25 use pygments for syntax highlighting + implemented with scope.sh ( ) #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..7bf94b41 100644 --- a/doc/ranger.1 +++ b/doc/ranger.1 @@ -1,235 +1,900 @@ -.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 +.\" Automatically generated by Pod::Man 2.22 (Pod::Simple 3.07) +.\" +.\" 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.4.3" "10/01/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 [\fIoptions\fR] [\fIpath/filename\fR] +.SH "DESCRIPTION" +.IX Header "DESCRIPTION" +Ranger is a file manager with an ncurses front-end written in Python. +.PP 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 +previews and backviews, dividing the screen into several columns. The +key bindings are similar to those of other console programs like \fBvim\fR, +\&\fBmutt\fR or \fBncmpcpp\fR so the usage will be intuitive and efficient. +.SH "OPTIONS" +.IX Header "OPTIONS" +.IP "\fB\-\-verison\fR" 14 +.IX Item "--verison" Print the version and exit. -.TP --h, --help +.IP "\fB\-h\fR, \fB\-\-help\fR" 14 +.IX Item "-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 +.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. -.TP ---choosefile=\fItargetfile\fR +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, +\&\fIapps\fR, \fIcommands\fR, \fIkeys\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\-c\fRd" 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. +.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 creating \fI~/.config/ranger/scope.sh\fR (see preview_script option.) +This script will then be executed each time you attempt to preview a file. +.PP +Fetch the default scope.sh (from \fIranger/data/scope.sh\fR) by running +\&\f(CW\*(C`ranger \-\-copy\-config=scope\*(C'\fR +.PP +This default script 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. Make sure to also have the options \*(L"use_preview_script\*(R" and +\&\*(L"preview_files\*(R" turned on. +.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 behaviour 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 "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 "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 "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>" 4 +.IX Item "Download: <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..b8d779c1 --- /dev/null +++ b/doc/ranger.pod @@ -0,0 +1,992 @@ +=head1 NAME + +ranger - visual file manager + + + + +=head1 SYNOPSIS + +B<ranger> [I<options>] [I<path/filename>] + + + + +=head1 DESCRIPTION + +Ranger is a file manager with an ncurses front-end 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 several columns. The +key bindings are similar to those of other console programs like B<vim>, +B<mutt> or B<ncmpcpp> so the usage will be intuitive and efficient. + + + + +=head1 OPTIONS + +=over 14 + +=item B<--verison> + +Print the version and exit. + +=item B<-h>, B<--help> + +Print a list of options and exit. + +=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<apps>, I<commands>, I<keys>, 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-c>d + +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. + +=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 creating F<~/.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 F<ranger/data/scope.sh>) by running +C<ranger --copy-config=scope> + +This default script 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. Make sure to also have the options "use_preview_script" and +"preview_files" turned on. + +=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 behaviour 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 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 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 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..44fc298f 100755 --- a/ranger.py +++ b/ranger.py @@ -1,7 +1,5 @@ #!/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> # # This program is free software: you can redistribute it and/or modify @@ -21,11 +19,14 @@ # after you exit ranger. Run it with the command: source ranger 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-)" + tempfile='/tmp/chosendir' + ranger="$1" + shift + "$ranger" --choosedir="$tempfile" "${@:-$(pwd)}" + test -f "$tempfile" && + if [ "$(cat -- "$tempfile")" != "$(echo -n `pwd`)" ]; then + cd "$(cat "$tempfile")" + rm -f -- "$tempfile" fi && return 0 else echo "usage: source path/to/ranger.py path/to/ranger.py" @@ -34,25 +35,19 @@ return 1 """ 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/api/apps.py b/ranger/api/apps.py index 45432705..86c7b017 100644 --- a/ranger/api/apps.py +++ b/ranger/api/apps.py @@ -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): @@ -101,7 +102,17 @@ class Applications(FileManagerAware): if app in get_executables(): return _generic_app(app, context) handler = self.app_default - return handler(context) + arguments = handler(context) + # flatten + if isinstance(arguments, str): + return (arguments, ) + 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""" @@ -147,7 +158,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..a22fd0b3 100644 --- a/ranger/api/commands.py +++ b/ranger/api/commands.py @@ -14,9 +14,11 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. import os +import ranger from collections import deque from ranger.api import * from ranger.core.shared import FileManagerAware +from ranger.ext.lazy_property import lazy_property from ranger.ext.command_parser import LazyParser as parse # A dummy that allows the generation of docstrings in ranger.defaults.commands @@ -38,7 +40,11 @@ class CommandContainer(object): for varname, var in vars(module).items(): try: if issubclass(var, Command) and var != Command: - self.commands[var.name or varname] = var + classdict = var.__mro__[0].__dict__ + if 'name' in classdict and classdict['name']: + self.commands[var.name] = var + else: + self.commands[varname] = var except TypeError: pass for new, old in self.aliases.items(): @@ -47,6 +53,18 @@ class CommandContainer(object): 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: lst = [cls for cmd, cls in self.commands.items() \ @@ -73,8 +91,14 @@ class Command(FileManagerAware): """Abstract command class""" name = None allow_abbrev = True - def __init__(self, line): + resolve_macros = True + quantifier = None + _shifted = 0 + + def __init__(self, line, quantifier=None): self.line = line + self.args = line.split() + self.quantifier = quantifier def execute(self): """Override this""" @@ -88,6 +112,51 @@ 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]) + + @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 @@ -187,3 +256,57 @@ class Command(FileManagerAware): # 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) + + +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/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..03122c59 100644 --- a/ranger/colorschemes/default.py +++ b/ranger/colorschemes/default.py @@ -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/container/__init__.py b/ranger/container/__init__.py index 3351cc63..21a336bc 100644 --- a/ranger/container/__init__.py +++ b/ranger/container/__init__.py @@ -1,22 +1,6 @@ -# 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..0dcfcbc3 100644 --- a/ranger/container/bookmarks.py +++ b/ranger/container/bookmarks.py @@ -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/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..f7507b3e 100644 --- a/ranger/container/settingobject.py +++ b/ranger/container/settingobject.py @@ -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..24fbd0a5 100644 --- a/ranger/container/tags.py +++ b/ranger/container/tags.py @@ -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..15e6dd74 100644 --- a/ranger/core/actions.py +++ b/ranger/core/actions.py @@ -24,6 +24,7 @@ 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 from ranger.ext.shell_escape import shell_quote from ranger import fsobject from ranger.core.shared import FileManagerAware, EnvironmentAware, \ @@ -34,7 +35,6 @@ from ranger.core.loader import CommandLoader class _MacroTemplate(string.Template): """A template for substituting macros in commands""" delimiter = '%' - idpattern = '\d?[a-z]' class Actions(FileManagerAware, EnvironmentAware, SettingsAware): search_method = 'ctime' @@ -70,8 +70,19 @@ 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(text, 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,17 +90,31 @@ 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() + command_name = string.split()[0] + try: + cmd_class = self.commands.get_command(command_name) + except: + self.notify("Command not found: `%s'" % command_name, bad=True) + else: + 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'] + string = self.substitute_macros(string, additional=macros) + try: + cmd_class(string, quantifier=quantifier).execute() + except Exception as error: + self.notify(error) - def substitute_macros(self, string): - return _MacroTemplate(string).safe_substitute(self._get_macros()) + def substitute_macros(self, string, additional=dict()): + return _MacroTemplate(string).safe_substitute(self._get_macros(), + **additional) def _get_macros(self): macros = {} @@ -150,6 +175,18 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware): return macros + def source_cmdlist(self, filename, narg=None): + 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. @@ -226,23 +263,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): @@ -294,10 +334,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 +356,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 +397,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,9 +421,9 @@ 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) @@ -464,8 +499,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) @@ -512,9 +546,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 +565,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, narg=None): + 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 +583,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 @@ -694,10 +700,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): @@ -845,6 +851,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..90f0fefa 100644 --- a/ranger/core/environment.py +++ b/ranger/core/environment.py @@ -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.keybindings import KeyBuffer, KeyMaps +from ranger.container 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) diff --git a/ranger/core/fm.py b/ranger/core/fm.py index fa972b50..c292e0c9 100644 --- a/ranger/core/fm.py +++ b/ranger/core/fm.py @@ -27,7 +27,7 @@ import sys import ranger from ranger.core.actions import Actions from ranger.container.tags import Tags -from ranger.gui.defaultui import DefaultUI +from ranger.gui.ui import UI from ranger.container import Bookmarks from ranger.core.runner import Runner from ranger.ext.get_executables import get_executables @@ -90,7 +90,7 @@ class FM(Actions, SignalDispatcher): 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 +142,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 +151,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): diff --git a/ranger/core/helper.py b/ranger/core/helper.py index ad5541f5..d8081b01 100644 --- a/ranger/core/helper.py +++ b/ranger/core/helper.py @@ -73,6 +73,8 @@ 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) @@ -85,14 +87,17 @@ def parse_arguments(): def load_settings(fm, clean): + from ranger.core.actions import Actions import ranger.core.shared import ranger.api.commands - import ranger.api.keys if not clean: allow_access_to_confdir(ranger.arg.confdir, True) # Load commands comcont = ranger.api.commands.CommandContainer() + exclude = ['settings'] + include = [name for name in dir(Actions) if name not in exclude] + comcont.load_commands_from_object(fm, include) ranger.api.commands.alias = comcont.alias try: import commands @@ -101,35 +106,74 @@ def load_settings(fm, clean): pass from ranger.defaults import commands comcont.load_commands_from_module(commands) - commands = comcont + fm.commands = comcont # Load apps try: import apps except ImportError: from ranger.defaults import apps - - # Load keys - keymanager = ranger.core.shared.EnvironmentAware.env.keymanager - ranger.api.keys.keymanager = keymanager - from ranger.defaults import keys + 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 + + # If load_default_rc is None, think hard: If the users rc.conf is + # about as large as the default rc.conf, he probably copied it as a whole + # and doesn't want to load the default rc.conf anymore. + if load_default_rc is None: + try: + custom_conf_size = os.stat(custom_conf).st_size + except: + load_default_rc = True + else: + default_conf_size = os.stat(default_conf).st_size + load_default_rc = custom_conf_size < default_conf_size - 2048 + + if load_default_rc: + fm.source_cmdlist(default_conf) + if os.access(custom_conf, os.R_OK): + fm.source_cmdlist(custom_conf) + + # Load plugins 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() + import types + ranger.fm = fm + for plugin in sorted(plugins): + try: + mod = __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 + from ranger.defaults import commands, apps + comcont = ranger.api.commands.CommandContainer() + exclude = ['settings'] + include = [name for name in dir(Actions) if name not in exclude] + comcont.load_commands_from_object(fm, include) comcont.load_commands_from_module(commands) - commands = comcont - fm.commands = commands - fm.keys = keys - fm.apps = apps.CustomApplications() + fm.commands = comcont + fm.source_cmdlist(fm.relpath('defaults', 'rc.conf')) + fm.apps = apps.CustomApplications() def load_apps(fm, clean): diff --git a/ranger/core/main.py b/ranger/core/main.py index e6392387..6595fbf2 100644 --- a/ranger/core/main.py +++ b/ranger/core/main.py @@ -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 + maps = fm.env.keymaps['browser'] + reversed_special_keys = dict((v,k) for k,v in special_keys.items()) + 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 diff --git a/ranger/core/runner.py b/ranger/core/runner.py index 53bede29..ad4ca558 100644 --- a/ranger/core/runner.py +++ b/ranger/core/runner.py @@ -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' @@ -199,7 +200,7 @@ class Runner(object): self._log("Failed to run: " + str(action)) 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..ce7c3184 100644 --- a/ranger/core/shared.py +++ b/ranger/core/shared.py @@ -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/defaults/apps.py b/ranger/defaults/apps.py index ffa828c0..77bc9c2b 100644 --- a/ranger/defaults/apps.py +++ b/ranger/defaults/apps.py @@ -28,21 +28,21 @@ This example modifies the behaviour of "feh" and adds a custom media player: from ranger.api.apps import * class CustomApplications(DefaultApps): - def app_kaffeine(self, c): - return tup('kaffeine', *c) + def app_kaffeine(self, context): + return 'kaffeine', context - def app_feh_fullscreen_by_default(self, c): - return tup('feh', '-F', *c) + def app_feh_fullscreen_by_default(self, context): + return 'feh', '-F', context - def app_default(self, c): - f = c.file #shortcut + def app_default(self, context): + f = context.file #shortcut if f.video or f.audio: - return self.app_kaffeine(c) + return self.app_kaffeine(context) - if f.image and c.mode == 0: - return self.app_feh_fullscreen_by_default(c) + if f.image and context.mode == 0: + return self.app_feh_fullscreen_by_default(context) - return DefaultApps.app_default(self, c) + return DefaultApps.app_default(self, context) #### end of the example """ @@ -96,7 +96,10 @@ class CustomApplications(Applications): return self.either(c, '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 +112,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 +135,66 @@ 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'} - + 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 + @depends_on('feh') + 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 tup('feh', '--start-at', c.file.basename, *images) - return tup('feh', *c) + return 'feh', '--start-at', c.file.basename, images + return 'feh', c + + @depends_on('sxiv') + 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] + position = images.index(c.file.basename) + 1 + 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') 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,30 +203,30 @@ 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') 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) +# def app_vim(self, context): +# return "vim", context # 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', diff --git a/ranger/defaults/commands.py b/ranger/defaults/commands.py index ad3fa0e5..a909cee9 100644 --- a/ranger/defaults/commands.py +++ b/ranger/defaults/commands.py @@ -66,15 +66,21 @@ alias('qall', 'quitall') 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)) + else: + destination = self.rest(1) + if not destination: destination = '~' @@ -137,6 +143,16 @@ class cd(Command): return (line.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) @@ -356,6 +372,7 @@ class set_(Command): name = line.chunk(1) name, value, _ = line.parse_setting_line() if name and value: + from re import compile as regexp try: value = eval(value) except: @@ -491,6 +508,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 +536,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 +555,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() @@ -569,7 +608,7 @@ class touch(Command): line = parse(self.line) fname = join(self.fm.env.cwd.path, expanduser(line.rest(1))) if not lexists(fname): - open(fname, 'a') + open(fname, 'a').close() else: self.fm.notify("file/directory exists!", bad=True) @@ -594,7 +633,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 +645,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 +681,23 @@ class rename(Command): def execute(self): from ranger.fsobject import File + from os import access + from os.path import join + line = parse(self.line) - if not line.rest(1): + new_name = line.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 +719,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 +745,113 @@ 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.run(['vim', cmdfile.name]) + 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): + self.fm.display_help() + + +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> 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 c9cbf8ad..fefdf39d 100644 --- a/ranger/defaults/options.py +++ b/ranger/defaults/options.py @@ -33,6 +33,13 @@ of the values stay the same. from ranger.api.options import * +# Load the deault rc.conf file? If you've copied it to your configuration +# direcory, then you should deactivate this option. "None" means guess. +load_default_rc = None + +# 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' hidden_filter = regexp( @@ -74,9 +81,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, 1, 4, 3) - # Enable the mouse support? mouse_enabled = True diff --git a/ranger/defaults/rc.conf b/ranger/defaults/rc.conf new file mode 100644 index 00000000..aabdbb30 --- /dev/null +++ b/ranger/defaults/rc.conf @@ -0,0 +1,360 @@ +# =================================================================== +# 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. +# =================================================================== + + +# =================================================================== +# == Define keys for the browser +# =================================================================== + +# Basic +map Q quit! +map q quit +map ZZ quit +map ZQ quit + +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 eval fm.ui.open_taskview() +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 val=True +map V mark_files all=True val=False +map uv mark_files all=True val=False + +# VIM-like +map gg move to=0 +map G move to=-1 +map j move down=1 +map k move up=1 +map h move left=1 +map l move right=1 +map J move down=0.5 pages=True +map K move up=0.5 pages=True +map <C-d> move down=0.5 pages=True +map <C-u> move up=0.5 pages=True +map <C-f> move down=1 pages=True +map <C-b> move up=1 pages=True + +# 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 <DEL> console delete +map <INS> console touch + +# 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 `<bg> draw_bookmarks +map '<bg> draw_bookmarks +map m<bg> draw_bookmarks +map um<bg> draw_bookmarks +map `<any> enter_bookmark %any +map '<any> enter_bookmark %any +map m<any> set_bookmark %any +map um<any> unset_bookmark %any + +# 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 <C-c> eval fm.ui.console.close() +cmap <C-d> eval fm.ui.console.close() +cmap <ESC> eval fm.ui.console.close() +cmap <CR> eval fm.ui.console.execute() +cmap <C-j> eval fm.ui.console.execute() +cmap <C-l> redraw_window + +# This special expression allows typing in numerals: +cmap <allow_quantifiers> false + +# Move around +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) +cmap <up> eval fm.ui.console.history_move(-1) +cmap <down> eval fm.ui.console.history_move(1) + +# And of course the emacs way +cmap <C-b> eval fm.ui.console.move(left=1) +cmap <C-f> eval fm.ui.console.move(right=1) +cmap <C-a> eval fm.ui.console.move(right=0, absolute=True) +cmap <C-e> eval fm.ui.console.move(right=-1, absolute=True) +cmap <C-p> eval fm.ui.console.history_move(-1) +cmap <C-n> eval fm.ui.console.history_move(1) + +# Line Editing +# Note: There are multiple ways to express backspaces. <backspace> (code 263) +# and <backspace2> (code 127). To be sure, use both. +cmap <backspace> eval fm.ui.console.delete(-1) +cmap <backspace2> eval fm.ui.console.delete(-1) +cmap <delete> eval fm.ui.console.delete(0) +cmap <C-h> eval fm.ui.console.delete(-1) +cmap <C-d> 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() + + +# =================================================================== +# == Pager Keybindings +# =================================================================== + +# Movement +pmap j eval -q fm.ui.browser.pager.move(down=1) +pmap k eval -q fm.ui.browser.pager.move(up=1) +pmap gg eval -q fm.ui.browser.pager.move(down=0, absolute=True) +pmap G eval -q fm.ui.browser.pager.move(down=-1, absolute=True) +pmap <cr> eval -q fm.ui.browser.pager.move(down=1) +pmap <down> eval -q fm.ui.browser.pager.move(down=1) +pmap <up> eval -q fm.ui.browser.pager.move(up=1) +pmap <home> eval -q fm.ui.browser.pager.move(down=0, absolute=True) +pmap <end> eval -q fm.ui.browser.pager.move(down=-1, absolute=True) +pmap <C-n> eval -q fm.ui.browser.pager.move(down=1) +pmap <C-p> eval -q fm.ui.browser.pager.move(up=1) + +pmap d eval -q fm.ui.browser.pager.move(down=0.5, pages=True) +pmap <C-d> eval -q fm.ui.browser.pager.move(down=0.5, pages=True) +pmap u eval -q fm.ui.browser.pager.move(up= 0.5, pages=True) +pmap <C-u> eval -q fm.ui.browser.pager.move(up= 0.5, pages=True) +pmap n eval -q fm.ui.browser.pager.move(down=1.0, pages=True) +pmap f eval -q fm.ui.browser.pager.move(down=1.0, pages=True) +pmap <pagedown> eval -q fm.ui.browser.pager.move(down=1.0, pages=True) +pmap p eval -q fm.ui.browser.pager.move(up= 1.0, pages=True) +pmap b eval -q fm.ui.browser.pager.move(up= 1.0, pages=True) +pmap <pageup> eval -q fm.ui.browser.pager.move(up= 1.0, pages=True) +pmap <space> eval -q fm.ui.browser.pager.move(down=0.8, pages=True) + +pmap h eval -q fm.ui.browser.pager.move(left=4) +pmap l eval -q fm.ui.browser.pager.move(right=4) +pmap <left> eval -q fm.ui.browser.pager.move(left=4) +pmap <right> eval -q fm.ui.browser.pager.move(right=4) + +# Basic +pmap q eval -q fm.ui.close_pager(); fm.ui.close_embedded_pager() +pmap i eval -q fm.ui.close_pager(); fm.ui.close_embedded_pager() +pmap <ESC> eval -q fm.ui.close_pager(); fm.ui.close_embedded_pager() +pmap <F3> eval -q fm.ui.close_pager(); fm.ui.close_embedded_pager() +pmap E edit_file + +# =================================================================== +# == Taskview Keybindings +# =================================================================== + +# Movement +tmap j eval -q fm.ui.taskview.move(down=1) +tmap k eval -q fm.ui.taskview.move(up=1) +tmap gg eval -q fm.ui.taskview.move(down=0, absolute=True) +tmap G eval -q fm.ui.taskview.move(down=-1, absolute=True) +tmap <down> eval -q fm.ui.taskview.move(down=1) +tmap <up> eval -q fm.ui.taskview.move(up=1) +tmap <home> eval -q fm.ui.taskview.move(down=0, absolute=True) +tmap <end> eval -q fm.ui.taskview.move(down=-1, absolute=True) + +# 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() + +# A bunch of aliases for closing +tmap w eval -q fm.ui.close_taskview() +tmap q eval -q fm.ui.close_taskview() +tmap Q eval -q fm.ui.close_taskview() +tmap <esc> eval -q fm.ui.close_taskview() +tmap <c-c> eval -q fm.ui.close_taskview() diff --git a/ranger/ext/keybinding_parser.py b/ranger/ext/keybinding_parser.py index bfd1b6d4..7dbf8c25 100644 --- a/ranger/ext/keybinding_parser.py +++ b/ranger/ext/keybinding_parser.py @@ -21,7 +21,7 @@ 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,16 +63,34 @@ def parse_keybinding(obj): for c in bracket_content: yield ord(c) +def construct_keybinding(iterable): + """ + Does the reverse of parse_keybinding + """ + result = [] + for c in iterable: + result.append(key_to_string(c)) + return ''.join(result) + +def key_to_string(key): + try: + return chr(key) + except: + return '?' + # Arbitrary numbers which are not used with curses.KEY_XYZ DIRKEY = 9001 ANYKEY = 9002 PASSIVE_ACTION = 9003 ALT_KEY = 9004 +QUANT_KEY = 9005 +HINT_KEY = 9006 very_special_keys = { 'dir': DIRKEY, 'any': ANYKEY, 'bg': PASSIVE_ACTION, + 'allow_quantifiers': QUANT_KEY, } special_keys = { diff --git a/ranger/ext/keybindings.py b/ranger/ext/keybindings.py new file mode 100644 index 00000000..133da83b --- /dev/null +++ b/ranger/ext/keybindings.py @@ -0,0 +1,111 @@ +# 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.keybinding_parser import (parse_keybinding, + ANYKEY, PASSIVE_ACTION, QUANT_KEY) +import sys + +PY3 = sys.version > '3' + +digits = set(range(ord('0'), ord('9')+1)) + +class KeyMaps(dict): + def __init__(self, keybuffer=None): + dict.__init__(self) + self.keybuffer = keybuffer + self.used_keymap = None + + def bind(self, context, keys, leaf): + try: + pointer = self[context] + except: + self[context] = pointer = dict() + if PY3: + keys = keys.encode('utf-8').decode('latin-1') + keys = list(parse_keybinding(keys)) + if not keys: + return + last_key = keys[-1] + for key in keys[:-1]: + try: + pointer = pointer[key] + except: + pointer[key] = pointer = dict() + pointer[last_key] = leaf + + 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() + + +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("{0:c}".format(c) for c in self.keys) 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..8986be61 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 # # 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/gui/bar.py b/ranger/gui/bar.py index aa5c9ab4..f2b1571a 100644 --- a/ranger/gui/bar.py +++ b/ranger/gui/bar.py @@ -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,18 @@ 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,7 +119,7 @@ class BarSide(list): if item.fixed: n += len(item) else: - n += 1 + n += item.min_size return n def nonfixed_items(self): @@ -126,19 +128,28 @@ class BarSide(list): 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/context.py b/ranger/gui/context.py index 20ce2817..a4219806 100644 --- a/ranger/gui/context.py +++ b/ranger/gui/context.py @@ -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/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/ui.py b/ranger/gui/ui.py index cc2871af..93f3da8e 100644 --- a/ranger/gui/ui.py +++ b/ranger/gui/ui.py @@ -20,7 +20,6 @@ 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 @@ -48,6 +47,7 @@ def _setup_mouse(signal): 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 @@ -58,8 +58,7 @@ class UI(DisplayableContainer): self.fm = fm self.win = curses.initscr() - self.env.keymanager.use_context('browser') - self.env.keybuffer.clear() + self.env.keymaps.use_keymap('browser') DisplayableContainer.__init__(self, None) @@ -86,6 +85,7 @@ class UI(DisplayableContainer): self.is_set_up = True self.setup() self.update_size() + self.is_on = True def suspend(self): """Turn off curses""" @@ -99,6 +99,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 +136,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 +206,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 +258,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 +291,66 @@ 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 + 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 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..2c865cbe 100644 --- a/ranger/gui/widgets/browsercolumn.py +++ b/ranger/gui/widgets/browsercolumn.py @@ -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 @@ -276,7 +277,6 @@ class BrowserColumn(Pager): 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..12e1d9bd 100644 --- a/ranger/gui/widgets/browserview.py +++ b/ranger/gui/widgets/browserview.py @@ -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) @@ -92,16 +94,17 @@ 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 + 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 +121,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 +168,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..127bd7ad 100644 --- a/ranger/gui/widgets/console.py +++ b/ranger/gui/widgets/console.py @@ -23,9 +23,8 @@ 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.ext.widestring import uwid from ranger.container import History from ranger.container.history import HistoryEmptyException import ranger @@ -88,12 +87,8 @@ class Console(Widget): 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 @@ -151,28 +146,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 @@ -238,14 +214,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,11 +283,11 @@ 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): @@ -329,7 +309,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..a95d3254 100644 --- a/ranger/gui/widgets/pager.py +++ b/ranger/gui/widgets/pager.py @@ -21,12 +21,6 @@ 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+\.') class Pager(Widget): source = None @@ -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: diff --git a/ranger/gui/widgets/statusbar.py b/ranger/gui/widgets/statusbar.py index b7ab123c..d8704af3 100644 --- a/ranger/gui/widgets/statusbar.py +++ b/ranger/gui/widgets/statusbar.py @@ -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..805fa270 100644 --- a/ranger/gui/widgets/taskview.py +++ b/ranger/gui/widgets/taskview.py @@ -22,7 +22,6 @@ 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 +95,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..c1994b5b 100644 --- a/ranger/gui/widgets/titlebar.py +++ b/ranger/gui/widgets/titlebar.py @@ -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("~") @@ -159,5 +159,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/setup.py b/setup.py index e63e28d2..0d72978b 100755 --- a/setup.py +++ b/setup.py @@ -29,7 +29,7 @@ if __name__ == '__main__': url='http://savannah.nongnu.org/projects/ranger', scripts=['scripts/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() |