├── __init__.py ├── LICENSE ├── README.md ├── fzf_marks.py └── .gitignore /__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021, laggardkernel and the ranger-fzf-marks contributors 2 | # SPDX-License-Identifier: MIT 3 | 4 | import ranger.api 5 | from .fzf_marks import * 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 laggardkernel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ranger-fzf-marks 2 | 3 | [![License: MIT][license icon]][license] 4 | 5 | A [ranger][ranger] plugin ported from [urbainvaes/fzf-marks][urbainvaes-fzf-marks]. 6 | It depends on command-line fuzzy finder [junegunn/fzf][junegunn-fzf]. 7 | 8 | ## Installation 9 | 10 | Git clone the plugin into ranger's plugin folder. (`ranger >= 1.9.3`) 11 | 12 | ```bash 13 | git clone https://github.com/laggardkernel/ranger-fzf-marks.git ~/.config/ranger/plugins/fzf-marks 14 | ``` 15 | 16 | Then add key binding for bookmark jump in `rc.conf`. 17 | 18 | ```bash 19 | map fzm 20 | ``` 21 | 22 | ## Usage 23 | 24 | Commands: 25 | 26 | - `:fmark `, add a current dir as bookmark 27 | - `:fzm []`, jump to a bookmark 28 | - `:dmark []`, delete a bookmark 29 | 30 | Bookmark jumping could be also be triggered with key binding like ``. 31 | 32 | Other features like output colorization, custom fzf command are not implemented. 33 | This plugin only ensure basic usage of bookmark with fzf support. 34 | 35 | ## Settings 36 | 37 | Customization is supported by environment variables. 38 | 39 | `FZF_MARKS_FILE`, same variable used in 40 | [urbainvaes/fzf-marks][urbainvaes-fzf-marks], defaults to `${HOME}/.fzf-marks`. 41 | 42 | `FZF_MARKS_CMD`, path to the `fzf` executable binary. You don't need this 43 | unless your `fzf` is not added in the `PATH`. 44 | 45 | `FZF_FZM_OPTS`, controls how command `fzm` (from this plugin) behave, defaults to 46 | `--cycle +m --ansi --bind=ctrl-o:accept,ctrl-t:toggle --select-1`. 47 | 48 | `FZF_DMARK_OPTS`, controls how command `dmark` (from this plugin) behave, 49 | defaults to `--cycle -m --ansi --bind=ctrl-o:accept,ctrl-t:toggle`. 50 | (`dmark` only supports delete one mark at each time for now, that's why I use `-m`) 51 | 52 | Besides above `FZF_*` related settings, fzf's behavior is also controlled by 53 | `FZF_DEFAULT_OPTS`, which is an env from `fzf` itself. Read `man fzf` for 54 | detail. 55 | 56 | ## Layout 57 | 58 | The default `fzf` layout `--layout=default` **display from the bottom of the screen**. 59 | For anyone who like it to be display from the top to the bottom, 60 | try `--reverse` and `--height`, `--min-height` to get what you want. e.g. 61 | 62 | ```bash 63 | export FZF_FZM_OPTS="--reverse --height 75% --min-height 30 --cycle +m --ansi --bind=ctrl-o:accept,ctrl-t:toggle --select-1" 64 | export FZF_DMARK_OPTS="--reverse --height 75% --min-height 30 --cycle -m --ansi --bind=ctrl-o:accept,ctrl-t:toggle" 65 | ``` 66 | 67 | ## TODO 68 | 69 | - [x] Custom storage support with `FZF_MARKS_FILE` 70 | - [ ] Delete action support in `:fzm` 71 | - [ ] Batch deletion support in `:dmark` 72 | - [x] Make `fmark`, `dmark` style customizable 73 | 74 | ## License 75 | 76 | The MIT License (MIT) 77 | 78 | Copyright (c) 2021 laggardkernel 79 | 80 | [license icon]: https://img.shields.io/badge/License-MIT-blue.svg 81 | [license]: https://opensource.org/licenses/MIT 82 | [ranger]: https://github.com/ranger/ranger 83 | [urbainvaes-fzf-marks]: https://github.com/urbainvaes/fzf-marks 84 | [junegunn-fzf]: https://github.com/junegunn/fzf 85 | -------------------------------------------------------------------------------- /fzf_marks.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021, laggardkernel and the ranger-fzf-marks contributors 2 | # SPDX-License-Identifier: MIT 3 | 4 | from __future__ import absolute_import, division, print_function 5 | import os 6 | from ranger.api.commands import Command 7 | 8 | 9 | class FzfMarksBase(Command): 10 | 11 | fzf_cmd = os.environ.get("FZF_MARKS_CMD", "fzf") 12 | # https://github.com/urbainvaes/fzf-marks 13 | bookmark_file = os.environ.get("FZF_MARKS_FILE") or os.path.join( 14 | os.environ.get("HOME", os.path.expanduser("~")), ".fzf-marks" 15 | ) 16 | 17 | 18 | class fmark(FzfMarksBase): 19 | """ 20 | :fmark 21 | Mark the current directory with provided keyword 22 | """ 23 | 24 | def execute(self): 25 | if not self.arg(1): 26 | self.fm.notify( 27 | "A keyword must be given for the current bookmark!", bad=True 28 | ) 29 | return 30 | 31 | item = "{} : {}".format(self.arg(1), self.fm.thisdir.path) 32 | 33 | if not os.path.exists(self.bookmark_file): 34 | with open(self.bookmark_file, "a") as f: 35 | pass 36 | 37 | with open(self.bookmark_file, "r") as f: 38 | for line in f.readlines(): 39 | if line.split(":")[1].strip() == self.fm.thisdir.path: 40 | self.fm.notify( 41 | "Fzf bookmark already exists: {}".format(line.strip()), bad=True 42 | ) 43 | return 44 | 45 | with open(self.bookmark_file, "a") as f: 46 | f.write("{}{}".format(item, os.linesep)) 47 | self.fm.notify("Fzf bookmark has been added: {}".format(item)) 48 | 49 | 50 | class dmark(FzfMarksBase): 51 | """ 52 | dmark: delete current directory from fzf-marks file 53 | """ 54 | 55 | fzf_opts = os.environ.get( 56 | "FZF_DMARK_OPTS", 57 | "--cycle -m --ansi --bind=ctrl-o:accept,ctrl-t:toggle", 58 | ) 59 | 60 | def execute(self): 61 | import subprocess 62 | 63 | items = None 64 | query = "" 65 | 66 | if self.arg(1): 67 | query = self.arg(1) 68 | 69 | if not os.path.exists(self.bookmark_file): 70 | self.fm.notify("No fzf bookmark is created yet!", bad=True) 71 | return 72 | 73 | # TODO: batch deletion 74 | command = '< "{2}" sort -f | {0} {1} --query="{3}"'.format( 75 | self.fzf_cmd, self.fzf_opts, self.bookmark_file, query 76 | ) 77 | 78 | process = self.fm.execute_command( 79 | command, universal_newlines=True, stdout=subprocess.PIPE 80 | ) 81 | stdout, stderr = process.communicate() 82 | if process.returncode == 0: 83 | items = stdout.rstrip().split("\n") 84 | 85 | if not items: 86 | return 87 | 88 | with open(self.bookmark_file, "r") as f: 89 | lines = f.readlines() 90 | 91 | with open(self.bookmark_file, "w") as f: 92 | for line in lines: 93 | if line.strip() not in items: 94 | f.write(line) 95 | 96 | self.fm.notify("Fzf bookmark is deleted: {}".format(", ".join(items))) 97 | 98 | 99 | class fzm(FzfMarksBase): 100 | """ 101 | fzm: select and jump to bookmark stored in fzf-marks 102 | """ 103 | 104 | fzf_opts = os.environ.get( 105 | "FZF_FZM_OPTS", 106 | "--cycle +m --ansi --bind=ctrl-o:accept,ctrl-t:toggle --select-1", 107 | ) 108 | 109 | def execute(self): 110 | import subprocess 111 | 112 | target = None 113 | query = "" 114 | 115 | if self.arg(1): 116 | query = self.arg(1) 117 | 118 | if not os.path.exists(self.bookmark_file): 119 | self.fm.notify("No fzf bookmark is created yet!", bad=True) 120 | return 121 | 122 | command = '< "{2}" sort -f | {0} {1} --query "{3}"'.format( 123 | self.fzf_cmd, self.fzf_opts, self.bookmark_file, query 124 | ) 125 | 126 | process = self.fm.execute_command( 127 | command, universal_newlines=True, stdout=subprocess.PIPE 128 | ) 129 | stdout, stderr = process.communicate() 130 | if process.returncode == 0: 131 | key, target = stdout.rstrip().split(" : ", 1) 132 | target = os.path.expanduser(target) 133 | 134 | if not target: 135 | return 136 | elif os.path.isdir(target): 137 | self.fm.cd(target) 138 | elif os.path.isfile(target): 139 | self.fm.select_file(target) 140 | else: 141 | self.fm.notify( 142 | "Invalid fzf bookmark location: {} : {}".format(key, target), True 143 | ) 144 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### macOS ### 2 | # General 3 | .DS_Store 4 | .AppleDouble 5 | .LSOverride 6 | 7 | # Icon must end with two \r 8 | Icon 9 | 10 | # Thumbnails 11 | ._* 12 | 13 | # Files that might appear in the root of a volume 14 | .DocumentRevisions-V100 15 | .fseventsd 16 | .Spotlight-V100 17 | .TemporaryItems 18 | .Trashes 19 | .VolumeIcon.icns 20 | .com.apple.timemachine.donotpresent 21 | 22 | # Directories potentially created on remote AFP share 23 | .AppleDB 24 | .AppleDesktop 25 | Network Trash Folder 26 | Temporary Items 27 | .apdisk 28 | 29 | ### Linux ### 30 | *~ 31 | 32 | # temporary files which can be created if a process still has a handle open of a deleted file 33 | .fuse_hidden* 34 | 35 | # KDE directory preferences 36 | .directory 37 | 38 | # Linux trash folder which might appear on any partition or disk 39 | .Trash-* 40 | 41 | # .nfs files are created when an open file is removed but is still being accessed 42 | .nfs* 43 | 44 | ### Windows ### 45 | # Windows thumbnail cache files 46 | Thumbs.db 47 | ehthumbs.db 48 | ehthumbs_vista.db 49 | 50 | # Dump file 51 | *.stackdump 52 | 53 | # Folder config file 54 | [Dd]esktop.ini 55 | 56 | # Recycle Bin used on file shares 57 | $RECYCLE.BIN/ 58 | 59 | # Windows Installer files 60 | *.cab 61 | *.msi 62 | *.msix 63 | *.msm 64 | *.msp 65 | 66 | # Windows shortcuts 67 | *.lnk 68 | 69 | ### Emacs ### 70 | # -*- mode: gitignore; -*- 71 | *~ 72 | \#*\# 73 | /.emacs.desktop 74 | /.emacs.desktop.lock 75 | *.elc 76 | auto-save-list 77 | tramp 78 | .\#* 79 | 80 | # Org-mode 81 | .org-id-locations 82 | *_archive 83 | 84 | # flymake-mode 85 | *_flymake.* 86 | 87 | # eshell files 88 | /eshell/history 89 | /eshell/lastdir 90 | 91 | # elpa packages 92 | /elpa/ 93 | 94 | # reftex files 95 | *.rel 96 | 97 | # AUCTeX auto folder 98 | /auto/ 99 | 100 | # cask packages 101 | .cask/ 102 | dist/ 103 | 104 | # Flycheck 105 | flycheck_*.el 106 | 107 | # server auth directory 108 | /server/ 109 | 110 | # projectiles files 111 | .projectile 112 | 113 | # directory configuration 114 | .dir-locals.el 115 | 116 | # network security 117 | /network-security.data 118 | 119 | 120 | ### Vim ### 121 | # Swap 122 | [._]*.s[a-v][a-z] 123 | [._]*.sw[a-p] 124 | [._]s[a-rt-v][a-z] 125 | [._]ss[a-gi-z] 126 | [._]sw[a-p] 127 | 128 | # Session 129 | Session.vim 130 | 131 | # Temporary 132 | .netrwhist 133 | *~ 134 | # Auto-generated tag files 135 | tags 136 | # Persistent undo 137 | [._]*.un~ 138 | 139 | ### SublimeText ### 140 | # Cache files for Sublime Text 141 | *.tmlanguage.cache 142 | *.tmPreferences.cache 143 | *.stTheme.cache 144 | 145 | # Workspace files are user-specific 146 | *.sublime-workspace 147 | 148 | # Project files should be checked into the repository, unless a significant 149 | # proportion of contributors will probably not be using Sublime Text 150 | # *.sublime-project 151 | 152 | # SFTP configuration file 153 | sftp-config.json 154 | 155 | # Package control specific files 156 | Package Control.last-run 157 | Package Control.ca-list 158 | Package Control.ca-bundle 159 | Package Control.system-ca-bundle 160 | Package Control.cache/ 161 | Package Control.ca-certs/ 162 | Package Control.merged-ca-bundle 163 | Package Control.user-ca-bundle 164 | oscrypto-ca-bundle.crt 165 | bh_unicode_properties.cache 166 | 167 | # Sublime-github package stores a github token in this file 168 | # https://packagecontrol.io/packages/sublime-github 169 | GitHub.sublime-settings 170 | 171 | ### VisualStudioCode ### 172 | .vscode/* 173 | !.vscode/settings.json 174 | !.vscode/tasks.json 175 | !.vscode/launch.json 176 | !.vscode/extensions.json 177 | 178 | ### VisualStudioCode Patch ### 179 | # Ignore all local history of files 180 | .history 181 | 182 | ### Python ### 183 | # Byte-compiled / optimized / DLL files 184 | __pycache__/ 185 | *.py[cod] 186 | *$py.class 187 | 188 | # C extensions 189 | *.so 190 | 191 | # Distribution / packaging 192 | .Python 193 | build/ 194 | develop-eggs/ 195 | dist/ 196 | downloads/ 197 | eggs/ 198 | .eggs/ 199 | lib/ 200 | lib64/ 201 | parts/ 202 | sdist/ 203 | var/ 204 | wheels/ 205 | pip-wheel-metadata/ 206 | share/python-wheels/ 207 | *.egg-info/ 208 | .installed.cfg 209 | *.egg 210 | MANIFEST 211 | 212 | # PyInstaller 213 | # Usually these files are written by a python script from a template 214 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 215 | *.manifest 216 | *.spec 217 | 218 | # Installer logs 219 | pip-log.txt 220 | pip-delete-this-directory.txt 221 | 222 | # Unit test / coverage reports 223 | htmlcov/ 224 | .tox/ 225 | .nox/ 226 | .coverage 227 | .coverage.* 228 | .cache 229 | nosetests.xml 230 | coverage.xml 231 | *.cover 232 | .hypothesis/ 233 | .pytest_cache/ 234 | 235 | # Translations 236 | *.mo 237 | *.pot 238 | 239 | # Django stuff: 240 | *.log 241 | local_settings.py 242 | db.sqlite3 243 | 244 | # Flask stuff: 245 | instance/ 246 | .webassets-cache 247 | 248 | # Scrapy stuff: 249 | .scrapy 250 | 251 | # Sphinx documentation 252 | docs/_build/ 253 | 254 | # PyBuilder 255 | target/ 256 | 257 | # Jupyter Notebook 258 | .ipynb_checkpoints 259 | 260 | # IPython 261 | profile_default/ 262 | ipython_config.py 263 | 264 | # pyenv 265 | .python-version 266 | 267 | # pipenv 268 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 269 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 270 | # having no cross-platform support, pipenv may install dependencies that don’t work, or not 271 | # install all needed dependencies. 272 | #Pipfile.lock 273 | 274 | # celery beat schedule file 275 | celerybeat-schedule 276 | 277 | # SageMath parsed files 278 | *.sage.py 279 | 280 | # Environments 281 | .env 282 | .venv 283 | env/ 284 | venv/ 285 | ENV/ 286 | env.bak/ 287 | venv.bak/ 288 | 289 | # Spyder project settings 290 | .spyderproject 291 | .spyproject 292 | 293 | # Rope project settings 294 | .ropeproject 295 | 296 | # mkdocs documentation 297 | /site 298 | 299 | # mypy 300 | .mypy_cache/ 301 | .dmypy.json 302 | dmypy.json 303 | 304 | # Pyre type checker 305 | .pyre/ 306 | 307 | --------------------------------------------------------------------------------