;;; helm-lib.el --- Helm routines. -*- lexical-binding: t -*-
|
|
|
|
;; Copyright (C) 2015 ~ 2019 Thierry Volpiatto <thierry.volpiatto@gmail.com>
|
|
|
|
;; Author: Thierry Volpiatto <thierry.volpiatto@gmail.com>
|
|
;; URL: http://github.com/emacs-helm/helm
|
|
|
|
;; This program is free software; you can redistribute it and/or modify
|
|
;; it under the terms of the GNU General Public License as published by
|
|
;; the Free Software Foundation, either version 3 of the License, or
|
|
;; (at your option) any later version.
|
|
|
|
;; This program is distributed in the hope that it will be useful,
|
|
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
;; GNU General Public License for more details.
|
|
|
|
;; You should have received a copy of the GNU General Public License
|
|
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
;;; Commentary:
|
|
;; All helm functions that don't require specific helm code should go here.
|
|
|
|
;;; Code:
|
|
|
|
(require 'cl-lib)
|
|
|
|
(declare-function wdired-change-to-dired-mode "wdired.el")
|
|
(declare-function wdired-do-symlink-changes "wdired.el")
|
|
(declare-function wdired-do-perm-changes "wdired.el")
|
|
(declare-function wdired-get-filename "wdired.el")
|
|
(declare-function wdired-do-renames "wdired.el")
|
|
(declare-function wdired-flag-for-deletion "wdired.el")
|
|
(declare-function wdired-normalize-filename "wdired.el")
|
|
(declare-function dired-mark-remembered "dired.el")
|
|
(declare-function dired-log-summary "dired.el")
|
|
(declare-function dired-current-directory "dired.el")
|
|
(declare-function ansi-color--find-face "ansi-color.el")
|
|
(declare-function ansi-color-apply-sequence "ansi-color.el")
|
|
(declare-function helm-get-sources "helm.el")
|
|
(declare-function helm-marked-candidates "helm.el")
|
|
(declare-function helm-follow-mode-p "helm.el")
|
|
(declare-function helm-attr "helm.el")
|
|
(declare-function helm-attrset "helm.el")
|
|
(declare-function org-open-at-point "org.el")
|
|
(declare-function org-content "org.el")
|
|
(declare-function org-mark-ring-goto "org.el")
|
|
(declare-function org-mark-ring-push "org.el")
|
|
(declare-function helm-interpret-value "helm.el")
|
|
(declare-function helm-get-current-source "helm.el")
|
|
(declare-function helm-source--cl--print-table "helm-source.el")
|
|
(defvar helm-sources)
|
|
(defvar helm-initial-frame)
|
|
(defvar helm-current-position)
|
|
(defvar wdired-old-marks)
|
|
(defvar wdired-keep-marker-rename)
|
|
(defvar wdired-allow-to-change-permissions)
|
|
(defvar wdired-allow-to-redirect-links)
|
|
(defvar helm-persistent-action-display-window)
|
|
(defvar completion-flex-nospace)
|
|
|
|
;;; User vars.
|
|
;;
|
|
(defcustom helm-file-globstar t
|
|
"Same as globstar bash shopt option.
|
|
When non--nil a pattern beginning with two stars will expand recursively.
|
|
Directories expansion is not supported yet."
|
|
:group 'helm
|
|
:type 'boolean)
|
|
|
|
(defcustom helm-yank-text-at-point-function nil
|
|
"The function used to forward point with `helm-yank-text-at-point'.
|
|
With a nil value, fallback to default `forward-word'.
|
|
The function should take one arg, an integer like `forward-word'.
|
|
NOTE: Using `forward-symbol' here is not very useful as it is already
|
|
provided by \\<helm-map>\\[next-history-element]."
|
|
:type 'function
|
|
:group 'helm)
|
|
|
|
(defcustom helm-scroll-amount nil
|
|
"Scroll amount when scrolling other window in a helm session.
|
|
It is used by `helm-scroll-other-window'
|
|
and `helm-scroll-other-window-down'.
|
|
|
|
If you prefer scrolling line by line, set this value to 1."
|
|
:group 'helm
|
|
:type 'integer)
|
|
|
|
(defcustom helm-help-full-frame t
|
|
"Display help window in full frame when non nil.
|
|
|
|
Even when `nil' probably the same result (full frame)
|
|
can be reach by tweaking `display-buffer-alist' but it is
|
|
much more convenient to use a simple boolean value here."
|
|
:type 'boolean
|
|
:group 'helm-help)
|
|
|
|
(defvar helm-ff--boring-regexp nil)
|
|
(defun helm-ff--setup-boring-regex (var val)
|
|
(set var val)
|
|
(setq helm-ff--boring-regexp
|
|
(cl-loop with last = (car (last val))
|
|
for r in (butlast val)
|
|
if (string-match "\\$\\'" r)
|
|
concat (concat r "\\|") into result
|
|
else concat (concat r "$\\|") into result
|
|
finally return
|
|
(concat result last
|
|
(if (string-match "\\$\\'" last) "" "$")))))
|
|
|
|
(defcustom helm-boring-file-regexp-list
|
|
(mapcar (lambda (f)
|
|
(let ((rgx (regexp-quote f)))
|
|
(if (string-match-p "[^/]$" f)
|
|
;; files: e.g .o => \\.o$
|
|
(concat rgx "$")
|
|
;; directories: e.g .git/ => \.git\\(/\\|$\\)
|
|
(concat (substring rgx 0 -1) "\\(/\\|$\\)"))))
|
|
completion-ignored-extensions)
|
|
"A list of regexps matching boring files.
|
|
|
|
This list is build by default on `completion-ignored-extensions'.
|
|
The directory names should end with \"/?\" e.g. \"\\.git/?\" and the
|
|
file names should end with \"$\" e.g. \"\\.o$\".
|
|
|
|
These regexps may be used to match the entire path, not just the file
|
|
name, so for example to ignore files with a prefix \".bak.\", use
|
|
\"\\.bak\\..*$\" as the regexp.
|
|
|
|
NOTE: When modifying this, be sure to use customize interface or the
|
|
customize functions e.g. `customize-set-variable' and NOT `setq'."
|
|
:group 'helm-files
|
|
:type '(repeat (choice regexp))
|
|
:set 'helm-ff--setup-boring-regex)
|
|
|
|
(defcustom helm-describe-function-function 'describe-function
|
|
"Function used to describe functions in Helm."
|
|
:group 'helm-elisp
|
|
:type 'function)
|
|
|
|
(defcustom helm-describe-variable-function 'describe-variable
|
|
"Function used to describe variables in Helm."
|
|
:group 'helm-elisp
|
|
:type 'function)
|
|
|
|
|
|
;;; Internal vars
|
|
;;
|
|
(defvar helm-yank-point nil)
|
|
(defvar helm-pattern ""
|
|
"The input pattern used to update the helm buffer.")
|
|
(defvar helm-buffer "*helm*"
|
|
"Buffer showing completions.")
|
|
(defvar helm-current-buffer nil
|
|
"Current buffer when `helm' is invoked.")
|
|
(defvar helm-suspend-update-flag nil)
|
|
(defvar helm-action-buffer "*helm action*"
|
|
"Buffer showing actions.")
|
|
|
|
|
|
;;; Compatibility
|
|
;;
|
|
(defun helm-add-face-text-properties (beg end face &optional append object)
|
|
"Add the face property to the text from START to END.
|
|
It is a compatibility function which behave exactly like
|
|
`add-face-text-property' if available otherwise like `add-text-properties'.
|
|
When only `add-text-properties' is available APPEND is ignored."
|
|
(if (fboundp 'add-face-text-property)
|
|
(add-face-text-property beg end face append object)
|
|
(add-text-properties beg end `(face ,face) object)))
|
|
|
|
;; Override `wdired-finish-edit'.
|
|
;; Fix emacs bug in `wdired-finish-edit' where
|
|
;; Wdired is not handling the case where `dired-directory' is a cons
|
|
;; cell instead of a string.
|
|
(defun helm--advice-wdired-finish-edit ()
|
|
(interactive)
|
|
(wdired-change-to-dired-mode)
|
|
(let ((changes nil)
|
|
(errors 0)
|
|
files-deleted
|
|
files-renamed
|
|
some-file-names-unchanged
|
|
file-old file-new tmp-value)
|
|
(save-excursion
|
|
(when (and wdired-allow-to-redirect-links
|
|
(fboundp 'make-symbolic-link))
|
|
(setq tmp-value (wdired-do-symlink-changes))
|
|
(setq errors (cdr tmp-value))
|
|
(setq changes (car tmp-value)))
|
|
(when (and wdired-allow-to-change-permissions
|
|
(boundp 'wdired-col-perm)) ; could have been changed
|
|
(setq tmp-value (wdired-do-perm-changes))
|
|
(setq errors (+ errors (cdr tmp-value)))
|
|
(setq changes (or changes (car tmp-value))))
|
|
(goto-char (point-max))
|
|
(while (not (bobp))
|
|
(setq file-old (wdired-get-filename nil t))
|
|
(when file-old
|
|
(setq file-new (wdired-get-filename))
|
|
(if (equal file-new file-old)
|
|
(setq some-file-names-unchanged t)
|
|
(setq changes t)
|
|
(if (not file-new) ;empty filename!
|
|
(push file-old files-deleted)
|
|
(when wdired-keep-marker-rename
|
|
(let ((mark (cond ((integerp wdired-keep-marker-rename)
|
|
wdired-keep-marker-rename)
|
|
(wdired-keep-marker-rename
|
|
(cdr (assoc file-old wdired-old-marks)))
|
|
(t nil))))
|
|
(when mark
|
|
(push (cons (substitute-in-file-name file-new) mark)
|
|
wdired-old-marks))))
|
|
(push (cons file-old (substitute-in-file-name file-new))
|
|
files-renamed))))
|
|
(forward-line -1)))
|
|
(when files-renamed
|
|
(setq errors (+ errors (wdired-do-renames files-renamed))))
|
|
(if changes
|
|
(progn
|
|
;; If we are displaying a single file (rather than the
|
|
;; contents of a directory), change dired-directory if that
|
|
;; file was renamed. (This ought to be generalized to
|
|
;; handle the multiple files case, but that's less trivial)
|
|
;; fixit [1].
|
|
(cond ((and (stringp dired-directory)
|
|
(not (file-directory-p dired-directory))
|
|
(null some-file-names-unchanged)
|
|
(= (length files-renamed) 1))
|
|
(setq dired-directory (cdr (car files-renamed))))
|
|
;; Fix [1] i.e dired buffers created with
|
|
;; (dired '(foo f1 f2 f3)).
|
|
((and (consp dired-directory)
|
|
(cdr dired-directory)
|
|
files-renamed)
|
|
(setcdr dired-directory
|
|
;; Replace in `dired-directory' files that have
|
|
;; been modified with their new name keeping
|
|
;; the ones that are unmodified at the same place.
|
|
(cl-loop with old-to-rename = (mapcar 'car files-renamed)
|
|
for f in (cdr dired-directory)
|
|
if (member f old-to-rename)
|
|
collect (assoc-default f files-renamed)
|
|
else collect f))))
|
|
;; Re-sort the buffer if all went well.
|
|
(unless (> errors 0) (revert-buffer))
|
|
(let ((inhibit-read-only t))
|
|
(dired-mark-remembered wdired-old-marks)))
|
|
(let ((inhibit-read-only t))
|
|
(remove-text-properties (point-min) (point-max)
|
|
'(old-name nil end-name nil old-link nil
|
|
end-link nil end-perm nil
|
|
old-perm nil perm-changed nil))
|
|
(message "(No changes to be performed)")))
|
|
(when files-deleted
|
|
(wdired-flag-for-deletion files-deleted))
|
|
(when (> errors 0)
|
|
(dired-log-summary (format "%d rename actions failed" errors) nil)))
|
|
(set-buffer-modified-p nil)
|
|
(setq buffer-undo-list nil))
|
|
|
|
;; Override `wdired-get-filename'.
|
|
;; Fix emacs bug in `wdired-get-filename' which returns the current
|
|
;; directory concatened with the filename i.e
|
|
;; "/home/you//home/you/foo" when filename is absolute in dired
|
|
;; buffer.
|
|
;; In consequence Wdired try to rename files even when buffer have
|
|
;; been modified and corrected, e.g delete one char and replace it so
|
|
;; that no change to file is done.
|
|
;; This also lead to ask confirmation for every files even when not
|
|
;; modified and when `wdired-use-interactive-rename' is nil.
|
|
(defun helm--advice-wdired-get-filename (&optional no-dir old)
|
|
;; FIXME: Use dired-get-filename's new properties.
|
|
(let (beg end file)
|
|
(save-excursion
|
|
(setq end (line-end-position))
|
|
(beginning-of-line)
|
|
(setq beg (next-single-property-change (point) 'old-name nil end))
|
|
(unless (eq beg end)
|
|
(if old
|
|
(setq file (get-text-property beg 'old-name))
|
|
;; In the following form changed `(1+ beg)' to `beg' so that
|
|
;; the filename end is found even when the filename is empty.
|
|
;; Fixes error and spurious newlines when marking files for
|
|
;; deletion.
|
|
(setq end (next-single-property-change beg 'end-name))
|
|
(setq file (buffer-substring-no-properties (1+ beg) end)))
|
|
;; Don't unquote the old name, it wasn't quoted in the first place
|
|
(and file (setq file (condition-case _err
|
|
;; emacs-25+
|
|
(apply #'wdired-normalize-filename
|
|
(list file (not old)))
|
|
(wrong-number-of-arguments
|
|
;; emacs-24
|
|
(wdired-normalize-filename file))))))
|
|
(if (or no-dir old (and file (file-name-absolute-p file)))
|
|
file
|
|
(and file (> (length file) 0)
|
|
(expand-file-name file (dired-current-directory)))))))
|
|
|
|
;;; Override `push-mark'
|
|
;;
|
|
;; Fix duplicates in `mark-ring' and `global-mark-ring' and update
|
|
;; buffers in `global-mark-ring' to recentest mark.
|
|
(defun helm--advice-push-mark (&optional location nomsg activate)
|
|
(unless (null (mark t))
|
|
(let ((marker (copy-marker (mark-marker))))
|
|
(setq mark-ring (cons marker (delete marker mark-ring))))
|
|
(when (> (length mark-ring) mark-ring-max)
|
|
;; Move marker to nowhere.
|
|
(set-marker (car (nthcdr mark-ring-max mark-ring)) nil)
|
|
(setcdr (nthcdr (1- mark-ring-max) mark-ring) nil)))
|
|
(set-marker (mark-marker) (or location (point)) (current-buffer))
|
|
;; Now push the mark on the global mark ring.
|
|
(setq global-mark-ring (cons (copy-marker (mark-marker))
|
|
;; Avoid having multiple entries
|
|
;; for same buffer in `global-mark-ring'.
|
|
(cl-loop with mb = (current-buffer)
|
|
for m in global-mark-ring
|
|
for nmb = (marker-buffer m)
|
|
unless (eq mb nmb)
|
|
collect m)))
|
|
(when (> (length global-mark-ring) global-mark-ring-max)
|
|
(set-marker (car (nthcdr global-mark-ring-max global-mark-ring)) nil)
|
|
(setcdr (nthcdr (1- global-mark-ring-max) global-mark-ring) nil))
|
|
(or nomsg executing-kbd-macro (> (minibuffer-depth) 0)
|
|
(message "Mark set"))
|
|
(when (or activate (not transient-mark-mode))
|
|
(set-mark (mark t)))
|
|
nil)
|
|
|
|
(defcustom helm-advice-push-mark t
|
|
"Override `push-mark' with a version avoiding duplicates when non nil."
|
|
:group 'helm
|
|
:type 'boolean
|
|
:set (lambda (var val)
|
|
(set var val)
|
|
(if val
|
|
(advice-add 'push-mark :override #'helm--advice-push-mark)
|
|
(advice-remove 'push-mark #'helm--advice-push-mark))))
|
|
|
|
;; This the version of Emacs-27 written by Stefan
|
|
(defun helm-advice--ffap-read-file-or-url (prompt guess)
|
|
(or guess (setq guess default-directory))
|
|
(if (ffap-url-p guess)
|
|
(read-string prompt guess nil nil t)
|
|
(unless (ffap-file-remote-p guess)
|
|
(setq guess (abbreviate-file-name (expand-file-name guess))))
|
|
(read-file-name prompt (file-name-directory guess) nil nil
|
|
(file-name-nondirectory guess))))
|
|
|
|
;;; Macros helper.
|
|
;;
|
|
(defmacro helm-with-gensyms (symbols &rest body)
|
|
"Bind the SYMBOLS to fresh uninterned symbols and eval BODY."
|
|
(declare (indent 1))
|
|
`(let ,(mapcar (lambda (s)
|
|
;; Use cl-gensym here instead of make-symbol
|
|
;; to ensure a symbol that have a live that go
|
|
;; beyond the live of its macro have different name.
|
|
;; i.e symbols created with `with-helm-temp-hook'
|
|
;; should have random names.
|
|
`(,s (cl-gensym (symbol-name ',s))))
|
|
symbols)
|
|
,@body))
|
|
|
|
;;; Command loop helper
|
|
;;
|
|
(defun helm-this-command ()
|
|
"Returns the actual command in action.
|
|
Like `this-command' but return the real command,
|
|
and not `exit-minibuffer' or other unwanted functions."
|
|
(cl-loop with bl = '(helm-maybe-exit-minibuffer
|
|
helm-confirm-and-exit-minibuffer
|
|
helm-exit-minibuffer
|
|
exit-minibuffer)
|
|
for count from 1 to 50
|
|
for btf = (backtrace-frame count)
|
|
for fn = (cl-second btf)
|
|
if (and
|
|
;; In some case we may have in the way an
|
|
;; advice compiled resulting in byte-code,
|
|
;; ignore it (Issue #691).
|
|
(symbolp fn)
|
|
(commandp fn)
|
|
(not (memq fn bl)))
|
|
return fn
|
|
else
|
|
if (and (eq fn 'call-interactively)
|
|
(> (length btf) 2))
|
|
return (cadr (cdr btf))))
|
|
|
|
|
|
;;; Iterators
|
|
;;
|
|
(cl-defmacro helm-position (item seq &key test all)
|
|
"A simple and faster replacement of CL `position'.
|
|
|
|
Returns ITEM first occurence position found in SEQ.
|
|
When SEQ is a string, ITEM have to be specified as a char.
|
|
Argument TEST when unspecified default to `eq'.
|
|
When argument ALL is non--nil return a list of all ITEM positions
|
|
found in SEQ."
|
|
(let ((key (if (stringp seq) 'across 'in)))
|
|
`(cl-loop with deftest = 'eq
|
|
for c ,key ,seq
|
|
for index from 0
|
|
when (funcall (or ,test deftest) c ,item)
|
|
if ,all collect index into ls
|
|
else return index
|
|
finally return ls)))
|
|
|
|
(defun helm-iter-list (seq)
|
|
"Return an iterator object from SEQ."
|
|
(let ((lis seq))
|
|
(lambda ()
|
|
(let ((elm (car lis)))
|
|
(setq lis (cdr lis))
|
|
elm))))
|
|
|
|
(defun helm-iter-circular (seq)
|
|
"Infinite iteration on SEQ."
|
|
(let ((lis seq))
|
|
(lambda ()
|
|
(let ((elm (car lis)))
|
|
(setq lis (pcase lis (`(,_ . ,ll) (or ll seq))))
|
|
elm))))
|
|
|
|
(cl-defun helm-iter-sub-next-circular (seq elm &key (test 'eq))
|
|
"Infinite iteration of SEQ starting at ELM."
|
|
(let* ((pos (1+ (helm-position elm seq :test test)))
|
|
(sub (append (nthcdr pos seq) (cl-subseq seq 0 pos)))
|
|
(iterator (helm-iter-circular sub)))
|
|
(lambda ()
|
|
(helm-iter-next iterator))))
|
|
|
|
(defun helm-iter-next (iterator)
|
|
"Return next elm of ITERATOR."
|
|
(and iterator (funcall iterator)))
|
|
|
|
|
|
;;; Anaphoric macros.
|
|
;;
|
|
(defmacro helm-aif (test-form then-form &rest else-forms)
|
|
"Anaphoric version of `if'.
|
|
Like `if' but set the result of TEST-FORM in a temporary variable called `it'.
|
|
THEN-FORM and ELSE-FORMS are then excuted just like in `if'."
|
|
(declare (indent 2) (debug t))
|
|
`(let ((it ,test-form))
|
|
(if it ,then-form ,@else-forms)))
|
|
|
|
(defmacro helm-awhile (sexp &rest body)
|
|
"Anaphoric version of `while'.
|
|
Same usage as `while' except that SEXP is bound to
|
|
a temporary variable called `it' at each turn.
|
|
An implicit nil block is bound to the loop so usage
|
|
of `cl-return' is possible to exit the loop."
|
|
(declare (indent 1) (debug t))
|
|
(helm-with-gensyms (flag)
|
|
`(let ((,flag t))
|
|
(cl-block nil
|
|
(while ,flag
|
|
(helm-aif ,sexp
|
|
(progn ,@body)
|
|
(setq ,flag nil)))))))
|
|
|
|
(defmacro helm-acond (&rest clauses)
|
|
"Anaphoric version of `cond'.
|
|
In each clause of CLAUSES, the result of the car of clause
|
|
is stored in a temporary variable called `it' and usable in the cdr
|
|
of this same clause. Each `it' variable is independent of its clause.
|
|
The usage is the same as `cond'."
|
|
(declare (debug cond))
|
|
(unless (null clauses)
|
|
(helm-with-gensyms (sym)
|
|
(let ((clause1 (car clauses)))
|
|
`(let ((,sym ,(car clause1)))
|
|
(helm-aif ,sym
|
|
(if (cdr ',clause1)
|
|
(progn ,@(cdr clause1))
|
|
it)
|
|
(helm-acond ,@(cdr clauses))))))))
|
|
|
|
(defmacro helm-aand (&rest conditions)
|
|
"Anaphoric version of `and'.
|
|
Each condition is bound to a temporary variable called `it' which is
|
|
usable in next condition."
|
|
(declare (debug (&rest form)))
|
|
(cond ((null conditions) t)
|
|
((null (cdr conditions)) (car conditions))
|
|
(t `(helm-aif ,(car conditions)
|
|
(helm-aand ,@(cdr conditions))))))
|
|
|
|
(defmacro helm-acase (expr &rest clauses)
|
|
"A simple anaphoric `cl-case' implementation handling strings.
|
|
EXPR is bound to a temporary variable called `it' which is usable in
|
|
CLAUSES to refer to EXPR.
|
|
NOTE: Duplicate keys in CLAUSES are deliberately not handled.
|
|
|
|
\(fn EXPR (KEYLIST BODY...)...)"
|
|
(declare (indent 1) (debug (form &rest (sexp body))))
|
|
(unless (null clauses)
|
|
(let ((clause1 (car clauses)))
|
|
`(let ((key ',(car clause1))
|
|
(it ,expr))
|
|
(if (or (equal it key)
|
|
(and (listp key) (member it key))
|
|
(eq key t))
|
|
(progn ,@(cdr clause1))
|
|
(helm-acase it ,@(cdr clauses)))))))
|
|
|
|
;;; Fuzzy matching routines
|
|
;;
|
|
(defsubst helm--mapconcat-pattern (pattern)
|
|
"Transform string PATTERN in regexp for further fuzzy matching.
|
|
e.g helm.el$
|
|
=> \"[^h]*h[^e]*e[^l]*l[^m]*m[^.]*[.][^e]*e[^l]*l$\"
|
|
^helm.el$
|
|
=> \"helm[.]el$\"."
|
|
(let ((ls (split-string-and-unquote pattern "")))
|
|
(if (string= "^" (car ls))
|
|
;; Exact match.
|
|
(mapconcat (lambda (c)
|
|
(if (and (string= c "$")
|
|
(string-match "$\\'" pattern))
|
|
c (regexp-quote c)))
|
|
(cdr ls) "")
|
|
;; Fuzzy match.
|
|
(mapconcat (lambda (c)
|
|
(if (and (string= c "$")
|
|
(string-match "$\\'" pattern))
|
|
c (format "[^%s]*%s" c (regexp-quote c))))
|
|
ls ""))))
|
|
|
|
(defsubst helm--collect-pairs-in-string (string)
|
|
(cl-loop for str on (split-string string "" t) by 'cdr
|
|
when (cdr str)
|
|
collect (list (car str) (cadr str))))
|
|
|
|
;;; Help routines.
|
|
;;
|
|
(defun helm-help-internal (bufname insert-content-fn)
|
|
"Show long message during `helm' session in BUFNAME.
|
|
INSERT-CONTENT-FN is the function that insert
|
|
text to be displayed in BUFNAME."
|
|
(let ((winconf (current-frame-configuration))
|
|
(hframe (selected-frame)))
|
|
(with-selected-frame helm-initial-frame
|
|
(select-frame-set-input-focus helm-initial-frame)
|
|
(unwind-protect
|
|
(progn
|
|
(setq helm-suspend-update-flag t)
|
|
(set-buffer (get-buffer-create bufname))
|
|
(switch-to-buffer bufname)
|
|
(when helm-help-full-frame (delete-other-windows))
|
|
(delete-region (point-min) (point-max))
|
|
(org-mode)
|
|
(org-mark-ring-push) ; Put mark at bob
|
|
(save-excursion
|
|
(funcall insert-content-fn))
|
|
(buffer-disable-undo)
|
|
(helm-help-event-loop))
|
|
(raise-frame hframe)
|
|
(setq helm-suspend-update-flag nil)
|
|
(set-frame-configuration winconf)))))
|
|
|
|
(defun helm-help-scroll-up (amount)
|
|
(condition-case _err
|
|
(scroll-up-command amount)
|
|
(beginning-of-buffer nil)
|
|
(end-of-buffer nil)))
|
|
|
|
(defun helm-help-scroll-down (amount)
|
|
(condition-case _err
|
|
(scroll-down-command amount)
|
|
(beginning-of-buffer nil)
|
|
(end-of-buffer nil)))
|
|
|
|
(defun helm-help-next-line ()
|
|
(condition-case _err
|
|
(call-interactively #'next-line)
|
|
(beginning-of-buffer nil)
|
|
(end-of-buffer nil)))
|
|
|
|
(defun helm-help-previous-line ()
|
|
(condition-case _err
|
|
(call-interactively #'previous-line)
|
|
(beginning-of-buffer nil)
|
|
(end-of-buffer nil)))
|
|
|
|
(defun helm-help-toggle-mark ()
|
|
(if (region-active-p)
|
|
(deactivate-mark)
|
|
(push-mark nil nil t)))
|
|
|
|
;; For movement of cursor in help buffer we need to call interactively
|
|
;; commands for impaired people using a synthetizer (#1347).
|
|
(defun helm-help-event-loop ()
|
|
(let ((prompt (propertize
|
|
"[SPC,C-v,next:ScrollUp b,M-v,prior:ScrollDown TAB:Cycle M-TAB:All C-s/r:Isearch q:Quit]"
|
|
'face 'helm-helper))
|
|
scroll-error-top-bottom
|
|
(iter-org-state (helm-iter-circular '(1 (16) (64)))))
|
|
(helm-awhile (read-key prompt)
|
|
(cl-case it
|
|
((?\C-v ? next) (helm-help-scroll-up helm-scroll-amount))
|
|
((?\M-v ?b prior) (helm-help-scroll-down helm-scroll-amount))
|
|
(?\C-s (isearch-forward))
|
|
(?\C-r (isearch-backward))
|
|
(?\C-a (call-interactively #'move-beginning-of-line))
|
|
(?\C-e (call-interactively #'move-end-of-line))
|
|
((?\C-f right) (call-interactively #'forward-char))
|
|
((?\C-b left) (call-interactively #'backward-char))
|
|
((?\C-n down) (helm-help-next-line))
|
|
((?\C-p up) (helm-help-previous-line))
|
|
(?\M-a (call-interactively #'backward-sentence))
|
|
(?\M-e (call-interactively #'forward-sentence))
|
|
(?\M-f (call-interactively #'forward-word))
|
|
(?\M-b (call-interactively #'backward-word))
|
|
(?\M-> (call-interactively #'end-of-buffer))
|
|
(?\M-< (call-interactively #'beginning-of-buffer))
|
|
(?\C- (helm-help-toggle-mark))
|
|
(?\t (org-cycle))
|
|
(?\C-m (ignore-errors (call-interactively #'org-open-at-point)))
|
|
(?\C-& (ignore-errors (call-interactively #'org-mark-ring-goto)))
|
|
(?\C-% (call-interactively #'org-mark-ring-push))
|
|
(?\M-\t (pcase (helm-iter-next iter-org-state)
|
|
((pred numberp) (org-content))
|
|
((and state) (org-cycle state))))
|
|
(?\M-w (copy-region-as-kill
|
|
(region-beginning) (region-end))
|
|
(deactivate-mark))
|
|
(?q (cl-return))
|
|
(t (ignore))))))
|
|
|
|
|
|
;;; Multiline transformer
|
|
;;
|
|
(defun helm-multiline-transformer (candidates _source)
|
|
(cl-loop with offset = (helm-interpret-value
|
|
(assoc-default 'multiline (helm-get-current-source)))
|
|
for i in candidates
|
|
if (numberp offset)
|
|
collect (cons (helm--multiline-get-truncated-candidate i offset) i)
|
|
else collect i))
|
|
|
|
(defun helm--multiline-get-truncated-candidate (candidate offset)
|
|
"Truncate CANDIDATE when its length is > than OFFSET."
|
|
(with-temp-buffer
|
|
(insert candidate)
|
|
(goto-char (point-min))
|
|
(if (and offset
|
|
(> (buffer-size) offset))
|
|
(let ((end-str "[...]"))
|
|
(concat
|
|
(buffer-substring
|
|
(point)
|
|
(save-excursion
|
|
(forward-char offset)
|
|
(setq end-str (if (looking-at "\n")
|
|
end-str (concat "\n" end-str)))
|
|
(point)))
|
|
end-str))
|
|
(buffer-string))))
|
|
|
|
;;; List processing
|
|
;;
|
|
(defun helm-flatten-list (seq &optional omit-nulls)
|
|
"Return a list of all single elements of sublists in SEQ."
|
|
(let (result)
|
|
(cl-labels ((flatten (seq)
|
|
(cl-loop
|
|
for elm in seq
|
|
if (and (or elm
|
|
(null omit-nulls))
|
|
(or (atom elm)
|
|
(functionp elm)
|
|
(and (consp elm)
|
|
(cdr elm)
|
|
(atom (cdr elm)))))
|
|
do (push elm result)
|
|
else do (flatten elm))))
|
|
(flatten seq))
|
|
(nreverse result)))
|
|
|
|
(defun helm-mklist (obj)
|
|
"If OBJ is a list \(but not lambda\), return itself.
|
|
Otherwise make a list with one element."
|
|
(if (and (listp obj) (not (functionp obj)))
|
|
obj
|
|
(list obj)))
|
|
|
|
(cl-defun helm-fast-remove-dups (seq &key (test 'eq))
|
|
"Remove duplicates elements in list SEQ.
|
|
|
|
This is same as `remove-duplicates' but with memoisation.
|
|
It is much faster, especially in large lists.
|
|
A test function can be provided with TEST argument key.
|
|
Default is `eq'.
|
|
NOTE: Comparison of special elisp objects (e.g. markers etc...) fails
|
|
because their printed representations which are stored in hash-table
|
|
can't be compared with with the real object in SEQ.
|
|
This is a bug in `puthash' which store the printable representation of
|
|
object instead of storing the object itself, this to provide at the
|
|
end a printable representation of hashtable itself."
|
|
(cl-loop with cont = (make-hash-table :test test)
|
|
for elm in seq
|
|
unless (gethash elm cont)
|
|
collect (puthash elm elm cont)))
|
|
|
|
(defsubst helm--string-join (strings &optional separator)
|
|
"Join all STRINGS using SEPARATOR."
|
|
(mapconcat 'identity strings separator))
|
|
|
|
(defun helm--concat-regexps (regexp-list)
|
|
"Return a regexp which matches any of the regexps in REGEXP-LIST."
|
|
(if regexp-list
|
|
(concat "\\(?:" (helm--string-join regexp-list "\\)\\|\\(?:") "\\)")
|
|
"\\<\\>")) ; Match nothing
|
|
|
|
(defun helm-skip-entries (seq black-regexp-list &optional white-regexp-list)
|
|
"Remove entries which matches one of REGEXP-LIST from SEQ."
|
|
(let ((black-regexp (helm--concat-regexps black-regexp-list))
|
|
(white-regexp (helm--concat-regexps white-regexp-list)))
|
|
(cl-loop for i in seq
|
|
unless (and (stringp i)
|
|
(string-match-p black-regexp i)
|
|
(null
|
|
(string-match-p white-regexp i)))
|
|
collect i)))
|
|
|
|
(defun helm-boring-directory-p (directory black-list)
|
|
"Check if one regexp in BLACK-LIST match DIRECTORY."
|
|
(helm-awhile (helm-basedir (directory-file-name
|
|
(expand-file-name directory)))
|
|
(when (string= it "/") (cl-return nil))
|
|
(when (cl-loop for r in black-list
|
|
thereis (string-match-p
|
|
r (directory-file-name directory)))
|
|
(cl-return t))
|
|
(setq directory it)))
|
|
|
|
(defun helm-shadow-entries (seq regexp-list)
|
|
"Put shadow property on entries in SEQ matching a regexp in REGEXP-LIST."
|
|
(let ((face 'italic))
|
|
(cl-loop for i in seq
|
|
if (cl-loop for regexp in regexp-list
|
|
thereis (and (stringp i)
|
|
(string-match regexp i)))
|
|
collect (propertize i 'face face)
|
|
else collect i)))
|
|
|
|
(defun helm-remove-if-not-match (regexp seq)
|
|
"Remove all elements of SEQ that don't match REGEXP."
|
|
(cl-loop for s in seq
|
|
for str = (cond ((symbolp s)
|
|
(symbol-name s))
|
|
((consp s)
|
|
(car s))
|
|
(t s))
|
|
when (string-match-p regexp str)
|
|
collect s))
|
|
|
|
(defun helm-remove-if-match (regexp seq)
|
|
"Remove all elements of SEQ that match REGEXP."
|
|
(cl-loop for s in seq
|
|
for str = (cond ((symbolp s)
|
|
(symbol-name s))
|
|
((consp s)
|
|
(car s))
|
|
(t s))
|
|
unless (string-match-p regexp str)
|
|
collect s))
|
|
|
|
(defun helm-transform-mapcar (function args)
|
|
"`mapcar' for candidate-transformer.
|
|
|
|
ARGS is (cand1 cand2 ...) or ((disp1 . real1) (disp2 . real2) ...)
|
|
|
|
\(helm-transform-mapcar 'upcase '(\"foo\" \"bar\"))
|
|
=> (\"FOO\" \"BAR\")
|
|
\(helm-transform-mapcar 'upcase '((\"1st\" . \"foo\") (\"2nd\" . \"bar\")))
|
|
=> ((\"1st\" . \"FOO\") (\"2nd\" . \"BAR\"))
|
|
"
|
|
(cl-loop for arg in args
|
|
if (consp arg)
|
|
collect (cons (car arg) (funcall function (cdr arg)))
|
|
else
|
|
collect (funcall function arg)))
|
|
|
|
(defun helm-append-at-nth (seq elm index)
|
|
"Append ELM at INDEX in SEQ."
|
|
(let ((len (length seq)))
|
|
(cond ((> index len) (setq index len))
|
|
((< index 0) (setq index 0)))
|
|
(if (zerop index)
|
|
(append elm seq)
|
|
(cl-loop for i in seq
|
|
for count from 1 collect i
|
|
when (= count index)
|
|
if (listp elm) append elm
|
|
else collect elm))))
|
|
|
|
(defun helm-source-by-name (name &optional sources)
|
|
"Get a Helm source in SOURCES by NAME.
|
|
|
|
Optional argument SOURCES is a list of Helm sources which default to
|
|
`helm-sources'."
|
|
(cl-loop with src-list = (if sources
|
|
(cl-loop for src in sources
|
|
collect (if (listp src)
|
|
src
|
|
(symbol-value src)))
|
|
helm-sources)
|
|
for source in src-list
|
|
thereis (and (string= name (assoc-default 'name source)) source)))
|
|
|
|
(defun helm-make-actions (&rest args)
|
|
"Build an alist with (NAME . ACTION) elements with each pairs in ARGS.
|
|
Where NAME is a string or a function returning a string or nil and ACTION
|
|
a function.
|
|
If NAME returns nil the pair is skipped.
|
|
|
|
\(fn NAME ACTION ...)"
|
|
(cl-loop for (name fn) on args by #'cddr
|
|
when (functionp name)
|
|
do (setq name (funcall name))
|
|
when name
|
|
collect (cons name fn)))
|
|
|
|
;;; Strings processing.
|
|
;;
|
|
(defun helm-stringify (elm)
|
|
"Return the representation of ELM as a string.
|
|
ELM can be a string, a number or a symbol."
|
|
(cl-typecase elm
|
|
(string elm)
|
|
(number (number-to-string elm))
|
|
(symbol (symbol-name elm))))
|
|
|
|
(defun helm-substring (str width)
|
|
"Return the substring of string STR from 0 to WIDTH.
|
|
Handle multibyte characters by moving by columns."
|
|
(with-temp-buffer
|
|
(save-excursion
|
|
(insert str))
|
|
(move-to-column width)
|
|
(buffer-substring (point-at-bol) (point))))
|
|
|
|
(defun helm-substring-by-width (str width &optional endstr)
|
|
"Truncate string STR to end at column WIDTH.
|
|
Similar to `truncate-string-to-width'.
|
|
Add ENDSTR at end of truncated STR.
|
|
Add spaces at end if needed to reach WIDTH when STR is shorter than WIDTH."
|
|
(cl-loop for ini-str = str
|
|
then (substring ini-str 0 (1- (length ini-str)))
|
|
for sw = (string-width ini-str)
|
|
when (<= sw width) return
|
|
(concat ini-str endstr (make-string (- width sw) ? ))))
|
|
|
|
(defun helm-string-multibyte-p (str)
|
|
"Check if string STR contains multibyte characters."
|
|
(cl-loop for c across str
|
|
thereis (> (char-width c) 1)))
|
|
|
|
(defun helm-get-pid-from-process-name (process-name)
|
|
"Get pid from running process PROCESS-NAME."
|
|
(cl-loop with process-list = (list-system-processes)
|
|
for pid in process-list
|
|
for process = (assoc-default 'comm (process-attributes pid))
|
|
when (and process (string-match process-name process))
|
|
return pid))
|
|
|
|
(defun helm-ff-find-printers ()
|
|
"Return a list of available printers on Unix systems."
|
|
(when (executable-find "lpstat")
|
|
(let ((printer-list (with-temp-buffer
|
|
(call-process "lpstat" nil t nil "-a")
|
|
(split-string (buffer-string) "\n"))))
|
|
(cl-loop for p in printer-list
|
|
for printer = (car (split-string p))
|
|
when printer
|
|
collect printer))))
|
|
|
|
(defun helm-region-active-p ()
|
|
(and transient-mark-mode mark-active (/= (mark) (point))))
|
|
|
|
(defun helm-quote-whitespace (candidate)
|
|
"Quote whitespace, if some, in string CANDIDATE."
|
|
(replace-regexp-in-string " " "\\\\ " candidate))
|
|
|
|
(defun helm-current-line-contents ()
|
|
"Current line string without properties."
|
|
(buffer-substring-no-properties (point-at-bol) (point-at-eol)))
|
|
|
|
(defun helm--replace-regexp-in-buffer-string (regexp rep str &optional fixedcase literal subexp start)
|
|
"Replace REGEXP by REP in string STR.
|
|
|
|
Same as `replace-regexp-in-string' but handle properly REP as
|
|
function with SUBEXP specified.
|
|
|
|
e.g
|
|
|
|
(helm--replace-regexp-in-buffer-string \"e\\\\(m\\\\)acs\" 'upcase \"emacs\" t nil 1)
|
|
=> \"eMacs\"
|
|
|
|
(replace-regexp-in-string \"e\\\\(m\\\\)acs\" 'upcase \"emacs\" t nil 1)
|
|
=> \"eEMACSacs\"
|
|
|
|
Also START argument behave as expected unlike
|
|
`replace-regexp-in-string'.
|
|
|
|
e.g
|
|
|
|
(helm--replace-regexp-in-buffer-string \"f\" \"r\" \"foofoo\" t nil nil 3)
|
|
=> \"fooroo\"
|
|
|
|
(replace-regexp-in-string \"f\" \"r\" \"foofoo\" t nil nil 3)
|
|
=> \"roo\"
|
|
|
|
Unlike `replace-regexp-in-string' this function is buffer-based
|
|
implemented i.e replacement is computed inside a temp buffer, so
|
|
REGEXP should be used differently than with
|
|
`replace-regexp-in-string'.
|
|
|
|
NOTE: This function is used internally for
|
|
`helm-ff-query-replace-on-filenames' and builded for this.
|
|
You should use `replace-regexp-in-string' instead unless the behavior
|
|
of this function is really needed."
|
|
(with-temp-buffer
|
|
(insert str)
|
|
(goto-char (or start (point-min)))
|
|
(while (re-search-forward regexp nil t)
|
|
(replace-match (cond ((and (functionp rep) subexp)
|
|
(funcall rep (match-string subexp)))
|
|
((functionp rep)
|
|
(funcall rep str))
|
|
(t rep))
|
|
fixedcase literal nil subexp))
|
|
(buffer-string)))
|
|
|
|
(defun helm-url-unhex-string (str)
|
|
"Same as `url-unhex-string' but ensure STR is completely decoded."
|
|
(setq str (or str ""))
|
|
(with-temp-buffer
|
|
(save-excursion (insert str))
|
|
(while (re-search-forward "%[A-Za-z0-9]\\{2\\}" nil t)
|
|
(replace-match (byte-to-string (string-to-number
|
|
(substring (match-string 0) 1)
|
|
16))
|
|
t t)
|
|
;; Restart from beginning until string is completely decoded.
|
|
(goto-char (point-min)))
|
|
(decode-coding-string (buffer-string) 'utf-8)))
|
|
|
|
(defun helm-read-answer (prompt answer-list)
|
|
"Prompt user for an answer.
|
|
Arg PROMPT is the prompt to present user the different possible
|
|
answers, ANSWER-LIST is a list of strings.
|
|
If user enter an answer which is one of ANSWER-LIST return this
|
|
answer, otherwise keep prompting for a valid answer.
|
|
Note that answer should be a single char, only short answer are
|
|
accepted.
|
|
|
|
Example:
|
|
|
|
(let ((answer (helm-read-answer
|
|
\"answer [y,n,!,q]: \"
|
|
'(\"y\" \"n\" \"!\" \"q\"))))
|
|
(pcase answer
|
|
(\"y\" \"yes\")
|
|
(\"n\" \"no\")
|
|
(\"!\" \"all\")
|
|
(\"q\" \"quit\")))
|
|
|
|
"
|
|
(helm-awhile (string
|
|
(read-key (propertize prompt 'face 'minibuffer-prompt)))
|
|
(if (member it answer-list)
|
|
(cl-return it)
|
|
(message "Please answer by %s" (mapconcat 'identity answer-list ", "))
|
|
(sit-for 1))))
|
|
|
|
;;; Symbols routines
|
|
;;
|
|
(defun helm-symbolify (str-or-sym)
|
|
"Get symbol of STR-OR-SYM."
|
|
(if (symbolp str-or-sym)
|
|
str-or-sym
|
|
(intern str-or-sym)))
|
|
|
|
(defun helm-symbol-name (obj)
|
|
(if (or (and (consp obj) (functionp obj))
|
|
(byte-code-function-p obj))
|
|
"Anonymous"
|
|
(symbol-name obj)))
|
|
|
|
(defun helm-describe-class (class)
|
|
"Display documentation of Eieio CLASS, a symbol or a string."
|
|
(advice-add 'cl--print-table :override #'helm-source--cl--print-table)
|
|
(unwind-protect
|
|
(let ((helm-describe-function-function 'describe-function))
|
|
(helm-describe-function class))
|
|
(advice-remove 'cl--print-table #'helm-source--cl--print-table)))
|
|
|
|
(defun helm-describe-function (func)
|
|
"Display documentation of FUNC, a symbol or string."
|
|
(cl-letf (((symbol-function 'message) #'ignore))
|
|
(funcall helm-describe-function-function (helm-symbolify func))))
|
|
|
|
(defun helm-describe-variable (var)
|
|
"Display documentation of VAR, a symbol or a string."
|
|
(cl-letf (((symbol-function 'message) #'ignore))
|
|
(funcall helm-describe-variable-function (helm-symbolify var))))
|
|
|
|
(defun helm-describe-face (face)
|
|
"Display documentation of FACE, a symbol or a string."
|
|
(let ((faces (helm-marked-candidates)))
|
|
(cl-letf (((symbol-function 'message) #'ignore))
|
|
(describe-face (if (cdr faces)
|
|
(mapcar 'helm-symbolify faces)
|
|
(helm-symbolify face))))))
|
|
|
|
(defun helm-elisp--persistent-help (candidate fun &optional name)
|
|
"Used to build persistent actions describing CANDIDATE with FUN.
|
|
Argument NAME is used internally to know which command to use when
|
|
symbol CANDIDATE refers at the same time to variable and a function.
|
|
See `helm-elisp-show-help'."
|
|
(let ((hbuf (get-buffer (help-buffer))))
|
|
(cond ((helm-follow-mode-p)
|
|
(if name
|
|
(funcall fun candidate name)
|
|
(funcall fun candidate)))
|
|
((or (and (helm-attr 'help-running-p)
|
|
(string= candidate (helm-attr 'help-current-symbol))))
|
|
(progn
|
|
;; When started from a help buffer,
|
|
;; Don't kill this buffer as it is helm-current-buffer.
|
|
(unless (equal hbuf helm-current-buffer)
|
|
(kill-buffer hbuf)
|
|
(set-window-buffer (get-buffer-window hbuf)
|
|
;; It is generally
|
|
;; helm-current-buffer but it may
|
|
;; be another buffer when helm have
|
|
;; been started from a dedicated window.
|
|
(if helm--buffer-in-new-frame-p
|
|
helm-current-buffer
|
|
helm-persistent-action-window-buffer)))
|
|
(helm-attrset 'help-running-p nil))
|
|
;; Force running update hook to may be delete
|
|
;; helm-persistent-action-display-window, this is done in
|
|
;; helm-persistent-action-display-window (the function).
|
|
(unless helm--buffer-in-new-frame-p
|
|
(helm-update (regexp-quote (helm-get-selection)))))
|
|
(t
|
|
(if name
|
|
(funcall fun candidate name)
|
|
(funcall fun candidate))
|
|
(helm-attrset 'help-running-p t)))
|
|
(helm-attrset 'help-current-symbol candidate)))
|
|
|
|
(defun helm-find-function (func)
|
|
"FUNC is symbol or string."
|
|
(find-function (helm-symbolify func)))
|
|
|
|
(defun helm-find-variable (var)
|
|
"VAR is symbol or string."
|
|
(find-variable (helm-symbolify var)))
|
|
|
|
(defun helm-find-face-definition (face)
|
|
"FACE is symbol or string."
|
|
(find-face-definition (helm-symbolify face)))
|
|
|
|
(defun helm-kill-new (candidate &optional replace)
|
|
"CANDIDATE is symbol or string.
|
|
See `kill-new' for argument REPLACE."
|
|
(kill-new (helm-stringify candidate) replace))
|
|
|
|
|
|
;;; Modes
|
|
;;
|
|
(defun helm-same-major-mode-p (start-buffer alist)
|
|
"Decide if current-buffer is related to START-BUFFER.
|
|
Argument ALIST is an alist of associated major modes."
|
|
;; START-BUFFER is the current-buffer where we start searching.
|
|
;; Determine the major-mode of START-BUFFER as `cur-maj-mode'.
|
|
;; Each time the loop go in another buffer we try from this buffer
|
|
;; to determine if its `major-mode' is:
|
|
;; - same as the `cur-maj-mode'
|
|
;; - derived from `cur-maj-mode' and from
|
|
;; START-BUFFER if its mode is derived from the one in START-BUFFER.
|
|
;; - have an assoc entry (major-mode . cur-maj-mode)
|
|
;; - have an rassoc entry (cur-maj-mode . major-mode)
|
|
;; - check if one of these entries inherit from another one in
|
|
;; `alist'.
|
|
(let* ((cur-maj-mode (with-current-buffer start-buffer major-mode))
|
|
(maj-mode major-mode)
|
|
(c-assoc-mode (assq cur-maj-mode alist))
|
|
(c-rassoc-mode (rassq cur-maj-mode alist))
|
|
(o-assoc-mode (assq major-mode alist))
|
|
(o-rassoc-mode (rassq major-mode alist))
|
|
(cdr-c-assoc-mode (cdr c-assoc-mode))
|
|
(cdr-o-assoc-mode (cdr o-assoc-mode)))
|
|
(or (eq major-mode cur-maj-mode)
|
|
(derived-mode-p cur-maj-mode)
|
|
(with-current-buffer start-buffer
|
|
(derived-mode-p maj-mode))
|
|
(or (eq cdr-c-assoc-mode major-mode)
|
|
(eq (car c-rassoc-mode) major-mode)
|
|
(eq (cdr (assq cdr-c-assoc-mode alist))
|
|
major-mode)
|
|
(eq (car (rassq cdr-c-assoc-mode alist))
|
|
major-mode))
|
|
(or (eq cdr-o-assoc-mode cur-maj-mode)
|
|
(eq (car o-rassoc-mode) cur-maj-mode)
|
|
(eq (cdr (assq cdr-o-assoc-mode alist))
|
|
cur-maj-mode)
|
|
(eq (car (rassq cdr-o-assoc-mode alist))
|
|
cur-maj-mode)))))
|
|
|
|
;;; Files routines
|
|
;;
|
|
(defun helm-file-name-sans-extension (filename)
|
|
"Same as `file-name-sans-extension' but remove all extensions."
|
|
(helm-aif (file-name-sans-extension filename)
|
|
;; Start searching at index 1 for files beginning with a dot (#1335).
|
|
(if (string-match "\\." (helm-basename it) 1)
|
|
(helm-file-name-sans-extension it)
|
|
it)))
|
|
|
|
(defun helm-basename (fname &optional ext)
|
|
"Print FNAME with any leading directory components removed.
|
|
If specified, also remove filename extension EXT.
|
|
Arg EXT can be specified as a string with or without dot,
|
|
in this case it should match file-name-extension.
|
|
It can also be non-nil (`t') in this case no checking
|
|
of file-name-extension is done and the extension is removed
|
|
unconditionally."
|
|
(let ((non-essential t))
|
|
(if (and ext (or (string= (file-name-extension fname) ext)
|
|
(string= (file-name-extension fname t) ext)
|
|
(eq ext t))
|
|
(not (file-directory-p fname)))
|
|
(file-name-sans-extension (file-name-nondirectory fname))
|
|
(file-name-nondirectory (directory-file-name fname)))))
|
|
|
|
(defun helm-basedir (fname)
|
|
"Return the base directory of filename ending by a slash."
|
|
(helm-aif (and fname
|
|
(or (and (string= fname "~") "~")
|
|
(file-name-directory fname)))
|
|
(file-name-as-directory it)))
|
|
|
|
(defun helm-current-directory ()
|
|
"Return current-directory name at point.
|
|
Useful in dired buffers when there is inserted subdirs."
|
|
(expand-file-name
|
|
(if (eq major-mode 'dired-mode)
|
|
(dired-current-directory)
|
|
default-directory)))
|
|
|
|
(defun helm-shadow-boring-files (files)
|
|
"Files matching `helm-boring-file-regexp' will be
|
|
displayed with the `file-name-shadow' face if available."
|
|
(helm-shadow-entries files helm-boring-file-regexp-list))
|
|
|
|
(defun helm-skip-boring-files (files)
|
|
"Files matching `helm-boring-file-regexp' will be skipped."
|
|
(helm-skip-entries files helm-boring-file-regexp-list))
|
|
|
|
(defun helm-skip-current-file (files)
|
|
"Current file will be skipped."
|
|
(remove (buffer-file-name helm-current-buffer) files))
|
|
|
|
(defun helm-w32-pathname-transformer (args)
|
|
"Change undesirable features of windows pathnames to ones more acceptable to
|
|
other candidate transformers."
|
|
(if (eq system-type 'windows-nt)
|
|
(helm-transform-mapcar
|
|
(lambda (x)
|
|
(replace-regexp-in-string
|
|
"/cygdrive/\\(.\\)" "\\1:"
|
|
(replace-regexp-in-string "\\\\" "/" x)))
|
|
args)
|
|
args))
|
|
|
|
(defun helm-w32-prepare-filename (file)
|
|
"Convert filename FILE to something usable by external w32 executables."
|
|
(replace-regexp-in-string ; For UNC paths
|
|
"/" "\\"
|
|
(replace-regexp-in-string ; Strip cygdrive paths
|
|
"/cygdrive/\\(.\\)" "\\1:"
|
|
file nil nil) nil t))
|
|
|
|
(defun helm-w32-shell-execute-open-file (file)
|
|
(with-no-warnings
|
|
(w32-shell-execute "open" (helm-w32-prepare-filename file))))
|
|
|
|
;; Same as `vc-directory-exclusion-list'.
|
|
(defvar helm-walk-ignore-directories
|
|
'("SCCS/" "RCS/" "CVS/" "MCVS/" ".svn/" ".git/" ".hg/" ".bzr/"
|
|
"_MTN/" "_darcs/" "{arch}/" ".gvfs/"))
|
|
|
|
(defsubst helm--dir-file-name (file dir)
|
|
(expand-file-name
|
|
(substring file 0 (1- (length file))) dir))
|
|
|
|
(defsubst helm--dir-name-p (str)
|
|
(char-equal (aref str (1- (length str))) ?/))
|
|
|
|
(cl-defun helm-walk-directory (directory &key (path 'basename)
|
|
directories
|
|
match skip-subdirs
|
|
noerror)
|
|
"Walk through DIRECTORY tree.
|
|
|
|
Argument PATH can be one of basename, relative, full, or a function
|
|
called on file name, default to basename.
|
|
|
|
Argument DIRECTORIES when `t' return also directories names,
|
|
otherwise skip directories names, with a value of `only' returns
|
|
only subdirectories, i.e files are skipped.
|
|
|
|
Argument MATCH is a regexp matching files or directories.
|
|
|
|
Argument SKIP-SUBDIRS when `t' will skip `helm-walk-ignore-directories'
|
|
otherwise if it is given as a list of directories, this list will be used
|
|
instead of `helm-walk-ignore-directories'.
|
|
|
|
Argument NOERROR when `t' will skip directories which are not accessible."
|
|
(let ((fn (cl-case path
|
|
(basename 'file-name-nondirectory)
|
|
(relative 'file-relative-name)
|
|
(full 'identity)
|
|
(t path)))) ; A function.
|
|
(setq skip-subdirs (if (listp skip-subdirs)
|
|
skip-subdirs
|
|
helm-walk-ignore-directories))
|
|
(cl-labels ((ls-rec (dir)
|
|
(unless (file-symlink-p dir)
|
|
(cl-loop for f in (sort (file-name-all-completions "" dir)
|
|
'string-lessp)
|
|
unless (member f '("./" "../"))
|
|
;; A directory.
|
|
;; Use `helm--dir-file-name' to remove the final slash.
|
|
;; Needed to avoid infloop on directory symlinks.
|
|
if (and (helm--dir-name-p f)
|
|
(helm--dir-file-name f dir))
|
|
nconc
|
|
(unless (or (member f skip-subdirs)
|
|
(and noerror
|
|
(not (file-accessible-directory-p it))))
|
|
(if (and directories
|
|
(or (null match)
|
|
(string-match match f)))
|
|
(nconc (list (concat (funcall fn it) "/"))
|
|
(ls-rec it))
|
|
(ls-rec it)))
|
|
;; A regular file.
|
|
else nconc
|
|
(when (and (null (eq directories 'only))
|
|
(or (null match) (string-match match f)))
|
|
(list (funcall fn (expand-file-name f dir))))))))
|
|
(ls-rec directory))))
|
|
|
|
(defun helm-file-expand-wildcards (pattern &optional full)
|
|
"Same as `file-expand-wildcards' but allow recursion.
|
|
Recursion happen when PATTERN starts with two stars.
|
|
Directories expansion is not supported."
|
|
(let ((bn (helm-basename pattern))
|
|
(case-fold-search nil))
|
|
(if (and helm-file-globstar
|
|
(string-match "\\`\\*\\{2\\}\\(.*\\)" bn))
|
|
(helm-walk-directory (helm-basedir pattern)
|
|
:path (cl-case full
|
|
(full 'full)
|
|
(relative 'relative)
|
|
((basename nil) 'basename)
|
|
(t 'full))
|
|
:directories nil
|
|
:match (wildcard-to-regexp bn)
|
|
:skip-subdirs t)
|
|
(file-expand-wildcards pattern full))))
|
|
|
|
;;; helm internals
|
|
;;
|
|
(defun helm-set-pattern (pattern &optional noupdate)
|
|
"Set minibuffer contents to PATTERN.
|
|
if optional NOUPDATE is non-nil, helm buffer is not changed."
|
|
(with-selected-window (or (active-minibuffer-window) (minibuffer-window))
|
|
(delete-minibuffer-contents)
|
|
(insert pattern))
|
|
(when noupdate
|
|
(setq helm-pattern pattern)))
|
|
|
|
(defun helm-minibuffer-completion-contents ()
|
|
"Return the user input in a minibuffer before point as a string.
|
|
That is what completion commands operate on."
|
|
(buffer-substring (field-beginning) (point)))
|
|
|
|
(defmacro with-helm-buffer (&rest body)
|
|
"Eval BODY inside `helm-buffer'."
|
|
(declare (indent 0) (debug t))
|
|
`(with-current-buffer (helm-buffer-get)
|
|
,@body))
|
|
|
|
(defmacro with-helm-current-buffer (&rest body)
|
|
"Eval BODY inside `helm-current-buffer'."
|
|
(declare (indent 0) (debug t))
|
|
`(with-current-buffer (or (and (buffer-live-p helm-current-buffer)
|
|
helm-current-buffer)
|
|
(setq helm-current-buffer
|
|
(current-buffer)))
|
|
,@body))
|
|
|
|
(defun helm-buffer-get ()
|
|
"Return `helm-action-buffer' if shown otherwise `helm-buffer'."
|
|
(if (helm-action-window)
|
|
helm-action-buffer
|
|
helm-buffer))
|
|
|
|
(defun helm-window ()
|
|
"Window of `helm-buffer'."
|
|
(get-buffer-window (helm-buffer-get) 0))
|
|
|
|
(defun helm-action-window ()
|
|
"Window of `helm-action-buffer'."
|
|
(get-buffer-window helm-action-buffer 'visible))
|
|
|
|
(defmacro with-helm-window (&rest body)
|
|
"Be sure BODY is excuted in the helm window."
|
|
(declare (indent 0) (debug t))
|
|
`(with-selected-window (helm-window)
|
|
,@body))
|
|
|
|
(defmacro helm-without-follow (&rest body)
|
|
"Ensure BODY runs without following.
|
|
I.e. when using `helm-next-line' and friends in BODY."
|
|
(declare (indent 0) (debug t))
|
|
`(cl-letf (((symbol-function 'helm-follow-mode-p)
|
|
(lambda (&optional _) nil)))
|
|
(let (helm-follow-mode-persistent)
|
|
(progn ,@body))))
|
|
|
|
;; Completion styles related functions
|
|
;;
|
|
(defun helm--setup-completion-styles-alist ()
|
|
(cl-pushnew '(helm helm-completion-try-completion
|
|
helm-completion-all-completions
|
|
"helm multi completion style.")
|
|
completion-styles-alist
|
|
:test 'equal)
|
|
(unless (assq 'flex completion-styles-alist)
|
|
;; Add helm-fuzzy style only if flex is not available.
|
|
(cl-pushnew '(helm-flex helm-flex-completion-try-completion
|
|
helm-flex-completion-all-completions
|
|
"helm flex completion style.\nProvide flex matching for emacs-26.")
|
|
completion-styles-alist
|
|
:test 'equal)))
|
|
|
|
(defvar helm-blacklist-completion-styles '(emacs21 emacs22))
|
|
(defun helm--prepare-completion-styles (&optional nomode)
|
|
"Return a suitable list of styles for `completion-styles'."
|
|
;; For `helm-completion-style' and `helm-completion-styles-alist'.
|
|
(require 'helm-mode)
|
|
(if (memq helm-completion-style '(helm helm-fuzzy))
|
|
;; Keep default settings, but probably nil is fine as well.
|
|
'(basic partial-completion emacs22)
|
|
(or
|
|
(pcase (and (null nomode)
|
|
(with-helm-current-buffer
|
|
(cdr (assq major-mode helm-completion-styles-alist))))
|
|
(`(,_l . ,ll) ll))
|
|
;; We need to have flex always behind helm, otherwise
|
|
;; when matching against e.g. '(foo foobar foao frogo bar
|
|
;; baz) with pattern "foo" helm style if before flex will
|
|
;; return foo and foobar only defeating flex that would
|
|
;; return foo foobar foao and frogo.
|
|
(let* ((wflex (car (or (assq 'flex completion-styles-alist)
|
|
(assq 'helm-flex completion-styles-alist))))
|
|
(styles (append (and (memq wflex completion-styles)
|
|
(list wflex))
|
|
(cl-loop for s in completion-styles
|
|
unless (or (memq s helm-blacklist-completion-styles)
|
|
(memq wflex completion-styles))
|
|
collect s))))
|
|
(helm-append-at-nth
|
|
styles '(helm)
|
|
(if (memq wflex completion-styles)
|
|
1 0))))))
|
|
|
|
(defun helm-dynamic-completion (collection predicate &optional point metadata nomode)
|
|
"Build a function listing the possible completions of `helm-pattern' in COLLECTION.
|
|
|
|
Only the elements of COLLECTION that satisfy PREDICATE are considered.
|
|
Argument POINT is same as in `completion-all-completions' and is
|
|
meaningful only when using some kind of `completion-at-point'.
|
|
The return value is a list of completions that may be sorted by the
|
|
sort function provided by the completion-style in use (emacs-27 only),
|
|
otherwise (emacs-26) the sort function have to be provided if needed
|
|
either with a FCT function in source or by passing the sort function
|
|
with METADATA e.g. (metadata (display-sort-function . foo)).
|
|
|
|
Example:
|
|
|
|
(helm :sources (helm-build-sync-source \"test\"
|
|
:candidates (helm-dynamic-completion
|
|
'(foo bar baz foab)
|
|
'symbolp)
|
|
:match-dynamic t)
|
|
:buffer \"*helm test*\")
|
|
|
|
When argument NOMODE is non nil don't use `completion-styles' as
|
|
specified in `helm-completion-styles-alist'."
|
|
(lambda ()
|
|
(let* ((completion-styles
|
|
(helm--prepare-completion-styles nomode))
|
|
(completion-flex-nospace t)
|
|
(compsfn (lambda (str pred _action)
|
|
(let* ((comps (completion-all-completions
|
|
str
|
|
(if (functionp collection)
|
|
(funcall collection str predicate t)
|
|
collection)
|
|
pred
|
|
(or point 0)
|
|
(or metadata '(metadata))))
|
|
(last-data (last comps))
|
|
(sort-fn (completion-metadata-get
|
|
metadata 'display-sort-function))
|
|
all)
|
|
(when (cdr last-data)
|
|
(setcdr last-data nil))
|
|
(setq all (copy-sequence comps))
|
|
(if sort-fn (funcall sort-fn all) all)))))
|
|
;; Ensure circular objects are removed.
|
|
(complete-with-action t compsfn helm-pattern predicate))))
|
|
|
|
;; Yank text at point.
|
|
;;
|
|
;;
|
|
(defun helm-yank-text-at-point (arg)
|
|
"Yank text at point in `helm-current-buffer' into minibuffer."
|
|
(interactive "p")
|
|
(with-helm-current-buffer
|
|
(let ((fwd-fn (or helm-yank-text-at-point-function #'forward-word))
|
|
diff)
|
|
;; Start to initial point if C-w have never been hit.
|
|
(unless helm-yank-point
|
|
(setq helm-yank-point (car helm-current-position)))
|
|
(save-excursion
|
|
(goto-char helm-yank-point)
|
|
(helm-set-pattern
|
|
(if (< arg 0)
|
|
(with-temp-buffer
|
|
(insert helm-pattern)
|
|
(let ((end (point-max)))
|
|
(goto-char end)
|
|
(funcall fwd-fn -1)
|
|
(setq diff (- end (point)))
|
|
(delete-region (point) end)
|
|
(buffer-string)))
|
|
(funcall fwd-fn arg)
|
|
(concat
|
|
;; Allow yankink beyond eol allow inserting e.g long
|
|
;; urls in mail buffers.
|
|
helm-pattern (replace-regexp-in-string
|
|
"\\`\n" ""
|
|
(buffer-substring-no-properties
|
|
helm-yank-point (point))))))
|
|
(setq helm-yank-point (if diff (- (point) diff) (point)))))))
|
|
(put 'helm-yank-text-at-point 'helm-only t)
|
|
|
|
(defun helm-undo-yank-text-at-point ()
|
|
"Undo last entry added by `helm-yank-text-at-point'."
|
|
(interactive)
|
|
(helm-yank-text-at-point -1))
|
|
(put 'helm-undo-yank-text-at-point 'helm-only t)
|
|
|
|
(defun helm-reset-yank-point ()
|
|
(setq helm-yank-point nil))
|
|
|
|
(add-hook 'helm-cleanup-hook 'helm-reset-yank-point)
|
|
(add-hook 'helm-after-initialize-hook 'helm-reset-yank-point)
|
|
|
|
;;; Ansi
|
|
;;
|
|
;;
|
|
(defvar helm--ansi-color-regexp
|
|
"\033\\[\\(K\\|[0-9;]*m\\)")
|
|
(defvar helm--ansi-color-drop-regexp
|
|
"\033\\[\\([ABCDsuK]\\|[12][JK]\\|=[0-9]+[hI]\\|[0-9;]*[Hf]\\)")
|
|
(defun helm--ansi-color-apply (string)
|
|
"A version of `ansi-color-apply' immune to upstream changes.
|
|
|
|
Similar to the emacs-24.5 version without support to `ansi-color-context'
|
|
which is buggy in emacs.
|
|
|
|
Modify also `ansi-color-regexp' by using own variable `helm--ansi-color-regexp'
|
|
that match whole STRING.
|
|
|
|
This is needed to provide compatibility for both emacs-25 and emacs-24.5
|
|
as emacs-25 version of `ansi-color-apply' is partially broken."
|
|
(let ((start 0)
|
|
codes end escape-sequence
|
|
result colorized-substring)
|
|
;; Find the next escape sequence.
|
|
(while (setq end (string-match helm--ansi-color-regexp string start))
|
|
(setq escape-sequence (match-string 1 string))
|
|
;; Colorize the old block from start to end using old face.
|
|
(when codes
|
|
(put-text-property
|
|
start end 'font-lock-face (ansi-color--find-face codes) string))
|
|
(setq colorized-substring (substring string start end)
|
|
start (match-end 0))
|
|
;; Eliminate unrecognized ANSI sequences.
|
|
(while (string-match helm--ansi-color-drop-regexp colorized-substring)
|
|
(setq colorized-substring
|
|
(replace-match "" nil nil colorized-substring)))
|
|
(push colorized-substring result)
|
|
;; Create new face, by applying escape sequence parameters.
|
|
(setq codes (ansi-color-apply-sequence escape-sequence codes)))
|
|
;; If the rest of the string should have a face, put it there.
|
|
(when codes
|
|
(put-text-property
|
|
start (length string)
|
|
'font-lock-face (ansi-color--find-face codes) string))
|
|
;; Save the remainder of the string to the result.
|
|
(if (string-match "\033" string start)
|
|
(push (substring string start (match-beginning 0)) result)
|
|
(push (substring string start) result))
|
|
(apply 'concat (nreverse result))))
|
|
|
|
(provide 'helm-lib)
|
|
|
|
;; Local Variables:
|
|
;; byte-compile-warnings: (not obsolete)
|
|
;; coding: utf-8
|
|
;; indent-tabs-mode: nil
|
|
;; End:
|
|
|
|
;;; helm-lib ends here
|