From c752c843ef60206c08d442dd203a386f4ca80ddd Mon Sep 17 00:00:00 2001 From: Eric Danan Date: Sat, 9 Jun 2018 23:12:51 +0200 Subject: [PATCH] Rewrite sorting mechanism See the "Sorting candidates" section of the README. --- README.md | 15 ++++-- counsel-projectile.el | 106 ++++++++++++++++++++++++++++++------------ 2 files changed, 86 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index d0c202f..e246c94 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/counsel-projectile.el b/counsel-projectile.el index b47d304..7bd9865 100644 --- a/counsel-projectile.el +++ b/counsel-projectile.el @@ -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)) - (files (projectile-current-project-files)) - file) - (dolist (buffer counsel-projectile--buffers files) - (when (setq file (buffer-file-name (get-buffer buffer))) - (setq files (remove (file-relative-name file root) files)))))))) + "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)) + (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)))) + ;; 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)