mirror of https://github.com/Chizi123/.emacs.d.git

Chizi123
2018-11-18 76bbd07de7add0f9d13c6914f158d19630fe2f62
commit | author | age
5cb5f7 1 ;;; helm-buffers.el --- helm support for buffers. -*- lexical-binding: t -*-
C 2
3 ;; Copyright (C) 2012 ~ 2018 Thierry Volpiatto <thierry.volpiatto@gmail.com>
4
5 ;; This program is free software; you can redistribute it and/or modify
6 ;; it under the terms of the GNU General Public License as published by
7 ;; the Free Software Foundation, either version 3 of the License, or
8 ;; (at your option) any later version.
9
10 ;; This program is distributed in the hope that it will be useful,
11 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
12 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 ;; GNU General Public License for more details.
14
15 ;; You should have received a copy of the GNU General Public License
16 ;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
18 ;;; Code:
19
20 (require 'cl-lib)
21 (require 'helm)
22 (require 'helm-types)
23 (require 'helm-utils)
24 (require 'helm-grep)
25 (require 'helm-regexp)
26 (require 'helm-help)
27
28 (declare-function ido-make-buffer-list "ido" (default))
29 (declare-function ido-add-virtual-buffers-to-list "ido")
30 (declare-function helm-comp-read "helm-mode")
31 (declare-function helm-browse-project "helm-files")
32
33
34 (defgroup helm-buffers nil
35   "Buffers related Applications and libraries for Helm."
36   :group 'helm)
37
38 (defcustom helm-boring-buffer-regexp-list
39   '("\\` " "\\`\\*helm" "\\`\\*Echo Area" "\\`\\*Minibuf")
40   "The regexp list that match boring buffers.
41 Buffer candidates matching these regular expression will be
42 filtered from the list of candidates if the
43 `helm-skip-boring-buffers' candidate transformer is used."
44   :type  '(repeat (choice regexp))
45   :group 'helm-buffers)
46
47 (defcustom helm-white-buffer-regexp-list nil
48   "The regexp list of not boring buffers.
49 These buffers will be displayed even if they match one of
50 `helm-boring-buffer-regexp-list'."
51   :type '(repeat (choice regexp))
52   :group 'helm-buffers)
53
54 (defcustom helm-buffers-favorite-modes '(lisp-interaction-mode
55                                          emacs-lisp-mode
56                                          text-mode
57                                          org-mode)
58   "List of preferred mode to open new buffers with."
59   :type '(repeat (choice function))
60   :group 'helm-buffers)
61
62 (defcustom helm-buffer-max-length 20
63   "Max length of buffer names before truncate.
64 When disabled (nil) use the longest buffer-name length found."
65   :group 'helm-buffers
66   :type  '(choice (const :tag "Disabled" nil)
67            (integer :tag "Length before truncate")))
68
69 (defcustom helm-buffer-details-flag t
70   "Always show details in buffer list when non--nil."
71   :group 'helm-buffers
72   :type 'boolean)
73
74 (defcustom helm-buffers-fuzzy-matching nil
75   "Fuzzy matching buffer names when non--nil.
76 Only buffer names are fuzzy matched when this is enabled,
77 `major-mode' matching is not affected by this."
78   :group 'helm-buffers
79   :type 'boolean)
80
81 (defcustom helm-buffer-skip-remote-checking nil
82   "Ignore checking for `file-exists-p' on remote files."
83   :group 'helm-buffers
84   :type 'boolean)
85
86 (defcustom helm-buffers-truncate-lines t
87   "Truncate lines in `helm-buffers-list' when non--nil."
88   :group 'helm-buffers
89   :type 'boolean)
90
91 (defcustom helm-mini-default-sources '(helm-source-buffers-list
92                                        helm-source-recentf
93                                        helm-source-buffer-not-found)
94   "Default sources list used in `helm-mini'.
95
96 When adding a source here it is up to you to ensure the library of
97 this source is accessible and properly loaded."
98   :group 'helm-buffers
99   :type '(repeat (choice symbol)))
100
101 (defcustom helm-buffers-end-truncated-string "..."
102   "The string to display at end of truncated buffer names."
103   :type 'string
104   :group 'helm-buffers)
105
106 (defcustom helm-buffers-column-separator "  "
107   "Separator for columns in buffer listing."
108   :type 'string
109   :group 'helm-buffers)
110
111 (defcustom helm-buffer--pretty-names '((dired-mode . "Dired")
112                                        (lisp-interaction-mode . "Lisp Inter"))
113   "An alist specifying pretty names for modes.
114 Most of the time buffer's `mode-name' is a string so no need to add it
115 here as there is no need to compute it, but sometimes it may be a
116 mode-line specification which may be costly to compute, in this case
117 add here the pretty name as a string to avoid this costly computation.
118 Also if some pretty names are too long you can add your own
119 abbreviation here."
120   :type '(alist :key-type symbol :value-type string)
121   :group 'helm-buffers)
122
123 ;;; Faces
124 ;;
125 ;;
126 (defgroup helm-buffers-faces nil
127   "Customize the appearance of helm-buffers."
128   :prefix "helm-"
129   :group 'helm-buffers
130   :group 'helm-faces)
131
132 (defface helm-buffer-saved-out
133     '((t (:foreground "red" :background "black")))
134   "Face used for buffer files modified outside of emacs."
135   :group 'helm-buffers-faces)
136
137 (defface helm-buffer-not-saved
138     '((t (:foreground "Indianred2")))
139   "Face used for buffer files not already saved on disk."
140   :group 'helm-buffers-faces)
141
142 (defface helm-buffer-modified
143     '((t :inherit font-lock-comment-face))
144   "Face used for modified buffers."
145   :group 'helm-buffers-faces)
146
147 (defface helm-buffer-size
148     '((((background dark)) :foreground "RosyBrown")
149       (((background light)) :foreground "SlateGray"))
150   "Face used for buffer size."
151   :group 'helm-buffers-faces)
152
153 (defface helm-buffer-process
154     '((t (:foreground "Sienna3")))
155   "Face used for process status in buffer."
156   :group 'helm-buffers-faces)
157
158 (defface helm-buffer-directory
159     '((t (:foreground "DarkRed" :background "LightGray")))
160   "Face used for directories in `helm-buffers-list'."
161   :group 'helm-buffers-faces)
162
163 (defface helm-buffer-file
164     '((t :inherit font-lock-builtin-face))
165   "Face for buffer file names in `helm-buffers-list'."
166   :group 'helm-buffers-faces)
167
168 (defface helm-buffer-archive
169     '((t (:foreground "Gold")))
170   "Face for archive file names in `helm-buffers-list'."
171   :group 'helm-buffers-faces)
172
173 (defface helm-non-file-buffer
174     '((t (:inherit italic)))
175   "Face used for non-file buffers in `helm-buffers-list'."
176   :group 'helm-buffers-faces)
177
178 (defvar helm-buffers-tick-counter nil
179   "Allows recording local changes to a non-file buffer.
180 Typical usage of this var is for modes that want to see if
181 their buffers have changed since last visit.
182 Such programs may want to record tick counter after visiting their
183 buffers like this:
184
185     (setq helm-buffers-tick-counter (buffer-modified-tick))
186
187 Note that this variable is buffer-local.")
188 (make-variable-buffer-local 'helm-buffers-tick-counter)
189
190
191 ;;; Buffers keymap
192 ;;
193 (defvar helm-buffer-map
194   (let ((map (make-sparse-keymap)))
195     (set-keymap-parent map helm-map)
196     ;; No need to have separate command for grep and zgrep
197     ;; as we don't use recursivity for buffers.
198     ;; So use zgrep for both as it is capable to handle non--compressed files.
199     (define-key map (kbd "M-g s")     'helm-buffer-run-zgrep)
200     (define-key map (kbd "C-s")       'helm-buffers-run-multi-occur)
201     (define-key map (kbd "C-x C-d")   'helm-buffers-run-browse-project)
202     (define-key map (kbd "C-c o")     'helm-buffer-switch-other-window)
203     (define-key map (kbd "C-c C-o")   'helm-buffer-switch-other-frame)
204     (define-key map (kbd "C-c =")     'helm-buffer-run-ediff)
205     (define-key map (kbd "M-=")       'helm-buffer-run-ediff-merge)
206     (define-key map (kbd "C-=")       'helm-buffer-diff-persistent)
207     (define-key map (kbd "M-G")       'helm-buffer-revert-persistent)
208     (define-key map (kbd "C-c d")     'helm-buffer-run-kill-persistent)
209     (define-key map (kbd "M-D")       'helm-buffer-run-kill-buffers)
210     (define-key map (kbd "C-x C-s")   'helm-buffer-save-persistent)
211     (define-key map (kbd "C-M-%")     'helm-buffer-run-query-replace-regexp)
212     (define-key map (kbd "M-%")       'helm-buffer-run-query-replace)
213     (define-key map (kbd "M-R")       'helm-buffer-run-rename-buffer)
214     (define-key map (kbd "M-m")       'helm-toggle-all-marks)
215     (define-key map (kbd "M-a")       'helm-mark-all)
216     (define-key map (kbd "C-]")       'helm-toggle-buffers-details)
217     (define-key map (kbd "C-c a")     'helm-buffers-toggle-show-hidden-buffers)
218     (define-key map (kbd "C-M-SPC")   'helm-buffers-mark-similar-buffers)
219     map)
220   "Keymap for buffer sources in helm.")
221
222 (defvar helm-buffers-ido-virtual-map
223   (let ((map (make-sparse-keymap)))
224     (set-keymap-parent map helm-map)
225     (define-key map (kbd "C-c o")   'helm-ff-run-switch-other-window)
226     (define-key map (kbd "C-c C-o") 'helm-ff-run-switch-other-frame)
227     (define-key map (kbd "M-g s")   'helm-ff-run-grep)
228     (define-key map (kbd "M-g z")   'helm-ff-run-zgrep)
229     (define-key map (kbd "M-D")     'helm-ff-run-delete-file)
230     (define-key map (kbd "C-c C-x") 'helm-ff-run-open-file-externally)
231     map))
232
233
234 (defvar helm-buffer-max-len-mode nil)
235 (defvar helm-buffers-in-project-p nil)
236 (defvar helm-source-buffers-list nil)
237
238 (defun helm-buffers-list--init ()
239   (require 'dired)
240   ;; Issue #51 Create the list before `helm-buffer' creation.
241   ;; We were using a global cache in the past and 'candidates was
242   ;; bound to this cache, this was a problem when using more than one
243   ;; source with a different 'buffer-list fn as the same cache was
244   ;; reused in each source (issue #1907), now 'candidates attr is set
245   ;; directly so that each list of candidates is local to source.
246   (helm-attrset 'candidates (funcall (helm-attr 'buffer-list)))
247   (let ((result (cl-loop with allbufs = (memq 'helm-shadow-boring-buffers
248                                               (helm-attr
249                                                'filtered-candidate-transformer
250                                                helm-source-buffers-list))
251                          for b in (if allbufs
252                                       (helm-attr 'candidates)
253                                     (helm-skip-boring-buffers
254                                      (helm-attr 'candidates)
255                                      helm-source-buffers-list))
256                          maximize (length b) into len-buf
257                          maximize (length (helm-buffer--format-mode-name b))
258                          into len-mode
259                          finally return (cons len-buf len-mode))))
260     (unless (default-value 'helm-buffer-max-length)
261       (helm-set-local-variable 'helm-buffer-max-length (car result)))
262     (unless (default-value 'helm-buffer-max-len-mode)
263       (helm-set-local-variable 'helm-buffer-max-len-mode (cdr result)))))
264
265 (defclass helm-source-buffers (helm-source-sync helm-type-buffer)
266   ((buffer-list
267     :initarg :buffer-list
268     :initform #'helm-buffer-list
269     :custom function
270     :documentation
271     "  A function with no arguments to create buffer list.")
272    (init :initform 'helm-buffers-list--init)
273    (multimatch :initform nil)
274    (match :initform 'helm-buffers-match-function)
275    (persistent-action :initform 'helm-buffers-list-persistent-action)
276    (keymap :initform helm-buffer-map)
277    (migemo :initform 'nomultimatch)
278    (volatile :initform t)
279    (nohighlight :initform t)
280    (resume :initform (lambda () (setq helm-buffers-in-project-p nil)))
281    (help-message :initform 'helm-buffer-help-message)))
282
283 (defvar helm-source-buffer-not-found
284   (helm-build-dummy-source
285    "Create buffer"
286    :action (helm-make-actions
287             "Create buffer (C-u choose mode)"
288             (lambda (candidate)
289              (let ((mjm (or (and helm-current-prefix-arg
290                                  (intern-soft (helm-comp-read
291                                                "Major-mode: "
292                                                helm-buffers-favorite-modes)))
293                             (cl-loop for (r . m) in auto-mode-alist
294                                      when (string-match r candidate)
295                                      return m)))
296                    (buffer (get-buffer-create candidate)))
297                (if mjm
298                    (with-current-buffer buffer (funcall mjm))
299                    (set-buffer-major-mode buffer))
300                (switch-to-buffer buffer))))))
301
302 (defvar ido-temp-list)
303 (defvar ido-ignored-list)
304 (defvar ido-process-ignore-lists)
305 (defvar ido-use-virtual-buffers)
306 (defvar ido-virtual-buffers)
307
308 (defvar helm-source-ido-virtual-buffers
309   (helm-build-sync-source "Ido virtual buffers"
310     :candidates (lambda ()
311                   (let (ido-temp-list
312                         ido-ignored-list
313                         (ido-process-ignore-lists t))
314                     (when ido-use-virtual-buffers
315                       (ido-add-virtual-buffers-to-list)
316                       ido-virtual-buffers)))
317     :fuzzy-match helm-buffers-fuzzy-matching
318     :keymap helm-buffers-ido-virtual-map
319     :help-message 'helm-buffers-ido-virtual-help-message
320     :action '(("Find file" . helm-find-many-files)
321               ("Find file other window" . find-file-other-window)
322               ("Find file other frame" . find-file-other-frame)
323               ("Find file as root" . helm-find-file-as-root)
324               ("Grep File(s) `C-u recurse'" . helm-find-files-grep)
325               ("Zgrep File(s) `C-u Recurse'" . helm-ff-zgrep)
326               ("View file" . view-file)
327               ("Delete file(s)" . helm-delete-marked-files)
328               ("Open file externally (C-u to choose)"
329                . helm-open-file-externally))))
330
331
332 (defvar ido-use-virtual-buffers)
333 (defvar ido-ignore-buffers)
334 (defun helm-buffer-list ()
335   "Return the current list of buffers.
336 Currently visible buffers are put at the end of the list.
337 See `ido-make-buffer-list' for more infos."
338   (require 'ido)
339   (let ((ido-process-ignore-lists t)
340         ido-ignored-list
341         ido-ignore-buffers
342         ido-use-virtual-buffers)
343     (ido-make-buffer-list nil)))
344
345 (defun helm-buffer-size (buffer)
346   "Return size of BUFFER."
347   (with-current-buffer buffer
348     (save-restriction
349       (widen)
350       (helm-file-human-size
351        (- (position-bytes (point-max))
352           (position-bytes (point-min)))))))
353
354 (defun helm-buffer--show-details (buf-name prefix help-echo
355                                   size mode dir face1 face2
356                                   proc details type)
357   (append
358    (list
359     (concat prefix
360             (propertize buf-name 'face face1
361                         'help-echo help-echo
362                         'type type)))
363    (and details
364         (list size mode
365               (propertize
366                (if proc
367                    (format "(%s %s in `%s')"
368                            (process-name proc)
369                            (process-status proc) dir)
370                  (format "(in `%s')" dir))
371                'face face2)))))
372
373 (defun helm-buffer--format-mode-name (buf)
374   "Prevent using `format-mode-line' as much as possible."
375   (with-current-buffer buf
376     (helm-acond ((assq major-mode helm-buffer--pretty-names)
377                  (cdr it))
378                 ((stringp mode-name) mode-name)
379                 (t (format-mode-line mode-name)))))
380
381 (defun helm-buffer--details (buffer &optional details)
382   (require 'dired)
383   (let* ((mode (helm-buffer--format-mode-name buffer))
384          (buf (get-buffer buffer))
385          (size (propertize (helm-buffer-size buf)
386                            'face 'helm-buffer-size))
387          (proc (get-buffer-process buf))
388          (dir (with-current-buffer buffer
389                 (helm-aif default-directory (abbreviate-file-name it))))
390          (file-name (helm-aif (buffer-file-name buf) (abbreviate-file-name it)))
391          (name (buffer-name buf))
392          (name-prefix (when (and dir (file-remote-p dir))
393                         (propertize "@ " 'face 'helm-ff-prefix)))
394          (archive-p (and (fboundp 'tramp-archive-file-name-p)
395                          (tramp-archive-file-name-p dir))))
396     (when name-prefix
397       ;; Remote tramp buffer names may be hexified, make them more readable.
398       (setq dir  (helm-url-unhex-string dir)
399             name (helm-url-unhex-string name)))
400     ;; Handle tramp archive buffers specially.
401     (if archive-p
402         (helm-buffer--show-details
403          name name-prefix file-name size mode dir
404          'helm-buffer-archive 'helm-buffer-process nil details 'filebuf)
405       ;; No fancy things on remote buffers.
406       (if (and name-prefix helm-buffer-skip-remote-checking)
407           (helm-buffer--show-details
408            name name-prefix file-name size mode dir
409            'helm-buffer-file 'helm-buffer-process nil details 'filebuf)
410         (cond
411           (;; A dired buffer.
412            (rassoc buf dired-buffers)
413            (helm-buffer--show-details
414             name name-prefix dir size mode dir
415             'helm-buffer-directory 'helm-buffer-process nil details 'dired))
416           ;; A buffer file modified somewhere outside of emacs.=>red
417           ((and file-name
418                 (file-exists-p file-name)
419                 (not (verify-visited-file-modtime buf)))
420            (helm-buffer--show-details
421             name name-prefix file-name size mode dir
422             'helm-buffer-saved-out 'helm-buffer-process nil details 'modout))
423           ;; A new buffer file not already saved on disk (or a deleted file) .=>indianred2
424           ((and file-name (not (file-exists-p file-name)))
425            (helm-buffer--show-details
426             name name-prefix file-name size mode dir
427             'helm-buffer-not-saved 'helm-buffer-process nil details 'notsaved))
428           ;; A buffer file modified and not saved on disk.=>orange
429           ((and file-name (buffer-modified-p buf))
430            (helm-buffer--show-details
431             name name-prefix file-name size mode dir
432             'helm-buffer-modified 'helm-buffer-process nil details 'mod))
433           ;; A buffer file not modified and saved on disk.=>green
434           (file-name
435            (helm-buffer--show-details
436             name name-prefix file-name size mode dir
437             'helm-buffer-file 'helm-buffer-process nil details 'filebuf))
438           ;; A non-file, modified buffer
439           ((with-current-buffer name
440              (and helm-buffers-tick-counter
441                   (/= helm-buffers-tick-counter (buffer-modified-tick))))
442            (helm-buffer--show-details
443             name (and proc name-prefix) dir size mode dir
444             'helm-buffer-modified 'helm-buffer-process proc details 'nofile-mod))
445           ;; Any non--file buffer.=>italic
446           (t
447            (helm-buffer--show-details
448             name (and proc name-prefix) dir size mode dir
449             'helm-non-file-buffer 'helm-buffer-process proc details 'nofile)))))))
450
451 (defun helm-highlight-buffers (buffers _source)
452   "Transformer function to highlight BUFFERS list.
453 Should be called after others transformers i.e (boring buffers)."
454   (cl-loop for i in buffers
455            for (name size mode meta) = (if helm-buffer-details-flag
456                                            (helm-buffer--details i 'details)
457                                          (helm-buffer--details i))
458            for truncbuf = (if (> (string-width name) helm-buffer-max-length)
459                               (helm-substring-by-width
460                                name helm-buffer-max-length
461                                helm-buffers-end-truncated-string)
462                             (concat name
463                                     (make-string
464                                      (- (+ helm-buffer-max-length
465                                            (length
466                                             helm-buffers-end-truncated-string))
467                                         (string-width name))
468                                      ? )))
469            for len = (length mode)
470            when (> len helm-buffer-max-len-mode)
471            do (setq helm-buffer-max-len-mode len)
472            for fmode = (concat (make-string
473                                 (- (max helm-buffer-max-len-mode len) len) ? )
474                                mode)
475            ;; The max length of a number should be 1023.9X where X is the
476            ;; units, this is 7 characters.
477            for formatted-size = (and size (format "%7s" size))
478            collect (let ((helm-pattern (helm-buffers--pattern-sans-filters
479                                         (and helm-buffers-fuzzy-matching ""))))
480                      (cons (if helm-buffer-details-flag
481                                (concat
482                                 (funcall helm-fuzzy-matching-highlight-fn
483                                          truncbuf)
484                                 helm-buffers-column-separator
485                                 formatted-size
486                                 helm-buffers-column-separator
487                                 fmode
488                                 helm-buffers-column-separator
489                                 meta)
490                              (funcall helm-fuzzy-matching-highlight-fn name))
491                            (get-buffer i)))))
492
493 (defun helm-buffer--get-preselection (buffer)
494   (let ((bufname (buffer-name buffer)))
495     (when (and bufname
496                (file-remote-p (with-current-buffer bufname
497                                 default-directory)))
498       (setq bufname (concat "@ " (helm-url-unhex-string bufname))))
499     (concat "^"
500             (if (and (null helm-buffer-details-flag)
501                      (numberp helm-buffer-max-length)
502                      (> (string-width bufname)
503                         helm-buffer-max-length))
504                 (regexp-quote
505                  (helm-substring-by-width
506                   bufname helm-buffer-max-length
507                   helm-buffers-end-truncated-string))
508               (concat (regexp-quote bufname)
509                       (if helm-buffer-details-flag
510                           "$" "[[:blank:]]+"))))))
511
512 (defun helm-toggle-buffers-details ()
513   (interactive)
514   (with-helm-alive-p
515     (let* ((buf (helm-get-selection))
516            (preselect (helm-buffer--get-preselection buf)))
517       (setq helm-buffer-details-flag (not helm-buffer-details-flag))
518       (helm-update (lambda ()
519                      (helm-awhile (re-search-forward preselect nil t)
520                        (helm-mark-current-line)
521                        (when (equal buf (helm-get-selection))
522                          (cl-return t))))))))
523 (put 'helm-toggle-buffers-details 'helm-only t)
524
525 (defun helm-buffers--pattern-sans-filters (&optional separator)
526   (cl-loop for p in (helm-mm-split-pattern helm-pattern)
527            unless (member (substring p 0 1) '("*" "/" "@" "!"))
528            collect p into lst
529            finally return (mapconcat 'identity lst (or separator " "))))
530
531 (defun helm-buffers-sort-transformer (candidates source)
532   (if (string= helm-pattern "")
533       candidates
534       (if helm-buffers-fuzzy-matching
535           (let ((helm-pattern (helm-buffers--pattern-sans-filters)))
536             (funcall helm-fuzzy-sort-fn candidates source))
537           (sort candidates
538                 (lambda (s1 s2)
539                   (< (string-width s1) (string-width s2)))))))
540
541 (defun helm-buffers-mark-similar-buffers-1 ()
542   (with-helm-window
543     (let* ((src (helm-get-current-source))
544            (type (get-text-property
545                   0 'type (helm-get-selection nil 'withprop src))))
546       (save-excursion
547         (goto-char (helm-get-previous-header-pos))
548         (helm-next-line)
549         (let* ((next-head (helm-get-next-header-pos))
550                (end       (and next-head
551                                (save-excursion
552                                  (goto-char next-head)
553                                  (forward-line -1)
554                                  (point))))
555                (maxpoint  (or end (point-max))))
556           (while (< (point) maxpoint)
557             (helm-mark-current-line)
558             (let ((cand (helm-get-selection nil 'withprop src)))
559               (when (and (not (helm-this-visible-mark))
560                          (eq (get-text-property 0 'type cand) type))
561                 (helm-make-visible-mark)))
562             (forward-line 1) (end-of-line))))
563       (helm-mark-current-line)
564       (helm-display-mode-line src t)
565       (message "%s candidates marked" (length helm-marked-candidates)))))
566
567 (defun helm-buffers-mark-similar-buffers ()
568     "Mark All buffers that have same property `type' than current.
569 i.e same color."
570   (interactive)
571   (with-helm-alive-p
572     (let ((marked (helm-marked-candidates)))
573       (if (and (>= (length marked) 1)
574                (with-helm-window helm-visible-mark-overlays))
575           (helm-unmark-all)
576           (helm-buffers-mark-similar-buffers-1)))))
577 (put 'helm-buffers-mark-similar-buffers 'helm-only t)
578
579
580 ;;; match functions
581 ;;
582 (defun helm-buffer--match-mjm (pattern mjm)
583   (when (string-match "\\`\\*" pattern)
584     (cl-loop with patterns = (split-string (substring pattern 1) ",")
585              for pat in patterns
586              if (string-match "\\`!" pat)
587              collect (string-match (substring pat 1) mjm) into neg
588              else collect (string-match pat mjm) into pos
589              finally return
590              (let ((neg-test (cl-loop for i in neg thereis (numberp i)))
591                    (pos-test (cl-loop for i in pos thereis (numberp i))))
592                (or
593                 (and neg (not pos) (not neg-test))
594                 (and pos pos-test)
595                 (and neg neg-test (not neg-test)))))))
596
597 (defvar helm-buffer--memo-hash (make-hash-table :test 'equal))
598 (defun helm-buffer--memo-pattern (pattern)
599   (or (gethash pattern helm-buffer--memo-hash)
600       (puthash pattern (helm--mapconcat-pattern pattern)
601                helm-buffer--memo-hash)))
602
603 (defun helm-buffer--match-pattern (pattern candidate &optional nofuzzy)
604   (let ((bfn (if (and helm-buffers-fuzzy-matching
605                       (not nofuzzy)
606                       (not helm-migemo-mode)
607                       (not (string-match "\\`\\^" pattern)))
608                  #'helm-buffer--memo-pattern
609                  #'identity))
610         (mfn (if helm-migemo-mode
611                  #'helm-mm-migemo-string-match #'string-match)))
612     (if (string-match "\\`!" pattern)
613         (not (funcall mfn (funcall bfn (substring pattern 1))
614                       candidate))
615         (funcall mfn (funcall bfn pattern) candidate))))
616
617 (defun helm-buffers--match-from-mjm (candidate)
618   (let* ((cand (replace-regexp-in-string "^\\s-\\{1\\}" "" candidate))
619          (buf  (get-buffer cand))
620          (regexp (cl-loop with pattern = helm-pattern
621                           for p in (helm-mm-split-pattern pattern)
622                           when (string-match "\\`\\*" p)
623                           return p)))
624     (if regexp
625         (when buf
626           (with-current-buffer buf
627             (let ((mjm (symbol-name major-mode)))
628               (helm-buffer--match-mjm regexp mjm))))
629         t)))
630
631 (defun helm-buffers--match-from-pat (candidate)
632   (let* ((regexp-list (cl-loop with pattern = helm-pattern
633                                for p in (helm-mm-split-pattern pattern)
634                                unless (string-match
635                                        "\\`\\(\\*\\|/\\|@\\)" p)
636                                collect p))
637          (nofuzzy (cdr regexp-list)))
638     (if regexp-list
639         (cl-loop for re in regexp-list
640                  always (helm-buffer--match-pattern re candidate nofuzzy))
641         t)))
642
643 (defun helm-buffers--match-from-inside (candidate)
644   (let* ((cand (replace-regexp-in-string "^\\s-\\{1\\}" "" candidate))
645          (buf  (get-buffer cand))
646          (regexp (cl-loop with pattern = helm-pattern
647                           for p in (helm-mm-split-pattern pattern)
648                           when (string-match "\\`@\\(.*\\)" p)
649                           return (match-string 1 p))))
650     (if (and buf regexp)
651         (with-current-buffer buf
652           (save-excursion
653             (goto-char (point-min))
654             (if helm-migemo-mode
655                 (helm-mm-migemo-forward regexp nil t)
656              (re-search-forward regexp nil t))))
657         t)))
658
659 (defun helm-buffers--match-from-directory (candidate)
660   (let* ((cand (replace-regexp-in-string "^\\s-\\{1\\}" "" candidate))
661          (buf  (get-buffer cand))
662          (buf-fname (or (buffer-file-name buf)
663                         (car-safe (rassoc buf dired-buffers))))
664          (regexps (cl-loop with pattern = helm-pattern
665                           for p in (helm-mm-split-pattern pattern)
666                           when (string-match "\\`/" p)
667                           collect p)))
668     (if regexps
669         (cl-loop for re in regexps
670                  thereis
671                  (and buf-fname
672                       (string-match
673                        (substring re 1) (helm-basedir buf-fname))))
674         t)))
675
676 (defun helm-buffers-match-function (candidate)
677   "Default function to match buffers."
678   (and (helm-buffers--match-from-pat candidate)
679        (helm-buffers--match-from-mjm candidate)
680        (helm-buffers--match-from-inside candidate)
681        (helm-buffers--match-from-directory candidate)))
682
683
684 (defun helm-buffer-query-replace-1 (&optional regexp-flag buffers)
685   "Query replace in marked buffers.
686 If REGEXP-FLAG is given use `query-replace-regexp'."
687   (let ((prompt (if regexp-flag "Query replace regexp" "Query replace"))
688         (bufs   (or buffers (helm-marked-candidates)))
689         (helm--reading-passwd-or-string t))
690     (cl-loop with args = (query-replace-read-args prompt regexp-flag t)
691              for buf in bufs
692              do
693              (save-window-excursion
694                (switch-to-buffer buf)
695                (save-excursion
696                  (let ((case-fold-search t))
697                    (goto-char (point-min))
698                    (apply #'perform-replace
699                           (list (nth 0 args) (nth 1 args)
700                                 t regexp-flag (nth 2 args) nil
701                                 multi-query-replace-map))))))))
702
703 (defun helm-buffer-query-replace-regexp (_candidate)
704   (helm-buffer-query-replace-1 'regexp))
705
706 (defun helm-buffer-query-replace (_candidate)
707   (helm-buffer-query-replace-1))
708
709 (defun helm-buffer-toggle-diff (candidate)
710   "Toggle diff buffer CANDIDATE with it's file."
711   (helm-aif (get-buffer-window "*Diff*" 'visible)
712       (progn (kill-buffer "*Diff*")
713              (set-window-buffer it helm-current-buffer))
714     (diff-buffer-with-file (get-buffer candidate))))
715
716 (defun helm-buffer-diff-persistent ()
717   "Toggle diff buffer without quitting helm."
718   (interactive)
719   (with-helm-alive-p
720     (helm-attrset 'diff-action 'helm-buffer-toggle-diff)
721     (helm-execute-persistent-action 'diff-action)))
722 (put 'helm-buffer-diff-persistent 'helm-only t)
723
724 (defun helm-revert-buffer (candidate)
725   (with-current-buffer candidate
726     (helm-aif (buffer-file-name)
727         (and (file-exists-p it) (revert-buffer t t)))))
728
729 (defun helm-revert-marked-buffers (_ignore)
730   (mapc 'helm-revert-buffer (helm-marked-candidates)))
731
732 (defun helm-buffer-revert-and-update (_candidate)
733   (with-helm-buffer
734     (let ((marked (helm-marked-candidates))
735           (preselect (helm-buffers--quote-truncated-buffer
736                       (helm-get-selection))))
737       (cl-loop for buf in marked do (helm-revert-buffer buf))
738       (when helm-marked-candidates (helm-unmark-all))
739       (helm-update preselect))))
740
741 (defun helm-buffer-revert-persistent ()
742   "Revert buffer without quitting helm."
743   (interactive)
744   (with-helm-alive-p
745     (helm-attrset 'revert-action '(helm-buffer-revert-and-update . never-split))
746     (helm-execute-persistent-action 'revert-action)))
747 (put 'helm-buffer-revert-persistent 'helm-only t)
748
749 (defun helm-buffer-save-and-update (_candidate)
750   (with-helm-buffer
751     (let ((marked (helm-marked-candidates))
752           (preselect (helm-get-selection nil t))
753           (enable-recursive-minibuffers t))
754       (cl-loop for buf in marked do
755                (with-current-buffer (get-buffer buf)
756                  (when (buffer-file-name) (save-buffer))))
757       (when helm-marked-candidates (helm-unmark-all))
758       (helm-update (regexp-quote preselect)))))
759
760 (defun helm-buffer-save-persistent ()
761   "Save buffer without quitting helm."
762   (interactive)
763   (with-helm-alive-p
764     (helm-attrset 'save-action '(helm-buffer-save-and-update . never-split))
765     (helm-execute-persistent-action 'save-action)))
766 (put 'helm-buffer-save-persistent 'helm-only t)
767
768 (defun helm-buffers-rename-buffer (candidate)
769   (with-current-buffer candidate
770     (rename-buffer (helm-read-string "New name: " (buffer-name)) t)))
771
772 (defun helm-buffer-run-rename-buffer ()
773   "Run rename buffer action from `helm-source-buffers-list'."
774   (interactive)
775   (with-helm-alive-p
776     (helm-exit-and-execute-action 'helm-buffers-rename-buffer)))
777 (put 'helm-buffer-run-rename-buffer 'helm-only t)
778
779 (defun helm-buffer-run-kill-persistent ()
780   "Kill buffer without quitting helm."
781   (interactive)
782   (with-helm-alive-p
783     (helm-attrset 'kill-action '(helm-buffers-persistent-kill . never-split))
784     (helm-execute-persistent-action 'kill-action)))
785 (put 'helm-buffer-run-kill-persistent 'helm-only t)
786
787 (defun helm-kill-marked-buffers (_ignore)
788   (let* ((bufs (helm-marked-candidates))
789          (killed-bufs (cl-count-if 'kill-buffer bufs)))
790     (when (buffer-live-p helm-buffer)
791       (with-helm-buffer
792         (setq helm-marked-candidates nil
793               helm-visible-mark-overlays nil)))
794     (message "Killed %s buffer(s)" killed-bufs)))
795
796 (defun helm-buffer-run-kill-buffers ()
797   "Run kill buffer action from `helm-source-buffers-list'."
798   (interactive)
799   (with-helm-alive-p
800     (helm-exit-and-execute-action 'helm-kill-marked-buffers)))
801 (put 'helm-buffer-run-kill-buffers 'helm-only t)
802
803 (defun helm-buffer-run-grep ()
804   "Run Grep action from `helm-source-buffers-list'."
805   (interactive)
806   (with-helm-alive-p
807     (helm-exit-and-execute-action 'helm-grep-buffers)))
808 (put 'helm-buffer-run-grep 'helm-only t)
809
810 (defun helm-buffer-run-zgrep ()
811   "Run Grep action from `helm-source-buffers-list'."
812   (interactive)
813   (with-helm-alive-p
814     (helm-exit-and-execute-action 'helm-zgrep-buffers)))
815 (put 'helm-buffer-run-zgrep 'helm-only t)
816
817 (defun helm-buffer-run-query-replace-regexp ()
818   "Run Query replace regexp action from `helm-source-buffers-list'."
819   (interactive)
820   (with-helm-alive-p
821     (helm-exit-and-execute-action 'helm-buffer-query-replace-regexp)))
822 (put 'helm-buffer-run-query-replace-regexp 'helm-only t)
823
824 (defun helm-buffer-run-query-replace ()
825   "Run Query replace action from `helm-source-buffers-list'."
826   (interactive)
827   (with-helm-alive-p
828     (helm-exit-and-execute-action 'helm-buffer-query-replace)))
829 (put 'helm-buffer-run-query-replace 'helm-only t)
830
831 (defun helm-buffer-switch-other-window ()
832   "Run switch to other window action from `helm-source-buffers-list'."
833   (interactive)
834   (with-helm-alive-p
835     (helm-exit-and-execute-action 'helm-buffer-switch-buffers-other-window)))
836 (put 'helm-buffer-switch-other-window 'helm-only t)
837
838 (defun helm-buffer-switch-other-frame ()
839   "Run switch to other frame action from `helm-source-buffers-list'."
840   (interactive)
841   (with-helm-alive-p
842     (helm-exit-and-execute-action 'switch-to-buffer-other-frame)))
843 (put 'helm-buffer-switch-other-frame 'helm-only t)
844
845 (defun helm-buffer-switch-buffers (_candidate)
846   "Switch to buffer candidates and replace current buffer.
847
848 If more than one buffer marked switch to these buffers in separate windows.
849 If a prefix arg is given split windows vertically."
850   (let ((buffers (helm-marked-candidates)))
851     (helm-window-show-buffers buffers)))
852
853 (defun helm-buffer-switch-buffers-other-window (_candidate)
854   "Switch to marked buffers in other windows."
855   (let ((buffers (helm-marked-candidates)))
856     (helm-window-show-buffers buffers t)))
857
858 (defun helm-buffer-run-ediff ()
859   "Run ediff action from `helm-source-buffers-list'."
860   (interactive)
861   (with-helm-alive-p
862     (helm-exit-and-execute-action 'helm-ediff-marked-buffers)))
863 (put 'helm-buffer-run-ediff 'helm-only t)
864
865 (defun helm-buffer-run-ediff-merge ()
866   "Run ediff action from `helm-source-buffers-list'."
867   (interactive)
868   (with-helm-alive-p
869     (helm-exit-and-execute-action 'helm-ediff-marked-buffers-merge)))
870 (put 'helm-buffer-run-ediff-merge 'helm-only t)
871
872 (defun helm-buffers-persistent-kill-1 (buffer-or-name)
873   "Persistent action to kill buffer."
874   (let ((buf (get-buffer buffer-or-name)) helm-buf-or-cur)
875     (if (or (and (eql buf (get-buffer helm-current-buffer))
876                  (setq helm-buf-or-cur "helm-current-buffer"))
877             (and (eql buf (get-buffer helm-buffer))
878                  (setq helm-buf-or-cur "helm-buffer")))
879         (progn
880           (message "Can't kill `%s' without quitting session" helm-buf-or-cur)
881           (sit-for 1))
882       (kill-buffer buf)
883       (helm-delete-current-selection))))
884
885 (defun helm-buffers--quote-truncated-buffer (buffer)
886   (let ((bufname (and (bufferp buffer)
887                       (buffer-name buffer))))
888     (when (and bufname
889                (file-remote-p (with-current-buffer bufname
890                                 default-directory)))
891       (setq bufname (concat "@ " (helm-url-unhex-string bufname))))
892     (when bufname
893       (regexp-quote
894        (if (and helm-buffer-max-length
895                 helm-buffer-details-flag)
896            (helm-substring-by-width
897             bufname helm-buffer-max-length
898             "")
899          bufname)))))
900
901 (defun helm-buffers-persistent-kill (_buffer)
902   (let ((marked (helm-marked-candidates))
903         (sel    (helm-get-selection)))
904     (unwind-protect
905          (cl-loop for b in marked
906                   do (progn
907                        ;; We need to preselect each marked because
908                        ;; helm-buffers-persistent-kill is deleting
909                        ;; current selection.
910                        (helm-preselect
911                         (format "^%s"
912                                 (helm-buffers--quote-truncated-buffer b)))
913                        (helm-buffers-persistent-kill-1 b)
914                        (message nil)
915                        (helm--remove-marked-and-update-mode-line b)))
916       (with-helm-buffer
917         (setq helm-marked-candidates nil
918               helm-visible-mark-overlays nil))
919       (helm-force-update (helm-buffers--quote-truncated-buffer sel)))))
920
921 (defun helm-buffers-list-persistent-action (candidate)
922   (let ((current (window-buffer helm-persistent-action-display-window)))
923     (if (or (helm-follow-mode-p)
924             (eql current (get-buffer helm-current-buffer))
925             (not (eql current (get-buffer candidate))))
926         (switch-to-buffer candidate)
927       (if (and helm-persistent-action-display-window
928                (window-dedicated-p
929                 (next-window helm-persistent-action-display-window 1)))
930           (delete-window helm-persistent-action-display-window)
931         (switch-to-buffer helm-current-buffer)))))
932
933 (defun helm-ediff-marked-buffers (_candidate &optional merge)
934   "Ediff 2 marked buffers or CANDIDATE and `helm-current-buffer'.
935 With optional arg MERGE call `ediff-merge-buffers'."
936   (let ((lg-lst (length (helm-marked-candidates)))
937         buf1 buf2)
938     (cl-case lg-lst
939       (0
940        (error "Error:You have to mark at least 1 buffer"))
941       (1
942        (setq buf1 helm-current-buffer
943              buf2 (cl-first (helm-marked-candidates))))
944       (2
945        (setq buf1 (cl-first (helm-marked-candidates))
946              buf2 (cl-second (helm-marked-candidates))))
947       (t
948        (error "Error:Too many buffers marked!")))
949     (if merge
950         (ediff-merge-buffers buf1 buf2)
951       (ediff-buffers buf1 buf2))))
952
953 (defun helm-ediff-marked-buffers-merge (candidate)
954   "Ediff merge `helm-current-buffer' with CANDIDATE.
955 See `helm-ediff-marked-buffers'."
956   (helm-ediff-marked-buffers candidate t))
957
958 (defun helm-multi-occur-as-action (_candidate)
959   "Multi occur action for `helm-source-buffers-list'.
960 Can be used by any source that list buffers."
961   (let ((helm-moccur-always-search-in-current
962          (if helm-current-prefix-arg
963              (not helm-moccur-always-search-in-current)
964            helm-moccur-always-search-in-current))
965         (buffers (helm-marked-candidates))
966         (input (cl-loop for i in (split-string helm-pattern " " t)
967                      thereis (and (string-match "\\`@\\(.*\\)" i)
968                                   (match-string 1 i)))))
969     (helm-multi-occur-1 buffers input)))
970
971 (defun helm-buffers-run-multi-occur ()
972   "Run `helm-multi-occur-as-action' by key."
973   (interactive)
974   (with-helm-alive-p
975     (helm-exit-and-execute-action 'helm-multi-occur-as-action)))
976 (put 'helm-buffers-run-multi-occur 'helm-only t)
977
978 (defun helm-buffers-toggle-show-hidden-buffers ()
979   (interactive)
980   (with-helm-alive-p
981     (let ((filter-attrs (helm-attr 'filtered-candidate-transformer
982                                    helm-source-buffers-list)))
983       (if (memq 'helm-shadow-boring-buffers filter-attrs)
984           (helm-attrset 'filtered-candidate-transformer
985                         (cons 'helm-skip-boring-buffers
986                               (remove 'helm-shadow-boring-buffers
987                                       filter-attrs))
988                         helm-source-buffers-list)
989         (helm-attrset 'filtered-candidate-transformer
990                       (cons 'helm-shadow-boring-buffers
991                             (remove 'helm-skip-boring-buffers
992                                     filter-attrs))
993                       helm-source-buffers-list))
994       (helm-force-update))))
995 (put 'helm-buffers-toggle-show-hidden-buffers 'helm-only t)
996
997 (defun helm-buffers-browse-project (buf)
998   "Browse project from buffer."
999   (with-current-buffer buf
1000     (helm-browse-project helm-current-prefix-arg)))
1001
1002 (defun helm-buffers-run-browse-project ()
1003   "Run `helm-buffers-browse-project' from key."
1004   (interactive)
1005   (with-helm-alive-p
1006       (if helm-buffers-in-project-p
1007           (user-error "You are already browsing this project")
1008           (helm-exit-and-execute-action 'helm-buffers-browse-project))))
1009
1010 ;;; Candidate Transformers
1011 ;;
1012 ;;
1013 (defun helm-skip-boring-buffers (buffers _source)
1014   (helm-skip-entries buffers
1015                      helm-boring-buffer-regexp-list
1016                      helm-white-buffer-regexp-list))
1017
1018 (defun helm-shadow-boring-buffers (buffers _source)
1019   "Buffers matching `helm-boring-buffer-regexp' will be
1020 displayed with the `file-name-shadow' face if available."
1021   (helm-shadow-entries buffers helm-boring-buffer-regexp-list))
1022
1023
1024 ;;;###autoload
1025 (defun helm-buffers-list ()
1026   "Preconfigured `helm' to list buffers."
1027   (interactive)
1028   (unless helm-source-buffers-list
1029     (setq helm-source-buffers-list
1030           (helm-make-source "Buffers" 'helm-source-buffers)))
1031   (helm :sources '(helm-source-buffers-list
1032                    helm-source-ido-virtual-buffers
1033                    helm-source-buffer-not-found)
1034         :buffer "*helm buffers*"
1035         :keymap helm-buffer-map
1036         :truncate-lines helm-buffers-truncate-lines))
1037
1038 ;;;###autoload
1039 (defun helm-mini ()
1040   "Preconfigured `helm' lightweight version \(buffer -> recentf\)."
1041   (interactive)
1042   (require 'helm-x-files)
1043   (unless helm-source-buffers-list
1044     (setq helm-source-buffers-list
1045           (helm-make-source "Buffers" 'helm-source-buffers)))
1046   (helm :sources helm-mini-default-sources
1047         :buffer "*helm mini*"
1048         :ff-transformer-show-only-basename nil
1049         :truncate-lines helm-buffers-truncate-lines))
1050
1051 (defun helm-quit-and-helm-mini ()
1052   "Drop into `helm-mini' from `helm'."
1053   (interactive)
1054   (with-helm-alive-p
1055     (helm-run-after-exit 'helm-mini)))
1056
1057 (provide 'helm-buffers)
1058
1059 ;; Local Variables:
1060 ;; byte-compile-warnings: (not obsolete)
1061 ;; coding: utf-8
1062 ;; indent-tabs-mode: nil
1063 ;; End:
1064
1065 ;;; helm-buffers.el ends here