Klimi's new dotfiles with stow.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

233 lines
8.9 KiB

5 years ago
  1. ;;; helm-comint.el --- Comint prompt navigation for helm. -*- lexical-binding: t -*-
  2. ;; Copyright (C) 2019 Pierre Neidhardt <mail@ambrevar.xyz>
  3. ;; This program is free software; you can redistribute it and/or modify
  4. ;; it under the terms of the GNU General Public License as published by
  5. ;; the Free Software Foundation, either version 3 of the License, or
  6. ;; (at your option) any later version.
  7. ;; This program is distributed in the hope that it will be useful,
  8. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. ;; GNU General Public License for more details.
  11. ;; You should have received a copy of the GNU General Public License
  12. ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
  13. ;;; Commentary:
  14. ;;
  15. ;; You can bind this as follows in .emacs:
  16. ;;
  17. ;; (add-hook 'comint-mode-hook
  18. ;; (lambda ()
  19. ;; (define-key comint-mode-map (kbd "M-s f") 'helm-comint-prompts-all)))
  20. ;;; Code:
  21. (require 'cl-lib)
  22. (require 'helm)
  23. (require 'helm-lib)
  24. (require 'helm-help)
  25. (require 'helm-elisp)
  26. ;;; Comint prompts
  27. ;;
  28. (defface helm-comint-prompts-promptidx
  29. '((t (:foreground "cyan")))
  30. "Face used to highlight comint prompt index."
  31. :group 'helm-comint-faces)
  32. (defface helm-comint-prompts-buffer-name
  33. '((t (:foreground "green")))
  34. "Face used to highlight comint buffer name."
  35. :group 'helm-comint-faces)
  36. (defcustom helm-comint-prompts-promptidx-p t
  37. "Show prompt number."
  38. :group 'helm-comint
  39. :type 'boolean)
  40. (defcustom helm-comint-mode-list '(comint-mode slime-repl-mode sly-mrepl-mode)
  41. "Supported modes for prompt navigation.
  42. Derived modes (e.g. Geiser's REPL) are automatically supported."
  43. :group 'helm-comint
  44. :type '(repeat (choice symbol)))
  45. (defcustom helm-comint-next-prompt-function '((sly-mrepl-mode . (lambda ()
  46. (sly-mrepl-next-prompt)
  47. (point))))
  48. "Alist of (MODE . NEXT-PROMPT-FUNCTION) to use.
  49. If the current major mode is a key in this list, the associated function will be
  50. used to navigate the prompts.
  51. The function must return the point after the prompt.
  52. Otherwise (comint-next-prompt 1) will be used."
  53. :group 'helm-comint
  54. :type '(alist :key-type symbol :value-type function))
  55. (defcustom helm-comint-max-offset 400
  56. "Max number of chars displayed per candidate in comint-input-ring browser.
  57. When `t', don't truncate candidate, show all.
  58. By default it is approximatively the number of bits contained in five lines
  59. of 80 chars each i.e 80*5.
  60. Note that if you set this to nil multiline will be disabled, i.e you
  61. will not have anymore separators between candidates."
  62. :type '(choice (const :tag "Disabled" t)
  63. (integer :tag "Max candidate offset"))
  64. :group 'helm-misc)
  65. (defvar helm-comint-prompts-keymap
  66. (let ((map (make-sparse-keymap)))
  67. (set-keymap-parent map helm-map)
  68. (define-key map (kbd "C-c o") 'helm-comint-prompts-other-window)
  69. (define-key map (kbd "C-c C-o") 'helm-comint-prompts-other-frame)
  70. map)
  71. "Keymap for `helm-comint-prompt-all'.")
  72. (defun helm-comint-prompts-list (mode &optional buffer)
  73. "List the prompts in BUFFER in mode MODE.
  74. Return a list of (\"prompt\" (point) (buffer-name) prompt-index))
  75. e.g. (\"ls\" 162 \"*shell*\" 3).
  76. If BUFFER is nil, use current buffer."
  77. (with-current-buffer (or buffer (current-buffer))
  78. (when (derived-mode-p mode)
  79. (save-excursion
  80. (goto-char (point-min))
  81. (let (result (count 1))
  82. (save-mark-and-excursion
  83. (helm-awhile (and (not (eobp))
  84. (helm-aif (alist-get major-mode helm-comint-next-prompt-function)
  85. (funcall it)
  86. (comint-next-prompt 1)))
  87. (push (list (buffer-substring-no-properties
  88. it (point-at-eol))
  89. it (buffer-name) count)
  90. result)
  91. (setq count (1+ count))))
  92. (nreverse result))))))
  93. (defun helm-comint-prompts-list-all (mode)
  94. "List the prompts of all buffers in mode MODE.
  95. See `helm-comint-prompts-list'."
  96. (cl-loop for b in (buffer-list)
  97. append (helm-comint-prompts-list mode b)))
  98. (defun helm-comint-prompts-transformer (candidates &optional all)
  99. ;; ("ls" 162 "*shell*" 3) => ("*shell*:3:ls" . ("ls" 162 "*shell*" 3))
  100. (cl-loop for (prt pos buf id) in candidates
  101. collect `(,(concat
  102. (when all
  103. (concat (propertize
  104. buf
  105. 'face 'helm-comint-prompts-buffer-name)
  106. ":"))
  107. (when helm-comint-prompts-promptidx-p
  108. (concat (propertize
  109. (number-to-string id)
  110. 'face 'helm-comint-prompts-promptidx)
  111. ":"))
  112. prt)
  113. . ,(list prt pos buf id))))
  114. (defun helm-comint-prompts-all-transformer (candidates)
  115. (helm-comint-prompts-transformer candidates t))
  116. (cl-defun helm-comint-prompts-goto (candidate &optional (action 'switch-to-buffer))
  117. ;; Candidate format: ("ls" 162 "*shell*" 3)
  118. (let ((buf (nth 2 candidate)))
  119. (unless (and (string= (buffer-name) buf)
  120. (eq action 'switch-to-buffer))
  121. (funcall action buf))
  122. (goto-char (nth 1 candidate))
  123. (recenter)))
  124. (defun helm-comint-prompts-goto-other-window (candidate)
  125. (helm-comint-prompts-goto candidate 'switch-to-buffer-other-window))
  126. (defun helm-comint-prompts-goto-other-frame (candidate)
  127. (helm-comint-prompts-goto candidate 'switch-to-buffer-other-frame))
  128. (defun helm-comint-prompts-other-window ()
  129. (interactive)
  130. (with-helm-alive-p
  131. (helm-exit-and-execute-action 'helm-comint-prompts-goto-other-window)))
  132. (put 'helm-comint-prompts-other-window 'helm-only t)
  133. (defun helm-comint-prompts-other-frame ()
  134. (interactive)
  135. (with-helm-alive-p
  136. (helm-exit-and-execute-action 'helm-comint-prompts-goto-other-frame)))
  137. (put 'helm-comint-prompts-other-frame 'helm-only t)
  138. ;;;###autoload
  139. (defun helm-comint-prompts ()
  140. "Pre-configured `helm' to browse the prompts of the current comint buffer."
  141. (interactive)
  142. (if (apply 'derived-mode-p helm-comint-mode-list)
  143. (helm :sources
  144. (helm-build-sync-source "Comint prompts"
  145. :candidates (helm-comint-prompts-list major-mode)
  146. :candidate-transformer 'helm-comint-prompts-transformer
  147. :action '(("Go to prompt" . helm-comint-prompts-goto)))
  148. :buffer "*helm comint prompts*")
  149. (message "Current buffer is not a comint buffer")))
  150. ;;;###autoload
  151. (defun helm-comint-prompts-all ()
  152. "Pre-configured `helm' to browse the prompts of all comint sessions."
  153. (interactive)
  154. (if (apply 'derived-mode-p helm-comint-mode-list)
  155. (helm :sources
  156. (helm-build-sync-source "All comint prompts"
  157. :candidates (helm-comint-prompts-list-all major-mode)
  158. :candidate-transformer 'helm-comint-prompts-all-transformer
  159. :action (quote (("Go to prompt" . helm-comint-prompts-goto)
  160. ("Go to prompt in other window `C-c o`" .
  161. helm-comint-prompts-goto-other-window)
  162. ("Go to prompt in other frame `C-c C-o`" .
  163. helm-comint-prompts-goto-other-frame)))
  164. :keymap helm-comint-prompts-keymap)
  165. :buffer "*helm comint all prompts*")
  166. (message "Current buffer is not a comint buffer")))
  167. ;;; Comint history
  168. ;;
  169. ;;
  170. (defun helm-comint-input-ring-action (candidate)
  171. "Default action for comint history."
  172. (with-helm-current-buffer
  173. (delete-region (comint-line-beginning-position) (point-max))
  174. (insert candidate)))
  175. (defvar helm-source-comint-input-ring
  176. (helm-build-sync-source "Comint history"
  177. :candidates (lambda ()
  178. (with-helm-current-buffer
  179. (cl-loop for elm in (ring-elements comint-input-ring)
  180. unless (string= elm "")
  181. collect elm)))
  182. :action 'helm-comint-input-ring-action
  183. ;; Multiline does not work for `shell' because of an Emacs bug.
  184. ;; It works in other REPLs like Geiser.
  185. :multiline 'helm-comint-max-offset)
  186. "Source that provides Helm completion against `comint-input-ring'.")
  187. ;;;###autoload
  188. (defun helm-comint-input-ring ()
  189. "Preconfigured `helm' that provide completion of `comint' history."
  190. (interactive)
  191. (when (derived-mode-p 'comint-mode)
  192. (helm :sources 'helm-source-comint-input-ring
  193. :input (buffer-substring-no-properties (comint-line-beginning-position)
  194. (point-at-eol))
  195. :buffer "*helm comint history*")))
  196. (provide 'helm-comint)
  197. ;; Local Variables:
  198. ;; byte-compile-warnings: (not obsolete)
  199. ;; coding: utf-8
  200. ;; indent-tabs-mode: nil
  201. ;; End:
  202. ;;; helm-comint.el ends here