├── images ├── doc-clojure.png ├── doc-elisp.png ├── lispy-logo.png ├── arglist-elisp.png └── arglist-clojure.png ├── .gitignore ├── lispy-pkg.el ├── .dir-locals.el ├── elpa.el ├── targets ├── checkdoc.el ├── compile.el ├── interactive-init.el ├── check-declare.el └── install-deps.el ├── .travis.yml ├── Cask ├── Makefile ├── le-scheme.el ├── le-julia.el ├── le-hy.el ├── lispy-python.py ├── le-lisp.el ├── lispy-clojure.clj ├── lispy-tags.el ├── le-clojure.el ├── lispytutor └── lispytutor.el ├── lispy-inline.el ├── le-python.el └── README.md /images/doc-clojure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruricolist/lispy/master/images/doc-clojure.png -------------------------------------------------------------------------------- /images/doc-elisp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruricolist/lispy/master/images/doc-elisp.png -------------------------------------------------------------------------------- /images/lispy-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruricolist/lispy/master/images/lispy-logo.png -------------------------------------------------------------------------------- /images/arglist-elisp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruricolist/lispy/master/images/arglist-elisp.png -------------------------------------------------------------------------------- /images/arglist-clojure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruricolist/lispy/master/images/arglist-clojure.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /init.el 2 | /gh-pages/ 3 | /.projectile 4 | /.cask/ 5 | *.elc 6 | \#*# 7 | .#* 8 | *~ 9 | *$py.class -------------------------------------------------------------------------------- /lispy-pkg.el: -------------------------------------------------------------------------------- 1 | (define-package "lispy" "0.26.1" 2 | "vi-like Paredit" 3 | '((emacs "24.1") 4 | (ace-window "0.9.0") 5 | (iedit "0.9.9") 6 | (swiper "0.7.0") 7 | (hydra "0.13.4") 8 | (zoutline "0.1.0"))) 9 | -------------------------------------------------------------------------------- /.dir-locals.el: -------------------------------------------------------------------------------- 1 | ;;; Directory Local Variables 2 | ;;; For more information see (info "(emacs) Directory Variables") 3 | 4 | ((emacs-lisp-mode 5 | (bug-reference-url-format . "https://github.com/abo-abo/lispy/issues/%s") 6 | (indent-tabs-mode . nil))) 7 | -------------------------------------------------------------------------------- /elpa.el: -------------------------------------------------------------------------------- 1 | (setq package-user-dir 2 | (expand-file-name (format ".cask/%s/elpa" emacs-version))) 3 | (package-initialize) 4 | (add-to-list 'load-path default-directory) 5 | ;; Silence the loading message 6 | (setq iedit-toggle-key-default nil) 7 | 8 | -------------------------------------------------------------------------------- /targets/checkdoc.el: -------------------------------------------------------------------------------- 1 | (setq files '("lispy.el" 2 | "lispy-inline.el" 3 | "le-clojure.el" 4 | "le-scheme.el" 5 | "le-lisp.el")) 6 | (require 'checkdoc) 7 | (dolist (file files) 8 | (checkdoc-file file)) 9 | -------------------------------------------------------------------------------- /targets/compile.el: -------------------------------------------------------------------------------- 1 | (setq files '("lispy.el" 2 | "lispy-inline.el" 3 | "le-clojure.el" 4 | "le-scheme.el" 5 | "le-lisp.el" 6 | "le-python.el")) 7 | (setq byte-compile--use-old-handlers nil) 8 | (mapc #'byte-compile-file files) 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: true 2 | dist: precise 3 | language: emacs-lisp 4 | env: 5 | matrix: 6 | - emacs=emacs-snapshot 7 | 8 | before_install: 9 | - sudo add-apt-repository -y ppa:ubuntu-elisp 10 | - sudo apt-get update -qq 11 | - sudo apt-get install -qq $emacs 12 | 13 | script: 14 | - make update 15 | - make test 16 | -------------------------------------------------------------------------------- /Cask: -------------------------------------------------------------------------------- 1 | (source gnu) 2 | (source melpa) 3 | 4 | (package-file "lispy.el") 5 | 6 | (files "*.el" (:exclude "init.el" "lispy-test.el")) 7 | 8 | (development 9 | (depends-on "iedit") 10 | (depends-on "multiple-cursors") 11 | (depends-on "cider") 12 | (depends-on "slime") 13 | (depends-on "sly") 14 | (depends-on "geiser") 15 | (depends-on "projectile") 16 | (depends-on "clojure-mode") 17 | (depends-on "swiper") 18 | (depends-on "hydra") 19 | (depends-on "ace-window") 20 | (depends-on "undercover") 21 | (depends-on "zoutline")) 22 | -------------------------------------------------------------------------------- /targets/interactive-init.el: -------------------------------------------------------------------------------- 1 | ;;* Base 2 | (require 'lispy) 3 | (dolist (h '(emacs-lisp-mode-hook 4 | lisp-interaction-mode-hook 5 | clojure-mode-hook 6 | scheme-mode-hook 7 | lisp-mode-hook)) 8 | (add-hook h #'lispy-mode)) 9 | 10 | ;;* Common Lisp 11 | (add-to-list 'load-path "~/git/slime") 12 | (require 'slime-autoloads) 13 | (setq inferior-lisp-program "/usr/bin/sbcl") 14 | (setq slime-contribs '(slime-fancy)) 15 | 16 | ;;* Clojure 17 | (add-to-list 'load-path "~/git/clojure-semantic") 18 | (load "~/git/clojure-semantic/clojure.el") 19 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | emacs ?= emacs 2 | CASK = ~/.cask/bin/cask 3 | BEMACS = $(emacs) -batch -l elpa.el 4 | LOAD = -l lispy-inline.el -l lispy.el 5 | QEMACS = $(emacs) -Q -l elpa.el -l targets/interactive-init.el 6 | 7 | all: compile test 8 | 9 | cask: 10 | $(shell EMACS=$(emacs) $(CASK) --verbose --debug) 11 | 12 | update: 13 | $(emacs) -batch -l targets/install-deps.el 14 | 15 | compile: 16 | $(BEMACS) $(LOAD) -l targets/compile.el 17 | 18 | checkdoc: 19 | emacs-snapshot -batch -l elpa.el $(LOAD) -l targets/checkdoc.el 20 | 21 | check-declare: 22 | $(BEMACS) $(LOAD) -l targets/check-declare.el 23 | 24 | test: 25 | @echo "Using $(shell which $(emacs))..." 26 | $(BEMACS) -l lispy-test.el $(LOAD) -f ert-run-tests-batch-and-exit 27 | 28 | elisp: 29 | $(QEMACS) lispy.el 30 | 31 | 32 | clean: 33 | rm -f *.elc 34 | 35 | .PHONY: all clean cask elisp check-declare 36 | -------------------------------------------------------------------------------- /targets/check-declare.el: -------------------------------------------------------------------------------- 1 | (require 'check-declare) 2 | (setq check-declare-ext-errors t) 3 | (setq files '("lispy.el" 4 | "lispy-inline.el" 5 | "le-clojure.el" 6 | "le-scheme.el" 7 | "le-lisp.el")) 8 | (add-to-list 'load-path 9 | (concat (file-name-directory 10 | (locate-library "slime")) 11 | "contrib/")) 12 | (require 'slime-repl) 13 | 14 | (defun find-library-file (lib) 15 | (save-window-excursion 16 | (let ((buf (find-library lib))) 17 | (when buf 18 | (buffer-file-name buf))))) 19 | 20 | 21 | (let ((sly-file (find-library-file "sly"))) 22 | (when sly-file 23 | (add-to-list 24 | 'load-path 25 | (expand-file-name 26 | "contrib" (file-name-directory sly-file))))) 27 | 28 | (apply #'check-declare-files files) 29 | -------------------------------------------------------------------------------- /targets/install-deps.el: -------------------------------------------------------------------------------- 1 | (setq package-user-dir 2 | (expand-file-name (format ".cask/%s/elpa" emacs-version))) 3 | (message "installing in %s ...\n" package-user-dir) 4 | (package-initialize) 5 | (setq package-archives 6 | '(("melpa" . "http://melpa.org/packages/") 7 | ("gnu" . "http://elpa.gnu.org/packages/"))) 8 | (package-refresh-contents) 9 | 10 | (defconst lispy-dev-packages 11 | '(iedit 12 | multiple-cursors 13 | cider 14 | slime 15 | sly 16 | geiser 17 | clojure-mode 18 | swiper 19 | hydra 20 | ace-window 21 | helm 22 | projectile 23 | find-file-in-project 24 | undercover 25 | zoutline)) 26 | 27 | (dolist (package lispy-dev-packages) 28 | (unless (package-installed-p package) 29 | (ignore-errors 30 | (package-install package)))) 31 | 32 | (save-window-excursion 33 | (package-list-packages t) 34 | (condition-case nil 35 | (progn 36 | (package-menu-mark-upgrades) 37 | (package-menu-execute t)) 38 | (error 39 | (message "All packages up to date")))) 40 | 41 | -------------------------------------------------------------------------------- /le-scheme.el: -------------------------------------------------------------------------------- 1 | ;;; le-scheme.el --- lispy support for Scheme. -*- lexical-binding: t -*- 2 | 3 | ;; Copyright (C) 2014-2015 Oleh Krehel 4 | 5 | ;; This file is not part of GNU Emacs 6 | 7 | ;; This file 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, or (at your option) 10 | ;; 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 | ;; For a full copy of the GNU General Public License 18 | ;; see . 19 | 20 | ;;; Commentary: 21 | ;; 22 | 23 | ;;; Code: 24 | 25 | (eval-and-compile 26 | (require 'geiser-eval nil t)) 27 | 28 | (require 'lispy-inline) 29 | 30 | (defvar geiser-impl--implementation) 31 | (declare-function geiser-repl--connection* "geiser-repl") 32 | (declare-function run-geiser "geiser-repl") 33 | (declare-function geiser-repl--buffer-name "geiser-repl") 34 | (declare-function geiser-eval--send/wait "geiser-eval") 35 | (declare-function geiser-eval--retort-error "geiser-eval") 36 | (declare-function geiser-mode "geiser-mode") 37 | (declare-function geiser-edit-symbol "geiser-edit") 38 | 39 | (defun lispy--eval-scheme (str) 40 | "Eval STR as Scheme code." 41 | (unless (geiser-repl--connection*) 42 | (save-window-excursion 43 | (if geiser-impl--implementation 44 | (run-geiser geiser-impl--implementation) 45 | (call-interactively 'run-geiser)) 46 | (geiser-mode 1))) 47 | (when (string-match "(\\(?:define\\|set!\\|struct\\)[ (]+\\(\\(?:\\w\\|\\s_\\)+\\)" str) 48 | (let ((name (match-string 1 str))) 49 | (setq str (format "(begin %s %s)" str name)))) 50 | (with-current-buffer (geiser-repl--buffer-name geiser-impl--implementation) 51 | (let* ((code `(:eval (:scm ,str))) 52 | (ret (geiser-eval--send/wait code)) 53 | (err (geiser-eval--retort-error ret)) 54 | (output-str (cdr (assoc 'output ret))) 55 | (result-str (cadr (assoc 'result ret)))) 56 | (cond (err 57 | (format "Error: %s" (string-trim output-str))) 58 | ((not (equal "" output-str)) 59 | (concat 60 | (propertize 61 | output-str 62 | 'face 'font-lock-string-face) 63 | "\n" 64 | result-str)) 65 | (t 66 | result-str))))) 67 | 68 | (defun lispy-goto-symbol-scheme (symbol) 69 | (geiser-edit-symbol (make-symbol symbol))) 70 | 71 | (provide 'le-scheme) 72 | 73 | ;;; le-scheme.el ends here 74 | -------------------------------------------------------------------------------- /le-julia.el: -------------------------------------------------------------------------------- 1 | ;;; le-julia.el --- lispy support for Julia. -*- lexical-binding: t -*- 2 | 3 | ;; Copyright (C) 2016 Oleh Krehel 4 | 5 | ;; This file is not part of GNU Emacs 6 | 7 | ;; This file 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, or (at your option) 10 | ;; 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 | ;; For a full copy of the GNU General Public License 18 | ;; see . 19 | 20 | ;;; Commentary: 21 | ;; 22 | 23 | ;;; Code: 24 | 25 | (require 'julia-mode nil t) 26 | (require 'julia-shell nil t) 27 | 28 | (declare-function julia-shell-collect-command-output "ext:julia-shell") 29 | 30 | (defun lispy--eval-julia (str &optional _plain) 31 | "Eval STR as Julia code." 32 | (string-trim-right (julia-shell-collect-command-output str))) 33 | 34 | ;; TODO: simplify 35 | (defun lispy-eval-julia (&optional _plain) 36 | (let (str bnd res) 37 | (setq str 38 | (save-excursion 39 | (cond ((region-active-p) 40 | ;; get rid of "unexpected indent" 41 | (replace-regexp-in-string 42 | (concat 43 | "^" 44 | (save-excursion 45 | (goto-char (region-beginning)) 46 | (buffer-substring-no-properties 47 | (line-beginning-position) 48 | (point)))) 49 | "" (lispy--string-dwim))) 50 | ((looking-at lispy-outline) 51 | (string-trim-right 52 | (lispy--string-dwim 53 | (lispy--bounds-dwim)))) 54 | ((lispy-bolp) 55 | (lispy--string-dwim 56 | (lispy--bounds-c-toplevel))) 57 | (t 58 | (cond ((lispy-left-p)) 59 | ((lispy-right-p) 60 | (backward-list)) 61 | (t 62 | (error "Unexpected"))) 63 | (setq bnd (lispy--bounds-dwim)) 64 | (ignore-errors (backward-sexp)) 65 | (while (eq (char-before) ?.) 66 | (backward-sexp)) 67 | (setcar bnd (point)) 68 | (lispy--string-dwim bnd))))) 69 | (setq res (lispy--eval-julia str)) 70 | (if res 71 | (lispy-message 72 | (replace-regexp-in-string 73 | "%" "%%" res)) 74 | (lispy-message lispy-eval-error)))) 75 | 76 | (provide 'le-julia) 77 | 78 | ;;; le-julia.el ends here 79 | -------------------------------------------------------------------------------- /le-hy.el: -------------------------------------------------------------------------------- 1 | ;;; le-hy.el --- lispy support for Hy. -*- lexical-binding: t -*- 2 | 3 | ;; Copyright (C) 2016 Oleh Krehel 4 | 5 | ;; This file is not part of GNU Emacs 6 | 7 | ;; This file 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, or (at your option) 10 | ;; 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 | ;; For a full copy of the GNU General Public License 18 | ;; see . 19 | 20 | ;;; Commentary: 21 | ;; 22 | 23 | ;;; Code: 24 | 25 | (require 'hy-mode nil t) 26 | (require 'inf-lisp) 27 | 28 | (defun lispy--hy-proc () 29 | (let ((proc-name "hy")) 30 | (if (process-live-p proc-name) 31 | (get-process proc-name) 32 | (get-buffer-process 33 | (make-comint proc-name "hy"))))) 34 | 35 | (defun lispy--comint-eval (command) 36 | "Collect output of COMMAND without changing point." 37 | (let ((command-output-begin nil) 38 | (str nil) 39 | (last-cmd nil) 40 | (last-cmd-with-prompt nil) 41 | (buffer (process-buffer (lispy--hy-proc)))) 42 | (with-current-buffer buffer 43 | (let ((inhibit-field-text-motion t) 44 | (inhibit-read-only t)) 45 | ;; save the last command and delete the old prompt 46 | (beginning-of-line) 47 | (setq last-cmd-with-prompt 48 | (buffer-substring (point) (line-end-position))) 49 | (setq last-cmd (replace-regexp-in-string 50 | "=> " "" last-cmd-with-prompt)) 51 | (delete-region (point) (line-end-position)) 52 | ;; send the command 53 | (setq command-output-begin (point)) 54 | (comint-simple-send (get-buffer-process (current-buffer)) 55 | command) 56 | ;; collect the output 57 | (while (null (save-excursion 58 | (let ((inhibit-field-text-motion t)) 59 | (goto-char command-output-begin) 60 | (re-search-forward "^[. ]*=> \\s-*$" nil t)))) 61 | (accept-process-output (get-buffer-process buffer)) 62 | (goto-char (point-max))) 63 | (goto-char (point-max)) 64 | (when (looking-back "^[. ]*=> *" (line-beginning-position)) 65 | (goto-char (1- (match-beginning 0)))) 66 | ;; save output to string 67 | (setq str (buffer-substring-no-properties command-output-begin (point))) 68 | ;; delete the output from the command line 69 | (delete-region command-output-begin (point-max)) 70 | ;; restore prompt and insert last command 71 | (goto-char (point-max)) 72 | (comint-send-string (get-buffer-process (current-buffer)) "\n") 73 | (insert last-cmd) 74 | ;; return the shell output 75 | str)))) 76 | 77 | (defun lispy--eval-hy (str) 78 | "Eval STR as Hy code." 79 | (let ((res (lispy--comint-eval str))) 80 | (if (member res '("" "\n")) 81 | "(ok)" 82 | res))) 83 | 84 | (provide 'le-hy) 85 | 86 | ;;; le-hy.el ends here 87 | -------------------------------------------------------------------------------- /lispy-python.py: -------------------------------------------------------------------------------- 1 | # lispy-python.py --- lispy support for Python. 2 | 3 | # Copyright (C) 2016 Oleh Krehel 4 | 5 | # This file is not part of GNU Emacs 6 | 7 | # This file 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, or (at your option) 10 | # 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 | # For a full copy of the GNU General Public License 18 | # see . 19 | 20 | import inspect 21 | import re 22 | import platform 23 | try: 24 | import jedi 25 | except: 26 | print ("failed to load jedi") 27 | 28 | def arglist_retrieve_java (method): 29 | name = method.__name__ 30 | if hasattr (method, "argslist"): 31 | # uses only the first args list... 32 | args = [x.__name__ for x in method.argslist[0].args] 33 | else: 34 | methods = eval ("method.__self__.class.getDeclaredMethods ()") 35 | methods_by_name = [m for m in methods if m.getName () == name] 36 | assert len (methods_by_name) == 1, "expected only a single method by name %s" % name 37 | meta = methods_by_name[0] 38 | args = [str (par.getType ().__name__ + " " + par.getName ()) for par in meta.getParameters ()] 39 | return inspect.ArgSpec (args, None, None, None) 40 | 41 | def arglist_retrieve (sym): 42 | try: 43 | if hasattr(inspect, "getfullargspec"): 44 | res = inspect.getfullargspec (sym) 45 | return inspect.ArgSpec (args = res.args, 46 | varargs = res.varargs, 47 | defaults = res.defaults, 48 | keywords = res.kwonlydefaults) 49 | else: 50 | return inspect.getargspec (sym) 51 | except TypeError as er: 52 | if (re.search ("is not a Python function$", er.message) 53 | and platform.system () == "Java"): 54 | if inspect.isclass (sym): 55 | return arglist_retrieve_java (sym.__init__) 56 | elif hasattr (sym, "argslist") or \ 57 | hasattr (sym, "__self__") and hasattr (sym.__self__, "class"): 58 | return arglist_retrieve_java (sym) 59 | else: 60 | print (er.message) 61 | else: 62 | print (er.message) 63 | 64 | def format_arg (arg_pair): 65 | name, default_value = arg_pair 66 | if default_value: 67 | return name + " = " + default_value 68 | else: 69 | return name 70 | 71 | def delete (element, lst): 72 | return [x for x in lst if x != element] 73 | 74 | def mapcar (func, lst): 75 | """Compatibility function for Python3. 76 | 77 | In Python2 `map' returns a list, as expected. But in Python3 78 | `map' returns a map object that can be converted to a list. 79 | """ 80 | return list (map (func, lst)) 81 | 82 | def arglist (sym): 83 | arg_info = arglist_retrieve (sym) 84 | if "self" in arg_info.args: 85 | arg_info.args.remove ("self") 86 | if arg_info.defaults: 87 | defaults = [None] * (len (arg_info.args) - len (arg_info.defaults)) + \ 88 | mapcar (repr, arg_info.defaults) 89 | args = mapcar (format_arg, zip (arg_info.args, defaults)) 90 | else: 91 | args = arg_info.args 92 | if arg_info.varargs: 93 | args += arg_info.varargs 94 | if arg_info.keywords: 95 | if type (arg_info.keywords) is dict: 96 | for k, v in arg_info.keywords.items (): 97 | args.append ("%s = %s" % (k, v)) 98 | else: 99 | args.append ("**" + arg_info.keywords) 100 | return args 101 | 102 | def arglist_jedi (line, column, filename): 103 | script = jedi.Script(None, line, column, filename) 104 | defs = script.goto_definitions () 105 | if len (defs) == 0: 106 | raise TypeError ("0 definitions found") 107 | elif len (defs) > 1: 108 | raise TypeError (">1 definitions found") 109 | else: 110 | return delete ('', mapcar (lambda x: str (x.name), defs[0].params)) 111 | 112 | def is_assignment (code): 113 | ops = ast.parse (code).body 114 | return len (ops) == 1 and type (ops[0]) is ast.Assign 115 | -------------------------------------------------------------------------------- /le-lisp.el: -------------------------------------------------------------------------------- 1 | ;;; le-lisp.el --- lispy support for Common Lisp. -*- lexical-binding: t -*- 2 | 3 | ;; Copyright (C) 2014-2015 Oleh Krehel 4 | 5 | ;; This file is not part of GNU Emacs 6 | 7 | ;; This file 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, or (at your option) 10 | ;; 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 | ;; For a full copy of the GNU General Public License 18 | ;; see . 19 | 20 | ;;; Commentary: 21 | ;; 22 | 23 | ;;; Code: 24 | 25 | (eval-and-compile 26 | (require 'slime nil t) 27 | (require 'sly nil t)) 28 | 29 | (declare-function slime-output-buffer "ext:slime-repl") 30 | (declare-function slime "ext:slime") 31 | (declare-function slime-current-connection "ext:slime") 32 | (declare-function slime-eval "ext:slime") 33 | (declare-function slime-edit-definition "ext:slime") 34 | (declare-function sly-mrepl--find-buffer "ext:sly-mrepl") 35 | (declare-function sly "ext:sly") 36 | (declare-function sly-current-connection "ext:sly") 37 | (declare-function sly-eval "ext:sly") 38 | (declare-function sly-edit-definition "ext:sly") 39 | 40 | (defcustom lispy-use-sly nil 41 | "Whether to use SLY instead of SLIME." 42 | :group 'lispy 43 | :type 'boolean) 44 | 45 | (defun lispy--eval-lisp (str) 46 | "Eval STR as Common Lisp code." 47 | (let* ((deactivate-mark nil) 48 | (result (with-current-buffer (process-buffer (lispy--cl-process)) 49 | (if lispy-use-sly 50 | (sly-eval `(slynk:eval-and-grab-output ,str)) 51 | (slime-eval `(swank:eval-and-grab-output ,str)))))) 52 | (if (equal (car result) "") 53 | (cadr result) 54 | (concat (propertize (car result) 55 | 'face 'font-lock-string-face) 56 | "\n\n" 57 | (cadr result))))) 58 | 59 | (defun lispy--cl-process () 60 | (unless lispy-use-sly 61 | (require 'slime-repl)) 62 | (or (if lispy-use-sly 63 | (sly-current-connection) 64 | (slime-current-connection)) 65 | (let (conn) 66 | (let ((wnd (current-window-configuration))) 67 | (if lispy-use-sly 68 | (sly) 69 | (slime)) 70 | (while (not (if lispy-use-sly 71 | (and (setq conn (sly-current-connection)) 72 | (sly-mrepl--find-buffer conn)) 73 | (and 74 | (setq conn (slime-current-connection)) 75 | (get-buffer-window (slime-output-buffer))))) 76 | (sit-for 0.2)) 77 | (set-window-configuration wnd) 78 | conn)))) 79 | 80 | (defun lispy--lisp-args (symbol) 81 | "Return a pretty string with arguments for SYMBOL." 82 | (let ((args 83 | (list 84 | (mapconcat 85 | #'prin1-to-string 86 | (read (lispy--eval-lisp 87 | (format (if lispy-use-sly 88 | "(slynk-backend:arglist #'%s)" 89 | "(swank-backend:arglist #'%s)") 90 | symbol))) 91 | " ")))) 92 | (if (listp args) 93 | (format 94 | "(%s %s)" 95 | (propertize symbol 'face 'lispy-face-hint) 96 | (mapconcat 97 | #'identity 98 | (mapcar (lambda (x) (propertize (downcase x) 99 | 'face 'lispy-face-req-nosel)) 100 | args) 101 | (concat "\n" 102 | (make-string (+ 2 (length symbol)) ?\ )))) 103 | (propertize args 'face 'lispy-face-hint)))) 104 | 105 | (defun lispy--lisp-describe (symbol) 106 | "Return documentation for SYMBOL." 107 | (read 108 | (lispy--eval-lisp 109 | (substring-no-properties 110 | (format 111 | "(let ((x '%s)) 112 | (or (if (boundp x) 113 | (documentation x 'variable) 114 | (documentation x 'function)) 115 | \"undocumented\"))" 116 | symbol))))) 117 | 118 | (defun lispy-flatten--lisp () 119 | (let* ((bnd (lispy--bounds-list)) 120 | (str (lispy--string-dwim bnd)) 121 | (expr (read str)) 122 | (fexpr (read (lispy--eval-lisp 123 | (format "(function-lambda-expression #'%S)" (car expr)))))) 124 | (if (not (eq (car-safe fexpr) 'SB-INT:NAMED-LAMBDA)) 125 | (error "Could not find the body of %S" (car expr)) 126 | (setq fexpr (downcase 127 | (prin1-to-string 128 | `(lambda ,(nth 2 fexpr) ,(caddr (nth 3 fexpr)))))) 129 | (goto-char (car bnd)) 130 | (delete-region (car bnd) (cdr bnd)) 131 | (let* ((e-args (cdr expr)) 132 | (body (lispy--flatten-function fexpr e-args))) 133 | (lispy--insert body))))) 134 | 135 | (defun lispy-goto-symbol-lisp (symbol) 136 | ;; start SLY or SLIME if necessary 137 | (lispy--cl-process) 138 | (if lispy-use-sly 139 | (sly-edit-definition symbol) 140 | (slime-edit-definition symbol))) 141 | 142 | (provide 'le-lisp) 143 | 144 | ;;; le-lisp.el ends here 145 | -------------------------------------------------------------------------------- /lispy-clojure.clj: -------------------------------------------------------------------------------- 1 | ;;; lispy-clojure.clj --- lispy support for Clojure. 2 | 3 | ;; Copyright (C) 2015 Oleh Krehel 4 | 5 | ;; This file is not part of GNU Emacs 6 | 7 | ;; This file 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, or (at your option) 10 | ;; 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 | ;; For a full copy of the GNU General Public License 18 | ;; see . 19 | 20 | (ns lispy-clojure 21 | (:require [clojure.repl :as repl] 22 | [clojure.java.io :as io]) 23 | (:import (java.io File LineNumberReader InputStreamReader 24 | PushbackReader FileInputStream) 25 | (clojure.lang RT Reflector))) 26 | 27 | (defn expand-home 28 | [path] 29 | (if (.startsWith path "~") 30 | (let [sep (.indexOf path File/separator)] 31 | (str (io/file (System/getProperty "user.home") 32 | (subs path (inc sep))))) 33 | path)) 34 | 35 | (defn source-fn 36 | "Returns a string of the source code for the given symbol, if it can 37 | find it. This requires that the symbol resolve to a Var defined in 38 | a namespace for which the .clj is in the classpath. Returns nil if 39 | it can't find the source. 40 | 41 | Example: (source-fn 'filter)" 42 | [x] 43 | (when-let [v (resolve x)] 44 | (when-let [filepath (expand-home (:file (meta v)))] 45 | (when-let [strm (or (.getResourceAsStream (RT/baseLoader) filepath) 46 | (FileInputStream. filepath))] 47 | (with-open [rdr (LineNumberReader. (InputStreamReader. strm))] 48 | (dotimes [_ (dec (:line (meta v)))] (.readLine rdr)) 49 | (let [text (StringBuilder.) 50 | pbr (proxy [PushbackReader] [rdr] 51 | (read [] (let [i (proxy-super read)] 52 | (.append text (char i)) 53 | i)))] 54 | (if (= :unknown *read-eval*) 55 | (throw (IllegalStateException. "Unable to read source while *read-eval* is :unknown.")) 56 | (read (PushbackReader. pbr))) 57 | (str text))))))) 58 | 59 | (defn symbol-function 60 | "Return the source code for function SYM." 61 | [sym] 62 | (read-string 63 | (source-fn 64 | sym))) 65 | 66 | (defn arity [args] 67 | (if (some #{'&} args) 68 | 1000 69 | (count args))) 70 | 71 | (defn flatten-expr 72 | "Flatten a function call EXPR by substituting the arguments." 73 | [expr] 74 | (let [func-name (first expr) 75 | args (rest expr) 76 | func-def (lispy-clojure/symbol-function func-name) 77 | func-doc (when (string? (nth func-def 2)) 78 | (nth func-def 2)) 79 | func-rest (drop (if func-doc 3 2) func-def) 80 | func-rest (if (map? (first func-rest)) 81 | (rest func-rest) 82 | func-rest) 83 | func-bodies (if (vector? (first func-rest)) 84 | (list func-rest) 85 | func-rest) 86 | func-body (first (filter #(>= (lispy-clojure/arity (first %)) (count args)) 87 | (sort (fn [a b] (< (lispy-clojure/arity (first a)) 88 | (lispy-clojure/arity (first b)))) 89 | func-bodies))) 90 | func-args (first func-body) 91 | func-impl (rest func-body)] 92 | (cons 'let 93 | (cons (vec (if (some #{'&} [func-args]) 94 | (vector func-args (vec args)) 95 | (apply concat 96 | (filter (fn [[a b]] 97 | (not (= a b))) 98 | (partition 99 | 2 (interleave func-args args)))))) 100 | func-impl)))) 101 | 102 | (defn quote-maybe 103 | "Quote X that isn't self-quoting, like symbol or list." 104 | [x] 105 | (if (fn? x) 106 | x 107 | (if (or (symbol? x) 108 | (list? x)) 109 | (list 'quote x) 110 | x))) 111 | 112 | (defn debug-step-in 113 | "Evaluate the function call arugments and sub them into function arguments." 114 | [expr] 115 | (let [func-name (first expr) 116 | args (rest expr) 117 | func-def (lispy-clojure/symbol-function func-name) 118 | func-doc (when (string? (nth func-def 2)) 119 | (nth func-def 2)) 120 | func-rest (drop (if func-doc 3 2) func-def) 121 | func-rest (if (map? (first func-rest)) 122 | (rest func-rest) 123 | func-rest) 124 | func-bodies (if (vector? (first func-rest)) 125 | (list func-rest) 126 | func-rest) 127 | func-body (first (filter #(>= (lispy-clojure/arity (first %)) (count args)) 128 | (sort (fn [a b] (< (lispy-clojure/arity (first a)) 129 | (lispy-clojure/arity (first b)))) 130 | func-bodies))) 131 | func-args (first func-body)] 132 | (cons 'do 133 | (map (fn [name val] 134 | (let [ev (eval val)] 135 | (list 'def name 136 | (if (fn? ev) 137 | val 138 | (lispy-clojure/quote-maybe ev))))) 139 | func-args args)))) 140 | -------------------------------------------------------------------------------- /lispy-tags.el: -------------------------------------------------------------------------------- 1 | ;;; lispy-tags.el --- Facilitate getting a pretty list of tags for many files -*- lexical-binding: t -*- 2 | 3 | ;; Copyright (C) 2015 Oleh Krehel 4 | 5 | ;; This file is not part of GNU Emacs 6 | 7 | ;; This file 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, or (at your option) 10 | ;; 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 | ;; For a full copy of the GNU General Public License 18 | ;; see . 19 | 20 | ;;; Commentary: 21 | ;; 22 | 23 | ;;; Code: 24 | 25 | (require 'cl-lib) 26 | (defvar lispy-db (make-hash-table :test 'equal) 27 | "An alist of file to a pretty list of tags.") 28 | 29 | (cl-defstruct lispy-dbfile 30 | file 31 | tags 32 | modtime 33 | plain-tags) 34 | 35 | (defun lispy--file-list () 36 | "Get the list of same type files in current directory." 37 | (and (buffer-file-name) 38 | (let ((ext (file-name-extension (buffer-file-name)))) 39 | (nreverse 40 | (cl-remove-if 41 | (lambda (x) (string-match "\\(?:^\\.?#\\|~$\\|loaddefs.el\\)" x)) 42 | (file-expand-wildcards (format "*.%s" ext))))))) 43 | 44 | (defun lispy--fetch-this-file-tags (&optional file) 45 | "Fetch tags for FILE." 46 | (setq file (or file (buffer-file-name))) 47 | (semantic-new-buffer-fcn) 48 | (let ((tags (semantic-parse-region (point-min) (point-max)))) 49 | (when (memq major-mode (cons 'lisp-mode lispy-elisp-modes)) 50 | (let ((arity (cdr (assoc major-mode lispy-tag-arity))) 51 | (tag-regex (lispy--tag-regexp))) 52 | (mapc (lambda (x) (lispy--modify-tag x tag-regex arity file)) tags))) 53 | tags)) 54 | 55 | (defun lispy-build-semanticdb (&optional dir) 56 | "Build and save semanticdb for DIR." 57 | (interactive) 58 | (setq dir (or dir default-directory)) 59 | (let ((default-directory dir)) 60 | (dolist (f (lispy--file-list)) 61 | (let ((buf (get-file-buffer f))) 62 | (with-current-buffer (find-file-noselect f) 63 | (semantic-mode 1) 64 | (let ((semantic-parse-tree-state 'needs-rebuild)) 65 | (lispy--fetch-this-file-tags)) 66 | (unless buf 67 | (kill-buffer)))))) 68 | (let ((db (semanticdb-directory-loaded-p dir))) 69 | (or (semanticdb-save-db db) db))) 70 | 71 | (defvar lispy-completion-method) 72 | (defvar lispy-helm-columns) 73 | 74 | (defun lispy--format-tag-line (x) 75 | "Add file name to (`lispy--tag-name' X)." 76 | (if (and (eq lispy-completion-method 'ido) 77 | (not (or (bound-and-true-p ido-vertical-mode) 78 | (bound-and-true-p ivy-mode)))) 79 | x 80 | (let* ((width (min (- (window-width) 81 | (if (and (boundp 'fringe-mode) 82 | (not (eq fringe-mode 0))) 0 1)) 83 | (cadr lispy-helm-columns))) 84 | (s1 (car x)) 85 | (s2 (file-name-nondirectory 86 | (cadr x)))) 87 | (cons (if (< width 50) 88 | (if (> (length s1) width) 89 | (concat (substring s1 0 (- width 3)) 90 | "...") 91 | s1) 92 | (format (format "%%s%% %ds" (- width 93 | (length s1))) 94 | s1 s2)) 95 | x)))) 96 | 97 | (defun lispy--file-fresh-p (actual-time stored-time) 98 | "Return t when ACTUAL-TIME isn't much larger than STORED-TIME." 99 | (and stored-time 100 | (< (time-to-seconds 101 | (time-subtract 102 | actual-time 103 | stored-time)) 104 | 1.0))) 105 | 106 | (defvar lispy-force-reparse nil 107 | "When non-nil, ignore that tags are up-to-date and parse anyway.") 108 | 109 | (defun lispy--fetch-tags (&optional file-list) 110 | "Get a list of tags for FILE-LIST." 111 | (require 'semantic/bovine/el) 112 | (setq file-list (or file-list (lispy--file-list))) 113 | (let (res dbfile db-to-save) 114 | (dolist (file file-list) 115 | (let ((file-modtime (nth 5 (file-attributes file 'integer))) 116 | (exfile (expand-file-name file))) 117 | (unless (and (null lispy-force-reparse) 118 | (setq dbfile 119 | (gethash exfile lispy-db)) 120 | (lispy--file-fresh-p 121 | file-modtime 122 | (lispy-dbfile-modtime dbfile)) 123 | (lispy-dbfile-tags dbfile)) 124 | (let ((table (semanticdb-create-table-for-file (expand-file-name file)))) 125 | (if (null table) 126 | (error "Couldn't open semanticdb for file: %S" file) 127 | (let ((db (car table)) 128 | (table (cdr table)) 129 | tags) 130 | (unless (and (null lispy-force-reparse) 131 | (lispy--file-fresh-p 132 | file-modtime 133 | (oref table lastmodtime)) 134 | (setq tags 135 | (ignore-errors 136 | (oref table tags))) 137 | (semantic-tag-overlay (car-safe tags)) 138 | (not (eq (cadr (car-safe tags)) 'code))) 139 | (let ((buf (get-file-buffer file))) 140 | (with-current-buffer (or buf (find-file-noselect file)) 141 | (semantic-new-buffer-fcn) 142 | (semantic-mode 1) 143 | (oset table tags 144 | (let ((semantic-parse-tree-state 'needs-update)) 145 | (lispy--fetch-this-file-tags file))) 146 | (oset table lastmodtime 147 | (current-time)) 148 | (semanticdb-set-dirty table) 149 | (cl-pushnew db db-to-save) 150 | (unless buf 151 | (kill-buffer))))) 152 | (puthash 153 | exfile 154 | (setq dbfile 155 | (make-lispy-dbfile 156 | :file file 157 | :modtime (oref table lastmodtime) 158 | :tags (mapcar 159 | (lambda (x) 160 | (lispy--make-tag x exfile)) 161 | (oref table tags)) 162 | :plain-tags (oref table tags))) 163 | lispy-db))))) 164 | (setq res (append (lispy-dbfile-tags dbfile) res)))) 165 | (dolist (db db-to-save) 166 | (semanticdb-save-db db)) 167 | res)) 168 | 169 | (defun lispy--make-tag (tag file) 170 | "Construct a modified TAG entry including FILE." 171 | (list (lispy--tag-name tag file) 172 | file 173 | (semantic-tag-overlay tag))) 174 | 175 | 176 | (provide 'lispy-tags) 177 | 178 | ;;; lispy-tags.el ends here 179 | -------------------------------------------------------------------------------- /le-clojure.el: -------------------------------------------------------------------------------- 1 | ;;; le-clojure.el --- lispy support for Clojure. -*- lexical-binding: t -*- 2 | 3 | ;; Copyright (C) 2014-2015 Oleh Krehel 4 | 5 | ;; This file is not part of GNU Emacs 6 | 7 | ;; This file 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, or (at your option) 10 | ;; 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 | ;; For a full copy of the GNU General Public License 18 | ;; see . 19 | 20 | ;;; Commentary: 21 | ;; 22 | 23 | ;;; Code: 24 | 25 | (require 'lispy) 26 | (require 'cider-client nil t) 27 | (require 'cider-interaction nil t) 28 | 29 | (defun lispy--clojure-lax (str) 30 | "Possibly transform STR into a more convenient Clojure expression." 31 | (let ((expr (lispy--read str))) 32 | (if (and expr 33 | (symbolp expr) 34 | (< (length (symbol-name expr)) 35 | (- (length str) 3))) 36 | (setq str (format "(do (def %s) %s)" str str)) 37 | str))) 38 | 39 | (defvar lispy--clojure-hook-lambda nil 40 | "Store a lambda to call.") 41 | 42 | (defun lispy--clojure-eval-hook-lambda () 43 | "Call `lispy--clojure-hook-lambda'." 44 | (when lispy--clojure-hook-lambda 45 | (funcall lispy--clojure-hook-lambda) 46 | (setq lispy--clojure-hook-lambda nil)) 47 | (remove-hook 'nrepl-connected-hook 48 | 'lispy--clojure-eval-hook-lambda)) 49 | 50 | (defun lispy--clojure-pretty-string (str) 51 | "Return STR fontified in `clojure-mode'." 52 | (with-temp-buffer 53 | (clojure-mode) 54 | (insert str) 55 | (lispy-font-lock-ensure) 56 | (buffer-string))) 57 | 58 | (defun lispy--eval-nrepl-clojure (str namespace) 59 | (condition-case nil 60 | (with-no-warnings 61 | (nrepl-sync-request:eval 62 | str 63 | (cider-current-connection) 64 | (cider-current-session) 65 | namespace)) 66 | (error 67 | (nrepl-sync-request:eval 68 | str 69 | (cider-current-connection) 70 | namespace)))) 71 | 72 | (defun lispy--eval-clojure (str &optional add-output lax) 73 | "Eval STR as Clojure code. 74 | The result is a string. 75 | 76 | When ADD-OUTPUT is non-nil, add the standard output to the result. 77 | 78 | When LAX is non-nil, expect STR to be two sexps from a let binding. 79 | Generate an appropriate def from for that let binding and eval it." 80 | (require 'cider) 81 | (let (deactivate-mark) 82 | (if (null (cider-default-connection t)) 83 | (progn 84 | (setq lispy--clojure-hook-lambda 85 | `(lambda () 86 | (set-window-configuration 87 | ,(current-window-configuration)) 88 | (message 89 | (lispy--eval-clojure ,str ,add-output ,lax)))) 90 | (add-hook 'nrepl-connected-hook 91 | 'lispy--clojure-eval-hook-lambda t) 92 | (cider-jack-in) 93 | "Starting CIDER...") 94 | (when lax 95 | (setq str (lispy--clojure-lax str))) 96 | (let* ((str 97 | (if lispy-do-pprint 98 | (format "(clojure.core/let [x %s] (with-out-str (clojure.pprint/pprint x)))" 99 | str) 100 | str)) 101 | (res (lispy--eval-nrepl-clojure 102 | str 103 | (if lax 104 | "user" 105 | (cider-current-ns)))) 106 | (status (nrepl-dict-get res "status")) 107 | (res (if (or (member "namespace-not-found" status) 108 | (member "eval-error" status)) 109 | (nrepl-sync-request:eval 110 | str 111 | (cider-current-connection) 112 | (cider-current-session)) 113 | res)) 114 | (val (nrepl-dict-get res "value")) 115 | out) 116 | (cond ((null val) 117 | (error "Eval error: %S" 118 | (nrepl-dict-get res "err"))) 119 | 120 | (add-output 121 | (if (setq out (nrepl-dict-get res "out")) 122 | (format "%s\n%s" 123 | (propertize 124 | out 'face 'font-lock-string-face) 125 | val) 126 | (lispy--clojure-pretty-string val))) 127 | 128 | (lispy-do-pprint 129 | (read res)) 130 | 131 | (t 132 | (lispy--clojure-pretty-string val))))))) 133 | 134 | (defvar cider--debug-mode-response) 135 | (declare-function cider--debug-mode "ext:cider-debug") 136 | (defvar nrepl-ongoing-sync-request) 137 | 138 | (defun lispy--clojure-debug-quit () 139 | (let ((pt (save-excursion 140 | (if (lispy--leftp) 141 | (forward-list) 142 | (lispy--out-forward 1)) 143 | (lispy-up 1) 144 | (point))) 145 | (str (format "(do %s)" 146 | (mapconcat 147 | (lambda (x) 148 | (format "(def %s %s)" (car x) (cadr x))) 149 | (nrepl-dict-get cider--debug-mode-response "locals") 150 | "\n")))) 151 | (nrepl-send-request 152 | (list "op" "debug-input" "input" ":quit" 153 | "key" (nrepl-dict-get cider--debug-mode-response "key")) 154 | (lambda (_response)) 155 | (cider-current-connection)) 156 | (lispy--eval-clojure str) 157 | (ignore-errors 158 | (let ((nrepl-ongoing-sync-request nil)) 159 | (cider--debug-mode -1))) 160 | (goto-char pt))) 161 | 162 | (defun lispy--clojure-resolve (symbol) 163 | "Return resolved SYMBOL. 164 | Return 'special or 'keyword appropriately. 165 | Otherwise try to resolve in current namespace first. 166 | If it doesn't work, try to resolve in all available namespaces." 167 | (let ((str (lispy--eval-clojure 168 | (format 169 | "(if (symbol? '%s) 170 | (if (special-symbol? '%s) 171 | 'special 172 | (or (resolve '%s) 173 | (first (keep #(ns-resolve %% '%s) (all-ns))) 174 | (if-let [val (try (load-string \"%s\") (catch Exception e))] 175 | (list 'variable (str val))))) 176 | (if (keyword? '%s) 177 | 'keyword 178 | 'unknown))" 179 | symbol 180 | symbol 181 | symbol 182 | symbol 183 | symbol 184 | symbol)))) 185 | (if (string-match "^#'\\(.*\\)$" str) 186 | (match-string 1 str) 187 | (read str)))) 188 | 189 | (defun lispy--clojure-args (symbol) 190 | "Return a pretty string with arguments for SYMBOL. 191 | Besides functions, handles specials, keywords, maps, vectors and sets." 192 | (let* ((sym (lispy--clojure-resolve symbol)) 193 | (args (cond 194 | ((eq sym 'special) 195 | (read 196 | (lispy--eval-clojure 197 | (format 198 | "(->> (with-out-str (clojure.repl/doc %s)) 199 | (re-find #\"\\(.*\\)\") 200 | read-string rest 201 | (map str) 202 | (clojure.string/join \" \") 203 | (format \"[%%s]\") 204 | list)" 205 | symbol)))) 206 | ((eq sym 'keyword) 207 | (list "[map]")) 208 | ((eq sym 'undefined) 209 | (error "Undefined")) 210 | ((and (listp sym) (eq (car sym) 'variable)) 211 | (list "variable")) 212 | ((null sym) 213 | (read 214 | (lispy--eval-clojure 215 | (format 216 | "(let [[_ cname mname] (re-find #\"(.*)/(.*)\" \"%s\") 217 | methods (and cname 218 | (try (load-string (format \"(.getMethods %%s)\" cname)) 219 | (catch Exception e))) 220 | methods (filter #(= (.getName %%) mname) methods)] 221 | (if (= 0 (count methods)) 222 | \"method not found\" 223 | (map (fn [m] 224 | (->> m 225 | .getParameterTypes 226 | (map #(.toString %%)) 227 | (clojure.string/join \" \"))) 228 | (filter #(java.lang.reflect.Modifier/isStatic 229 | (.getModifiers %%)) 230 | methods))))" 231 | symbol)))) 232 | (t 233 | (read (lispy--eval-clojure 234 | (format 235 | "(let [args (map str (:arglists (meta #'%s)))] 236 | (if (empty? args) 237 | (eval '(list 238 | (condp #(%%1 %%2) %s 239 | map? \"[key]\" 240 | set? \"[key]\" 241 | vector? \"[idx]\" 242 | \"is uncallable\"))) 243 | args))" 244 | sym 245 | sym))))))) 246 | (if (listp args) 247 | (format 248 | "(%s %s)" 249 | (propertize symbol 'face 'lispy-face-hint) 250 | (mapconcat 251 | #'identity 252 | (mapcar (lambda (x) (propertize (downcase x) 253 | 'face 'lispy-face-req-nosel)) 254 | args) 255 | (concat "\n" 256 | (make-string (+ 2 (length symbol)) ?\ )))) 257 | (propertize args 'face 'lispy-face-hint)))) 258 | 259 | (defun lispy--describe-clojure-java (sym) 260 | "Return description for Clojure Java symol SYM." 261 | (read 262 | (lispy--eval-clojure 263 | (format 264 | "(let [[_ cname mname] (re-find #\"(.*)/(.*)\" \"%s\") 265 | methods (and cname 266 | (try (load-string (format \"(.getMethods %%s)\" cname)) 267 | (catch Exception e))) 268 | methods (filter #(= (.getName %%) mname) methods)] 269 | (if (= 0 (count methods)) 270 | nil 271 | (clojure.string/join 272 | \"\\n\" (map (fn [m] (.toString m)) 273 | methods))))" 274 | sym)))) 275 | 276 | (defun lispy--clojure-jump (symbol) 277 | "Jump to Clojure SYMBOL." 278 | (let* ((dict (nrepl-send-sync-request 279 | (list 280 | "op" "info" 281 | "session" (cider-current-session) 282 | "ns" (cider-current-ns) 283 | "symbol" symbol) 284 | (cider-current-connection))) 285 | (file (nrepl-dict-get dict "file")) 286 | (line (nrepl-dict-get dict "line")) 287 | (col (nrepl-dict-get dict "column"))) 288 | (switch-to-buffer (cider-find-file file)) 289 | (goto-char (point-min)) 290 | (forward-line (1- line)) 291 | (forward-char (1- col)))) 292 | 293 | (defun lispy--clojure-macrop (symbol) 294 | "Test if SYMBOL is a macro." 295 | (equal (lispy--eval-clojure 296 | (format "(:macro (meta #'%s))" symbol)) 297 | "true")) 298 | 299 | (defvar lispy--clojure-middleware-loaded-p nil 300 | "Nil if the Clojure middleware in \"lispy-clojure.clj\" wasn't loaded yet.") 301 | 302 | (defun lispy--clojure-middleware-unload () 303 | "Mark the Clojure middleware in \"lispy-clojure.clj\" as not loaded." 304 | (setq lispy--clojure-middleware-loaded-p nil)) 305 | 306 | (defun lispy--clojure-middleware-load () 307 | "Load the custom Clojure code in \"lispy-clojure.clj\"." 308 | (unless lispy--clojure-middleware-loaded-p 309 | (lispy--eval-clojure 310 | (format "(load-file \"%s\")" 311 | (expand-file-name "lispy-clojure.clj" lispy-site-directory))) 312 | (setq lispy--clojure-middleware-loaded-p t) 313 | (add-hook 'nrepl-disconnected-hook #'lispy--clojure-middleware-unload))) 314 | 315 | (defun lispy-flatten--clojure (_arg) 316 | "Inline a Clojure function at the point of its call." 317 | (let* ((begp (if (looking-at lispy-left) 318 | t 319 | (if (lispy-right-p) 320 | (progn (backward-list) 321 | nil) 322 | (lispy-left 1)))) 323 | (bnd (lispy--bounds-list)) 324 | (str (lispy--string-dwim bnd)) 325 | (expr (lispy--read str)) 326 | (result 327 | (if (and (symbolp (car expr)) 328 | (lispy--clojure-macrop (symbol-name (car expr)))) 329 | (lispy--eval-clojure 330 | (format "(macroexpand '%s)" str)) 331 | (lispy--clojure-middleware-load) 332 | (lispy--eval-clojure 333 | (format "(lispy-clojure/flatten-expr '%s)" str))))) 334 | (goto-char (car bnd)) 335 | (delete-region (car bnd) (cdr bnd)) 336 | (insert result) 337 | (when begp 338 | (goto-char (car bnd)))) 339 | (lispy-alt-multiline)) 340 | 341 | (defun lispy--clojure-debug-step-in () 342 | "Inline a Clojure function at the point of its call." 343 | (lispy--clojure-middleware-load) 344 | (let* ((str (lispy--eval-clojure 345 | (format "(lispy-clojure/debug-step-in '%s)" 346 | (lispy--string-dwim)))) 347 | (expr (lispy--read str)) 348 | (n-args (1- (length expr)))) 349 | (lispy-follow) 350 | (lispy--eval-clojure str) 351 | (forward-char 1) 352 | (forward-sexp 2) 353 | (when (looking-at "[ \t\n]*\"") 354 | (goto-char (1- (match-end 0))) 355 | (forward-sexp 1)) 356 | (if (looking-at "[ \t\n]*\\[") 357 | (progn 358 | (goto-char (1- (match-end 0))) 359 | (lispy-flow 1)) 360 | (lispy-forward 1) 361 | (lispy-backward 1) 362 | (lispy-flow 1) 363 | (while (/= n-args (length (lispy--read (lispy--string-dwim)))) 364 | (lispy--out-backward 1) 365 | (lispy-down 1) 366 | (lispy-flow 1)) 367 | (lispy-down 1)))) 368 | 369 | (defun lispy-goto-symbol-clojure (symbol) 370 | "Goto SYMBOL." 371 | (let ((rsymbol (lispy--clojure-resolve symbol))) 372 | (cond ((stringp rsymbol) 373 | (lispy--clojure-jump rsymbol)) 374 | ((eq rsymbol 'special) 375 | (error "Can't jump to '%s because it's special" symbol)) 376 | ((eq rsymbol 'keyword) 377 | (error "Can't jump to keywords")) 378 | ((and (listp rsymbol) 379 | (eq (car rsymbol) 'variable)) 380 | (error "Can't jump to Java variables")) 381 | (t 382 | (error "Could't resolve '%s" symbol)))) 383 | (lispy--back-to-paren)) 384 | 385 | (defun lispy-goto-symbol-clojurescript (symbol) 386 | "Goto SYMBOL." 387 | (cider-find-var nil symbol)) 388 | 389 | (provide 'le-clojure) 390 | 391 | ;;; le-clojure.el ends here 392 | -------------------------------------------------------------------------------- /lispytutor/lispytutor.el: -------------------------------------------------------------------------------- 1 | ;;* Welcome to the `lispy-mode' Tutor 2 | ;; 3 | ;; `lispy-mode' is a very powerful way to edit LISP code that has many 4 | ;; commands, too many to explain in a tutor such as this. This tutor 5 | ;; is designed to describe enough of the commands that you will be 6 | ;; able to easily use `lispy-mode' as an all-purpose LISP editor. 7 | ;; 8 | ;; The approximate time required to complete the tutor is 25-30 9 | ;; minutes, depending upon how much time is spent with 10 | ;; experimentation. 11 | ;; 12 | ;; ATTENTION: 13 | ;; The commands in the lessons will modify the code. You can always 14 | ;; revert the file using git. 15 | ;; 16 | ;; It is important to remember that this tutor is set up to teach by 17 | ;; use. That means that you need to execute the commands to learn 18 | ;; them properly. If you only read the text, you will forget the 19 | ;; commands! 20 | ;; 21 | ;; Remember that this is Emacs, you can rebind any binding that you 22 | ;; don't like. However, a lot of thought has been put into the current 23 | ;; bindings so don't be surprised if you find stuff inconvenient after 24 | ;; rebinding without thinking it through. 25 | ;; 26 | ;; Your point should now be at beginning of the first line. 27 | ;; Press =M-<= if it isn't. 28 | ;; 29 | ;; Press =J= to move to the next outline downwards. 30 | ;; 31 | ;; You should now be at Lesson 1.1. 32 | ;; 33 | ;; Press =i= to show the lesson. 34 | ;; Press =N= to narrow to the lesson. 35 | ;; 36 | ;;* Lesson 1.1: TUTORIAL NAVIGATION 37 | ;; This tutorial uses lispy outlines for navigation. 38 | ;; 39 | ;; Outlines provide a way of navigating code in a tree-like fashion. 40 | ;; The outlines themselves form the nodes of the tree. 41 | ;; 42 | ;; An outline begins with a comment ";;" followed by one or more 43 | ;; asterisks. The number of asterisks determines the outline's depth 44 | ;; in the tree. Your cursor should be at the start of the outline: 45 | ;; ";;* Lesson 1.1: TUTORIAL NAVIGATION" 46 | ;; 47 | ;; Place it there if it isn't already. 48 | ;; 49 | ;; Lispy enables special keybindings when your cursor is placed on an 50 | ;; object lispy recognizes. 51 | ;; 52 | ;; Here's a few to get started: 53 | ;; =J= moves to the next outline 54 | ;; =K= moves to the previous outline 55 | ;; =i= cycles the expansion of the outline and its children. 56 | ;; 57 | ;; Press =J= and =K= a few times to navigate the outlines in this lesson. 58 | ;; Try cycling the expansion of outlines with =i=. 59 | ;; 60 | ;; When you're finished proceed to Exercise 1. 61 | ;; 62 | ;;** Exercise 1 63 | ;; Lispy can "narrow" or "widen" outlines. 64 | ;; 65 | ;; Narrowing an outline hides all the outlines in the buffer except 66 | ;; for the outline at the cursor. This is useful when you want to 67 | ;; view or operate on the contents of a single outline. 68 | ;; 69 | ;; Press =N= to narrow to the outline for Exercise 1. 70 | ;; 71 | ;; Widening shows all outlines that have been hidden through 72 | ;; narrowing. 73 | ;; 74 | ;; Press =W= to widen the outlines. Notice how the rest of the 75 | ;; tutorial is displayed. 76 | ;; 77 | ;; Move to Exercise 2, show it, and narrow to it using the 78 | ;; shortcuts you've learned. 79 | ;; 80 | ;;** Exercise 2 81 | ;; Let's wrap up this lesson with one final shortcut. 82 | ;; 83 | ;; =2-I= will show the condensed outline structure for all the visible 84 | ;; outlines in the buffer. 85 | ;; 86 | ;; Press =W= to show the hidden outlines. 87 | ;; Press =2-I= and proceed to Lesson 1.2 88 | ;; 89 | ;;* Lesson 1.2: MOVING THE CURSOR 90 | ;; 91 | ;; Before we learn about moving the cursor, let's get some terminology 92 | ;; out the way: 93 | ;; 94 | ;; Def. 1: Point - The current cursor position. 95 | ;; 96 | ;; Def. 2: Opener - A term for the characters ( and [ and {. 97 | ;; 98 | ;; Def. 3: Closer - A term for the characters ) and ] and }. 99 | ;; 100 | ;; Def. 4: Short Binding - A command called by an uppercase or 101 | ;; lowercase letter. 102 | ;; 103 | ;; Def. 5: Special - A state the point can be in under certain 104 | ;; conditions. 105 | ;; 106 | ;; The point is 'special' when any of the conditions below are true: 107 | ;; 108 | ;; - the point is on an opener 109 | ;; - the point is on a closer 110 | ;; - the point is at the start of a comment 111 | ;; - the region is active 112 | ;; 113 | ;; When the point is special, lowercase and uppercase letters will 114 | ;; call short bindings instead of inserting characters. The digit 115 | ;; keys will call `digit-argument'. The behavior of these commands 116 | ;; can depend on the particular variation of special as described 117 | ;; above. 118 | ;; 119 | ;;** Exercise 1 120 | ;; 121 | ;; Let's test =f=, which is bound to `lispy-flow'. 122 | ;; 123 | ;; 1. Move into the special position: the first char of the line 124 | ;; indicated below. 125 | ;; 2. Press =f= to move the cursor to the next paren in current direction. 126 | ;; <--- special 127 | ;; 128 | ;; Hold =f= to move repeatedly until you reach "Skip a bit, Brother..." 129 | (with-output-to-string 130 | (defvar weapon "hand grenade") 131 | (let ((name "Saint Atilla")) 132 | (princ (format "And %s raised the %s up on high, saying, " name weapon))) 133 | (defun cite (str) 134 | (format "\"%s\"" str)) 135 | (princ (cite (concat 136 | "O Lord, bless this thy " 137 | weapon 138 | ", that with it thou mayst blow thine " 139 | "enemies to tiny bits, in thy mercy."))) 140 | (princ " And the Lord did grin. ") 141 | (princ "And the people did feast upon ") 142 | (princ (mapconcat (lambda (x) (format "the %ss" x)) 143 | '("lamb" "sloth" "carp" "anchovie" "orangutan" 144 | "breakfast cereal" "fruit bat") 145 | ", and ")) 146 | (princ ", and large chu...") 147 | (princ "\n\nSkip a bit, Brother...")) 148 | ;; 1. Now press =d= bound to `lispy-different' to switch to the different 149 | ;; side of the list. Press it a few times to get the feeling. 150 | ;; 151 | ;; 2. Making sure that you are at the right paren, press and hold =f= until 152 | ;; you reach "hand grenade". 153 | ;; 154 | ;; 3. Press =d= and hold =f=, making circles around the expressions until 155 | ;; you're comfortable. 156 | ;; 157 | ;; ** To move the cursor, press the =h=, =j=, =k=, =l= keys as indicated. ** 158 | ;; 159 | ;; ^ 160 | ;; k Hint: The =h= key is at the left and moves left. 161 | ;; < h l > The =l= key is at the right and moves right. 162 | ;; j The =j= key looks like a down arrow. 163 | ;; v 164 | ;; 165 | ;; Note that if it's impossible to move, the commands will fail silently. 166 | ;; In common code situations, if =j= doesn't work, it means that you've 167 | ;; reached the last expression of the parent list. The typical follow-up is 168 | ;; on =h= followed by either =j= or =k=. 169 | ;; 170 | ;; When on an outline, =j= and =k= are equivalent to =J= and =K=, respectively. 171 | ;; 172 | ;; Use your knowledge of =f= and =d= to setup the point in various places 173 | ;; and see what the arrow keys do there. Remember, the arrow keys will 174 | ;; behave differently depending on the side of the expression your cursor 175 | ;; is at. 176 | ;; 177 | ;; <--- To end the lesson, move the point here and press =W= (`widen'). 178 | ;;* Lesson 1.3: EXITING AND ENTERING SPECIAL 179 | ;; 180 | ;; There are a few ways to exit special. 181 | ;; If you're on an opener, closer, or comment character, just press 182 | ;; =C-f= or =C-n= or =C-p= or anything that will move your cursor away 183 | ;; from the special condition character. 184 | ;; 185 | ;;** Exercise 1: 186 | ;; 187 | ;; Let's quickly try exiting special. 188 | ;; 189 | ;; Hold =f= to move repeatedly until you reach "Skip a bit, Brother..." 190 | (with-output-to-string 191 | (defvar weapon "hand grenade") 192 | (let ((name "Saint Atilla")) 193 | (princ (format "And %s raised the %s up on high, saying, " name weapon))) 194 | (defun cite (str) 195 | (format "\"%s\"" str)) 196 | (princ (cite (concat 197 | "O Lord, bless this thy " 198 | weapon 199 | ", that with it thou mayst blow thine " 200 | "enemies to tiny bits, in thy mercy."))) 201 | (princ " And the Lord did grin. ") 202 | (princ "And the people did feast upon ") 203 | (princ (mapconcat (lambda (x) (format "the %ss" x)) 204 | '("lamb" "sloth" "carp" "anchovie" "orangutan" 205 | "breakfast cereal" "fruit bat") 206 | ", and ")) 207 | (princ ", and large chu...") 208 | (princ "\n\nSkip a bit, Brother...")) 209 | ;; Now press =C-f= to exit special mode. 210 | ;; You'll notice that pressing =f= now just self-inserts the character. 211 | ;; 212 | ;; Proceed to the next outline, LOCAL AND GLOBAL BINDINGS. 213 | ;; 214 | ;;** LOCAL AND GLOBAL BINDINGS 215 | ;; 216 | ;; You've seen how lispy's short bindings provide a concise way to 217 | ;; navigate lisp code, but they can only be used when the point is 218 | ;; special. These bindings are referred to as 'local'. 219 | ;; 220 | ;; Def. 6: Local - Bindings that only work in special. 221 | ;; 222 | ;; Lispy provides other bindings that can be used anytime when `lispy-mode' 223 | ;; is active. These bindings are referred to as 'global'. 224 | ;; 225 | ;; Def. 7: Global - Bindings that work anywhere, regardless of the 226 | ;; point location. 227 | ;; 228 | ;; Proceed to the next exercise. 229 | ;; 230 | ;;** Exercise 2: 231 | ;; 232 | ;; Let's experiment with some lispy globals. Below are some global 233 | ;; bindings that are particularly useful for entering special: 234 | ;; 235 | ;; - =[= calls `lispy-backward': when not in special, this command moves to the 236 | ;; left paren of the containing list. Otherwise, its behavior is similar to 237 | ;; the Emacs command `backward-list'. 238 | ;; 239 | ;; - =]= calls `lispy-forward': when not in special, this command moves to the 240 | ;; right paren of the containing list. Otherwise, its behavior is similar to 241 | ;; the Emacs command `forward-list'. 242 | ;; 243 | ;; - =M-m= calls `lispy-mark-symbol': This command marks the current symbol with 244 | ;; the region. When the region is already active, try to extend it by one 245 | ;; symbol. 246 | ;; 247 | ;; Hold =f= to move repeatedly until you reach "Skip a bit, Brother..." 248 | (with-output-to-string 249 | (defvar weapon "hand grenade") 250 | (let ((name "Saint Atilla")) 251 | (princ (format "And %s raised the %s up on high, saying, " name weapon))) 252 | (defun cite (str) 253 | (format "\"%s\"" str)) 254 | (princ (cite (concat 255 | "O Lord, bless this thy " 256 | weapon 257 | ", that with it thou mayst blow thine " 258 | "enemies to tiny bits, in thy mercy."))) 259 | (princ " And the Lord did grin. ") 260 | (princ "And the people did feast upon ") 261 | (princ (mapconcat (lambda (x) (format "the %ss" x)) 262 | '("lamb" "sloth" "carp" "anchovie" "orangutan" 263 | "breakfast cereal" "fruit bat") 264 | ", and ")) 265 | (princ ", and large chu...") 266 | (princ "\n\nSkip a bit, Brother...")) 267 | ;; Press =C-f= to exit special and move forward one character. 268 | ;; Edit the text in the string to your liking and then press =[= twice. 269 | ;; 270 | ;; Your point should be on the previous expression and it should be special. 271 | ;; Verify by moving around with =j= and =k=. 272 | ;; 273 | ;; Now navigate your point so you're on the expression with ", and large chu..." 274 | ;; Press =C-f= again to exit special. 275 | ;; Edit the string to your liking and then press =]= twice. 276 | ;; 277 | ;; Your point should now be at the end of the last expression. 278 | ;; 279 | ;; Take some time to experiment with =[= and =]= and the commands you've learned 280 | ;; previously. 281 | ;; 282 | ;; When you're finished, place your cursor back on "Skip a bit, Brother...". 283 | ;; Press =C-f= to exit special. 284 | ;; Press =M-m=. This should mark the current symbol `princ'. 285 | ;; 286 | ;; Marking will be covered in more detail later, but for now, take some time to 287 | ;; experiment with =M-m= then proceed to Lesson 1.3. 288 | ;; 289 | ;;* Lesson 1.4: LISP EDITING - DELETION 290 | ;; 291 | ;; To delete things, first you need to have a lot of them. 292 | ;; 293 | ;; 1. You can generate a lot of code by moving the point before 294 | ;; (with-output-to-string and pressing =c= bound to `lispy-clone' a 295 | ;; few times. 296 | ;; 297 | ;; 2. Now, press =C-d= bound to `lispy-delete' to delete the whole 298 | ;; sexp. Do this until you have only two top-level sexps left. 299 | ;; 300 | ;; 3. Press =f= to move inside the top-level sexp and hold =C-d= until 301 | ;; everything inside is deleted. Note how the top-level sexp is 302 | ;; deleted after all its children are gone. 303 | ;; 304 | ;; 4. Make another clone with =c= and switch to the different side 305 | ;; with =d=. Do a single =f= and practice pressing =DEL= bound to 306 | ;; `lispy-delete-backward'. 307 | ;; 308 | ;; 5. Use conventional means to position the point at either side of 309 | ;; the string and the quote and test what both =C-d= and =DEL= do. 310 | (with-output-to-string 311 | (defvar weapon "hand granade") 312 | (let ((name "Saint Atilla")) 313 | (princ (format "And %s raised the %s up on high, saying, " name weapon))) 314 | (defun cite (str) 315 | (format "\"%s\"" str)) 316 | (princ (cite (concat 317 | "O Lord, bless this thy " 318 | weapon 319 | ", that with it thou mayst blow thine " 320 | "enemies to tiny bits, in thy mercy."))) 321 | (princ " And the Lord did grin. ") 322 | (princ "And the people did feast upon ") 323 | (princ (mapconcat (lambda (x) (format "the %ss" x)) 324 | '("lamb" "sloth" "carp" "anchovie" "orangutan" 325 | "breakfast cereal" "fruit bat") 326 | ", and ")) 327 | (princ ", and large chu...") 328 | (princ "\n\nSkip a bit, Brother...")) 329 | ;;* Lesson 1.5: LISP EDITING - INSERTION 330 | ;; 331 | ;; Basic insertion bindings: 332 | ;; 333 | ;; =(= - inserts () and goes backward one char in code, self-inserts 334 | ;; in comments and strings. 335 | ;; ={= - inserts {} and goes backward one char everywhere 336 | ;; =}= - inserts [] and goes backward one char everywhere 337 | ;; ="= - inserts "" and goes backward one char, auto-quotes in strings 338 | ;; 339 | ;; All four of the pairs above will try to add once space where 340 | ;; appropriate, so to insert "(list ())", press =(list(=. 341 | ;; 342 | ;; If you want any of the mentined keys to self-insert once, remember 343 | ;; that prefixing any key with =C-q= will do that. 344 | ;; 345 | ;; All four pairs will wrap the current thing when the region is active. 346 | ;; All four pairs will wrap the current symbol when prefixed with =C-u=. 347 | ;; 348 | ;;* Lesson 1.6: LISP EDITING - APPENDING 349 | ;; 350 | ;; You can append the current list: 351 | ;; 352 | ;; - from the front with =2 SPC= 353 | ;; - from the back with =3 SPC= 354 | ;; - from the back with a newline =4 SPC= 355 | ;;* Lesson 1.7: OUTLINE NAVIGATION 356 | ;; 357 | ;; Here, several bindings depend on conditions additional to being in 358 | ;; special. Some of them are a superset of others, for example 359 | ;; COMMENT is a superset of OUTLINE, since all outlines are comments. 360 | ;; 361 | ;; When at COMMENT, you can: 362 | ;; 363 | ;; - navigate to the next outline with =j=, 364 | ;; - navigate to the previous outline with =k=. 365 | ;; - navigate to the first code paren of the outline with =f=. 366 | ;; 367 | ;; When at OUTLINE, you can: 368 | ;; 369 | ;; - fold and unfold the outline with =i=, 370 | ;; - promote the outline with =h=, 371 | ;; - demote the outline with =l=, 372 | ;; - recenter / undo recenter with =v=, 373 | ;; - create a new outline with =a=, 374 | ;; - move to the end of the oneline heading with =t=, 375 | ;; - narrow to the outline with =N=, 376 | ;; - widen with =W=. 377 | ;; 378 | ;; Anywhere, you can: 379 | ;; 380 | ;; - navigate to the next outline with =J=, 381 | ;; - navigate to the previous outline with =K=, 382 | ;; - toggle folding to all outlines of level 1 with =I=, 383 | ;; - get the table of contents with a prefix argument and =I=, e.g. =2I=, 384 | ;; 385 | ;; Also note that =I= has a global alias =S-TAB=, which you can use 386 | ;; even when not is special. It mirrors the popular `org-mode' 387 | ;; package, with the difference that it's a two-way toggle instead of 388 | ;; three-way. You can get the third option with a prefix argument. 389 | ;;* Lesson 1.8: MOVING THE REGION 390 | -------------------------------------------------------------------------------- /lispy-inline.el: -------------------------------------------------------------------------------- 1 | ;;; lispy-inline.el --- inline arglist and documentation. -*- lexical-binding: t -*- 2 | 3 | ;; Copyright (C) 2014-2015 Oleh Krehel 4 | 5 | ;; This file is not part of GNU Emacs 6 | 7 | ;; This file 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, or (at your option) 10 | ;; 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 | ;; For a full copy of the GNU General Public License 18 | ;; see . 19 | 20 | ;;; Commentary: 21 | ;; 22 | ;; Display current function arguments or docstring in an in-place 23 | ;; overlay. 24 | 25 | ;;; Code: 26 | 27 | (if (version< emacs-version "24.4") 28 | (progn 29 | (defsubst string-trim-left (string) 30 | "Remove leading whitespace from STRING." 31 | (if (string-match "\\`[ \t\n\r]+" string) 32 | (replace-match "" t t string) 33 | string)) 34 | (defsubst string-trim-right (string) 35 | "Remove trailing whitespace from STRING." 36 | (if (string-match "[ \t\n\r]+\\'" string) 37 | (replace-match "" t t string) 38 | string)) 39 | (defsubst string-trim (string) 40 | "Remove leading and trailing whitespace from STRING." 41 | (string-trim-left (string-trim-right string)))) 42 | (require 'subr-x)) 43 | 44 | (defgroup lispy-faces nil 45 | "Font-lock faces for `lispy'." 46 | :group 'lispy 47 | :prefix "lispy-face-") 48 | 49 | (defface lispy-face-hint 50 | '((((class color) (background light)) 51 | :background "#fff3bc" :foreground "black") 52 | (((class color) (background dark)) 53 | :background "black" :foreground "#fff3bc")) 54 | "Basic hint face." 55 | :group 'lispy-faces) 56 | 57 | (defface lispy-face-req-nosel 58 | '((t (:inherit lispy-face-hint))) 59 | "Face for required unselected args." 60 | :group 'lispy-faces) 61 | 62 | (defface lispy-face-req-sel 63 | '((t (:inherit lispy-face-req-nosel :bold t))) 64 | "Face for required selected args." 65 | :group 'lispy-faces) 66 | 67 | (defface lispy-face-opt-nosel 68 | '((t (:inherit lispy-face-hint :slant italic))) 69 | "Face for optional unselected args." 70 | :group 'lispy-faces) 71 | 72 | (defface lispy-face-key-nosel 73 | '((t (:inherit lispy-face-hint :slant italic))) 74 | "Face for keyword unselected args." 75 | :group 'lispy-faces) 76 | 77 | (defface lispy-face-opt-sel 78 | '((t (:inherit lispy-face-opt-nosel :bold t))) 79 | "Face for optional selected args." 80 | :group 'lispy-faces) 81 | 82 | (defface lispy-face-key-sel 83 | '((t (:inherit lispy-face-opt-nosel :bold t))) 84 | "Face for keyword selected args." 85 | :group 'lispy-faces) 86 | 87 | (defface lispy-face-rst-nosel 88 | '((t (:inherit lispy-face-hint))) 89 | "Face for rest unselected args." 90 | :group 'lispy-faces) 91 | 92 | (defface lispy-face-rst-sel 93 | '((t (:inherit lispy-face-rst-nosel :bold t))) 94 | "Face for rest selected args." 95 | :group 'lispy-faces) 96 | 97 | (defcustom lispy-window-height-ratio 0.65 98 | "`lispy--show' will fail with string taller than window height times this. 99 | The caller of `lispy--show' might use a substitute e.g. `describe-function'." 100 | :type 'float 101 | :group 'lispy) 102 | 103 | (defvar lispy-elisp-modes 104 | '(emacs-lisp-mode lisp-interaction-mode eltex-mode minibuffer-inactive-mode) 105 | "Modes for which `lispy--eval-elisp' and related functions are appropriate.") 106 | 107 | (defvar lispy-clojure-modes 108 | '(clojure-mode clojurescript-mode clojurex-mode clojurec-mode) 109 | "Modes for which clojure related functions are appropriate.") 110 | 111 | (defvar lispy-overlay nil 112 | "Hint overlay instance.") 113 | 114 | (defvar lispy-hint-pos nil 115 | "Point position where the hint should be (re-) displayed.") 116 | 117 | (declare-function lispy--eval-clojure "le-clojure") 118 | (declare-function lispy--clojure-args "le-clojure") 119 | (declare-function lispy--clojure-resolve "le-clojure") 120 | (declare-function lispy--describe-clojure-java "le-clojure") 121 | (declare-function lispy--eval-scheme "le-scheme") 122 | (declare-function lispy--eval-lisp "le-lisp") 123 | (declare-function lispy--lisp-args "le-lisp") 124 | (declare-function lispy--lisp-describe "le-lisp") 125 | (declare-function lispy--back-to-paren "lispy") 126 | (declare-function lispy--current-function "lispy") 127 | 128 | ;; ——— Commands ———————————————————————————————————————————————————————————————— 129 | (defun lispy--back-to-python-function () 130 | "Move point from function call at point to the function name." 131 | (let ((pt (point)) 132 | bnd) 133 | (if (lispy--in-comment-p) 134 | (error "Not possible in a comment") 135 | (condition-case nil 136 | (progn 137 | (when (setq bnd (lispy--bounds-string)) 138 | (goto-char (car bnd))) 139 | (up-list -1)) 140 | (error (goto-char pt))) 141 | (unless (looking-at "\\_<") 142 | (re-search-backward "\\_<" (line-beginning-position)))))) 143 | 144 | (defun lispy-arglist-inline () 145 | "Display arglist for `lispy--current-function' inline." 146 | (interactive) 147 | (save-excursion 148 | (if (eq major-mode 'python-mode) 149 | (lispy--back-to-python-function) 150 | (lispy--back-to-paren)) 151 | (unless (and (prog1 (lispy--cleanup-overlay) 152 | (when (window-minibuffer-p) 153 | (window-resize (selected-window) -1))) 154 | (= lispy-hint-pos (point))) 155 | (cond ((memq major-mode lispy-elisp-modes) 156 | (let ((sym (intern-soft (lispy--current-function)))) 157 | (cond ((fboundp sym) 158 | (setq lispy-hint-pos (point)) 159 | (lispy--show (lispy--pretty-args sym)))))) 160 | ((or (memq major-mode '(cider-repl-mode)) 161 | (memq major-mode lispy-clojure-modes)) 162 | (require 'le-clojure) 163 | (setq lispy-hint-pos (point)) 164 | (lispy--show (lispy--clojure-args (lispy--current-function)))) 165 | 166 | ((eq major-mode 'lisp-mode) 167 | (require 'le-lisp) 168 | (setq lispy-hint-pos (point)) 169 | (lispy--show (lispy--lisp-args (lispy--current-function)))) 170 | 171 | ((eq major-mode 'python-mode) 172 | (require 'le-python) 173 | (setq lispy-hint-pos (point)) 174 | (let ((arglist (lispy--python-arglist 175 | (python-info-current-symbol) 176 | (buffer-file-name) 177 | (line-number-at-pos) 178 | (current-column)))) 179 | (while (eq (char-before) ?.) 180 | (backward-sexp)) 181 | (lispy--show arglist))) 182 | 183 | (t (error "%s isn't supported currently" major-mode)))))) 184 | 185 | (defun lispy--delete-help-windows () 186 | "Delete help windows. 187 | Return t if at least one was deleted." 188 | (let (deleted) 189 | (mapc (lambda (window) 190 | (when (eq (with-current-buffer (window-buffer window) 191 | major-mode) 192 | 'help-mode) 193 | (delete-window window) 194 | (setq deleted t))) 195 | (window-list)) 196 | deleted)) 197 | 198 | (defvar lispy--di-window-config nil 199 | "Store window configuration before `lispy-describe-inline'.") 200 | 201 | (defun lispy--hint-pos () 202 | "Point position for the first column of the hint." 203 | (save-excursion 204 | (cond ((region-active-p) 205 | (goto-char (region-beginning))) 206 | ((eq major-mode 'python-mode) 207 | (goto-char (beginning-of-thing 'sexp))) 208 | (t 209 | (lispy--back-to-paren))) 210 | (point))) 211 | 212 | (defun lispy--cleanup-overlay () 213 | "Delete `lispy-overlay' if it's valid and return t." 214 | (when (overlayp lispy-overlay) 215 | (delete-overlay lispy-overlay) 216 | (setq lispy-overlay nil) 217 | t)) 218 | 219 | (declare-function geiser-doc-symbol-at-point "geiser-doc") 220 | 221 | (defun lispy--describe-inline () 222 | "Toggle the overlay hint." 223 | (condition-case nil 224 | (let ((new-hint-pos (lispy--hint-pos)) 225 | doc) 226 | (if (and (eq lispy-hint-pos new-hint-pos) 227 | (overlayp lispy-overlay)) 228 | (lispy--cleanup-overlay) 229 | (save-excursion 230 | (when (= 0 (count-lines (window-start) (point))) 231 | (recenter 1)) 232 | (setq lispy-hint-pos new-hint-pos) 233 | (if (eq major-mode 'scheme-mode) 234 | (geiser-doc-symbol-at-point) 235 | (when (setq doc (lispy--docstring (lispy--current-function))) 236 | (goto-char lispy-hint-pos) 237 | (lispy--show (propertize doc 'face 'lispy-face-hint))))))) 238 | (error 239 | (lispy--cleanup-overlay)))) 240 | 241 | (defun lispy--docstring (sym) 242 | "Get the docstring for SYM." 243 | (cond 244 | ((memq major-mode lispy-elisp-modes) 245 | (let (dc) 246 | (setq sym (intern-soft sym)) 247 | (cond ((fboundp sym) 248 | (if (lispy--show-fits-p 249 | (setq dc (or (documentation sym) 250 | "undocumented"))) 251 | dc 252 | (setq lispy--di-window-config (current-window-configuration)) 253 | (save-selected-window 254 | (describe-function sym)) 255 | nil)) 256 | ((boundp sym) 257 | (if (lispy--show-fits-p 258 | (setq dc (or (documentation-property 259 | sym 'variable-documentation) 260 | "undocumented"))) 261 | dc 262 | (setq lispy--di-window-config (current-window-configuration)) 263 | (save-selected-window 264 | (describe-variable sym)) 265 | nil)) 266 | (t "unbound")))) 267 | ((or (memq major-mode lispy-clojure-modes) 268 | (memq major-mode '(cider-repl-mode))) 269 | (require 'le-clojure) 270 | (let ((rsymbol (lispy--clojure-resolve sym))) 271 | (string-trim-left 272 | (replace-regexp-in-string 273 | "^\\(?:-+\n\\|\n*.*$.*@.*\n*\\)" "" 274 | (cond ((stringp rsymbol) 275 | (read 276 | (lispy--eval-clojure 277 | (format "(with-out-str (clojure.repl/doc %s))" rsymbol)))) 278 | ((eq rsymbol 'special) 279 | (read 280 | (lispy--eval-clojure 281 | (format "(with-out-str (clojure.repl/doc %s))" sym)))) 282 | ((eq rsymbol 'keyword) 283 | "No docs for keywords") 284 | ((and (listp rsymbol) 285 | (eq (car rsymbol) 'variable)) 286 | (cadr rsymbol)) 287 | (t 288 | (or (lispy--describe-clojure-java sym) 289 | (format "Could't resolve '%s" sym)))))))) 290 | ((eq major-mode 'lisp-mode) 291 | (require 'le-lisp) 292 | (lispy--lisp-describe sym)) 293 | ((eq major-mode 'python-mode) 294 | (semantic-mode 1) 295 | (let ((sym (semantic-ctxt-current-symbol))) 296 | (if sym 297 | (progn 298 | (setq sym (mapconcat #'identity sym ".")) 299 | (require 'le-python) 300 | (or 301 | (lispy--python-docstring sym) 302 | (progn 303 | (message "no doc: %s" sym) 304 | nil))) 305 | (error "The point is not on a symbol")))) 306 | (t 307 | (format "%s isn't supported currently" major-mode)))) 308 | 309 | (declare-function semantic-ctxt-current-symbol "ctxt") 310 | 311 | (defun lispy-describe-inline () 312 | "Display documentation for `lispy--current-function' inline." 313 | (interactive) 314 | (if (cl-some (lambda (window) 315 | (eq (with-current-buffer (window-buffer window) 316 | major-mode) 317 | 'help-mode)) 318 | (window-list)) 319 | (if (window-configuration-p lispy--di-window-config) 320 | (set-window-configuration lispy--di-window-config) 321 | (lispy--delete-help-windows)) 322 | (lispy--describe-inline))) 323 | 324 | (declare-function lispy--python-docstring "le-python") 325 | (declare-function lispy--python-arglist "le-python") 326 | (declare-function python-info-current-symbol "python") 327 | 328 | ;; ——— Utilities ——————————————————————————————————————————————————————————————— 329 | (defun lispy--arglist (symbol) 330 | "Get arglist for SYMBOL." 331 | (let (doc) 332 | (if (setq doc (help-split-fundoc (documentation symbol t) symbol)) 333 | (car doc) 334 | (prin1-to-string 335 | (cons symbol (help-function-arglist symbol t)))))) 336 | 337 | (defun lispy--join-pad (strs width) 338 | "Join STRS padding each line with WIDTH spaces." 339 | (let* ((maxw (apply #'max (mapcar #'length strs))) 340 | (padding (make-string width ?\ )) 341 | (fstring (format "%%- %ds" maxw))) 342 | (mapconcat 343 | (lambda (x) 344 | (concat padding 345 | (ivy--add-face (format fstring x) 'lispy-face-hint))) 346 | strs 347 | "\n"))) 348 | 349 | (defun lispy--show-fits-p (str) 350 | "Return nil if window isn't large enough to display STR whole." 351 | (let ((strs (split-string str "\n"))) 352 | (when (or (< (length strs) (* lispy-window-height-ratio (window-height))) 353 | (window-minibuffer-p)) 354 | strs))) 355 | 356 | (defun lispy--show (str) 357 | "Show STR hint when `lispy--show-fits-p' is t." 358 | (let ((last-point (point)) 359 | (strs (lispy--show-fits-p str))) 360 | (if strs 361 | (progn 362 | (setq str (lispy--join-pad 363 | strs 364 | (+ (if (window-minibuffer-p) 365 | (- (minibuffer-prompt-end) (point-min)) 366 | 0) 367 | (string-width (buffer-substring 368 | (line-beginning-position) 369 | (point)))))) 370 | (save-excursion 371 | (goto-char lispy-hint-pos) 372 | (if (= -1 (forward-line -1)) 373 | (setq str (concat str "\n")) 374 | (end-of-line) 375 | (setq str (concat "\n" str))) 376 | (setq str (concat str 377 | (buffer-substring (point) (1+ (point))))) 378 | (if lispy-overlay 379 | (progn 380 | (move-overlay lispy-overlay (point) (+ (point) 1)) 381 | (overlay-put lispy-overlay 'invisible nil)) 382 | (setq lispy-overlay (make-overlay (point) (+ (point) 1))) 383 | (overlay-put lispy-overlay 'priority 9999)) 384 | (overlay-put lispy-overlay 'display str) 385 | (overlay-put lispy-overlay 'after-string "") 386 | (put 'lispy-overlay 'last-point last-point))) 387 | (setq lispy--di-window-config (current-window-configuration)) 388 | (save-selected-window 389 | (pop-to-buffer (get-buffer-create "*lispy-help*")) 390 | (let ((inhibit-read-only t)) 391 | (delete-region (point-min) (point-max)) 392 | (insert str) 393 | (goto-char (point-min)) 394 | (help-mode)))))) 395 | 396 | (defun lispy--pretty-args (symbol) 397 | "Return a vector of fontified strings for function SYMBOL." 398 | (let* ((args (cdr (read (lispy--arglist symbol)))) 399 | (p-opt (cl-position '&optional args :test 'equal)) 400 | (p-rst (or (cl-position '&rest args :test 'equal) 401 | (cl-position-if (lambda (x) 402 | (and (symbolp x) 403 | (string-match 404 | "\\.\\.\\.\\'" 405 | (symbol-name x)))) 406 | args))) 407 | (a-req (cl-subseq args 0 (or p-opt p-rst (length args)))) 408 | (a-opt (and p-opt 409 | (cl-subseq args (1+ p-opt) (or p-rst (length args))))) 410 | (a-rst (and p-rst (last args)))) 411 | (format 412 | "(%s)" 413 | (mapconcat 414 | #'identity 415 | (append 416 | (list (propertize (symbol-name symbol) 'face 'lispy-face-hint)) 417 | (mapcar 418 | (lambda (x) 419 | (propertize (downcase (prin1-to-string x)) 'face 'lispy-face-req-nosel)) 420 | a-req) 421 | (mapcar 422 | (lambda (x) 423 | (propertize (downcase (prin1-to-string x)) 'face 'lispy-face-opt-nosel)) 424 | a-opt) 425 | (mapcar 426 | (lambda (x) 427 | (setq x (downcase (symbol-name x))) 428 | (unless (string-match "\\.\\.\\.$" x) 429 | (setq x (concat x "..."))) 430 | (propertize x 'face 'lispy-face-rst-nosel)) 431 | a-rst)) 432 | " ")))) 433 | 434 | (provide 'lispy-inline) 435 | 436 | ;;; Local Variables: 437 | ;;; outline-regexp: ";; ———" 438 | ;;; End: 439 | 440 | ;;; lispy-inline.el ends here 441 | -------------------------------------------------------------------------------- /le-python.el: -------------------------------------------------------------------------------- 1 | ;;; le-python.el --- lispy support for Python. -*- lexical-binding: t -*- 2 | 3 | ;; Copyright (C) 2016 Oleh Krehel 4 | 5 | ;; This file is not part of GNU Emacs 6 | 7 | ;; This file 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, or (at your option) 10 | ;; 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 | ;; For a full copy of the GNU General Public License 18 | ;; see . 19 | 20 | ;;; Commentary: 21 | ;; 22 | 23 | ;;; Code: 24 | 25 | (require 'python) 26 | (require 'json) 27 | 28 | (defun lispy-trim-python (str) 29 | "Trim extra Python indentation from STR. 30 | 31 | STR is a string copied from Python code. It can be that each line 32 | of STR is prefixed by e.g. 4 or 8 or 12 spaces. 33 | Stripping them will produce code that's valid for an eval." 34 | (if (string-match "\\`\\( +\\)" str) 35 | (let* ((indent (match-string 1 str)) 36 | (re (concat "^" indent))) 37 | (apply #'concat 38 | (split-string str re t))) 39 | str)) 40 | 41 | (defun lispy-eval-python-bnd () 42 | (let (bnd) 43 | (save-excursion 44 | (cond ((region-active-p) 45 | (cons 46 | (if (> (count-lines (region-beginning) (region-end)) 1) 47 | (save-excursion 48 | (goto-char (region-beginning)) 49 | (skip-chars-backward " ") 50 | (point)) 51 | (region-beginning)) 52 | (region-end))) 53 | ((and (looking-at lispy-outline) 54 | (looking-at lispy-outline-header)) 55 | (lispy--bounds-outline)) 56 | ((setq bnd (lispy-bounds-python-block))) 57 | ((lispy-bolp) 58 | (lispy--bounds-c-toplevel)) 59 | (t 60 | (cond ((lispy-left-p)) 61 | ((lispy-right-p) 62 | (backward-list)) 63 | (t 64 | (error "Unexpected"))) 65 | (setq bnd (lispy--bounds-dwim)) 66 | (ignore-errors (backward-sexp)) 67 | (while (or (eq (char-before) ?.) 68 | (eq (char-after) ?\()) 69 | (backward-sexp)) 70 | (setcar bnd (point)) 71 | bnd))))) 72 | 73 | (defun lispy-eval-python-str () 74 | (let* ((bnd (lispy-eval-python-bnd)) 75 | (str (lispy-trim-python 76 | (lispy--string-dwim bnd)))) 77 | (when (string-match "\\`([^\0]*)\\'" str) 78 | (setq str (replace-regexp-in-string "\n *" " " str))) 79 | (replace-regexp-in-string 80 | ",\n +" "," 81 | (replace-regexp-in-string 82 | "\\\\\n +" "" 83 | str)))) 84 | 85 | (defun lispy-bounds-python-block () 86 | (if (save-excursion 87 | (when (looking-at " ") 88 | (forward-char)) 89 | (python-info-beginning-of-block-p)) 90 | (let ((indent (if (bolp) 91 | 0 92 | (1+ (- (point) (line-beginning-position)))))) 93 | (cons 94 | (line-beginning-position) 95 | (save-excursion 96 | (python-nav-end-of-block) 97 | (while (let ((pt (point)) 98 | bnd) 99 | (skip-chars-forward "\n ") 100 | (when (setq bnd (lispy--bounds-comment)) 101 | (goto-char (cdr bnd))) 102 | (beginning-of-line) 103 | (if (looking-at (format "[\n ]\\{%d,\\}\\(except\\|else\\|elif\\)" indent)) 104 | t 105 | (goto-char pt) 106 | nil)) 107 | (goto-char (match-beginning 1)) 108 | (python-nav-end-of-block)) 109 | (point)))) 110 | (cons (point) 111 | (save-excursion 112 | (end-of-line) 113 | (let (bnd) 114 | (when (setq bnd (lispy--bounds-string)) 115 | (goto-char (cdr bnd)))) 116 | (end-of-line) 117 | (while (member (char-before) 118 | '(?\\ ?\,)) 119 | (end-of-line 2)) 120 | (point))))) 121 | 122 | (defun lispy-eval-python (&optional plain) 123 | (let ((res (lispy--eval-python 124 | (lispy-eval-python-str) 125 | plain))) 126 | (if (and res (not (equal res ""))) 127 | (lispy-message 128 | (replace-regexp-in-string 129 | "%" "%%" res)) 130 | (lispy-message 131 | (replace-regexp-in-string 132 | "%" "%%" lispy-eval-error))))) 133 | 134 | (defvar-local lispy-python-proc nil) 135 | 136 | (defun lispy-set-python-process () 137 | "Associate a (possibly new) Python process to the current buffer. 138 | 139 | Each buffer can have only a single Python process associated with 140 | it at one time." 141 | (interactive) 142 | (let* ((process-names 143 | (delq nil 144 | (mapcar 145 | (lambda (x) 146 | (when (string-match "^lispy-python-\\(.*\\)" (process-name x)) 147 | (match-string 1 (process-name x)))) 148 | (process-list))))) 149 | (ivy-read "Process: " process-names 150 | :action (lambda (x) 151 | (setq lispy-python-proc 152 | (lispy--python-proc (concat "lispy-python-" x)))) 153 | :caller 'lispy-set-python-process))) 154 | 155 | (defvar lispy--python-middleware-loaded-p nil 156 | "Nil if the Python middleware in \"lispy-python.py\" wasn't loaded yet.") 157 | 158 | (defun lispy--python-proc (&optional name) 159 | (let* ((proc-name (or name 160 | (and (process-live-p lispy-python-proc) 161 | lispy-python-proc) 162 | "lispy-python-default")) 163 | (process (get-process proc-name))) 164 | (if (process-live-p process) 165 | process 166 | (let ((python-shell-font-lock-enable nil) 167 | (inferior-python-mode-hook nil) 168 | (python-binary-name (python-shell-calculate-command))) 169 | (save-excursion 170 | (goto-char (point-min)) 171 | (when (looking-at "#!\\(.*\\)$") 172 | (setq python-binary-name 173 | (concat 174 | (match-string-no-properties 1) 175 | " " 176 | python-shell-interpreter-args)))) 177 | (setq process (get-buffer-process 178 | (python-shell-make-comint 179 | python-binary-name proc-name nil nil)))) 180 | (setq lispy--python-middleware-loaded-p nil) 181 | (lispy--python-middleware-load) 182 | process))) 183 | 184 | (defun lispy--eval-python (str &optional plain) 185 | "Eval STR as Python code." 186 | (let ((single-line-p (= (cl-count ?\n str) 0))) 187 | (unless plain 188 | (setq str (string-trim str)) 189 | (cond ((and (or (string-match "\\`\\(\\(?:[., ]\\|\\sw\\|\\s_\\|[][]\\)+\\) += " str) 190 | (string-match "\\`\\(([^)]+)\\) *=[^=]" str)) 191 | (save-match-data 192 | (or single-line-p 193 | (and (not (string-match-p "lp\\." str)) 194 | (equal (lispy--eval-python 195 | (format "x=lp.is_assignment(\"\"\"%s\"\"\")\nprint (x)" str) 196 | t) 197 | "True"))))) 198 | (setq str (concat str (format "\nprint (repr ((%s)))" (match-string 1 str))))) 199 | ;; match e.g. "x in array" part of "for x in array:" 200 | ((and single-line-p 201 | (string-match "\\`\\([A-Z_a-z,0-9 ()]+\\) in \\(.*\\)\\'" str)) 202 | (let ((vars (match-string 1 str)) 203 | (val (match-string 2 str))) 204 | (setq str (format "%s = list (%s)[0]\nprint ((%s))" vars val vars))))) 205 | (when (and single-line-p (string-match "\\`return \\(.*\\)\\'" str)) 206 | (setq str (match-string 1 str)))) 207 | (let ((res 208 | (cond ((or single-line-p 209 | (string-match "\n .*\\'" str) 210 | (string-match "\"\"\"" str)) 211 | (python-shell-send-string-no-output 212 | str (lispy--python-proc))) 213 | ((string-match "\\`\\([\0-\377[:nonascii:]]*\\)\n\\([^\n]*\\)\\'" str) 214 | (let* ((p1 (match-string 1 str)) 215 | (p2 (match-string 2 str)) 216 | (p1-output (python-shell-send-string-no-output 217 | p1 (lispy--python-proc))) 218 | p2-output) 219 | (cond ((null p1-output) 220 | (lispy-message lispy-eval-error)) 221 | ((null (setq p2-output (lispy--eval-python p2))) 222 | (lispy-message lispy-eval-error)) 223 | (t 224 | (concat 225 | (if (string= p1-output "") 226 | "" 227 | (concat p1-output "\n")) 228 | p2-output))))) 229 | (t 230 | (error "unexpected"))))) 231 | (cond 232 | ((string-match "SyntaxError: 'return' outside function\\'" res) 233 | (lispy--eval-python 234 | (concat "__return__ = None\n" 235 | (replace-regexp-in-string 236 | "\\(^ *\\)return" 237 | (lambda (x) (concat (match-string 1 x) "__return__ =")) 238 | str) 239 | "\nprint (repr(__return__))") 240 | t)) 241 | ((string-match "^Traceback.*:" res) 242 | (set-text-properties 243 | (match-beginning 0) 244 | (match-end 0) 245 | '(face error) 246 | res) 247 | (setq lispy-eval-error res) 248 | nil) 249 | ((equal res "") 250 | (setq lispy-eval-error "(ok)") 251 | "") 252 | (t 253 | (replace-regexp-in-string "\\\\n" "\n" res)))))) 254 | 255 | (defun lispy--python-array-to-elisp (array-str) 256 | "Transform a Python string ARRAY-STR to an Elisp string array." 257 | (when (and (stringp array-str) 258 | (not (string= array-str ""))) 259 | (let ((parts (with-temp-buffer 260 | (python-mode) 261 | (insert (substring array-str 1 -1)) 262 | (goto-char (point-min)) 263 | (let (beg res) 264 | (while (< (point) (point-max)) 265 | (setq beg (point)) 266 | (forward-sexp) 267 | (push (buffer-substring-no-properties beg (point)) res) 268 | (skip-chars-forward ", ")) 269 | (nreverse res))))) 270 | (mapcar (lambda (s) 271 | (if (string-match "\\`\"" s) 272 | (read s) 273 | (if (string-match "\\`'\\(.*\\)'\\'" s) 274 | (match-string 1 s) 275 | s))) 276 | parts)))) 277 | 278 | (defun lispy-dir-string< (a b) 279 | (if (string-match "/$" a) 280 | (if (string-match "/$" b) 281 | (string< a b) 282 | t) 283 | (if (string-match "/$" b) 284 | nil 285 | (string< a b)))) 286 | 287 | (defun lispy-python-symbol-bnd () 288 | (let ((bnd (or (bounds-of-thing-at-point 'symbol) 289 | (cons (point) (point))))) 290 | (save-excursion 291 | (goto-char (car bnd)) 292 | (while (progn 293 | (skip-chars-backward " ") 294 | (lispy-after-string-p ".")) 295 | (backward-char 1) 296 | (skip-chars-backward " ") 297 | (if (lispy-after-string-p ")") 298 | (backward-sexp 2) 299 | (backward-sexp))) 300 | (skip-chars-forward " ") 301 | (setcar bnd (point))) 302 | bnd)) 303 | 304 | (defun lispy-python-completion-at-point () 305 | (cond ((looking-back "^\\(import\\|from\\) .*" (line-beginning-position)) 306 | (let* ((line (buffer-substring-no-properties 307 | (line-beginning-position) 308 | (point))) 309 | (str 310 | (format 311 | "import jedi; script=jedi.Script(\"%s\",1,%d); [_x_.name for _x_ in script.completions()]" 312 | line (length line))) 313 | (cands 314 | (lispy--python-array-to-elisp 315 | (lispy--eval-python str))) 316 | (bnd (bounds-of-thing-at-point 'symbol)) 317 | (beg (if bnd (car bnd) (point))) 318 | (end (if bnd (cdr bnd) (point)))) 319 | (list beg end cands))) 320 | ((lispy--in-string-p) 321 | (let* ((bnd-1 (lispy--bounds-string)) 322 | (bnd-2 (or (bounds-of-thing-at-point 'symbol) 323 | (cons (point) (point)))) 324 | (str (buffer-substring-no-properties 325 | (1+ (car bnd-1)) 326 | (1- (cdr bnd-1))))) 327 | (list (car bnd-2) 328 | (cdr bnd-2) 329 | (cl-sort (delete "./" (all-completions str #'read-file-name-internal)) 330 | #'lispy-dir-string<)))) 331 | (t 332 | (let* ((bnd (lispy-python-symbol-bnd)) 333 | (str (buffer-substring-no-properties 334 | (car bnd) (cdr bnd)))) 335 | (when (string-match "\\()\\)[^)]*\\'" str) 336 | (let ((expr (format "__t__ = %s" (substring str 0 (match-end 1))))) 337 | (setq str (concat "__t__" (substring str (match-end 1)))) 338 | (cl-incf (car bnd) (match-end 1)) 339 | (lispy--eval-python expr t))) 340 | (list (car bnd) 341 | (cdr bnd) 342 | (mapcar (lambda (s) 343 | (replace-regexp-in-string 344 | "__t__" "" 345 | (if (string-match "(\\'" s) 346 | (substring s 0 (match-beginning 0)) 347 | s))) 348 | (python-shell-completion-get-completions 349 | (lispy--python-proc) 350 | nil str))))))) 351 | 352 | (defvar lispy--python-arg-key-re "\\`\\(\\(?:\\sw\\|\\s_\\)+\\) ?= ?\\(.*\\)\\'" 353 | "Constant regexp for matching function keyword spec.") 354 | 355 | (defun lispy--python-args (beg end) 356 | (let (res) 357 | (save-excursion 358 | (goto-char beg) 359 | (while (< (point) end) 360 | (forward-sexp) 361 | (while (and (< (point) end) 362 | (not (looking-at ","))) 363 | (forward-sexp)) 364 | (push (buffer-substring-no-properties 365 | beg (point)) 366 | res) 367 | (skip-chars-forward ", \n") 368 | (setq beg (point)))) 369 | (nreverse res))) 370 | 371 | (defun lispy--python-debug-step-in () 372 | (when (looking-at " *(") 373 | ;; tuple assignment 374 | (forward-list 1)) 375 | (re-search-forward "(" (line-end-position)) 376 | (backward-char) 377 | (let* ((p-ar-beg (point)) 378 | (p-ar-end (save-excursion 379 | (forward-list) 380 | (point))) 381 | (p-fn-end (progn 382 | (skip-chars-backward " ") 383 | (point))) 384 | (method-p nil) 385 | (p-fn-beg (progn 386 | (backward-sexp) 387 | (while (eq (char-before) ?.) 388 | (setq method-p t) 389 | (backward-sexp)) 390 | (point))) 391 | (fn (buffer-substring-no-properties 392 | p-fn-beg p-fn-end)) 393 | (args 394 | (lispy--python-args (1+ p-ar-beg) (1- p-ar-end))) 395 | (args (if (and method-p 396 | (string-match "\\`\\(.*?\\)\\.\\([^.]+\\)\\'" fn)) 397 | (cons (match-string 1 fn) 398 | args) 399 | args)) 400 | (args-key (cl-remove-if-not 401 | (lambda (s) 402 | (string-match lispy--python-arg-key-re s)) 403 | args)) 404 | (args-normal (cl-set-difference args args-key)) 405 | (fn-data 406 | (json-read-from-string 407 | (substring 408 | (lispy--eval-python 409 | (format "import inspect, json; json.dumps (inspect.getargspec (%s))" 410 | fn)) 411 | 1 -1))) 412 | (fn-args 413 | (mapcar #'identity (elt fn-data 0))) 414 | (fn-defaults 415 | (mapcar 416 | (lambda (x) 417 | (cond ((null x) 418 | "None") 419 | ((eq x t) 420 | "True") 421 | (t 422 | (prin1-to-string x)))) 423 | (elt fn-data 3))) 424 | (fn-alist 425 | (cl-mapcar #'cons 426 | fn-args 427 | (append (make-list (- (length fn-args) 428 | (length fn-defaults)) 429 | nil) 430 | fn-defaults))) 431 | fn-alist-x dbg-cmd) 432 | (when method-p 433 | (unless (member '("self") fn-alist) 434 | (push '("self") fn-alist))) 435 | (setq fn-alist-x fn-alist) 436 | (dolist (arg args-normal) 437 | (setcdr (pop fn-alist-x) arg)) 438 | (dolist (arg args-key) 439 | (if (string-match lispy--python-arg-key-re arg) 440 | (let ((arg-name (match-string 1 arg)) 441 | (arg-val (match-string 2 arg)) 442 | arg-cell) 443 | (if (setq arg-cell (assoc arg-name fn-alist)) 444 | (setcdr arg-cell arg-val) 445 | (error "\"%s\" is not in %s" arg-name fn-alist))) 446 | (error "\"%s\" does not match the regex spec" arg))) 447 | (when (memq nil (mapcar #'cdr fn-alist)) 448 | (error "Not all args were provided: %s" fn-alist)) 449 | (setq dbg-cmd 450 | (mapconcat (lambda (x) 451 | (format "%s = %s" (car x) (cdr x))) 452 | fn-alist 453 | "; ")) 454 | (if (lispy--eval-python dbg-cmd t) 455 | (progn 456 | (goto-char p-fn-end) 457 | (lispy-goto-symbol fn)) 458 | (goto-char p-ar-beg) 459 | (message lispy-eval-error)))) 460 | 461 | (declare-function deferred:sync! "ext:deferred") 462 | (declare-function jedi:goto-definition "ext:jedi-core") 463 | (declare-function jedi:call-deferred "ext:jedi-core") 464 | 465 | (defun lispy-goto-symbol-python (_symbol) 466 | (save-restriction 467 | (widen) 468 | (let ((res (ignore-errors 469 | (or 470 | (deferred:sync! 471 | (jedi:goto-definition)) 472 | t)))) 473 | (if (member res '(nil "Definition not found.")) 474 | (let* ((symbol (python-info-current-symbol)) 475 | (symbol-re (concat "^def.*" (car (last (split-string symbol "\\." t))))) 476 | (file (lispy--eval-python 477 | (format 478 | "import inspect\nprint(inspect.getsourcefile(%s))" symbol)))) 479 | (cond ((and (equal file "None") 480 | (re-search-backward symbol-re nil t))) 481 | (file 482 | (find-file file) 483 | (goto-char (point-min)) 484 | (re-search-forward symbol-re) 485 | (beginning-of-line)) 486 | (t 487 | (error "Both jedi and inspect failed")))) 488 | (unless (looking-back "def " (line-beginning-position)) 489 | (jedi:goto-definition)))))) 490 | 491 | (defun lispy--python-docstring (symbol) 492 | "Look up the docstring for SYMBOL. 493 | 494 | First, try to see if SYMBOL.__doc__ returns a string in the 495 | current REPL session (dynamic). 496 | 497 | Otherwise, fall back to Jedi (static)." 498 | (let ((dynamic-result (lispy--eval-python (concat symbol ".__doc__")))) 499 | (if (> (length dynamic-result) 0) 500 | (mapconcat #'string-trim-left 501 | (split-string (substring dynamic-result 1 -1) "\\\\n") 502 | "\n") 503 | (require 'jedi) 504 | (plist-get (car (deferred:sync! 505 | (jedi:call-deferred 'get_definition))) 506 | :doc)))) 507 | 508 | (defun lispy-python-middleware-reload () 509 | (interactive) 510 | (setq lispy--python-middleware-loaded-p nil) 511 | (lispy--python-middleware-load)) 512 | 513 | (defun lispy--python-middleware-load () 514 | "Load the custom Python code in \"lispy-python.py\"." 515 | (unless lispy--python-middleware-loaded-p 516 | (let ((r (lispy--eval-python 517 | (format "import imp;lp=imp.load_source('lispy-python','%s');__name__='__repl__'" 518 | (expand-file-name "lispy-python.py" lispy-site-directory))))) 519 | (if r 520 | (setq lispy--python-middleware-loaded-p t) 521 | (lispy-message lispy-eval-error))))) 522 | 523 | (defun lispy--python-arglist (symbol filename line column) 524 | (lispy--python-middleware-load) 525 | (let* ((boundp (lispy--eval-python symbol)) 526 | (code (if boundp 527 | (format "lp.arglist(%s)" symbol) 528 | (format "lp.arglist_jedi(%d, %d, '%s')" line column filename))) 529 | (args (lispy--python-array-to-elisp 530 | (lispy--eval-python 531 | code)))) 532 | (format "%s (%s)" 533 | symbol 534 | (mapconcat #'identity 535 | (delete "self" args) 536 | ", ")))) 537 | 538 | (provide 'le-python) 539 | 540 | ;;; le-python.el ends here 541 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![License GPL 3][badge-license]](http://www.gnu.org/licenses/gpl-3.0.txt) 2 | [![Build Status](https://travis-ci.org/abo-abo/lispy.svg?branch=master)](https://travis-ci.org/abo-abo/lispy) 3 | [![Coverage Status](https://coveralls.io/repos/abo-abo/lispy/badge.svg?branch=master)](https://coveralls.io/r/abo-abo/lispy?branch=master) 4 | [![MELPA](http://melpa.org/packages/lispy-badge.svg)](http://melpa.org/#/lispy) 5 | [![MELPA Stable](http://stable.melpa.org/packages/lispy-badge.svg)](http://stable.melpa.org/#/lispy) 6 | 7 |

8 | lispy logo 10 |

11 | 12 | > short and sweet LISP editing 13 | 14 | 15 | **Table of Contents** 16 | 17 | - [Introduction](#introduction) 18 | - [Relation to vi](#relation-to-vi) 19 | - [Features](#features) 20 | - [Function reference](#function-reference) 21 | - [Getting Started](#getting-started) 22 | - [Installation instructions](#installation-instructions) 23 | - [via MELPA](#via-melpa) 24 | - [via el-get](#via-el-get) 25 | - [Configuration instructions](#configuration-instructions) 26 | - [Customization instructions](#customization-instructions) 27 | - [Operating on lists](#operating-on-lists) 28 | - [How to get into list-editing mode (special)](#how-to-get-into-list-editing-mode-special) 29 | - [Digit keys in special](#digit-keys-in-special) 30 | - [How to get out of special](#how-to-get-out-of-special) 31 | - [List commands overview](#list-commands-overview) 32 | - [Inserting pairs](#inserting-pairs) 33 | - [Reversible commands](#reversible-commands) 34 | - [Keys that modify whitespace](#keys-that-modify-whitespace) 35 | - [Command chaining](#command-chaining) 36 | - [Navigating with `avy`-related commands](#navigating-with-ace-jump-mode-related-commands) 37 | - [Operating on regions](#operating-on-regions) 38 | - [Ways to activate region](#ways-to-activate-region) 39 | - [Move region around](#move-region-around) 40 | - [Switch to the other side of the region](#switch-to-the-other-side-of-the-region) 41 | - [Grow/shrink region](#growshrink-region) 42 | - [Commands that operate on region](#commands-that-operate-on-region) 43 | - [IDE-like features](#ide-like-features) 44 | - [Demos](#demos) 45 | - [[Demo 1: Practice generating code](http://abo-abo.github.io/lispy/demo-1)](#demo-1-practice-generating-codehttpabo-abogithubiolispydemo-1) 46 | - [[Demo 2: The substitution model for procedure application](http://abo-abo.github.io/lispy/demo-2)](#demo-2-the-substitution-model-for-procedure-applicationhttpabo-abogithubiolispydemo-2) 47 | - [[Demo 3: Down the rabbit hole](http://abo-abo.github.io/lispy/demo-3)](#demo-3-down-the-rabbit-holehttpabo-abogithubiolispydemo-3) 48 | - [[Demo 4: Project Euler p100 and Clojure](http://abo-abo.github.io/lispy/demo-4)](#demo-4-project-euler-p100-and-clojurehttpabo-abogithubiolispydemo-4) 49 | - [[Demo 5: ->>ification](http://abo-abo.github.io/lispy/demo-5)](#demo-5--ificationhttpabo-abogithubiolispydemo-5) 50 | - [[Demo 6: cond->if->cond](http://abo-abo.github.io/lispy/demo-6)](#demo-6-cond-if-condhttpabo-abogithubiolispydemo-6) 51 | - [Screencasts](#screencasts) 52 | 53 | 54 | 55 | # Introduction 56 | 57 | This package reimagines Paredit - a popular method to navigate and 58 | edit LISP code in Emacs. 59 | 60 | The killer-feature are the short bindings: 61 | 62 | | command | binding | binding | command 63 | |:-----------------------------|:----------------:|:------------:|:------------------ 64 | |`paredit-forward` | C-M-f | j | `lispy-down` 65 | |`paredit-backward` | C-M-b | k | `lispy-up` 66 | |`paredit-backward-up` | C-M-u | h | `lispy-left` 67 | |`paredit-forward-up` | C-M-n | l | `lispy-right` 68 | |`paredit-raise-sexp` | M-r | r | `lispy-raise` 69 | |`paredit-convolute-sexp` | M-? | C | `lispy-convolute` 70 | |`paredit-forward-slurp-sexp` | C-) | > | `lispy-slurp` 71 | |`paredit-forward-barf-sexp` | C-} | < | `lispy-barf` 72 | |`paredit-backward-slurp-sexp` | C-( | > | `lispy-slurp` 73 | |`paredit-backward-barf-sexp` | C-{ | < | `lispy-barf` 74 | 75 | Most of more than 100 interactive commands that `lispy` provides are 76 | bound to a-z and A-Z in 77 | `lispy-mode`. You can see the full command reference with many 78 | examples [here](http://abo-abo.github.io/lispy/). 79 | 80 | The price for these short bindings is that they are only active when: 81 | 82 | - the point is before an open paren: `(`, `[` or `{` 83 | - the point is after a close paren: `)`, `]` or `}` 84 | - the region is active 85 | 86 | The advantage of short bindings is that you are more likely to use 87 | them. As you use them more, you learn how to combine them, increasing 88 | your editing efficiency. 89 | 90 | To further facilitate building complex commands from smaller commands, 91 | `lispy-mode` binds `digit-argument` to 0-9. For 92 | example, you can mark the third element of the list with 93 | 3m. You can then mark third through fifth element (three 94 | total) with 2> or >>. You can then move the 95 | selection to the last three elements of the list with 99j. 96 | 97 | If you are currently using Paredit, note that `lispy-mode` and 98 | `paredit-mode` can actually coexist with very few conflicts, although 99 | there would be some redundancy. 100 | 101 | ## Relation to vi 102 | 103 | The key binding method is influenced by vi, although this isn't modal 104 | editing *per se*. 105 | 106 | Here's a quote from Wikipedia on how vi works, in case you don't know: 107 | 108 | > vi is a modal editor: it operates in either insert mode (where typed 109 | > text becomes part of the document) or normal mode (where keystrokes 110 | > are interpreted as commands that control the edit session). For 111 | > example, typing i while in normal mode switches the editor to insert 112 | > mode, but typing i again at this point places an "i" character in 113 | > the document. From insert mode, pressing ESC switches the editor 114 | > back to normal mode. 115 | 116 | Here's an illustration of Emacs, vi and lispy bindings for inserting a 117 | char and calling a command: 118 | 119 | | | insert "j" | forward-list 120 | |------------------|:--------------:|:-------------: 121 | |Emacs | j | C-M-n 122 | |vi in insert mode | j | impossible 123 | |vi in normal mode | impossible | j 124 | |lispy | j | j 125 | 126 | Advantages/disadvantages: 127 | 128 | - Emacs can both insert and call commands without switching modes (since it has none), 129 | but the command bindings are long 130 | - vi has short command bindings, but you have to switch modes between inserting and calling commands 131 | - lispy has short command bindings and doesn't need to switch modes 132 | 133 | Of course it's not magic, lispy needs to have normal/insert mode to 134 | perform both functions with j. The difference from vi is 135 | that the mode is **explicit** instead of **implicit** - it's 136 | determined by the point position or the region state: 137 | 138 | - you are in normal mode when the point is before/after paren or the 139 | region is active 140 | - otherwise you are in insert mode 141 | 142 | So people who generally like Emacs bindings (like me) can have the 143 | cake and eat it too (no dedicated insert mode + shorter key bindings). 144 | While people who like vi can still get an experience that's reasonably 145 | close to vi for LISP editing (since vi's line-based approach isn't 146 | very appropriate for LISP anyway). 147 | 148 | But if you ask: 149 | 150 | > What if I want to insert when the point is before/after paren or the region is active? 151 | 152 | The answer is that because of the LISP syntax you don't want to write 153 | this: 154 | 155 | ```cl 156 | j(progn 157 | (forward-char 1))k 158 | ``` 159 | 160 | Also, Emacs does nothing special by default when the region is active 161 | and you press a normal key, so new commands can be called in that 162 | situation. 163 | 164 | ## Features 165 | 166 | - Basic navigation by-list and by-region: 167 | - h moves left 168 | - j moves down 169 | - k moves up 170 | - l moves right 171 | - f steps inside the list 172 | - b moves back in history for all above commands 173 | 174 | - Paredit transformations, callable by plain letters: 175 | - > slurps 176 | - < barfs 177 | - r raises 178 | - C convolutes 179 | - s moves down 180 | - w moves up 181 | - IDE-like features for Elisp, Clojure, Scheme, Common Lisp, Hy, Python and Julia: 182 | - e evals 183 | - E evals and inserts 184 | - g jumps to any tag in the current directory with semantic 185 | - G jumps to any tag in the current file 186 | - M-. jumps to symbol, M-, jumps back 187 | - F jumps to symbol, D jumps back 188 | - C-1 shows documentation in an overlay 189 | - C-2 shows arguments in an overlay 190 | - [Z](http://abo-abo.github.io/lispy/#lispy-edebug-stop) breaks 191 | out of `edebug`, while storing current function's arguments 192 | 193 | Some pictures [here](#ide-like-features). 194 | - Code manipulation: 195 | - i prettifies code (remove extra space, hanging parens ...) 196 | - xi transforms `cond` expression to equivalent `if` expressions 197 | - xc transforms `if` expressions to an equivalent `cond` expression 198 | - xf flattens function or macro call (extract body and substitute arguments) 199 | - xr evals and replaces 200 | - xl turns current `defun` into a `lambda` 201 | - xd turns current `lambda` into a `defun` 202 | - O formats the code into one line 203 | - M formats the code into multiple lines 204 | - Misc. bindings: 205 | - outlines navigation/folding (J, K, I, i) 206 | - narrow/widen (N, W) 207 | - `ediff` (b, B) 208 | - `ert` (T) 209 | - `edebug` (xe) 210 | 211 | ## Function reference 212 | Most functions are cataloged and described at http://abo-abo.github.io/lispy/. 213 | 214 | # Getting Started 215 | ## Installation instructions 216 | ### via MELPA 217 | 218 | It's easiest/recommended to install from [MELPA](http://melpa.org/). 219 | Here's a minimal MELPA configuration for your `~/.emacs`: 220 | 221 | ```cl 222 | (package-initialize) 223 | (add-to-list 'package-archives '("melpa" . "http://melpa.org/packages/")) 224 | ``` 225 | 226 | Afterwards, M-x package-install RET lispy RET (you might 227 | want to M-x package-refresh-contents RET beforehand if 228 | you haven't done so recently). 229 | 230 | ### via el-get 231 | 232 | [el-get](https://github.com/dimitri/el-get) also features a lispy recipe. 233 | Use M-x el-get-install RET lispy RET to install. 234 | 235 | ## Configuration instructions 236 | **Enable lispy automatically for certain modes** 237 | 238 | After installing, you can call M-x lispy-mode for any 239 | buffer with a LISP dialect source. To have `lispy-mode` activated 240 | automatically, use something like this: 241 | 242 | 243 | ```cl 244 | (add-hook 'emacs-lisp-mode-hook (lambda () (lispy-mode 1))) 245 | ``` 246 | 247 | **Enable lispy for `eval-expression`** 248 | 249 | Although I prefer to eval things in `*scratch*`, sometimes 250 | M-: - `eval-expression` is handy. Here's how to use lispy 251 | in the minibuffer during `eval-expression`: 252 | 253 | ```cl 254 | (defun conditionally-enable-lispy () 255 | (when (eq this-command 'eval-expression) 256 | (lispy-mode 1))) 257 | (add-hook 'minibuffer-setup-hook 'conditionally-enable-lispy) 258 | ``` 259 | 260 | ## Customization instructions 261 | 262 | If you want to replace some of the `lispy-mode`'s bindings you can do 263 | it like this: 264 | 265 | ```cl 266 | (eval-after-load "lispy" 267 | `(progn 268 | ;; replace a global binding with own function 269 | (define-key lispy-mode-map (kbd "C-e") 'my-custom-eol) 270 | ;; replace a global binding with major-mode's default 271 | (define-key lispy-mode-map (kbd "C-j") nil) 272 | ;; replace a local binding 273 | (lispy-define-key lispy-mode-map "s" 'lispy-down))) 274 | ``` 275 | 276 | # Operating on lists 277 | 278 | ## How to get into list-editing mode (special) 279 | 280 | The plain keys will call commands when: 281 | - the point is positioned before paren 282 | - the point is positioned after paren 283 | - the region is active 284 | 285 | When one of the first two conditions is true, I say that the point is 286 | special. When the point is special, it's very clear to which sexp the 287 | list-manipulating command will be applied to, what the result be and 288 | where the point should end up afterwards. You can enhance this effect 289 | with `show-paren-mode` or similar. 290 | 291 | Here's an illustration to this effect, with `lispy-clone` (here, `|` 292 | represents the point): 293 | 294 | |before | key | after 295 | |:-------------------|:------------:|:----------------------- 296 | |`(looking-at "(")|` | c | `(looking-at "(")` 297 | | | | `(looking-at "(")|` 298 | 299 | |before | key | after 300 | |:-------------------|:------------:|:----------------------- 301 | |`|(looking-at "(")` | c | `|(looking-at "(")` 302 | | | | ` (looking-at "(")` 303 | 304 | You can use plain Emacs navigation commands to get into special, or you can use 305 | some of the dedicated commands: 306 | 307 | Key Binding | Description 308 | ----------------|----------------------------------------------------------- 309 | ] | `lispy-forward` - move to the end of the closest list, analogous to C-M-n (`forward-list`) 310 | [| `lispy-backward` - move to the start of the closest list, analogous to C-M-p (`backward-list`) 311 | C-3 | `lispy-right` - exit current list forwards, analogous to `up-list` 312 | ) | `lispy-right-nostring` exit current list forwards, but self-insert in strings and comments 313 | 314 | These are the few lispy commands that don't care whether the point is 315 | special or not. Other such bindings are DEL, C-d, C-k. 316 | 317 | Special is useful for manipulating/navigating lists. If you want to 318 | manipulate symbols, use [region selection](#operating-on-regions) 319 | instead. 320 | 321 | ## Digit keys in special 322 | 323 | When special, the digit keys call `digit-argument` which is very 324 | useful since most lispy commands accept a numeric argument. 325 | For instance, 3c is equivalent to ccc (clone sexp 3 times), and 326 | 4j is equivalent to jjjj (move point 4 sexps down). 327 | 328 | Some useful applications are 9l and 9h - they exit list forwards 329 | and backwards respectively at most 9 times which makes them 330 | effectively equivalent to `end-of-defun` and `beginning-of-defun`. Or 331 | you can move to the last sexp of the file with 999j. 332 | 333 | ## How to get out of special 334 | 335 | To get out of the special position, you can use any of the good-old 336 | navigational commands such as C-f or C-n. 337 | Additionally SPC will break out of special to get around the 338 | situation when you have the point between the open parens like this 339 | 340 | (|( 341 | 342 | and want to start inserting; SPC will change the code to 343 | this: 344 | 345 | (| ( 346 | 347 | ## List commands overview 348 | ### Inserting pairs 349 | 350 | Here's a list of commands for inserting [pairs](http://abo-abo.github.io/lispy/#lispy-pair): 351 | 352 | key | command 353 | ------------------|------------------------------------------------------------------- 354 | ( | [`lispy-parens`](http://abo-abo.github.io/lispy/#lispy-parens) 355 | { | [`lispy-braces`](http://abo-abo.github.io/lispy/#lispy-braces) 356 | } | [`lispy-brackets`](http://abo-abo.github.io/lispy/#lispy-brackets) 357 | " | [`lispy-quotes`](http://abo-abo.github.io/lispy/#lispy-quotes) 358 | 359 | ### Reversible commands 360 | 361 | A lot of Lispy commands come in pairs - one reverses the other: 362 | 363 | key | command | key | command 364 | ----------------|--------------------------|----------------------------------|---------------------- 365 | j | `lispy-down` | k | `lispy-up` 366 | s | `lispy-move-down` | w | `lispy-move-up` 367 | > | `lispy-slurp` | < | `lispy-barf` 368 | c | `lispy-clone` | C-d or DEL | 369 | C | `lispy-convolute` | C | reverses itself 370 | d | `lispy-different` | d | reverses itself 371 | M-j | `lispy-split` | + | `lispy-join` 372 | O | `lispy-oneline` | M | `lispy-multiline` 373 | S | `lispy-stringify` | C-u " | `lispy-quotes` 374 | ; | `lispy-comment` | C-u ; | `lispy-comment` 375 | xi | `lispy-to-ifs` | xc | `lispy-to-cond` 376 | 377 | ### Keys that modify whitespace 378 | 379 | These commands handle whitespace in addition to inserting the expected 380 | thing. 381 | 382 | key | command 383 | ----------------|--------------------------- 384 | SPC | `lispy-space` 385 | : | `lispy-colon` 386 | ^ | `lispy-hat` 387 | C-m | `lispy-newline-and-indent` 388 | 389 | ### Command chaining 390 | 391 | Most special commands will leave the point special after they're 392 | done. This allows to chain them as well as apply them 393 | continuously by holding the key. Some useful hold-able keys are 394 | jkf<>cws;. 395 | Not so useful, but fun is /: start it from `|(` position and hold 396 | until all your Lisp code is turned into Python :). 397 | 398 | ### Navigating with `avy`-related commands 399 | 400 | key | command 401 | ----------------|-------------------------- 402 | q | `lispy-ace-paren` 403 | Q | `lispy-ace-char` 404 | a | `lispy-ace-symbol` 405 | H | `lispy-ace-symbol-replace` 406 | - | `lispy-ace-subword` 407 | 408 | q - `lispy-ace-paren` jumps to a "(" character within current 409 | top-level form (e.g. `defun`). It's much faster than typing in the 410 | `avy` binding + selecting "(", and there's less candidates, 411 | since they're limited to the current top-level form. 412 | 413 | a - `lispy-ace-symbol` will let you select which symbol to 414 | mark within current form. This can be followed up with e.g. eval, 415 | describe, follow, raise etc. Or you can simply m to 416 | deactivate the mark and edit from there. 417 | 418 | - - `lispy-ace-subword` is a niche command for a neat combo. Start with: 419 | 420 | (buffer-substring-no-properties 421 | (region-beginning)|) 422 | 423 | Type c, -, b and C-d to get: 424 | 425 | (buffer-substring-no-properties 426 | (region-beginning) 427 | (region-|)) 428 | 429 | Fill `end` to finish the statement. 430 | 431 | # Operating on regions 432 | Sometimes the expression that you want to operate on isn't bounded by parens. 433 | In that case you can mark it with a region and operate on that. 434 | 435 | ## Ways to activate region 436 | While in special: 437 | - Mark a sexp with m - `lispy-mark-list` 438 | - Mark a symbol within sexp a - `lispy-ace-symbol`. 439 | 440 | While not in special: 441 | - C-SPC - `set-mark-command` 442 | - mark a symbol at point with M-m - `lispy-mark-symbol` 443 | - mark containing expression (list or string or comment) with C-M-, - `lispy-mark` 444 | 445 | ## Move region around 446 | 447 | The arrow keys j/k will move the region up/down within the current 448 | list. The actual code will not be changed. 449 | 450 | ## Switch to the other side of the region 451 | 452 | Use d - `lispy-different` to switch between different sides 453 | of the region. The side is important since the grow/shrink operations 454 | apply to current side of the region. 455 | 456 | ## Grow/shrink region 457 | 458 | Use a combination of: 459 | - > - `lispy-slurp` - extend by one sexp from the current side. Use digit 460 | argument to extend by several sexps. 461 | - < - `lispy-barf` - shrink by one sexp from the current side. Use digit 462 | argument to shrink by several sexps. 463 | 464 | The other two arrow keys will mark the parent list of the current region: 465 | 466 | - h - `lispy-left` - mark the parent list with the point on the left 467 | - l - `lispy-right` - mark the parent list with the point on the right 468 | 469 | To do the reverse of the previous operation, i.e. to mark the first 470 | child of marked list, use i - `lispy-tab`. 471 | 472 | ## Commands that operate on region 473 | - m - `lispy-mark-list` - deactivate region 474 | - c - `lispy-clone` - clone region and keep it active 475 | - s - `lispy-move-down` - move region one sexp down 476 | - w - `lispy-move-up` - move region one sexp up 477 | - u - `lispy-undo` - deactivate region and undo 478 | - t - `lispy-teleport` - move region inside the sexp you select with `lispy-ace-paren` 479 | - C - `lispy-convolute` - exchange the order of application of two sexps that contain region 480 | - n - `lispy-new-copy` - copy region as kill without deactivating the mark 481 | - P - `lispy-paste` - replace region with current kill 482 | 483 | # IDE-like features 484 | 485 | These features are specific to the Lisp dialect used. Currently Elisp 486 | and Clojure (via `cider`) are supported. There's also basic 487 | evaluation support for: 488 | 489 | - Scheme (via `geiser`) 490 | - Common lisp (via `slime` or `sly`). 491 | - Hy (via `comint`). 492 | - Python (via `comint` and `jedi`). 493 | - Julia (via `julia-shell`). 494 | 495 | **`lispy-describe-inline`** 496 | 497 | Bound to C-1. Show the doc for the current function inline. 498 | 499 | C-h f is fine, but the extra buffer, and having to navigate to a symbol 500 | is tiresome. C-1 toggles on/off the inline doc for current function. 501 | No extra buffer necessary: 502 | 503 | ![screenshot](https://raw.github.com/abo-abo/lispy/master/images/doc-elisp.png) 504 | 505 | Here's how it looks for Clojure: 506 | 507 | ![screenshot](https://raw.github.com/abo-abo/lispy/master/images/doc-clojure.png) 508 | 509 | **`lispy-arglist-inline`** 510 | 511 | Bound to C-2. Show arguments for current function inline. 512 | 513 | `eldoc-mode` is cool, but it shows you arguments *over there* and 514 | you're writing *over here*!. No problem, C-2 fixes that: 515 | 516 | ![screenshot](https://raw.github.com/abo-abo/lispy/master/images/arglist-elisp.png) 517 | 518 | As you see, normal, &optional and &rest arguments have each a 519 | different face. Here's how it looks for Clojure: 520 | 521 | ![screenshot](https://raw.github.com/abo-abo/lispy/master/images/arglist-clojure.png) 522 | 523 | **`lispy-goto`** 524 | 525 | Bound to g. 526 | 527 | Use completion to select a symbol to jump to from all top-level symbols in the in current directory. 528 | 529 | Works out of the box for Elisp, Scheme and Common Lisp. 530 | [clojure-semantic](https://github.com/kototama/clojure-semantic) is 531 | required for Clojure. 532 | 533 | **`lispy-eval`** 534 | 535 | There's a feature similar to `ipython-notebook`. Evaluating an Emacs 536 | outline will evaluate all of the outline's code and echo the result of 537 | the last expression. When an outline ends with a colon (`:`), the 538 | result will instead be inserted into the buffer. If the evaluation 539 | result changes for whatever reason, it will be replaced after each 540 | subsequent e. 541 | 542 | Python and Julia currently have a slightly better notebook support, 543 | pressing e on the parent outline will evaluate all the 544 | children outlines sequentially. This allows to arrange scripts 545 | hierarchically, with relatively few top-level outlines and relatively 546 | many total outlines. Each outline's output can be examined by adding a 547 | `:` to the title of the outline. 548 | 549 | The following example shows a buffer before and after pressing e. 550 | 551 | ![lispy-python-notebook.png](https://raw.githubusercontent.com/wiki/abo-abo/lispy/images/lispy-python-notebook.png) 552 | 553 | There is one top-level outline, with one level-2 child, which in turn 554 | has a four level-3 children. Three of these children end in `:`, so 555 | their output will be updated after the eval. 556 | 557 | # Demos 558 | 559 | ## [Demo 1: Practice generating code](http://abo-abo.github.io/lispy/demo-1) 560 | ## [Demo 2: The substitution model for procedure application](http://abo-abo.github.io/lispy/demo-2) 561 | ## [Demo 3: Down the rabbit hole](http://abo-abo.github.io/lispy/demo-3) 562 | ## [Demo 4: Project Euler p100 and Clojure](http://abo-abo.github.io/lispy/demo-4) 563 | ## [Demo 5: ->>ification](http://abo-abo.github.io/lispy/demo-5) 564 | ## [Demo 6: cond->if->cond](http://abo-abo.github.io/lispy/demo-6) 565 | 566 | # Screencasts 567 | 568 | - The older stuff can be found on [vimeo](http://vimeo.com/user24828177/videos). 569 | - The newer stuff is on https://www.youtube.com/user/abo5abo/videos. 570 | 571 | [badge-license]: https://img.shields.io/badge/license-GPL_3-green.svg 572 | --------------------------------------------------------------------------------