diff options
author | Wojciech Siewierski <wojciech.siewierski@onet.pl> | 2018-12-26 16:14:19 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-12-26 16:14:19 +0100 |
commit | aecce4a251910c9da2d58017eccaefbd22edc4cb (patch) | |
tree | 397d68f4219a210eb41615c2ea9b75956a3fd40d | |
parent | 71c7564107a7e7ae22a55190d16c5eba4615f4a0 (diff) | |
parent | 7f20f31379eec35b97c138186d11d0265c9429f3 (diff) | |
download | ranger-aecce4a251910c9da2d58017eccaefbd22edc4cb.tar.gz |
Merge branch 'master' into anypathmacro
32 files changed, 666 insertions, 315 deletions
diff --git a/.travis.yml b/.travis.yml index 0efd09fc..7a156b2b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,12 +3,11 @@ dist: 'xenial' language: 'python' python: - '2.7' - - '3.4' - '3.5' + - '3.6' install: - - 'pip install pipenv' - - 'pipenv update --dev' + - 'pip install -r requirements.txt' script: - 'make test' diff --git a/CHANGELOG.md b/CHANGELOG.md index 346b18dd..e8eb913c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,44 @@ This log documents changes between stable versions. +# 2018-09-09: version 1.9.2 +* Added a `hint_collapse_threshold` setting +* Added a `traverse_backwards` command analogous to `traverse` +* Added a command to shift tabs +* Added a normal mode mapping to quickly enter the console and scroll through + the history `C-p` +* Added a section to `scope.sh` for image previews of archives +* Added an avfs plugin +* Added an option to the move command to enable launching the selected file + instead of the marked files +* Added filtering functionality inspired by dired's filter stack, `.n, .| ...` +* Added image preview method for Kitty +* Added option to disable the display of free space for high latency situations +* Added section to `scope.sh` for pdf previews with mutool +* Added several emacs/readline-inspired keybindings, `C-g` for `ESC`, `alt-f/b` +* Added systemwide `rc.conf` and `commands.py` in `/etc/ranger` +* Added the `%any_path` macro to allow bookmarks to be used with commands that + need a path and are unaware of bookmarks +* Added versioning logic to include extra info in unreleased versions +* Change tab saving to save all tabs, not just the active tab +* Changed `draw_borders` setting to enable drawing only borders or seperators +* Changed behavior of positional arguments to the ranger command, if you + specify a path to a file ranger will open with that file selected +* Changed the `tilde_in_titlebar` setting to influence the window titlebar too +* Changed the default colorscheme to work properly in terminals that don't + equate bold and bright +* Fixed StopIteration errors +* Fixed embedded null errors +* Fixed issues reported by coverity scan +* Fixed running ranger as root on Mac OS +* Fixed unicode issue for python2 +* Fixed w3m preview issues with black stripes +* Improved PEP8 adherence +* Improved VCS symbols +* Improved `--cmd` functionality +* Improved file encoding detection by using chardet if it's available +* Rifle's flag t should now work with more terminals than xterm and urxvt +* Update colorscheme documentation + # 2018-02-22: version 1.9.1 * Fixed the rifle config backwards compatibility (regression in 1.9.0) * Fixed the POSIX compatibility of `Makefile` diff --git a/Pipfile b/Pipfile deleted file mode 100644 index a927408c..00000000 --- a/Pipfile +++ /dev/null @@ -1,15 +0,0 @@ -[[source]] -url = "https://pypi.org/simple" -verify_ssl = true -name = "pypi" - -[requires] -python_version = "3.5" - -[dev-packages] -pytest = "*" -"flake8" = "*" -pylint = "<2.0.0" -"enum34" = "*" - -[packages] diff --git a/Pipfile.lock b/Pipfile.lock deleted file mode 100644 index be0d4a62..00000000 --- a/Pipfile.lock +++ /dev/null @@ -1,175 +0,0 @@ -{ - "_meta": { - "hash": { - "sha256": "ccd51f0d238502cb3001a4b709d4455134eeaebb96800ebaad364567ba1ba784" - }, - "pipfile-spec": 6, - "requires": { - "python_version": "3.5" - }, - "sources": [ - { - "name": "pypi", - "url": "https://pypi.org/simple", - "verify_ssl": true - } - ] - }, - "default": {}, - "develop": { - "astroid": { - "hashes": [ - "sha256:0ef2bf9f07c3150929b25e8e61b5198c27b0dca195e156f0e4d5bdd89185ca1a", - "sha256:fc9b582dba0366e63540982c3944a9230cbc6f303641c51483fa547dcc22393a" - ], - "version": "==1.6.5" - }, - "atomicwrites": { - "hashes": [ - "sha256:240831ea22da9ab882b551b31d4225591e5e447a68c5e188db5b89ca1d487585", - "sha256:a24da68318b08ac9c9c45029f4a10371ab5b20e4226738e150e6e7c571630ae6" - ], - "version": "==1.1.5" - }, - "attrs": { - "hashes": [ - "sha256:4b90b09eeeb9b88c35bc642cbac057e45a5fd85367b985bd2809c62b7b939265", - "sha256:e0d0eb91441a3b53dab4d9b743eafc1ac44476296a2053b6ca3af0b139faf87b" - ], - "version": "==18.1.0" - }, - "enum34": { - "hashes": [ - "sha256:2d81cbbe0e73112bdfe6ef8576f2238f2ba27dd0d55752a776c41d38b7da2850", - "sha256:644837f692e5f550741432dd3f223bbb9852018674981b1664e5dc339387588a", - "sha256:6bd0f6ad48ec2aa117d3d141940d484deccda84d4fcd884f5c3d93c23ecd8c79", - "sha256:8ad8c4783bf61ded74527bffb48ed9b54166685e4230386a9ed9b1279e2df5b1" - ], - "index": "pypi", - "version": "==1.1.6" - }, - "flake8": { - "hashes": [ - "sha256:7253265f7abd8b313e3892944044a365e3f4ac3fcdcfb4298f55ee9ddf188ba0", - "sha256:c7841163e2b576d435799169b78703ad6ac1bbb0f199994fc05f700b2a90ea37" - ], - "index": "pypi", - "version": "==3.5.0" - }, - "isort": { - "hashes": [ - "sha256:1153601da39a25b14ddc54955dbbacbb6b2d19135386699e2ad58517953b34af", - "sha256:b9c40e9750f3d77e6e4d441d8b0266cf555e7cdabdcff33c4fd06366ca761ef8", - "sha256:ec9ef8f4a9bc6f71eec99e1806bfa2de401650d996c59330782b89a5555c1497" - ], - "version": "==4.3.4" - }, - "lazy-object-proxy": { - "hashes": [ - "sha256:0ce34342b419bd8f018e6666bfef729aec3edf62345a53b537a4dcc115746a33", - "sha256:1b668120716eb7ee21d8a38815e5eb3bb8211117d9a90b0f8e21722c0758cc39", - "sha256:209615b0fe4624d79e50220ce3310ca1a9445fd8e6d3572a896e7f9146bbf019", - "sha256:27bf62cb2b1a2068d443ff7097ee33393f8483b570b475db8ebf7e1cba64f088", - "sha256:27ea6fd1c02dcc78172a82fc37fcc0992a94e4cecf53cb6d73f11749825bd98b", - "sha256:2c1b21b44ac9beb0fc848d3993924147ba45c4ebc24be19825e57aabbe74a99e", - "sha256:2df72ab12046a3496a92476020a1a0abf78b2a7db9ff4dc2036b8dd980203ae6", - "sha256:320ffd3de9699d3892048baee45ebfbbf9388a7d65d832d7e580243ade426d2b", - "sha256:50e3b9a464d5d08cc5227413db0d1c4707b6172e4d4d915c1c70e4de0bbff1f5", - "sha256:5276db7ff62bb7b52f77f1f51ed58850e315154249aceb42e7f4c611f0f847ff", - "sha256:61a6cf00dcb1a7f0c773ed4acc509cb636af2d6337a08f362413c76b2b47a8dd", - "sha256:6ae6c4cb59f199d8827c5a07546b2ab7e85d262acaccaacd49b62f53f7c456f7", - "sha256:7661d401d60d8bf15bb5da39e4dd72f5d764c5aff5a86ef52a042506e3e970ff", - "sha256:7bd527f36a605c914efca5d3d014170b2cb184723e423d26b1fb2fd9108e264d", - "sha256:7cb54db3535c8686ea12e9535eb087d32421184eacc6939ef15ef50f83a5e7e2", - "sha256:7f3a2d740291f7f2c111d86a1c4851b70fb000a6c8883a59660d95ad57b9df35", - "sha256:81304b7d8e9c824d058087dcb89144842c8e0dea6d281c031f59f0acf66963d4", - "sha256:933947e8b4fbe617a51528b09851685138b49d511af0b6c0da2539115d6d4514", - "sha256:94223d7f060301b3a8c09c9b3bc3294b56b2188e7d8179c762a1cda72c979252", - "sha256:ab3ca49afcb47058393b0122428358d2fbe0408cf99f1b58b295cfeb4ed39109", - "sha256:bd6292f565ca46dee4e737ebcc20742e3b5be2b01556dafe169f6c65d088875f", - "sha256:cb924aa3e4a3fb644d0c463cad5bc2572649a6a3f68a7f8e4fbe44aaa6d77e4c", - "sha256:d0fc7a286feac9077ec52a927fc9fe8fe2fabab95426722be4c953c9a8bede92", - "sha256:ddc34786490a6e4ec0a855d401034cbd1242ef186c20d79d2166d6a4bd449577", - "sha256:e34b155e36fa9da7e1b7c738ed7767fc9491a62ec6af70fe9da4a057759edc2d", - "sha256:e5b9e8f6bda48460b7b143c3821b21b452cb3a835e6bbd5dd33aa0c8d3f5137d", - "sha256:e81ebf6c5ee9684be8f2c87563880f93eedd56dd2b6146d8a725b50b7e5adb0f", - "sha256:eb91be369f945f10d3a49f5f9be8b3d0b93a4c2be8f8a5b83b0571b8123e0a7a", - "sha256:f460d1ceb0e4a5dcb2a652db0904224f367c9b3c1470d5a7683c0480e582468b" - ], - "version": "==1.3.1" - }, - "mccabe": { - "hashes": [ - "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", - "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" - ], - "version": "==0.6.1" - }, - "more-itertools": { - "hashes": [ - "sha256:2b6b9893337bfd9166bee6a62c2b0c9fe7735dcf85948b387ec8cba30e85d8e8", - "sha256:6703844a52d3588f951883005efcf555e49566a48afd4db4e965d69b883980d3", - "sha256:a18d870ef2ffca2b8463c0070ad17b5978056f403fb64e3f15fe62a52db21cc0" - ], - "version": "==4.2.0" - }, - "pluggy": { - "hashes": [ - "sha256:7f8ae7f5bdf75671a718d2daf0a64b7885f74510bcd98b1a0bb420eb9a9d0cff", - "sha256:d345c8fe681115900d6da8d048ba67c25df42973bda370783cd58826442dcd7c", - "sha256:e160a7fcf25762bb60efc7e171d4497ff1d8d2d75a3d0df7a21b76821ecbf5c5" - ], - "version": "==0.6.0" - }, - "py": { - "hashes": [ - "sha256:3fd59af7435864e1a243790d322d763925431213b6b8529c6ca71081ace3bbf7", - "sha256:e31fb2767eb657cbde86c454f02e99cb846d3cd9d61b318525140214fdc0e98e" - ], - "version": "==1.5.4" - }, - "pycodestyle": { - "hashes": [ - "sha256:682256a5b318149ca0d2a9185d365d8864a768a28db66a84a2ea946bcc426766", - "sha256:6c4245ade1edfad79c3446fadfc96b0de2759662dc29d07d80a6f27ad1ca6ba9" - ], - "version": "==2.3.1" - }, - "pyflakes": { - "hashes": [ - "sha256:08bd6a50edf8cffa9fa09a463063c425ecaaf10d1eb0335a7e8b1401aef89e6f", - "sha256:8d616a382f243dbf19b54743f280b80198be0bca3a5396f1d2e1fca6223e8805" - ], - "version": "==1.6.0" - }, - "pylint": { - "hashes": [ - "sha256:a48070545c12430cfc4e865bf62f5ad367784765681b3db442d8230f0960aa3c", - "sha256:fff220bcb996b4f7e2b0f6812fd81507b72ca4d8c4d05daf2655c333800cb9b3" - ], - "index": "pypi", - "version": "==1.9.2" - }, - "pytest": { - "hashes": [ - "sha256:0453c8676c2bee6feb0434748b068d5510273a916295fd61d306c4f22fbfd752", - "sha256:4b208614ae6d98195430ad6bde03641c78553acee7c83cec2e85d613c0cd383d" - ], - "index": "pypi", - "version": "==3.6.3" - }, - "six": { - "hashes": [ - "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9", - "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb" - ], - "version": "==1.11.0" - }, - "wrapt": { - "hashes": [ - "sha256:d4d560d479f2c21e1b5443bbd15fe7ec4b37fe7e53d335d3b9b0a7b1226fe3c6" - ], - "version": "==1.10.11" - } - } -} diff --git a/README.md b/README.md index e14bab46..d7ca0493 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -ranger 1.9.1 +ranger 1.9.2 ============ [![Build Status](https://travis-ci.org/ranger/ranger.svg?branch=master)](https://travis-ci.org/ranger/ranger) diff --git a/doc/howto-publish-a-release.md b/doc/howto-publish-a-release.md index e8d669ce..6bd70047 100644 --- a/doc/howto-publish-a-release.md +++ b/doc/howto-publish-a-release.md @@ -37,7 +37,7 @@ Make a release commit * [ ] Write changelog entry * [ ] Think of a witty commit message * [ ] Commit -* [ ] Tag the signed release with `git tag -a <commit-id>`, using the same +* [ ] Tag the signed release with `git tag -as vX.Y.Z`, using the same commit message as annotation * [ ] Push release and tag @@ -73,4 +73,4 @@ Announce the update Change back to before --------------------- -* [ ] Change `VERSION` in `ranger/__init__.py` back to `master` +* [ ] Change `__release__` in `ranger/__init__.py` back to `False` diff --git a/doc/ranger.1 b/doc/ranger.1 index ea010eac..5a159e12 100644 --- a/doc/ranger.1 +++ b/doc/ranger.1 @@ -133,7 +133,7 @@ .\" ======================================================================== .\" .IX Title "RANGER 1" -.TH RANGER 1 "ranger-1.9.1" "2018-07-15" "ranger manual" +.TH RANGER 1 "ranger-1.9.2" "2018-10-26" "ranger manual" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l @@ -299,6 +299,9 @@ This does not work over ssh, requires certain terminals (tested on \*(L"xterm\*( To enable this feature, install the program \*(L"w3m\*(R" and set the option \&\f(CW\*(C`preview_images_method\*(C'\fR to w3m. .PP +When using a terminal with a nonzero border which is not automatically detected, the w3m preview will be misaligned. +Use the \f(CW\*(C`w3m_offset\*(C'\fR option to manually adjust the image offset. This should be the same value as the terminal's border value. +.PP \fIiTerm2\fR .IX Subsection "iTerm2" .PP @@ -667,6 +670,9 @@ the current directory. For a more permanent solution, use the command .IP ".n" 14 .IX Item ".n" Apply a new filename filter. +.IP ".m" 14 +.IX Item ".m" +Apply a new mimetype filter. .IP ".d" 14 .IX Item ".d" Apply the typefilter \*(L"directory\*(R". @@ -840,9 +846,16 @@ Display the free disk space 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_borders [bool]" 4 -.IX Item "draw_borders [bool]" -Draw borders around columns? +.IP "draw_borders [string]" 4 +.IX Item "draw_borders [string]" +Draw borders around or between the columns? Possible values are: +.Sp +.Vb 4 +\& none no borders of any sort +\& outline draw an outline around all the columns +\& separators draw only vertical lines between columns +\& both both of the above +.Ve .IP "draw_progress_bar_in_status_bar [bool]" 4 .IX Item "draw_progress_bar_in_status_bar [bool]" Draw a progress bar in the status bar which displays the average state of all @@ -880,6 +893,9 @@ Otherwise the submaps are replaced with \*(L"...\*(R". .IP "hostname_in_titlebar [bool]" 4 .IX Item "hostname_in_titlebar [bool]" Show hostname in titlebar? +.IP "size_in_bytes [bool]" 4 +.IX Item "size_in_bytes [bool]" +Print file sizes in bytes instead of the default human-readable format. .IP "idle_delay [integer]" 4 .IX Item "idle_delay [integer]" The delay that ranger idly waits for user input, in milliseconds, with a @@ -1057,6 +1073,11 @@ traditional miller column view that shows multiple levels of the hierarchy, or .IX Item "w3m_delay [float]" Delay in seconds before displaying an image with the w3m method. Increase it in case of experiencing display corruption. +.IP "w3m_offset [int]" 4 +.IX Item "w3m_offset [int]" +Offset in pixels for the inner border of the terminal. Some terminals require +the offset to be specified explicitly, among others st and UXterm, some don't +like urxvt. .IP "wrap_scroll [bool]" 4 .IX Item "wrap_scroll [bool]" Enable scroll wrapping \- moving down while on the last item will wrap around to @@ -1463,6 +1484,9 @@ This can be used to re-evaluate the rc.conf file after changing it: .Vb 1 \& map X chain shell vim \-p %confdir/rc.conf %rangerdir/config/rc.conf; source %confdir/rc.conf .Ve +.IP "scroll_preview \fIvalue\fR" 2 +.IX Item "scroll_preview value" +Scroll the file preview by \fIvalue\fR lines. .IP "terminal" 2 .IX Item "terminal" Spawns the \fIx\-terminal-emulator\fR starting in the current directory. @@ -1641,7 +1665,7 @@ 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 +\& git clone git@github.com:ranger/ranger.git .Ve .SH "SEE ALSO" .IX Header "SEE ALSO" diff --git a/doc/ranger.pod b/doc/ranger.pod index 5c668235..8fbc8826 100644 --- a/doc/ranger.pod +++ b/doc/ranger.pod @@ -209,6 +209,9 @@ This does not work over ssh, requires certain terminals (tested on "xterm" and To enable this feature, install the program "w3m" and set the option C<preview_images_method> to w3m. +When using a terminal with a nonzero border which is not automatically detected, the w3m preview will be misaligned. +Use the C<w3m_offset> option to manually adjust the image offset. This should be the same value as the terminal's border value. + =head3 iTerm2 This only works in iTerm2 compiled with image preview support, but works over @@ -628,6 +631,10 @@ the current directory. For a more permanent solution, use the command Apply a new filename filter. +=item .m + +Apply a new mimetype filter. + =item .d Apply the typefilter "directory". @@ -852,10 +859,15 @@ Display the free disk space in the status bar? Display tags in all columns? -=item draw_borders [bool] +=item draw_borders [string] -Draw borders around columns? +Draw borders around or between the columns? Possible values are: + none no borders of any sort + outline draw an outline around all the columns + separators draw only vertical lines between columns + both both of the above + =item draw_progress_bar_in_status_bar [bool] Draw a progress bar in the status bar which displays the average state of all @@ -896,6 +908,10 @@ Otherwise the submaps are replaced with "...". Show hostname in titlebar? +=item size_in_bytes [bool] + +Print file sizes in bytes instead of the default human-readable format. + =item idle_delay [integer] The delay that ranger idly waits for user input, in milliseconds, with a @@ -1110,6 +1126,12 @@ B<multipane> to use multiple panes (one per tab) similar to midnight-commander. Delay in seconds before displaying an image with the w3m method. Increase it in case of experiencing display corruption. +=item w3m_offset [int] + +Offset in pixels for the inner border of the terminal. Some terminals require +the offset to be specified explicitly, among others st and UXterm, some don't +like urxvt. + =item wrap_scroll [bool] Enable scroll wrapping - moving down while on the last item will wrap around to @@ -1563,6 +1585,10 @@ This can be used to re-evaluate the rc.conf file after changing it: map X chain shell vim -p %confdir/rc.conf %rangerdir/config/rc.conf; source %confdir/rc.conf +=item scroll_preview I<value> + +Scroll the file preview by I<value> lines. + =item terminal Spawns the I<x-terminal-emulator> starting in the current directory. @@ -1803,7 +1829,7 @@ GNU General Public License 3 or (at your option) any later version. ranger is maintained with the git version control system. To fetch a fresh copy, run: - git clone git://git.savannah.nongnu.org/ranger.git + git clone git@github.com:ranger/ranger.git diff --git a/doc/rifle.1 b/doc/rifle.1 index c8c679ec..9ed1a145 100644 --- a/doc/rifle.1 +++ b/doc/rifle.1 @@ -1,4 +1,4 @@ -.\" Automatically generated by Pod::Man 4.09 (Pod::Simple 3.35) +.\" Automatically generated by Pod::Man 4.10 (Pod::Simple 3.35) .\" .\" Standard preamble: .\" ======================================================================== @@ -54,16 +54,20 @@ .\" Avoid warning from groff about undefined register 'F'. .de IX .. -.if !\nF .nr F 0 -.if \nF>0 \{\ -. de IX -. tm Index:\\$1\t\\n%\t"\\$2" +.nr rF 0 +.if \n(.g .if rF .nr rF 1 +.if (\n(rF:(\n(.g==0)) \{\ +. if \nF \{\ +. de IX +. tm Index:\\$1\t\\n%\t"\\$2" .. -. if !\nF==2 \{\ -. nr % 0 -. nr F 2 +. if !\nF==2 \{\ +. nr % 0 +. nr F 2 +. \} . \} .\} +.rr rF .\" .\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2). .\" Fear. Run. Save yourself. No user-serviceable parts. @@ -129,7 +133,7 @@ .\" ======================================================================== .\" .IX Title "RIFLE 1" -.TH RIFLE 1 "rifle-1.9.1" "2018-06-07" "rifle manual" +.TH RIFLE 1 "rifle-1.9.2" "2018-09-09" "rifle manual" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l diff --git a/examples/plugin_pmount_dynamic.py b/examples/plugin_pmount_dynamic.py new file mode 100644 index 00000000..1448e15b --- /dev/null +++ b/examples/plugin_pmount_dynamic.py @@ -0,0 +1,70 @@ +# Tested with ranger 1.7.2 +# +# This plugin creates a bunch of keybindings used to mount and unmount +# the devices using pmount(1). +# +# (multiple partitions): alt+m <letter> <digit> : mount /dev/sd<letter><digit> +# (one partition): alt+m <letter> : mount /dev/sd<letter>1 +# (no partitions): alt+m <letter> : mount /dev/sd<letter> +# +# (multiple partitions): alt+M <letter> <digit> : unmount /dev/sd<letter><digit> +# (one partition): alt+M <letter> : unmount /dev/sd<letter>1 +# (no partitions): alt+M <letter> : unmount /dev/sd<letter> +# +# alt+n : list the devices + +from __future__ import (absolute_import, division, print_function) + +import subprocess +import ranger.api + +MOUNT_KEY = '<alt>m' +UMOUNT_KEY = '<alt>M' +LIST_MOUNTS_KEY = '<alt>n' +HOOK_INIT_OLD = ranger.api.hook_init + + +def hook_init(fm): + fm.execute_console("map {key} shell -p lsblk".format(key=LIST_MOUNTS_KEY)) + + diskcmd = "lsblk -lno NAME | awk '!/[1-9]/ {sub(/sd/, \"\"); print}'" + disks = subprocess.check_output( + diskcmd, shell=True).decode('utf-8').replace('\r', '').replace('\n', '') + + for disk in disks: + partcmd = "lsblk -lno NAME /dev/sd{0} | sed 's/sd{0}//' | tail -n 1".format(disk) + + try: + numparts = int(subprocess.check_output( + partcmd, shell=True).decode('utf-8').replace('\r', '').replace('\n', '')) + except ValueError: + numparts = 0 + + if numparts == 0: + # no partition, mount the whole device + fm.execute_console("map {key}{0} chain shell pmount sd{0}; cd /media/sd{0}".format( + disk, key=MOUNT_KEY)) + fm.execute_console("map {key}{0} chain cd; chain shell pumount sd{0}".format( + disk, key=UMOUNT_KEY)) + + elif numparts == 1: + # only one partition, mount the partition + fm.execute_console( + "map {key}{0} chain shell pmount sd{0}1; cd /media/sd{0}1".format( + disk, key=MOUNT_KEY)) + fm.execute_console("map {key}{0} chain cd; shell pumount sd{0}1".format( + disk, key=UMOUNT_KEY)) + + else: + # use range start 1, /dev/sd{device}0 doesn't exist + for part in range(1, numparts + 1): + fm.execute_console( + "map {key}{0}{1} chain shell pmount sd{0}{1}; cd /media/sd{0}{1}".format( + disk, part, key=MOUNT_KEY)) + fm.execute_console("map {key}{0}{1} chain cd; shell pumount sd{0}{1}".format( + disk, part, key=UMOUNT_KEY)) + + return HOOK_INIT_OLD(fm) + + +ranger.api.hook_init = hook_init diff --git a/ranger/__init__.py b/ranger/__init__.py index ae1ecc48..2228d40e 100644 --- a/ranger/__init__.py +++ b/ranger/__init__.py @@ -32,7 +32,7 @@ def version_helper(): # Information __license__ = 'GPL3' -__version__ = '1.9.1' +__version__ = '1.9.2' __release__ = False __author__ = __maintainer__ = 'Roman Zimbelmann' __email__ = 'hut@hut.pm' diff --git a/ranger/colorschemes/default.py b/ranger/colorschemes/default.py index 350c8359..a9e01e0c 100644 --- a/ranger/colorschemes/default.py +++ b/ranger/colorschemes/default.py @@ -6,7 +6,7 @@ from __future__ import (absolute_import, division, print_function) from ranger.gui.colorscheme import ColorScheme from ranger.gui.color import ( black, blue, cyan, green, magenta, red, white, yellow, default, - normal, bold, reverse, + normal, bold, reverse, dim, BRIGHT, default_colors, ) @@ -39,18 +39,22 @@ class Default(ColorScheme): if context.directory: attr |= bold fg = blue + fg += BRIGHT elif context.executable and not \ any((context.media, context.container, context.fifo, context.socket)): attr |= bold fg = green + fg += BRIGHT if context.socket: - fg = magenta attr |= bold + fg = magenta + fg += BRIGHT if context.fifo or context.device: fg = yellow if context.device: attr |= bold + fg += BRIGHT if context.link: fg = cyan if context.good else magenta if context.tag_marker and not context.selected: @@ -59,10 +63,19 @@ class Default(ColorScheme): fg = white else: fg = red + fg += BRIGHT if not context.selected and (context.cut or context.copied): - fg = black attr |= bold + fg = black + fg += BRIGHT + # If the terminal doesn't support bright colors, use dim white + # instead of black. + if BRIGHT == 0: + attr |= dim + fg = white if context.main_column: + # Doubling up with BRIGHT here causes issues because it's + # additive not idempotent. if context.selected: attr |= bold if context.marked: @@ -78,7 +91,6 @@ class Default(ColorScheme): fg = cyan elif context.in_titlebar: - attr |= bold if context.hostname: fg = red if context.bad else green elif context.directory: @@ -88,6 +100,8 @@ class Default(ColorScheme): bg = green elif context.link: fg = cyan + attr |= bold + fg += BRIGHT elif context.in_statusbar: if context.permissions: @@ -98,13 +112,16 @@ class Default(ColorScheme): if context.marked: attr |= bold | reverse fg = yellow + fg += BRIGHT if context.frozen: attr |= bold | reverse fg = cyan + fg += BRIGHT if context.message: if context.bad: attr |= bold fg = red + fg += BRIGHT if context.loaded: bg = self.progress_bar_color if context.vcsinfo: diff --git a/ranger/colorschemes/snow.py b/ranger/colorschemes/snow.py index 8e9686a8..c5f23c1c 100644 --- a/ranger/colorschemes/snow.py +++ b/ranger/colorschemes/snow.py @@ -4,7 +4,7 @@ from __future__ import (absolute_import, division, print_function) from ranger.gui.colorscheme import ColorScheme -from ranger.gui.color import default_colors, reverse, bold +from ranger.gui.color import default_colors, reverse, bold, BRIGHT class Snow(ColorScheme): @@ -20,6 +20,7 @@ class Snow(ColorScheme): attr = reverse if context.directory: attr |= bold + fg += BRIGHT elif context.highlight: attr |= reverse @@ -36,6 +37,7 @@ class Snow(ColorScheme): elif context.in_taskview: if context.selected: attr |= bold + fg += BRIGHT if context.loaded: attr |= reverse diff --git a/ranger/config/rc.conf b/ranger/config/rc.conf index 9c1a6a83..98a059d4 100644 --- a/ranger/config/rc.conf +++ b/ranger/config/rc.conf @@ -106,12 +106,20 @@ set preview_images false # while slower, this allows remote previews, # for example during an ssh session. # Tmux is unsupported. +# +# * ueberzug: +# Preview images in full color with the external command "ueberzug". +# Images are shown by using a child window. +# Only for users who run X11 in GNU/Linux. set preview_images_method w3m # Delay in seconds before displaying an image with the w3m method. # Increase it in case of experiencing display corruption. set w3m_delay 0.02 +# Manually adjust the w3mimg offset when using a terminal which needs this +set w3m_offset 0 + # Default iTerm2 font size (see: preview_images_method: iterm2) set iterm2_font_width 8 set iterm2_font_height 11 @@ -146,8 +154,11 @@ set status_bar_on_top false # currently running tasks which support progress bars? set draw_progress_bar_in_status_bar true -# Draw borders around columns? -set draw_borders false +# Draw borders around columns? (separators, outline, both, or none) +# Separators are vertical lines between columns. +# Outline draws a box around all the columns. +# Both combines the two. +set draw_borders none # Display the directory name in tabs? set dirname_in_tabs false @@ -287,6 +298,9 @@ set global_inode_type_filter # should be 'false' during start-up, but you can toggle it by pressing F. set freeze_files false +# Print file sizes in bytes instead of the default human-readable format. +set size_in_bytes false + # =================================================================== # == Local Options # =================================================================== @@ -334,6 +348,8 @@ map <esc> change_mode normal map ~ set viewmode! map i display_file +map <A-j> scroll_preview 1 +map <A-k> scroll_preview -1 map ? help map W display_log map w taskview_open @@ -517,6 +533,8 @@ map <a-6> tab_open 6 map <a-7> tab_open 7 map <a-8> tab_open 8 map <a-9> tab_open 9 +map <a-r> tab_shift 1 +map <a-l> tab_shift -1 # Sorting map or set sort_reverse! @@ -561,6 +579,7 @@ copymap zf zz # Filter stack map .n console filter_stack add name%space +map .m console filter_stack add mime%space map .d filter_stack add type d map .f filter_stack add type f map .l filter_stack add type l diff --git a/ranger/config/rifle.conf b/ranger/config/rifle.conf index 5a87c1a5..babdcda7 100644 --- a/ranger/config/rifle.conf +++ b/ranger/config/rifle.conf @@ -212,6 +212,34 @@ ext rar, has unrar = unrar l "$1" | less ext rar, has unrar = for file in "$@"; do unrar x "$file"; done #------------------------------------------- +# Flag t fallback terminals +#------------------------------------------- +# Rarely installed terminal emulators get higher priority; It is assumed that +# if you install a rare terminal emulator, you probably use it. +# gnome-terminal/konsole/xterm on the other hand are often installed as part of +# a desktop environment or as fallback terminal emulators. +mime ^ranger/x-terminal-emulator, has terminology = terminology -e "$@" +mime ^ranger/x-terminal-emulator, has kitty = kitty -- "$@" +mime ^ranger/x-terminal-emulator, has alacritty = alacritty -e "$@" +mime ^ranger/x-terminal-emulator, has sakura = sakura -e "$@" +mime ^ranger/x-terminal-emulator, has lilyterm = lilyterm -e "$@" +#mime ^ranger/x-terminal-emulator, has cool-retro-term = cool-retro-term -e "$@" +mime ^ranger/x-terminal-emulator, has termite = termite -x '"$@"' +#mime ^ranger/x-terminal-emulator, has yakuake = yakuake -e "$@" +mime ^ranger/x-terminal-emulator, has guake = guake -ne "$@" +mime ^ranger/x-terminal-emulator, has tilda = tilda -c "$@" +mime ^ranger/x-terminal-emulator, has st = st -e "$@" +mime ^ranger/x-terminal-emulator, has terminator = terminator -x "$@" +mime ^ranger/x-terminal-emulator, has urxvt = urxvt -e "$@" +mime ^ranger/x-terminal-emulator, has pantheon-terminal = pantheon-terminal -e "$@" +mime ^ranger/x-terminal-emulator, has lxterminal = lxterminal -e "$@" +mime ^ranger/x-terminal-emulator, has mate-terminal = mate-terminal -x "$@" +mime ^ranger/x-terminal-emulator, has xfce4-terminal = xfce4-terminal -x "$@" +mime ^ranger/x-terminal-emulator, has konsole = konsole -e "$@" +mime ^ranger/x-terminal-emulator, has gnome-terminal = gnome-terminal -- "$@" +mime ^ranger/x-terminal-emulator, has xterm = xterm -e "$@" + +#------------------------------------------- # Misc #------------------------------------------- label wallpaper, number 11, mime ^image, has feh, X = feh --bg-scale "$1" diff --git a/ranger/container/bookmarks.py b/ranger/container/bookmarks.py index 59838c00..cbfc541a 100644 --- a/ranger/container/bookmarks.py +++ b/ranger/container/bookmarks.py @@ -185,7 +185,13 @@ class Bookmarks(FileManagerAware): old_perms = os.stat(self.path) os.chown(path_new, old_perms.st_uid, old_perms.st_gid) os.chmod(path_new, old_perms.st_mode) - os.rename(path_new, self.path) + + if os.path.islink(self.path): + target_path = os.path.realpath(self.path) + os.rename(path_new, target_path) + else: + os.rename(path_new, self.path) + except OSError as ex: self.fm.notify('Bookmarks error: {0}'.format(str(ex)), bad=True) return diff --git a/ranger/container/settings.py b/ranger/container/settings.py index 94e455a5..478f6124 100644 --- a/ranger/container/settings.py +++ b/ranger/container/settings.py @@ -41,7 +41,7 @@ ALLOWED_SETTINGS = { 'display_size_in_status_bar': bool, "display_free_space_in_status_bar": bool, 'display_tags_in_all_columns': bool, - 'draw_borders': bool, + 'draw_borders': str, 'draw_progress_bar_in_status_bar': bool, 'flushinput': bool, 'freeze_files': bool, @@ -49,6 +49,7 @@ ALLOWED_SETTINGS = { 'hidden_filter': str, 'hint_collapse_threshold': int, 'hostname_in_titlebar': bool, + 'size_in_bytes': bool, 'idle_delay': int, 'iterm2_font_width': int, 'iterm2_font_height': int, @@ -94,6 +95,7 @@ ALLOWED_SETTINGS = { 'vcs_backend_svn': str, 'viewmode': str, 'w3m_delay': float, + 'w3m_offset': int, 'wrap_scroll': bool, 'xterm_alt_key': bool, } @@ -101,10 +103,12 @@ ALLOWED_SETTINGS = { ALLOWED_VALUES = { 'cd_tab_case': ['sensitive', 'insensitive', 'smart'], 'confirm_on_delete': ['multiple', 'always', 'never'], + 'draw_borders': ['none', 'both', 'outline', 'separators'], 'line_numbers': ['false', 'absolute', 'relative'], 'one_indexed': [False, True], 'preview_images_method': ['w3m', 'iterm2', 'terminology', - 'urxvt', 'urxvt-full', 'kitty'], + 'urxvt', 'urxvt-full', 'kitty', + 'ueberzug'], 'vcs_backend_bzr': ['disabled', 'local', 'enabled'], 'vcs_backend_git': ['enabled', 'disabled', 'local'], 'vcs_backend_hg': ['disabled', 'local', 'enabled'], diff --git a/ranger/core/actions.py b/ranger/core/actions.py index c2726015..6f4b76d0 100644 --- a/ranger/core/actions.py +++ b/ranger/core/actions.py @@ -211,7 +211,7 @@ class Actions( # pylint: disable=too-many-instance-attributes,too-many-public-m self.ui.redraw_main_column() def redraw_window(self): - """:redraw + """:redraw_window Redraw the window. """ @@ -420,7 +420,7 @@ class Actions( # pylint: disable=too-many-instance-attributes,too-many-public-m are multiple choices label: a string to select an opening method by its label flags: a string specifying additional options, see `man rifle` - mimetyle: pass the mimetype to rifle, overriding its own guess + mimetype: pass the mimetype to rifle, overriding its own guess """ mode = kw['mode'] if 'mode' in kw else 0 @@ -502,7 +502,10 @@ class Actions( # pylint: disable=too-many-instance-attributes,too-many-public-m if narg is not None: mode = narg tfile = self.thisfile - selection = self.thistab.get_selection() + if kw.get('selection', True): + selection = self.thistab.get_selection() + else: + selection = [tfile] if tfile.is_directory: self.thistab.enter_dir(tfile) elif selection: @@ -990,6 +993,17 @@ class Actions( # pylint: disable=too-many-instance-attributes,too-many-public-m else: pager.set_source(fobj) + def scroll_preview(self, lines, narg=None): + """:scroll_preview <lines> + + Scroll the file preview by <lines> lines. + """ + preview_column = self.ui.browser.columns[-1] + if preview_column.target and preview_column.target.is_file: + if narg is not None: + lines = narg + preview_column.scrollbit(lines) + # -------------------------- # -- Previews # -------------------------- @@ -1151,31 +1165,34 @@ class Actions( # pylint: disable=too-many-instance-attributes,too-many-public-m @staticmethod def read_text_file(path, count=None): """Encoding-aware reading of a text file.""" + # Guess encoding ourselves. + # These should be the most frequently used ones. + # latin-1 as the last resort + encodings = [('utf-8', 'strict'), ('utf-16', 'strict'), + ('latin-1', 'replace')] + + with open(path, 'rb') as fobj: + data = fobj.read(count) + try: import chardet except ImportError: - # Guess encoding ourselves. These should be the most frequently used ones. - encodings = ('utf-8', 'utf-16') - for encoding in encodings: - try: - with codecs.open(path, 'r', encoding=encoding) as fobj: - text = fobj.read(count) - except UnicodeDecodeError: - pass - else: - LOG.debug("guessed encoding of '%s' as %r", path, encoding) - return text + pass else: - with open(path, 'rb') as fobj: - data = fobj.read(count) result = chardet.detect(data) - LOG.debug("chardet guess for '%s': %s", path, result) guessed_encoding = result['encoding'] - return codecs.decode(data, guessed_encoding, 'replace') + if guessed_encoding is not None: + # Add chardet's guess before our own. + encodings.insert(0, (guessed_encoding, 'replace')) - # latin-1 as the last resort - with codecs.open(path, 'r', encoding='latin-1', errors='replace') as fobj: - return fobj.read(count) + for (encoding, error_scheme) in encodings: + try: + text = codecs.decode(data, encoding, error_scheme) + except UnicodeDecodeError: + pass + else: + LOG.debug("Guessed encoding of '%s' as %s", path, encoding) + return text # -------------------------- # -- Tabs @@ -1257,6 +1274,53 @@ class Actions( # pylint: disable=too-many-instance-attributes,too-many-public-m i += 1 return self.tab_open(i, path) + def tab_shift(self, offset=0, to=None): # pylint: disable=invalid-name + """Shift the tab left/right + + Shift the current tab to the left or right by either: + offset - changes the tab number by offset + to - shifts the tab to the specified tab number + """ + + oldtab_index = self.current_tab + if to is None: + assert isinstance(offset, int) + # enumerated index (1 to 9) + newtab_index = oldtab_index + offset + else: + assert isinstance(to, int) + newtab_index = to + # shift tabs without enumerating, preserve tab numbers when can + if newtab_index != oldtab_index: + # the other tabs shift in the opposite direction + if (newtab_index - oldtab_index) > 0: + direction = -1 + else: + direction = 1 + + def tabshiftreorder(source_index): + # shift the tabs to make source_index empty + if source_index in self.tabs: + target_index = source_index + direction + # make the target_index empty recursively + tabshiftreorder(target_index) + # shift the source to target + source_tab = self.tabs[source_index] + self.tabs[target_index] = source_tab + del self.tabs[source_index] + + # first remove the current tab from the dict + oldtab = self.tabs[oldtab_index] + del self.tabs[oldtab_index] + # make newtab_index empty by shifting + tabshiftreorder(newtab_index) + self.tabs[newtab_index] = oldtab + self.current_tab = newtab_index + self.thistab = oldtab + self.ui.titlebar.request_redraw() + self.signal_emit('tab.layoutchange') + return None + def tab_switch(self, path, create_directory=False): """Switches to tab of given path, opening a new tab as necessary. diff --git a/ranger/core/filter_stack.py b/ranger/core/filter_stack.py index ff9f4080..2ca2b1c5 100644 --- a/ranger/core/filter_stack.py +++ b/ranger/core/filter_stack.py @@ -6,6 +6,7 @@ from __future__ import (absolute_import, division, print_function) import re +import mimetypes from ranger.container.directory import accept_file, InodeFilterConstants @@ -48,6 +49,22 @@ class NameFilter(BaseFilter): return "<Filter: name =~ /{}/>".format(self.pattern) +@stack_filter("mime") +class MimeFilter(BaseFilter): + def __init__(self, pattern): + self.pattern = pattern + self.regex = re.compile(pattern) + + def __call__(self, fobj): + mimetype, _ = mimetypes.guess_type(fobj.relative_path) + if mimetype is None: + return False + return self.regex.search(mimetype) + + def __str__(self): + return "<Filter: mimetype =~ /{}/>".format(self.pattern) + + @stack_filter("type") class TypeFilter(BaseFilter): type_to_function = { diff --git a/ranger/core/fm.py b/ranger/core/fm.py index 61b3cb11..43001e8b 100644 --- a/ranger/core/fm.py +++ b/ranger/core/fm.py @@ -25,7 +25,7 @@ from ranger.core.runner import Runner from ranger.ext.img_display import (W3MImageDisplayer, ITerm2ImageDisplayer, TerminologyImageDisplayer, URXVTImageDisplayer, URXVTImageFSDisplayer, - KittyImageDisplayer, + KittyImageDisplayer, UeberzugImageDisplayer, ImageDisplayer) from ranger.core.metadata import MetadataManager from ranger.ext.rifle import Rifle @@ -102,6 +102,8 @@ class FM(Actions, # pylint: disable=too-many-instance-attributes self.rifle.reload_config() def set_image_displayer(): + if self.image_displayer: + self.image_displayer.quit() self.image_displayer = self._get_image_displayer() set_image_displayer() self.settings.signal_bind('setopt.preview_images_method', set_image_displayer, @@ -238,6 +240,8 @@ class FM(Actions, # pylint: disable=too-many-instance-attributes return URXVTImageFSDisplayer() elif self.settings.preview_images_method == "kitty": return KittyImageDisplayer() + elif self.settings.preview_images_method == "ueberzug": + return UeberzugImageDisplayer() return ImageDisplayer() def _get_thisfile(self): diff --git a/ranger/core/loader.py b/ranger/core/loader.py index 274dc610..9f32535f 100644 --- a/ranger/core/loader.py +++ b/ranger/core/loader.py @@ -194,6 +194,7 @@ class CommandLoader( # pylint: disable=too-many-instance-attributes selectlist.append(process.stdout) if not self.silent: selectlist.append(process.stderr) + read_stdout = None while process.poll() is None: yield if self.finished: @@ -210,10 +211,11 @@ class CommandLoader( # pylint: disable=too-many-instance-attributes self.fm.notify(read, bad=True) elif robjs == process.stdout: read = robjs.read(512) - if py3: - read = safe_decode(read) if read: - self.stdout_buffer += read + if read_stdout is None: + read_stdout = read + else: + read_stdout += read except select.error: sleep(0.03) if not self.silent: @@ -223,9 +225,12 @@ class CommandLoader( # pylint: disable=too-many-instance-attributes self.fm.notify(line, bad=True) if self.read: read = process.stdout.read() + if read: + read_stdout += read + if read_stdout: if py3: - read = safe_decode(read) - self.stdout_buffer += read + read_stdout = safe_decode(read_stdout) + self.stdout_buffer += read_stdout self.finished = True self.signal_emit('after', process=process, loader=self) diff --git a/ranger/data/scope.sh b/ranger/data/scope.sh index 25251533..8e0a0f6d 100755 --- a/ranger/data/scope.sh +++ b/ranger/data/scope.sh @@ -83,6 +83,11 @@ handle_extension() { lynx -dump -- "${FILE_PATH}" && exit 5 elinks -dump "${FILE_PATH}" && exit 5 ;; # Continue with next handler on failure + # JSON + json) + jq --color-output . "${FILE_PATH}" && exit 5 + python -m json.tool -- "${FILE_PATH}" && exit 5 + ;; esac } @@ -123,6 +128,43 @@ handle_image() { # -jpeg -tiffcompression jpeg \ # -- "${FILE_PATH}" "${IMAGE_CACHE_PATH%.*}" \ # && exit 6 || exit 1;; + + # Preview archives using the first image inside. + # (Very useful for comic book collections for example.) + # application/zip|application/x-rar|application/x-7z-compressed|\ + # application/x-xz|application/x-bzip2|application/x-gzip|application/x-tar) + # local fn=""; local fe="" + # local zip=""; local rar=""; local tar=""; local bsd="" + # case "${mimetype}" in + # application/zip) zip=1 ;; + # application/x-rar) rar=1 ;; + # application/x-7z-compressed) ;; + # *) tar=1 ;; + # esac + # { [ "$tar" ] && fn=$(tar --list --file "${FILE_PATH}"); } || \ + # { fn=$(bsdtar --list --file "${FILE_PATH}") && bsd=1 && tar=""; } || \ + # { [ "$rar" ] && fn=$(unrar lb -p- -- "${FILE_PATH}"); } || \ + # { [ "$zip" ] && fn=$(zipinfo -1 -- "${FILE_PATH}"); } || return + # + # fn=$(echo "$fn" | python -c "import sys; import mimetypes as m; \ + # [ print(l, end='') for l in sys.stdin if \ + # (m.guess_type(l[:-1])[0] or '').startswith('image/') ]" |\ + # sort -V | head -n 1) + # [ "$fn" = "" ] && return + # [ "$bsd" ] && fn=$(printf '%b' "$fn") + # + # [ "$tar" ] && tar --extract --to-stdout \ + # --file "${FILE_PATH}" -- "$fn" > "${IMAGE_CACHE_PATH}" && exit 6 + # fe=$(echo -n "$fn" | sed 's/[][*?\]/\\\0/g') + # [ "$bsd" ] && bsdtar --extract --to-stdout \ + # --file "${FILE_PATH}" -- "$fe" > "${IMAGE_CACHE_PATH}" && exit 6 + # [ "$bsd" ] || [ "$tar" ] && rm -- "${IMAGE_CACHE_PATH}" + # [ "$rar" ] && unrar p -p- -inul -- "${FILE_PATH}" "$fn" > \ + # "${IMAGE_CACHE_PATH}" && exit 6 + # [ "$zip" ] && unzip -pP "" -- "${FILE_PATH}" "$fe" > \ + # "${IMAGE_CACHE_PATH}" && exit 6 + # [ "$rar" ] || [ "$zip" ] && rm -- "${IMAGE_CACHE_PATH}" + # ;; esac } diff --git a/ranger/ext/human_readable.py b/ranger/ext/human_readable.py index f365e594..385e56f4 100644 --- a/ranger/ext/human_readable.py +++ b/ranger/ext/human_readable.py @@ -3,6 +3,8 @@ from __future__ import (absolute_import, division, print_function) +from ranger.core.shared import SettingsAware + def human_readable(byte, separator=' '): # pylint: disable=too-many-return-statements """Convert a large number of bytes to an easily readable format. @@ -19,6 +21,9 @@ def human_readable(byte, separator=' '): # pylint: disable=too-many-return-stat if byte is None: return '' + if SettingsAware.settings.size_in_bytes: + return format(byte, 'n') # 'n' = locale-aware separator. + # I know this can be written much shorter, but this long version # performs much better than what I had before. If you attempt to # shorten this code, take performance into consideration. diff --git a/ranger/ext/img_display.py b/ranger/ext/img_display.py index f78e170b..9c84ce5e 100644 --- a/ranger/ext/img_display.py +++ b/ranger/ext/img_display.py @@ -20,6 +20,8 @@ import os import struct import sys import warnings +import json +import threading from subprocess import Popen, PIPE import termios @@ -219,9 +221,12 @@ class W3MImageDisplayer(ImageDisplayer, FileManagerAware): width = (width * max_height_pixels) // height height = max_height_pixels + start_x = int((start_x - 0.2) * fontw) + self.fm.settings.w3m_offset + start_y = (start_y * fonth) + self.fm.settings.w3m_offset + return "0;1;{x};{y};{w};{h};;;;;{filename}\n4;\n3;\n".format( - x=int((start_x - 0.2) * fontw), - y=start_y * fonth, + x=start_x, + y=start_y, # y = (start_y + 1) * fonth, # (for tmux top status bar) w=width, h=height, @@ -251,6 +256,9 @@ class ITerm2ImageDisplayer(ImageDisplayer, FileManagerAware): self.fm.ui.win.redrawwin() self.fm.ui.win.refresh() + def quit(self): + self.clear(0, 0, 0, 0) + def _generate_iterm2_input(self, path, max_cols, max_rows): """Prepare the image content of path for image display in iTerm2""" image_width, image_height = self._get_image_dimensions(path) @@ -376,6 +384,9 @@ class TerminologyImageDisplayer(ImageDisplayer, FileManagerAware): self.fm.ui.win.redrawwin() self.fm.ui.win.refresh() + def quit(self): + self.clear(0, 0, 0, 0) + class URXVTImageDisplayer(ImageDisplayer, FileManagerAware): """Implementation of ImageDisplayer working by setting the urxvt @@ -588,14 +599,14 @@ class KittyImageDisplayer(ImageDisplayer): image = image.resize((int(scale * image.width), int(scale * image.height)), self.backend.LANCZOS) + if image.mode != 'RGB' and image.mode != 'RGBA': + image = image.convert('RGB') # start_x += ((box[0] - image.width) // 2) // self.pix_row # start_y += ((box[1] - image.height) // 2) // self.pix_col if self.stream: # encode the whole image as base64 # TODO: implement z compression # to possibly increase resolution in sent image - if image.mode != 'RGB' and image.mode != 'RGBA': - image = image.convert('RGB') # t: transmissium medium, 'd' for embedded # f: size of a pixel fragment (8bytes per color) # s, v: size of the image to recompose the flattened data @@ -664,3 +675,55 @@ class KittyImageDisplayer(ImageDisplayer): # os.remove(self.temp_paths[k]) # except (OSError, IOError): # continue + + +class UeberzugImageDisplayer(ImageDisplayer): + """Implementation of ImageDisplayer using ueberzug. + Ueberzug can display images in a Xorg session. + Does not work over ssh. + """ + IMAGE_ID = 'preview' + is_initialized = False + + def __init__(self): + self.process = None + + def initialize(self): + """start ueberzug""" + if (self.is_initialized and self.process.poll() is None + and not self.process.stdin.closed): + return + + self.process = Popen(['ueberzug', 'layer', '--silent'], + stdin=PIPE, universal_newlines=True) + self.is_initialized = True + + def _execute(self, **kwargs): + self.initialize() + self.process.stdin.write(json.dumps(kwargs) + '\n') + self.process.stdin.flush() + + def draw(self, path, start_x, start_y, width, height): + self._execute( + action='add', + identifier=self.IMAGE_ID, + x=start_x, + y=start_y, + max_width=width, + max_height=height, + path=path + ) + + def clear(self, start_x, start_y, width, height): + if self.process and not self.process.stdin.closed: + self._execute(action='remove', identifier=self.IMAGE_ID) + + def quit(self): + if self.is_initialized and self.process.poll() is None: + timer_kill = threading.Timer(1, self.process.kill, []) + try: + self.process.terminate() + timer_kill.start() + self.process.communicate() + finally: + timer_kill.cancel() diff --git a/ranger/ext/rifle.py b/ranger/ext/rifle.py index 07a76488..377f9b8a 100755 --- a/ranger/ext/rifle.py +++ b/ranger/ext/rifle.py @@ -21,7 +21,7 @@ import re from subprocess import Popen, PIPE import sys -__version__ = 'rifle 1.9.1' +__version__ = 'rifle 1.9.2' # Options and constants that a user might want to change: DEFAULT_PAGER = 'less' @@ -162,9 +162,7 @@ class Rifle(object): # pylint: disable=too-many-instance-attributes config_file = self.config_file fobj = open(config_file, 'r') self.rules = [] - lineno = 0 for line in fobj: - lineno += 1 line = line.strip() if line.startswith('#') or line == '': continue @@ -363,23 +361,74 @@ class Rifle(object): # pylint: disable=too-many-instance-attributes cmd = prefix + [command] if 't' in flags: - if 'TERMCMD' not in os.environ: - term = os.environ['TERM'] - if term.startswith('rxvt-unicode'): + term = os.environ.get('TERMCMD', os.environ['TERM']) + + # Handle aliases of xterm and urxvt, rxvt and st and + # termite + # Match 'xterm', 'xterm-256color' + if term in ['xterm', 'xterm-256color']: + term = 'xterm' + if term in ['xterm-kitty']: + term = 'kitty' + if term in ['xterm-termite']: + term = 'termite' + if term in ['st', 'st-256color']: + term = 'st' + if term in ['urxvt', 'rxvt-unicode', + 'rxvt-unicode-256color']: + term = 'urxvt' + if term in ['rxvt', 'rxvt-256color']: + if 'rxvt' in get_executables(): + term = 'rxvt' + else: term = 'urxvt' - elif term.startswith('rxvt-'): - # Sometimes urxvt calls itself "rxvt-256color" - if 'rxvt' in get_executables(): - term = 'rxvt' - else: - term = 'urxvt' - if term not in get_executables(): - self.hook_logger("Can not determine terminal command. " - "Please set $TERMCMD manually.") - # A fallback terminal that is likely installed: - term = 'xterm' - os.environ['TERMCMD'] = term - cmd = [os.environ['TERMCMD'], '-e'] + cmd + + if term not in get_executables(): + self.hook_logger("Can not determine terminal command, " + "using rifle to determine fallback. " + "Please set $TERMCMD manually or " + "change fallbacks in rifle.conf.") + self._mimetype = 'ranger/x-terminal-emulator' + self.execute( + files=[command.split(';')[1].split('--')[0].strip()] + + files, flags='f', + mimetype='ranger/x-terminal-emulator') + return None + + # Choose correct cmdflag accordingly + if term in ['xfce4-terminal', 'mate-terminal', + 'terminator']: + cmdflag = '-x' + elif term in ['xterm', 'urxvt', 'rxvt', 'lxterminal', + 'konsole', 'lilyterm', 'cool-retro-term', + 'terminology', 'pantheon-terminal', 'termite', + 'st', 'stterm']: + cmdflag = '-e' + elif term in ['gnome-terminal', 'kitty']: + cmdflag = '--' + elif term in ['tilda', ]: + cmdflag = '-c' + else: + cmdflag = '-e' + + os.environ['TERMCMD'] = term + + # These terms don't work with the '/bin/sh set --' scheme. + # A temporary fix. + if term in ['tilda', 'pantheon-terminal', 'terminology', + 'termite']: + + target = command.split(';')[0].split('--')[1].strip() + app = command.split(';')[1].split('--')[0].strip() + cmd = [os.environ['TERMCMD'], cmdflag, '%s %s' + % (app, target)] + elif term in ['guake']: + cmd = [os.environ['TERMCMD'], '-n', '${PWD}', cmdflag] + cmd + else: + cmd = [os.environ['TERMCMD'], cmdflag] + cmd + + # self.hook_logger('cmd: %s' %cmd) + if 'f' in flags or 't' in flags: Popen_forked(cmd, env=self.hook_environment(os.environ)) else: diff --git a/ranger/gui/color.py b/ranger/gui/color.py index 45f983e8..8f6439c7 100644 --- a/ranger/gui/color.py +++ b/ranger/gui/color.py @@ -66,6 +66,11 @@ blink = curses.A_BLINK reverse = curses.A_REVERSE underline = curses.A_UNDERLINE invisible = curses.A_INVIS +dim = curses.A_DIM default_colors = (default, default, normal) # pylint: enable=invalid-name,bad-whitespace + +curses.setupterm() +# Adding BRIGHT to a color achieves what `bold` was used for. +BRIGHT = 8 if curses.tigetnum('colors') >= 16 else 0 diff --git a/ranger/gui/ui.py b/ranger/gui/ui.py index 441e9032..9eab4c87 100644 --- a/ranger/gui/ui.py +++ b/ranger/gui/ui.py @@ -136,6 +136,9 @@ class UI( # pylint: disable=too-many-instance-attributes,too-many-public-method self.vcsthread.pause() self.vcsthread.paused.wait() + if self.fm.image_displayer: + self.fm.image_displayer.quit() + self.win.keypad(0) curses.nocbreak() curses.echo() diff --git a/ranger/gui/widgets/browsercolumn.py b/ranger/gui/widgets/browsercolumn.py index bc6f7b1b..ecc66f44 100644 --- a/ranger/gui/widgets/browsercolumn.py +++ b/ranger/gui/widgets/browsercolumn.py @@ -60,7 +60,7 @@ class BrowserColumn(Pager): # pylint: disable=too-many-instance-attributes def request_redraw(self): self.need_redraw = True - def click(self, event): + def click(self, event): # pylint: disable=too-many-branches """Handle a MouseEvent""" direction = event.mouse_wheel_direction() if not (event.pressed(1) or event.pressed(3) or direction): @@ -95,7 +95,8 @@ class BrowserColumn(Pager): # pylint: disable=too-many-instance-attributes elif self.level == 0: self.fm.thisdir.move_to_obj(clicked_file) self.fm.execute_file(clicked_file) - + elif self.target.is_file: + self.scrollbit(direction) else: if self.level > 0 and not direction: self.fm.move(right=0) @@ -155,6 +156,7 @@ class BrowserColumn(Pager): # pylint: disable=too-many-instance-attributes if target != self.old_dir: self.need_redraw = True self.old_dir = target + self.scroll_extra = 0 # reset scroll start if target: target.use() diff --git a/ranger/gui/widgets/pager.py b/ranger/gui/widgets/pager.py index d64d4ac1..c1aa2765 100644 --- a/ranger/gui/widgets/pager.py +++ b/ranger/gui/widgets/pager.py @@ -34,6 +34,7 @@ class Pager(Widget): # pylint: disable=too-many-instance-attributes Widget.__init__(self, win) self.embedded = embedded self.scroll_begin = 0 + self.scroll_extra = 0 self.startx = 0 self.markup = None self.lines = [] @@ -74,6 +75,12 @@ class Pager(Widget): # pylint: disable=too-many-instance-attributes def finalize(self): self.fm.ui.win.move(self.y, self.x) + def scrollbit(self, lines): + target_scroll = self.scroll_extra + lines + max_scroll = len(self.lines) - self.hei + self.scroll_extra = max(0, min(target_scroll, max_scroll)) + self.need_redraw = True + def draw(self): if self.need_clear_image: self.need_redraw = True @@ -94,8 +101,9 @@ class Pager(Widget): # pylint: disable=too-many-instance-attributes self.clear_image() if not self.image: + scroll_pos = self.scroll_begin + self.scroll_extra line_gen = self._generate_lines( - starty=self.scroll_begin, startx=self.startx) + starty=scroll_pos, startx=self.startx) for line, i in zip(line_gen, range(self.hei)): self._draw_line(i, line) diff --git a/ranger/gui/widgets/view_miller.py b/ranger/gui/widgets/view_miller.py index e138ee4b..55d401a0 100644 --- a/ranger/gui/widgets/view_miller.py +++ b/ranger/gui/widgets/view_miller.py @@ -99,7 +99,12 @@ class ViewMiller(ViewBase): # pylint: disable=too-many-ancestors,too-many-insta directory.use() DisplayableContainer.draw(self) if self.settings.draw_borders: - self._draw_borders() + draw_borders = self.settings.draw_borders.lower() + if draw_borders in ['both', 'true']: # 'true' for backwards compat. + border_types = ['separators', 'outline'] + else: + border_types = [draw_borders] + self._draw_borders(border_types) if self.draw_bookmarks: self._draw_bookmarks() elif self.draw_hints: @@ -107,8 +112,9 @@ class ViewMiller(ViewBase): # pylint: disable=too-many-ancestors,too-many-insta elif self.draw_info: self._draw_info(self.draw_info) - def _draw_borders(self): + def _draw_borders(self, border_types): # pylint: disable=too-many-branches win = self.win + self.color('in_browser', 'border') left_start = 0 @@ -129,49 +135,57 @@ class ViewMiller(ViewBase): # pylint: disable=too-many-ancestors,too-many-insta right_end = self.wid - 1 # Draw horizontal lines and the leftmost vertical line - try: - # pylint: disable=no-member - win.hline(0, left_start, curses.ACS_HLINE, right_end - left_start) - win.hline(self.hei - 1, left_start, curses.ACS_HLINE, right_end - left_start) - win.vline(1, left_start, curses.ACS_VLINE, self.hei - 2) - # pylint: enable=no-member - except curses.error: - pass + if 'outline' in border_types: + try: + # pylint: disable=no-member + win.hline(0, left_start, curses.ACS_HLINE, right_end - left_start) + win.hline(self.hei - 1, left_start, curses.ACS_HLINE, right_end - left_start) + win.vline(1, left_start, curses.ACS_VLINE, self.hei - 2) + # pylint: enable=no-member + except curses.error: + pass # Draw the vertical lines in the middle - for child in self.columns[:-1]: - if not child.has_preview(): - continue - if child.main_column and self.pager.visible: - # If we "zoom in" with the pager, we have to - # skip the between main_column and pager. - break - x = child.x + child.wid - y = self.hei - 1 + if 'separators' in border_types: + for child in self.columns[:-1]: + if not child.has_preview(): + continue + if child.main_column and self.pager.visible: + # If we "zoom in" with the pager, we have to + # skip the between main_column and pager. + break + x = child.x + child.wid + y = self.hei - 1 + try: + # pylint: disable=no-member + win.vline(1, x, curses.ACS_VLINE, y - 1) + if 'outline' in border_types: + self.addch(0, x, curses.ACS_TTEE, 0) + self.addch(y, x, curses.ACS_BTEE, 0) + else: + self.addch(0, x, curses.ACS_VLINE, 0) + self.addch(y, x, curses.ACS_VLINE, 0) + # pylint: enable=no-member + except curses.error: + # in case it's off the boundaries + pass + + if 'outline' in border_types: + # Draw the last vertical line try: # pylint: disable=no-member - win.vline(1, x, curses.ACS_VLINE, y - 1) - self.addch(0, x, curses.ACS_TTEE, 0) - self.addch(y, x, curses.ACS_BTEE, 0) + win.vline(1, right_end, curses.ACS_VLINE, self.hei - 2) # pylint: enable=no-member except curses.error: - # in case it's off the boundaries pass - # Draw the last vertical line - try: + if 'outline' in border_types: # pylint: disable=no-member - win.vline(1, right_end, curses.ACS_VLINE, self.hei - 2) + self.addch(0, left_start, curses.ACS_ULCORNER) + self.addch(self.hei - 1, left_start, curses.ACS_LLCORNER) + self.addch(0, right_end, curses.ACS_URCORNER) + self.addch(self.hei - 1, right_end, curses.ACS_LRCORNER) # pylint: enable=no-member - except curses.error: - pass - - # pylint: disable=no-member - self.addch(0, left_start, curses.ACS_ULCORNER) - self.addch(self.hei - 1, left_start, curses.ACS_LLCORNER) - self.addch(0, right_end, curses.ACS_URCORNER) - self.addch(self.hei - 1, right_end, curses.ACS_LRCORNER) - # pylint: enable=no-member def _collapse(self): # Should the last column be cut off? (Because there is no preview) @@ -195,10 +209,12 @@ class ViewMiller(ViewBase): # pylint: disable=too-many-ancestors,too-many-insta """Resize all the columns according to the given ratio""" ViewBase.resize(self, y, x, hei, wid) - borders = self.settings.draw_borders - pad = 1 if borders else 0 + border_type = self.settings.draw_borders.lower() + if border_type in ['outline', 'both', 'true']: + pad = 1 + else: + pad = 0 left = pad - self.is_collapsed = self._collapse() if self.is_collapsed: generator = enumerate(self.stretch_ratios) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..411a2a97 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +flake8 +pylint<2 +pytest diff --git a/tests/ranger/container/test_bookmarks.py b/tests/ranger/container/test_bookmarks.py index 6fba2a3d..64192c06 100644 --- a/tests/ranger/container/test_bookmarks.py +++ b/tests/ranger/container/test_bookmarks.py @@ -56,3 +56,20 @@ def testbookmarks(tmpdir): secondstore.update_if_outdated() secondstore.update = origupdate secondstore.update_if_outdated() + + +def test_bookmark_symlink(tmpdir): + # Initialize plain file and symlink paths + bookmarkfile_link = tmpdir.join("bookmarkfile") + bookmarkfile_orig = tmpdir.join("bookmarkfile.orig") + + # Create symlink pointing towards the original plain file. + os.symlink(str(bookmarkfile_orig), str(bookmarkfile_link)) + + # Initialize the bookmark file and save the file. + bmstore = Bookmarks(str(bookmarkfile_link)) + bmstore.save() + + # Once saved, the bookmark file should still be a symlink pointing towards the plain file. + assert os.path.islink(str(bookmarkfile_link)) + assert not os.path.islink(str(bookmarkfile_orig)) |