├── screenshots ├── buffer.png ├── counsel-m-x.png ├── all-the-icons.png ├── counsel-bookmark.png ├── counsel-recentf.png ├── package-install.png └── counsel-describe-variable.png ├── screenshots.org ├── .gitignore ├── README.org └── ivy-rich.el /screenshots/buffer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yevgnen/ivy-rich/HEAD/screenshots/buffer.png -------------------------------------------------------------------------------- /screenshots/counsel-m-x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yevgnen/ivy-rich/HEAD/screenshots/counsel-m-x.png -------------------------------------------------------------------------------- /screenshots/all-the-icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yevgnen/ivy-rich/HEAD/screenshots/all-the-icons.png -------------------------------------------------------------------------------- /screenshots/counsel-bookmark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yevgnen/ivy-rich/HEAD/screenshots/counsel-bookmark.png -------------------------------------------------------------------------------- /screenshots/counsel-recentf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yevgnen/ivy-rich/HEAD/screenshots/counsel-recentf.png -------------------------------------------------------------------------------- /screenshots/package-install.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yevgnen/ivy-rich/HEAD/screenshots/package-install.png -------------------------------------------------------------------------------- /screenshots/counsel-describe-variable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yevgnen/ivy-rich/HEAD/screenshots/counsel-describe-variable.png -------------------------------------------------------------------------------- /screenshots.org: -------------------------------------------------------------------------------- 1 | #+title: Screenshots 2 | #+date: <2020-12-29 Tue> 3 | 4 | * Screenshots 5 | :PROPERTIES: 6 | :CUSTOM_ID: h:A3BD2C78-CADB-4D4A-AB42-1D8ECD8AB2AD 7 | :END: 8 | 9 | ** ~ivy-switch-buffer~ 10 | 11 | [[file:screenshots/buffer.png]] 12 | 13 | ** ~counsel-M-x~ 14 | 15 | [[file:screenshots/counsel-m-x.png]] 16 | 17 | ** ~counsel-describe-variable~ 18 | 19 | [[file:screenshots/counsel-describe-variable.png]] 20 | 21 | ** ~counsel-recentf~ 22 | 23 | [[file:screenshots/counsel-recentf.png]] 24 | 25 | ** ~counsel-bookmark~ 26 | 27 | [[file:screenshots/counsel-bookmark.png]] 28 | 29 | ** ~package-install~ 30 | 31 | [[file:screenshots/package-install.png]] 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/osx 3 | 4 | ### OSX ### 5 | *.DS_Store 6 | .AppleDouble 7 | .LSOverride 8 | 9 | # Icon must end with two \r 10 | Icon 11 | # Thumbnails 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 | # Directories potentially created on remote AFP share 22 | .AppleDB 23 | .AppleDesktop 24 | Network Trash Folder 25 | Temporary Items 26 | .apdisk 27 | 28 | # Created by https://www.gitignore.io/api/elisp 29 | 30 | ### Elisp ### 31 | # Compiled 32 | *.elc 33 | 34 | # Packaging 35 | .cask 36 | -------------------------------------------------------------------------------- /README.org: -------------------------------------------------------------------------------- 1 | #+title: README 2 | #+date: <2020-03-22 Sun> 3 | 4 | * More friendly interface for ivy 5 | 6 | This package comes with rich transformers for commands from ~ivy~ and ~counsel~. It should be easy enough to define your own transformers too. 7 | 8 | Screenshots are available [[file:screenshots.org][here]]. 9 | 10 | ** Installation 11 | 12 | ~M-x package-install RET ivy-rich RET~ 13 | 14 | ** Basic Usages 15 | 16 | #+begin_src emacs-lisp 17 | (require 'ivy-rich) 18 | (ivy-rich-mode 1) 19 | #+end_src 20 | 21 | It is recommended to set also 22 | 23 | #+begin_src emacs-lisp 24 | (setcdr (assq t ivy-format-functions-alist) #'ivy-format-function-line) 25 | #+end_src 26 | 27 | ** Customization 28 | :PROPERTIES: 29 | :CUSTOM_ID: h:6A171A3A-50DF-42F6-B19B-321B160F198E 30 | :END: 31 | 32 | *A transformer is just a string processing function with some format properties.* The transformer format for each ~ivy~ command is defined in the [[https://www.gnu.org/software/emacs/manual/html_node/elisp/Property-Lists.html#Property-Lists][plist]] ~ivy-rich-display-transformers-list~. Each plist key is a ~ivy~ command and plist value is its transformer format definitions or a pre-defined transformer. Refer to the documentation of ~ivy-rich-display-transformers-list~ for details. 33 | 34 | You can also define your own transformers, see [[id:20201229-how-i-can-add-icons-for-ivy-switch-buffer][this]] for an example. 35 | 36 | ** Convenience functions 37 | 38 | Convenience functions exist for customizing column properties without rewriting the entire transformer definition 39 | 40 | - ~ivy-rich-modify-column~ :: Update properties of existed transformer column. 41 | - ~ivy-rich-modify-columns~ :: Like ~ivy-rich-modify-column~, but for multiple columns. 42 | - ~ivy-rich-set-columns~ :: Set/Replace transformer columns. 43 | 44 | Below are two examples that use the default transformers, but with user-defined customizations. 45 | 46 | This example customizes the ~:width~ and ~:face~ properties of the major mode column in the ~ivy-switch-buffer~ transformer: 47 | 48 | #+begin_src elisp 49 | (ivy-rich-modify-column 50 | 'ivy-switch-buffer 51 | 'ivy-rich-switch-buffer-major-mode 52 | '(:width 20 :face error)) 53 | #+end_src 54 | 55 | This example customizes properties of two columns of the ~ivy-switch-buffer~ transformer: 56 | 57 | #+begin_src elisp 58 | (ivy-rich-modify-columns 59 | 'ivy-switch-buffer 60 | '((ivy-rich-switch-buffer-size (:align right)) 61 | (ivy-rich-switch-buffer-major-mode (:width 20 :face error)))) 62 | #+end_src 63 | 64 | Refer to the docstring for more details. 65 | 66 | ** Additional settings for ~ivy-switch-buffer~ 67 | 68 | To abbreviate paths using ~abbreviate-file-name~ (e.g. replace "/home/username" with "~") 69 | 70 | #+begin_src emacs-lisp 71 | (setq ivy-rich-path-style 'abbrev) 72 | #+end_src 73 | 74 | To always show absolute path, set it to ~full~ or ~absolute~. Any other value will show the file path relative to the project root or =default-Directory=. 75 | 76 | Note that this may not affect remote files. There are two variables ~ivy-rich-parse-remote-buffer~ and ~ivy-rich-parse-remote-file-path~ controls how remote buffers are processed, please refer to the docstring of them for more details if you have trouble using this function under ~tramp~. 77 | 78 | *** Project Performance 79 | 80 | When having many open buffers, calling and navigating ~ivy-switch-buffers~ might become slow when you have project-related columns. If that's the case, you can enable ~ivy-rich-project-root-cache-mode~, to cache each buffers project. The project for a buffer is cached until the buffer is killed, ~ivy-rich-project-root-cache-mode~ is disabled or ~ivy-rich-clear-project-root-cache~ is called. 81 | 82 | ** Notes 83 | 84 | 1. If you modify ~ivy-rich-display-transformers-list~, you may need to disable and re-enable ~ivy-rich-mode~ again to make the changes take effect. 85 | 86 | 2. If you define transformers for commands comes from neither ~ivy~ nor ~counsel~, e.g. ~counsel-projectile-*~, it currently may not take effect since if you enable ~ivy-rich-mode~ before loading ~counsel-projectile~, the transformer setting is overwritten by loading the package. Try to load all these packages before loading ~ivy-rich~. 87 | 88 | 3. Disabling the minor mode ~ivy-rich-mode~ will restore the transformers to what they were before, but *not* necessarily to the 'built-in default' one. For example, the default transformer for ~ivy-switch-buffer~ is ~ivy-switch-buffer-transformer~ from the ~ivy~ package. But if you set the transformer to ~some-function~ before enabling ~ivy-rich-mode~, disabling the minor mode will restore it to ~some-function~ other than ~ivy-switch-buffer-transformer~. 89 | 90 | * Important Changes 91 | 92 | Since the version 0.1.0 of ~ivy-rich~, the transformer format can be customized. Variables from older version like ~ivy-rich-switch-buffer-mode-max-length~ or ~ivy-rich-switch-buffer-project-max-length~ has been deprecated since they are now packed into ~ivy-rich-display-transformers-list~ as stated in the [[#h:6A171A3A-50DF-42F6-B19B-321B160F198E][customization section]]. 93 | 94 | Supports for virtual buffers and shorten file paths in ~ivy-switch-buffer~ are temporarily Removed. 95 | 96 | * FAQ 97 | 98 | ** Can I search buffers by ~major-mode~, ~project~ in ~ivy-switch-buffer~? 99 | 100 | No, as far as I know, you can not right now. ~ivy-rich~ provides just transformers to display the original ~ivy~ candidates in a different way. It does not modify the original candidates. At least for now I have no idea how to add feature to search in the transformer columns. It probably requires some change in ~ivy~. 101 | 102 | So you can not search the description of ~counsel-describe-function~ neither. 103 | 104 | ** How I can add icons for ~ivy-switch-buffer~? 105 | :PROPERTIES: 106 | :ID: 20201229-how-i-can-add-icons-for-ivy-switch-buffer 107 | :END: 108 | 109 | The package [[https://github.com/domtronn/all-the-icons.el][all-the-icons.el]] provides functionality to use icon fonts easily in emacs. For example, you can define a transformer 110 | 111 | #+begin_src elisp 112 | (defun ivy-rich-switch-buffer-icon (candidate) 113 | (with-current-buffer 114 | (get-buffer candidate) 115 | (let ((icon (all-the-icons-icon-for-mode major-mode))) 116 | (if (symbolp icon) 117 | (all-the-icons-icon-for-mode 'fundamental-mode) 118 | icon)))) 119 | #+end_src 120 | 121 | and add it to the plist value of ~ivy-switch-buffer~ in ~ivy-rich-display-transformers-list~ 122 | 123 | #+begin_src elisp 124 | (setq ivy-rich-display-transformers-list 125 | '(ivy-switch-buffer 126 | (:columns 127 | ((ivy-rich-switch-buffer-icon (:width 2)) 128 | (ivy-rich-candidate (:width 30)) 129 | (ivy-rich-switch-buffer-size (:width 7)) 130 | (ivy-rich-switch-buffer-indicators (:width 4 :face error :align right)) 131 | (ivy-rich-switch-buffer-major-mode (:width 12 :face warning)) 132 | (ivy-rich-switch-buffer-project (:width 15 :face success)) 133 | (ivy-rich-switch-buffer-path (:width (lambda (x) (ivy-rich-switch-buffer-shorten-path x (ivy-rich-minibuffer-width 0.3)))))) 134 | :predicate 135 | (lambda (cand) (get-buffer cand))))) 136 | #+end_src 137 | 138 | See also [[https://github.com/seagle0128/all-the-icons-ivy-rich][all-the-icons-ivy-rich]]. 139 | 140 | * Related Packages 141 | 142 | - [[https://github.com/casouri/ivy-filthy-rich][ivy-filthy-rich.el]] by @casouri 143 | 144 | - [[https://github.com/asok/all-the-icons-ivy][all-the-icons-ivy]] by @asok 145 | 146 | - [[https://github.com/seagle0128/all-the-icons-ivy-rich][all-the-icons-ivy-rich]] by @seagle0128 147 | 148 | # Local Variables: 149 | # fill-column: 72 150 | # End: 151 | -------------------------------------------------------------------------------- /ivy-rich.el: -------------------------------------------------------------------------------- 1 | ;;; ivy-rich.el --- More friendly display transformer for ivy -*- lexical-binding: t; -*- 2 | 3 | ;; Copyright (C) 2016 Yevgnen Koh 4 | 5 | ;; Author: Yevgnen Koh 6 | ;; Homepage: https://github.com/Yevgnen/ivy-rich 7 | ;; Package-Requires: ((emacs "25.1") (ivy "0.13.0")) 8 | ;; Version: 0.1.7 9 | ;; Keywords: convenience, ivy 10 | 11 | ;; This program is free software; you can redistribute it and/or modify 12 | ;; it under the terms of the GNU General Public License as published by 13 | ;; the Free Software Foundation, either version 3 of the License, or 14 | ;; (at your option) any later version. 15 | 16 | ;; This program is distributed in the hope that it will be useful, 17 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | ;; GNU General Public License for more details. 20 | 21 | ;; You should have received a copy of the GNU General Public License 22 | ;; along with this program. If not, see . 23 | 24 | ;;; Commentary: 25 | 26 | ;; More friendly interface (display transformer) for ivy. 27 | ;; Usage: 28 | ;; (require 'ivy-rich) 29 | ;; (ivy-rich-mode 1) 30 | ;; 31 | ;; See documentation on https://github.com/yevgnen/ivy-rich. 32 | 33 | ;;; Code: 34 | 35 | (require 'cl-lib) 36 | (require 'ivy) 37 | (require 'subr-x) 38 | 39 | (eval-when-compile 40 | (require 'package) 41 | (require 'bookmark) 42 | (require 'project)) 43 | 44 | (declare-function projectile-project-name "ext:projectile") 45 | (declare-function projectile-project-p "ext:projectile") 46 | (declare-function projectile-project-root "ext:projectile") 47 | 48 | (defgroup ivy-rich nil 49 | "More friendly interface (display transformer) for ivy." 50 | :group 'ivy) 51 | 52 | (defcustom ivy-rich-display-transformers-list 53 | '(ivy-switch-buffer 54 | (:columns 55 | ((ivy-switch-buffer-transformer (:width 0.35)) 56 | (ivy-rich-switch-buffer-size (:width 7)) 57 | (ivy-rich-switch-buffer-indicators (:width 4 :face error :align right)) 58 | (ivy-rich-switch-buffer-major-mode (:width 12 :face warning)) 59 | (ivy-rich-switch-buffer-project (:width 0.18 :face success)) 60 | (ivy-rich-switch-buffer-path (:width (lambda (x) (ivy-rich-switch-buffer-shorten-path x (ivy-rich-minibuffer-width 0.3)))))) 61 | :predicate 62 | (lambda (cand) (get-buffer cand))) 63 | counsel-find-file 64 | (:columns 65 | ((ivy-read-file-transformer) 66 | (ivy-rich-counsel-find-file-truename (:face font-lock-doc-face)))) 67 | counsel-M-x 68 | (:columns 69 | ((counsel-M-x-transformer (:width 0.4)) 70 | (ivy-rich-counsel-function-docstring (:face font-lock-doc-face)))) 71 | counsel-describe-function 72 | (:columns 73 | ((counsel-describe-function-transformer (:width 0.4)) 74 | (ivy-rich-counsel-function-docstring (:face font-lock-doc-face)))) 75 | counsel-describe-variable 76 | (:columns 77 | ((counsel-describe-variable-transformer (:width 0.4)) 78 | (ivy-rich-counsel-variable-docstring (:face font-lock-doc-face)))) 79 | counsel-recentf 80 | (:columns 81 | ((ivy-rich-candidate (:width 0.8)) 82 | (ivy-rich-file-last-modified-time (:face font-lock-comment-face)))) 83 | counsel-bookmark 84 | (:columns ((ivy-rich-candidate (:width 0.3)) 85 | (ivy-rich-bookmark-type) 86 | (ivy-rich-bookmark-info))) 87 | package-install 88 | (:columns 89 | ((ivy-rich-candidate (:width 30)) 90 | (ivy-rich-package-version (:width 16 :face font-lock-comment-face)) 91 | (ivy-rich-package-archive-summary (:width 7 :face font-lock-builtin-face)) 92 | (ivy-rich-package-install-summary (:face font-lock-doc-face))))) 93 | "Definitions for ivy-rich transformers. 94 | 95 | The definitions should be in the following plist format 96 | 97 | '(CMD-1 TRANSFORM-PROPS-1 98 | ... 99 | CMD-N TRANSFORM-PROPS-N) 100 | 101 | A transformer named `ivy-rich--CMD-transformer' is built for each 102 | command CMD. 103 | 104 | CMD should be an ivy command, which is typically a return value 105 | of `ivy-read'. 106 | 107 | TRANSFORM-PROPS are properties for defining transformer in plist 108 | format, i.e. 109 | 110 | (:columns (COLUMN-FN1 (KEY1 VALUE1 KEY2 VALUE2 ...)) 111 | (COLUMN-FN2 (KEY1 VALUE1 KEY2 VALUE2 ...)) 112 | :predicate PREDICATE-FN) 113 | 114 | COLUMN-FN is a function which takes the completion candidate as 115 | single argument and it should return a transformed string. This 116 | function should return an empty string \"\" instead of nil when 117 | the transformed string is empty. 118 | 119 | The KEY-VALUE pairs are custom properties in plist format for the 120 | corresponding column definition. Current supported keys are 121 | :width, :face and :align. 122 | 123 | A integer (or float) :width value indicates the max 124 | width (percentage) of current column. For better displaying, you 125 | should set :width to some reasonable values. If :width is a 126 | function, the transformed string is again passed to it and it 127 | should return a new string with properly processed width. 128 | 129 | :face is the face property for the column string. :align 130 | should be set to 'left (default if not given) or 'right to 131 | indicate where to pad extra spaces to the columns for alignment. 132 | 133 | The value of :delimiter should be a string for current 134 | transformer. If not given, the default is a single space. 135 | 136 | If :predicate is provide, it should be a function which takes the 137 | completion candidate as single argument. A candidate with nil 138 | predication will not be transformed. 139 | 140 | It is possible to set TRANSFORM-PROPS to a pre-defined 141 | transformer, e.g. 142 | 143 | (... 144 | counsel-M-x 145 | (:columns 146 | ((counsel-M-x-transformer (:width 40)) 147 | (ivy-rich-counsel-function-docstring (:face font-lock-doc-face)))) 148 | 149 | execute-extended-command ; reuse transformer built 150 | ivy-rich--counsel-M-x-transformer ; for `counsel-M-x' 151 | ...) 152 | 153 | `execute-extended-command' is set to used `counsel-M-x''s 154 | transformer. This is useful if one want to reuse transformers 155 | without duplicating definitions. 156 | 157 | Note that you may need to disable and enable the `ivy-rich-mode' 158 | again to make this variable take effect.") 159 | 160 | ;;; User convenience functions 161 | ;; Helper functions for user profile configuration 162 | 163 | (defun ivy-rich-modify-column (cmd column attrs) 164 | "Customize the CMD transformer's properties for a specific COLUMN. 165 | Each key-value pair in ATTRS is put into the property list for the column. 166 | Existing properties for the column are left unchanged. 167 | 168 | The COLUMN has to be exist. You can't modify a non-exist column 169 | because ivy-rich doesn't know how to order new columns. 170 | 171 | Usage: 172 | 173 | (ivy-rich-modify-column 'ivy-switch-buffer 174 | 'ivy-rich-switch-buffer-major-mode 175 | '(:width 20 :face error))" 176 | (if (cl-evenp (length attrs)) 177 | (let* ((trans (plist-get ivy-rich-display-transformers-list cmd)) 178 | (columns (plist-get trans :columns)) 179 | (props (cadr (assq column columns)))) 180 | (unless props 181 | (error "Can not modify non-exist column")) 182 | (while attrs 183 | (setq props (plist-put props (pop attrs) (pop attrs)))) 184 | (setcdr (assq column columns) (list props)) 185 | (setq ivy-rich-display-transformers-list 186 | (plist-put ivy-rich-display-transformers-list 187 | cmd 188 | (plist-put trans :columns columns))) 189 | (ivy-rich-set-display-transformer nil)) 190 | 191 | (error "Column key-value attributes must be in pairs"))) 192 | 193 | (defun ivy-rich-modify-columns (cmd column-list) 194 | "Customize the CMD transformer's properties for a COLUMN-LIST. 195 | This is a convenience function that calls `ivy-rich-modify-column' for each item 196 | in COLUMN-LIST, allowing multiple columns to be modified for a transformer. 197 | Each item in COLUMN-LIST is a two-item list comprised of a column and list 198 | of attribute key-value pairs. 199 | 200 | Usage: 201 | 202 | (ivy-rich-modify-columns 203 | 'ivy-switch-buffer 204 | '((ivy-rich-switch-buffer-size (:align right)) 205 | (ivy-rich-switch-buffer-major-mode (:width 20 :face error))))" 206 | (while column-list 207 | (if-let ((column (caar column-list)) 208 | (attrs (cadar column-list))) 209 | (prog1 210 | (ivy-rich-modify-column cmd column attrs) 211 | (setq column-list (cdr column-list))) 212 | (error "Column/attributes are incorrectly specified")))) 213 | 214 | (defun ivy-rich-set-columns (cmd column-list) 215 | "Set the CMD transformer's properties for a COLUMN-LIST. 216 | 217 | The :columns of the given command will be replaced by COLUMN-LIST. 218 | Each item in COLUMN-LIST is a two-item list comprised of a column and list 219 | of attribute key-value pairs. 220 | 221 | Usage: 222 | 223 | (ivy-rich-set-columns 224 | 'counsel-recentf 225 | '((file-name-nondirectory 226 | (:width 0.2)) 227 | (ivy-rich-candidate 228 | (:width 0.6))))" 229 | (let* ((trans (plist-get ivy-rich-display-transformers-list cmd))) 230 | (setq ivy-rich-display-transformers-list 231 | (plist-put ivy-rich-display-transformers-list 232 | cmd 233 | (plist-put trans :columns column-list))) 234 | (ivy-rich-set-display-transformer nil))) 235 | 236 | ;; Common Functions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 237 | (defalias 'ivy-rich-candidate 'identity) 238 | 239 | (defun ivy-rich-empty-p (str) 240 | (or (null str) 241 | (string-empty-p (string-trim str)))) 242 | 243 | (defun ivy-rich-normalize-width (str len &optional right-aligned) 244 | "Normalize the width of a string. 245 | 246 | If the length of STR is smaller than LEN, the string is padded to 247 | right aligned if RIGHT-ALIGNED is not nil and is padded to left 248 | otherwise. 249 | 250 | If the lenght of STR is larger that LEN, the string is truncated 251 | using …." 252 | (let ((str-len (string-width str))) 253 | (cond ((< str-len len) 254 | (if right-aligned 255 | (concat (make-string (- len str-len) ? ) str) 256 | (concat str (make-string (- len str-len) ? )))) 257 | ((<= len (- str-len)) "") 258 | ((> str-len len) 259 | (truncate-string-to-width str len 0 nil "…")) 260 | (t str)))) 261 | 262 | (defun ivy-rich-minibuffer-width (width) 263 | (cond ((and (integerp width) 264 | (> width 0)) 265 | width) 266 | ((and (floatp width) 267 | (> width 0.0) 268 | (<= width 1.0)) 269 | (floor (* (window-width (minibuffer-window)) width))) 270 | (t (user-error "Width should be positive integer or float int (0.0, 1.0]")))) 271 | 272 | ;; Supports for `ivy-switch-buffer' ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 273 | (defcustom ivy-rich-path-style 274 | 'relative 275 | "File path style. 276 | 277 | When set to 'full or 'absolute, absolute path will be used. 278 | When set to 'abbrev or 'abbreviate, abbreviated will be used. This 279 | may not affect remote files since `abbreviate-file-name' does not 280 | take care of them. 281 | When set to 'relative or any other value, path relative to project 282 | home will be used." 283 | :type 'symbol) 284 | 285 | (defcustom ivy-rich-parse-remote-buffer 286 | t 287 | "Whether to parse remote files. 288 | 289 | When `nil', only basic info of remote buffers, like buffer size, 290 | major mode, etc. will be parsed, otherwise, all info inculding 291 | project details, file path will be parsed. 292 | 293 | If you have performance issue when accessing tramp files, set 294 | this to `nil'." 295 | :type 'boolean) 296 | 297 | (defcustom ivy-rich-parse-remote-file-path 298 | nil 299 | "Whether `ivy-rich-path-style' should take care of remote file. 300 | 301 | When `nil', always show absolute path of remote files, 302 | otherwise, treat remote files as local files. 303 | 304 | Sometimes when you are editing files with same names and same 305 | directory structures in local and remote machines, setting this 306 | option to `nil' would make the candidates easier to be 307 | distinguished. 308 | 309 | Note that this variable takes effect only when 310 | `ivy-rich-parse-remote-buffer' is set to `t'." 311 | :type 'boolean) 312 | 313 | (defun ivy-rich-switch-buffer-user-buffer-p (buffer) 314 | "Check whether BUFFER-NAME is a user buffer." 315 | (let ((buffer-name 316 | (if (stringp buffer) 317 | buffer 318 | (buffer-name buffer)))) 319 | (not (string-match "^\\*" buffer-name)))) 320 | 321 | (defun ivy-rich-switch-buffer-shorten-path (file len) 322 | "Shorten the path of FILE until the length of FILE <= LEN. 323 | 324 | For example, a path /a/b/c/d/e/f.el will be shortened to 325 | /a/…/c/d/e/f.el 326 | or /a/…/d/e/f.el 327 | or /a/…/e/f.el 328 | or /a/…/f.el." 329 | (if (> (length file) len) 330 | (let ((new-file (replace-regexp-in-string "/?.+?/\\(\\(…/\\)?.+?\\)/.*" "…" file nil nil 1))) 331 | (if (string= new-file file) 332 | file 333 | (ivy-rich-switch-buffer-shorten-path new-file len))) 334 | file)) 335 | 336 | (defun ivy-rich--local-values (buffer args) 337 | (let ((buffer (get-buffer buffer))) 338 | (if (listp args) 339 | (mapcar #'(lambda (x) (buffer-local-value x buffer)) args) 340 | (buffer-local-value args buffer)))) 341 | 342 | (defun ivy-rich-switch-buffer-buffer-name (candidate) 343 | candidate) 344 | 345 | (defun ivy-rich-switch-buffer-indicators (candidate) 346 | (let* ((buffer (get-buffer candidate)) 347 | (process-p (get-buffer-process buffer))) 348 | (cl-destructuring-bind 349 | (filename directory read-only) 350 | (ivy-rich--local-values candidate '(buffer-file-name default-directory buffer-read-only)) 351 | (let ((modified (if (and (buffer-modified-p buffer) 352 | (null process-p) 353 | (ivy-rich-switch-buffer-user-buffer-p candidate)) 354 | "*" 355 | "")) 356 | (readonly (if (and read-only (ivy-rich-switch-buffer-user-buffer-p candidate)) 357 | "!" 358 | "")) 359 | (process (if process-p 360 | "&" 361 | "")) 362 | (remote (if (file-remote-p (or filename directory)) 363 | "@" 364 | ""))) 365 | (format "%s%s%s%s" remote readonly modified process))))) 366 | 367 | (defun ivy-rich-switch-buffer-size (candidate) 368 | (with-current-buffer 369 | (get-buffer candidate) 370 | (let ((size (buffer-size))) 371 | (cond 372 | ((> size 1000000) (format "%.1fM " (/ size 1000000.0))) 373 | ((> size 1000) (format "%.1fk " (/ size 1000.0))) 374 | (t (format "%d " size)))))) 375 | 376 | (defun ivy-rich-switch-buffer-major-mode (candidate) 377 | (capitalize 378 | (replace-regexp-in-string 379 | "-" 380 | " " 381 | (replace-regexp-in-string 382 | "-mode" 383 | "" 384 | (symbol-name (ivy-rich--local-values candidate 'major-mode)))))) 385 | 386 | (defun ivy-rich--switch-buffer-directory (candidate) 387 | "Return directory of file visited by buffer named CANDIDATE, or nil if no file." 388 | (let* ((buffer (get-buffer candidate)) 389 | (fn (buffer-file-name buffer))) 390 | ;; if valid filename, i.e. buffer visiting file: 391 | (if fn 392 | ;; return containing directory 393 | (file-name-directory fn) 394 | ;; else if mode explicitly offering list-buffers-directory, return that; else nil. 395 | ;; buffers that don't explicitly visit files, but would like to show a filename, 396 | ;; e.g. magit or dired, set the list-buffers-directory variable 397 | (buffer-local-value 'list-buffers-directory buffer)))) 398 | 399 | (defvar ivy-rich--project-root-cache 400 | (make-hash-table :test 'equal) 401 | "Hash-table caching each file's project for 402 | `ivy-rich-switch-buffer-root'. 403 | 404 | The cache can is enabled when `ivy-rich-project-root-cache-mode' 405 | is enabled and cleared when the mode is disabled. Additionally, 406 | buffers are removed from the cached when killd. 407 | 408 | The cache can be cleared manually by calling 409 | `ivy-rich-clear-project-root-cache'.") 410 | 411 | (defun ivy-rich-clear-project-root-cache () 412 | "Resets `ivy-rich--project-root-cache'." 413 | (interactive) 414 | (clrhash ivy-rich--project-root-cache)) 415 | 416 | (defun ivy-rich-switch-buffer-root-lookup (candidate dir) 417 | (unless (or (and (file-remote-p dir) 418 | (not ivy-rich-parse-remote-buffer)) 419 | ;; Workaround for `browse-url-emacs' buffers , it changes 420 | ;; `default-directory' to "http://" (#25) 421 | (string-match "https?://" dir)) 422 | (cond ((bound-and-true-p projectile-mode) 423 | (let ((project (or (ivy-rich--local-values 424 | candidate 'projectile-project-root) 425 | (projectile-project-root dir)))) 426 | (unless (string= project "-") 427 | project))) 428 | ((require 'find-file-in-project nil t) 429 | (let ((default-directory dir)) 430 | (ffip-project-root))) 431 | ((require 'project nil t) 432 | (when-let ((project (project-current nil dir))) 433 | (car (project-roots project))))))) 434 | 435 | (defun ivy-rich-switch-buffer-root (candidate) 436 | (when-let ((dir (ivy-rich--switch-buffer-directory candidate))) 437 | (let ((cached-value (if ivy-rich-project-root-cache-mode 438 | (gethash dir ivy-rich--project-root-cache 'not-found) 439 | 'not-found))) 440 | (if (not (eq cached-value 'not-found)) 441 | cached-value 442 | (let ((value (ivy-rich-switch-buffer-root-lookup candidate dir))) 443 | (when ivy-rich-project-root-cache-mode 444 | (puthash dir value ivy-rich--project-root-cache)) 445 | value))))) 446 | 447 | (defun ivy-rich-project-root-cache-kill-buffer-hook () 448 | "This hook is used to remove buffer from 449 | `ivy-rich--project-root-cache' when they are killed." 450 | (remhash (ivy-rich--switch-buffer-directory 451 | (buffer-name (current-buffer))) 452 | ivy-rich--project-root-cache)) 453 | 454 | (defun ivy-rich-switch-buffer-project (candidate) 455 | (file-name-nondirectory 456 | (directory-file-name 457 | (or (ivy-rich-switch-buffer-root candidate) "")))) 458 | 459 | (defun ivy-rich--switch-buffer-root-and-filename (candidate) 460 | (when-let ((root (ivy-rich-switch-buffer-root candidate)) 461 | (dir (ivy-rich--switch-buffer-directory candidate))) 462 | (when (bound-and-true-p projectile-mode) 463 | (setq dir (or (file-name-directory 464 | (or (ivy-rich--local-values 465 | candidate 'buffer-file-truename) 466 | "")) 467 | (file-truename dir)))) 468 | (cons (expand-file-name root) (expand-file-name dir)))) 469 | 470 | (defun ivy-rich-switch-buffer-path (candidate) 471 | (if-let ((result (ivy-rich--switch-buffer-root-and-filename candidate))) 472 | (cl-destructuring-bind (root . filename) result 473 | (cond 474 | ;; Case: absolute 475 | ((or (memq ivy-rich-path-style '(full absolute)) 476 | (and (null ivy-rich-parse-remote-file-path) 477 | (or (file-remote-p root)))) 478 | (or filename root)) 479 | ;; Case: abbreviate 480 | ((memq ivy-rich-path-style '(abbreviate abbrev)) 481 | (abbreviate-file-name (or filename root))) 482 | ;; Case: relative 483 | ((or (eq ivy-rich-path-style 'relative) 484 | t) ; make 'relative default 485 | (if (and filename root) 486 | (let ((relative-path (string-remove-prefix root filename))) 487 | (if (string= relative-path candidate) 488 | (file-name-as-directory 489 | (file-name-nondirectory 490 | (directory-file-name (file-name-directory filename)))) 491 | relative-path)) 492 | "")))) 493 | "")) 494 | 495 | 496 | ;; Supports for `counsel-find-file' 497 | (defun ivy-rich-counsel-find-file-truename (candidate) 498 | (let ((type (car (ignore-errors (file-attributes (directory-file-name (expand-file-name candidate ivy--directory))))))) 499 | (if (stringp type) 500 | (concat "-> " (expand-file-name type ivy--directory)) 501 | ""))) 502 | 503 | ;; Supports for `counsel-M-x', `counsel-describe-function', `counsel-describe-variable' 504 | (defun ivy-rich-counsel-function-docstring (candidate) 505 | (let* ( 506 | ;; Stole from: 507 | ;; https://github.com/minad/marginalia/blob/51f750994aaa0b6798d97366acfb0d397639af66/marginalia.el#L355 508 | (regex (rx bos 509 | (1+ (seq (? "This function has ") 510 | (or ":before" ":after" ":around" ":override" 511 | ":before-while" ":before-until" ":after-while" 512 | ":after-until" ":filter-args" ":filter-return") 513 | " advice: " (0+ nonl) "\n")) 514 | "\n")) 515 | (doc (replace-regexp-in-string 516 | regex 517 | "" 518 | (or (ignore-errors (documentation (intern-soft candidate))) "")))) 519 | (if (string-match "^\\(.+\\)\\([\r\n]\\)?" doc) 520 | (setq doc (match-string 1 doc)) 521 | ""))) 522 | 523 | (defun ivy-rich-counsel-variable-docstring (candidate) 524 | (let ((doc (documentation-property 525 | (intern-soft candidate) 'variable-documentation))) 526 | (if (and doc (string-match "^\\(.+\\)\\([\r\n]\\)?" doc)) 527 | (setq doc (match-string 1 doc)) 528 | ""))) 529 | 530 | ;; Supports for `counsel-recentf' 531 | (defun ivy-rich-file-last-modified-time (candidate) 532 | (let ((candidate (expand-file-name candidate ivy--directory))) 533 | (if (file-remote-p candidate) 534 | "?" 535 | (format-time-string "%Y-%m-%d %H:%M:%S" (nth 5 (file-attributes candidate)))))) 536 | 537 | ;; Supports for `counsel-bookmark' 538 | (defun ivy-rich-bookmark-value (candidate key) 539 | (cdr (assoc key (cdr (assoc candidate bookmark-alist))))) 540 | 541 | (defun ivy-rich-bookmark-filename (candidate) 542 | (ivy-rich-bookmark-value candidate 'filename)) 543 | 544 | (defun ivy-rich-bookmark-handler-props (candidate) 545 | (let ((handler (ivy-rich-bookmark-value candidate 'handler))) 546 | (unless (null handler) 547 | (list (upcase (car (cl-remove-if (lambda (x) 548 | (or (string= "bookmark" x) 549 | (string= "jump" x))) 550 | (split-string (symbol-name handler) "-")))) 551 | 'font-lock-keyword-face)))) 552 | 553 | (defun ivy-rich-bookmark-propertize-type (string face) 554 | (propertize (format "%-8.8s" string) 'face face)) 555 | 556 | (defun ivy-rich-bookmark-type (candidate) 557 | (let ((filename (ivy-rich-bookmark-filename candidate))) 558 | (apply #'ivy-rich-bookmark-propertize-type 559 | (cond ((null filename) (or (ivy-rich-bookmark-handler-props candidate) 560 | '("NOFILE" warning))) 561 | ((file-remote-p filename) '("REMOTE" mode-line-buffer-id)) 562 | ((not (file-exists-p filename)) (or (ivy-rich-bookmark-handler-props candidate) 563 | '("NOTFOUND" error))) 564 | ((file-directory-p filename) '("DIRED" warning)) 565 | (t '("FILE" success)))))) 566 | 567 | (defun ivy-rich-bookmark-info (candidate) 568 | (let ((filename (ivy-rich-bookmark-filename candidate))) 569 | (cond (filename 570 | (cond ((null filename) 571 | "") 572 | ((file-remote-p filename) 573 | candidate) 574 | ((file-exists-p filename) 575 | (file-truename filename)) 576 | (t filename)))))) 577 | 578 | ;; Supports for `counsel-projectile' 579 | ;; Possible setup: 580 | ;; counsel-projectile-switch-project 581 | ;; (:columns 582 | ;; ((ivy-rich-counsel-projectile-switch-project-project-name (:width 30 :face success)) 583 | ;; (ivy-rich-candidate))) 584 | (defun ivy-rich-counsel-projectile-switch-project-project-name (candidate) 585 | (or (projectile-project-name candidate) "")) 586 | 587 | ;; Supports for `package-install' 588 | (defun ivy-rich-package-install-summary (candidate) 589 | (let ((package-desc (cadr (assoc-string candidate package-archive-contents)))) 590 | (if package-desc (package-desc-summary package-desc) ""))) 591 | 592 | (defun ivy-rich-package-archive-summary (candidate) 593 | (let ((package-arch (cadr (assoc-string candidate package-archive-contents)))) 594 | (if package-arch (package-desc-archive package-arch) ""))) 595 | 596 | (defun ivy-rich-package-version (candidate) 597 | (let ((package-vers (cadr (assoc-string candidate package-archive-contents)))) 598 | (if package-vers (package-version-join (package-desc-version package-vers)) ""))) 599 | 600 | ;; Definition of `ivy-rich-mode' ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 601 | (defvar ivy-rich--original-display-transformers-list nil) ; Backup list 602 | 603 | (defun ivy-rich-format-column (candidate column) 604 | (let* ((fn (car column)) 605 | (props (cadr column)) 606 | (width (plist-get props :width)) 607 | (align (plist-get props :align)) 608 | (face (plist-get props :face)) 609 | (formated (funcall fn candidate))) 610 | (when width 611 | (if (functionp width) 612 | (setq formated (funcall width formated)) 613 | (if (floatp width) 614 | (setq width (floor (* (window-width (minibuffer-window)) width)))) 615 | (setq formated (ivy-rich-normalize-width formated width (eq align 'right))))) 616 | (if face 617 | (setq formated (propertize formated 'face face))) 618 | formated)) 619 | 620 | (defun ivy-rich-format (candidate columns &optional delimiter) 621 | (mapconcat 622 | (lambda (column) 623 | (or (ivy-rich-format-column candidate column) "")) 624 | columns 625 | delimiter)) 626 | 627 | (defun ivy-rich-backup-transformer (cmd) 628 | (setq ivy-rich--original-display-transformers-list 629 | (plist-put ivy-rich--original-display-transformers-list 630 | cmd 631 | (alist-get cmd ivy--display-transformers-alist)))) 632 | 633 | (defun ivy-rich-restore-transformer (cmd) 634 | (setq ivy--display-transformers-alist 635 | (ivy--alist-set 'ivy--display-transformers-alist 636 | cmd 637 | (plist-get ivy-rich--original-display-transformers-list cmd)))) 638 | 639 | (defun ivy-rich-build-transformer (cmd transformer-props) 640 | (if (functionp transformer-props) 641 | transformer-props 642 | (defalias (intern (format "ivy-rich--%s-transformer" (symbol-name cmd))) 643 | (lambda (candidate) 644 | (let ((columns (plist-get transformer-props :columns)) 645 | (predicate-fn (or (plist-get transformer-props :predicate) (lambda (_) t))) 646 | (delimiter (or (plist-get transformer-props :delimiter) " "))) 647 | (if (and predicate-fn 648 | (not (funcall predicate-fn candidate))) 649 | candidate 650 | (ivy-rich-format candidate columns delimiter))))))) 651 | 652 | (defun ivy-rich-set-display-transformer (backup) 653 | (cl-loop for (cmd transformer-props) on ivy-rich-display-transformers-list by 'cddr do 654 | (let* ((cmd-string (symbol-name cmd)) 655 | (package (if (string-match "^\\(swiper\\|counsel\\)" cmd-string) 656 | (match-string 1 cmd-string)))) 657 | (if package 658 | (require (intern package))) ; NOTE: Need to load the original transformer 659 | (if backup 660 | (ivy-rich-backup-transformer cmd)) 661 | (ivy-set-display-transformer cmd (ivy-rich-build-transformer cmd transformer-props))))) 662 | 663 | (defun ivy-rich-unset-display-transformer () 664 | (cl-loop for (cmd _transformer-fn) on ivy-rich--original-display-transformers-list by 'cddr do 665 | (ivy-rich-restore-transformer cmd)) 666 | (setq ivy-rich--original-display-transformers-list nil)) 667 | 668 | (defun ivy-rich-setup-project-root-cache-mode () 669 | (add-hook 'kill-buffer-hook 'ivy-rich-project-root-cache-kill-buffer-hook)) 670 | 671 | (defun ivy-rich-cleanup-project-root-cache-mode () 672 | (ivy-rich-clear-project-root-cache) 673 | (remove-hook 'kill-buffer-hook 'ivy-rich-project-root-cache-kill-buffer-hook)) 674 | 675 | ;;;###autoload 676 | (define-minor-mode ivy-rich-mode 677 | "Toggle ivy-rich mode globally." 678 | :global t 679 | (if ivy-rich-mode 680 | (unless ivy-rich--original-display-transformers-list 681 | (ivy-rich-set-display-transformer 'backup)) 682 | (ivy-rich-unset-display-transformer))) 683 | 684 | ;;;###autoload 685 | (defun ivy-rich-reload () 686 | (when ivy-rich-mode 687 | (ivy-rich-mode -1) 688 | (ivy-rich-mode 1))) 689 | 690 | ;;;###autoload 691 | (define-minor-mode ivy-rich-project-root-cache-mode 692 | "Toggle ivy-rich-root-cache-mode globally." 693 | :global t 694 | (if ivy-rich-project-root-cache-mode 695 | (ivy-rich-setup-project-root-cache-mode) 696 | (ivy-rich-cleanup-project-root-cache-mode))) 697 | 698 | (provide 'ivy-rich) 699 | 700 | ;;; ivy-rich.el ends here 701 | 702 | ;; Local Variables: 703 | ;; indent-tabs-mode: nil 704 | ;; End: 705 | --------------------------------------------------------------------------------