├── 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 | [](https://travis-ci.org/abo-abo/lispy)
3 | [](https://coveralls.io/r/abo-abo/lispy?branch=master)
4 | [](http://melpa.org/#/lispy)
5 | [](http://stable.melpa.org/#/lispy)
6 |
7 |
8 |
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 | 
504 |
505 | Here's how it looks for Clojure:
506 |
507 | 
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 | 
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 | 
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 | 
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 |
--------------------------------------------------------------------------------