diff --git a/Cookbook.py b/Cookbook.py
new file mode 100644
index 00000000..d28d54b2
--- /dev/null
+++ b/Cookbook.py
@@ -0,0 +1,7 @@
+def test(recipe):
+ return ["pytest test/test_lispy-python.py"]
+
+def typecheck(recipe):
+ return "dmypy run -- lispy-python.py"
+
+# del typecheck
diff --git a/Makefile b/Makefile
index 7a276ea6..44988dd7 100644
--- a/Makefile
+++ b/Makefile
@@ -30,4 +30,4 @@ clojure:
clean:
rm -f *.elc
-.PHONY: all clean elisp check-declare
+.PHONY: all clean elisp check-declare test
diff --git a/le-clojure.el b/le-clojure.el
index f9886c8d..bce663bd 100644
--- a/le-clojure.el
+++ b/le-clojure.el
@@ -68,34 +68,57 @@
"Nil if the Clojure middleware in \"lispy-clojure.clj\" wasn't loaded yet.")
(defun lispy--clojure-process-buffer ()
- (let ((cur-type (cider-repl-type-for-buffer)))
- (car (cider-repls cur-type nil))))
+ (if (or org-src-mode (eq major-mode 'org-mode))
+ (cadr (first (sesman--all-system-sessions 'CIDER)))
+ (let ((cur-type (cider-repl-type-for-buffer)))
+ (car (cider-repls cur-type nil)))))
(defun lispy--clojure-middleware-loaded-p ()
(let ((conn (lispy--clojure-process-buffer)))
(and conn (gethash conn lispy--clojure-middleware-loaded-hash))))
+(defun lispy--clojure-babashka-p ()
+ (ignore-errors (cider--babashka-version)))
+
(defun lispy--eval-clojure-context (e-str)
(cond
- ((eq major-mode 'clojurescript-mode)
+ ((or (eq major-mode 'clojurescript-mode)
+ (lispy--clojure-babashka-p))
e-str)
((string-match-p "#break" e-str)
e-str)
((lispy--clojure-middleware-loaded-p)
- (format (if (memq this-command '(special-lispy-eval
- special-lispy-eval-and-insert))
- "(lispy.clojure/pp (lispy.clojure/reval %S %S :file %S :line %S))"
- "(lispy.clojure/reval %S %S :file %S :line %S)")
- e-str
- (condition-case nil
- (let ((deactivate-mark nil))
- (save-mark-and-excursion
- (lispy--out-backward 1 t)
- (deactivate-mark)
- (lispy--string-dwim)))
- (error ""))
- (buffer-file-name)
- (line-number-at-pos)))
+ (let ((context-str
+ (condition-case nil
+ (let ((deactivate-mark nil))
+ (save-mark-and-excursion
+ (lispy--out-backward 1 t)
+ (deactivate-mark)
+ (lispy--string-dwim)))
+ (error ""))))
+ (when (and (lispy--leftp)
+ (looking-back "(for[ \t\n]*" (line-beginning-position -1)))
+ (let* ((e-str-1 (save-excursion
+ (forward-char 1)
+ (forward-sexp 2)
+ (lispy--string-dwim)))
+ (coll (read (lispy--eval-clojure-1
+ (format "(lispy.clojure/with-shadows (map str %s))" e-str-1)
+ nil)))
+ (idx (lispy--idx-from-list coll))
+ (sym (save-excursion
+ (forward-char 1)
+ (lispy--string-dwim))))
+ (setq e-str (format "%s (nth %s %d)" sym e-str-1 idx))))
+ (format (if (memq this-command '(special-lispy-eval
+ special-lispy-eval-and-insert
+ lispy-eval-current-outline))
+ "(lispy.clojure/pp (lispy.clojure/reval %S %S :file %S :line %S))"
+ "(lispy.clojure/reval %S %S :file %S :line %S)")
+ e-str
+ context-str
+ (buffer-file-name)
+ (line-number-at-pos))))
(t
e-str)))
@@ -118,12 +141,7 @@
(remove-hook 'nrepl-connected-hook
'lispy--clojure-eval-hook-lambda))
-(defvar lispy-cider-jack-in-dependencies
- '(("me.raynes/fs" "1.4.6")
- ("compliment" "0.3.11")
- ("com.cemerick/pomegranate" "0.4.0")
- ("org.tcrawley/dynapath" "0.2.5")
- ("nrepl" "0.8.2")))
+(defvar lispy-cider-jack-in-dependencies nil)
(defvar cider-jack-in-cljs-dependencies)
(defvar cider-jack-in-dependencies)
@@ -193,14 +211,10 @@ Add the standard output to the result."
(or
(and (stringp e-str)
(lispy--eval-clojure-handle-ns e-str))
- (let* (pp
- (stra (if (setq pp (string-match "\\`(lispy.clojure/\\(pp\\|reval\\)" f-str))
- f-str
- f-str))
- (res (lispy--eval-nrepl-clojure stra lispy--clojure-ns))
+ (let* ((res (lispy--eval-nrepl-clojure f-str lispy--clojure-ns))
(status (nrepl-dict-get res "status"))
(res (cond ((or (member "namespace-not-found" status))
- (lispy--eval-nrepl-clojure stra))
+ (lispy--eval-nrepl-clojure f-str))
((member "eval-error" status)
(signal 'eval-error (lispy--clojure-pretty-string
(nrepl-dict-get res "err"))))
@@ -212,7 +226,7 @@ Add the standard output to the result."
(when out
(setq lispy-eval-output
(concat (propertize out 'face 'font-lock-string-face) "\n")))
- (if pp
+ (if (string-match "\\`(lispy.clojure/\\(pp\\|reval\\)" f-str)
(condition-case nil
(string-trim (read val))
(error val))
@@ -386,8 +400,6 @@ Besides functions, handles specials, keywords, maps, vectors and sets."
"Mark the Clojure middleware in \"lispy-clojure.clj\" as not loaded."
(puthash (lispy--clojure-process-buffer) nil lispy--clojure-middleware-loaded-hash))
-(defvar cider-jdk-src-paths)
-
(defun lispy-cider-load-file (filename)
(let ((ns-form (cider-ns-form)))
(cider-map-repls :auto
@@ -400,7 +412,7 @@ Besides functions, handles specials, keywords, maps, vectors and sets."
(file-name-nondirectory filename)
connection)))))
-(defcustom lispy-clojure-middleware-tests t
+(defcustom lispy-clojure-middleware-tests nil
"When non-nil, run the tests from lispy-clojure.clj when loading it."
:type 'boolean
:group 'lispy)
@@ -424,24 +436,19 @@ Besides functions, handles specials, keywords, maps, vectors and sets."
(file-attributes middleware-fname))))
(when (or (null access-time) (time-less-p access-time middleware-access-time))
(setq lispy--clojure-ns "user")
- (save-window-excursion
- (lispy-cider-load-file
- (expand-file-name middleware-fname lispy-site-directory)))
+ (unless (lispy--clojure-babashka-p)
+ (save-window-excursion
+ (lispy-cider-load-file
+ (expand-file-name middleware-fname lispy-site-directory))))
(puthash conn middleware-access-time lispy--clojure-middleware-loaded-hash)
(add-hook 'nrepl-disconnected-hook #'lispy--clojure-middleware-unload)
(when (equal conn-type 'clj)
- (when cider-jdk-src-paths
- (let ((sources-expr
- (format
- "(do \n %s)"
- (mapconcat
- (lambda (p) (format "(cemerick.pomegranate/add-classpath %S)" p))
- cider-jdk-src-paths
- "\n "))))
- (lispy--eval-clojure-cider sources-expr)))
- (when lispy-clojure-middleware-tests
- (lispy-message
- (lispy--eval-clojure-cider "(lispy.clojure/run-lispy-tests)")))))))
+ (let ((test-fname (expand-file-name "lispy-clojure-test.clj"
+ lispy-site-directory)))
+ (when (and lispy-clojure-middleware-tests
+ (file-exists-p test-fname))
+ (lispy-message
+ (lispy--eval-clojure-cider (format "(load-file \"%s\")" test-fname)))))))))
(defun lispy-flatten--clojure (_arg)
"Inline a Clojure function at the point of its call."
@@ -474,10 +481,13 @@ Besides functions, handles specials, keywords, maps, vectors and sets."
(let* ((e-str (format "(lispy.clojure/debug-step-in\n'%s)"
(lispy--string-dwim)))
(str (substring-no-properties
- (lispy--eval-clojure-cider e-str))))
+ (lispy--eval-clojure-1 e-str nil)))
+ (old-session (sesman-current-session 'CIDER)))
(lispy-follow)
(when (string-match "(clojure.core/in-ns (quote \\([^)]+\\))" str)
(setq lispy--clojure-ns (match-string 1 str)))
+ (when (equal (file-name-nondirectory (buffer-file-name)) "lispy-clojure.clj")
+ (sesman-link-session 'CIDER old-session))
(lispy--eval-clojure-cider str)
(lispy-flow 1)))
@@ -563,31 +573,7 @@ Besides functions, handles specials, keywords, maps, vectors and sets."
(setq cands (all-completions (lispy--string-dwim bnd) cands)))
(list (car bnd) (cdr bnd) cands)))
((eq (lispy--clojure-process-type) 'cljs)
- nil)
- ((save-excursion
- (lispy--out-backward 2 t)
- (looking-at "(import"))
- (let* ((prefix (save-excursion
- (lispy--out-backward 1 t)
- (forward-char)
- (thing-at-point 'symbol t)))
- (cands (read (lispy--eval-clojure-cider
- (format
- "(lispy.clojure/complete %S)"
- prefix))))
- (len (1+ (length prefix)))
- (candsa (mapcar (lambda (s) (substring s len)) cands)))
- (when (> (cdr bnd) (car bnd))
- (setq candsa (all-completions (lispy--string-dwim bnd) candsa)))
- (list (car bnd) (cdr bnd) candsa)))
- (t
- (let* ((prefix (lispy--string-dwim bnd))
- (cands (read (lispy--eval-clojure-cider-noerror
- (format
- "(lispy.clojure/complete %S)"
- prefix)))))
- (when cands
- (list (car bnd) (cdr bnd) cands))))))))))
+ nil)))))))
(defun lispy--eval-clojure-cider-noerror (e-str)
(condition-case nil
diff --git a/le-js.el b/le-js.el
new file mode 100644
index 00000000..41a59843
--- /dev/null
+++ b/le-js.el
@@ -0,0 +1,41 @@
+(require 'indium)
+
+(defun lispy--eval-js (str)
+ (let ((r nil))
+ (indium-eval
+ str (lambda (value) (setq r (indium-render-remote-object-to-string value))))
+ (while (not r)
+ (accept-process-output))
+ (substring-no-properties r)))
+
+(defun lispy--eval-js-str ()
+ (if (region-active-p)
+ (lispy--string-dwim)
+ (lispy--string-dwim
+ (lispy-bounds-python-block))))
+
+(defun lispy--js-completion-at-point ()
+ (let* ((prefix (buffer-substring-no-properties
+ (let ((bol (point-at-bol))
+ (prev-delimiter (1+ (save-excursion
+ (re-search-backward "[([:space:]]" nil t)))))
+ (if prev-delimiter
+ (max bol prev-delimiter)
+ bol))
+ (point)))
+ (expression (if (string-match-p "\\." prefix)
+ (replace-regexp-in-string "\\.[^\\.]*$" "" prefix)
+ "this"))
+ (cands nil))
+ (indium-client-get-completion
+ expression
+ indium-debugger-current-frame
+ (lambda (candidates)
+ (setq cands candidates)))
+ (while (null cands)
+ (accept-process-output))
+ (list (- (point) (length (company-grab-symbol)))
+ (point)
+ (mapcar #'identity cands))))
+
+(provide 'le-js)
diff --git a/le-lisp.el b/le-lisp.el
index 5aefb544..ab8061ea 100644
--- a/le-lisp.el
+++ b/le-lisp.el
@@ -47,16 +47,15 @@
(defun lispy--eval-lisp (str)
"Eval STR as Common Lisp code."
(let* ((deactivate-mark nil)
- (result (with-current-buffer (process-buffer (lispy--cl-process))
- (if (lispy--use-sly-p)
- (sly-eval `(slynk:eval-and-grab-output ,str))
- (slime-eval `(swank:eval-and-grab-output ,str))))))
- (if (equal (car result) "")
- (cadr result)
- (concat (propertize (car result)
- 'face 'font-lock-string-face)
- "\n\n"
- (cadr result)))))
+ (result (if (lispy--use-sly-p)
+ (with-current-buffer (process-buffer (lispy--cl-process))
+ (sly-eval `(slynk:eval-and-grab-output ,str)))
+ (slime-eval `(swank:eval-and-grab-output ,str)))))
+ (pcase result
+ (`("" "") "(ok)")
+ (`("" ,val) val)
+ (`(,out ,val)
+ (concat (propertize (string-trim-left out) 'face 'font-lock-string-face) "\n\n" val)))))
(defun lispy--cl-process ()
(unless (lispy--use-sly-p)
diff --git a/le-python.el b/le-python.el
index 6e215754..da7aa50b 100644
--- a/le-python.el
+++ b/le-python.el
@@ -53,11 +53,11 @@ Stripping them will produce code that's valid for an eval."
((and (looking-at lispy-outline)
(looking-at lispy-outline-header))
(lispy--bounds-outline))
- ((looking-at "@")
+ ((and (looking-at "@") (bolp))
(setq bnd (cons (point)
(save-excursion
- (forward-sexp)
- (skip-chars-forward "[ \t\n]")
+ (re-search-forward "^def" nil t)
+ (goto-char (match-beginning 0))
(cdr (lispy-bounds-python-block))))))
((setq bnd (lispy-bounds-python-block)))
((bolp)
@@ -88,37 +88,45 @@ Stripping them will produce code that's valid for an eval."
count))
(defun lispy-extended-eval-str (bnd)
- (let ((lp (lispy--count-regex "(" bnd))
- (rp (lispy--count-regex ")" bnd)))
- (save-excursion
- (goto-char (cdr bnd))
- (while (< rp lp)
- (re-search-forward "[()]" nil t)
- (cond ((string= (match-string 0) "(")
- (cl-incf lp))
- ((string= (match-string 0) ")")
- (cl-incf rp))
- (t
- (error "Unexpected"))))
- (if (lispy-after-string-p ")")
- (let ((end (point)))
- (save-excursion
- (forward-sexp -1)
- (concat (buffer-substring-no-properties
- (car bnd) (point))
- (replace-regexp-in-string
- "[\\]*\n[\t ]*" " "
- (replace-regexp-in-string
- "^ *#.*$" ""
- (buffer-substring-no-properties
- (point) end))))))
- (buffer-substring-no-properties (car bnd) (point))))))
+ "This function should strip python comments.
+So that we can make multi-line expression into a single-line one.
+It didn't work great."
+ (buffer-substring-no-properties (car bnd) (cdr bnd))
+ ;; (let ((lp (lispy--count-regex "(" bnd))
+ ;; (rp (lispy--count-regex ")" bnd)))
+ ;; (save-excursion
+ ;; (goto-char (cdr bnd))
+ ;; (while (< rp lp)
+ ;; (re-search-forward "[()]" nil t)
+ ;; (cond ((string= (match-string 0) "(")
+ ;; (cl-incf lp))
+ ;; ((string= (match-string 0) ")")
+ ;; (cl-incf rp))
+ ;; (t
+ ;; (error "Unexpected"))))
+ ;; (if (lispy-after-string-p ")")
+ ;; (let ((end (point)))
+ ;; (save-excursion
+ ;; (forward-sexp -1)
+ ;; (concat (buffer-substring-no-properties
+ ;; (car bnd) (point))
+ ;; (replace-regexp-in-string
+ ;; "[\\]*\n[\t ]*" " "
+ ;; (replace-regexp-in-string
+ ;; " *#.*[^\"]$" ""
+ ;; (buffer-substring-no-properties
+ ;; (point) end))))))
+ ;; (buffer-substring-no-properties (car bnd) (point)))))
+ )
(defun lispy-eval-python-str (&optional bnd)
(let* ((bnd (or bnd (lispy-eval-python-bnd)))
(str1 (lispy-trim-python
(lispy-extended-eval-str bnd)))
- (str1.5 (replace-regexp-in-string "^ *#[^\n]+\n" "" str1))
+ (str1.4 (if (string-match-p "\\`\\(\\w\\|\\s_\\)+ :=" str1)
+ (replace-regexp-in-string ":=" "=" str1)
+ str1))
+ (str1.5 (replace-regexp-in-string "^ *#[^\n]+\n" "" str1.4))
;; (str2 (replace-regexp-in-string "\\\\\n +" "" str1.5))
;; (str3 (replace-regexp-in-string "\n *\\([])}]\\)" "\\1" str2))
;; (str4 (replace-regexp-in-string "\\([({[,]\\)\n +" "\\1" str3))
@@ -168,10 +176,41 @@ Stripping them will produce code that's valid for an eval."
(end-of-line 2)))
(point)))))
-(defvar-local lispy-python-proc nil)
+(defvar-local lispy-python-buf nil)
(declare-function mash-make-shell "ext:mash")
+(defvar-local python-shell--interpreter nil)
+(defvar-local python-shell--interpreter-args nil)
+
+(define-minor-mode lispy-python-interaction-mode
+ "Minor mode for eval-ing Python code."
+ :group 'lispy
+ (when lispy-python-interaction-mode
+ (when python-shell--parent-buffer
+ (python-util-clone-local-variables python-shell--parent-buffer))
+ (setq-local indent-tabs-mode nil)
+ (setq-local python-shell--prompt-calculated-input-regexp nil)
+ (setq-local python-shell--block-prompt nil)
+ (setq-local python-shell--prompt-calculated-output-regexp nil)
+ (python-shell-prompt-set-calculated-regexps)
+ (setq comint-prompt-regexp python-shell--prompt-calculated-input-regexp)
+ (setq-local comint-prompt-read-only t)
+ (setq mode-line-process '(":%s"))
+ (setq-local comint-output-filter-functions
+ '(ansi-color-process-output
+ python-shell-comint-watch-for-first-prompt-output-filter
+ python-comint-postoutput-scroll-to-bottom
+ comint-watch-for-password-prompt))
+ (setq-local compilation-error-regexp-alist python-shell-compilation-regexp-alist)
+ (add-hook 'completion-at-point-functions
+ #'python-shell-completion-at-point nil 'local)
+ (define-key inferior-python-mode-map "\t"
+ 'python-shell-completion-complete-or-indent)
+ (make-local-variable 'python-shell-internal-last-output)
+ (compilation-shell-minor-mode 1)
+ (python-pdbtrack-setup-tracking)))
+
(defun lispy-set-python-process-action (x)
(when (and current-prefix-arg (consp x))
(let* ((process (cdr x))
@@ -180,20 +219,32 @@ Stripping them will produce code that's valid for an eval."
(sit-for 0.01)
(kill-buffer buffer)
(setq x (car x))))
- (setq lispy-python-proc
- (cond ((consp x)
- (cdr x))
- ((require 'mash-python nil t)
- (save-window-excursion
- (get-buffer-process
- (mash-make-shell x 'mash-new-lispy-python))))
- (t
- (lispy--python-proc (concat "lispy-python-" x)))))
- (unless (lispy--eval-python "lp")
- (lispy-python-middleware-reload)))
+ (let ((buf (cond ((consp x)
+ (process-buffer (cdr x)))
+ ((require 'mash-python nil t)
+ (save-window-excursion
+ (mash-make-shell x 'mash-new-lispy-python)))
+ (t
+ (process-buffer
+ (lispy--python-proc (concat "lispy-python-" x)))))))
+
+ (setq lispy-python-buf buf)
+ (with-current-buffer lispy-python-buf
+ (lispy-python-interaction-mode)
+ (setq lispy--python-middleware-file
+ (if (file-name-absolute-p lispy-python-middleware-file)
+ lispy-python-middleware-file
+ (expand-file-name "lispy-python.py" lispy-site-directory)))
+ (setq lispy-python-buf buf)))
+ (let ((lp (ignore-errors (lispy--eval-python-plain "lp"))))
+ (unless (and lp (string-match-p "module 'lispy-python'" lp))
+ (lispy-python-middleware-reload))))
(defvar lispy-python-process-regexes
- '("^lispy-python-\\(.*\\)" "\\`\\(Python\\)\\'" "\\`\\(comint\\)\\'")
+ '("^lispy-python-\\(.*\\)" "\\`\\(Python\\)\\'"
+ "\\`\\(comint.*\\)\\'"
+ "\\`\\(shell.*\\)\\'"
+ "\\`\\(gud-\\(?:pdb\\|python\\)\\)\\'")
"List of regexes for process buffers that run Python.")
(defun lispy-short-process-name (x)
@@ -203,7 +254,10 @@ Stripping them will produce code that's valid for an eval."
(mapcar
(lambda (re)
(when (string-match re pname)
- (match-string 1 pname)))
+ (let ((m (match-string 1 pname)))
+ (if (string-match-p "comint\\|shell" m)
+ (buffer-name (process-buffer x))
+ m))))
lispy-python-process-regexes)))))
(defvar lispy-override-python-binary nil
@@ -228,33 +282,68 @@ it at one time."
(read-string "python binary: "))))
(ivy-read (if arg "Restart process: " "Process: ") process-names
:action #'lispy-set-python-process-action
- :preselect (when (process-live-p lispy-python-proc)
- (lispy-short-process-name lispy-python-proc))
+ :preselect (when (process-live-p (get-buffer-process lispy-python-buf))
+ (lispy-short-process-name (get-buffer-process lispy-python-buf)))
:caller 'lispy-set-python-process)))
(defvar lispy--python-middleware-loaded-p nil
"Nil if the Python middleware in \"lispy-python.py\" wasn't loaded yet.")
+(defvar lispy-python-middleware-file "lispy-python.py")
+
+(defvar lispy--python-middleware-file "lispy-python.py")
+
+(defvar lispy-python-init-file (expand-file-name "~/git/site-python/init.py"))
+
+(defvar lispy--python-init-file nil)
+
+(defun lispy--python-poetry-name ()
+ (let ((pyproject (expand-file-name "pyproject.toml" (counsel-locate-git-root))))
+ (and (file-exists-p pyproject)
+ (not (equal python-shell-interpreter "python"))
+ (with-current-buffer (find-file-noselect pyproject)
+ (goto-char (point-min))
+ (when (re-search-forward "\\[tool.poetry\\]\nname *= *\"\\([^\"]+\\)\"" nil t)
+ (match-string-no-properties 1))))))
+
+(defun lispy--python-proc-name ()
+ (or (and (process-live-p (get-buffer-process lispy-python-buf))
+ (process-name (get-buffer-process lispy-python-buf)))
+ (let ((name
+ (or (lispy--python-poetry-name)
+ (if (string-match "\\(?::python \\|python_\\)\\(.*\\)\\'" python-shell-interpreter-args)
+ (match-string 1 python-shell-interpreter-args)
+ "default"))))
+ (concat "lispy-python-" name))))
+
(defun lispy--python-proc (&optional name)
(let* ((proc-name (or name
- (and (process-live-p lispy-python-proc)
- lispy-python-proc)
- "lispy-python-default"))
+ (lispy--python-proc-name)))
(process (get-process proc-name)))
(if (process-live-p process)
process
(let* ((python-shell-font-lock-enable nil)
(inferior-python-mode-hook nil)
+ (poetry-name (lispy--python-poetry-name))
(python-shell-interpreter
- (cond
- ((save-excursion
- (goto-char (point-min))
- (looking-at "#!\\(?:/usr/bin/env \\)\\(.*\\)$"))
- (match-string-no-properties 1))
- ((file-exists-p python-shell-interpreter)
- (expand-file-name python-shell-interpreter))
- (t
- python-shell-interpreter)))
+ (if poetry-name
+ "poetry"
+ python-shell-interpreter))
+ (python-shell-interpreter-args
+ (if poetry-name
+ "run python"
+ python-shell-interpreter-args))
+ ;; (python-shell-interpreter
+ ;; (cond
+ ;; ((and (file-exists-p python-shell-interpreter)
+ ;; (not (file-directory-p python-shell-interpreter)))
+ ;; (expand-file-name python-shell-interpreter))
+ ;; ((save-excursion
+ ;; (goto-char (point-min))
+ ;; (looking-at "#!\\(?:/usr/bin/env \\)\\(.*\\)$"))
+ ;; (match-string-no-properties 1))
+ ;; (t
+ ;; python-shell-interpreter)))
(python-binary-name
(or lispy-override-python-binary
(concat
@@ -264,109 +353,148 @@ it at one time."
" "
python-shell-interpreter-args)))
(buffer
- (let ((python-shell-completion-native-enable nil))
+ (let ((python-shell-completion-native-enable nil)
+ (default-directory (if poetry-name
+ (counsel-locate-git-root)
+ default-directory)))
(python-shell-make-comint
python-binary-name proc-name nil nil))))
+ (setq lispy--python-middleware-file
+ (if (file-name-absolute-p lispy-python-middleware-file)
+ lispy-python-middleware-file
+ (expand-file-name "lispy-python.py" lispy-site-directory)))
+ (setq lispy--python-init-file lispy-python-init-file)
(setq process (get-buffer-process buffer))
+ (sit-for 0.1)
+ (unless (process-live-p process)
+ (pop-to-buffer buffer)
+ (user-error "Could not start %s" python-binary-name))
(with-current-buffer buffer
(python-shell-completion-native-turn-on)
- (setq lispy-python-proc process)
+ (setq lispy-python-buf buffer)
(lispy-python-middleware-reload)))
process)))
(defun lispy--python-print (str)
(format
(if (and (memq this-command '(pspecial-lispy-eval lispy-eval))
- (null current-prefix-arg))
- "lp.pprint(%s)"
- "print(repr(%s))")
+ (memq current-prefix-arg '(nil)))
+ "lp.pprint((%s))"
+ "print(repr((%s)))")
str))
+(defun lispy--py-to-el (py)
+ (read (lispy--eval-python-plain (format "lp.print_elisp(%s)" py))))
+
+(defun lispy--python-nth (py-lst)
+ (let (;; (repr (condition-case nil
+ ;; (read (lispy--eval-python-plain (format "print_elisp(%s)" py-expr)))
+ ;; (error
+ ;; (lispy-message "Eval-error: %s" py-expr))))
+
+ (len (lispy--py-to-el (format "len(list(%s))" py-lst)))
+ (repr (ignore-errors (lispy--py-to-el (format "list(%s)" py-lst)))))
+ (read
+ (ivy-read
+ "idx: "
+ (if repr
+ (cl-mapcar (lambda (x i)
+ (concat (number-to-string i)
+ " "
+ (cond ((listp x)
+ (mapconcat
+ (lambda (y) (if (stringp y) y (prin1-to-string y)))
+ x " "))
+ ((stringp x)
+ x)
+ (t
+ (prin1-to-string x)))))
+ repr
+ (number-sequence 0 (1- len)))
+ (mapcar #'number-to-string (number-sequence 0 (1- len))))))))
+
+
(defun lispy--python-nth-element (str single-line-p)
"Check if STR is of the form \"ITEM in ARRAY_LIKE\".
If so, return an equivalent of ITEM = ARRAY_LIKE[IDX]; ITEM."
(when (and single-line-p
- (string-match "\\`\\([A-Z_a-z0-9]+\\|\\(?:([^\n]+)\\)\\) in \\(.*\\)\\'" str)
+ (string-match "\\`\\([A-Z_a-z0-9, ]+\\|\\(?:([^\n]+)\\)\\) in \\(.*\\)\\'" str)
(not (save-excursion (beginning-of-line) (looking-at " *if"))))
(let* ((vars (match-string 1 str))
(val (match-string 2 str))
- (len (read (lispy--eval-python (format "len(list(%s))" val) t)))
- (repr (ignore-errors (read (lispy--eval-python (format "lp.print_elisp(%s)" val) t))))
- (idx
- (read
- (ivy-read
- "idx: "
- (if repr
- (cl-mapcar (lambda (x i)
- (concat (number-to-string i)
- " "
- (cond ((listp x)
- (mapconcat
- (lambda (y) (if (stringp y) y (prin1-to-string y)))
- x " "))
- ((keywordp x)
- (prin1-to-string x))
- (t
- x))))
- repr
- (number-sequence 0 (1- len)))
- (mapcar #'number-to-string (number-sequence 0 (1- len))))))))
+ (idx (lispy--python-nth val)))
(format "%s = list (%s)[%s]\nprint ((%s))"
vars val idx vars))))
(defun lispy--python-eval-string-dwim (str)
(setq str (string-trim str))
- (when (string-match-p "\\`super()" str)
- (let* ((cls (car (split-string (python-info-current-defun) "\\."))))
- (setq str (replace-regexp-in-string
- "super()" (format "super(%s, self)" cls) str))))
(let ((single-line-p (= (cl-count ?\n str) 0)))
(cond
- ((string-match "^\\[" str)
- (format "__last__ = %s\n%s"
- str (lispy--python-print "__last__")))
- ((string-match "\\`\\(\\(?:\\sw\\|\\s_\\)+\\)\\'" str)
- (lispy--python-print (match-string 1 str)))
- ((and (or (string-match "\\`\\(\\(?:[., ]\\|\\sw\\|\\s_\\|[][]\\)+\\) += " str)
- (string-match "\\`\\(([^)]+)\\) *=[^=]" str)
- (string-match "\\`\\(\\(?:\\sw\\|\\s_\\)+ *\\[[^]]+\\]\\) *=[^=]" str))
- (save-match-data
- (or single-line-p
- (and (not (string-match-p "lp\\." str))
- (equal (lispy--eval-python
- (format "x=lp.is_assignment(\"\"\"%s\"\"\")\nprint (x)" str)
- t)
- "True")))))
- (concat str "\n" (lispy--python-print (match-string 1 str))))
- ((lispy--python-nth-element str single-line-p))
- ((string-match "\\`def \\([a-zA-Z_0-9]+\\)\\s-*(\\s-*self" str)
- (let ((qual-name (python-info-current-defun)))
- (concat str
- "\n"
- (format "lp.rebind(%s, fname='%s', line=%d)"
- qual-name
- (buffer-file-name)
- (line-number-at-pos)))))
- (t
- str))))
+ ((string-match "\\`@pytest.mark.parametrize([^\"]+\"\\([^\"]+\\)\",\\(.*?\\)[, ]*)\\'" str)
+ (let* ((vars (match-string 1 str))
+ (vals (match-string 2 str))
+ (idx (lispy--python-nth vals)))
+ (format "%s = %s[%s]\nprint((%s))" vars vals idx vars)))
+ ((string-match-p "\"\"\"" str)
+ str)
+ ((string-match "^\\[" str)
+ (format "__last__ = %s\n%s"
+ str (lispy--python-print "__last__")))
+ ((string-match "\\`\\(\\(?:\\sw\\|\\s_\\)+\\)\\'" str)
+ (lispy--python-print (match-string 1 str)))
+ ((and (or (string-match "\\`\\(\\(?:[.,* ]\\|\\sw\\|\\s_\\|[][]\\)+\\) += " str)
+ (string-match "\\`\\(([^)]+)\\) *=[^=]" str)
+ (string-match "\\`\\(\\(?:\\sw\\|\\s_\\)+ *\\[[^]]+\\]\\) *=[^=]" str)
+ (string-match "\\`\\(\\(?:\\sw\\|\\s_\\)+\\)\\(:[^:=]+\\) *=" str))
+ (save-match-data
+ (or single-line-p
+ (and (not (string-match-p "lp\\." str))
+ (equal (ignore-errors
+ (lispy--eval-python
+ (format "x=lp.is_assignment(\"\"\"%s\"\"\")\nprint (x)" str)
+ t))
+ "True")))))
+ (concat str "\n" (lispy--python-print (match-string 1 str))))
+ ((lispy--python-nth-element str single-line-p))
+ ((string-match "\\`def \\([a-zA-Z_0-9]+\\)\\s-*(\\s-*self" str)
+ (let ((qual-name (python-info-current-defun)))
+ (concat str
+ "\n"
+ (format "lp.rebind(%s, fname='%s', line=%d)"
+ qual-name
+ (buffer-file-name)
+ (line-number-at-pos)))))
+ ((string-match "\\`\\([^if].*\\) as \\(\\(?:\\sw\\|\\s_\\)+\\)\\'" str)
+ (let ((val (match-string 1 str))
+ (var (match-string 2 str)))
+ (format "%s = %s.__enter__()" var val)))
+ ((eq current-prefix-arg 2)
+ (lispy--python-print str))
+ (t
+ str))))
(declare-function lpy-switch-to-shell "ext:lpy")
-(defun lispy--eval-python (str &optional plain)
+(defun lispy--eval-python-old (str &optional plain)
"Eval STR as Python code."
(let ((single-line-p (= (cl-count ?\n str) 0)))
(unless plain
(setq str (lispy--python-eval-string-dwim str))
+ ;; bind __file__
(when (string-match "__file__" str)
- (lispy--eval-python (format "__file__ = '%s'\n" (buffer-file-name)) t))
+ (lispy--eval-python-plain (format "__file__ = '%s'\n" (buffer-file-name))))
+ ;; eliminate return
(when (and single-line-p (string-match "\\`return \\(.*\\)\\'" str))
(setq str (match-string 1 str))))
(let ((res
(cond ((or single-line-p
(string-match "\n .*\\'" str)
(string-match "\"\"\"" str))
- (python-shell-send-string-no-output
- str (lispy--python-proc)))
+ (replace-regexp-in-string
+ "
" ""
+ (python-shell-send-string-no-output
+ str (lispy--python-proc))))
+ ;; split last line
((string-match "\\`\\([\0-\377[:nonascii:]]*\\)\n\\([^\n]*\\)\\'" str)
(let* ((p1 (match-string 1 str))
(p2 (match-string 2 str))
@@ -374,65 +502,174 @@ If so, return an equivalent of ITEM = ARRAY_LIKE[IDX]; ITEM."
p1 (lispy--python-proc)))
p2-output)
(cond
- ((string-match-p "SyntaxError:\\|error:" p1-output)
- (python-shell-send-string-no-output
- str (lispy--python-proc)))
- ((null p1-output)
- (signal 'eval-error ""))
- ((null (setq p2-output (lispy--eval-python p2)))
- (signal 'eval-error ""))
- (t
- (concat
- (if (string= p1-output "")
- ""
- (concat p1-output "\n"))
- p2-output)))))
+ ((string-match-p "SyntaxError:\\|error:" p1-output)
+ (python-shell-send-string-no-output
+ str (lispy--python-proc)))
+ ((null p1-output)
+ (signal 'eval-error ""))
+ ((null (setq p2-output (lispy--eval-python p2)))
+ (signal 'eval-error ""))
+ (t
+ (concat
+ (if (string= p1-output "")
+ ""
+ (concat p1-output "\n"))
+ p2-output)))))
(t
(error "unexpected")))))
(cond
- ((string-match "SyntaxError: 'return' outside function\\'" res)
- (lispy--eval-python
- (concat "__return__ = None\n"
- (replace-regexp-in-string
- "\\(^ *\\)return\\(.*\\)"
- (lambda (x)
- (concat
- (match-string 1 x)
- "__return__ ="
- (if (= 0 (length (match-string 2 x)))
- " None"
- (match-string 2 x))))
- str)
- "\n"
- (lispy--python-print "__return__"))
- t))
- ((string-match "^RuntimeError: break$" res)
- (lpy-switch-to-shell)
- (goto-char (point-max))
- (insert "lp.pm()")
- (comint-send-input)
- "breakpoint")
- ((string-match "^Traceback.*:" res)
- (set-text-properties
- (match-beginning 0)
- (match-end 0)
- '(face error)
- res)
- (signal 'eval-error res))
- ((equal res "")
- (setq lispy-eval-output "(ok)")
- "")
- ((string-match-p "^<\\(?:map\\|filter\\|generator\\|enumerate\\|zip\\) object" res)
- (let ((last (car (last (split-string str "\n")))))
- (cond ((string-match "\\`lp.pprint(\\(.*\\))\\'" last)
- (setq str (match-string 1 last)))
- ((string-match "\\`print(repr(\\(.*\\)))\\'" last)
- (setq str (match-string 1 last)))))
- (lispy--eval-python (format "list(%s)" str) t))
- ((string-match-p "SyntaxError:" res)
- (signal 'eval-error res))
- (t
- (replace-regexp-in-string "\\\\n" "\n" res))))))
+ ((string-match "SyntaxError: 'return' outside function\\'" res)
+ (lispy--eval-python
+ (concat "__return__ = None\n"
+ (replace-regexp-in-string
+ "\\(^ *\\)return\\(.*\\)"
+ (lambda (x)
+ (concat
+ (match-string 1 x)
+ "__return__ ="
+ (if (= 0 (length (match-string 2 x)))
+ " None"
+ (match-string 2 x))))
+ str)
+ "\n"
+ (lispy--python-print "__return__"))
+ t))
+ ((string-match "^RuntimeError: break$" res)
+ (lpy-switch-to-shell)
+ (goto-char (point-max))
+ (insert "lp.pm()")
+ (sit-for 0.1)
+ (comint-send-input)
+ "breakpoint")
+ ((string-match "^Traceback.*:" res)
+ (set-text-properties
+ (match-beginning 0)
+ (match-end 0)
+ '(face error)
+ res)
+ (signal 'eval-error res))
+ ((equal res "")
+ (setq lispy-eval-output "(ok)")
+ "")
+ ((string-match-p "^<\\(?:map\\|filter\\|generator\\|enumerate\\|zip\\) object" res)
+ (let ((last (car (last (split-string str "\n")))))
+ (if (cond ((string-match "\\`lp.pprint(\\(.*\\))\\'" last)
+ (setq str (match-string 1 last)))
+ ((string-match "\\`print(repr(\\(.*\\)))\\'" last)
+ (setq str (match-string 1 last))))
+ (lispy--eval-python-plain (format "%s = list(%s)\nlp.pprint(%s)" str str str))
+ (lispy--eval-python-plain (format "dbg = list(%s)\nlp.pprint(dbg)" str str str)))))
+ ((string-match-p "SyntaxError:" res)
+ (signal 'eval-error res))
+ (t
+ (replace-regexp-in-string "\\\\n" "\n" res))))))
+
+(defun lispy--dict (&rest plist)
+ (let (k v r)
+ (while (setq k (pop plist))
+ (setq v (pop plist))
+ (push (format
+ "'%s': %s"
+ (cond ((keywordp k)
+ (substring (symbol-name k) 1))
+ ((stringp k)
+ k
+ k)
+ (t
+ (error "Unexpected")))
+ (cond ((eq v 't)
+ "True")
+ ((eq v nil)
+ "None")
+ ((stringp v)
+ (format "'%s'" v))
+ (t
+ (prin1-to-string v)))) r))
+ (concat "{"
+ (mapconcat #'identity (nreverse r) ",")
+ "}")))
+
+(defun lispy--python-nth-1 (cands)
+ (let ((len (length cands)))
+ (read
+ (ivy-read
+ "idx: "
+ (cl-mapcar
+ (lambda (x i)
+ (concat
+ (number-to-string i)
+ " "
+ (cond ((listp x)
+ (mapconcat
+ (lambda (y) (if (stringp y) y (prin1-to-string y)))
+ x " "))
+ ((stringp x)
+ x)
+ (t
+ (prin1-to-string x)))))
+ cands
+ (number-sequence 0 (1- len)))))))
+
+(defun lispy--eval-python-plain (str)
+ (python-shell-send-string-no-output
+ str (lispy--python-proc)))
+
+(defun lispy--eval-python (str &optional use-in-expr)
+ (setq lispy-eval-output nil)
+ (let* ((echo (if (eq current-prefix-arg 2) nil t))
+ (fstr
+ (cond
+ ((eq current-prefix-arg 3)
+ (format "lp.eval_to_json(\"\"\"lp.select_item(\"%s\", 0)\"\"\")" str))
+ ((or (string-match-p ".\n+." str) (string-match-p "\"\"\"" str))
+ (let ((temp-file-name (python-shell--save-temp-file str)))
+ (format "lp.eval_to_json('', %s)"
+ (lispy--dict
+ :code temp-file-name
+ :fname (buffer-file-name)
+ :echo echo))))
+ (t
+ (format "lp.eval_to_json(\"\"\"%s \"\"\", %s)"
+ (replace-regexp-in-string "\\\\n" "\\\\n" str nil t)
+ (lispy--dict :fname (buffer-file-name)
+ :echo echo
+ :use-in-expr use-in-expr)))))
+ (rs (python-shell-send-string-no-output
+ fstr
+ (lispy--python-proc)))
+ (extra-out (and (string-match "^{" rs) (substring rs 0 (match-beginning 0))))
+ (res (json-parse-string
+ (substring rs (match-beginning 0)) :object-type 'plist :null-object nil))
+ (val (plist-get res :res))
+ (binds (plist-get res :binds))
+ (out (concat extra-out (plist-get res :out)))
+ (err (plist-get res :err)))
+ (when (eq current-prefix-arg 3)
+ (kill-new fstr))
+ (if err
+ (signal 'eval-error (concat out err))
+ (unless (equal out "")
+ (setq lispy-eval-output
+ (concat (propertize out 'face 'font-lock-string-face) "\n")))
+ (cond
+ ((string= val "'select'")
+ (setq lispy-eval-output nil)
+ (let* ((cands (read out))
+ (idx (lispy--python-nth-1 cands)))
+ (lispy--eval-python-plain
+ (format "lp.select_item(\"\"\"%s\"\"\", %d)" str idx))))
+ ((and val (not (string= val "'unset'")))
+ (if (string-prefix-p "\"" val)
+ (read val)
+ val))
+ (binds
+ (mapconcat
+ (lambda (x)
+ (concat (substring (symbol-name (car x)) 1) " = " (cadr x)))
+ (seq-partition binds 2)
+ "\n"))
+ (t
+ "(ok)")))))
(defun lispy--python-array-to-elisp (array-str)
"Transform a Python string ARRAY-STR to an Elisp string array."
@@ -486,7 +723,9 @@ If so, return an equivalent of ITEM = ARRAY_LIKE[IDX]; ITEM."
(point)))
(defun lispy-python-completion-at-point ()
- (cond ((looking-back "^\\(import\\|from\\) .*" (line-beginning-position))
+ (cond ((save-excursion
+ (back-to-indentation)
+ (looking-at "\\(import\\|from\\) .*"))
(let* ((line (buffer-substring-no-properties
(line-beginning-position)
(point)))
@@ -494,7 +733,7 @@ If so, return an equivalent of ITEM = ARRAY_LIKE[IDX]; ITEM."
(format "lp.jedi_completions('%s')" line))
(cands
(lispy--python-array-to-elisp
- (lispy--eval-python str)))
+ (lispy--eval-python-plain str)))
(bnd (bounds-of-thing-at-point 'symbol))
(beg (if bnd (car bnd) (point)))
(end (if bnd (cdr bnd) (point))))
@@ -506,35 +745,40 @@ If so, return an equivalent of ITEM = ARRAY_LIKE[IDX]; ITEM."
(cons (point)
(lispy--python-beginning-of-object))))
(str (lispy--string-dwim bnd))
- (keys (read (lispy--eval-python
+ (keys (read (lispy--eval-python-plain
(format "lp.print_elisp(%s.keys())" str)))))
(list (point) (point)
(if (> (length quote_str) 0)
keys
- (mapcar (lambda (s) (concat "\"" s "\"")) keys)))))
+ (mapcar
+ (lambda (s)
+ (if (stringp s)
+ (concat "\"" s "\"")
+ (prin1-to-string s)))
+ keys)))))
((lispy-complete-fname-at-point))
(t
(let* ((bnd (lispy-python-symbol-bnd))
(str (buffer-substring-no-properties
- (car bnd) (cdr bnd))))
+ (car bnd) (cdr bnd)))
+ (str-com str))
(when (string-match "\\`\\(.*\\)\\.[^.]*\\'" str)
(let ((expr (format "__t__ = %s" (substring str 0 (match-end 1)))))
- (setq str (concat "__t__" (substring str (match-end 1))))
- (cl-incf (car bnd) (match-end 1))
- (lispy--eval-python expr t)))
+ (setq str-com (concat "__t__" (substring str (match-end 1))))
+ (cl-incf (car bnd) (1+ (- (match-end 1) (match-beginning 0))))
+ (lispy--eval-python-plain expr)))
(list (car bnd)
(cdr bnd)
- (mapcar (lambda (s)
- (replace-regexp-in-string
- "__t__" ""
- (if (string-match "(\\'" s)
- (substring s 0 (match-beginning 0))
- s)))
- (python-shell-completion-get-completions
- (lispy--python-proc)
- nil str)))))))
-
-(defvar lispy--python-arg-key-re "\\`\\(\\(?:\\sw\\|\\s_\\)+\\)=\\([^=].*\\)\\'"
+ (let ((out (lispy--eval-python-plain
+ (format "lp.print_elisp(lp.get_completions('%s'))" str-com))))
+ (with-temp-buffer
+ (insert out)
+ (goto-char (point-min))
+ (when (re-search-forward "^(" nil t)
+ (goto-char (match-beginning 0)))
+ (read (current-buffer)))))))))
+
+(defvar lispy--python-arg-key-re "\\`\\(\\(?:\\sw\\|\\s_\\)+\\)=\\([^\\0]+\\)\\'"
"Constant regexp for matching function keyword spec.")
(defun lispy--python-args (beg end)
@@ -546,8 +790,10 @@ If so, return an equivalent of ITEM = ARRAY_LIKE[IDX]; ITEM."
(while (< (point) end)
(forward-sexp)
(while (and (< (point) end)
- (not (looking-at ",")))
- (forward-sexp))
+ (not (looking-at ","))
+ (ignore-errors
+ (forward-sexp)
+ t)))
(push (buffer-substring-no-properties
beg (point))
res)
@@ -608,10 +854,12 @@ If so, return an equivalent of ITEM = ARRAY_LIKE[IDX]; ITEM."
args))
(args-normal (cl-set-difference args args-key))
(fn-data
- (read (lispy--eval-python
+ (read (lispy--eval-python-plain
(format "lp.argspec(%s)" fn))))
(fn-args
(plist-get fn-data :args))
+ (fn-varkw
+ (plist-get fn-data :varkw))
(fn-varargs
(plist-get fn-data :varargs))
(fn-keywords
@@ -619,8 +867,8 @@ If so, return an equivalent of ITEM = ARRAY_LIKE[IDX]; ITEM."
(fn-defaults
(mapcar (lambda (x)
(cond ((stringp x)
- (prin1-to-string x))
- (x x)
+ x)
+ (x (prin1-to-string x))
(t
"None")))
(plist-get fn-data :defaults)))
@@ -650,11 +898,14 @@ If so, return an equivalent of ITEM = ARRAY_LIKE[IDX]; ITEM."
(let ((arg-name (match-string 1 arg))
(arg-val (match-string 2 arg))
arg-cell)
- (if (setq arg-cell (assoc arg-name fn-alist))
- (setcdr arg-cell arg-val)
- (if fn-keywords
- (push (concat arg-name "=" arg-val) extra-keywords)
- (error "\"%s\" is not in %s" arg-name fn-alist))))
+ (cond ((setq arg-cell (assoc arg-name fn-alist))
+ (setcdr arg-cell arg-val))
+ (fn-keywords
+ (push (concat arg-name "=" arg-val) extra-keywords))
+ (fn-varkw
+ (push (cons arg-name arg-val) fn-alist))
+ (t
+ (error "\"%s\" is not in %s" arg-name fn-alist))))
(error "\"%s\" does not match the regex spec" arg)))
(when (memq nil (mapcar #'cdr fn-alist))
(error "Not all args were provided: %s" fn-alist))
@@ -669,25 +920,36 @@ If so, return an equivalent of ITEM = ARRAY_LIKE[IDX]; ITEM."
(concat "dict(" (mapconcat #'identity extra-keywords ", ") ")"))
fn-alist))
(setq dbg-cmd
- (mapconcat (lambda (x)
- (format "%s = %s" (car x) (cdr x)))
- fn-alist
- "; "))
+ (concat
+ (format "lp.step_into_module_maybe(%s); "
+ (if method-p
+ (cdar fn-alist)
+ fn))
+ "("
+ (mapconcat #'car fn-alist ", ")
+ ")=("
+ (mapconcat #'cdr fn-alist ", ")
+
+ ")"))
(condition-case nil
- (lispy--eval-python dbg-cmd t)
+ (lispy--eval-python-plain dbg-cmd)
(error
(lispy--eval-python
(format "lp.step_in(%s,%s)" fn (buffer-substring-no-properties
(1+ p-ar-beg) (1- p-ar-end))))))
- (goto-char orig-point)
- (when fn-data
- (set-text-properties
- 0 1
- `(
- filename ,(plist-get fn-data :filename)
- line ,(plist-get fn-data :line))
- fn))
- (lispy-goto-symbol fn)))))
+ (let ((line (plist-get fn-data :line)))
+ (unless (eq line 1)
+ (goto-char orig-point)
+ (when fn-data
+ (set-text-properties
+ 0 1
+ `(
+ filename ,(plist-get fn-data :filename)
+ line ,line)
+ fn))))
+ (let ((buf lispy-python-buf))
+ (lispy-goto-symbol fn)
+ (setq lispy-python-buf buf))))))
(declare-function deferred:sync! "ext:deferred")
(declare-function jedi:goto-definition "ext:jedi-core")
@@ -701,6 +963,19 @@ If so, return an equivalent of ITEM = ARRAY_LIKE[IDX]; ITEM."
(unless (bolp)
(backward-char)))
+(defun lispy--python-goto-definition ()
+ (save-buffer)
+ (let ((definition (lispy--eval-python-plain
+ (format "lp.goto_definition('%s',%d,%d)"
+ (buffer-file-name)
+ (line-number-at-pos)
+ (current-column)))))
+ (unless (string= definition "")
+ (cl-destructuring-bind (fname line column) (read definition)
+ (lispy--goto-symbol-python fname line)
+ (forward-char column))
+ t)))
+
(defun lispy-goto-symbol-python (symbol)
(save-restriction
(widen)
@@ -708,27 +983,23 @@ If so, return an equivalent of ITEM = ARRAY_LIKE[IDX]; ITEM."
(line (get-text-property 0 'line symbol)))
(if file
(lispy--goto-symbol-python file line)
- (let ((res (ignore-errors
- (or
- (deferred:sync!
- (jedi:goto-definition))
- t))))
- (if (member res '(nil "Definition not found."))
- (let* ((symbol (python-info-current-symbol))
- (symbol-re (concat "^\\(?:def\\|class\\).*" (car (last (split-string symbol "\\." t)))))
- (r (lispy--eval-python
- (format "lp.argspec(%s)" symbol)))
- (plist (and r (read r))))
- (cond (plist
- (lispy--goto-symbol-python
- (plist-get plist :filename)
- (plist-get plist :line)))
- ((and (equal file "None")
- (re-search-backward symbol-re nil t)))
- (t
- (error "Both jedi and inspect failed"))))
- (unless (looking-back "def " (line-beginning-position))
- (jedi:goto-definition))))))))
+ (or (lispy--python-goto-definition)
+ (let* ((symbol (or (python-info-current-symbol) symbol))
+ (r (ignore-errors
+ (lispy--eval-python-plain
+ (format "lp.argspec(%s)" symbol))))
+ (plist (and r (read r))))
+ (cond (plist
+ (lispy--goto-symbol-python
+ (plist-get plist :filename)
+ (plist-get plist :line)))
+ ((and (equal file "None")
+ (let ((symbol-re
+ (concat "^\\(?:def\\|class\\).*"
+ (car (last (split-string symbol "\\." t))))))
+ (re-search-backward symbol-re nil t))))
+ (t
+ (error "Both jedi and inspect failed")))))))))
(defun lispy--python-docstring (symbol)
"Look up the docstring for SYMBOL.
@@ -737,7 +1008,7 @@ First, try to see if SYMBOL.__doc__ returns a string in the
current REPL session (dynamic).
Otherwise, fall back to Jedi (static)."
- (let ((dynamic-result (lispy--eval-python (concat symbol ".__doc__"))))
+ (let ((dynamic-result (lispy--eval-python-plain (concat symbol ".__doc__"))))
(if (> (length dynamic-result) 0)
(mapconcat #'string-trim-left
(split-string (substring dynamic-result 1 -1) "\\\\n")
@@ -752,38 +1023,39 @@ Otherwise, fall back to Jedi (static)."
(setq lispy--python-middleware-loaded-p nil)
(lispy--python-middleware-load))
-(defvar lispy-python-init-file "~/git/site-python/init.py")
+(defun lispy--python-slurp (f)
+ (with-temp-buffer
+ (insert-file-contents f)
+ (buffer-string)))
-(defvar lispy-python-init-file-remote "/opt/lispy-python.py")
+(defun lispy--python-setup-cmd ()
+ (concat
+ "from importlib.machinery import SourceFileLoader;"
+ (format "lp=SourceFileLoader('lispy-python', '%s').load_module();"
+ lispy--python-middleware-file)
+ (format "lp.setup('%s')" lispy--python-init-file)))
(defun lispy--python-middleware-load ()
"Load the custom Python code in \"lispy-python.py\"."
(unless lispy--python-middleware-loaded-p
- (let* ((lispy-python-py
- (if (file-remote-p default-directory)
- lispy-python-init-file-remote
- (expand-file-name "lispy-python.py" lispy-site-directory)))
- (module-path (format "'lispy-python','%s'" lispy-python-py)))
- (lispy--eval-python
- (format
- (concat
- "try:\n"
- " from importlib.machinery import SourceFileLoader\n"
- " lp=SourceFileLoader(%s).load_module()\n"
- "except:\n"
- " import imp;lp=imp.load_source(%s)\n"
- "__name__='__repl__';"
- "pm=lp.Autocall(lp.pm);")
- module-path module-path))
- (when (file-exists-p lispy-python-init-file)
- (lispy--eval-python
- (format "exec (open ('%s').read(), globals ())"
- (expand-file-name lispy-python-init-file))))
+ (let* ((default-directory (or (locate-dominating-file default-directory ".git")
+ default-directory))
+ out)
+ ;; send single line so that python.el does no /tmp/*.py magic, which does not work in Docker
+ (setq out (lispy--eval-python-plain (lispy--python-setup-cmd)))
+ (when (string-match "FileNotFoundError" out)
+ (let* ((text (lispy--python-slurp lispy--python-middleware-file))
+ (ben (replace-regexp-in-string "\n" "" (base64-encode-string text)))
+ (setup-cmd (let ((lispy--python-middleware-file "/tmp/lispy.py"))
+ (lispy--python-setup-cmd))))
+ (lispy--eval-python-plain
+ (format "import base64; open('/tmp/lispy.py','w').write(base64.b64decode('%s').decode()); %s"
+ ben setup-cmd))))
(setq lispy--python-middleware-loaded-p t))))
(defun lispy--python-arglist (symbol filename line column)
(lispy--python-middleware-load)
- (let* ((boundp (lispy--eval-python symbol))
+ (let* ((boundp (ignore-errors (lispy--eval-python-plain symbol) t))
(code (if boundp
(format "lp.arglist(%s)" symbol)
(format "lp.arglist_jedi(%d, %d, '%s')" line column filename)))
diff --git a/lispy-clojure-test.clj b/lispy-clojure-test.clj
index 7be032f6..f7ce8a05 100644
--- a/lispy-clojure-test.clj
+++ b/lispy-clojure-test.clj
@@ -18,20 +18,20 @@
;; see .
(ns lispy-clojure-test
- (:use [clojure.test :only [is deftest]]
- [lispy.clojure :only [add-location-to-defn
- add-location-to-def
- debug-step-in
- dest
- expand-home
- get-func-args
- get-func-args-def
- guess-intent
- object-members
- position
- reader=
- reval
- symbol-function]]))
+ (:require
+ [clojure.test :refer [is deftest]]
+ [lispy.clojure :refer [add-location-to-defn
+ add-location-to-def
+ debug-step-in
+ dest
+ get-func-args
+ get-func-args-def
+ guess-intent
+ object-members
+ position
+ reader=
+ reval
+ symbol-function]]))
(deftest get-func-args-test
(is (= (get-func-args (symbol-function 'string?) 1) '[x]))
@@ -76,7 +76,7 @@
(deftest debug-step-in-test
(is (= (debug-step-in
- '(expand-home (str "/foo" "/bar")))
+ '(lispy.clojure/expand-home (str "/foo" "/bar")))
{:path "/foo/bar"}))
(is
(=
@@ -145,7 +145,6 @@
["/foo/bar.clj" 42]))))
(deftest guess-intent-test
- (is (= (guess-intent '(defproject) nil) '(lispy.clojure/fetch-packages)))
(is (= (guess-intent 'x '[x y]) 'x))
(is (= (guess-intent '*ns* '*ns*) '*ns*))
(is (= (guess-intent '(+ 1 2) '[(+ 1 2) (+ 3 4) (+ 5 6)])
diff --git a/lispy-clojure.clj b/lispy-clojure.clj
index d1fac077..171ac5f9 100644
--- a/lispy-clojure.clj
+++ b/lispy-clojure.clj
@@ -21,33 +21,22 @@
(:require [clojure.repl :as repl]
[clojure.pprint]
[clojure.java.io :as io]
- [clojure.string :as str])
- (:use [cemerick.pomegranate :only (add-dependencies)])
- (:import (java.io File LineNumberReader InputStreamReader
- PushbackReader FileInputStream)
- (clojure.lang RT Reflector)))
-
-(defn use-package [name version]
- (add-dependencies
- :coordinates [[name version]]
- :repositories (merge cemerick.pomegranate.aether/maven-central
- {"clojars" "https://clojars.org/repo"})
- :classloader (. (. (. Compiler/LOADER deref) getParent) getParent)))
-
-(defn expand-file-name [name dir]
- (. (io/file dir name) getCanonicalPath))
-
-(use-package 'compliment "0.3.11")
-(require '[compliment.core :as compliment])
-
-(use-package 'me.raynes/fs "1.4.6")
-(require '[me.raynes.fs :as fs])
-
-(defmacro xcond [& clauses]
+ [clojure.string :as str]
+ [clojure.tools.namespace.file :as nfile]
+ [clojure.tools.namespace.find :as nf]
+ [clojure.java.classpath :as cp])
+ (:import
+ (java.io File LineNumberReader InputStreamReader
+ PushbackReader FileInputStream)
+ (java.util.jar JarFile)
+ (clojure.lang RT)))
+
+(defmacro xcond
"Common Lisp style `cond'.
It's more structured than `cond', thus exprs that use it are lot more
malleable to refactoring."
+ [& clauses]
(when clauses
(let [clause (first clauses)]
(if (= (count clause) 1)
@@ -59,23 +48,11 @@ malleable to refactoring."
(xcond
~@(next clauses)))))))
-(defn fetch-packages []
- (xcond ((fs/exists? "deps.edn")
- (println "fixme"))
- ((fs/exists? "project.clj")
- (let [deps (->> (slurp "project.clj")
- (read-string)
- (drop 3)
- (partition 2)
- (map vec)
- (into {})
- :dependencies)]
- (doseq [[name ver] deps]
- (use-package name ver))))
- (:else
- (throw
- (ex-info "Found no project.clj or deps.edn"
- {:cwd fs/*cwd*})))))
+(defn file-exists? [f]
+ (. (io/file f) exists))
+
+(defn expand-file-name [name dir]
+ (. (io/file dir name) getCanonicalPath))
(defn expand-home
[path]
@@ -246,7 +223,7 @@ malleable to refactoring."
(let [[_do & forms] (dest bindings)
[defs out] (partition-by map? forms)]
`(let ~(vec (mapcat (fn [[_ n v]] [n v]) defs))
- ~@(if (not= *ns* nspc)
+ ~@(when (not= *ns* nspc)
`((in-ns '~(ns-name nspc))))
~@(map
(fn [x]
@@ -323,7 +300,7 @@ malleable to refactoring."
(or
(resolve sym)
(first (keep #(ns-resolve % sym) (all-ns)))
- (if-let [val (try (load-string (str sym)) (catch Exception e))]
+ (when-let [val (try (load-string (str sym)) (catch Exception _e))]
(list 'variable (str val)))))]
[(keyword? sym) 'keyword]
@@ -352,7 +329,7 @@ malleable to refactoring."
(xcond
((= 'special rsym)
(->> (with-out-str
- (eval (list 'clojure.repl/doc sym)))
+ (eval (list #'repl/doc sym)))
(re-find #"\(.*\)")
read-string rest
(map str)
@@ -413,7 +390,7 @@ malleable to refactoring."
(and
(reader= (first a) (first b))
(reader= (rest a) (rest b)))))
- (catch Exception e
+ (catch Exception _e
(= a b))))
(defn position [x coll equality]
@@ -432,13 +409,13 @@ malleable to refactoring."
expr
(let [idx (position expr context reader=)]
(xcond
- ((#{'defproject} (first expr))
- `(fetch-packages))
((nil? idx)
expr)
+
;; [x |(+ 1 2) y (+ 3 4)] => {:x 3}
- ;; TODO: would be better to have 1 level higher context, so that we just check
- ;; (= (first context) 'let)
+ ((and (= (first context) 'let) (= idx 1))
+ (shadow-dest expr))
+
((and (vector? context)
(= 0 (rem (count context) 2))
(= 0 (rem (inc idx) 2))
@@ -468,9 +445,8 @@ malleable to refactoring."
(= 'defn (first expr))
file line)
(let [arglist-pos (first (keep-indexed
- (fn [i x] (if (or
- (vector? x)
- (list? x)) i))
+ (fn [i x] (when (or (vector? x) (list? x))
+ i))
expr))
expr-head (take arglist-pos expr)
expr-tail (drop arglist-pos expr)
@@ -509,7 +485,7 @@ malleable to refactoring."
(java.io.StringReader. s))]
(loop [res []]
(if-let [x (try (read reader)
- (catch Exception e))]
+ (catch Exception _e))]
(recur (conj res x))
res))))
@@ -534,14 +510,51 @@ malleable to refactoring."
(clojure.core/str "error: " ~ 'e)))))))
(defn file->elisp [f]
- (if (fs/exists? f)
+ (if (file-exists? f)
f
(. (io/resource f) getPath)))
+(defonce ns-to-jar (atom {}))
+
+(defn ns-location [sym]
+ (when (empty? @ns-to-jar)
+ (reset! ns-to-jar
+ (apply hash-map
+ (->>
+ (cp/classpath)
+ (mapcat #(interleave
+ (if (. % isFile)
+ (nf/find-namespaces-in-jarfile (JarFile. %))
+ (nf/find-namespaces-in-dir % nil))
+ (repeat %)))))))
+ (let [dir (get @ns-to-jar sym)]
+ (if (. dir isFile)
+ (let [jf (JarFile. dir)
+ file-in-jar (first
+ (filter
+ (fn [f]
+ (let [entry (nf/read-ns-decl-from-jarfile-entry jf f nil)]
+ (when (and entry (= (first entry) sym))
+ f)))
+ (nf/clojure-sources-in-jar jf)))]
+ (list
+ (str "file:" dir "!/" file-in-jar)
+ 0))
+ (let [file-in-dir (first
+ (filter
+ (fn [f]
+ (let [decl (nfile/read-file-ns-decl f nil)]
+ (and decl (= (first decl) sym)
+ f)))
+ (nf/find-clojure-sources-in-dir dir)))]
+ (list (.getCanonicalPath file-in-dir) 0)))))
+
(defn location [sym]
(let [rs (resolve sym)
m (meta rs)]
- (xcond ((:l-file m)
+ (xcond
+ ((and (nil? rs) (ns-location sym)))
+ ((:l-file m)
(list (:l-file m) (:l-line m)))
((and (:file m) (not (re-matches #"^/tmp/" (:file m))))
(list (file->elisp (:file m)) (:line m))))))
@@ -562,14 +575,3 @@ malleable to refactoring."
(fn [v]
(let [m (meta v)]
(str v "\n" (:arglists m) "\n" (:doc m))))))))
-
-(defn complete [prefix]
- (compliment/completions
- prefix
- {:context :same :plain-candidates true}))
-
-(defn run-lispy-tests []
- (let [dd (fs/parent (:file (meta #'use-package)))
- fname (java.io.File. dd "lispy-clojure-test.clj")]
- (when (fs/exists? fname)
- (load-file (str fname)))))
diff --git a/lispy-inline.el b/lispy-inline.el
index 7b8c5900..7b019234 100644
--- a/lispy-inline.el
+++ b/lispy-inline.el
@@ -218,23 +218,15 @@ The caller of `lispy--show' might use a substitute e.g. `describe-function'."
(declare-function geiser-doc-symbol-at-point "geiser-doc")
-(defun lispy--describe-inline ()
+(defun lispy--describe-inline (str pos)
"Toggle the overlay hint."
(condition-case nil
- (let ((new-hint-pos (lispy--hint-pos))
- doc)
- (if (and (eq lispy-hint-pos new-hint-pos)
- (overlayp lispy-overlay))
- (lispy--cleanup-overlay)
- (save-excursion
- (when (= 0 (count-lines (window-start) (point)))
- (recenter 1))
- (setq lispy-hint-pos new-hint-pos)
- (if (eq major-mode 'scheme-mode)
- (geiser-doc-symbol-at-point)
- (when (setq doc (lispy--docstring (lispy--current-function)))
- (goto-char lispy-hint-pos)
- (lispy--show (propertize doc 'face 'lispy-face-hint)))))))
+ (save-excursion
+ (when (= 0 (count-lines (window-start) (point)))
+ (recenter 1))
+ (setq lispy-hint-pos pos)
+ (goto-char lispy-hint-pos)
+ (lispy--show (propertize str 'face 'lispy-face-hint)))
(error
(lispy--cleanup-overlay))))
@@ -310,13 +302,22 @@ The caller of `lispy--show' might use a substitute e.g. `describe-function'."
(defun lispy-describe-inline ()
"Display documentation for `lispy--current-function' inline."
(interactive)
- (if (cl-some
- (lambda (window)
- (equal (buffer-name (window-buffer window)) "*lispy-help*"))
- (window-list))
- (when (window-configuration-p lispy--di-window-config)
- (set-window-configuration lispy--di-window-config))
- (lispy--describe-inline)))
+ (cond ((cl-some
+ (lambda (window)
+ (equal (buffer-name (window-buffer window)) "*lispy-help*"))
+ (window-list))
+ (when (window-configuration-p lispy--di-window-config)
+ (set-window-configuration lispy--di-window-config)))
+ ((eq major-mode 'scheme-mode)
+ (geiser-doc-symbol-at-point))
+ (t
+ (let ((new-hint-pos (lispy--hint-pos)))
+ (if (and (eq lispy-hint-pos new-hint-pos)
+ (overlayp lispy-overlay))
+ (lispy--cleanup-overlay)
+ (lispy--describe-inline
+ (lispy--docstring (lispy--current-function))
+ new-hint-pos))))))
(declare-function lispy--python-docstring "le-python")
(declare-function lispy--python-arglist "le-python")
diff --git a/lispy-occur.el b/lispy-occur.el
new file mode 100644
index 00000000..113f6960
--- /dev/null
+++ b/lispy-occur.el
@@ -0,0 +1,216 @@
+;;; lispy-occur.el --- Select a line within the current top level sexp. -*- lexical-binding: t -*-
+
+;; Copyright (C) 2014-2021 Oleh Krehel
+
+;; This file is not part of GNU Emacs
+
+;; This file is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 3, or (at your option)
+;; any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; For a full copy of the GNU General Public License
+;; see .
+
+;;; Commentary:
+;;
+
+;;; Code:
+(require 'swiper)
+
+(defcustom lispy-occur-backend 'ivy
+ "Method to navigate to a line with `lispy-occur'."
+ :type '(choice
+ (const :tag "Ivy" ivy)
+ (const :tag "Helm" helm)))
+
+(defvar lispy--occur-beg 1
+ "Start position of the top level sexp during `lispy-occur'.")
+
+(defvar lispy--occur-end 1
+ "End position of the top level sexp during `lispy-occur'.")
+
+(defun lispy--occur-candidates (&optional bnd)
+ "Return the candidates for `lispy-occur'."
+ (setq bnd (or bnd (save-excursion
+ (unless (and (bolp)
+ (lispy-left-p))
+ (beginning-of-defun))
+ (lispy--bounds-dwim))))
+ (let ((line-number -1)
+ candidates)
+ (setq lispy--occur-beg (car bnd))
+ (setq lispy--occur-end (cdr bnd))
+ (save-excursion
+ (goto-char lispy--occur-beg)
+ (while (< (point) lispy--occur-end)
+ (push (format "%-3d %s"
+ (cl-incf line-number)
+ (buffer-substring
+ (line-beginning-position)
+ (line-end-position)))
+ candidates)
+ (forward-line 1)))
+ (nreverse candidates)))
+
+(defun lispy--occur-preselect ()
+ "Initial candidate regex for `lispy-occur'."
+ (format "^%d"
+ (-
+ (line-number-at-pos (point))
+ (line-number-at-pos lispy--occur-beg))))
+
+(defvar helm-input)
+(declare-function helm "ext:helm")
+
+(defun lispy-occur-action-goto-paren (x)
+ "Goto line X for `lispy-occur'."
+ (setq x (read x))
+ (goto-char lispy--occur-beg)
+ (let ((input (if (eq lispy-occur-backend 'helm)
+ helm-input
+ ivy-text))
+ str-or-comment)
+ (cond ((string= input "")
+ (forward-line x)
+ (back-to-indentation)
+ (when (re-search-forward lispy-left (line-end-position) t)
+ (goto-char (match-beginning 0))))
+
+ ((setq str-or-comment
+ (progn
+ (forward-line x)
+ (re-search-forward (ivy--regex input)
+ (line-end-position) t)
+ (lispy--in-string-or-comment-p)))
+ (goto-char str-or-comment))
+
+ ((re-search-backward lispy-left (line-beginning-position) t)
+ (goto-char (match-beginning 0)))
+
+ ((re-search-forward lispy-left (line-end-position) t)
+ (goto-char (match-beginning 0)))
+
+ (t
+ (back-to-indentation)))))
+
+(defun lispy-occur-action-goto-end (x)
+ "Goto line X for `lispy-occur'."
+ (setq x (read x))
+ (goto-char lispy--occur-beg)
+ (forward-line x)
+ (re-search-forward (ivy--regex ivy-text) (line-end-position) t))
+
+(defun lispy-occur-action-goto-beg (x)
+ "Goto line X for `lispy-occur'."
+ (when (lispy-occur-action-goto-end x)
+ (goto-char (match-beginning 0))))
+
+(defun lispy-occur-action-mc (_x)
+ "Make a fake cursor for each `lispy-occur' candidate."
+ (let ((cands (nreverse ivy--old-cands))
+ cand)
+ (while (setq cand (pop cands))
+ (goto-char lispy--occur-beg)
+ (forward-line (read cand))
+ (re-search-forward (ivy--regex ivy-text) (line-end-position) t)
+ (when cands
+ (mc/create-fake-cursor-at-point))))
+ (multiple-cursors-mode 1))
+
+(ivy-set-actions
+ 'lispy-occur
+ '(("m" lispy-occur-action-mc "multiple-cursors")
+ ("j" lispy-occur-action-goto-beg "goto start")
+ ("k" lispy-occur-action-goto-end "goto end")))
+
+(defvar ivy-last)
+(declare-function ivy-state-window "ext:ivy")
+
+;;;###autoload
+(defun lispy-occur ()
+ "Select a line within current top level sexp.
+See `lispy-occur-backend' for the selection back end."
+ (interactive)
+ (swiper--init)
+ (cond ((eq lispy-occur-backend 'helm)
+ (require 'helm)
+ (add-hook 'helm-move-selection-after-hook
+ #'lispy--occur-update-input-helm)
+ (add-hook 'helm-update-hook
+ #'lispy--occur-update-input-helm)
+ (unwind-protect
+ (helm :sources
+ `((name . "this defun")
+ (candidates . ,(lispy--occur-candidates))
+ (action . lispy-occur-action-goto-paren)
+ (match-strict .
+ (lambda (x)
+ (ignore-errors
+ (string-match
+ (ivy--regex helm-input) x)))))
+ :preselect (lispy--occur-preselect)
+ :buffer "*lispy-occur*")
+ (swiper--cleanup)
+ (remove-hook 'helm-move-selection-after-hook
+ #'lispy--occur-update-input-helm)
+ (remove-hook 'helm-update-hook
+ #'lispy--occur-update-input-helm)))
+ ((eq lispy-occur-backend 'ivy)
+ (unwind-protect
+ (ivy-read "pattern: "
+ (lispy--occur-candidates)
+ :preselect (lispy--occur-preselect)
+ :require-match t
+ :update-fn (lambda ()
+ (lispy--occur-update-input
+ ivy-text
+ (ivy-state-current ivy-last)))
+ :action #'lispy-occur-action-goto-paren
+ :caller 'lispy-occur)
+ (swiper--cleanup)
+ (when (null ivy-exit)
+ (goto-char swiper--opoint))))
+ (t
+ (error "Bad `lispy-occur-backend': %S" lispy-occur-backend))))
+
+(defun lispy--occur-update-input-helm ()
+ "Update selection for `lispy-occur' using `helm' back end."
+ (lispy--occur-update-input
+ helm-input
+ (buffer-substring-no-properties
+ (line-beginning-position)
+ (line-end-position))))
+
+(defun lispy--occur-update-input (input str)
+ "Update selection for `ivy-occur'.
+INPUT is the current input text.
+STR is the full current candidate."
+ (swiper--cleanup)
+ (let ((re (ivy--regex input))
+ (num (if (string-match "^[0-9]+" str)
+ (string-to-number (match-string 0 str))
+ 0)))
+ (with-selected-window (ivy-state-window ivy-last)
+ (goto-char lispy--occur-beg)
+ (when (cl-plusp num)
+ (forward-line num)
+ (unless (<= (point) lispy--occur-end)
+ (recenter)))
+ (let ((ov (make-overlay (line-beginning-position)
+ (1+ (line-end-position)))))
+ (overlay-put ov 'face 'swiper-line-face)
+ (overlay-put ov 'window (ivy-state-window ivy-last))
+ (push ov swiper--overlays))
+ (re-search-forward re (line-end-position) t)
+ (swiper--add-overlays
+ re
+ lispy--occur-beg
+ lispy--occur-end))))
+
+;;; lispy-occur.el ends here
diff --git a/lispy-pkg.el b/lispy-pkg.el
index 86fd72f4..9794ae70 100644
--- a/lispy-pkg.el
+++ b/lispy-pkg.el
@@ -3,6 +3,6 @@
'((emacs "24.3")
(ace-window "0.9.0")
(iedit "0.9.9")
- (counsel "0.11.0")
+ (swiper "0.13.4")
(hydra "0.14.0")
- (zoutline "0.1.0")))
+ (zoutline "0.2.0")))
diff --git a/lispy-python.py b/lispy-python.py
index fe88ae7f..f39aef6c 100644
--- a/lispy-python.py
+++ b/lispy-python.py
@@ -18,33 +18,58 @@
# see .
#* Imports
-from __future__ import print_function
import ast
-import sys
+import collections
+import importlib
import inspect
+import io
+import json
+import os
+import pprint as pp
import re
-import platform
import shlex
-import types
-import collections
-import pprint as pp
+import subprocess
+import sys
+from ast import AST
+from contextlib import redirect_stdout
+from typing import List, Dict, Any, Union, Tuple, Optional, TypedDict, Callable, cast
+from types import TracebackType, MethodType, FunctionType, ModuleType, FrameType
+
+def sh(cmd: str) -> str:
+ r = subprocess.run(
+ shlex.split(cmd),
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ encoding="utf-8",
+ check=True)
+ return r.stdout.strip()
+
try:
import reprlib
repr1 = reprlib.Repr()
repr1.maxlist = 10
- repr1.maxstring = 100
+ repr1.maxstring = 200
except:
pass
try:
import jedi
except:
- print("failed to load jedi")
+ try:
+ pyenv_version = sh("pyenv global")
+ pyversion = ".".join(pyenv_version.split(".")[:-1])
+ site_packages = os.path.expanduser(f"~/.pyenv/versions/{pyenv_version}/lib/python{pyversion}/site-packages/")
+ sys.path.append(site_packages)
+ import jedi
+ except:
+ print("Failed to load jedi. Some features won't work")
#* Classes
class Stack:
- line_numbers = {}
- def __init__(self, tb):
+ line_numbers: Dict[Tuple[str, str], int] = {}
+
+ def __init__(self, tb: Optional[TracebackType]):
self.stack = []
+ self.stack_idx = 0
while tb:
name = tb.tb_frame.f_code.co_name
fname = tb.tb_frame.f_code.co_filename
@@ -55,15 +80,16 @@ def __init__(self, tb):
self.stack.append((fname, lineno, tb.tb_frame))
tb = tb.tb_next
self.stack_top = len(self.stack) - 1
- self.set_frame(self.stack_top)
+ if self.stack_top >= 0:
+ self.set_frame(self.stack_top)
- def frame_string(self, i):
+ def frame_string(self, i: int) -> str:
(fname, line, f) = self.stack[i]
res = " File \"%s\", line %d, Frame [%d/%d] (%s):" % (
f.f_code.co_filename, line, i, self.stack_top, f.f_code.co_name)
return res
- def __repr__(self):
+ def __repr__(self) -> str:
frames = []
for i in range(self.stack_top + 1):
s = self.frame_string(i)
@@ -72,124 +98,111 @@ def __repr__(self):
frames.append(s)
return "\n".join(frames)
- def set_frame(self, i):
- f = self.stack[i][2]
- self.stack_idx = i
- tf = top_level()
- tf.f_globals["lnames"] = f.f_locals.keys()
- for (k, v) in f.f_locals.items():
- tf.f_globals[k] = v
- for (k, v) in f.f_globals.items():
- tf.f_globals[k] = v
+ def set_frame(self, i: int) -> None:
+ if i >= 0:
+ f = self.stack[i][2]
+ self.stack_idx = i
+ tf = top_level()
+ tf.f_globals["lnames"] = f.f_locals.keys()
+ for (k, v) in f.f_locals.items():
+ tf.f_globals[k] = v
+ for (k, v) in f.f_globals.items():
+ tf.f_globals[k] = v
- print(self.frame_string(self.stack_idx))
+ print(self.frame_string(self.stack_idx))
- def up(self, delta = 1):
+ def up(self, delta: int = 1) -> None:
if self.stack_idx <= 0:
- print("top frame already")
- print(self.frame_string(self.stack_idx))
+ if self.stack:
+ print(self.frame_string(self.stack_idx))
else:
self.stack_idx = max(self.stack_idx - delta, 0)
self.set_frame(self.stack_idx)
- def down(self, delta = 1):
+ def down(self, delta: int = 1) -> None:
if self.stack_idx >= self.stack_top:
- print("bottom frame already")
- print(self.frame_string(self.stack_idx))
+ if self.stack:
+ print(self.frame_string(self.stack_idx))
else:
self.stack_idx = min(self.stack_idx + delta, self.stack_top)
self.set_frame(self.stack_idx)
class Autocall:
- def __init__(self, f):
+ def __init__(self, f: Callable):
self.f = f
- def __call__(self, n):
+ def __call__(self, n: Any) -> None:
self.f(n)
- def __repr__(self):
- self.f()
+ def __repr__(self) -> str:
+ try:
+ self.f()
+ except:
+ pass
return ""
#* Functions
-def arglist_retrieve_java(method):
- name = method.__name__
- if hasattr(method, "argslist"):
- # uses only the first args list...
- args = [x.__name__ for x in method.argslist[0].args]
- else:
- methods = eval("method.__self__.class.getDeclaredMethods()")
- methods_by_name = [m for m in methods if m.getName() == name]
- assert len(methods_by_name) == 1, "expected only a single method by name %s" % name
- meta = methods_by_name[0]
- args = [str(par.getType().__name__ + " " + par.getName()) for par in meta.getParameters()]
- return inspect.ArgSpec(args, None, None, None)
-
-def arglist_retrieve(sym):
+def get_import_name(fname: str) -> str:
+ for p in sys.path:
+ if p == "":
+ continue
+ if fname.startswith(p):
+ return fname[len(p) + 1:].partition(".")[0].replace("/", ".")
+ return os.path.splitext(os.path.basename(fname))[0]
+
+def chfile(f: str) -> None:
+ tf = top_level()
+ tf.f_globals["__file__"] = f
+ name = get_import_name(f)
+ tf.f_globals["__name__"] = name
+ d = os.path.dirname(f)
try:
- if hasattr(inspect, "getfullargspec"):
- res = inspect.getfullargspec(sym)
- return inspect.ArgSpec(args = res.args,
- varargs = res.varargs,
- defaults = res.defaults,
- keywords = res.kwonlydefaults)
- else:
- return inspect.getargspec(sym)
- except TypeError as er:
- if(re.search("is not a Python function$", er.message)
- and platform.system() == "Java"):
- if inspect.isclass(sym):
- return arglist_retrieve(sym.__init__)
- elif hasattr(sym, "argslist") or \
- hasattr(sym, "__self__") and hasattr(sym.__self__, "class"):
- return arglist_retrieve_java(sym)
- else:
- print(er.message)
- else:
- print(er.message)
-
-def format_arg(arg_pair):
- name, default_value = arg_pair
- if default_value:
- return name + " = " + default_value
- else:
- return name
-
-def delete(element, lst):
- return [x for x in lst if x != element]
-
-def mapcar(func, lst):
- """Compatibility function for Python3.
-
- In Python2 `map' returns a list, as expected. But in Python3
- `map' returns a map object that can be converted to a list.
- """
- return list(map(func, lst))
+ os.chdir(d)
+ if "sys" not in tf.f_globals:
+ tf.f_globals["sys"] = importlib.import_module("sys")
+ if name not in tf.f_globals["sys"].modules:
+ try:
+ mod = importlib.import_module(name)
+ tf.f_globals["sys"].modules[name] = mod
+ except:
+ pass
+ except:
+ raise
-def arglist(sym):
- arg_info = arglist_retrieve(sym)
+def arglist(sym: Callable) -> List[str]:
+ def format_arg(arg_pair: Tuple[str, Optional[str]]) -> str:
+ name, default_value = arg_pair
+ if default_value:
+ return name + " = " + default_value
+ else:
+ return name
+ arg_info = inspect.getfullargspec(sym)
if "self" in arg_info.args:
arg_info.args.remove("self")
if arg_info.defaults:
- defaults = [None] *(len(arg_info.args) - len(arg_info.defaults)) + \
- mapcar(repr, arg_info.defaults)
- args = mapcar(format_arg, zip(arg_info.args, defaults))
+ defaults: List[Optional[str]] = [None] * (len(arg_info.args) - len(arg_info.defaults))
+ defaults += [repr(x) for x in arg_info.defaults]
+ args = [format_arg(x) for x in zip(arg_info.args, defaults)]
else:
args = arg_info.args
if arg_info.varargs:
args += arg_info.varargs
- if arg_info.keywords:
- if type(arg_info.keywords) is dict:
- for k, v in arg_info.keywords.items():
- args.append("%s = %s" %(k, v))
- else:
- args.append("**" + arg_info.keywords)
+ keywords = arg_info.kwonlydefaults
+ if keywords:
+ for k, v in keywords.items():
+ args.append(f"{k} = {v}")
return args
-def print_elisp(obj, end="\n"):
+def print_elisp(obj: Any, end: str = "\n") -> None:
if hasattr(obj, "_asdict") and obj._asdict is not None:
+ if hasattr(type(obj), "__repr__"):
+ print('"' + str(obj).replace('"', '') + '"')
+ return
# namedtuple
- print_elisp(obj._asdict(), end)
+ try:
+ print_elisp(obj._asdict(), end)
+ except:
+ print('"' + str(obj) + '"')
elif hasattr(obj, "__array__"):
# something that converts to a numpy array
print_elisp(list(obj.__array__()))
@@ -216,60 +229,75 @@ def print_elisp(obj, end="\n"):
for (k, v) in obj:
print_elisp((k, v), end="\n")
print(")")
- elif isinstance(obj, collections.KeysView):
+ elif isinstance(obj, collections.abc.KeysView):
print_elisp(list(obj))
elif isinstance(obj, int):
print(obj)
+ elif isinstance(obj, list) or isinstance(obj, tuple):
+ print("(", end="")
+ for x in obj:
+ print_elisp(x)
+ print(")")
else:
- if obj:
- if type(obj) is list or type(obj) is tuple:
- print("(", end="")
- for x in obj:
- print_elisp(x)
- print(")")
- elif type(obj) is str:
+ if obj is not None:
+ if type(obj) is str:
# quote strings?
# print("\"'" + re.sub("\"", "\\\"", obj) + "'\"", end=" ")
print('"' + re.sub("\"", "\\\"", obj) + '"', end=" ")
+ elif hasattr(obj, "to_dict"):
+ print_elisp(obj.to_dict())
else:
- print('"' + repr(obj) + '"', end=" ")
+ print('"' + repr(obj) + '"', end=" ")
else:
print('nil', end=end)
def argspec(sym):
- arg_info = inspect.getargspec(sym)
+ arg_info = inspect.getfullargspec(sym)
if arg_info:
di = arg_info._asdict()
fn = sym.__init__ if type(sym) is type else sym
- filename = fn.__code__.co_filename
- di["filename"] = filename
- if hasattr(sym, "__self__"):
- # bound method
- qname = sym.__self__.__class__.__name__ + "." + sym.__name__
- else:
- qname = sym.__qualname__
- tu = (filename, qname)
- if tu in Stack.line_numbers:
- di["line"] = Stack.line_numbers[tu]
- else:
- di["line"] = fn.__code__.co_firstlineno
+ try:
+ filename = fn.__code__.co_filename
+ di["filename"] = filename
+ if hasattr(sym, "__self__"):
+ # bound method
+ qname = sym.__self__.__class__.__name__ + "." + sym.__name__
+ else:
+ qname = sym.__qualname__
+ tu = (filename, qname)
+ if tu in Stack.line_numbers:
+ di["line"] = Stack.line_numbers[tu]
+ else:
+ di["line"] = fn.__code__.co_firstlineno
+ except AttributeError:
+ m = sys.modules[sym.__module__]
+ filename = m.__file__
+ nodes = ast.parse(open(filename).read()).body
+ for node in nodes:
+ if (type(node) in [ast.ClassDef, ast.FunctionDef] and
+ node.name == sym.__name__):
+ di["filename"] = filename
+ di["line"] = node.lineno
+
print_elisp(di)
else:
print("nil")
def arglist_jedi(line, column, filename):
- script = jedi.Script(None, line, column, filename)
- defs = script.goto_definitions()
- if len(defs) == 0:
- raise TypeError("0 definitions found")
- elif len(defs) > 1:
- raise TypeError(">1 definitions found")
+ script = jedi.Script(path=filename)
+ defs = script.get_signatures(line, column)
+ if defs:
+ return [x.name for x in defs[0].params]
else:
- return delete('', mapcar(lambda x: str(x.name), defs[0].params))
+ return []
def jedi_completions(line):
- script=jedi.Script(source=line, line=1, column=len(line), sys_path=sys.path)
- return [_x_.name for _x_ in script.completions()]
+ script = jedi.Script(code=line)
+ return [x.name for x in script.complete()]
+
+def jedi_file_completions(fname, line, column):
+ script = jedi.Script(path=fname)
+ return [x.name for x in script.complete(line, column)]
def is_assignment(code):
ops = ast.parse(code).body
@@ -280,13 +308,15 @@ def top_level():
f = sys._getframe()
while f.f_back:
f = f.f_back
+ if f.f_code.co_filename == "":
+ return f
return f
def list_step(varname, lst):
f_globals = top_level().f_globals
try:
val = f_globals[varname]
- i =(lst.index(val) + 1) % len(lst)
+ i = (lst.index(val) + 1) % len(lst)
except:
i = 0
val = lst[i]
@@ -294,7 +324,7 @@ def list_step(varname, lst):
f_globals[varname] = val
return val
-def argv(cmd):
+def argv(cmd: str) -> None:
sys.argv = shlex.split(cmd)
def find_global_vars(class_name):
@@ -312,11 +342,11 @@ def rebind(method, fname=None, line=None):
(cls_name, fun_name) = qname.split(".")
for (n, v) in find_global_vars(cls_name):
print("rebind:", n)
- top_level().f_globals[n].__dict__[fun_name] = types.MethodType(top_level().f_globals[fun_name], v)
+ top_level().f_globals[n].__dict__[fun_name] = MethodType(top_level().f_globals[fun_name], v)
if fname and line:
Stack.line_numbers[(fname, qname)] = line
-def pm():
+def pm() -> None:
"""Post mortem: recover the locals and globals from the last traceback."""
if hasattr(sys, 'last_traceback'):
stack = Stack(sys.last_traceback)
@@ -327,9 +357,7 @@ def pm():
tl.f_globals["dn"] = Autocall(stack.down)
globals()["stack"] = stack
-pp1 = pp.PrettyPrinter(width=100)
-
-def pprint(x):
+def pprint(x: Any) -> None:
r1 = repr(x)
if len(r1) > 1000 and repr1:
print(repr1.repr(x))
@@ -337,10 +365,501 @@ def pprint(x):
if type(x) == collections.OrderedDict:
print("{" + ",\n ".join([str(k) + ": " + str(v) for (k, v) in x.items()]) + "}")
else:
- pp1.pprint(x)
+ pp.PrettyPrinter(width=200).pprint(x)
+
+def to_str(x: Any) -> str:
+ with io.StringIO() as buf, redirect_stdout(buf):
+ pprint(x)
+ return buf.getvalue().strip()
def step_in(fn, *args):
- spec = inspect.getargspec(fn)
+ spec = inspect.getfullargspec(fn)
f_globals = top_level().f_globals
for (arg_name, arg_val) in zip(spec.args, args):
f_globals[arg_name] = arg_val
+
+def step_into_module_maybe(module):
+ if isinstance(module, FunctionType):
+ try:
+ module = sys.modules[module.__module__]
+ except:
+ pass
+ elif getattr(module, "__module__", None):
+ if module.__module__ == "__repl__":
+ return
+ module = sys.modules[module.__module__]
+ if inspect.ismodule(module):
+ tf = top_level()
+ for (k, v) in module.__dict__.items():
+ if not re.match("^__", k):
+ print(k)
+ tf.f_globals[k] = v
+
+def slurp(fname: str) -> str:
+ """Return `fname' contents as text."""
+ with open(fname, "r", encoding="utf-8") as fh:
+ return fh.read()
+
+def definitions(path):
+ (_, ext) = os.path.splitext(path)
+ if ext == ".yml":
+ return yaml_definitions(path)
+ script = jedi.Script(slurp(path), path=path)
+ res = []
+ for x in script.get_names():
+ if (x.get_definition_start_position()[0] == x.get_definition_end_position()[0]
+ and "import" in x.get_line_code()):
+ continue
+ if x.type == "function":
+ try:
+ desc = x.description + "(" + ", ".join(p.name for p in x.params) + ")"
+ except:
+ desc = x.description
+ res.append([desc, x.line])
+ elif x.type == "module":
+ res.append(["import " + x.name, x.line])
+ elif x.type == "class":
+ res.append([x.description, x.line])
+ try:
+ members = x.defined_names()
+ except:
+ members = []
+ for m in members:
+ res.append([x.name + "." + m.name, m.line])
+ else:
+ res.append([x.description, x.line])
+ return res
+
+
+def yaml_definitions(path):
+ res = []
+ ls = slurp(path).strip().splitlines()
+ prev = ""
+ symbol = "(\\w|[-_])+"
+ for (i, line) in enumerate(ls, 1):
+ if m := re.match(f"^({symbol})", line):
+ res.append([m.group(1), i])
+ prev = m.group(1) + "."
+ elif m := re.match(f"^ ({symbol})", line):
+ res.append([prev + m.group(1), i])
+ return res
+
+
+def get_completions_readline(text):
+ completions = []
+ completer = None
+ try:
+ import readline
+ # pylint: disable=unused-import
+ import rlcompleter
+ completer = readline.get_completer()
+ if getattr(completer, 'PYTHON_EL_WRAPPED', False):
+ completer.print_mode = False
+ i = 0
+ while True:
+ completion = completer(text, i)
+ if not completion:
+ break
+ i += 1
+ if not re.match("[0-9]__", completion):
+ completions.append(completion)
+ except:
+ pass
+ finally:
+ if getattr(completer, 'PYTHON_EL_WRAPPED', False):
+ completer.print_mode = True
+ return [re.sub("__t__.", "", c) for c in completions]
+
+def get_completions(text):
+ completions = get_completions_readline(text)
+ if completions:
+ return sorted(completions)
+ m = re.match(r"([^.]+)\.(.*)", text)
+ if m:
+ (obj, part) = m.groups()
+ regex = re.compile("^" + part)
+ o = top_level().f_globals[obj]
+ items = list(o.__dict__.keys()) if hasattr(o, "__dict__") else []
+ items += list(type(o).__dict__.keys()) if hasattr(type(o), "__dict__") else []
+ for x in set(items):
+ if re.match(regex, x):
+ if not x.startswith("_") or part.startswith("_"):
+ completions.append(x)
+ return sorted(completions)
+ else:
+ return []
+
+def __PYTHON_EL_native_completion_setup():
+ import readline
+ try:
+ import __builtin__
+ except ImportError:
+ # Python 3
+ import builtins as __builtin__
+
+ builtins = dir(__builtin__)
+ is_ipython = ('__IPYTHON__' in builtins or
+ '__IPYTHON__active' in builtins)
+
+ class __PYTHON_EL_Completer:
+ '''Completer wrapper that prints candidates to stdout.
+
+ It wraps an existing completer function and changes its behavior so
+ that the user input is unchanged and real candidates are printed to
+ stdout.
+
+ Returned candidates are '0__dummy_completion__' and
+ '1__dummy_completion__' in that order ('0__dummy_completion__' is
+ returned repeatedly until all possible candidates are consumed).
+
+ The real candidates are printed to stdout so that they can be
+ easily retrieved through comint output redirect trickery.
+ '''
+
+ PYTHON_EL_WRAPPED = True
+
+ def __init__(self, completer):
+ self.completer = completer
+ self.last_completion = None
+ self.print_mode = True
+
+ def __call__(self, text, state):
+ if state == 0:
+ # Set the first dummy completion.
+ self.last_completion = None
+ completion = '0__dummy_completion__'
+ else:
+ completion = self.completer(text, state - 1)
+
+ if not completion:
+ if self.last_completion != '1__dummy_completion__':
+ # When no more completions are available, returning a
+ # dummy with non-sharing prefix allow ensuring output
+ # while preventing changes to current input.
+ # Coincidentally it's also the end of output.
+ completion = '1__dummy_completion__'
+ elif completion.endswith('('):
+ # Remove parens on callables as it breaks completion on
+ # arguments (e.g. str(Ari)).
+ completion = completion[:-1]
+ self.last_completion = completion
+
+ if completion in (
+ '0__dummy_completion__', '1__dummy_completion__'):
+ return completion
+ elif completion:
+ # For every non-dummy completion, return a repeated dummy
+ # one and print the real candidate so it can be retrieved
+ # by comint output filters.
+ if self.print_mode:
+ print(completion)
+ return '0__dummy_completion__'
+ else:
+ return completion
+ else:
+ return completion
+
+ completer = readline.get_completer()
+
+ if not completer:
+ # Used as last resort to avoid breaking customizations.
+ # pylint: disable=unused-import
+ import rlcompleter
+ completer = readline.get_completer()
+
+ if completer and not getattr(completer, 'PYTHON_EL_WRAPPED', False):
+ # Wrap the existing completer function only once.
+ new_completer = __PYTHON_EL_Completer(completer)
+ if not is_ipython:
+ readline.set_completer(new_completer)
+ else:
+ # Try both initializations to cope with all IPython versions.
+ # This works fine for IPython 3.x but not for earlier:
+ readline.set_completer(new_completer)
+ # IPython<3 hacks readline such that `readline.set_completer`
+ # won't work. This workaround injects the new completer
+ # function into the existing instance directly:
+ instance = getattr(completer, 'im_self', completer.__self__)
+ instance.rlcomplete = new_completer
+
+ if readline.__doc__ and 'libedit' in readline.__doc__:
+ raise Exception('''libedit based readline is known not to work,
+ see etc/PROBLEMS under \"In Inferior Python mode, input is echoed\".''')
+
+ readline.parse_and_bind('tab: complete')
+ # Require just one tab to send output.
+ readline.parse_and_bind('set show-all-if-ambiguous on')
+
+
+__PYTHON_EL_native_completion_setup()
+
+def setup(init_file=None):
+ sys.modules['__repl__'] = sys.modules[__name__]
+ tl = top_level()
+ tl.f_globals["__name__"] = "__repl__"
+ tl.f_globals["pm"] = Autocall(pm)
+ if init_file and os.path.exists(init_file):
+ try:
+ exec(open(init_file).read(), tl.f_globals)
+ except:
+ pass
+
+def reload():
+ import importlib.util
+ spec = importlib.util.spec_from_file_location('lispy-python', __file__)
+ mod = importlib.util.module_from_spec(spec)
+ spec.loader.exec_module(mod)
+ top_level().f_globals["lp"] = mod
+ sys._getframe().f_back.f_globals["lp"] = mod
+ sys._getframe().f_back.f_locals["lp"] = mod
+ return mod
+
+def reload_module(fname):
+ to_reload = []
+ for (name, module) in sys.modules.copy().items():
+ try:
+ if module.__dict__.get("__file__") == fname and name != "__main__":
+ to_reload.append((name, module))
+ except:
+ pass
+ for (name, module) in to_reload:
+ try:
+ importlib.reload(module)
+ except:
+ pass
+
+def goto_definition(fname: str, line: int, column: int) -> None:
+ d = jedi.Script(path=fname).goto(line, column)
+ if d:
+ print_elisp((str(d[0].module_path), d[0].line, d[0].column))
+
+def goto_link_definition(c: str) -> None:
+ module = c.split(".")[0]
+ d = jedi.Script(code=f"import {module}\n{c}").goto(2, len(c) - 2, follow_imports=True)
+ if d:
+ print_elisp((str(d[0].module_path), d[0].line, d[0].column))
+
+def ast_pp(code: str) -> str:
+ parsed = ast.parse(code, mode="exec")
+ return ast.dump(parsed.body[0], indent=4)
+
+Expr = Union[List[ast.stmt], ast.stmt, Any]
+
+def has_return(p: Expr) -> bool:
+ if isinstance(p, list):
+ return any(has_return(x) for x in p)
+ if isinstance(p, ast.Return):
+ return True
+ elif isinstance(p, ast.If):
+ return has_return(p.body) or has_return(p.orelse)
+ else:
+ return False
+
+def tr_returns(p: Expr) -> Expr:
+ if isinstance(p, list):
+ return [tr_returns(x) for x in p]
+ if isinstance(p, ast.Return) and p.value:
+ return ast.parse("return locals() | {'__return__': " + ast.unparse(p.value) + "}").body[0]
+ elif isinstance(p, ast.If):
+ return ast.If(
+ test=p.test,
+ body=tr_returns(p.body),
+ orelse=tr_returns(p.orelse))
+ else:
+ return p
+
+def ast_call(func: Union[str, AST], args: List[Any] = [], keywords: List[Any] = []):
+ if isinstance(func, str):
+ func = ast.Name(func)
+ return ast.Call(func=func, args=args, keywords=keywords)
+
+def wrap_return(parsed: List[ast.stmt]) -> Expr:
+ return [
+ ast.FunctionDef(
+ name="__res__",
+ body=[*parsed, *ast.parse("return {'__return__': None}").body],
+ decorator_list=[],
+ args=[],
+ lineno=0,
+ col_offset=0),
+ ast.Expr(
+ ast_call(
+ ast.Attribute(value=ast_call("locals"), attr="update"),
+ args=[ast_call("__res__")])),
+ ast.Expr(value=ast.Name("__return__"))
+ ]
+
+def try_in_expr(p: Expr) -> Optional[Tuple[ast.expr, ast.expr]]:
+ if not isinstance(p, list):
+ return None
+ if pytest_mark := try_pytest_mark(p):
+ return pytest_mark
+ p0 = p[0]
+ if not isinstance(p0, ast.Expr):
+ return None
+ if not isinstance(p0.value, ast.Compare):
+ return None
+ if not isinstance(p0.value.ops[0], ast.In):
+ return None
+ return (p0.value.left, p0.value.comparators[0])
+
+def select_item(code: str, idx: int, _f: Optional[FrameType] = None) -> Any:
+ _f = _f or sys._getframe().f_back
+ parsed = ast.parse(code, mode="exec").body
+ in_expr = try_in_expr(parsed)
+ assert in_expr
+ (left, right) = in_expr
+ l = ast.unparse(left)
+ r = ast.unparse(right)
+ locals_1 = locals()
+ locals_2 = locals_1.copy()
+ # pylint: disable=exec-used
+ exec(f"{l} = list({r})[{idx}]", _f.f_locals | _f.f_globals, locals_2)
+ for bind in [k for k in locals_2.keys() if k not in locals_1.keys()]:
+ _f.f_globals[bind] = locals_2[bind]
+ # pylint: disable=eval-used
+ return eval(l, locals_2)
+
+def ast_match(p: Expr, expr: Any) -> bool:
+ if isinstance(p, ast.Attribute):
+ if expr[0] != ".":
+ return False
+ return (
+ expr[2] == p.attr
+ and ast_match(p.value, expr[1]))
+ elif isinstance(p, ast.Name):
+ return p.id == expr
+ elif isinstance(p, str):
+ return p == expr
+ else:
+ raise RuntimeError(f"Can't compare: {p} == {expr}")
+
+def try_pytest_mark(p: Expr) -> Optional[Expr]:
+ if not isinstance(p, list):
+ return None
+ if not len(p) == 1:
+ return None
+ p0 = p[0]
+ if not isinstance(p0, ast.FunctionDef):
+ return None
+ if not len(p0.decorator_list) == 1:
+ return None
+ decorator = p0.decorator_list[0]
+ if ast_match(decorator.func, (".", (".", "pytest", "mark"), "parametrize")):
+ assert len(decorator.args) == 2
+ return [ast.Name(decorator.args[0].value), decorator.args[1]]
+ return None
+
+def to_elisp(code: str, _f: Optional[FrameType] = None) -> str:
+ _f = _f or top_level()
+ with io.StringIO() as buf, redirect_stdout(buf):
+ # pylint: disable=eval-used
+ print_elisp(eval(code, _f.f_locals | _f.f_globals))
+ return buf.getvalue().strip()
+
+def translate(code: str, _f: Optional[FrameType] = None, use_in_expr: bool = False) -> Any:
+ _f = _f or sys._getframe().f_back
+ parsed = ast.parse(code, mode="exec").body
+ in_expr = try_in_expr(parsed)
+ if use_in_expr and in_expr:
+ (left, right) = in_expr
+ out = to_elisp(ast.unparse(right), _f)
+ nc = f"print('''{out}''')\n'select'"
+ return ast.parse(nc).body
+ elif has_return(parsed):
+ r = tr_returns(parsed)
+ assert isinstance(r, list)
+ return wrap_return(r)
+ else:
+ return parsed
+
+class EvalResult(TypedDict):
+ res: str
+ binds: Dict[str, str]
+ out: str
+ err: Optional[str]
+
+def eval_code(_code: str, _env: Dict[str, Any] = {}) -> EvalResult:
+ _res = "unset"
+ binds = {}
+ out = ""
+ err: Optional[str] = None
+ _f = _env.get("frame", sys._getframe().f_back)
+ if "fname" in _env:
+ _f.f_globals["__file__"] = _env["fname"]
+ try:
+ _code = _code or slurp(_env["code"])
+ new_code = translate(_code, _f, _env.get("use-in-expr", False))
+ (*butlast, last) = new_code
+ _locals = {}
+ locals_1 = _locals
+ locals_2 = locals_1.copy()
+ locals_globals = _f.f_locals | _f.f_globals
+ if "debug" in _env:
+ print(f"{ast.unparse(last)=}")
+ with io.StringIO() as buf, redirect_stdout(buf):
+ if butlast:
+ # pylint: disable=exec-used
+ exec(ast.unparse(butlast), locals_globals, locals_2)
+ for bind in [k for k in locals_2.keys() if k not in locals_1.keys()]:
+ _f.f_globals[bind] = locals_2[bind]
+ try:
+ # pylint: disable=eval-used
+ _res = eval(ast.unparse(last), locals_globals, locals_2)
+ except SyntaxError:
+ locals_1 = _locals
+ locals_2 = locals_1.copy()
+ exec(ast.unparse(last), locals_globals, locals_2)
+ out = buf.getvalue().strip()
+ binds1 = [k for k in locals_2.keys() if k not in locals_1.keys()]
+ for bind in binds1:
+ _f.f_globals[bind] = locals_2[bind]
+ binds2 = [bind for bind in binds1 if bind not in ["__res__", "__return__"]]
+ print_fn = cast(Callable[..., str], to_str if _env.get("echo") else str)
+ binds = {bind: print_fn(locals_2[bind]) for bind in binds2}
+ # except RuntimeError as e:
+ # if str(e) == "break":
+ # pm()
+ # else:
+ # raise
+ # pylint: disable=broad-except
+ except Exception as e:
+ err = f"{e.__class__.__name__}: {e}\n{e.__dict__}"
+ _f.f_globals["e"] = e
+ locs = e.__traceback__.tb_frame.f_locals.get("locals_2", {})
+ for bind in locs:
+ _f.f_globals[bind] = locs[bind]
+ return {
+ "res": to_str(_res) if _env.get("echo") else repr(_res),
+ "binds": binds,
+ "out": out,
+ "err": err
+ }
+
+def eval_to_json(code: str, env: Dict[str, Any] = {}) -> None:
+ try:
+ env["frame"] = sys._getframe().f_back
+ s = json.dumps(eval_code(code, env))
+ print(s)
+ # pylint: disable=broad-except
+ except Exception as e:
+ print(json.dumps({
+ "res": None,
+ "binds": {},
+ "out": "",
+ "err": str(e)}))
+
+def find_module(fname: str) -> Optional[ModuleType]:
+ for (name, module) in sys.modules.items():
+ if getattr(module, "__file__", None) == fname:
+ return module
+ return None
+
+def generate_import(code_fname: str, buffer_fname: str) -> None:
+ code = slurp(code_fname)
+ parsed = ast.parse(code).body[0]
+ if isinstance(parsed, ast.FunctionDef):
+ name = parsed.name
+ module = find_module(buffer_fname)
+ assert module
+ print(f"from {module.__name__} import {name}")
diff --git a/lispy-test.el b/lispy-test.el
index 97c4e30d..8de14f0f 100644
--- a/lispy-test.el
+++ b/lispy-test.el
@@ -148,9 +148,10 @@ Insert KEY if there's no command."
(should (string= (lispy-with "|(c 3 (b 2 (a 1)))"
(call-interactively #'lispy-toggle-thread-last))
"|(thread-last (a 1) (b 2) (c 3))"))
- (should (string= (lispy-with "|(equal 1443070800.0\n (ts-unix\n (ts-parse-org-element\n (org-element-context))))"
- (lispy-toggle-thread-last))
- "|(thread-last (org-element-context)\n (ts-parse-org-element)\n (ts-unix)\n (equal 1443070800.0))"))
+ (should (string-match-p
+ "|(thread-last (org-element-context)\n +(ts-parse-org-element)\n +(ts-unix)\n +(equal 1443070800.0))"
+ (lispy-with "|(equal 1443070800.0\n (ts-unix\n (ts-parse-org-element\n (org-element-context))))"
+ (lispy-toggle-thread-last))))
(should (string= (lispy-with "|(thread-last (org-element-context)\n (ts-parse-org-element)\n (ts-unix)\n (equal 1443070800.0))"
(lispy-toggle-thread-last))
"|(equal 1443070800.0\n (ts-unix\n (ts-parse-org-element\n (org-element-context))))")))
@@ -2549,24 +2550,21 @@ Insert KEY if there's no command."
lispy--eval-cond-msg))))
(ert-deftest lispy-eval-other-window ()
- (setq lispy--eval-sym nil)
- (should (string= (lispy-with-v el "(dolist |(s '(1 2 3))\n (message \"val: %d\" s))"
- (lispy-eval-other-window)) "1"))
- (should (string= (lispy-with-v el "(dolist |(s '(1 2 3))\n (message \"val: %d\" s))"
- (lispy-eval-other-window)) "2"))
- (should (string= (lispy-with-v el "(dolist |(s '(1 2 3))\n (message \"val: %d\" s))"
- (lispy-eval-other-window)) "3"))
- (should (string= (lispy-with-v el "(dolist |(s '(1 2 3))\n (message \"val: %d\" s))"
- (lispy-eval-other-window)) "nil"))
- (should (string= (lispy-with-v el "(dolist |(s '(1 2 3))\n (message \"val: %d\" s))"
- (lispy-eval-other-window)) "1"))
- (setq lispy--eval-sym nil)
- (should (string= (lispy-with-v el "(mapcar |(lambda (s) (* s s)) '(1 2))"
- (lispy-eval-other-window)) "1"))
- (should (string= (lispy-with-v el "(mapcar |(lambda (s) (* s s)) '(1 2))"
- (lispy-eval-other-window)) "2"))
- (should (string= (lispy-with-v el "(mapcar |(lambda (s) (* s s)) '(1 2))"
- (lispy-eval-other-window)) "nil")))
+ (unless noninteractive
+ (cl-labels ((p (keys)
+ (setq s nil)
+ (setq unread-command-events (listify-key-sequence (kbd keys)))
+ (lispy-eval-other-window)))
+ (should (string= (lispy-with-v el "(dolist |(s '(1 2 3))\n (message \"val: %d\" s))"
+ (p "C-m")) "1"))
+ (should (string= (lispy-with-v el "(dolist |(s '(1 2 3))\n (message \"val: %d\" s))"
+ (p "C-n C-m")) "2"))
+ (should (string= (lispy-with-v el "(dolist |(s '(1 2 3))\n (message \"val: %d\" s))"
+ (p "C-n C-n C-m")) "3"))
+ (should (string= (lispy-with-v el "(mapcar |(lambda (s) (* s s)) '(1 2))"
+ (p "C-m")) "1"))
+ (should (string= (lispy-with-v el "(mapcar |(lambda (s) (* s s)) '(1 2))"
+ (p "C-n C-m")) "2")))))
(ert-deftest lispy-ace-char ()
(should (string= (lispy-with "|(cons 'norwegian 'blue)"
@@ -3267,36 +3265,18 @@ Insert KEY if there's no command."
"\nif cond1:\n |if cond2:\n expr1\n if cond3:\n expr2\n else:\n expr3\n else:\n expr4\nelse:\n expr5"
(lispy-eval-python-str))
"if cond2:\n expr1\n if cond3:\n expr2\n else:\n expr3\n else:\n expr4"))
- (should (equal (lispy-with-v py
- "|s = (\n \"this \"\n \"is \"\n \"a string\")"
- (lispy-eval-python-str))
- "s = ( \"this \" \"is \" \"a string\")"))
(should (equal (lispy-with-v py
"|@up_down\ndef greet(name):\n return \"my oh my, {}\".format(name)\n\ndef other():\n pass"
(let ((forward-sexp-function nil))
(lispy-eval-python-str)))
"@up_down\ndef greet(name):\n return \"my oh my, {}\".format(name)"))
- (should (equal (lispy-with-v py
- "|scores = np.array([[1, 2, 3, 6],\n [2, 4, 5, 6],\n [3, 8, 7, 6]])"
- (let ((forward-sexp-function nil))
- (lispy-eval-python-str)))
- "scores = np.array([[1, 2, 3, 6], [2, 4, 5, 6], [3, 8, 7, 6]])"))
- (should (equal (lispy-with-v py
- "|scores = np.array([[1, 2, 3, 6],\\\n [2, 4, 5, 6],\\\n [3, 8, 7, 6]])"
- (let ((forward-sexp-function nil))
- (lispy-eval-python-str)))
- "scores = np.array([[1, 2, 3, 6], [2, 4, 5, 6], [3, 8, 7, 6]])"))
(unless (version< emacs-version "24.4.1")
(should (equal (progn
;; skip initialization msg
- (lispy--eval-python "")
+ (lispy--eval-python-plain "")
(sit-for 0.1)
(lispy--eval-python "print(\"one\")\nprint(\"two\")\nx = 2 + 1"))
- "one\ntwo\n3")))
- (should (equal (lispy-with-v py
- "def func ():\n |v = Foo.bar (\n Foo.baz,\n self.comp, xrt)\n x = 0"
- (lispy-eval-python-str))
- "v = Foo.bar ( Foo.baz, self.comp, xrt)")))
+ "x = 3"))))
(ert-deftest lispy-python-symbol-bnd ()
(should (equal (lispy-with-v py "def test_detector ():\n detector.getChannelCount ().|"
@@ -3350,21 +3330,21 @@ Insert KEY if there's no command."
;; (should (string= (lispy--python-eval-string-dwim "(x, i) in enumerate(lvl_npoints)")
;; "(x, i) = list (enumerate(lvl_npoints))[0]\nprint (((x, i)))"))
(should (string= (lispy--python-eval-string-dwim "asdf_123")
- "print(repr(asdf_123))"))
+ "print(repr((asdf_123)))"))
(should (string= (let ((this-command 'lispy-eval))
(lispy--python-eval-string-dwim "asdf_123"))
- "lp.pprint(asdf_123)"))
+ "lp.pprint((asdf_123))"))
(should (string= (let ((this-command 'lispy-eval))
(lispy--python-eval-string-dwim "x[\"foo\"] = 2 + 2"))
- "x[\"foo\"] = 2 + 2\nlp.pprint(x[\"foo\"])")))
+ "x[\"foo\"] = 2 + 2\nlp.pprint((x[\"foo\"]))")))
(ert-deftest lispy-extended-eval-str ()
- (should (string=
- (lispy-with-v py
- "|x = (\n \"abc\" +\n \"def\" +\n \"foo\" + \"bar\")"
- (lispy-extended-eval-str
- (cons (line-beginning-position) (line-end-position))))
- "x = ( \"abc\" + \"def\" + \"foo\" + \"bar\")"))
+ ;; (should (string=
+ ;; (lispy-with-v py
+ ;; "|x = (\n \"abc\" +\n \"def\" +\n \"foo\" + \"bar\")"
+ ;; (lispy-extended-eval-str
+ ;; (cons (line-beginning-position) (line-end-position))))
+ ;; "x = ( \"abc\" + \"def\" + \"foo\" + \"bar\")"))
(should (string=
(lispy-with-v py
"|print(\"((\", end=\"\")\nprint(\" \".join(['\"' + arg + '\"' for arg in arg_info.args]))"
diff --git a/lispy.el b/lispy.el
index 5ba27b1e..f2322482 100644
--- a/lispy.el
+++ b/lispy.el
@@ -140,6 +140,7 @@
(require 'ediff-util)
(require 'semantic)
(require 'semantic/db))
+(require 'zoutline)
(require 'mode-local)
(require 'lispy-tags)
(require 'help-fns)
@@ -151,7 +152,6 @@
(require 'lispy-inline)
(setq iedit-toggle-key-default nil)
(require 'delsel)
-(require 'swiper)
(require 'pcase)
(require 'hydra)
(eval-after-load 'cider '(require 'le-clojure))
@@ -219,7 +219,7 @@ The hint will consist of the possible nouns that apply to the verb."
:group 'lispy)
(defcustom lispy-close-quotes-at-end-p nil
- "If t, when pressing the `\"' at the end of a quoted string, it will move you past the end quote."
+ "If t, when pressing `\"' at the end of a quoted string, move past the end quote."
:type 'boolean
:group 'lispy)
@@ -278,6 +278,12 @@ The hint will consist of the possible nouns that apply to the verb."
"Keys for jumping."
:type '(repeat :tag "Keys" (character :tag "char")))
+(declare-function counsel-imenu "ext:counsel")
+
+(defcustom lispy-imenu-function #'counsel-imenu
+ "Function used to jump to an imenu entry."
+ :type 'function)
+
(defface lispy-command-name-face
'((((class color) (background light))
:background "#d8d8f7" :inherit font-lock-function-name-face)
@@ -451,6 +457,13 @@ Depends on `default-directory'."
(lispy--normalize-files
(all-completions "" #'read-file-name-internal)))))))
+(defconst lispy-font-lock-keywords
+ '(("^;; ?\\(\\*\\|\\*[^*\n].*\\)$" 1 'org-level-1 prepend)
+ ("^;; ?\\(\\*\\{2\\}\\|\\*\\{2\\}[^*\n].*\\)$" 1 'org-level-2 prepend)
+ ("^;; ?\\(\\*\\{3\\}\\|\\*\\{3\\}[^*\n].*\\)$" 1 'org-level-3 prepend)
+ ("^;; ?\\(\\*\\{4\\}\\|\\*\\{4\\}[^*\n].*\\)$" 1 'org-level-4 prepend)
+ ("^;; ?\\(\\*\\{5\\}\\|\\*\\{5\\}[^*\n].*\\)$" 1 'org-level-5 prepend)))
+
;;;###autoload
(define-minor-mode lispy-mode
"Minor mode for navigating and editing LISP dialects.
@@ -492,8 +505,8 @@ backward through lists, which is useful to move into special.
((eq major-mode 'clojure-mode)
(eval-after-load 'le-clojure
'(setq completion-at-point-functions
- '(lispy-clojure-complete-at-point
- cider-complete-at-point)))
+ '(lispy-clojure-complete-at-point
+ cider-complete-at-point)))
(setq-local outline-regexp (substring lispy-outline 1)))
((eq major-mode 'python-mode)
(setq-local lispy-outline "^#\\*+")
@@ -504,11 +517,15 @@ backward through lists, which is useful to move into special.
(setq-local outline-regexp (substring lispy-outline 1))))
(when (called-interactively-p 'any)
(mapc #'lispy-raise-minor-mode
- (cons 'lispy-mode lispy-known-verbs))))
+ (cons 'lispy-mode lispy-known-verbs)))
+ (font-lock-add-keywords major-mode lispy-font-lock-keywords))
(when lispy-old-outline-settings
(setq outline-regexp (car lispy-old-outline-settings))
(setq outline-level (cdr lispy-old-outline-settings))
- (setq lispy-old-outline-settings nil))))
+ (setq lispy-old-outline-settings nil))
+ (font-lock-remove-keywords major-mode lispy-font-lock-keywords)))
+
+
(defun lispy-raise-minor-mode (mode)
"Make MODE the first on `minor-mode-map-alist'."
@@ -581,6 +598,9 @@ Otherwise return the amount of times executed."
(nthcdr (1- ,n) (prog1 ,lst (setq ,lst (nthcdr ,n ,lst))))
nil))))
+(defvar lispy-whitespace-types '(clojure-commas)
+ "List of tokens to treat as whitespace")
+
(defvar lispy-site-directory (file-name-directory
load-file-name)
"The directory where all of the lispy files are located.")
@@ -1138,7 +1158,10 @@ If position isn't special, move to previous or error."
(line-beginning-position)))
(delete-char 1)
(backward-delete-char 1))
- ((lispy-left-p)
+ ((and (lispy-left-p)
+ (if (memq major-mode lispy-elisp-modes)
+ (not (eq (char-after) ?\{))
+ t))
(if (progn
(setq bnd (lispy--bounds-list))
(> (count-lines (car bnd) (cdr bnd)) 1))
@@ -1162,7 +1185,7 @@ If position isn't special, move to previous or error."
(while (and (< (point) end)
(ignore-errors
(forward-sexp 1)
- (skip-chars-forward " ")
+ (skip-chars-forward " ,")
t))
(when (setq bnd (lispy--bounds-comment))
(goto-char (cdr bnd))))
@@ -1199,8 +1222,8 @@ If position isn't special, move to previous or error."
'kill-region
last-command)))
(lispy-dotimes arg
- (when (lispy--in-comment-p)
- (skip-chars-backward " \n"))
+ (when (and (lispy--in-comment-p) (looking-back ";[ \n]+" (point-min)))
+ (goto-char (1+ (match-beginning 0))))
(if (memq (char-syntax (char-before))
'(?w ?_ ?\s))
(if (lispy-looking-back "\\_<\\s_+")
@@ -1667,26 +1690,29 @@ When ARG is more than 1, mark ARGth element."
(t
(lispy--mark (lispy--bounds-dwim))))))
+(defvar lispy-kill-at-point-hook nil)
+
(defun lispy-kill-at-point ()
"Kill the quoted string or the list that includes the point."
(interactive)
- (cond ((region-active-p)
- (lispy--maybe-safe-kill-region (region-beginning)
- (region-end)))
- ((derived-mode-p 'text-mode)
- (let ((beg (save-excursion
- (1+ (re-search-backward "[ \t\n]" nil t))))
- (end (save-excursion
- (1- (re-search-forward "[ \t\n]" nil t)))))
- (kill-region beg end)))
- (t
- (let ((bounds (or (lispy--bounds-comment)
- (lispy--bounds-string)
- (lispy--bounds-list))))
- (if buffer-read-only
- (kill-new (buffer-substring
- (car bounds) (cdr bounds)))
- (kill-region (car bounds) (cdr bounds)))))))
+ (cond
+ ((run-hook-with-args-until-success 'lispy-kill-at-point-hook))
+ ((region-active-p)
+ (lispy--maybe-safe-kill-region (region-beginning)
+ (region-end)))
+ (t
+ (let ((bnd (or (lispy--bounds-comment)
+ (lispy--bounds-string)
+ (lispy--bounds-list)
+ (and (derived-mode-p 'text-mode)
+ (cons (save-excursion
+ (1+ (re-search-backward "[ \t\n]" nil t)))
+ (save-excursion
+ (1- (re-search-forward "[ \t\n]" nil t))))))))
+ (if buffer-read-only
+ (kill-new (buffer-substring
+ (car bnd) (cdr bnd)))
+ (kill-region (car bnd) (cdr bnd)))))))
(defun lispy-new-copy ()
"Copy marked region or sexp to kill ring."
@@ -1868,7 +1894,7 @@ otherwise the whole string is unquoted."
(self-insert-command 1))
(t
- (lispy--space-unless "^\\|\\s-\\|\\s(\\|[#]")
+ (lispy--space-unless "^\\|\\s-\\|\\s(\\|[#][a-zA-Z]?")
(insert "\"\"")
(unless (looking-at "\n\\|)\\|}\\|\\]\\|$")
(just-one-space)
@@ -1949,7 +1975,8 @@ colon following `lispy-colon-no-space-regex'. To disable this
behavior, set this variable to nil.")
(defvar lispy-colon-no-space-regex
- '((lisp-mode . "\\s-\\|[:^?#]\\|ql\\|\\(?:\\s([[:word:]-]*\\)"))
+ '((lisp-mode . "\\s-\\|[:^?#]\\|ql\\|\\(?:\\(\\s(\\|'\\)[[:word:]-]*\\)")
+ (slime-repl-mode . ""))
"Overrides REGEX that `lispy-colon' will consider for `major-mode'.
`lispy-colon' will insert \" :\" instead of \":\" unless
`lispy-no-space' is t or `looking-back' REGEX.")
@@ -2073,6 +2100,7 @@ When the region is active, toggle a ~ at the start of the region."
(declare-function slime-repl-return "ext:slime-repl")
(declare-function sly-mrepl-return "ext:sly-mrepl")
(declare-function racket-repl-submit "ext:racket-repl")
+(declare-function geiser-repl-maybe-send "ext:geiser-repl")
(defun lispy-newline-and-indent-plain ()
"When in minibuffer, exit it. Otherwise forward to `newline-and-indent'."
(interactive)
@@ -2094,6 +2122,8 @@ When the region is active, toggle a ~ at the start of the region."
(ielm-return))
(racket-repl-mode
(racket-repl-submit))
+ (geiser-repl-mode
+ (geiser-repl-maybe-send))
(t
(if (and (not (lispy--in-string-or-comment-p))
(if (memq major-mode lispy-clojure-modes)
@@ -2130,10 +2160,13 @@ When ARG is nagative, add them above instead"
(defun lispy-meta-return ()
"Insert a new heading."
(interactive)
- (let ((pt (point)))
+ (let ((pt (point))
+ (lvl (lispy-outline-level)))
(cond ((lispy--in-comment-p)
- (end-of-line)
- (newline))
+ (goto-char (cdr (zo-bnd-subtree)))
+ (when (looking-back "\n+" (point-min))
+ (delete-region (match-beginning 0) (match-end 0)))
+ (insert "\n\n"))
((and (lispy-bolp)
(looking-at " *$"))
(delete-region
@@ -2148,12 +2181,11 @@ When ARG is nagative, add them above instead"
(forward-list 1)
(newline))
(newline)
- (backward-char 1)))))
- (insert lispy-outline-header
- (make-string (max (lispy-outline-level) 1)
- ?\*)
- " ")
- (beginning-of-line))
+ (backward-char 1))))
+ (insert lispy-outline-header
+ (make-string (max lvl 1) ?\*)
+ " ")
+ (beginning-of-line)))
(defun lispy-alt-line (&optional N)
"Do a context-aware exit, then `newline-and-indent', N times.
@@ -2232,197 +2264,6 @@ to all the functions, while maintaining the parens in a pretty state."
(iedit-mode 0)
(iedit-mode))))
-;;* Locals: navigation
-;;** Occur
-(defcustom lispy-occur-backend 'ivy
- "Method to navigate to a line with `lispy-occur'."
- :type '(choice
- (const :tag "Ivy" ivy)
- (const :tag "Helm" helm)))
-
-(defvar lispy--occur-beg 1
- "Start position of the top level sexp during `lispy-occur'.")
-
-(defvar lispy--occur-end 1
- "End position of the top level sexp during `lispy-occur'.")
-
-(defun lispy--occur-candidates (&optional bnd)
- "Return the candidates for `lispy-occur'."
- (setq bnd (or bnd (save-excursion
- (unless (and (bolp)
- (lispy-left-p))
- (beginning-of-defun))
- (lispy--bounds-dwim))))
- (let ((line-number -1)
- candidates)
- (setq lispy--occur-beg (car bnd))
- (setq lispy--occur-end (cdr bnd))
- (save-excursion
- (goto-char lispy--occur-beg)
- (while (< (point) lispy--occur-end)
- (push (format "%-3d %s"
- (cl-incf line-number)
- (buffer-substring
- (line-beginning-position)
- (line-end-position)))
- candidates)
- (forward-line 1)))
- (nreverse candidates)))
-
-(defun lispy--occur-preselect ()
- "Initial candidate regex for `lispy-occur'."
- (format "^%d"
- (-
- (line-number-at-pos (point))
- (line-number-at-pos lispy--occur-beg))))
-
-(defvar helm-input)
-(declare-function helm "ext:helm")
-
-(defun lispy-occur-action-goto-paren (x)
- "Goto line X for `lispy-occur'."
- (setq x (read x))
- (goto-char lispy--occur-beg)
- (let ((input (if (eq lispy-occur-backend 'helm)
- helm-input
- ivy-text))
- str-or-comment)
- (cond ((string= input "")
- (forward-line x)
- (back-to-indentation)
- (when (re-search-forward lispy-left (line-end-position) t)
- (goto-char (match-beginning 0))))
-
- ((setq str-or-comment
- (progn
- (forward-line x)
- (re-search-forward (ivy--regex input)
- (line-end-position) t)
- (lispy--in-string-or-comment-p)))
- (goto-char str-or-comment))
-
- ((re-search-backward lispy-left (line-beginning-position) t)
- (goto-char (match-beginning 0)))
-
- ((re-search-forward lispy-left (line-end-position) t)
- (goto-char (match-beginning 0)))
-
- (t
- (back-to-indentation)))))
-
-(defun lispy-occur-action-goto-end (x)
- "Goto line X for `lispy-occur'."
- (setq x (read x))
- (goto-char lispy--occur-beg)
- (forward-line x)
- (re-search-forward (ivy--regex ivy-text) (line-end-position) t))
-
-(defun lispy-occur-action-goto-beg (x)
- "Goto line X for `lispy-occur'."
- (when (lispy-occur-action-goto-end x)
- (goto-char (match-beginning 0))))
-
-(defun lispy-occur-action-mc (_x)
- "Make a fake cursor for each `lispy-occur' candidate."
- (let ((cands (nreverse ivy--old-cands))
- cand)
- (while (setq cand (pop cands))
- (goto-char lispy--occur-beg)
- (forward-line (read cand))
- (re-search-forward (ivy--regex ivy-text) (line-end-position) t)
- (when cands
- (mc/create-fake-cursor-at-point))))
- (multiple-cursors-mode 1))
-
-(ivy-set-actions
- 'lispy-occur
- '(("m" lispy-occur-action-mc "multiple-cursors")
- ("j" lispy-occur-action-goto-beg "goto start")
- ("k" lispy-occur-action-goto-end "goto end")))
-
-(defvar ivy-last)
-(declare-function ivy-state-window "ext:ivy")
-
-(defun lispy-occur ()
- "Select a line within current top level sexp.
-See `lispy-occur-backend' for the selection back end."
- (interactive)
- (swiper--init)
- (cond ((eq lispy-occur-backend 'helm)
- (require 'helm)
- (add-hook 'helm-move-selection-after-hook
- #'lispy--occur-update-input-helm)
- (add-hook 'helm-update-hook
- #'lispy--occur-update-input-helm)
- (unwind-protect
- (helm :sources
- `((name . "this defun")
- (candidates . ,(lispy--occur-candidates))
- (action . lispy-occur-action-goto-paren)
- (match-strict .
- (lambda (x)
- (ignore-errors
- (string-match
- (ivy--regex helm-input) x)))))
- :preselect (lispy--occur-preselect)
- :buffer "*lispy-occur*")
- (swiper--cleanup)
- (remove-hook 'helm-move-selection-after-hook
- #'lispy--occur-update-input-helm)
- (remove-hook 'helm-update-hook
- #'lispy--occur-update-input-helm)))
- ((eq lispy-occur-backend 'ivy)
- (unwind-protect
- (ivy-read "pattern: "
- (lispy--occur-candidates)
- :preselect (lispy--occur-preselect)
- :require-match t
- :update-fn (lambda ()
- (lispy--occur-update-input
- ivy-text
- (ivy-state-current ivy-last)))
- :action #'lispy-occur-action-goto-paren
- :caller 'lispy-occur)
- (swiper--cleanup)
- (when (null ivy-exit)
- (goto-char swiper--opoint))))
- (t
- (error "Bad `lispy-occur-backend': %S" lispy-occur-backend))))
-
-(defun lispy--occur-update-input-helm ()
- "Update selection for `lispy-occur' using `helm' back end."
- (lispy--occur-update-input
- helm-input
- (buffer-substring-no-properties
- (line-beginning-position)
- (line-end-position))))
-
-(defun lispy--occur-update-input (input str)
- "Update selection for `ivy-occur'.
-INPUT is the current input text.
-STR is the full current candidate."
- (swiper--cleanup)
- (let ((re (ivy--regex input))
- (num (if (string-match "^[0-9]+" str)
- (string-to-number (match-string 0 str))
- 0)))
- (with-selected-window (ivy-state-window ivy-last)
- (goto-char lispy--occur-beg)
- (when (cl-plusp num)
- (forward-line num)
- (unless (<= (point) lispy--occur-end)
- (recenter)))
- (let ((ov (make-overlay (line-beginning-position)
- (1+ (line-end-position)))))
- (overlay-put ov 'face 'swiper-line-face)
- (overlay-put ov 'window (ivy-state-window ivy-last))
- (push ov swiper--overlays))
- (re-search-forward re (line-end-position) t)
- (swiper--add-overlays
- re
- lispy--occur-beg
- lispy--occur-end))))
-
;;* Locals: Paredit transformations
(defun lispy--sub-slurp-forward (arg)
"Grow current marked symbol by ARG words forwards.
@@ -2630,7 +2471,11 @@ If indenting does not adjust indentation or move the point, call
(bnd (when (region-active-p)
(cons (region-beginning)
(region-end)))))
- (indent-for-tab-command)
+ ;; the current TAB may not always be `indent-for-tab-command'
+ (cond
+ ((memq major-mode '(minibuffer-mode minibuffer-inactive-mode))
+ (completion-at-point))
+ (t (indent-for-tab-command)))
(when (and (= tick (buffer-chars-modified-tick))
(= pt (point)))
(if bnd
@@ -3632,8 +3477,8 @@ When ARG is `fill', do nothing for short expressions."
(lispy--insert res))))))
(defvar-local lispy--multiline-take-3
- '(defvar defun defmacro defcustom defgroup defvar-local declare-function
- define-key nth throw define-error defadvice defhydra defsubst)
+ '(defvar defun defmacro defcustom defgroup defvar-local declare-function
+ define-key nth throw define-error defadvice defhydra defsubst)
"List of constructs for which the first 3 elements are on the first line.")
(setq-mode-local
@@ -3677,18 +3522,31 @@ The third one is assumed to be the arglist and will not be changed.")
"List of constructs for which the first 2 elements are on the first line.
The second one will not be changed.")
-(defun lispy-interleave (x lst &optional step)
+(defmacro lispy--slurp-whitespace (from to)
+ "Move any leading whitespace in FROM into TO."
+ `(while (member (cadar ,from) lispy-whitespace-types)
+ (setq ,to (cons (car ,from) ,to))
+ (setq ,from (cdr ,from))))
+
+(defun lispy-interleave (x lst &optional step slurp-whitespace)
"Insert X in between each element of LST.
Don't insert X when it's already there.
-When STEP is non-nil, insert in between each STEP elements instead."
+When STEP is non-nil, insert in between each STEP elements instead.
+When SLURP-WHITESPACE is non-nil, add any whitespace following split into previous line."
(setq step (or step 1))
(let ((res (nreverse (lispy-multipop lst step)))
item)
+ (if slurp-whitespace
+ (lispy--slurp-whitespace lst res))
(while lst
(unless (equal (car res) x)
(push x res))
(unless (equal (car res)
(car (setq item (lispy-multipop lst step))))
+ (when slurp-whitespace
+ (setq item (nreverse item))
+ (lispy--slurp-whitespace lst item)
+ (setq item (nreverse item)))
(setq res (nconc (nreverse item) res))))
(nreverse res)))
@@ -3726,7 +3584,8 @@ When QUOTED is not nil, assume that EXPR is quoted and ignore some rules."
(list 'ly-raw (cadr expr)
(lispy-interleave '(ly-raw newline)
(mapcar #'lispy--multiline-1 (cl-caddr expr))
- 2)))
+ 2
+ t)))
((and (eq (car-safe expr) 'ly-raw)
(eq (nth 1 expr) 'splice))
(list 'ly-raw (nth 1 expr) (nth 2 expr) (nth 3 expr)
@@ -4193,8 +4052,6 @@ When ARG isn't nil, call `lispy-goto-projectile' instead."
(mapcar #'lispy--format-tag-line candidates))
#'lispy--action-jump)))
-(declare-function counsel-imenu "ext:counsel")
-
(defun lispy-goto-local (&optional arg)
"Jump to symbol within current file.
When ARG is non-nil, force a reparse."
@@ -4207,7 +4064,7 @@ When ARG is non-nil, force a reparse."
(lispy--fetch-tags (list (buffer-file-name))))
#'lispy--action-jump))
(no-semantic-support
- (counsel-imenu))))
+ (funcall lispy-imenu-function))))
(defun lispy-goto-elisp-commands (&optional arg)
"Jump to Elisp commands within current file.
@@ -4286,8 +4143,7 @@ SYMBOL is a string."
(lispy--current-function))))
(lispy--remember)
(deactivate-mark)
- (with-no-warnings
- (ring-insert find-tag-marker-ring (point-marker)))
+ (xref-push-marker-stack)
(let ((narrowedp (buffer-narrowed-p)))
(when narrowedp
(widen))
@@ -4353,11 +4209,13 @@ SYMBOL is a string."
le-clojure lispy-eval-clojure)
(python-mode
le-python lispy--eval-python lispy-eval-python-str lispy-eval-python-bnd)
+ (js2-mode
+ le-js lispy--eval-js lispy--eval-js-str)
(julia-mode
le-julia lispy-eval-julia lispy-eval-julia-str)
(racket-mode
le-racket lispy--eval-racket)
- (scheme-mode
+ ((scheme-mode geiser-repl-mode)
le-scheme lispy--eval-scheme)
(lisp-mode
le-lisp lispy--eval-lisp)
@@ -4373,7 +4231,7 @@ SYMBOL is a string."
(define-error 'eval-error "Eval error")
-(defun lispy-eval (arg)
+(defun lispy-eval (arg &optional e-str)
"Eval the current sexp and display the result.
When ARG is 2, insert the result as a comment.
When at an outline, eval the outline."
@@ -4386,7 +4244,7 @@ When at an outline, eval the outline."
(looking-at lispy-outline-header))
(lispy-eval-outline))
(t
- (let ((res (lispy--eval nil)))
+ (let ((res (lispy--eval e-str)))
(when (memq major-mode lispy-clojure-modes)
(setq res (lispy--clojure-pretty-string res)))
(when lispy-eval-output
@@ -4493,38 +4351,46 @@ Return the result of the last evaluation as a string."
(cons beg end))))
(defun lispy-eval-single-outline ()
- (let* ((pt (point))
- (bnd (lispy--eval-bounds-outline))
- (res
- (lispy--eval-dwim bnd)))
- (when lispy-eval-output
- (setq res (concat lispy-eval-output res)))
- (cond ((equal res "")
- (message "(ok)"))
- ((= ?: (char-before (line-end-position)))
- (goto-char (cdr bnd))
- (save-restriction
- (narrow-to-region
- (point)
- (if (re-search-forward outline-regexp nil t)
- (1- (match-beginning 0))
- (point-max)))
- (goto-char (point-min))
- (unless (looking-at (concat "\n" lispy-outline-header))
- (newline))
- (lispy--insert-eval-result res))
- (goto-char pt)
- res)
- (t
- (message (replace-regexp-in-string "%" "%%" res))))))
+ (condition-case e
+ (let* ((pt (point))
+ (bnd (lispy--eval-bounds-outline))
+ (res
+ (lispy--eval-dwim bnd t)))
+ (when lispy-eval-output
+ (unless (looking-at ".*::$")
+ (setq res (concat lispy-eval-output res))))
+ (cond ((equal res "")
+ (message "(ok)"))
+ ((= ?: (char-before (line-end-position)))
+ (goto-char (cdr bnd))
+ (save-restriction
+ (narrow-to-region
+ (point)
+ (if (re-search-forward outline-regexp nil t)
+ (1- (match-beginning 0))
+ (point-max)))
+ (goto-char (point-min))
+ (unless (looking-at (concat "\n" lispy-outline-header))
+ (newline))
+ (lispy--insert-eval-result res))
+ (goto-char pt)
+ res)
+ (t
+ (lispy-message (replace-regexp-in-string "%" "%%" res)))))
+
+ (eval-error
+ (lispy-message (cdr e)))))
(defvar lispy-message-limit 4000
"String length limit for `lispy-message' to pop up a window.
For smaller strings `message' is used.")
+(defvar lispy-last-message nil)
+
(defun lispy-message (str &optional popup)
"Display STR in the echo area.
If STR is too large, pop it to a buffer instead."
+ (setq lispy-last-message str)
(if (or
popup
(> (length str) lispy-message-limit)
@@ -4536,8 +4402,9 @@ If STR is too large, pop it to a buffer instead."
(special-mode)
(let ((inhibit-read-only t))
(delete-region (point-min) (point-max))
- (insert str)
- (ignore-errors (pp-buffer))
+ (insert (ansi-color-apply str))
+ ;; (unless (> (length str) 2000)
+ ;; (ignore-errors (pp-buffer)))
(goto-char (point-min))
(while (re-search-forward "\\\\n" nil t)
(replace-match "\n" nil t))
@@ -4547,18 +4414,22 @@ If STR is too large, pop it to a buffer instead."
(message str)
(error (message (replace-regexp-in-string "%" "%%" str))))))
-(defun lispy--eval-dwim (&optional bnd)
- (let ((eval-str
- (cond (bnd
- (lispy--string-dwim bnd))
- ((eq major-mode 'python-mode)
- (lispy-eval-python-str))
- (t
- (lispy--string-dwim)))))
- (if (string= "" eval-str)
+(defun lispy--eval-dwim (&optional bnd outline-p)
+ (let* ((eval-str-1
+ (cond (bnd
+ (lispy--string-dwim bnd))
+ ((eq major-mode 'python-mode)
+ (lispy-eval-python-str))
+ (t
+ (lispy--string-dwim))))
+ (eval-str-2
+ (if (and outline-p (eq major-mode 'lisp-mode))
+ (concat "(progn " eval-str-1 ")")
+ eval-str-1)))
+ (if (string= "" eval-str-1)
""
(condition-case e
- (lispy--eval eval-str)
+ (lispy--eval eval-str-2)
(eval-error (cdr e))))))
(defun lispy-eval-and-insert ()
@@ -4655,23 +4526,24 @@ If STR is too large, pop it to a buffer instead."
(eq major-mode 'python-mode))
(cond ((< (current-column) 100))
((looking-back "[]}]" (line-beginning-position))
- (let ((cnt (if (string= "]" (match-string 0))
- -1
- -2))
- (beg (save-excursion
- (forward-list -1)
- (1+ (point)))))
- (backward-char 1)
- (ignore-errors
- (while (> (point) beg)
- (if (lispy-after-string-p ">")
- (progn
- (re-search-backward "<" nil t)
- (newline-and-indent)
- (backward-char 3))
- (forward-sexp cnt)
- (when (> (point) beg)
- (newline-and-indent))))))
+ (if (string= "]" (match-string 0))
+ (let ((beg (save-excursion
+ (forward-list -1)
+ (1+ (point)))))
+ (backward-char 1)
+ (ignore-errors
+ (while (> (point) beg)
+ (if (lispy-after-string-p ">")
+ (progn
+ (re-search-backward "<" nil t)
+ (newline-and-indent)
+ (backward-char 3))
+ (forward-sexp -1)
+ (when (> (point) beg)
+ (newline-and-indent))))))
+ (goto-char (point-min))
+ (while (re-search-forward ", " nil t)
+ (newline-and-indent)))
(goto-char (point-max)))
((and (looking-back "[])]\\]" (line-beginning-position))
(eq (point-min)
@@ -4739,6 +4611,10 @@ Unlike `comment-region', ensure a contiguous comment."
(format "%s: nil" (propertize "cond" 'face 'font-lock-keyword-face))
"Message to echo when the current `cond' branch is nil.")
+(defconst lispy--eval-pcase-msg
+ (format "%s: nil" (propertize "pcase" 'face 'font-lock-keyword-face))
+ "Message to echo when the current `pcase' branch is nil.")
+
(defvar lispy-eval-other--window nil
"Target window for `lispy-eval-other-window'.")
@@ -4761,18 +4637,25 @@ Unlike `comment-region', ensure a contiguous comment."
(declare-function aw-select "ext:ace-window")
(defvar aw-dispatch-always)
+(defun lispy--idx-from-list (lst &optional preselect)
+ (read
+ (ivy-read "idx: "
+ (cl-mapcar
+ (lambda (x i)
+ (concat (number-to-string i)
+ " "
+ (if (stringp x)
+ x
+ (prin1-to-string x))))
+ lst
+ (number-sequence 0 (1- (length lst))))
+ :preselect preselect)))
+
(defun lispy--set-sym-from-list (sym lst)
- (let ((idx (read (ivy-read "idx: "
- (cl-mapcar
- (lambda (x i)
- (concat (number-to-string i)
- " "
- (if (stringp x)
- x
- (prin1-to-string x))))
- lst
- (number-sequence 0 (1- (length lst))))
- :preselect (and (boundp sym) (cl-position (symbol-value sym) lst))))))
+ (let ((idx
+ (lispy--idx-from-list
+ lst
+ (and (boundp sym) (cl-position (symbol-value sym) lst)))))
(set sym (nth idx lst))))
(defun lispy--dolist-item-expr (expr)
@@ -4792,6 +4675,22 @@ SYM will take on each value of LST with each eval."
(car lmda))))
(lispy--set-sym-from-list sym lst)))
+(defun lispy--print-object (res)
+ (cond ((member res (list lispy--eval-cond-msg
+ lispy--eval-pcase-msg))
+ (lispy-message res))
+ ((and (fboundp 'object-p) (object-p res))
+ (message "(eieio object length %d)" (length res)))
+ ((and (consp res)
+ (numberp (car res))
+ (numberp (cdr res)))
+ (lispy-message
+ (format "%S\n%s" res
+ (lispy--string-dwim res))))
+ (t
+ (lispy-message
+ (lispy--prin1 res)))))
+
(defun lispy-eval-other-window (&optional arg)
"Eval current expression in the context of other window.
In case the point is on a let-bound variable, add a `setq'.
@@ -4817,28 +4716,18 @@ When ARG is non-nil, force select the window."
(setq lispy-eval-other--cfg nil)
(selected-window))))
res)
- (cond ((memq major-mode '(lisp-mode scheme-mode))
- (lispy-message (lispy--eval (prin1-to-string expr))))
- ((memq major-mode lispy-clojure-modes)
- (lispy-eval 1))
- (t
- (with-selected-window target-window
- (setq res (lispy--eval-elisp-form expr lexical-binding)))
- (cond ((equal res lispy--eval-cond-msg)
- (lispy-message res))
- ((and (fboundp 'object-p) (object-p res))
- (message "(eieio object length %d)" (length res)))
- ((and (memq major-mode lispy-elisp-modes)
- (consp res)
- (numberp (car res))
- (numberp (cdr res)))
- (lispy-message
- (format "%S\n%s" res
- (with-selected-window target-window
- (lispy--string-dwim res)))))
- (t
- (lispy-message
- (lispy--prin1 res))))))))
+ (condition-case e
+ (cond ((memq major-mode '(lisp-mode scheme-mode))
+ (lispy-message (lispy--eval (prin1-to-string expr))))
+ ((memq major-mode lispy-clojure-modes)
+ (lispy-eval 1))
+ (t
+ (with-selected-window target-window
+ (setq res (lispy--eval-elisp-form expr lexical-binding))
+ (kill-new (if (stringp res) res (prin1-to-string res)) t)
+ (lispy--print-object res))))
+ (eval-error
+ (message "%S" e)))))
(defun lispy-follow ()
"Follow to `lispy--current-function'."
@@ -5522,6 +5411,7 @@ Macro used may be customized in `lispy-thread-last-macro', which see."
(iedit-start (iedit-regexp-quote (lispy--string-dwim)) beg end)
(lispy-mark-symbol)
(lispy-move-down 1)
+ (iedit-update-occurrences)
(iedit-mode)
(deactivate-mark)
(lispy-left 1)
@@ -5579,7 +5469,7 @@ The bindings of `lispy-backward' or `lispy-mark-symbol' can also be used."
(let* ((bnd (lispy--bounds-dwim))
(str (lispy--string-dwim bnd))
(kind (lispy--bind-variable-kind))
- (fmt (if (eq major-mode 'clojure-mode)
+ (fmt (if (memq major-mode lispy-clojure-modes)
'("(let [ %s]\n)" . 6)
'("(let (( %s))\n)" . 7))))
(setq lispy-bind-var-in-progress t)
@@ -5882,52 +5772,57 @@ ARG is 4: `eval-defun' on the function from this sexp."
(let* ((ldsi-sxp (lispy--setq-expression))
(ldsi-fun (car ldsi-sxp)))
(cond
- ((memq ldsi-fun '(mapcar mapc mapcan
- cl-remove-if cl-remove-if-not
- cl-find-if cl-find-if-not
- cl-some cl-every cl-any cl-notany))
- (let ((fn (nth 1 ldsi-sxp))
- (lst (nth 2 ldsi-sxp)))
- (when (eq (car-safe fn) 'lambda)
- (set (car (cadr fn)) (car (eval lst)))
- (lispy-flow 2))))
- ((or (functionp ldsi-fun)
- (macrop ldsi-fun))
- (when (eq ldsi-fun 'funcall)
- (setq ldsi-fun (eval (cadr ldsi-sxp)))
- (setq ldsi-sxp (cons ldsi-fun (cddr ldsi-sxp))))
- (let ((ldsi-args
- (copy-sequence
- (help-function-arglist
- (if (ad-is-advised ldsi-fun)
- (ad-get-orig-definition ldsi-fun)
- ldsi-fun)
- t)))
- (ldsi-vals (cdr ldsi-sxp))
- ldsi-arg
- ldsi-val)
- (catch 'done
- (while (setq ldsi-arg (pop ldsi-args))
- (cond ((eq ldsi-arg '&optional)
- (setq ldsi-arg (pop ldsi-args))
- (set ldsi-arg (eval (pop ldsi-vals))))
- ((eq ldsi-arg '&rest)
- (setq ldsi-arg (pop ldsi-args))
- (set ldsi-arg
- (if (functionp ldsi-fun)
- (mapcar #'eval ldsi-vals)
- ldsi-vals))
- (throw 'done t))
- (t
- (setq ldsi-val (pop ldsi-vals))
- (set ldsi-arg
- (if (functionp ldsi-fun)
- (eval ldsi-val)
- ldsi-val))))))
- (lispy-goto-symbol ldsi-fun)))
- (t
- (lispy-complain
- (format "%S isn't a function" ldsi-fun))))))
+ ((memq ldsi-fun '(mapcar mapc mapcan
+ cl-remove-if cl-remove-if-not
+ cl-find-if cl-find-if-not
+ cl-some cl-every cl-any cl-notany))
+ (let ((fn (nth 1 ldsi-sxp))
+ (lst (nth 2 ldsi-sxp)))
+ (when (eq (car-safe fn) 'lambda)
+ (set (car (cadr fn)) (car (eval lst)))
+ (lispy-flow 2))))
+ ((or (functionp ldsi-fun)
+ (macrop ldsi-fun))
+ (cond ((eq ldsi-fun 'funcall)
+ (setq ldsi-fun (eval (cadr ldsi-sxp)))
+ (setq ldsi-sxp (cons ldsi-fun (cddr ldsi-sxp))))
+ ((eq ldsi-fun 'apply)
+ (setq ldsi-fun (eval (nth 1 ldsi-sxp)))
+ (let ((vals (mapcar #'eval (cddr ldsi-sxp))))
+ (setq ldsi-sxp
+ (cons ldsi-fun (append (butlast vals) (car (last vals))))))))
+ (let ((ldsi-args
+ (copy-sequence
+ (help-function-arglist
+ (if (ad-is-advised ldsi-fun)
+ (ad-get-orig-definition ldsi-fun)
+ ldsi-fun)
+ t)))
+ (ldsi-vals (cdr ldsi-sxp))
+ ldsi-arg
+ ldsi-val)
+ (catch 'done
+ (while (setq ldsi-arg (pop ldsi-args))
+ (cond ((eq ldsi-arg '&optional)
+ (setq ldsi-arg (pop ldsi-args))
+ (set ldsi-arg (eval (pop ldsi-vals))))
+ ((eq ldsi-arg '&rest)
+ (setq ldsi-arg (pop ldsi-args))
+ (set ldsi-arg
+ (if (functionp ldsi-fun)
+ (mapcar #'eval ldsi-vals)
+ ldsi-vals))
+ (throw 'done t))
+ (t
+ (setq ldsi-val (pop ldsi-vals))
+ (set ldsi-arg
+ (if (functionp ldsi-fun)
+ (eval ldsi-val)
+ ldsi-val))))))
+ (lispy-goto-symbol ldsi-fun)))
+ (t
+ (lispy-complain
+ (format "%S isn't a function" ldsi-fun))))))
((eq major-mode 'clojure-mode)
(require 'le-clojure)
(lispy--clojure-debug-step-in))
@@ -5976,9 +5871,9 @@ An equivalent of `cl-destructuring-bind'."
(interactive)
(if (eq major-mode 'python-mode)
(let* ((pwd
- (lispy--eval-python "import os; print(os.getcwd())"))
+ (lispy--eval-python-plain "import os; print(os.getcwd())"))
(cwd (read-directory-name "cd: " pwd)))
- (lispy--eval-python (format "os.chdir('%s')" cwd))
+ (lispy--eval-python-plain (format "os.chdir('%s')" cwd))
(message cwd))
(user-error "Unimplemented for %S" major-mode)))
@@ -6886,17 +6781,20 @@ The result is a string."
lispy-eval-alist))))
(if handler
(progn
+ (require (nth 0 handler))
(setq e-str
(or e-str (if (> (length handler) 2)
(funcall (nth 2 handler))
(save-excursion
(unless (or (lispy-right-p) (region-active-p))
- (lispy-forward 1))
+ (let ((lispy-ignore-whitespace t))
+ (lispy-forward 1)))
(lispy--string-dwim)))))
- (require (nth 0 handler))
(funcall (nth 1 handler) e-str))
(error "%s isn't supported currently" major-mode))))
+(defvar lispy-eval-expression-history nil)
+
(defun lispy-eval-expression ()
"Like `eval-expression', but for current language."
(interactive)
@@ -6904,7 +6802,8 @@ The result is a string."
(if (member major-mode lispy-elisp-modes)
#'lispy-mode
#'ignore)
- (read-from-minibuffer "Eval: "))))
+ (read-from-minibuffer
+ "Eval: " nil nil nil 'lispy-eval-expression-history))))
(lispy-message (lispy--eval form))))
(defvar lispy-eval-match-data nil)
@@ -7477,8 +7376,12 @@ See https://clojure.org/guides/weird_characters#_character_literal.")
(lispy--read-1)
;; ——— ? char syntax ——————————
(goto-char (point-min))
- (if (memq major-mode (cons 'hy-mode lispy-clojure-modes))
- (lispy--read-replace "[[:alnum:]-/*<>_?.,\\\\:!@#=]+" "clojure-symbol")
+ (cond
+ ((eq major-mode 'hy-mode)
+ (lispy--read-replace "[[:alnum:]-/*<>_?.,\\\\:!@#]+" "clojure-symbol"))
+ ((memq major-mode lispy-clojure-modes)
+ (lispy--read-replace "[[:alnum:]-+/*<>_?.\\\\:!@#=]+" "clojure-symbol"))
+ (t
(while (re-search-forward "\\(?:\\s-\\|\\s(\\)\\?" nil t)
(unless (lispy--in-string-or-comment-p)
(let ((pt (point))
@@ -7486,12 +7389,18 @@ See https://clojure.org/guides/weird_characters#_character_literal.")
(lispy--skip-elisp-char)
(setq sexp (buffer-substring-no-properties pt (point)))
(delete-region (1- pt) (point))
- (insert (format "(ly-raw char %S)" sexp))))))
+ (insert (format "(ly-raw char %S)" sexp)))))))
(when (eq major-mode 'clojure-mode)
(lispy--read-replace " *,+" "clojure-commas"))
;; ——— \ char syntax (LISP)————
(goto-char (point-min))
- (while (re-search-forward "#\\\\\\(.\\)" nil t)
+ (while (let ((case-fold-search t))
+ ;; http://lispworks.com/documentation/HyperSpec/Body/02_ac.htm
+ (re-search-forward (rx "#\\" (or "tab" "page" "space"
+ "return" "rubout"
+ "newline" "linefeed"
+ "backspace" any))
+ nil t))
(unless (lispy--in-string-or-comment-p)
(replace-match (format "(ly-raw lisp-char %S)"
(substring-no-properties
@@ -7939,7 +7848,8 @@ Defaults to `error'."
(forward-line 1))
(move-marker end nil)))
-(defvar lispy-no-indent-modes '(minibuffer-inactive-mode
+(defvar lispy-no-indent-modes '(minibuffer-mode
+ minibuffer-inactive-mode
comint-mode)
"List of major modes where `indent-for-tab-command' should not be used.
`lispy--indent-for-tab' will do nothing if the current mode or any of its parent
@@ -8099,6 +8009,7 @@ Try to refresh if nil is returned."
(defvar helm-update-blacklist-regexps)
(defvar helm-candidate-number-limit)
+(declare-function helm "ext:helm")
(defvar lispy-tag-history nil
"History for tags.")
@@ -8148,9 +8059,7 @@ ACTION is called for the selected candidate."
(defun lispy--action-jump (tag)
"Jump to TAG."
(if (eq (length tag) 3)
- (with-selected-window (if (eq lispy-completion-method 'ivy)
- (ivy--get-window ivy-last)
- (selected-window))
+ (progn
(push-mark)
(find-file (cadr tag))
(goto-char
@@ -8257,7 +8166,7 @@ The outer delimiters are stripped."
(delete-region beg (point))
(insert (cl-caddr sxp)))
((clojure-commas)
- (delete-region (1- beg) (point))
+ (delete-region (if (> beg (point-min)) (1- beg) beg) (point))
(insert (cl-caddr sxp)))
(clojure-symbol
(delete-region beg (point))
@@ -8797,98 +8706,105 @@ Return an appropriate `setq' expression when in `let', `dolist',
(when tsexp
(lispy-different)
(cond
- ((looking-back "(\\(?:lexical-\\)?let\\(?:\\*\\|-when-compile\\)?[ \t\n]*"
- (line-beginning-position 0))
- (cons 'setq
- (cl-mapcan
- (lambda (x) (unless (listp x) (list x nil)))
- tsexp)))
-
- ((lispy-after-string-p "(dolist ")
- `(lispy--dolist-item-expr ',tsexp))
-
- ((and (consp tsexp)
- (eq (car tsexp) 'lambda)
- (eq (length (cadr tsexp)) 1)
- (looking-back "(map\\sw* +"
- (line-beginning-position)))
- `(lispy--mapcar-item-expr ,tsexp
- ,(save-excursion
- (lispy-different)
- (read (current-buffer)))))
- ((and (consp tsexp)
- (eq (car tsexp) 'find-file-noselect))
- `(switch-to-buffer ,tsexp))
-
- ;; point moves
- ((progn
- (lispy--out-backward 1)
- (looking-back
- "(\\(?:lexical-\\|if-\\|when-\\)?let\\(?:\\*\\|-when-compile\\)?[ \t\n]*"
- (line-beginning-position 0)))
- (cons
- (if (eq major-mode 'scheme-mode)
- 'define
- 'setq)
- tsexp))
-
- ((looking-back
- "(\\(?:cl-\\)?labels[ \t\n]*"
- (line-beginning-position 0))
- (cons 'defun tsexp))
-
- ((looking-at
- "(cond\\b")
- (let ((re tsexp))
- (if (cdr re)
- `(if ,(car re)
- (progn
- ,@(cdr re))
- lispy--eval-cond-msg)
- `(or ,(car re)
- lispy--eval-cond-msg))))
- ((looking-at "(pcase\\s-*")
- (goto-char (match-end 0))
- (if (eval (pcase--expand (lispy--read (lispy--string-dwim))
- `((,(car tsexp) t))))
- `(progn
- ,(funcall (lispy--pcase-pattern-matcher (car tsexp))
- (eval (read (lispy--string-dwim))))
- "pcase: t")
- "pcase: nil"))
- ((looking-at "(cl-destructuring-bind")
- (let* ((x-expr (read (lispy--string-dwim)))
- (x-parts (eval (nth 2 x-expr))))
- (cl-mapc
- #'set (nth 1 x-expr) x-parts)
- (cons 'list (nth 1 x-expr))))
- ((looking-at "(with-current-buffer")
- `(switch-to-buffer ,tsexp))
- ((and (looking-at "(\\(?:cl-\\)?\\(?:defun\\|defmacro\\)")
- (save-excursion
- (lispy-flow 1)
- (eq (point) origin)))
- (let* ((fn-name (save-excursion
- (forward-char)
- (forward-sexp 2)
- (lispy--preceding-sexp)))
- (int-form
- (and (fboundp fn-name)
- (interactive-form fn-name)))
- (int-form (when (eq (car int-form) 'interactive)
- (cond ((listp (cadr int-form))
- (cadr int-form))
- ((equal (cadr int-form) "p")
- ''(1))))))
- (if int-form
- `(lispy-destructuring-setq ,tsexp
- ,int-form)
- `(progn
- ,@(mapcar
- (lambda (x)
- (list 'setq x nil))
- (delq '&key (delq '&optional (delq '&rest tsexp))))))))
- (t tsexp))))))
+ ((looking-back "(\\(?:lexical-\\)?let\\(?:\\*\\|-when-compile\\)?[ \t\n]*"
+ (line-beginning-position 0))
+ (cons 'setq
+ (cl-mapcan
+ (lambda (x) (unless (listp x) (list x nil)))
+ tsexp)))
+
+ ((lispy-after-string-p "(dolist ")
+ `(lispy--dolist-item-expr ',tsexp))
+
+ ((lispy-after-string-p "(cl-destructuring-bind ")
+ (let* ((x-expr (read (lispy--string-dwim)))
+ (x-parts (eval (save-excursion
+ (lispy-down 1)
+ (read (lispy--string-dwim))))))
+ (cl-mapc
+ #'set x-expr x-parts)
+ (cons 'list
+ (apply #'append
+ (mapcar (lambda (sym)
+ (list (list 'quote sym) (eval sym))) x-expr)))))
+
+ ((and (consp tsexp)
+ (eq (car tsexp) 'lambda)
+ (eq (length (cadr tsexp)) 1)
+ (looking-back "(map\\sw* +"
+ (line-beginning-position)))
+ `(lispy--mapcar-item-expr ,tsexp
+ ,(save-excursion
+ (lispy-different)
+ (read (current-buffer)))))
+ ((and (consp tsexp)
+ (eq (car tsexp) 'find-file-noselect))
+ `(switch-to-buffer ,tsexp))
+
+ ;; point moves
+ ((progn
+ (lispy--out-backward 1)
+ (looking-back
+ "(\\(?:lexical-\\|if-\\|when-\\)?let\\(?:\\*\\|-when-compile\\)?[ \t\n]*"
+ (line-beginning-position 0)))
+ (cons
+ (if (eq major-mode 'scheme-mode)
+ 'define
+ 'setq)
+ tsexp))
+
+ ((looking-back
+ "(\\(?:cl-\\)?labels[ \t\n]*"
+ (line-beginning-position 0))
+ (cons 'defun tsexp))
+
+ ((looking-at
+ "(cond\\b")
+ (let ((re tsexp))
+ (if (cdr re)
+ `(if ,(car re)
+ (progn
+ ,@(cdr re))
+ lispy--eval-cond-msg)
+ `(or ,(car re)
+ lispy--eval-cond-msg))))
+ ((looking-at "(pcase\\s-*")
+ (goto-char (match-end 0))
+ (if (eval (pcase--expand (read (lispy--string-dwim))
+ `((,(car tsexp) t))))
+ (let ((pexpr (funcall (lispy--pcase-pattern-matcher (car tsexp))
+ (eval (read (lispy--string-dwim))))))
+
+ `(progn
+ ,pexpr
+ ',pexpr))
+ lispy--eval-pcase-msg))
+
+ ((and (looking-at "(\\(?:cl-\\)?\\(?:defun\\|defmacro\\)")
+ (save-excursion
+ (lispy-flow 1)
+ (eq (point) origin)))
+ (let* ((fn-name (save-excursion
+ (forward-char)
+ (forward-sexp 2)
+ (lispy--preceding-sexp)))
+ (int-form
+ (and (fboundp fn-name)
+ (interactive-form fn-name)))
+ (int-form (when (eq (car int-form) 'interactive)
+ (cond ((listp (cadr int-form))
+ (cadr int-form))
+ ((equal (cadr int-form) "p")
+ ''(1))))))
+ (if int-form
+ `(lispy-destructuring-setq ,tsexp
+ ,int-form)
+ `(progn
+ ,@(mapcar
+ (lambda (x)
+ (list 'setq x nil))
+ (delq '&key (delq '&optional (delq '&rest tsexp))))))))
+ (t tsexp))))))
(defun lispy--find-unmatched-delimiters (beg end)
"Return the positions of unmatched delimiters between BEG and END.
@@ -9159,6 +9075,8 @@ FUNC is obtained from (`lispy--insert-or-call' DEF PLIST)."
("k" lispy-knight-up)
("z" nil))
+(autoload 'lispy-occur "lispy-occur")
+
(defvar lispy-mode-map-special
(let ((map (make-sparse-keymap)))
;; navigation
@@ -9201,8 +9119,8 @@ FUNC is obtained from (`lispy--insert-or-call' DEF PLIST)."
;; dialect-specific
(lispy-define-key map "e" 'lispy-eval)
(lispy-define-key map "E" 'lispy-eval-and-insert)
- (lispy-define-key map "G" 'lispy-goto-local)
- (lispy-define-key map "g" 'lispy-goto)
+ (lispy-define-key map "g" 'lispy-goto-local)
+ (lispy-define-key map "G" 'lispy-goto)
(lispy-define-key map "F" 'lispy-follow t)
(lispy-define-key map "D" 'pop-tag-mark)
(lispy-define-key map "A" 'lispy-beginning-of-defun)
@@ -9474,7 +9392,7 @@ When ARG is non-nil, unquote the current string."
(insert "\""))
(t
- (lispy--space-unless "^\\|\\s-\\|\\s(\\|[#]")
+ (lispy--space-unless "^\\|\\s-\\|\\s(\\|[#][a-zA-Z]?")
(insert "\"\"")
(unless (looking-at "\n\\|)\\|}\\|\\]\\|$")
(just-one-space)
diff --git a/mypy.ini b/mypy.ini
new file mode 100644
index 00000000..b05a279a
--- /dev/null
+++ b/mypy.ini
@@ -0,0 +1,9 @@
+[mypy]
+# disallow_untyped_defs = True
+color_output = False
+
+[mypy-jedi.*]
+ignore_missing_imports = True
+
+[mypy-__builtin__]
+ignore_missing_imports = True
\ No newline at end of file
diff --git a/targets/install-deps.el b/targets/install-deps.el
index f69ab770..158b8e3c 100644
--- a/targets/install-deps.el
+++ b/targets/install-deps.el
@@ -8,7 +8,7 @@
sly
geiser
clojure-mode
- counsel
+ swiper
hydra
ace-window
helm
diff --git a/test/test_lispy-python.py b/test/test_lispy-python.py
new file mode 100644
index 00000000..75677117
--- /dev/null
+++ b/test/test_lispy-python.py
@@ -0,0 +1,226 @@
+import io
+import os
+import re
+import ast
+import sys
+from importlib.machinery import SourceFileLoader
+from contextlib import redirect_stdout
+from textwrap import dedent
+
+lp = SourceFileLoader("lispy-python", os.path.dirname(__file__) + "/../lispy-python.py").load_module()
+
+def print_elisp_to_str(obj):
+ with io.StringIO() as buf, redirect_stdout(buf):
+ lp.print_elisp(obj)
+ return buf.getvalue().strip()
+
+
+def with_output_to_string(code):
+ with io.StringIO() as buf, redirect_stdout(buf):
+ exec(code)
+ return buf.getvalue().strip()
+
+def lp_eval(code, env={}):
+ return with_output_to_string(f"__res__=lp.eval_code('''{code}''', {env})")
+
+def test_exec():
+ # Need to run this with globals().
+ # Otherwise, x will not be defined later
+ exec("x=1", globals())
+ assert x == 1
+
+def test_tr_returns_1():
+ code_1 = dedent("""
+ x=1
+ y=2
+ return x + y""")
+ parsed = ast.parse(code_1).body
+ assert len(parsed) == 3
+ translated = lp.tr_returns(parsed)
+ assert len(translated) == 3
+ assert parsed[0] == translated[0]
+ assert parsed[1] == translated[1]
+ assert ast.unparse(translated[2]) == "return locals() | {'__return__': x + y}"
+ code = ast.unparse(lp.wrap_return(translated))
+ exec(code, globals())
+ assert x == 1
+ assert y == 2
+ assert __return__ == 3
+
+def test_tr_returns_2():
+ code_2 = dedent("""
+ if os.environ.get("FOO"):
+ return 0
+ x = 1
+ y = 2
+ return x + y""")
+ parsed = ast.parse(code_2).body
+ assert len(parsed) == 4
+ translated = lp.tr_returns(parsed)
+ assert len(translated) == 4
+ assert ast.unparse(translated[3]) == "return locals() | {'__return__': x + y}"
+ assert ast.unparse(translated[0].body) == "return locals() | {'__return__': 0}"
+ code = ast.unparse(lp.wrap_return(translated))
+ exec(code, globals())
+ assert x == 1
+ assert y == 2
+ assert __return__ == 3
+ os.environ["FOO"] = "BAR"
+ exec(code, globals())
+ assert __return__ == 0
+
+def test_translate_def():
+ code = dedent("""
+ def add(x, y):
+ return x + y
+ """)
+ tr = lp.translate(code)
+ assert len(tr) == 1
+ assert isinstance(tr[0], ast.FunctionDef)
+ r = lp.eval_code(code)
+ assert r["res"] == "'unset'"
+ assert "" \\)', r)