Rewrite sorting mechanism

See the "Sorting candidates" section of the README.
This commit is contained in:
Eric Danan 2018-06-09 23:12:51 +02:00
parent 8f85a392d8
commit c752c843ef
2 changed files with 86 additions and 35 deletions

View file

@ -26,6 +26,8 @@
- [Setting `counsel-projectile-org-capture` templates](#setting-counsel-projectile-org-capture-templates)
- [Removing the current project or buffer from the list of candidates](#removing-the-current-project-or-buffer-from-the-list-of-candidates)
- [Initial input for the project search commands](#initial-input-for-the-project-search-commands)
- [Matcher for `counsel-projectile-find-file`](#matcher-for-counsel-projectile-find-file)
- [Sorting candidates](#sorting-candidates)
- [Upgrading from previous version](#upgrading-from-previous-version)
- [Key bindings](#key-bindings)
- [Action lists](#action-lists)
@ -236,16 +238,21 @@ By default, the command `counsel-projectile-find-file` relies on the the matcher
The matcher specified by `counsel-find-file-ignore-regexp` is also used by `counsel-projectile` to match files.
## Sorting candidates
The following commands allow to modify the way candidates are sorted:
- `counsel-projectile`
- `cousnel-projectile-switch-project`
- `counsel-projectile-find-file`
- `counsel-projectile-find-dir`
- `counsel-projectile-switch-to-buffer`
To do so you need to add sorting functions to `ivy-sort-functions-alist`, e.g.
Sorting for these commands is controlled by the following variables, respectively:
- `counsel-projectile-sort-projects`
- `counsel-projectile-sort-files`
- `counsel-projectile-sort-directories`
- `counsel-projectile-sort-buffers`
If one of these variable is nil, the default, the command's candidates are not sorted. If it is non-nil, they are sorted. The sorting criterion can be customized through the variable `ivy-sort-functions-alist`. For instance, if you want files to be sorted from newest to oldest, then you need to add the following entry to this list:
```emacs-lisp
(setcdr (assoc 'counsel-projectile-find-file ivy-sort-functions-alist)
'file-newer-than-file-p)
'(counsel-projectile-find-file . file-newer-than-file-p)
```
Note that the `counsel-projectile` command always sorts buffers before files. Buffers are sorted as in `counsel-projectile-switch-to-buffer` and files are sorted according to `counsel-projectile-find-file`.
# Upgrading from previous version
If you are upgrading from version `0.1` to version `0.2`, please read below about important changes, some of which may require you to update your configuration.
## Key bindings

View file

@ -244,6 +244,15 @@ If anything goes wrong, throw an error and do not modify ACTION-VAR."
;;;; counsel-projectile-find-file
(defcustom counsel-projectile-sort-files nil
"Non-nil if files should be sorted in
`counsel-projectile-find-file' and `counsel-projectile'.
The sorting function can be modified by adding an entry for
`counsel-projectile-find-file' in `ivy-sort-functions-alist'."
:type 'boolean
:group 'counsel-projectile)
(defcustom counsel-projectile-find-file-matcher 'counsel--find-file-matcher
"Function returning candidates matching minibuffer input in
`counsel-projectile-find-file', also used to match files in
@ -369,19 +378,25 @@ With a prefix ARG, invalidate the cache first."
(projectile-current-project-files)
:matcher counsel-projectile-find-file-matcher
:require-match t
:sort t
:sort counsel-projectile-sort-files
:action counsel-projectile-find-file-action
:caller 'counsel-projectile-find-file))
(unless (assq #'counsel-projectile-find-file ivy-sort-functions-alist)
(push (list #'counsel-projectile-find-file) ivy-sort-functions-alist))
(ivy-set-display-transformer
'counsel-projectile-find-file
'counsel-projectile-find-file-transformer)
;;;; counsel-projectile-find-dir
(defcustom counsel-projectile-sort-directories nil
"Non-nil if directories should be sorted in
`counsel-projectile-find-dir'.
The sorting function can be modified by adding an entry for
`counsel-projectile-find-dir' in `ivy-sort-functions-alist'."
:type 'boolean
:group 'counsel-projectile)
(counsel-projectile--defcustom-action
'counsel-projectile-find-dir
'(1
@ -422,15 +437,22 @@ With a prefix ARG, invalidate the cache first."
(ivy-read (projectile-prepend-project-name "Find dir: ")
(counsel-projectile--project-directories)
:require-match t
:sort t
:sort counsel-projectile-sort-directories
:action counsel-projectile-find-dir-action
:caller 'counsel-projectile-find-dir))
(unless (assq #'counsel-projectile-find-dir ivy-sort-functions-alist)
(push (list #'counsel-projectile-find-dir) ivy-sort-functions-alist))
;;;; counsel-projectile-switch-to-buffer
(defcustom counsel-projectile-sort-buffers nil
"Non-nil if buffers should be sorted in
`counsel-projectile-switch-to-buffer' and `counsel-projectile'.
The sorting function can be modified by adding an entry for
`counsel-projectile-switch-to-buffer' in
`ivy-sort-functions-alist'."
:type 'boolean
:group 'counsel-projectile)
(defcustom counsel-projectile-remove-current-buffer nil
"Non-nil if current buffer should be removed from the
candidates list of `counsel-projectile-switch-to-buffer' and
@ -506,14 +528,11 @@ This simply applies the same transformer as in `ivy-switch-buffer', which is `iv
#'counsel-projectile--project-buffers
:matcher #'ivy--switch-buffer-matcher
:require-match t
:sort t
:sort counsel-projectile-sort-buffers
:action counsel-projectile-switch-to-buffer-action
:keymap counsel-projectile-switch-to-buffer-map
:caller 'counsel-projectile-switch-to-buffer))
(unless (assq #'counsel-projectile--project-buffers ivy-sort-functions-alist)
(push (list #'counsel-projectile--project-buffers) ivy-sort-functions-alist))
(ivy-set-display-transformer
'counsel-projectile-switch-to-buffer
'counsel-projectile-switch-to-buffer-transformer)
@ -911,6 +930,16 @@ The capture templates are read from the variables
;;;; counsel-projectile-switch-project
(defcustom counsel-projectile-sort-projects nil
"Non-nil if projects should be sorted in
`counsel-projectile-switch-project'.
The sorting function can be modified by adding an entry for
`counsel-projectile-switch-project' in
`ivy-sort-functions-alist'."
:type 'boolean
:group 'counsel-projectile)
(defcustom counsel-projectile-remove-current-project nil
"Non-nil if current project should be removed from the
candidates list of `counsel-projectile-switch-project'."
@ -1110,12 +1139,9 @@ action."
(abbreviate-file-name (projectile-project-root)))
:action counsel-projectile-switch-project-action
:require-match t
:sort t
:sort counsel-projectile-sort-projects
:caller 'counsel-projectile-switch-project))
(unless (assq #'counsel-projectile-switch-project ivy-sort-functions-alist)
(push (list #'counsel-projectile-switch-project) ivy-sort-functions-alist))
;;;; counsel-projectile
(counsel-projectile--defcustom-action
@ -1155,17 +1181,39 @@ action."
(defun counsel-projectile--project-buffers-and-files (&rest _)
;; The ignored arguments are so that the function can be used as
;; collection function in `counsel-projectile'.
"Return a list of buffers and files in the current project."
(append
(setq counsel-projectile--buffers
(counsel-projectile--project-buffers))
(setq counsel-projectile--non-visited-files
(let ((root (projectile-project-root))
"Return a list of buffers and non-visited files in the current
project. Buffers and files are separately sorted depending on
`counsel-projectile-sort-buffers' and
`counsel-projectile-sort-files', respectively."
(let ((buffers (counsel-projectile--project-buffers))
(files (projectile-current-project-files))
file)
(dolist (buffer counsel-projectile--buffers files)
(root (projectile-project-root))
file sort-fn)
;; Remove files that are visited by a buffer:
(dolist (buffer buffers files)
(when (setq file (buffer-file-name (get-buffer buffer)))
(setq files (remove (file-relative-name file root) files))))))))
(setq files (remove (file-relative-name file root) files))))
;; Sort buffers and files depending on
;; `counsel-projectile-sort-buffers' and
;; `counsel-projectile-sort-files', respectively.
;; We need to do this here because matching will be done against
;; the variables `counsel-projectile--buffers' and
;; `counsel-projectile--non-visited-files', not against the
;; returned collection, so ivy's native sorting mechanism won't
;; work.
(when (and counsel-projectile-sort-buffers
(<= (length buffers) ivy-sort-max-size)
(setq sort-fn (ivy--sort-function 'counsel-projectile-switch-to-buffer)))
(setq buffers (sort (copy-sequence buffers) sort-fn)))
(when (and counsel-projectile-sort-files
(<= (length files) ivy-sort-max-size)
(setq sort-fn (ivy--sort-function 'counsel-projectile-find-file)))
(setq files (sort (copy-sequence files) sort-fn)))
;; Finally, bind `counsel-projectile--buffers' and
;; `counsel-projectile--non-visited-files' and return the whole
;; collection.
(append (setq counsel-projectile--buffers buffers)
(setq counsel-projectile--non-visited-files files))))
(defun counsel-projectile--matcher (regexp _candidates)
"Return REGEXP-matching CANDIDATES for `counsel-projectile'.
@ -1237,14 +1285,10 @@ If not inside a project, call `counsel-projectile-switch-project'."
#'counsel-projectile--project-buffers-and-files
:matcher #'counsel-projectile--matcher
:require-match t
:sort t
:action counsel-projectile-action
:keymap counsel-projectile-map
:caller 'counsel-projectile)))
(unless (assq #'counsel-projectile--project-buffers-and-files ivy-sort-functions-alist)
(push (list #'counsel-projectile--project-buffers-and-files) ivy-sort-functions-alist))
(ivy-set-display-transformer
'counsel-projectile
'counsel-projectile-transformer)