subr-x.el 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. ;;; subr-x.el --- extra Lisp functions -*- lexical-binding:t -*-
  2. ;; Copyright (C) 2013-2017 Free Software Foundation, Inc.
  3. ;; Maintainer: emacs-devel@gnu.org
  4. ;; Keywords: convenience
  5. ;; Package: emacs
  6. ;; This file is part of GNU Emacs.
  7. ;; GNU Emacs is free software: you can redistribute it and/or modify
  8. ;; it under the terms of the GNU General Public License as published by
  9. ;; the Free Software Foundation, either version 3 of the License, or
  10. ;; (at your option) any later version.
  11. ;; GNU Emacs is distributed in the hope that it will be useful,
  12. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. ;; GNU General Public License for more details.
  15. ;; You should have received a copy of the GNU General Public License
  16. ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
  17. ;;; Commentary:
  18. ;; Less commonly used functions that complement basic APIs, often implemented in
  19. ;; C code (like hash-tables and strings), and are not eligible for inclusion
  20. ;; in subr.el.
  21. ;; Do not document these functions in the lispref.
  22. ;; http://lists.gnu.org/archive/html/emacs-devel/2014-01/msg01006.html
  23. ;; NB If you want to use this library, it's almost always correct to use:
  24. ;; (eval-when-compile (require 'subr-x))
  25. ;;; Code:
  26. (eval-when-compile (require 'cl-lib))
  27. (defmacro internal--thread-argument (first? &rest forms)
  28. "Internal implementation for `thread-first' and `thread-last'.
  29. When Argument FIRST? is non-nil argument is threaded first, else
  30. last. FORMS are the expressions to be threaded."
  31. (pcase forms
  32. (`(,x (,f . ,args) . ,rest)
  33. `(internal--thread-argument
  34. ,first? ,(if first? `(,f ,x ,@args) `(,f ,@args ,x)) ,@rest))
  35. (`(,x ,f . ,rest) `(internal--thread-argument ,first? (,f ,x) ,@rest))
  36. (_ (car forms))))
  37. (defmacro thread-first (&rest forms)
  38. "Thread FORMS elements as the first argument of their successor.
  39. Example:
  40. (thread-first
  41. 5
  42. (+ 20)
  43. (/ 25)
  44. -
  45. (+ 40))
  46. Is equivalent to:
  47. (+ (- (/ (+ 5 20) 25)) 40)
  48. Note how the single `-' got converted into a list before
  49. threading."
  50. (declare (indent 1)
  51. (debug (form &rest [&or symbolp (sexp &rest form)])))
  52. `(internal--thread-argument t ,@forms))
  53. (defmacro thread-last (&rest forms)
  54. "Thread FORMS elements as the last argument of their successor.
  55. Example:
  56. (thread-last
  57. 5
  58. (+ 20)
  59. (/ 25)
  60. -
  61. (+ 40))
  62. Is equivalent to:
  63. (+ 40 (- (/ 25 (+ 20 5))))
  64. Note how the single `-' got converted into a list before
  65. threading."
  66. (declare (indent 1) (debug thread-first))
  67. `(internal--thread-argument nil ,@forms))
  68. (defsubst internal--listify (elt)
  69. "Wrap ELT in a list if it is not one."
  70. (if (not (listp elt))
  71. (list elt)
  72. elt))
  73. (defsubst internal--check-binding (binding)
  74. "Check BINDING is properly formed."
  75. (when (> (length binding) 2)
  76. (signal
  77. 'error
  78. (cons "`let' bindings can have only one value-form" binding)))
  79. binding)
  80. (defsubst internal--build-binding-value-form (binding prev-var)
  81. "Build the conditional value form for BINDING using PREV-VAR."
  82. `(,(car binding) (and ,prev-var ,(cadr binding))))
  83. (defun internal--build-binding (binding prev-var)
  84. "Check and build a single BINDING with PREV-VAR."
  85. (thread-first
  86. binding
  87. internal--listify
  88. internal--check-binding
  89. (internal--build-binding-value-form prev-var)))
  90. (defun internal--build-bindings (bindings)
  91. "Check and build conditional value forms for BINDINGS."
  92. (let ((prev-var t))
  93. (mapcar (lambda (binding)
  94. (let ((binding (internal--build-binding binding prev-var)))
  95. (setq prev-var (car binding))
  96. binding))
  97. bindings)))
  98. (defmacro if-let* (bindings then &rest else)
  99. "Bind variables according to VARLIST and eval THEN or ELSE.
  100. Each binding is evaluated in turn with `let*', and evaluation
  101. stops if a binding value is nil. If all are non-nil, the value
  102. of THEN is returned, or the last form in ELSE is returned.
  103. Each element of VARLIST is a symbol (which is bound to nil)
  104. or a list (SYMBOL VALUEFORM) (which binds SYMBOL to the value of VALUEFORM).
  105. In the special case you only want to bind a single value,
  106. VARLIST can just be a plain tuple.
  107. \n(fn VARLIST THEN ELSE...)"
  108. (declare (indent 2)
  109. (debug ([&or (&rest [&or symbolp (symbolp form)]) (symbolp form)]
  110. form body)))
  111. (when (and (<= (length bindings) 2)
  112. (not (listp (car bindings))))
  113. ;; Adjust the single binding case
  114. (setq bindings (list bindings)))
  115. `(let* ,(internal--build-bindings bindings)
  116. (if ,(car (internal--listify (car (last bindings))))
  117. ,then
  118. ,@else)))
  119. (defmacro when-let* (bindings &rest body)
  120. "Bind variables according to VARLIST and conditionally eval BODY.
  121. Each binding is evaluated in turn with `let*', and evaluation
  122. stops if a binding value is nil. If all are non-nil, the value
  123. of the last form in BODY is returned.
  124. Each element of VARLIST is a symbol (which is bound to nil)
  125. or a list (SYMBOL VALUEFORM) (which binds SYMBOL to the value of VALUEFORM).
  126. In the special case you only want to bind a single value,
  127. VARLIST can just be a plain tuple.
  128. \n(fn VARLIST BODY...)"
  129. (declare (indent 1) (debug if-let))
  130. (list 'if-let bindings (macroexp-progn body)))
  131. (defalias 'if-let 'if-let*)
  132. (defalias 'when-let 'when-let*)
  133. (defalias 'and-let* 'when-let*)
  134. (defsubst hash-table-empty-p (hash-table)
  135. "Check whether HASH-TABLE is empty (has 0 elements)."
  136. (zerop (hash-table-count hash-table)))
  137. (defsubst hash-table-keys (hash-table)
  138. "Return a list of keys in HASH-TABLE."
  139. (cl-loop for k being the hash-keys of hash-table collect k))
  140. (defsubst hash-table-values (hash-table)
  141. "Return a list of values in HASH-TABLE."
  142. (cl-loop for v being the hash-values of hash-table collect v))
  143. (defsubst string-empty-p (string)
  144. "Check whether STRING is empty."
  145. (string= string ""))
  146. (defsubst string-join (strings &optional separator)
  147. "Join all STRINGS using SEPARATOR."
  148. (mapconcat 'identity strings separator))
  149. (define-obsolete-function-alias 'string-reverse 'reverse "25.1")
  150. (defsubst string-trim-left (string &optional regexp)
  151. "Trim STRING of leading string matching REGEXP.
  152. REGEXP defaults to \"[ \\t\\n\\r]+\"."
  153. (if (string-match (concat "\\`\\(?:" (or regexp "[ \t\n\r]+")"\\)") string)
  154. (replace-match "" t t string)
  155. string))
  156. (defsubst string-trim-right (string &optional regexp)
  157. "Trim STRING of trailing string matching REGEXP.
  158. REGEXP defaults to \"[ \\t\\n\\r]+\"."
  159. (if (string-match (concat "\\(?:" (or regexp "[ \t\n\r]+") "\\)\\'") string)
  160. (replace-match "" t t string)
  161. string))
  162. (defsubst string-trim (string &optional trim-left trim-right)
  163. "Trim STRING of leading and trailing strings matching TRIM-LEFT and TRIM-RIGHT.
  164. TRIM-LEFT and TRIM-RIGHT default to \"[ \\t\\n\\r]+\"."
  165. (string-trim-left (string-trim-right string trim-right) trim-left))
  166. (defsubst string-blank-p (string)
  167. "Check whether STRING is either empty or only whitespace."
  168. (string-match-p "\\`[ \t\n\r]*\\'" string))
  169. (defsubst string-remove-prefix (prefix string)
  170. "Remove PREFIX from STRING if present."
  171. (if (string-prefix-p prefix string)
  172. (substring string (length prefix))
  173. string))
  174. (defsubst string-remove-suffix (suffix string)
  175. "Remove SUFFIX from STRING if present."
  176. (if (string-suffix-p suffix string)
  177. (substring string 0 (- (length string) (length suffix)))
  178. string))
  179. (defun read-multiple-choice (prompt choices)
  180. "Ask user a multiple choice question.
  181. PROMPT should be a string that will be displayed as the prompt.
  182. CHOICES is an alist where the first element in each entry is a
  183. character to be entered, the second element is a short name for
  184. the entry to be displayed while prompting (if there's room, it
  185. might be shortened), and the third, optional entry is a longer
  186. explanation that will be displayed in a help buffer if the user
  187. requests more help.
  188. This function translates user input into responses by consulting
  189. the bindings in `query-replace-map'; see the documentation of
  190. that variable for more information. In this case, the useful
  191. bindings are `recenter', `scroll-up', and `scroll-down'. If the
  192. user enters `recenter', `scroll-up', or `scroll-down' responses,
  193. perform the requested window recentering or scrolling and ask
  194. again.
  195. When `use-dialog-box' is t (the default), this function can pop
  196. up a dialog window to collect the user input. That functionality
  197. requires `display-popup-menus-p' to return t. Otherwise, a text
  198. dialog will be used.
  199. The return value is the matching entry from the CHOICES list.
  200. Usage example:
  201. \(read-multiple-choice \"Continue connecting?\"
  202. \\='((?a \"always\")
  203. (?s \"session only\")
  204. (?n \"no\")))"
  205. (let* ((altered-names nil)
  206. (full-prompt
  207. (format
  208. "%s (%s): "
  209. prompt
  210. (mapconcat
  211. (lambda (elem)
  212. (let* ((name (cadr elem))
  213. (pos (seq-position name (car elem)))
  214. (altered-name
  215. (cond
  216. ;; Not in the name string.
  217. ((not pos)
  218. (format "[%c] %s" (car elem) name))
  219. ;; The prompt character is in the name, so highlight
  220. ;; it on graphical terminals...
  221. ((display-supports-face-attributes-p
  222. '(:underline t) (window-frame))
  223. (setq name (copy-sequence name))
  224. (put-text-property pos (1+ pos)
  225. 'face 'read-multiple-choice-face
  226. name)
  227. name)
  228. ;; And put it in [bracket] on non-graphical terminals.
  229. (t
  230. (concat
  231. (substring name 0 pos)
  232. "["
  233. (upcase (substring name pos (1+ pos)))
  234. "]"
  235. (substring name (1+ pos)))))))
  236. (push (cons (car elem) altered-name)
  237. altered-names)
  238. altered-name))
  239. (append choices '((?? "?")))
  240. ", ")))
  241. tchar buf wrong-char answer)
  242. (save-window-excursion
  243. (save-excursion
  244. (while (not tchar)
  245. (message "%s%s"
  246. (if wrong-char
  247. "Invalid choice. "
  248. "")
  249. full-prompt)
  250. (setq tchar
  251. (if (and (display-popup-menus-p)
  252. last-input-event ; not during startup
  253. (listp last-nonmenu-event)
  254. use-dialog-box)
  255. (x-popup-dialog
  256. t
  257. (cons prompt
  258. (mapcar
  259. (lambda (elem)
  260. (cons (capitalize (cadr elem))
  261. (car elem)))
  262. choices)))
  263. (condition-case nil
  264. (let ((cursor-in-echo-area t))
  265. (read-char))
  266. (error nil))))
  267. (setq answer (lookup-key query-replace-map (vector tchar) t))
  268. (setq tchar
  269. (cond
  270. ((eq answer 'recenter)
  271. (recenter) t)
  272. ((eq answer 'scroll-up)
  273. (ignore-errors (scroll-up-command)) t)
  274. ((eq answer 'scroll-down)
  275. (ignore-errors (scroll-down-command)) t)
  276. ((eq answer 'scroll-other-window)
  277. (ignore-errors (scroll-other-window)) t)
  278. ((eq answer 'scroll-other-window-down)
  279. (ignore-errors (scroll-other-window-down)) t)
  280. (t tchar)))
  281. (when (eq tchar t)
  282. (setq wrong-char nil
  283. tchar nil))
  284. ;; The user has entered an invalid choice, so display the
  285. ;; help messages.
  286. (when (and (not (eq tchar nil))
  287. (not (assq tchar choices)))
  288. (setq wrong-char (not (memq tchar '(?? ?\C-h)))
  289. tchar nil)
  290. (when wrong-char
  291. (ding))
  292. (with-help-window (setq buf (get-buffer-create
  293. "*Multiple Choice Help*"))
  294. (with-current-buffer buf
  295. (erase-buffer)
  296. (pop-to-buffer buf)
  297. (insert prompt "\n\n")
  298. (let* ((columns (/ (window-width) 25))
  299. (fill-column 21)
  300. (times 0)
  301. (start (point)))
  302. (dolist (elem choices)
  303. (goto-char start)
  304. (unless (zerop times)
  305. (if (zerop (mod times columns))
  306. ;; Go to the next "line".
  307. (goto-char (setq start (point-max)))
  308. ;; Add padding.
  309. (while (not (eobp))
  310. (end-of-line)
  311. (insert (make-string (max (- (* (mod times columns)
  312. (+ fill-column 4))
  313. (current-column))
  314. 0)
  315. ?\s))
  316. (forward-line 1))))
  317. (setq times (1+ times))
  318. (let ((text
  319. (with-temp-buffer
  320. (insert (format
  321. "%c: %s\n"
  322. (car elem)
  323. (cdr (assq (car elem) altered-names))))
  324. (fill-region (point-min) (point-max))
  325. (when (nth 2 elem)
  326. (let ((start (point)))
  327. (insert (nth 2 elem))
  328. (unless (bolp)
  329. (insert "\n"))
  330. (fill-region start (point-max))))
  331. (buffer-string))))
  332. (goto-char start)
  333. (dolist (line (split-string text "\n"))
  334. (end-of-line)
  335. (if (bolp)
  336. (insert line "\n")
  337. (insert line))
  338. (forward-line 1)))))))))))
  339. (when (buffer-live-p buf)
  340. (kill-buffer buf))
  341. (assq tchar choices)))
  342. (provide 'subr-x)
  343. ;;; subr-x.el ends here