├── .gitignore ├── doc ├── specs.png ├── apropos.png ├── spec-ns.png ├── browse-ns.png ├── cheatsheet.png ├── repl-history.png └── apropos-documentation.png ├── CHANGELOG.md ├── README.md ├── helm-cider-util.el ├── helm-cider-repl.el ├── helm-cider-cheatsheet.el ├── helm-cider-spec.el └── helm-cider.el /.gitignore: -------------------------------------------------------------------------------- 1 | *.elc -------------------------------------------------------------------------------- /doc/specs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clojure-emacs/helm-cider/HEAD/doc/specs.png -------------------------------------------------------------------------------- /doc/apropos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clojure-emacs/helm-cider/HEAD/doc/apropos.png -------------------------------------------------------------------------------- /doc/spec-ns.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clojure-emacs/helm-cider/HEAD/doc/spec-ns.png -------------------------------------------------------------------------------- /doc/browse-ns.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clojure-emacs/helm-cider/HEAD/doc/browse-ns.png -------------------------------------------------------------------------------- /doc/cheatsheet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clojure-emacs/helm-cider/HEAD/doc/cheatsheet.png -------------------------------------------------------------------------------- /doc/repl-history.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clojure-emacs/helm-cider/HEAD/doc/repl-history.png -------------------------------------------------------------------------------- /doc/apropos-documentation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clojure-emacs/helm-cider/HEAD/doc/apropos-documentation.png -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## master (unreleased) 4 | 5 | ## 0.5.0 (2022-01-03) 6 | 7 | ### New features 8 | 9 | - [#10](https://github.com/clojure-emacs/helm-cider/pull/10): Port `clojure-cheatsheet`. 10 | 11 | ### Changed 12 | 13 | - [#13](https://github.com/clojure-emacs/helm-cider/pull/13): Remove `cider-compat` require (file was removed from CIDER). 14 | - Require Emacs 26 (follows upstream changes in CIDER). 15 | 16 | ## 0.4.0 (2018-01-20) 17 | 18 | ### New features 19 | 20 | * 9480e96: Add spec browser 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![MELPA](https://melpa.org/packages/helm-cider-badge.svg)](https://melpa.org/#/helm-cider) 2 | [![License GPL 3][badge-license]](http://www.gnu.org/licenses/gpl-3.0.txt) 3 | 4 | # Helm Cider 5 | 6 | [Helm](https://github.com/emacs-helm/helm) interface to [CIDER](https://github.com/clojure-emacs/cider). 7 | 8 | ## Installation 9 | 10 | Install via [MELPA](https://melpa.org/#/). 11 | 12 | For general information on installing Emacs packages, see the [Emacs Wiki](https://www.emacswiki.org/emacs/InstallingPackages). 13 | 14 | ## Setup 15 | 16 | In your init file, add the following to activate `helm-cider-mode` and use Helm versions of CIDER commands: 17 | 18 | ```emacs-lisp 19 | (helm-cider-mode 1) 20 | ``` 21 | 22 | Use `M-x helm-cider-mode` to turn the minor mode off, or call 23 | 24 | ```emacs-lisp 25 | (helm-cider-mode -1) 26 | ``` 27 | 28 | ## Features 29 | 30 | The following assumes that `helm-cider-mode` is turned on, and that the reader is familiar with [Helm](https://github.com/emacs-helm/helm) functionality. 31 | 32 | ### Apropos 33 | 34 | Calling `cider-apropos` or `cider-apropos-select` brings up a Helm buffer of all symbols across all namespaces (except those excluded using the `helm-cider-apropos-excluded-ns` customizable variable). 35 | 36 | Each Helm source is a namespace. To select a namespace, use `C-c n` (the default value of `helm-cider-apropos-ns-key`) and select the desired namespace. 37 | 38 | ![Apropos](./doc/apropos.png) 39 | 40 | ### Apropos documentation 41 | 42 | Calling `cider-apropos-documentation` or `cider-apropos-documentation-select` brings up a Helm buffer of all symbols across all namespaces with documentation. 43 | 44 | Select a namespace as with normal apropos. 45 | 46 | If customizable variable `helm-cider-apropos-full-doc` is `t`, show full instead of short documentation. Toggle between full/short documentation with `C-]`. 47 | 48 | ![Apropos documentation](./doc/apropos-documentation.png) 49 | 50 | ### Namespaces 51 | 52 | Calling `cider-browse-ns` or `cider-browse-ns-all` brings up a Helm buffer of namespaces. 53 | 54 | Use `RET` to select a namespace and view symbols in that namespace *without* documentation. Use `S-RET` to select a namespace and view symbols in that namespace *with* documentation. 55 | 56 | ![Browse namespaces](./doc/browse-ns.png) 57 | 58 | ### Specs 59 | 60 | Calling `helm-cider-spec` brings up a Helm buffer of all spec names across all namespaces. 61 | 62 | Each Helm source is a namespace. To select a namespace, use `C-c n` (the default value of `helm-cider-spec-ns-key`) and select the desired namespace. 63 | 64 | ![Specs](./doc/specs.png) 65 | 66 | ### Spec namespaces 67 | 68 | Calling `helm-cider-spec-ns` brings up a Helm buffer of [spec](https://clojure.org/about/spec) namespaces. 69 | 70 | Use `RET` to select a namespace and view spec names in that namespace. 71 | 72 | ![Spec namespaces](./doc/spec-ns.png) 73 | 74 | ### REPL history 75 | 76 | Calling `helm-cider-repl-history` (bound to `C-c C-l` by default) in a CIDER REPL brings up a Helm buffer of REPL entries. 77 | 78 | Use `RET` to select an entry and insert it into the REPL. 79 | 80 | Use `f2` to delete an entry from history. Multiple entries can be marked and deleted. 81 | 82 | ![REPL History](./doc/repl-history.png) 83 | 84 | ### Clojure cheatsheet 85 | 86 | Calling `helm-cider-cheatsheet` brings up a Clojure cheatsheet. This is largely a port of @krisajenkins's [`clojure-cheatsheet`](https://github.com/clojure-emacs/clojure-cheatsheet). 87 | 88 | Matches are against both candidates and categories. For example, `arith` will match against the `Primitives : Numbers : Arithmetic` category. 89 | 90 | ![Clojure cheatsheet](./doc/cheatsheet.png) 91 | 92 | ## See Also 93 | 94 | - CIDER issue [#1541](https://github.com/clojure-emacs/cider/issues/1541), regarding [Ido](https://www.gnu.org/software/emacs/manual/html_mono/ido.html)/Helm support for `apropos`. 95 | - CIDER issue [#1059](https://github.com/clojure-emacs/cider/issues/1059), regarding seeing more completion candidates and grouping them by namespace. 96 | - [helm-clojure](https://github.com/prepor/helm-clojure), an earlier project with similar ideas. 97 | 98 | [badge-license]: https://img.shields.io/badge/license-GPLv3-blue.svg 99 | -------------------------------------------------------------------------------- /helm-cider-util.el: -------------------------------------------------------------------------------- 1 | ;;; helm-cider.el --- Helm interface to CIDER -*- lexical-binding: t; -*- 2 | 3 | ;; Copyright (C) 2016-2022 Tianxiang Xiong 4 | 5 | ;; Author: Tianxiang Xiong 6 | 7 | ;; This program 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 | 12 | ;; This program is distributed in the hope that it will be useful, 13 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | ;; GNU General Public License for more details. 16 | 17 | ;; You should have received a copy of the GNU General Public License 18 | ;; along with this program. If not, see . 19 | 20 | ;;; Commentary: 21 | 22 | ;; Common utility functions that don't belong anywhere else. 23 | 24 | ;;; Code: 25 | 26 | (require 'cider) 27 | (require 'cl-lib) 28 | (require 'subr-x) 29 | 30 | 31 | ;;;; Utilities 32 | 33 | (defun helm-cider--regexp-symbol (string) 34 | "Create a regexp that matches STRING as a symbol. 35 | 36 | If STRING starts with a character that `helm-major-mode' does not 37 | consider to be in the word or symbol syntax class, do not include 38 | a symbol-start \(\\_<\); otherwise, the regexp wouldn't 39 | match. Same for symbol-end." 40 | (if (string-empty-p string) 41 | "" 42 | (let* ((fchar (aref string 0)) 43 | (lchar (aref string (1- (length string)))) 44 | (symbol-start (with-syntax-table helm-major-mode-syntax-table 45 | (if (or (= ?w (char-syntax fchar)) 46 | (= ?_ (char-syntax fchar))) 47 | "\\_<" 48 | ""))) 49 | (symbol-end (with-syntax-table helm-major-mode-syntax-table 50 | (if (or (= ?w (char-syntax lchar)) 51 | (= ?_ (char-syntax lchar))) 52 | "\\_>" 53 | "")))) 54 | (concat symbol-start (regexp-quote string) symbol-end)))) 55 | 56 | (defun helm-cider--source-by-name (name &optional sources) 57 | "Get a Helm source in SOURCES by NAME. 58 | 59 | Default value of SOURCES is `helm-sources'." 60 | (car (cl-member-if (lambda (source) 61 | (string= name (assoc-default 'name source))) 62 | (or sources helm-sources)))) 63 | 64 | (defun helm-cider--symbol-name (qualified-name) 65 | "Get the name portion of the fully qualified symbol name 66 | QUALIFIED-NAME (e.g. \"reduce\" for \"clojure.core/reduce\"). 67 | 68 | Defaults to QUALIFIED-NAME if name is NOT qualified (as is the 69 | case for special forms)." 70 | (if (string-match-p "/" qualified-name) 71 | (cadr (split-string qualified-name "/")) 72 | qualified-name)) 73 | 74 | (defun helm-cider--symbol-ns (qualified-name) 75 | "Get the namespace portion of the fully qualified symbol name 76 | QUALIFIED-NAME (e.g. \"clojure.core\" for 77 | \"clojure.core/reduce\"). 78 | 79 | Defaults to the `clojure.core' ns if name is NOT qualified (as is 80 | the case for special forms)." 81 | (if (string-match-p "/" qualified-name) 82 | (car (split-string qualified-name "/")) 83 | "clojure.core")) 84 | 85 | (defun helm-cider--find-ns (ns) 86 | (cider-find-ns nil ns)) 87 | 88 | (defun helm-cider--find-var (var) 89 | (cider-find-var nil var)) 90 | 91 | (defun helm-cider--symbol-face (type) 92 | "Face for symbol of TYPE. 93 | 94 | TYPE values include \"function\", \"macro\", etc." 95 | (pcase type 96 | ("function" 'font-lock-function-name-face) 97 | ("macro" 'font-lock-keyword-face) 98 | ("special-form" 'font-lock-keyword-face) 99 | ("variable" 'font-lock-variable-name-face))) 100 | 101 | (defun helm-cider--doc-lookup-persistent-action (candidate) 102 | "Persistent action calling `cider-doc-lookup' on CANDIDATE." 103 | (cider-ensure-connected) 104 | (cider-ensure-op-supported "info") 105 | (if (and (helm-get-attr 'doc-lookup-p) 106 | (string= candidate (helm-get-attr 'current-candidate))) 107 | (progn 108 | (kill-buffer cider-doc-buffer) 109 | (helm-set-attr 'doc-lookup-p nil)) 110 | (cider-doc-lookup candidate) 111 | (helm-set-attr 'doc-lookup-p t)) 112 | (helm-set-attr 'current-candidate candidate)) 113 | 114 | (defmacro wrap-helm-cider-action (f &optional ops) 115 | "Wrap Helm CIDER actions. 116 | 117 | - Ensure that CIDER is connected 118 | - Ensure ops are supported" 119 | `(lambda (&rest args) 120 | (cider-ensure-connected) 121 | ,@(mapcar (lambda (op) `(cider-ensure-op-supported ,op)) ops) 122 | (apply #',f args))) 123 | 124 | (defvar helm-cider--doc-actions 125 | (helm-make-actions 126 | "CiderDoc" (wrap-helm-cider-action cider-doc-lookup) 127 | "Find definition" (wrap-helm-cider-action helm-cider--find-var) 128 | "Find on ClojureDocs" (wrap-helm-cider-action cider-clojuredocs-lookup)) 129 | "Actions for looking up symbol's documentation.") 130 | 131 | 132 | (provide 'helm-cider-util) 133 | 134 | ;;; helm-cider-util.el ends here 135 | -------------------------------------------------------------------------------- /helm-cider-repl.el: -------------------------------------------------------------------------------- 1 | ;;; helm-cider-repl.el --- Helm interface to CIDER REPL -*- lexical-binding: t; -*- 2 | 3 | ;; Copyright (C) 2016-2022 Tianxiang Xiong 4 | 5 | ;; Author: Tianxiang Xiong 6 | 7 | ;; This program 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 | 12 | ;; This program is distributed in the hope that it will be useful, 13 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | ;; GNU General Public License for more details. 16 | 17 | ;; You should have received a copy of the GNU General Public License 18 | ;; along with this program. If not, see . 19 | 20 | ;;; Commentary: 21 | 22 | ;; Helm interface to CIDER REPL. 23 | 24 | ;;; Code: 25 | 26 | (require 'cider-repl) 27 | (require 'cl-lib) 28 | (require 'subr-x) 29 | (require 'helm-core) 30 | 31 | 32 | ;;;; Customize 33 | 34 | (defgroup helm-cider-repl nil 35 | "Helm interface to CIDER REPL." 36 | :prefix "helm-cider-repl-" 37 | :group 'helm-cider 38 | :tag "Helm CIDER REPL") 39 | 40 | (defcustom helm-cider-repl-history-max-lines 5 41 | "Max number of lines displayed per candidate in CIDER REPL history browser. 42 | 43 | If not a positive integer, don't truncate candidate, show all." 44 | :group 'helm-cider-repl 45 | :type '(choice 46 | (const :tag "Disabled" nil) 47 | (integer :tag "Max number of lines"))) 48 | 49 | (defcustom helm-cider-repl-history-actions 50 | (helm-make-actions 51 | "Insert" 'helm-cider-repl-history-insert 52 | "Delete" 'helm-cider-repl-history-delete) 53 | "List of actions for Helm CIDER REPL source." 54 | :group 'helm-cider-repl 55 | :type '(alist :key-type string :value-type function)) 56 | 57 | (defcustom helm-cider-repl-follow nil 58 | "If true, turn on `helm-follow-mode' for CIDER REPL entries." 59 | :group 'helm-cider-repl 60 | :type 'boolean) 61 | 62 | 63 | ;;;; CIDER REPL History 64 | 65 | (defvar helm-cider-repl-history-map 66 | (let ((map (make-sparse-keymap))) 67 | (set-keymap-parent map helm-map) 68 | (define-key map (kbd "M-D") (lambda () 69 | (interactive) 70 | (with-helm-alive-p 71 | (helm-exit-and-execute-action 'helm-cider-repl-history-delete)))) 72 | map) 73 | "Keymap for `helm-cider-repl-history'.") 74 | 75 | (defun helm-cider-repl--history-candidates () 76 | "Candidates for Helm CIDER REPL history. 77 | 78 | Blank and duplicate candidates are excluded." 79 | (cl-loop 80 | for c in (helm-fast-remove-dups cider-repl-input-history :test 'equal) 81 | unless (string-blank-p c) 82 | collect c)) 83 | 84 | (defun helm-cider-repl--history-transformer (candidates _source) 85 | "Display only the `helm-cider-repl-history-max-lines' 86 | lines of candidate." 87 | (cl-loop 88 | for c in candidates 89 | for m = helm-cider-repl-history-max-lines 90 | when (get-text-property 0 'read-only c) 91 | do (set-text-properties 0 (length c) '(read-only nil) c) 92 | for n = (with-temp-buffer (insert c) (count-lines (point-min) (point-max))) 93 | if (and (integerp m) 94 | (> n m 0)) 95 | collect (cons (with-temp-buffer 96 | (insert c) 97 | (goto-char (point-min)) 98 | (concat 99 | (buffer-substring 100 | (point-min) 101 | (save-excursion 102 | (forward-line helm-cider-repl-history-max-lines) 103 | (point))) 104 | "[...]")) 105 | c) 106 | ;; Need to collect a cons b/c persistent action is executed on 107 | ;; unpropertied string otherwise 108 | ;; See: https://github.com/emacs-helm/helm/blob/v2.4.0/helm.el#L4999 109 | else collect (cons c c))) 110 | 111 | (defun helm-cider-repl--history-preview (candidate) 112 | "Preview the CIDER REPL history candidate in a temp buffer. 113 | 114 | Useful when the candidate longer than `helm-cider-repl-history-max-lines' lines." 115 | 116 | (let ((buf (get-buffer-create "*Helm CIDER REPL History Preview*"))) 117 | (cl-flet ((preview (candidate) 118 | (switch-to-buffer buf) 119 | (let ((inhibit-read-only t)) 120 | (erase-buffer) 121 | (insert candidate)))) 122 | (if (and (helm-get-attr 'previewp) 123 | (string= candidate (helm-get-attr 'current-candidate))) 124 | (progn 125 | (kill-buffer buf) 126 | (helm-set-attr 'previewp nil)) 127 | (preview candidate) 128 | (helm-set-attr 'previewp t))) 129 | (helm-set-attr 'current-candidate candidate))) 130 | 131 | (defun helm-cider-repl--history-source () 132 | "Source for Helm CIDER REPL history." 133 | (helm-build-sync-source "CIDER REPL History" 134 | :action helm-cider-repl-history-actions 135 | :candidates (helm-cider-repl--history-candidates) 136 | :filtered-candidate-transformer #'helm-cider-repl--history-transformer 137 | :follow (when helm-cider-repl-follow 1) 138 | :keymap helm-cider-repl-history-map 139 | :multiline t 140 | :persistent-action #'helm-cider-repl--history-preview 141 | :persistent-help "Preview entry")) 142 | 143 | 144 | ;;;; API 145 | 146 | ;;;###autoload 147 | (defun helm-cider-repl-history-insert (candidate) 148 | "Insert candidate at the last CIDER REPL prompt. 149 | 150 | Existing input at the prompt is cleared. 151 | 152 | This function is meant to be one of `helm-cider-repl-history-actions'." 153 | (goto-char (point-max)) 154 | (cider-repl-kill-input) 155 | (insert candidate)) 156 | 157 | ;;;###autoload 158 | (defun helm-cider-repl-history-delete (_candidate) 159 | "Delete marked candidates from `cider-repl-input-history'. 160 | 161 | This function is meant to be one of `helm-cider-repl-history-actions'." 162 | (cl-loop 163 | for cand in (helm-marked-candidates) 164 | do (setq cider-repl-input-history 165 | (delete cand cider-repl-input-history)))) 166 | 167 | ;;;###autoload 168 | (defun helm-cider-repl-history () 169 | "Helm interface to CIDER REPL history." 170 | (interactive) 171 | (if (null cider-repl-input-history) 172 | (user-error "No CIDER REPL history") 173 | (helm :buffer "*Helm CIDER REPL History*" 174 | :sources (helm-cider-repl--history-source)))) 175 | 176 | 177 | (provide 'helm-cider-repl) 178 | 179 | ;;; helm-cider-repl.el ends here 180 | -------------------------------------------------------------------------------- /helm-cider-cheatsheet.el: -------------------------------------------------------------------------------- 1 | ;;; helm-cider-cheatsheet.el --- Helm interface to CIDER cheatsheet -*- lexical-binding: t; -*- 2 | 3 | ;; Copyright (C) 2016-2022 Tianxiang Xiong 4 | 5 | ;; Author: Tianxiang Xiong 6 | 7 | ;; This program 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 | 12 | ;; This program is distributed in the hope that it will be useful, 13 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | ;; GNU General Public License for more details. 16 | 17 | ;; You should have received a copy of the GNU General Public License 18 | ;; along with this program. If not, see . 19 | 20 | ;;; Commentary: 21 | 22 | ;; Helm interface to CIDER cheatsheet. 23 | 24 | ;; Mostly taken from Kris Jenkins' `clojure-cheatsheet' 25 | ;; See: https://github.com/clojure-emacs/clojure-cheatsheet 26 | 27 | ;;; Code: 28 | 29 | (require 'cider) 30 | (require 'cider-cheatsheet) 31 | (require 'cl-lib) 32 | (require 'helm-core) 33 | (require 'helm-multi-match) 34 | (require 'helm-cider-util) 35 | 36 | 37 | ;;;; Helpers 38 | 39 | (defvar helm-cider-cheatsheet--jacked-in-source-p nil 40 | "Non-nil `helm-cider-cheatsheet--source' was evaluated while 41 | CIDER was jacked in. 42 | 43 | When loading this package at init, CIDER is not jacked in. Some 44 | font-locking etc. are then not available. 45 | 46 | When calling `helm-cider-cheatsheet', if this var is nil and 47 | CIDER is connected, the source is re-evaluated to obtain the 48 | additional information.") 49 | 50 | (defun helm-cider-cheatsheet--treewalk (before after node) 51 | "Walk a tree. 52 | Invoke BEFORE before the walk, and AFTER after it, on each NODE." 53 | (thread-last node 54 | (funcall before) 55 | (funcall (lambda (new-node) 56 | (if (listp new-node) 57 | (mapcar (lambda (child) 58 | (helm-cider-cheatsheet--treewalk before after child)) 59 | new-node) 60 | new-node))) 61 | (funcall after))) 62 | 63 | (defun helm-cider-cheatsheet--symbol-qualifier (namespace symbol) 64 | "Given a (Clojure) NAMESPACE and a SYMBOL, fully-qualify that symbol." 65 | (intern (format "%s/%s" namespace symbol))) 66 | 67 | (defun helm-cider-cheatsheet--string-qualifier (head subnode) 68 | (cond 69 | ((keywordp (car subnode)) (list head subnode)) 70 | ((symbolp (car subnode)) (cons head subnode)) 71 | ((stringp (car subnode)) (cons (format "%s : %s" head (car subnode)) 72 | (cdr subnode))) 73 | (t (mapcar (apply-partially 'helm-cider-cheatsheet--string-qualifier head) subnode)))) 74 | 75 | (defun helm-cider-cheatsheet--propagate-headings (node) 76 | (helm-cider-cheatsheet--treewalk 77 | #'identity 78 | (lambda (item) 79 | (if (listp item) 80 | (cl-destructuring-bind (head &rest tail) item 81 | (cond ((equal :special head) tail) 82 | ((keywordp head) item) 83 | ((symbolp head) (mapcar (apply-partially #'helm-cider-cheatsheet--symbol-qualifier head) tail)) 84 | ((stringp head) (mapcar (apply-partially #'helm-cider-cheatsheet--string-qualifier head) tail)) 85 | (t item))) 86 | item)) 87 | node)) 88 | 89 | (defun helm-cider-cheatsheet--flatten (node) 90 | "Flatten NODE, which is a tree structure, into a list of its leaves." 91 | (cond 92 | ((not (listp node)) node) 93 | ((keywordp (car node)) node) 94 | ((listp (car node)) (apply 'append (mapcar 'helm-cider-cheatsheet--flatten node))) 95 | (t (list (mapcar 'helm-cider-cheatsheet--flatten node))))) 96 | 97 | (defun helm-cider-cheatsheet--group-by-head (data) 98 | "Group the DATA, which should be a list of lists, by the head of each list." 99 | (let ((result '())) 100 | (dolist (item data result) 101 | (let* ((head (car item)) 102 | (tail (cdr item)) 103 | (current (cdr (assoc head result)))) 104 | (if current 105 | (setf (cdr (assoc head result)) 106 | (append current tail)) 107 | (setq result (append result (list item)))))))) 108 | 109 | (defvar helm-cider-cheatsheet--ns-mappings 110 | '(("clojure.core" . "") 111 | ("clojure.core.async" . "async") 112 | ("clojure.data" . "data") 113 | ("clojure.data.zip.xml" . "zip.xml") 114 | ("clojure.edn" . "edn") 115 | ("clojure.java.browse" . "browse") 116 | ("clojure.java.io" . "io") 117 | ("clojure.java.javadoc" . "javadoc") 118 | ("clojure.java.shell" . "shell") 119 | ("clojure.pprint" . "pprint") 120 | ("clojure.repl" . "repl") 121 | ("clojure.set" . "set") 122 | ("clojure.spec.alpha" . "s") 123 | ("clojure.string" . "str") 124 | ("clojure.test" . "test") 125 | ("clojure.walk" . "walk") 126 | ("clojure.xml" . "xml") 127 | ("clojure.zip" . "zip"))) 128 | 129 | (defun helm-cider-cheatsheet--shorten-ns (ns) 130 | (or (assoc-default ns helm-cider-cheatsheet--ns-mappings) ns)) 131 | 132 | (defun helm-cider-cheatsheet--item-to-helm-source (item &optional apropos-ht) 133 | "Turn ITEM, which will be (\"HEADING\" candidates...), into a helm-source. 134 | 135 | APROPOS-HT is a hash-table of (NAME APROPOS-DICT) entries." 136 | (cl-destructuring-bind (heading &rest entries) item 137 | (helm-build-sync-source heading 138 | :action helm-cider--doc-actions 139 | :candidates (cl-loop 140 | for s in (mapcar #'symbol-name entries) 141 | for ns = (helm-cider--symbol-ns s) 142 | for name = (helm-cider--symbol-name s) 143 | for face = (when apropos-ht 144 | (thread-first (gethash s apropos-ht) 145 | (nrepl-dict-get "type") 146 | helm-cider--symbol-face)) 147 | for propertized-s = (let ((short-ns (helm-cider-cheatsheet--shorten-ns ns)) 148 | (propertized-name (if face (cider-propertize name face) name))) 149 | (if (string-empty-p short-ns) 150 | propertized-name 151 | (concat (cider-propertize short-ns 'ns) "/" propertized-name))) 152 | collect (cons propertized-s s) into candidates 153 | finally return (cl-sort candidates #'string< :key #'car)) 154 | :match (lambda (candidate) 155 | (helm-mm-3-match (format "%s %s" candidate heading))) 156 | :persistent-action #'helm-cider--doc-lookup-persistent-action 157 | :persistent-help "Look up documentation"))) 158 | 159 | (defun helm-cider-cheatsheet--make-source (&optional hierarchy) 160 | (let ((ht (when (cider-connected-p) 161 | (let ((ht (make-hash-table :test 'equal))) 162 | (dolist (dict (cider-sync-request:apropos "")) 163 | (puthash (nrepl-dict-get dict "name") dict ht)) 164 | ht)))) 165 | (thread-last (or hierarchy cider-cheatsheet-hierarchy) 166 | helm-cider-cheatsheet--propagate-headings 167 | helm-cider-cheatsheet--flatten 168 | helm-cider-cheatsheet--group-by-head 169 | (mapcar (lambda (x) (helm-cider-cheatsheet--item-to-helm-source x ht)))))) 170 | 171 | (defvar helm-cider-cheatsheet--source 172 | (helm-cider-cheatsheet--make-source) 173 | "Helm source for `helm-cider-cheatsheet.'") 174 | 175 | 176 | ;;;; API 177 | 178 | ;;;###autoload 179 | (defun helm-cider-cheatsheet () 180 | "Use Helm to show a Clojure cheatsheet." 181 | (interactive) 182 | (helm :buffer "*Helm CIDER Cheatsheet*" 183 | :sources (if (and (not helm-cider-cheatsheet--jacked-in-source-p) 184 | (cider-connected-p)) 185 | (progn 186 | (message "Preparing cheatsheet (this is only needed once after `cider-jack-in')...") 187 | (setq helm-cider-cheatsheet--jacked-in-source-p t 188 | helm-cider-cheatsheet--source (helm-cider-cheatsheet--make-source))) 189 | helm-cider-cheatsheet--source))) 190 | 191 | 192 | (provide 'helm-cider-cheatsheet) 193 | 194 | ;;; helm-cider-cheatsheet.el ends here 195 | -------------------------------------------------------------------------------- /helm-cider-spec.el: -------------------------------------------------------------------------------- 1 | ;;; helm-cider-spec.el --- Helm interface to CIDER spec browser -*- lexical-binding: t; -*- 2 | 3 | ;; Copyright (C) 2016-2022 Tianxiang Xiong 4 | 5 | ;; Author: Tianxiang Xiong 6 | 7 | ;; This program 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 | 12 | ;; This program is distributed in the hope that it will be useful, 13 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | ;; GNU General Public License for more details. 16 | 17 | ;; You should have received a copy of the GNU General Public License 18 | ;; along with this program. If not, see . 19 | 20 | ;;; Commentary: 21 | 22 | ;; Helm interface to CIDER spec browser 23 | 24 | ;;; Code: 25 | 26 | (require 'cider-client) 27 | (require 'cl-lib) 28 | (require 'helm-cider-util) 29 | (require 'subr-x) 30 | 31 | 32 | ;;;; Customize 33 | 34 | (defgroup helm-cider-spec nil 35 | "Helm interface to CIDER spec browser." 36 | :prefix "helm-cider-spec-" 37 | :group 'helm-cider 38 | :tag "Helm CIDER Spec") 39 | 40 | (defcustom helm-cider-spec-ns-compare-fn 41 | #'helm-cider-spec--ns-compare-fn 42 | "Function to compare spec namespaces for sorting. 43 | 44 | Comparison function takes two arguments, ns-1 and ns-2. If ns-1 45 | < ns-2, it appears earlier as a Helm source in `helm-cider-spec' 46 | and candidate in `helm-cider-spec-ns'." 47 | :group 'helm-cider-spec 48 | :type 'function) 49 | 50 | (defcustom helm-cider-spec-ns-key "C-c n" 51 | "String representation of key sequence for executing 52 | `helm-cider-spec-ns'. 53 | 54 | This is intended to be added to the keymap for 55 | `helm-cider-spec-symbol'." 56 | :group 'helm-cider-spec 57 | :type 'key-sequence) 58 | 59 | (defcustom helm-cider-spec-follow nil 60 | "If non-nil, turn on `helm-follow-mode' for CIDER specs." 61 | :group 'helm-cider-spec 62 | :type 'boolean) 63 | 64 | (defcustom helm-cider-spec-actions 65 | (helm-make-actions 66 | "View spec" #'cider-browse-spec--browse) 67 | "Actions for Helm CIDER specs." 68 | :group 'helm-cider-spec 69 | :type '(alist :key-type string :value-type function)) 70 | 71 | (defcustom helm-cider-spec-ns-actions 72 | (helm-make-actions 73 | "Search in namespace" #'helm-cider-spec-symbol) 74 | "Actions for Helm CIDER spec namespaces." 75 | :group 'helm-cider-spec 76 | :type '(alist :key-type string :value-type function)) 77 | 78 | 79 | ;;;; Spec 80 | 81 | (defun helm-cider-spec--hashtable (spec-names) 82 | "Build a hash table from list of SPECS-NAMES. 83 | 84 | Keys are kw namespaces and values are lists of names." 85 | (let ((ht (make-hash-table :test #'equal))) 86 | (dolist (name spec-names) 87 | (let ((ns (helm-cider--symbol-ns name))) 88 | (puthash ns (cons name (gethash ns ht)) ht))) 89 | (cl-loop 90 | for ns being the hash-keys of ht 91 | using (hash-value names) 92 | do (setf (gethash ns ht) (sort names #'string<)) 93 | finally (return ht)))) 94 | 95 | (defun helm-cider-spec--candidate (spec-name) 96 | "Create a Helm CIDER spec candidate. 97 | 98 | SPEC-NAME is a spec keyword string." 99 | (cons (propertize (helm-cider--symbol-name spec-name) 100 | 'face 'clojure-keyword-face) 101 | spec-name)) 102 | 103 | (defvar helm-cider-spec--map 104 | (let ((keymap (copy-keymap helm-map))) 105 | ;; Select ns 106 | (define-key keymap (kbd helm-cider-spec-ns-key) 107 | (lambda () 108 | (interactive) 109 | (helm-exit-and-execute-action (lambda (candidate) 110 | (helm-cider-spec-ns candidate))))) 111 | keymap) 112 | "Keymap for use with `helm-cider-spec'.") 113 | 114 | (defun helm-cider-spec--persistent-action (candidate) 115 | "Persistent action for Helm CIDER apropos." 116 | (if (and (helm-get-attr 'spec-lookup-p) 117 | (string= candidate (helm-get-attr 'current-candidate))) 118 | (progn 119 | (kill-buffer cider-browse-spec-buffer) 120 | (helm-set-attr 'spec-lookup-p nil)) 121 | (cider-browse-spec--browse candidate) 122 | (helm-set-attr 'spec-lookup-p t)) 123 | (helm-set-attr 'current-candidate candidate)) 124 | 125 | (defun helm-cider-spec--source (ns spec-names &optional follow) 126 | "Helm source for specs in namespace NS. 127 | 128 | SPEC-NAMES is a list of spec keywords strings." 129 | (helm-build-sync-source ns 130 | :action helm-cider-spec-actions 131 | :candidates (mapcar #'helm-cider-spec--candidate spec-names) 132 | :follow (when follow 1) 133 | :keymap helm-cider-spec--map 134 | :nomark t 135 | :persistent-action #'helm-cider-spec--persistent-action 136 | :persistent-help "View spec" 137 | :volatile t)) 138 | 139 | (defun helm-cider-spec--ns-compare-fn (ns-1 ns-2) 140 | "Function to compare spec namespaces NS-1 and NS-2. 141 | 142 | Namespaces are compared as symbols, without keywords' leading 143 | colons." 144 | (let ((ns-1 (replace-regexp-in-string "^:" "" ns-1)) 145 | (ns-2 (replace-regexp-in-string "^:" "" ns-2))) 146 | (string< ns-1 ns-2))) 147 | 148 | (defun helm-cider-spec--sources (&optional spec-names) 149 | "A list of Helm sources specs. 150 | 151 | Each source is the set specs in a namespace. 152 | 153 | Optional argument SPEC-NAMES is a list of spec keyword strings. 154 | If not supplied, it is retrieved with 155 | `cider-sync-request:spec-list'." 156 | (cl-loop 157 | with ht = (helm-cider-spec--hashtable (or spec-names 158 | (cider-sync-request:spec-list ""))) 159 | for ns being the hash-keys in ht using (hash-value names) 160 | collect (helm-cider-spec--source ns names helm-cider-spec-follow) 161 | into sources 162 | finally (return (cl-sort sources helm-cider-spec-ns-compare-fn 163 | :key (lambda (source) (assoc-default 'name source)))))) 164 | 165 | (defun helm-cider-spec--all-ns () 166 | "Return a list of all spec namespace strings. 167 | 168 | E.g. '(\":ring.async.handler\", \":ring.core\", ...)." 169 | (let ((nss (thread-last (cider-sync-request:spec-list "") 170 | (mapcar #'helm-cider--symbol-ns)))) 171 | (cl-delete-duplicates nss :test #'equal))) 172 | 173 | (defun helm-cider-spec--propertize-ns (ns) 174 | "Propertize a spec namespace NS. 175 | 176 | I.e. how `:foo' would be propertized in `:foo/bar' in 177 | `clojure-mode'. 178 | 179 | It is also possible for a spec ns to be a regular symbol 180 | instead of keyword, as with `clojure.core'." 181 | (if (string-prefix-p ":" ns) 182 | (concat (propertize (substring ns 0 1) 183 | 'face 'clojure-keyword-face) 184 | (propertize (substring ns 1) 185 | 'face 'font-lock-type-face)) 186 | (propertize ns 'face 'font-lock-type-face))) 187 | 188 | (defun helm-cider-spec--ns-source (&optional follow) 189 | "Helm source of namespaces. 190 | 191 | Namespaces in EXCLUDED-NS are excluded. If not supplied, 192 | `helm-cider-apropos-excluded-ns' is used. 193 | 194 | If FOLLOW is true, use function `helm-follow-mode' for source." 195 | (helm-build-sync-source "Clojure Spec Namespaces" 196 | :action helm-cider-spec-ns-actions 197 | :candidates (cl-loop 198 | for ns in (helm-cider-spec--all-ns) 199 | collect (helm-cider-spec--propertize-ns ns) into all 200 | finally (return (sort all helm-cider-spec-ns-compare-fn))) 201 | :follow (when follow 1) 202 | :nomark t 203 | :persistent-action #'ignore 204 | :volatile t)) 205 | 206 | (defun helm-cider-spec--resolve-name (&optional ns name) 207 | "Try to get correct values for NS and NAME. 208 | 209 | NS is a spec keyword ns, e.g. \":ring.core\". NAME is a spec 210 | keyword name, e.g. \"error\"." 211 | (let* ((name (or name (unless ns (cider-symbol-at-point)))) 212 | (qualified (when name 213 | (or (cider-namespace-qualified-p name) 214 | (string-match-p "^::" name)))) 215 | (ns (cond (qualified (if (string-match-p "^::" name) 216 | (cider-current-ns) 217 | (helm-cider--symbol-ns name))) 218 | (t ns)))) 219 | (list ns (if qualified 220 | (helm-cider--symbol-name name) 221 | name)))) 222 | 223 | 224 | ;;;; API 225 | 226 | ;;;###autoload 227 | (defun helm-cider-spec-symbol (&optional ns name) 228 | "Choose Clojure specs across namespaces. 229 | 230 | Each Helm source is a Clojure namespace (ns), and candidates are 231 | spec keywords in the namespace. 232 | 233 | If both NS and NAME are supplied, puts selection line on 234 | first NAME of NS. 235 | 236 | If NS is supplied, puts the selection line on the first 237 | candidate of source with name NS. 238 | 239 | If NAME is supplied, puts the selection line on the 240 | first candidate matching NAME. 241 | 242 | Set `helm-cider-spec-follow' to non-nil to turn on function 243 | `helm-follow-mode' for all sources. This is useful for quickly 244 | viewing specs." 245 | (interactive) 246 | (cider-ensure-connected) 247 | (cl-multiple-value-bind (ns name) (helm-cider-spec--resolve-name ns name) 248 | (let ((name (when name 249 | (helm-cider--regexp-symbol name)))) 250 | (with-helm-after-update-hook 251 | (with-helm-buffer 252 | (let ((helm--force-updating-p t)) 253 | (if name 254 | (helm-preselect name (helm-cider--source-by-name ns)) 255 | (helm-goto-source (or ns "")) 256 | (when ns 257 | (helm-next-line))) 258 | (recenter 1)))) 259 | (helm :buffer "*Helm Clojure Specs*" 260 | :candidate-number-limit 9999 261 | :sources (helm-cider-spec--sources))))) 262 | 263 | ;;;###autoload 264 | (defun helm-cider-spec-ns (&optional kw-ns-or-qualified-name) 265 | "Choose spec namespace to call `helm-cider-browse-spec' on. 266 | 267 | KW-NS-OR-QUALIFIED-NAME is a spec keyword namespace 268 | (e.g. \":ring.core\") or a qualified keyword 269 | name (e.g. \":ring.core/error\"). If supplied, it is used as the 270 | default selection." 271 | (interactive) 272 | (cider-ensure-connected) 273 | (helm :buffer "*Helm Clojure Spec Namespaces*" 274 | :candidate-number-limit 9999 275 | :preselect (thread-first (or kw-ns-or-qualified-name "") 276 | helm-cider--symbol-ns 277 | helm-cider--regexp-symbol) 278 | :sources (helm-cider-spec--ns-source))) 279 | 280 | ;;;###autoload 281 | (defun helm-cider-spec (&optional arg) 282 | "Helm interface to CIDER specs. 283 | 284 | If ARG is raw prefix argument \\[universal-argument] 285 | \\[universal-argument], choose namespace before symbol." 286 | (interactive "P") 287 | (cond ((equal arg '(4)) (helm-cider-spec-ns)) 288 | (t (helm-cider-spec-symbol)))) 289 | 290 | 291 | 292 | (provide 'helm-cider-spec) 293 | 294 | ;;; helm-cider-spec.el ends here 295 | -------------------------------------------------------------------------------- /helm-cider.el: -------------------------------------------------------------------------------- 1 | ;;; helm-cider.el --- Helm interface to CIDER -*- lexical-binding: t; -*- 2 | 3 | ;; Copyright (C) 2016-2022 Tianxiang Xiong 4 | 5 | ;; Author: Tianxiang Xiong 6 | ;; Package-Requires: ((emacs "26") (cider "1.0") (helm-core "3.7.0")) 7 | ;; Keywords: cider, clojure, helm, languages 8 | ;; URL: https://github.com/clojure-emacs/helm-cider 9 | ;; Version: 0.5.0 10 | 11 | ;; This program is free software; you can redistribute it and/or modify 12 | ;; it under the terms of the GNU General Public License as published by 13 | ;; the Free Software Foundation, either version 3 of the License, or 14 | ;; (at your option) any later version. 15 | 16 | ;; This program is distributed in the hope that it will be useful, 17 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | ;; GNU General Public License for more details. 20 | 21 | ;; You should have received a copy of the GNU General Public License 22 | ;; along with this program. If not, see . 23 | 24 | ;;; Commentary: 25 | 26 | ;; Helm interface to CIDER. 27 | 28 | ;; For more about Helm, see: https://github.com/emacs-helm/helm 29 | ;; For more about CIDER, see: https://github.com/clojure-emacs/cider 30 | 31 | ;;; Code: 32 | 33 | (require 'cider) 34 | (require 'cl-lib) 35 | (require 'helm-core) 36 | (require 'helm-cider-cheatsheet) 37 | (require 'helm-cider-repl) 38 | (require 'helm-cider-spec) 39 | (require 'helm-cider-util) 40 | (require 'helm-lib) 41 | (require 'helm-multi-match) 42 | (require 'helm-source) 43 | 44 | 45 | ;;;; Customize 46 | (defgroup helm-cider nil 47 | "Helm interface to CIDER." 48 | :prefix "helm-cider-" 49 | :group 'cider 50 | :tag "Helm CIDER") 51 | 52 | ;;;;; Apropos 53 | 54 | (defgroup helm-cider-apropos nil 55 | "Helm CIDER apropos" 56 | :prefix "helm-cider-apropos-" 57 | :group 'helm-cider 58 | :tag "Helm CIDER Apropos") 59 | 60 | (defcustom helm-cider-apropos-excluded-ns '("cider.*") 61 | "List of namespaces to exclude from apropos. 62 | 63 | Namespace globs (e.g. \"cider.*\" for all CIDER-specific 64 | namespaces) are accepted. 65 | 66 | By default, CIDER-specific namespaces (those used by CIDER 67 | itself, e.g. \"cider.nrepl.middleware.apropos\") are excluded." 68 | :group 'helm-cider-apropos 69 | :type '(repeat string)) 70 | 71 | (defcustom helm-cider-apropos-follow nil 72 | "If true, enable function `helm-follow-mode' for apropos sources." 73 | :group 'helm-cider-apropos 74 | :type 'boolean) 75 | 76 | (defcustom helm-cider-apropos-ns-actions 77 | (helm-make-actions 78 | "Search in namespace" #'helm-cider-apropos-symbol 79 | "Find definition" #'helm-cider--find-ns 80 | "CiderDoc" (wrap-helm-cider-action cider-doc-lookup) 81 | "Search in namespace with docs" #'helm-cider-apropos-symbol-doc 82 | "Set REPL namespace" #'cider-repl-set-ns) 83 | "Actions for Helm apropos namespaces." 84 | :group 'helm-cider-apropos 85 | :type '(alist :key-type string :value-type function)) 86 | 87 | (defcustom helm-cider-apropos-actions 88 | helm-cider--doc-actions 89 | "Actions for Helm apropos symbols." 90 | :group 'helm-cider-apropos 91 | :type '(alist :key-type string :value-type function)) 92 | 93 | (defcustom helm-cider-apropos-full-doc nil 94 | "If true, use full documentation in `helm-cider-apropos-symbol-doc'." 95 | :group 'helm-cider-apropos 96 | :type 'boolean) 97 | 98 | (defcustom helm-cider-apropos-ns-key "C-c n" 99 | "String representation of key sequence for executing 100 | `helm-cider-apropos-ns'. 101 | 102 | This is intended to be added to the keymap for 103 | `helm-cider-apropos'." 104 | :group 'helm-cider-apropos 105 | :type 'key-sequence) 106 | 107 | (defcustom helm-cider-apropos-symbol-doc-key "S-" 108 | "String representation of key sequence of executing 109 | `helm-cider-apropos-symbol-doc'. 110 | 111 | This is intended to be added to the keymap for 112 | `helm-cider-apropos-ns.'" 113 | :group 'helm-cider-apropos 114 | :type 'key-sequence) 115 | 116 | 117 | ;;;; Apropos 118 | 119 | (defun helm-cider--excluded-ns-p (ns &optional excluded-ns) 120 | "Return true when namespace NS matches one of EXCLUDED-NS. 121 | 122 | EXCLUDED-NS is a list of namespaces (e.g. \"clojure.core\") 123 | and/or namespace globs (e.g. \"cider.*\"). If not supplied, 124 | `helm-cider-apropos-excluded-ns' is used. 125 | 126 | NS matches a string equal to itself, or a string ending in \"*\" 127 | that is a prefix of NS, excluding the \"*\"." 128 | (catch 'excluded 129 | (dolist (ex (or excluded-ns 130 | helm-cider-apropos-excluded-ns)) 131 | (when (or (and (string-suffix-p "*" ex) 132 | (string-prefix-p (substring ex 0 (1- (length ex))) ns)) 133 | (string= ns ex)) 134 | (throw 'excluded t))))) 135 | 136 | (defun helm-cider--apropos-dicts (&optional excluded-ns full-doc) 137 | "List of apropos results (nREPL dicts). 138 | 139 | Optional argument EXCLUDED-NS is a list of ns for which symbols 140 | are excluded. If not supplied, `helm-cider-apropos-excluded-ns' 141 | is used. 142 | 143 | Optional argument FULL-DOC, if t, retrieves full documentation." 144 | (cl-loop 145 | with excluded-ns = (or excluded-ns helm-cider-apropos-excluded-ns) 146 | for dict in (cider-sync-request:apropos "" nil full-doc) 147 | unless (helm-cider--excluded-ns-p (helm-cider--symbol-ns (nrepl-dict-get dict "name")) 148 | excluded-ns) 149 | do (unless (nrepl-dict-contains dict "ns") 150 | (nrepl-dict-put dict "ns" "clojure.core")) 151 | collect dict)) 152 | 153 | (defun helm-cider--apropos-hashtable (dicts) 154 | "Build a hash table from apropos results (DICTS). 155 | 156 | Keys are namespaces and values are lists of results (nREPL 157 | dict objects)." 158 | (let ((ht (make-hash-table :test 'equal))) 159 | (dolist (dict dicts) 160 | (let ((ns (helm-cider--symbol-ns (nrepl-dict-get dict "name")))) 161 | (puthash ns (cons dict (gethash ns ht)) ht))) 162 | ht)) 163 | 164 | (defun helm-cider--apropos-candidate (dict) 165 | "Create a Helm apropos candidate. 166 | 167 | DICT is an nREPL dict." 168 | (nrepl-dbind-response dict (name type) 169 | (cons (propertize (helm-cider--symbol-name name) 170 | 'face (helm-cider--symbol-face type)) 171 | name))) 172 | 173 | (defun helm-cider--apropos-doc-candidate (dict &optional full-doc) 174 | "Create a Helm apropos doc candidate. 175 | 176 | DICT is an nREPL dict. 177 | 178 | Optional argument FULL-DOC, when t, indicates that full 179 | documentation is used." 180 | (nrepl-dbind-response dict (name type doc) 181 | (with-temp-buffer 182 | ;; Name 183 | (insert (propertize name 'face (helm-cider--symbol-face type))) 184 | (insert "\n") 185 | ;; Doc 186 | (let ((beg (point))) 187 | (insert doc "\n") 188 | ;; Short doc is not already filled; full doc is 189 | (when (not full-doc) 190 | (fill-region beg (point-max)))) 191 | ;; Candidate 192 | (cons (buffer-string) name)))) 193 | 194 | (defvar helm-cider--apropos-map 195 | (let ((keymap (copy-keymap helm-map))) 196 | ;; Select namespace 197 | (define-key keymap (kbd helm-cider-apropos-ns-key) 198 | (lambda () 199 | (interactive) 200 | (helm-exit-and-execute-action (lambda (candidate) 201 | (helm-cider-apropos-ns candidate))))) 202 | ;; Apropos with docs 203 | (dolist (key (cl-mapcan (lambda (command) 204 | (where-is-internal command cider-mode-map)) 205 | '(cider-apropos-documentation 206 | cider-apropos-documentation-select))) 207 | (define-key keymap key 208 | (lambda () 209 | (interactive) 210 | (helm-exit-and-execute-action 211 | (lambda (candidate) 212 | (helm-cider-apropos-symbol-doc (helm-cider--symbol-ns candidate) 213 | (helm-cider--symbol-name candidate))))))) 214 | ;; Apropos 215 | (dolist (key (cl-mapcan (lambda (command) 216 | (where-is-internal command cider-mode-map)) 217 | '(cider-apropos 218 | cider-apropos-select))) 219 | (define-key keymap key 220 | (lambda () 221 | (interactive) 222 | (helm-exit-and-execute-action 223 | (lambda (candidate) 224 | (helm-cider-apropos-symbol (helm-cider--symbol-ns candidate) 225 | (helm-cider--symbol-name candidate))))))) 226 | keymap) 227 | "Keymap for use with `helm-cider-apropos-symbol'.") 228 | 229 | (defvar helm-cider--apropos-doc-map 230 | (let ((keymap (copy-keymap helm-cider--apropos-map))) 231 | ;; Toggle full documentation 232 | (define-key keymap (kbd "C-]") 233 | (lambda () 234 | (interactive) 235 | (helm-exit-and-execute-action 236 | (lambda (candidate) 237 | (customize-set-value 'helm-cider-apropos-full-doc 238 | (not helm-cider-apropos-full-doc)) 239 | (helm-cider-apropos-symbol-doc (helm-cider--symbol-ns candidate) 240 | (helm-cider--symbol-name candidate)))))) 241 | keymap) 242 | "Keymap for use with `helm-cider-apropos-symbol-doc'.") 243 | 244 | (defun helm-cider--apropos-source (ns &optional dicts doc full-doc follow) 245 | "Helm source for namespace NS (e.g. \"clojure.core\"). 246 | 247 | DICTS is a list of apropos results (nREPL dicts) for 248 | NS. If not supplied, it is obtained with 249 | `cider-sync-request:apropos'. 250 | 251 | If DOC is true, include symbol documentation in candidates. 252 | 253 | If FULL-DOC is true, full documentation is used; candidates may 254 | need to be treated differently by 255 | `helm-cider--apropos-doc-candidate'. 256 | 257 | If FOLLOW is true, use function `helm-follow-mode' for source." 258 | (helm-build-sync-source ns 259 | :action helm-cider-apropos-actions 260 | :candidates (thread-first (if doc 261 | (lambda (dict) 262 | (helm-cider--apropos-doc-candidate dict full-doc)) 263 | #'helm-cider--apropos-candidate) 264 | (mapcar (if dicts 265 | (copy-sequence dicts) 266 | (cider-sync-request:apropos "" ns doc))) 267 | (cl-sort #'string< :key (lambda (dict) (helm-cider--symbol-name (cdr dict))))) 268 | :follow (when follow 1) 269 | :keymap (if doc helm-cider--apropos-doc-map helm-cider--apropos-map) 270 | :multiline doc 271 | :nomark t 272 | :persistent-action #'helm-cider--doc-lookup-persistent-action 273 | :persistent-help "Look up documentation" 274 | :volatile t)) 275 | 276 | (defun helm-cider--apropos-sources (&optional excluded-ns doc full-doc) 277 | "A list of Helm sources for apropos. 278 | 279 | Each source is the set of symbols in a namespace. Namespaces in 280 | EXCLUDED-NS are excluded. If not supplied, 281 | `helm-cider-apropos-excluded-ns' is used. 282 | 283 | If DOC is true, include symbol documentation in candidates. 284 | 285 | If FULL-DOC is true, include full instead of short documentation." 286 | (cl-loop 287 | with ht = (helm-cider--apropos-hashtable 288 | (helm-cider--apropos-dicts excluded-ns full-doc)) 289 | for ns being the hash-keys in ht using (hash-value dicts) 290 | collect (helm-cider--apropos-source ns dicts doc full-doc helm-cider-apropos-follow) 291 | into sources 292 | finally (return (cl-sort sources #'string< :key (lambda (source) (assoc-default 'name source)))))) 293 | 294 | (defvar helm-cider--apropos-ns-map 295 | (let ((keymap (copy-keymap helm-map))) 296 | (define-key keymap (kbd helm-cider-apropos-symbol-doc-key) 297 | (lambda () 298 | (interactive) 299 | (helm-exit-and-execute-action (lambda (candidate) 300 | (helm-cider-apropos-symbol-doc candidate))))) 301 | keymap) 302 | "Return a keymap for use with `helm-cider-apropos-ns'.") 303 | 304 | (defun helm-cider--apropos-ns-source (&optional excluded-ns follow) 305 | "Helm source of namespaces. 306 | 307 | Namespaces in EXCLUDED-NS are excluded. If not supplied, 308 | `helm-cider-apropos-excluded-ns' is used. 309 | 310 | If FOLLOW is true, use function `helm-follow-mode' for source." 311 | (helm-build-sync-source "Clojure Namespaces" 312 | :action helm-cider-apropos-ns-actions 313 | :candidates (cl-loop for ns in (cider-sync-request:ns-list) 314 | unless (helm-cider--excluded-ns-p ns excluded-ns) 315 | collect (cider-propertize ns 'ns) into all 316 | finally (return (sort all #'string<))) 317 | :follow (when follow 1) 318 | :keymap helm-cider--apropos-ns-map 319 | :nomark t 320 | :persistent-action #'helm-cider--doc-lookup-persistent-action 321 | :persistent-help "Look up documentation" 322 | :volatile t)) 323 | 324 | (defun helm-cider--resolve-symbol (&optional ns symbol) 325 | "Try to get correct values for NS and SYMBOL." 326 | (let* ((symbol (or symbol (unless ns (cider-symbol-at-point)))) 327 | (qualified (when symbol (cider-namespace-qualified-p symbol))) 328 | (ns (cond (qualified (nrepl-dict-get (cider-var-info symbol t) "ns")) 329 | ((and ns symbol) 330 | (nrepl-dict-get (cider-var-info (concat ns "/" symbol) t) "ns")) 331 | (symbol (nrepl-dict-get (cider-var-info symbol t) "ns")) 332 | (t ns)))) 333 | (list ns (if qualified 334 | (helm-cider--symbol-name symbol) 335 | symbol)))) 336 | 337 | 338 | ;;;; API 339 | 340 | ;;;;; Apropos 341 | 342 | ;;;###autoload 343 | (defun helm-cider-apropos-symbol (&optional ns symbol doc full-doc) 344 | "Choose Clojure symbols across namespaces. 345 | 346 | Each Helm source is a Clojure namespace (ns), and candidates are 347 | symbols in the namespace. 348 | 349 | If both NS and SYMBOL are supplied, puts selection line on 350 | first SYMBOL of NS. 351 | 352 | If NS is supplied, puts the selection line on the first 353 | candidate of source with name NS. 354 | 355 | If SYMBOL is supplied, puts the selection line on the 356 | first candidate matching SYMBOL. 357 | 358 | If neither NS nor SYMBOL is supplied, tries to put the 359 | selection line on candidate matching symbol at point. 360 | 361 | If DOC is true, include symbol documentation in candidate. 362 | 363 | If FULL-DOC is true, include full instead of short documentation. 364 | 365 | Set `helm-cider-apropos-follow' to true to turn on function 366 | `helm-follow-mode' for all sources. This is useful for quickly 367 | browsing documentation." 368 | (interactive) 369 | (cider-ensure-connected) 370 | (cl-multiple-value-bind (ns symbol) (helm-cider--resolve-symbol ns symbol) 371 | (let ((symbol (cond ((and symbol doc) (regexp-quote (concat ns "/" symbol))) 372 | (symbol (helm-cider--regexp-symbol symbol))))) 373 | (with-helm-after-update-hook 374 | (with-helm-buffer 375 | (let ((helm--force-updating-p t)) 376 | (if symbol 377 | (helm-preselect symbol (helm-cider--source-by-name ns)) 378 | (helm-goto-source (or ns "")) 379 | (when ns 380 | (helm-next-line))) 381 | (recenter 1)))) 382 | (helm :buffer "*Helm Clojure Symbols*" 383 | :candidate-number-limit 9999 384 | :sources (helm-cider--apropos-sources nil doc full-doc))))) 385 | 386 | ;;;###autoload 387 | (defun helm-cider-apropos-symbol-doc (&optional ns symbol) 388 | "Choose Clojure SYMBOLs, with docs, across namespaces. 389 | 390 | Optional arguments NS and SYMBOL are as in 391 | `helm-cider-apropos-symbol'." 392 | (interactive) 393 | (helm-cider-apropos-symbol ns symbol t helm-cider-apropos-full-doc)) 394 | 395 | ;;;###autoload 396 | (defun helm-cider-apropos-ns (&optional ns-or-qualified-name) 397 | "Choose Clojure namespace to call Helm CIDER apropos on. 398 | 399 | NS-OR-QUALIFIED-NAME is a Clojure 400 | namespace (e.g. \"clojure.core\") or a qualified symbol 401 | name (e.g. \"clojure.core/reduce\"). If supplied, it is used as 402 | the default selection." 403 | (interactive) 404 | (cider-ensure-connected) 405 | (helm :buffer "*Helm Clojure Namespaces*" 406 | :candidate-number-limit 9999 407 | :preselect (helm-cider--regexp-symbol 408 | (helm-cider--symbol-ns (or ns-or-qualified-name ""))) 409 | :sources (helm-cider--apropos-ns-source))) 410 | 411 | ;;;###autoload 412 | (defun helm-cider-apropos (&optional arg) 413 | "Helm interface to CIDER apropos. 414 | 415 | If ARG is raw prefix argument \\[universal-argument], include 416 | symbol documentation. 417 | 418 | If ARG is raw prefix argument \\[universal-argument] 419 | \\[universal-argument], choose namespace before symbol." 420 | (interactive "P") 421 | (cond ((equal arg '(16)) (helm-cider-apropos-ns)) 422 | ((equal arg '(4)) (helm-cider-apropos-symbol-doc)) 423 | (t (helm-cider-apropos-symbol)))) 424 | 425 | 426 | ;;;; Key bindings and minor mode 427 | 428 | (defcustom helm-cider-overrides 429 | '((cider-apropos . helm-cider-apropos) 430 | (cider-apropos-select . helm-cider-apropos) 431 | (cider-apropos-documentation . helm-cider-apropos-symbol-doc) 432 | (cider-apropos-documentation-select . helm-cider-apropos-symbol-doc) 433 | (cider-browse-ns . helm-cider-apropos-ns) 434 | (cider-browse-ns-all . helm-cider-apropos-ns) 435 | (cider-browse-spec-all . helm-cider-spec)) 436 | "Alist of CIDER functions and Helm versions replacing them." 437 | :group 'helm-cider 438 | :type '(alist :key-type symbol :value-type symbol)) 439 | 440 | (defun helm-cider--override () 441 | "Override CIDER functions with Helm versions. 442 | 443 | The old and new functions are those specified in 444 | `helm-cider-overrides'." 445 | (dolist (pair helm-cider-overrides) 446 | (let ((symbol (car pair)) 447 | (newfun (symbol-function (cdr pair)))) 448 | (unless (advice-member-p newfun symbol) 449 | (advice-add symbol :override newfun))))) 450 | 451 | (defun helm-cider--revert () 452 | "Revert to original CIDER functions." 453 | (dolist (pair helm-cider-overrides) 454 | (advice-remove (car pair) (symbol-function (cdr pair))))) 455 | 456 | ;;;###autoload 457 | (define-minor-mode helm-cider-mode "Use Helm for CIDER." 458 | :global t 459 | :require 'helm-cider 460 | (if helm-cider-mode 461 | (progn 462 | (helm-cider--override) 463 | (define-key cider-repl-mode-map (kbd "C-c C-l") #'helm-cider-repl-history)) 464 | (helm-cider--revert) 465 | (define-key cider-repl-mode-map (kbd "C-c C-l") nil))) 466 | 467 | 468 | (provide 'helm-cider) 469 | 470 | ;;; helm-cider.el ends here 471 | --------------------------------------------------------------------------------