Klimi's new dotfiles with stow.
25'ten fazla konu seçemezsiniz Konular bir harf veya rakamla başlamalı, kısa çizgiler ('-') içerebilir ve en fazla 35 karakter uzunluğunda olabilir.

484 satır
18 KiB

5 yıl önce
  1. ;;; smex.el --- M-x interface with Ido-style fuzzy matching. -*- lexical-binding: t; -*-
  2. ;; Copyright (C) 2009-2014 Cornelius Mika and contributors
  3. ;;
  4. ;; Author: Cornelius Mika <cornelius.mika@gmail.com> and contributors
  5. ;; URL: http://github.com/nonsequitur/smex/
  6. ;; Package-Version: 20151212.2209
  7. ;; Package-Requires: ((emacs "24"))
  8. ;; Version: 3.0
  9. ;; Keywords: convenience, usability
  10. ;; This file is not part of GNU Emacs.
  11. ;;; License:
  12. ;; Licensed under the same terms as Emacs.
  13. ;;; Commentary:
  14. ;; Quick start:
  15. ;; run (smex-initialize)
  16. ;;
  17. ;; Bind the following commands:
  18. ;; smex, smex-major-mode-commands
  19. ;;
  20. ;; For a detailed introduction see:
  21. ;; http://github.com/nonsequitur/smex/blob/master/README.markdown
  22. ;;; Code:
  23. (require 'ido)
  24. (defgroup smex nil
  25. "M-x interface with Ido-style fuzzy matching and ranking heuristics."
  26. :group 'extensions
  27. :group 'convenience
  28. :link '(emacs-library-link :tag "Lisp File" "smex.el"))
  29. (defcustom smex-auto-update t
  30. "If non-nil, `Smex' checks for new commands each time it is run.
  31. Turn it off for minor speed improvements on older systems."
  32. :type 'boolean
  33. :group 'smex)
  34. (defcustom smex-save-file (locate-user-emacs-file "smex-items" ".smex-items")
  35. "File in which the smex state is saved between Emacs sessions.
  36. Variables stored are: `smex-data', `smex-history'.
  37. Must be set before initializing Smex."
  38. :type 'string
  39. :group 'smex)
  40. (defcustom smex-history-length 7
  41. "Determines on how many recently executed commands
  42. Smex should keep a record.
  43. Must be set before initializing Smex."
  44. :type 'integer
  45. :group 'smex)
  46. (defcustom smex-prompt-string "M-x "
  47. "String to display in the Smex prompt."
  48. :type 'string
  49. :group 'smex)
  50. (defcustom smex-flex-matching t
  51. "Enables Ido flex matching. On by default.
  52. Set this to nil to disable fuzzy matching."
  53. :type 'boolean
  54. :group 'smex)
  55. (defvar smex-initialized-p nil)
  56. (defvar smex-cache)
  57. (defvar smex-ido-cache)
  58. (defvar smex-data)
  59. (defvar smex-history)
  60. (defvar smex-command-count 0)
  61. (defvar smex-custom-action nil)
  62. ;; Check if Smex is supported
  63. (when (equal (cons 1 1)
  64. (ignore-errors
  65. (subr-arity (symbol-function 'execute-extended-command))))
  66. (error "Your Emacs has a non-elisp version of `execute-extended-command', which is incompatible with Smex"))
  67. ;;--------------------------------------------------------------------------------
  68. ;; Smex Interface
  69. ;;;###autoload
  70. (defun smex ()
  71. (interactive)
  72. (unless smex-initialized-p
  73. (smex-initialize))
  74. (if (smex-already-running)
  75. (smex-update-and-rerun)
  76. (and smex-auto-update
  77. (smex-detect-new-commands)
  78. (smex-update))
  79. (smex-read-and-run smex-ido-cache)))
  80. (defun smex-already-running ()
  81. (and (boundp 'ido-choice-list)
  82. (eql ido-choice-list smex-ido-cache)
  83. (minibuffer-window-active-p (selected-window))))
  84. (defun smex-update-and-rerun ()
  85. (smex-do-with-selected-item
  86. (lambda (_) (smex-update) (smex-read-and-run smex-ido-cache ido-text))))
  87. (defun smex-read-and-run (commands &optional initial-input)
  88. (let* ((chosen-item-name (smex-completing-read commands initial-input))
  89. (chosen-item (intern chosen-item-name)))
  90. (if smex-custom-action
  91. (let ((action smex-custom-action))
  92. (setq smex-custom-action nil)
  93. (funcall action chosen-item))
  94. (unwind-protect
  95. (with-no-warnings ; Don't warn about interactive use of `execute-extended-command'
  96. (execute-extended-command current-prefix-arg chosen-item-name))
  97. (smex-rank chosen-item)))))
  98. ;;;###autoload
  99. (defun smex-major-mode-commands ()
  100. "Like `smex', but limited to commands that are relevant to the active major mode."
  101. (interactive)
  102. (unless smex-initialized-p
  103. (smex-initialize))
  104. (let ((commands (delete-dups (append (smex-extract-commands-from-keymap (current-local-map))
  105. (smex-extract-commands-from-features major-mode)))))
  106. (setq commands (smex-sort-according-to-cache commands))
  107. (setq commands (mapcar #'symbol-name commands))
  108. (smex-read-and-run commands)))
  109. (defun smex-completing-read (choices initial-input)
  110. (let ((ido-completion-map ido-completion-map)
  111. (ido-setup-hook (cons 'smex-prepare-ido-bindings ido-setup-hook))
  112. (ido-enable-prefix nil)
  113. (ido-enable-flex-matching smex-flex-matching)
  114. (ido-max-prospects 10)
  115. (minibuffer-completion-table choices))
  116. (ido-completing-read (smex-prompt-with-prefix-arg) choices nil nil
  117. initial-input 'extended-command-history (car choices))))
  118. (defun smex-prompt-with-prefix-arg ()
  119. (if (not current-prefix-arg)
  120. smex-prompt-string
  121. (concat
  122. (if (eq current-prefix-arg '-)
  123. "- "
  124. (if (integerp current-prefix-arg)
  125. (format "%d " current-prefix-arg)
  126. (if (= (car current-prefix-arg) 4)
  127. "C-u "
  128. (format "%d " (car current-prefix-arg)))))
  129. smex-prompt-string)))
  130. (defun smex-prepare-ido-bindings ()
  131. (define-key ido-completion-map (kbd "TAB") 'minibuffer-complete)
  132. (define-key ido-completion-map (kbd "C-h f") 'smex-describe-function)
  133. (define-key ido-completion-map (kbd "C-h w") 'smex-where-is)
  134. (define-key ido-completion-map (kbd "M-.") 'smex-find-function)
  135. (define-key ido-completion-map (kbd "C-a") 'move-beginning-of-line))
  136. ;;--------------------------------------------------------------------------------
  137. ;; Cache and Maintenance
  138. (defun smex-rebuild-cache ()
  139. (interactive)
  140. (setq smex-cache nil)
  141. ;; Build up list 'new-commands' and later put it at the end of 'smex-cache'.
  142. ;; This speeds up sorting.
  143. (let (new-commands)
  144. (mapatoms (lambda (symbol)
  145. (when (commandp symbol)
  146. (let ((known-command (assq symbol smex-data)))
  147. (if known-command
  148. (setq smex-cache (cons known-command smex-cache))
  149. (setq new-commands (cons (list symbol) new-commands)))))))
  150. (if (eq (length smex-cache) 0)
  151. (setq smex-cache new-commands)
  152. (setcdr (last smex-cache) new-commands)))
  153. (setq smex-cache (sort smex-cache 'smex-sorting-rules))
  154. (smex-restore-history)
  155. (setq smex-ido-cache (smex-convert-for-ido smex-cache)))
  156. (defun smex-convert-for-ido (command-items)
  157. (mapcar (lambda (command-item) (symbol-name (car command-item))) command-items))
  158. (defun smex-restore-history ()
  159. "Rearranges `smex-cache' according to `smex-history'"
  160. (if (> (length smex-history) smex-history-length)
  161. (setcdr (nthcdr (- smex-history-length 1) smex-history) nil))
  162. (mapc (lambda (command)
  163. (unless (eq command (caar smex-cache))
  164. (let ((command-cell-position (smex-detect-position
  165. smex-cache
  166. (lambda (cell)
  167. (eq command (caar cell))))))
  168. (when command-cell-position
  169. (let ((command-cell (smex-remove-nth-cell
  170. command-cell-position smex-cache)))
  171. (setcdr command-cell smex-cache)
  172. (setq smex-cache command-cell))))))
  173. (reverse smex-history)))
  174. (defun smex-sort-according-to-cache (list)
  175. "Sorts a list of commands by their order in `smex-cache'"
  176. (let (sorted)
  177. (dolist (command-item smex-cache)
  178. (let ((command (car command-item)))
  179. (when (memq command list)
  180. (setq sorted (cons command sorted))
  181. (setq list (delq command list)))))
  182. (nreverse (append list sorted))))
  183. (defun smex-update ()
  184. (interactive)
  185. (smex-save-history)
  186. (smex-rebuild-cache))
  187. (defun smex-detect-new-commands ()
  188. (let ((i 0))
  189. (mapatoms (lambda (symbol) (if (commandp symbol) (setq i (1+ i)))))
  190. (unless (= i smex-command-count)
  191. (setq smex-command-count i))))
  192. (defun smex-auto-update (&optional idle-time)
  193. "Update Smex when Emacs has been idle for IDLE-TIME."
  194. (unless idle-time (setq idle-time 60))
  195. (run-with-idle-timer idle-time t
  196. '(lambda () (if (smex-detect-new-commands) (smex-update)))))
  197. ;;;###autoload
  198. (defun smex-initialize ()
  199. (interactive)
  200. (unless ido-mode (smex-initialize-ido))
  201. (smex-load-save-file)
  202. (smex-detect-new-commands)
  203. (smex-rebuild-cache)
  204. (add-hook 'kill-emacs-hook 'smex-save-to-file)
  205. (setq smex-initialized-p t))
  206. (defun smex-initialize-ido ()
  207. "Sets up a minimal Ido environment for `ido-completing-read'."
  208. (with-no-warnings ; `ido-init-completion-maps' is deprecated in Emacs 25
  209. (ido-init-completion-maps))
  210. (add-hook 'minibuffer-setup-hook 'ido-minibuffer-setup))
  211. (defsubst smex-save-file-not-empty-p ()
  212. (string-match-p "\[^[:space:]\]" (buffer-string)))
  213. (defun smex-load-save-file ()
  214. "Loads `smex-history' and `smex-data' from `smex-save-file'"
  215. (let ((save-file (expand-file-name smex-save-file)))
  216. (if (file-readable-p save-file)
  217. (with-temp-buffer
  218. (insert-file-contents save-file)
  219. (condition-case nil
  220. (setq smex-history (read (current-buffer))
  221. smex-data (read (current-buffer)))
  222. (error (if (smex-save-file-not-empty-p)
  223. (error "Invalid data in smex-save-file (%s). Can't restore history."
  224. smex-save-file)
  225. (unless (boundp 'smex-history) (setq smex-history nil))
  226. (unless (boundp 'smex-data) (setq smex-data nil))))))
  227. (setq smex-history nil smex-data nil))))
  228. (defun smex-save-history ()
  229. "Updates `smex-history'"
  230. (setq smex-history nil)
  231. (let ((cell smex-cache))
  232. (dotimes (_ smex-history-length)
  233. (setq smex-history (cons (caar cell) smex-history))
  234. (setq cell (cdr cell))))
  235. (setq smex-history (nreverse smex-history)))
  236. (defmacro smex-pp (list-var)
  237. `(smex-pp* ,list-var ,(symbol-name list-var)))
  238. (defun smex-save-to-file ()
  239. (interactive)
  240. (smex-save-history)
  241. (with-temp-file (expand-file-name smex-save-file)
  242. (smex-pp smex-history)
  243. (smex-pp smex-data)))
  244. ;;--------------------------------------------------------------------------------
  245. ;; Ranking
  246. (defun smex-sorting-rules (command-item other-command-item)
  247. "Returns true if COMMAND-ITEM should sort before OTHER-COMMAND-ITEM."
  248. (let* ((count (or (cdr command-item ) 0))
  249. (other-count (or (cdr other-command-item) 0))
  250. (name (car command-item))
  251. (other-name (car other-command-item))
  252. (length (length (symbol-name name)))
  253. (other-length (length (symbol-name other-name))))
  254. (or (> count other-count) ; 1. Frequency of use
  255. (and (= count other-count)
  256. (or (< length other-length) ; 2. Command length
  257. (and (= length other-length)
  258. (string< name other-name))))))) ; 3. Alphabetical order
  259. (defun smex-rank (command)
  260. (let ((command-item (or (assq command smex-cache)
  261. ;; Update caches and try again if not found.
  262. (progn (smex-update)
  263. (assq command smex-cache)))))
  264. (when command-item
  265. (smex-update-counter command-item)
  266. ;; Don't touch the cache order if the chosen command
  267. ;; has just been execucted previously.
  268. (unless (eq command-item (car smex-cache))
  269. (let (command-cell
  270. (pos (smex-detect-position smex-cache (lambda (cell)
  271. (eq command-item (car cell))))))
  272. ;; Remove the just executed command.
  273. (setq command-cell (smex-remove-nth-cell pos smex-cache))
  274. ;; And put it on top of the cache.
  275. (setcdr command-cell smex-cache)
  276. (setq smex-cache command-cell)
  277. ;; Repeat the same for the ido cache. Should this be DRYed?
  278. (setq command-cell (smex-remove-nth-cell pos smex-ido-cache))
  279. (setcdr command-cell smex-ido-cache)
  280. (setq smex-ido-cache command-cell)
  281. ;; Now put the last history item back to its normal place.
  282. (smex-sort-item-at smex-history-length))))))
  283. (defun smex-update-counter (command-item)
  284. (let ((count (cdr command-item)))
  285. (setcdr command-item
  286. (if count
  287. (1+ count)
  288. ;; Else: Command has just been executed for the first time.
  289. ;; Add it to `smex-data'.
  290. (if smex-data
  291. (setcdr (last smex-data) (list command-item))
  292. (setq smex-data (list command-item)))
  293. 1))))
  294. (defun smex-sort-item-at (n)
  295. "Sorts item at position N in `smex-cache'."
  296. (let* ((command-cell (nthcdr n smex-cache))
  297. (command-item (car command-cell)))
  298. (let ((insert-at (smex-detect-position
  299. command-cell
  300. (lambda (cell)
  301. (smex-sorting-rules command-item (car cell))))))
  302. ;; TODO: Should we handle the case of 'insert-at' being nil?
  303. ;; This will never happen in practice.
  304. (when (> insert-at 1)
  305. (setq command-cell (smex-remove-nth-cell n smex-cache))
  306. ;; smex-cache just got shorter by one element, so subtract '1' from insert-at.
  307. (setq insert-at (+ n (- insert-at 1)))
  308. (smex-insert-cell command-cell insert-at smex-cache)
  309. ;; Repeat the same for the ido cache. DRY?
  310. (setq command-cell (smex-remove-nth-cell n smex-ido-cache))
  311. (smex-insert-cell command-cell insert-at smex-ido-cache)))))
  312. (defun smex-detect-position (cell function)
  313. "Detects, relatively to CELL, the position of the cell
  314. on which FUNCTION returns true.
  315. Only checks cells after CELL, starting with the cell right after CELL.
  316. Returns nil when reaching the end of the list."
  317. (let ((pos 1))
  318. (catch 'break
  319. (while t
  320. (setq cell (cdr cell))
  321. (if (not cell)
  322. (throw 'break nil)
  323. (if (funcall function cell) (throw 'break pos))
  324. (setq pos (1+ pos)))))))
  325. (defun smex-remove-nth-cell (n list)
  326. "Removes and returns the Nth cell in LIST."
  327. (let* ((previous-cell (nthcdr (- n 1) list))
  328. (result (cdr previous-cell)))
  329. (setcdr previous-cell (cdr result))
  330. result))
  331. (defun smex-insert-cell (new-cell n list)
  332. "Inserts cell at position N in LIST."
  333. (let* ((cell (nthcdr (- n 1) list))
  334. (next-cell (cdr cell)))
  335. (setcdr (setcdr cell new-cell) next-cell)))
  336. ;;--------------------------------------------------------------------------------
  337. ;; Help and Reference
  338. (defun smex-do-with-selected-item (fn)
  339. (setq smex-custom-action fn)
  340. (ido-exit-minibuffer))
  341. (defun smex-describe-function ()
  342. (interactive)
  343. (smex-do-with-selected-item (lambda (chosen)
  344. (describe-function chosen)
  345. (pop-to-buffer "*Help*"))))
  346. (defun smex-where-is ()
  347. (interactive)
  348. (smex-do-with-selected-item 'where-is))
  349. (defun smex-find-function ()
  350. (interactive)
  351. (smex-do-with-selected-item 'find-function))
  352. (defun smex-extract-commands-from-keymap (map)
  353. (let (commands)
  354. (smex-parse-keymap map commands)
  355. commands))
  356. (defun smex-parse-keymap (map commands)
  357. (map-keymap (lambda (_binding element)
  358. (if (and (listp element) (eq 'keymap (car element)))
  359. (smex-parse-keymap element commands)
  360. ;; Strings are commands, too. Reject them.
  361. (if (and (symbolp element) (commandp element))
  362. (push element commands))))
  363. map))
  364. (defun smex-extract-commands-from-features (mode)
  365. (let ((library-path (symbol-file mode))
  366. (mode-name (symbol-name mode))
  367. commands)
  368. (string-match "\\(.+?\\)\\(-mode\\)?$" mode-name)
  369. ;; 'lisp-mode' -> 'lisp'
  370. (setq mode-name (match-string 1 mode-name))
  371. (if (string= mode-name "c") (setq mode-name "cc"))
  372. (setq mode-name (regexp-quote mode-name))
  373. (dolist (feature load-history)
  374. (let ((feature-path (car feature)))
  375. (when (and feature-path (or (equal feature-path library-path)
  376. (string-match mode-name (file-name-nondirectory
  377. feature-path))))
  378. (dolist (item (cdr feature))
  379. (if (and (listp item) (eq 'defun (car item)))
  380. (let ((function (cdr item)))
  381. (when (commandp function)
  382. (setq commands (append commands (list function))))))))))
  383. commands))
  384. (defun smex-show-unbound-commands ()
  385. "Shows unbound commands in a new buffer,
  386. sorted by frequency of use."
  387. (interactive)
  388. (setq smex-data (sort smex-data 'smex-sorting-rules))
  389. (let ((unbound-commands (delq nil
  390. (mapcar (lambda (command-item)
  391. (unless (where-is-internal (car command-item))
  392. command-item))
  393. smex-data))))
  394. (view-buffer-other-window "*Smex: Unbound Commands*")
  395. (setq buffer-read-only t)
  396. (let ((inhibit-read-only t))
  397. (erase-buffer)
  398. (smex-pp unbound-commands))
  399. (set-buffer-modified-p nil)
  400. (goto-char (point-min))))
  401. ;; A copy of `ido-pp' that's compatible with lexical bindings
  402. (defun smex-pp* (list list-name)
  403. (let ((print-level nil) (eval-expression-print-level nil)
  404. (print-length nil) (eval-expression-print-length nil))
  405. (insert "\n;; ----- " list-name " -----\n(\n ")
  406. (while list
  407. (let* ((elt (car list))
  408. (s (if (consp elt) (car elt) elt)))
  409. (if (and (stringp s) (= (length s) 0))
  410. (setq s nil))
  411. (if s
  412. (prin1 elt (current-buffer)))
  413. (if (and (setq list (cdr list)) s)
  414. (insert "\n "))))
  415. (insert "\n)\n")))
  416. (provide 'smex)
  417. ;;; smex.el ends here