├── Cookbook.py ├── README.org ├── lpy-soap.el ├── lpy.el └── targets ├── plain.el └── tutor.py /Cookbook.py: -------------------------------------------------------------------------------- 1 | #* Imports 2 | from pycook.recipes.emacs import byte_compile, checkdoc 3 | import pycook.insta as st 4 | 5 | #* Recipes 6 | def python_deps(recipe): 7 | st.install_package("libreadline-dev") 8 | st.git_clone( 9 | "https://github.com/pyenv/pyenv.git", 10 | "~/.pyenv/", 11 | "38de38e3") 12 | st.patch("~/.profile", ['+PATH="$HOME/.pyenv/bin:$PATH"']) 13 | st.make( 14 | "~/.pyenv/versions/3.6.0", 15 | ["pyenv install 3.6.0"]) 16 | return [ 17 | 'eval "$(pyenv init -)"', 18 | "python --version", 19 | "pip install --upgrade pip jedi readline", 20 | "pip --version" 21 | ] 22 | 23 | def emacs_deps(recipe): 24 | packages = ["company", "jedi", "company-jedi", "lispy", "use-package"] 25 | return "cook :emacs install " + " ".join(packages) 26 | 27 | def plain(recipe): 28 | st.make( 29 | "~/.pyenv/versions/lpy-virtualenv", 30 | [ 31 | "pyenv virtualenv 3.6.0 lpy-virtualenv", 32 | "pyenv local lpy-virtualenv"]) 33 | return [ 34 | 'eval "$(pyenv init -)"', 35 | "python --version", 36 | "cook :emacs elpa lpy.el targets/plain.el" 37 | ] 38 | -------------------------------------------------------------------------------- /README.org: -------------------------------------------------------------------------------- 1 | * lpy 2 | 3 | Deceptively minimalistic Python IDE for GNU Emacs. 4 | 5 | * Setup 6 | Install from MELPA and enable =lpy-mode=. 7 | 8 | * Introduction 9 | Q: How many keys does it take to define/redefine a Python function? 10 | A: One - ~e~. 11 | 12 | ** Arrow navigation 13 | Q: Does this also apply to classes, import statements, for loops and 14 | plain one liners? 15 | A: Of course. 16 | 17 | Q: How many keys does it take to move to the next Python function? 18 | A: One - ~j~. 19 | 20 | Q: Does this also apply to classes, import statements, for loops and 21 | plain one liners? 22 | A: Of course. 23 | 24 | Q: So, how do I eval a statement, move to the second statement, and 25 | eval the second statement? 26 | A: ~eje~. 27 | 28 | Q: How to move back to the first statement? 29 | A: ~k~. 30 | 31 | Q: How do I descent into a compound statement, like =def=? 32 | A: ~l~. 33 | 34 | Q: How do I ascent back? 35 | A: ~h~. 36 | 37 | Q: Back to eval, how do I eval something that's not a compound 38 | statement? 39 | A: Mark as much legal code as you like with a region and press ~e~. 40 | 41 | Q: Any shortcut for marking a symbol in order to eval it? 42 | A: ~M-m~. 43 | 44 | ** Semantic navigation 45 | Q: How can I select a top-level symbol in the current file? 46 | A: ~g~. 47 | 48 | Q: How do I follow to the current symbol's definition? 49 | A: ~M-.~. 50 | 51 | Q: How do I go back from there? 52 | A: =M-,=. 53 | 54 | Q: Does =M-,= also bring me back after ~g~? 55 | A: Yes. 56 | 57 | ** Eval in more detail 58 | Q: Where do I see the result of the eval? 59 | A: In the minibuffer. 60 | 61 | Q: What if the result of the operation is =None=? 62 | A: Then =(ok)= is printed to confirm that the operation went through. 63 | 64 | Q: What if an uncaught exception is thrown? 65 | A: The exception will be displayed in the minibuffer, highlighted in 66 | red. 67 | 68 | Q: Is it possible to be productive without ever seeing the REPL 69 | buffer? 70 | A: Yes. 71 | 72 | Q: How do I see the REPL buffer? 73 | A: ~C-c C-z~. 74 | 75 | Q: Can I have more than one REPL session? 76 | A: Yes, use ~xp~ to select session or start a new one. 77 | 78 | Q: Does pressing ~e~ on a variable assignment output nothing because the 79 | result is =None=? 80 | A: No, magic is used to make it print the new value of the variable. 81 | 82 | Q: Where else is ~e~ magical? 83 | A: ~e~ on =__file__= will print the actual file name. 84 | 85 | Q: Any more? 86 | A: ~e~ on a single line =return= statement will print the return result. 87 | 88 | Q: How do I insert the result of the eval into the buffer? 89 | A: ~E~. 90 | 91 | Q: How do I insert the result of the eval into the buffer as a 92 | comment? 93 | A: ~2e~. 94 | 95 | Q: Howto easily select things to eval? 96 | A: ~M-m~ to get into special. Then ~+~ and ~-~ to expand/shrink selection with =expand-region=. 97 | 98 | ** Debugging 99 | Q: How do I step into an expression? 100 | A: ~xj~. 101 | 102 | Q: Where does this work? 103 | A: Function or method call, possibly with the result being assigned to 104 | a variable. 105 | 106 | Q: What does that do exactly? 107 | A: Evaluate the function arguments, jump to the function definition, 108 | and store the function arguments into global variable with appropriate 109 | names. 110 | 111 | Q: So if all I have is =sys.argv= I can use ~xj~ (combined with ~j~, ~l~ and 112 | ~e~) to step into my program as deep as I want? 113 | A: Yes. 114 | 115 | Q: What if I just want to place a breakpoint to speed things up? 116 | A: To place a breakpoint, throw any exception. Then, enter 117 | =lp.pm()= into the REPL. 118 | 119 | Q: =lp.pm()= sounds weird, how can I see how it works? 120 | A: Enter into the REPL: =os.path.realpath(42)= which will raise an 121 | exception a few functions down the line. Then enter =lp.pm()=. 122 | 123 | Q: Wait, so this is like =pdb=? 124 | A: Yes, very similar. 125 | 126 | Q: So why go through the trouble? 127 | A: To get global variable context and proceed with ~e~ and ~j~. 128 | 129 | Q: Got it. Anyway, I see =File "/usr/lib/python2.7/posixpath.py", 130 | line 61, Frame [3/3]:=, what does it mean? 131 | A: =realpath= called =_joinrealpath=, which called =isabs=, which raised an 132 | Exception. 133 | 134 | Q: Three functions (=realpath=, =_joinrealpath=, and =isabs=), that's what 135 | 3/3 means. And I'm in =isabs= now? 136 | A: Yes. 137 | 138 | Q: How do I open the source code for =isabs=? 139 | A: It's a link: use the mouse or =ace-link= or =next-error=. 140 | 141 | Q: I'm at the definition of =isabs= now, was its argument value stored 142 | somewhere? 143 | A: Yes, if you eval =s=, you get =42= - the value that propagated from 144 | =os.path.realpath(42)=. 145 | 146 | Q: =isabs= is boring. How do I go up the stack? 147 | A: Enter =up= into the REPL. 148 | 149 | Q: I entered up, and now the frame is 2/3 and 150 | I'm at =def _joinrealpath(path, rest, seen)=. Does this mean I can eval 151 | =path=, =rest=, and =seen= since they were propagated from the 152 | =os.path.realpath(42)= call? 153 | A: Yes. 154 | 155 | Q: And entering =up= again will bring me to 1/3 and =realpath=? 156 | A: Yes. 157 | 158 | Q: How do I go back down stack? 159 | A: Enter =dn=. 160 | 161 | ** Notebooks 162 | Q: Is this like IPython? 163 | A: Yes. 164 | 165 | Q: But it's different how? 166 | A: You use Emacs instead of a browser, and the cells are 167 | self-contained in comments. 168 | 169 | Q: Sounds nice, but I'm not sold yet? 170 | A: It's like Org-mode embedded in Python code. 171 | 172 | Q: So I can fold / unfold each cell? 173 | A: Yes. 174 | 175 | Q: And eval/reeval it with ~e~? 176 | A: Yes. 177 | 178 | Q: How about organizing cells in a hierarchy? 179 | A: Also possible. 180 | 181 | Q: And ~e~ works on the hierarchy as well? 182 | A: Yes. 183 | 184 | Q: Are cells actually called different or something? 185 | A: Yes, they're outlines. 186 | 187 | Q: How do I make an outline named =Includes=? 188 | A: Enter =#* Includes=. 189 | 190 | Q: And like in Org-mode, the amount of stars is that outline's level? 191 | A: Yes. 192 | 193 | Q: Do ~M-left~ and ~M-right~ work like in Org-mode for promotion/demotion? 194 | A: Yes. 195 | 196 | Q: How to fold/unfold an outline? 197 | A: ~i~. 198 | 199 | Q: How do I fold/unfold all outlines? 200 | A: ~I~. 201 | 202 | Q: How do I make a table of contents? 203 | A: ~2I~. 204 | 205 | Q: When I press ~e~ on an outline it evaluates itself and the result is 206 | echoed; how do I make the result insert itself into the buffer 207 | instead? 208 | A: End the outline name in =:= (semicolon) 209 | 210 | Q: How do I clean up all inserted results? 211 | A: ~M-x~ =lpy-clean=. 212 | 213 | Q: Any more neat stuff about outlines? 214 | A: Yes, outlines are structured statements and parents to the 215 | top-level statements. 216 | 217 | Q: So ~j~ / ~k~ and ~h~ / ~l~, and even ~e~ treat outlines as statements? Neat. 218 | A: Yeah. 219 | 220 | ** Completion 221 | Q: How do I get completion at point? 222 | A: Press ~C-M-i~. 223 | 224 | Q: Is this static completion or does it depend on the REPL state? 225 | A: Both. The static one is more convenient and is tried first. But the 226 | dynamic one is very reliable, since it knows exactly on which type of 227 | object you're operating. 228 | 229 | Q: But dynamic completion won't work unless my current object has a 230 | value in the REPL? 231 | A: Correct. 232 | 233 | Q: What's used for static completion? 234 | A: Jedi. 235 | 236 | ** Inline hints 237 | Q: How do I look up the function arguments of the current function? 238 | A: Toggle ~C-2~. 239 | 240 | Q: What about the docstring? 241 | A: Toggle ~C-1~. 242 | -------------------------------------------------------------------------------- /lpy-soap.el: -------------------------------------------------------------------------------- 1 | ;;; lpy-soap.el --- Smart Operator a posteriori 2 | 3 | ;; Copyright (C) 2015-2020 Oleh Krehel 4 | 5 | ;; Author: Oleh Krehel 6 | ;; URL: https://github.com/abo-abo/lpy 7 | ;; Version: 0.1.0 8 | ;; Keywords: convenience 9 | ;; Package-Requires: ((emacs "25.1")) 10 | 11 | ;; This file is not part of GNU Emacs. 12 | 13 | ;; This file is free software; you can redistribute it and/or modify 14 | ;; it under the terms of the GNU General Public License as published by 15 | ;; the Free Software Foundation; either version 3, or (at your option) 16 | ;; any later version. 17 | 18 | ;; This program is distributed in the hope that it will be useful, 19 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 | ;; GNU General Public License for more details. 22 | 23 | ;; For a full copy of the GNU General Public License 24 | ;; see . 25 | 26 | ;;; Commentary: 27 | ;; 28 | ;; Insert spaces around operators, e.g. " += ". 29 | ;; 30 | ;; Based on smart-operator.el by William Xu. 31 | 32 | ;;; Code: 33 | 34 | (require 'lispy) 35 | 36 | (defvar lpy-soap-alist 37 | '("\\+" "-" "\\*" "/" "%" "&" "|" "<" "=" ">")) 38 | 39 | (defun lpy-soap-default-action (op) 40 | "Insert OP, possibly with spaces around it." 41 | (delete-horizontal-space t) 42 | (let ((op-p nil) 43 | (one-char-back nil)) 44 | (unless (bolp) 45 | (backward-char) 46 | (setq one-char-back t)) 47 | (setq op-p 48 | (catch 'return 49 | (dolist (front lpy-soap-alist) 50 | (when (looking-at front) 51 | (throw 'return t))))) 52 | (when (and (or op-p (not (and (bolp) (eolp)))) 53 | one-char-back) 54 | (forward-char)) 55 | (if (or op-p (bolp)) 56 | (insert op) 57 | (insert " " op))) 58 | (delete-horizontal-space t) 59 | (unless (eq (char-after) ?\ ) 60 | (insert " "))) 61 | 62 | (defun lpy-soap-command (&optional arg) 63 | "Similar to `self-insert-command' with ARG, except handles whitespace." 64 | (interactive "p") 65 | (setq arg (or arg 1)) 66 | (let ((op (this-command-keys))) 67 | (cond ((and (lispy--in-string-or-comment-p) 68 | (member op (list "+" "-" "*" "/" "%" "&" "|" "="))) 69 | (self-insert-command arg)) 70 | 71 | ((lispy-after-string-p "(") 72 | (insert op)) 73 | 74 | ((string= op "+") 75 | (cond 76 | ((looking-back " ?\\+ " (line-beginning-position)) 77 | (if (eq major-mode 'haskell-mode) 78 | (progn 79 | (backward-delete-char 1) 80 | (insert "+ ")) 81 | (backward-delete-char 3) 82 | (insert "++"))) 83 | ((looking-back "\\s-\\|=\\|\\+\\|\\([0-9.]+e\\)" (line-beginning-position)) 84 | (insert "+")) 85 | (t (lpy-soap-default-action op)))) 86 | 87 | ((string= op "-") 88 | (cond 89 | ((lispy-after-string-p " - ") 90 | (backward-delete-char 3) 91 | (insert "--")) 92 | ((or (looking-at "[\\s-]*>") (bolp)) 93 | (insert op)) 94 | ((looking-back "\\s-\\|=\\|-\\|this\\|(\\|\\([0-9.]+e\\)" (line-beginning-position)) 95 | (insert op)) 96 | ((looking-back "[:[]" (line-beginning-position)) 97 | (insert op)) 98 | (t (lpy-soap-default-action op)))) 99 | 100 | ((string= op "*") 101 | (cond ((lispy-after-string-p " * ") 102 | (delete-char -3) 103 | (insert "**")) 104 | ((or (looking-back "(\\|[\t :.]+" (line-beginning-position)) 105 | (looking-at ">") 106 | (looking-back "^\\sw+" (line-beginning-position)) 107 | (looking-back "char\\|int\\|double\\|void" (line-beginning-position))) 108 | (insert op)) 109 | ((memq major-mode '(java-mode)) 110 | (insert op)) 111 | 112 | (t 113 | (lpy-soap-default-action op)))) 114 | 115 | ((string= op "/") 116 | (cond ((or (lispy-after-string-p ".") 117 | (looking-back "^#.*" (line-beginning-position))) 118 | (insert op)) 119 | ((and (looking-back "^ *" (line-beginning-position)) 120 | (memq major-mode '(c++-mode c-mode))) 121 | (insert "// ")) 122 | (t 123 | (lpy-soap-default-action op)))) 124 | 125 | ((and (string= op "%") 126 | (looking-back "[ \t]+" (line-beginning-position))) 127 | (insert op)) 128 | 129 | ((string= op "&") 130 | (cond ((lispy-after-string-p "&") 131 | (backward-delete-char 1) 132 | (insert " && ")) 133 | (t 134 | (insert "&")))) 135 | 136 | ((string= op ",") 137 | (if (and (looking-at "[^\n<]*>") 138 | (looking-back "<[^\n>]+" (line-beginning-position))) 139 | (insert op) 140 | (insert ", "))) 141 | 142 | ((string= op ":") 143 | (insert ": ")) 144 | 145 | ((string= op ">") 146 | (cond ((looking-back 147 | "\\(?:this\\)?\\(-\\| \\- \\)" 148 | (line-beginning-position)) 149 | (delete-region 150 | (match-beginning 1) 151 | (match-end 1)) 152 | (if (eq major-mode 'python-mode) 153 | (insert " -> ") 154 | (insert "->"))) 155 | ((eq major-mode 'sml-mode) 156 | (cond ((lispy-after-string-p ":") 157 | (insert "> ")) 158 | ((lispy-after-string-p "- ") 159 | (delete-char -1) 160 | (insert "> ")) 161 | (t (lpy-soap-default-action ">")))) 162 | ((looking-back 163 | "\\(?:this\\)?\\(-\\| \\- \\)" 164 | (line-beginning-position)) 165 | (delete-region 166 | (match-beginning 1) 167 | (match-end 1)) 168 | (insert "->")) 169 | (t (lpy-soap-default-action ">")))) 170 | 171 | ((string= op "<") 172 | (if (lispy--in-string-or-comment-p) 173 | (insert op) 174 | (lpy-soap-default-action op))) 175 | 176 | ((string= op "=") 177 | (cond 178 | ((eq major-mode 'python-mode) 179 | (cond 180 | ;; keyword argument in Python 181 | ((looking-back "[,(][\n ]*\\(\\sw\\|\\s_\\)+" (line-beginning-position -1)) 182 | (insert op)) 183 | ;; walrus 184 | ((looking-back ":" (line-beginning-position)) 185 | (delete-char -1) 186 | (insert " := ")) 187 | ((looking-back "!" (line-beginning-position)) 188 | (delete-char -1) 189 | (insert " != ")) 190 | (t 191 | (lpy-soap-default-action op)))) 192 | ((lispy-after-string-p "!") 193 | (backward-delete-char 1) 194 | (just-one-space) 195 | (insert "!= ")) 196 | ((lispy-after-string-p "<") 197 | (delete-char -1) 198 | (insert " <= ")) 199 | ((looking-back "\\sw\\( ?\\+ ?\\)" (line-beginning-position)) 200 | (delete-region (match-beginning 1) 201 | (match-end 1)) 202 | (insert " += ")) 203 | ((lispy-after-string-p "[") 204 | (insert op)) 205 | (t 206 | (lpy-soap-default-action op)))) 207 | (t 208 | (lpy-soap-default-action op))))) 209 | 210 | (provide 'lpy-soap) 211 | 212 | ;;; lpy-soap.el ends here 213 | -------------------------------------------------------------------------------- /lpy.el: -------------------------------------------------------------------------------- 1 | ;;; lpy.el --- A lispy interface to Python -*- lexical-binding: t -*- 2 | 3 | ;; Copyright (C) 2016-2020 Oleh Krehel 4 | 5 | ;; Author: Oleh Krehel 6 | ;; URL: https://github.com/abo-abo/lpy 7 | ;; Version: 0.1.0 8 | ;; Keywords: python, lisp 9 | ;; Package-Requires: ((emacs "25.1") (lispy "0.27.0")) 10 | 11 | ;; This file is not part of GNU Emacs 12 | 13 | ;; This file is free software; you can redistribute it and/or modify 14 | ;; it under the terms of the GNU General Public License as published by 15 | ;; the Free Software Foundation; either version 3, or (at your option) 16 | ;; any later version. 17 | 18 | ;; This program is distributed in the hope that it will be useful, 19 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 | ;; GNU General Public License for more details. 22 | 23 | ;; For a full copy of the GNU General Public License 24 | ;; see . 25 | 26 | ;;; Commentary: 27 | ;; 28 | ;; This is an attempt to implement a variant of `lispy-mode' 29 | ;; (https://github.com/abo-abo/lispy) for Python. Unfortunately, 30 | ;; Python isn't nearly as well-structured as LISP. But Python is 31 | ;; ubiquitous, and the less powerful `lpy-mode' is better than nothing 32 | ;; at all. 33 | ;; 34 | ;; The basic idea of `lpy-mode' is to increase the editing efficiency 35 | ;; by binding useful navigation, refactoring and evaluation commands 36 | ;; to unprefixed keys, e.g. "j" or "e". But only in certain point 37 | ;; positions, so that you are still able to use uprefixed keys to 38 | ;; insert themselves. 39 | ;; 40 | ;; Example, here "|" represents the point position: 41 | ;; 42 | ;; print |("2+2=%d" % (2 + 2)) 43 | ;; 44 | ;; Here, if you press the key "e", the whole line will be evaluated 45 | ;; and "2+2=4" will be printed in the Echo Area. Note that if 46 | ;; `lpy-mode' was off, pressing "e" would instead result in: 47 | ;; 48 | ;; print e|("2+2=%d" % (2 + 2)) 49 | ;; 50 | ;; So inserting any key isn't actually useful with that point position 51 | ;; and e.g. the "e" can be used for evaluating the current statement. 52 | ;; 53 | ;; But, for instance, if you wanted to edit "print" into "printe", you 54 | ;; could do that in a straightforward way, just like you would with 55 | ;; `lpy-mode' off : with "C-b e". 56 | ;; 57 | ;;; Code: 58 | 59 | (require 'lispy) 60 | (require 'le-python) 61 | (require 'lpy-soap) 62 | (require 'flyspell nil t) 63 | (require 'zoutline) 64 | 65 | (defgroup lpy nil 66 | "List navigation and editing for Python." 67 | :group 'bindings 68 | :prefix "lpy-") 69 | 70 | (defconst lpy-font-lock-keywords 71 | '(("^# ?\\(\\*\\|\\*[^*\n].*\\)$" 1 'org-level-1 prepend) 72 | ("^# ?\\(\\*\\{2\\}\\|\\*\\{2\\}[^*\n].*\\)$" 1 'org-level-2 prepend) 73 | ("^# ?\\(\\*\\{3\\}\\|\\*\\{3\\}[^*\n].*\\)$" 1 'org-level-3 prepend) 74 | ("^# ?\\(\\*\\{4\\}\\|\\*\\{4\\}[^*\n].*\\)$" 1 'org-level-4 prepend) 75 | ("^# ?\\(\\*\\{5\\}\\|\\*\\{5\\}[^*\n].*\\)$" 1 'org-level-5 prepend) 76 | (lpy-outline-comment-highlight 1 'default prepend) 77 | ;; ("^# \\([^ ].*\\)$" 1 'default prepend) 78 | ("`\\([^\n']+\\)'" 1 font-lock-constant-face prepend))) 79 | 80 | (defun lpy-fill-forward-paragraph-function (&optional arg) 81 | "The setting for `fill-forward-paragraph-function'. 82 | The argument ARG is passed to `forward-paragraph'." 83 | (let (bnd) 84 | (if (and (setq bnd (lispy--bounds-string)) 85 | (string-match "\\`\"\"\"" (lispy--string-dwim bnd))) 86 | (goto-char (cdr bnd)) 87 | (forward-paragraph arg)))) 88 | 89 | (defun lpy-fill-paragraph (&optional _justify) 90 | "The setting for `fill-paragraph-function'." 91 | (interactive) 92 | (let (bnd) 93 | (cond ((setq bnd (lispy--bounds-comment)) 94 | (save-restriction 95 | (save-excursion 96 | (goto-char (car bnd)) 97 | (while (looking-at outline-regexp) 98 | (beginning-of-line 2) 99 | (setcar bnd (point))) 100 | (narrow-to-region (car bnd) (cdr bnd)) 101 | (let ((fill-paragraph-function nil)) 102 | (fill-paragraph))))) 103 | ((setq bnd (lispy--bounds-string)) 104 | (let ((str (lispy--string-dwim bnd)) 105 | (pt (- (point) (car bnd)))) 106 | (delete-region (car bnd) (cdr bnd)) 107 | (insert 108 | (with-temp-buffer 109 | (insert str) 110 | (goto-char pt) 111 | (fill-paragraph) 112 | (buffer-string))) 113 | 0)) 114 | (t 115 | (let ((fill-paragraph-function nil)) 116 | (fill-paragraph)))))) 117 | 118 | (defun lpy-outline-comment-highlight (limit) 119 | "Highlight outline comment up to LIMIT." 120 | (catch 'done 121 | (while (re-search-forward "^#\\(?:[^*\n]\\)" limit t) 122 | (let* ((pt (point)) 123 | (success (save-excursion 124 | (and (re-search-backward "^#\\*" nil t) 125 | (null (re-search-forward "^[^#]" pt t)))))) 126 | (when success 127 | (set-match-data (list (line-beginning-position) (line-end-position) 128 | (point) (line-end-position))) 129 | (end-of-line) 130 | (throw 'done t)))))) 131 | 132 | (defun lpy-left-p () 133 | "Return t if before `lispy-left'." 134 | (looking-at lispy-left)) 135 | 136 | (defun lpy-right-p () 137 | "Return t if after `lispy-right'." 138 | (looking-back lispy-right 139 | (line-beginning-position))) 140 | 141 | (defun lpy-outline-p () 142 | "Return t when before `outline-regexp'." 143 | (and (looking-at outline-regexp) 144 | (looking-at lispy-outline-header))) 145 | 146 | (defun lpy-space (arg) 147 | "Forward to `self-insert-command' ARG. 148 | Use this to detect a space elsewhere." 149 | (interactive "p") 150 | (self-insert-command arg)) 151 | 152 | (defun lpy-line-left-p () 153 | "Return t when in special position in Python." 154 | (or (and (bolp) 155 | (not (or 156 | (eolp) 157 | (looking-at " ") 158 | (memq last-command '(lpy-space newline)) 159 | (looking-at "[ \n]*\\'")))) 160 | (and (looking-at " ") 161 | (looking-back "^ +" (line-beginning-position)) 162 | (not (= (mod (current-column) 4) 0))))) 163 | 164 | (defun lpy-outline-add () 165 | "Insert an outline below the current one." 166 | (interactive) 167 | (if (looking-at lispy-outline) 168 | (let ((lvl (lispy-outline-level))) 169 | (condition-case nil 170 | (outline-forward-same-level 1) 171 | (error (goto-char (point-max)))) 172 | (while (eq (char-before) ?\n) 173 | (delete-char -1)) 174 | (insert 175 | "\n\n" 176 | lispy-outline-header 177 | (make-string lvl ?\*) 178 | " \n") 179 | (beginning-of-line 0)) 180 | (user-error "Not on an outline"))) 181 | 182 | (defun lpy-tab () 183 | "Fold or unfold the current outline." 184 | (interactive) 185 | (cond ((or (lispy--in-comment-p) 186 | (looking-at lispy-outline)) 187 | (let* ((bnd (zo-bnd-subtree)) 188 | (eoh (car bnd)) 189 | (eos (cdr bnd))) 190 | (if (and (get-char-property (1- eos) 'invisible) 191 | (get-char-property (1+ eoh) 'invisible)) 192 | (outline-flag-region eoh eos nil) 193 | (outline-flag-region eoh eos t)))) 194 | ((region-active-p) 195 | (cond ((lpy-listp) 196 | (let ((beg (region-beginning)) 197 | (end (region-end))) 198 | (if (and (save-excursion 199 | (goto-char beg) 200 | (lpy-arg-leftp)) 201 | (save-excursion 202 | (goto-char end) 203 | (lpy-arg-rightp))) 204 | (when (save-excursion 205 | (goto-char beg) 206 | (re-search-forward "=" nil end)) 207 | (set-mark (1+ beg)) 208 | (goto-char (match-beginning 0)))))) 209 | ((looking-at " \\(?:if\\|elif\\) \\([^\n]+\\):") 210 | (lispy--mark (cons (match-beginning 1) 211 | (match-end 1)))))) 212 | ((looking-at "(") 213 | (let ((beg (point)) 214 | (end (save-excursion (forward-list))) 215 | (col (1+ (current-column)))) 216 | (move-beginning-of-line 2) 217 | (while (< (point) end) 218 | (skip-chars-forward " ") 219 | (indent-to col) 220 | (backward-delete-char (- (current-column) col)) 221 | (move-beginning-of-line 2)) 222 | (goto-char beg))))) 223 | 224 | (defun lpy-contents () 225 | "Toggle contents for the current outline." 226 | (interactive) 227 | (let ((bnd (zo-bnd-subtree))) 228 | (if (get-char-property (car bnd) 'invisible) 229 | (outline-show-subtree) 230 | (outline-show-subtree) 231 | (outline-hide-body)))) 232 | 233 | (defun lpy--insert-or-call (def) 234 | "Return a command to either self-insert or call DEF." 235 | `(lambda () 236 | ,(format "Call `%s' when special, self-insert otherwise.\n\n%s" 237 | (symbol-name def) (documentation def)) 238 | (interactive) 239 | (unless (looking-at lispy-outline) 240 | (lispy--ensure-visible)) 241 | (cond ((region-active-p) 242 | (call-interactively ',def)) 243 | 244 | ((or (lispy--in-string-or-comment-p) 245 | (eq last-command 'python-indent-dedent-line-backspace)) 246 | (call-interactively 'self-insert-command)) 247 | 248 | ((or (lpy-line-left-p) 249 | (and (lispy-bolp) 250 | (not (memq last-command '(self-insert-command 251 | lpy-soap-command 252 | newline))) 253 | (or (looking-at lispy-outline-header) 254 | (looking-at lispy-outline)))) 255 | (call-interactively ',def)) 256 | 257 | (t 258 | (setq this-command 'self-insert-command) 259 | (call-interactively 260 | 'self-insert-command))))) 261 | 262 | (defun lpy-define-key (keymap key def) 263 | "Forward to (`define-key' KEYMAP KEY DEF)." 264 | (declare (indent 3)) 265 | (let ((func (defalias (intern (concat "pspecial-" (symbol-name def))) 266 | (lpy--insert-or-call def)))) 267 | (add-to-list 'ac-trigger-commands func) 268 | (unless (memq func mc/cmds-to-run-once) 269 | (add-to-list 'mc/cmds-to-run-for-all func)) 270 | (unless (memq func flyspell-delayed-commands) 271 | (add-to-list 'flyspell-delayed-commands func)) 272 | (define-key keymap (kbd key) func))) 273 | 274 | (defun lpy-line-p () 275 | "Return t when a line is selected with a region." 276 | (and (region-active-p) 277 | (= (region-end) (line-end-position)) 278 | (save-excursion 279 | (goto-char (region-beginning)) 280 | (lispy-bolp)))) 281 | 282 | (defvar lpy-listp-last nil) 283 | 284 | (defun lpy-listp () 285 | "When on a list, return its bounds as a cons." 286 | (ignore-errors 287 | (unless (or (memq last-command '(python-indent-dedent-line-backspace 288 | lpy-parens 289 | self-insert-command)) 290 | (and (eq ?\) (char-after)) 291 | (eq ?\( (char-before))) 292 | (lispy--in-string-or-comment-p)) 293 | (let (end) 294 | (when (save-excursion 295 | (let ((forward-sexp-function nil)) 296 | (ignore-errors 297 | (up-list 1) 298 | (setq end (point)) 299 | (and (lispy-after-string-p ")") 300 | (< (point) (point-max)))))) 301 | (setq lpy-listp-last 302 | (cons 303 | (save-excursion 304 | (goto-char end) 305 | (forward-list -1)) 306 | end))))))) 307 | 308 | (defun lpy-arg-leftp () 309 | "Test if on the left side of arg." 310 | (or (eq (point) (1+ (car lpy-listp-last))) 311 | (and (looking-at " ") 312 | (looking-back 313 | ",[ \n]*" 314 | (- (line-beginning-position) 2))))) 315 | 316 | (defun lpy-arg-rightp () 317 | "Test if on the right of arg." 318 | (or (and (eq (point) (1- (cdr lpy-listp-last))) 319 | (not (lispy-after-string-p " "))) 320 | (looking-at ","))) 321 | 322 | (defun lpy-arg-forward () 323 | "Forward by arg." 324 | (while (and (< (point) (point-max)) 325 | (not (looking-at ","))) 326 | (forward-sexp 1))) 327 | 328 | (defun lpy-bof-position () 329 | "Return the start of the current function." 330 | (save-excursion 331 | (if (beginning-of-defun) 332 | (point) 333 | (line-beginning-position)))) 334 | 335 | (defun lpy-next-top-level-sexp () 336 | "Go to next top level sexp." 337 | (let* ((outline-regexp "# ?\\*+") 338 | (end (lispy--outline-end))) 339 | (forward-char 1) 340 | (while (and (re-search-forward "^[^ \n]" end t) 341 | (lispy--in-string-or-comment-p)) 342 | (python-nav-end-of-statement)) 343 | (forward-char -1))) 344 | 345 | (defun lpy-prev-top-level-sexp () 346 | "Go to previous top level sexp." 347 | (let* ((outline-regexp "# ?\\*+") 348 | (beg (lispy--outline-beg))) 349 | (unless (eq beg 1) 350 | (cl-incf beg)) 351 | (while (and (re-search-backward "^[^ \n]" beg t) 352 | (or (lispy--in-string-or-comment-p) 353 | (looking-at "#")))))) 354 | 355 | (defhydra hydra-lispy-move (:pre 356 | (progn 357 | (setq hydra-is-helpful nil) 358 | (set-cursor-color "#e52b50")) 359 | :before-exit 360 | (progn 361 | (setq hydra-is-helpful t) 362 | (set-cursor-color "#ffffff"))) 363 | ("h" lispy-outline-demote "left") 364 | ("j" lispy-move-down "down") 365 | ("k" lispy-move-up "up") 366 | ("l" lispy-outline-promote "right") 367 | ("v" lpy-outline-edit-below "edit outline" :exit t) 368 | ("n" lpy-outline-add "new" :exit t) 369 | ("q" nil "quit") 370 | ("c" nil "quit")) 371 | 372 | (defun lpy-outline-edit-below () 373 | "Edit the current outline below." 374 | (interactive) 375 | (beginning-of-line) 376 | (forward-line 1) 377 | (unless (bolp) 378 | (newline))) 379 | 380 | (defun lpy-multiline-string-bnd () 381 | "Return the bounds of the current triple quoted string." 382 | (let (bnd) 383 | (and (setq bnd (lispy--bounds-string)) 384 | (> (count-lines (car bnd) (cdr bnd)) 1) 385 | bnd))) 386 | 387 | (defun lpy-down (arg) 388 | "Move down ARG times inside the current sexp." 389 | (interactive "p") 390 | (lispy--remember) 391 | (lispy-dotimes arg 392 | (cond ((lpy-line-p) 393 | (let ((beg (region-beginning)) 394 | (end (region-end)) 395 | (leftp (= (point) (region-beginning))) 396 | indent) 397 | (goto-char beg) 398 | (setq indent (concat 399 | "^" 400 | (buffer-substring-no-properties 401 | (line-beginning-position) 402 | (point)))) 403 | (forward-char) 404 | (if (re-search-forward indent (line-end-position 2) t) 405 | (progn 406 | (while (and 407 | (< (point) (point-max)) 408 | (= (point) (line-end-position))) 409 | (forward-char)) 410 | (lispy--mark (cons (point) (line-end-position)))) 411 | (lispy--mark (cons beg end))) 412 | (when leftp 413 | (exchange-point-and-mark)))) 414 | ((region-active-p) 415 | (if (lpy-listp) 416 | (cond ((lpy-arg-rightp) 417 | (unless (eq (point) (1- (cdr lpy-listp-last))) 418 | (lpy-slurp) 419 | (exchange-point-and-mark) 420 | (lpy-barf) 421 | (exchange-point-and-mark))) 422 | ((lpy-arg-leftp) 423 | (exchange-point-and-mark) 424 | (if (eq (point) (1- (cdr lpy-listp-last))) 425 | (exchange-point-and-mark) 426 | (lpy-slurp) 427 | (exchange-point-and-mark) 428 | (lpy-barf))) 429 | (t 430 | nil)) 431 | 432 | (lispy-down arg))) 433 | ((looking-at outline-regexp) 434 | (zo-down arg)) 435 | ((lpy-line-left-p) 436 | (when (looking-at " ") 437 | (forward-char 1)) 438 | (let (bnd) 439 | (if (setq bnd (lispy--bounds-comment)) 440 | (progn 441 | (goto-char (cdr bnd)) 442 | (back-to-indentation))) 443 | (let* ((offset (current-column)) 444 | (bound (if (= offset 0) 445 | (save-excursion 446 | (or (outline-next-heading) 447 | (point-max))) 448 | (save-excursion 449 | (or 450 | (let ((match-found nil)) 451 | (while (and (re-search-forward 452 | (format "^%s[^ \n]" (make-string (- offset lpy-offset) 32)) 453 | nil t) 454 | (not (lispy-looking-back ")")) 455 | (setq match-found t) 456 | (lispy--in-string-or-comment-p))) 457 | (if match-found 458 | (point))) 459 | (point-max))))) 460 | (regex (format "^%s[^ \n)]" (buffer-substring-no-properties 461 | (line-beginning-position) (point)))) 462 | (old-point (point)) 463 | bnd) 464 | (forward-char 1) 465 | (unless (catch 'done 466 | (while (re-search-forward regex bound t) 467 | (goto-char (1- (match-end 0))) 468 | (if (setq bnd (lpy-multiline-string-bnd)) 469 | (goto-char (cdr bnd)) 470 | (throw 'done t)))) 471 | (goto-char old-point)) 472 | (unless (bolp) 473 | (backward-char 1)))))))) 474 | 475 | (defun lpy-bounds-defun () 476 | "Return the bounds of the current defun." 477 | (save-excursion 478 | (let (beg) 479 | (unless (looking-at "^") 480 | (beginning-of-defun)) 481 | (setq beg (point)) 482 | (end-of-defun) 483 | (cons beg (point))))) 484 | 485 | (defun lpy-avy-symbol-action (pt) 486 | "Mark the symbol at PT." 487 | (goto-char pt) 488 | (cond ((looking-at "^")) 489 | ((looking-back "^ +" (line-beginning-position)) 490 | (backward-char)) 491 | (t 492 | (lpy-mark-symbol)))) 493 | 494 | (defun lpy-avy-symbol () 495 | "Select a symbol with avy and mark it. 496 | When on an outline, add an outline below." 497 | (interactive) 498 | (if (looking-at lispy-outline) 499 | (lpy-add-outline) 500 | (deactivate-mark) 501 | (let ((avy-action 'lpy-avy-symbol-action) 502 | (avy-handler-function (lambda (_) (throw 'done 'exit)))) 503 | (lispy--avy-do 504 | "\\_<\\sw" 505 | (or 506 | (lispy-bounds-python-block) 507 | (lpy-bounds-defun)) 508 | (lambda () 509 | (not (save-excursion 510 | (forward-char -1) 511 | (lispy--in-string-or-comment-p)))) 512 | 'at-full)))) 513 | 514 | (defun lpy-avy () 515 | "Select a line in the current function with avy." 516 | (interactive) 517 | (lispy--remember) 518 | (deactivate-mark) 519 | (let ((avy-keys lispy-avy-keys) 520 | (bnd (bounds-of-thing-at-point 'defun))) 521 | (avy-with lpy-avy 522 | (lispy--avy-do 523 | "^ *\\( \\)" 524 | bnd 525 | (lambda () (not (lispy--in-string-or-comment-p))) 526 | 'at 1)) 527 | (back-to-indentation) 528 | (backward-char))) 529 | 530 | (defun lpy-add-outline () 531 | "Add an outline below the current one." 532 | (let ((bnd (zo-bnd-subtree)) 533 | (lvl (lispy-outline-level)) 534 | (outline 535 | (when (looking-at (concat lispy-outline-header "\\*+ :$")) 536 | (concat (match-string-no-properties 0) "\n\n")))) 537 | (goto-char (cdr bnd)) 538 | (cond 539 | ((<= (- (point-max) (point)) 1) 540 | (newline) 541 | (newline)) 542 | ((looking-at "\n\n") 543 | (forward-char) 544 | (newline) 545 | (backward-char)) 546 | (t 547 | (newline))) 548 | (if outline 549 | (progn 550 | (insert outline) 551 | (backward-char)) 552 | (insert lispy-outline-header 553 | (make-string lvl ?\*) 554 | " ")))) 555 | 556 | (defun lpy-up (arg) 557 | "Move up ARG times inside the current sexp." 558 | (interactive "p") 559 | (lispy--remember) 560 | (lispy-dotimes arg 561 | (cond ((lpy-line-p) 562 | (let ((beg (region-beginning)) 563 | (end (region-end)) 564 | (leftp (= (point) (region-beginning))) 565 | indent) 566 | (unless (= beg (point-min)) 567 | (goto-char beg) 568 | (setq indent (concat 569 | "^" 570 | (buffer-substring-no-properties 571 | (line-beginning-position) 572 | (point)))) 573 | (backward-char) 574 | (if (re-search-backward indent (lpy-bof-position) t) 575 | (progn 576 | (goto-char (match-end 0)) 577 | (while (and 578 | (> (point) (point-min)) 579 | (= (point) (line-end-position))) 580 | (forward-line -1)) 581 | (lispy--mark (cons (point) (line-end-position)))) 582 | (lispy--mark (cons beg end))) 583 | (when leftp 584 | (exchange-point-and-mark))))) 585 | ((lpy-outline-p) 586 | (zo-up arg) 587 | (unless (= 0 (skip-chars-forward " ")) 588 | (backward-char))) 589 | ((region-active-p) 590 | (if (lpy-listp) 591 | (cond ((lpy-arg-rightp) 592 | (exchange-point-and-mark) 593 | (if (= (point) (1+ (car lpy-listp-last))) 594 | (exchange-point-and-mark) 595 | (lpy-slurp) 596 | (exchange-point-and-mark) 597 | (lpy-barf))) 598 | ((lpy-arg-leftp) 599 | (lpy-slurp) 600 | (exchange-point-and-mark) 601 | (lpy-barf))) 602 | 603 | (lispy-up arg))) 604 | ((lpy-line-left-p) 605 | (when (looking-at " ") 606 | (forward-char 1)) 607 | (let* ((offset (current-column)) 608 | (bound (if (= offset 0) 609 | (condition-case nil 610 | (save-excursion 611 | (lpy-back-to-outline) 612 | (1+ (point))) 613 | (error (point-min))) 614 | (save-excursion 615 | (while (and (re-search-backward 616 | (format "^%s[^ \n]" (make-string (- offset lpy-offset) 32))) 617 | (or (looking-at ")") 618 | (lispy--in-string-or-comment-p)))) 619 | (point)))) 620 | (regex (format "^%s[^ \n)]" (buffer-substring-no-properties 621 | (line-beginning-position) (point)))) 622 | bnd) 623 | (catch 'done 624 | (while (re-search-backward regex bound t) 625 | (goto-char (- (match-end 0) 1)) 626 | ;; triple quoted strings 627 | (if (setq bnd (lpy-multiline-string-bnd)) 628 | (goto-char (car bnd)) 629 | (throw 'done t)))) 630 | (let (bnd) 631 | (when (setq bnd (lispy--bounds-comment)) 632 | (goto-char (car bnd)))) 633 | (unless (bolp) 634 | (backward-char 1))))))) 635 | 636 | (defun lpy-flow () 637 | "Navigate to the next child item." 638 | (interactive) 639 | (cond ((looking-at lispy-outline) 640 | (lpy-next-top-level-sexp)) 641 | ((lpy-line-left-p) 642 | (let ((bnd (lpy-bounds-defun)) 643 | (old-pt (point))) 644 | (python-nav-forward-block) 645 | (if (> (point) (cdr bnd)) 646 | (goto-char old-pt) 647 | (backward-char)))) 648 | (t 649 | (self-insert-command 1)))) 650 | 651 | (defvar-local lpy-offset 4 652 | "Offset between indentation levels.") 653 | 654 | (defun lpy-left (arg) 655 | "Go left ARG times." 656 | (interactive "p") 657 | (lispy--remember) 658 | (cond ((lpy-outline-p) 659 | (zo-left arg)) 660 | ((lpy-line-left-p) 661 | (when (looking-at " ") 662 | (forward-char 1)) 663 | (let ((offset (current-column))) 664 | (if (= offset 0) 665 | (when (eq major-mode 'python-mode) 666 | (outline-back-to-heading)) 667 | (while (and (progn (beginning-of-line 0) 668 | (> (point) (point-min))) 669 | (or (eolp) 670 | (and (looking-at "^ +") 671 | (>= (- (match-end 0) (match-beginning 0)) 672 | offset))))) 673 | (when (looking-at "^ +)") 674 | (beginning-of-defun)) 675 | (back-to-indentation) 676 | (when (and (bolp) (eolp)) 677 | (re-search-backward "^[^\n ]" nil t)) 678 | (unless (bolp) 679 | (backward-char 1))))) 680 | (t 681 | (self-insert-command 1)))) 682 | 683 | (defun lpy-right (_arg) 684 | "Go right." 685 | (interactive "p") 686 | (lispy--remember) 687 | (cond ((lpy-outline-p) 688 | (unless (zo-right 1) 689 | (when (re-search-forward "^\\sw" (cdr (zo-bnd-subtree)) t) 690 | (backward-char)))) 691 | ((lpy-line-left-p) 692 | (let* ((cur-offset (if (bolp) 0 (1+ (current-column)))) 693 | (new-offset (+ cur-offset lpy-offset)) 694 | (regex (format "^ \\{%d,\\}[^ \n]" new-offset)) 695 | (pt (point)) 696 | (end (save-excursion 697 | (end-of-defun) 698 | (if (<= (- pt (point)) 0) 699 | (point-max) 700 | (point)))) 701 | success) 702 | (while (and (re-search-forward regex end t) 703 | (if (lispy--in-comment-p) 704 | t 705 | (setq success t) 706 | nil))) 707 | (if success 708 | (backward-char) 709 | (goto-char pt)) 710 | (unless (or (bolp) (looking-at " ")) 711 | (backward-char 1)))) 712 | ((eolp)) 713 | (t 714 | (self-insert-command 1)))) 715 | 716 | (defun lpy-different () 717 | "Switch to the different side of current sexp." 718 | (interactive) 719 | (cond ((and (region-active-p) 720 | (not (= (region-beginning) (region-end)))) 721 | (exchange-point-and-mark)) 722 | ((lpy-listp) 723 | (cond ((lpy-arg-leftp) 724 | (forward-sexp)) 725 | ((lpy-arg-rightp) 726 | (backward-sexp)))) 727 | ((or (lispy-left-p) 728 | (lispy-right-p)) 729 | (lispy-different)) 730 | ((looking-at lispy-outline) 731 | (goto-char (lispy--outline-end))) 732 | ((or (eobp) 733 | (looking-at "\n#")) 734 | (goto-char (lispy--outline-beg))) 735 | (t 736 | (user-error "Unexpected")))) 737 | 738 | (defun lpy-mark-symbol () 739 | "Mark the current symbol." 740 | (interactive) 741 | (lispy--remember) 742 | (let (bnd) 743 | (cond 744 | ;; Gradually mark e.g. self.foo.bar with "M-m M-m M-m" 745 | ;; Unmark with "b b b" 746 | ((region-active-p) 747 | (when (looking-at "\\.") 748 | (let ((end 749 | (save-excursion 750 | (goto-char (region-beginning)) 751 | (with-syntax-table python-dotty-syntax-table 752 | (+ (point) (length (symbol-name (symbol-at-point)))))))) 753 | (forward-char) 754 | (or (prog1 (search-forward "." end t) 755 | (backward-char)) 756 | (forward-sexp 1))))) 757 | ((looking-at "(") 758 | (mark-sexp) 759 | (exchange-point-and-mark)) 760 | ((and (looking-at " ?\\(\"\\)") 761 | (save-excursion 762 | (goto-char (match-beginning 1)) 763 | (setq bnd (lispy--bounds-string)) 764 | (= (car bnd) (point)))) 765 | (lispy--mark bnd)) 766 | ((looking-at " ") 767 | (skip-chars-forward " ") 768 | (lispy--mark (bounds-of-thing-at-point 'symbol))) 769 | (t 770 | (lispy--mark (bounds-of-thing-at-point 'symbol)))))) 771 | 772 | (defvar lpy-no-space t 773 | "When non-nil, don't insert a space before parens.") 774 | 775 | (defun lpy-just-one-space () 776 | "Make sure there is just one space around the point." 777 | (unless lpy-no-space 778 | (just-one-space))) 779 | 780 | (defun lpy-parens (&optional arg) 781 | "Insert a pair of parens. 782 | When ARG is non-nil, wrap the current item with parens." 783 | (interactive "P") 784 | (cond ((region-active-p) 785 | (lispy--surround-region 786 | "(" 787 | ")") 788 | (backward-char 1) 789 | (lpy-just-one-space) 790 | (backward-char 1)) 791 | (arg 792 | (let ((bnd (lispy--bounds-dwim))) 793 | (goto-char (cdr bnd)) 794 | (insert ")") 795 | (save-excursion 796 | (goto-char (car bnd)) 797 | (insert "(")))) 798 | (t 799 | (cond ((lispy--in-string-p)) 800 | ((lispy-after-string-p "(")) 801 | ((lispy-after-string-p "[")) 802 | ((lispy-after-string-p "*") 803 | (delete-char -1) 804 | (insert " * ")) 805 | ((looking-back 806 | "^ *" 807 | (line-beginning-position))) 808 | (t 809 | (unless (eq major-mode 'julia-mode) 810 | (lpy-just-one-space)))) 811 | (insert "()") 812 | (backward-char)))) 813 | 814 | (defvar lpy-back-to-outline nil 815 | "Store the point for 'lpy-back-to-outline.") 816 | 817 | (defun lpy-back-to-outline () 818 | "Go to the current outline start. 819 | Call this twice to go back." 820 | (interactive) 821 | (if (and (memq last-command '(lpy-back-to-outline)) 822 | (looking-at lispy-outline)) 823 | (lispy-pam-restore 'lpy-back-to-outline) 824 | (lispy-pam-store 'lpy-back-to-outline) 825 | (outline-back-to-heading))) 826 | 827 | (defun lpy-back-to-special () 828 | "Go back to special." 829 | (interactive) 830 | (if (lispy-bolp) 831 | (lispy-pam-restore 'lpy-back-to-special) 832 | (lispy-pam-store 'lpy-back-to-special) 833 | (back-to-indentation) 834 | (let (bnd) 835 | (when (setq bnd (lispy--bounds-string)) 836 | (goto-char (car bnd)))) 837 | (unless (bolp) 838 | (backward-char)))) 839 | 840 | (defun lpy-mark () 841 | "Mark the current thing." 842 | (interactive) 843 | (lispy--remember) 844 | (let (bnd) 845 | (cond ((region-active-p) 846 | (deactivate-mark)) 847 | ((and (looking-at " ?#") 848 | (save-excursion 849 | (forward-char 1) 850 | (setq bnd (lispy--bounds-comment)))) 851 | (set-mark (cdr bnd))) 852 | ((setq bnd (lispy-bounds-python-block)) 853 | (lispy--mark bnd) 854 | (exchange-point-and-mark) 855 | (back-to-indentation) 856 | (when (lispy-after-string-p " ") 857 | (backward-char))) 858 | ((lpy-listp) 859 | (cond ((lpy-arg-leftp) 860 | (set-mark (point)) 861 | (forward-sexp)) 862 | ((lpy-arg-rightp) 863 | (set-mark (point)) 864 | (backward-sexp)))) 865 | (t 866 | (lispy-mark))))) 867 | 868 | (defun lpy-slurp () 869 | "Slurp in an item into the region." 870 | (interactive) 871 | (cond ((region-active-p) 872 | (cond ((eolp) 873 | (end-of-line 2)) 874 | ((lpy-listp) 875 | (cond ((lpy-arg-rightp) 876 | (forward-sexp)) 877 | ((lpy-arg-leftp) 878 | (backward-sexp)))) 879 | (t 880 | (let ((regex "[[.]")) 881 | (when (looking-at regex) 882 | (forward-char)) 883 | (if (re-search-forward regex (line-end-position) t) 884 | (backward-char) 885 | (end-of-line)))))) 886 | ((lispy--in-string-or-comment-p) 887 | (self-insert-command 1)) 888 | (t 889 | (lpy-soap-command)))) 890 | 891 | (defun lpy-barf () 892 | "Barf out an item from the region." 893 | (cond ((and (region-active-p) 894 | (lpy-listp)) 895 | (cond ((lpy-arg-rightp) 896 | (let ((pt (point))) 897 | (backward-sexp) 898 | (if (= (point) (region-beginning)) 899 | (goto-char pt) 900 | (skip-chars-backward ", \n")))) 901 | ((lpy-arg-leftp) 902 | (let ((pt (point))) 903 | (forward-sexp) 904 | (if (= (point) (region-end)) 905 | (goto-char pt)) 906 | (skip-chars-forward ", \n") 907 | (backward-char))))) 908 | ((lispy--in-string-or-comment-p) 909 | (self-insert-command 1)) 910 | (t 911 | (lpy-soap-command)))) 912 | 913 | (defun lpy-plus () 914 | (interactive) 915 | (cond ((region-active-p) 916 | (er/expand-region 1)) 917 | ((lispy--in-string-p) 918 | (self-insert-command 1)) 919 | (t 920 | (lpy-soap-command)))) 921 | 922 | (defun lpy-minus () 923 | (interactive) 924 | (cond ((region-active-p) 925 | (er/expand-region -1)) 926 | ((eq major-mode 'yaml-mode) 927 | (self-insert-command 1)) 928 | ((lispy--in-string-p) 929 | (self-insert-command 1)) 930 | (t 931 | (lpy-soap-command)))) 932 | 933 | (defun lpy-open () 934 | "Insert a newline after the current marked line." 935 | (interactive) 936 | (when (and (region-active-p) 937 | (eq (line-number-at-pos (region-beginning)) 938 | (line-number-at-pos (region-end)))) 939 | (deactivate-mark) 940 | (end-of-line) 941 | (newline-and-indent))) 942 | 943 | (defun lpy-teleport () 944 | "Go to the end of the current outline." 945 | (interactive) 946 | (if (looking-at lispy-outline) 947 | (end-of-line) 948 | (self-insert-command 1))) 949 | 950 | (defun lpy-meta-return () 951 | "Insert a new heading." 952 | (interactive) 953 | (cond 954 | ((eq major-mode 'python-mode) 955 | (unless (bolp) 956 | (newline)) 957 | (insert lispy-outline-header 958 | (make-string (max (lispy-outline-level) 1) 959 | ?\*) 960 | " ") 961 | (end-of-line)) 962 | 963 | ((eq major-mode 'yaml-mode) 964 | (let ((prefix 965 | (save-excursion 966 | (back-to-indentation) 967 | (buffer-substring-no-properties 968 | (line-beginning-position) 969 | (if (looking-at "- ") 970 | (match-end 0) 971 | (point)))))) 972 | (newline) 973 | (insert prefix))))) 974 | 975 | (defun lpy-clean () 976 | "Clean up the evaluation results in the current buffer." 977 | (interactive) 978 | (save-excursion 979 | (goto-char (point-min)) 980 | (while (re-search-forward "^ *# =>" nil t) 981 | (let ((bnd (lispy--bounds-comment))) 982 | (delete-region (car bnd) (cdr bnd)) 983 | (delete-region (1- (line-beginning-position)) (point)))) 984 | (save-buffer))) 985 | 986 | (defun lpy-view () 987 | "Recenter the buffer around the current point." 988 | (interactive) 989 | (let ((window-line (count-lines (window-start) (point)))) 990 | (if (or (= window-line 0) 991 | (and (not (bolp)) (= window-line (1+ scroll-margin)))) 992 | (recenter (or (get 'lispy-recenter :line) 0)) 993 | (put 'lispy-recenter :line window-line) 994 | (recenter 0)))) 995 | 996 | (defvar sd-force-reparse) 997 | (defvar moo-jump-local-cache) 998 | (declare-function moo-flatten-namepaces "ext:function-args") 999 | (declare-function moo-format-tag-line "ext:function-args") 1000 | (declare-function moo-select-candidate "ext:function-args") 1001 | (declare-function moo-action-jump "ext:function-args") 1002 | (declare-function sd-fetch-tags "ext:function-args") 1003 | (declare-function semantic-tag-get-attribute "tag") 1004 | (declare-function semantic-tag-class "tag") 1005 | (declare-function semantic-tag-overlay "tag") 1006 | (declare-function compile-goto-error "compile") 1007 | 1008 | (defun lpy-goto (arg) 1009 | "Select a tag to jump to from tags defined in current buffer. 1010 | When ARG is 2, jump to tags in current dir." 1011 | (interactive "p") 1012 | (require 'semantic-directory) 1013 | (require 'function-args) 1014 | (let* ((file-list 1015 | (if (> arg 1) 1016 | (cl-remove-if 1017 | (lambda (x) 1018 | (string-match "^\\.#" x)) 1019 | (append (file-expand-wildcards "*.py"))) 1020 | (list (buffer-file-name)))) 1021 | (sd-force-reparse (> arg 2)) 1022 | (ready-tags 1023 | (or 1024 | (let ((tags (moo-flatten-namepaces 1025 | (sd-fetch-tags file-list)))) 1026 | (when (memq major-mode '(python-mode)) 1027 | (setq tags 1028 | (delq nil 1029 | (mapcar 1030 | (lambda (x) 1031 | (let ((s (lpy-tag-name x))) 1032 | (when s 1033 | (cons 1034 | (moo-format-tag-line 1035 | s (semantic-tag-get-attribute x :truefile)) 1036 | x)))) 1037 | tags)))) 1038 | (puthash file-list tags moo-jump-local-cache) 1039 | tags))) 1040 | (preselect (python-info-current-defun))) 1041 | (moo-select-candidate 1042 | ready-tags 1043 | #'moo-action-jump 1044 | preselect) 1045 | (unless (or (bolp) (looking-at " ")) 1046 | (backward-char 1)))) 1047 | 1048 | (defvar lpy-goto-history nil) 1049 | 1050 | (defun lpy--current-symbol () 1051 | (cl-case major-mode 1052 | (python-mode 1053 | (python-info-current-defun)) 1054 | (yaml-mode 1055 | (save-excursion 1056 | (back-to-indentation) 1057 | (thing-at-point 'symbol))))) 1058 | 1059 | (defun lpy-goto () 1060 | (interactive) 1061 | (let ((defs (lispy--py-to-el (format "lp.definitions('%s')" (buffer-file-name))))) 1062 | (ivy-read "tag: " defs 1063 | :action #'lpy-goto-action 1064 | :preselect (lpy--current-symbol) 1065 | :history 'lpy-goto-history 1066 | :caller 'lpy-goto))) 1067 | 1068 | (defun lpy-goto-action (x) 1069 | (goto-char (point-min)) 1070 | (forward-line (1- (cadr x))) 1071 | (when (looking-at " ") 1072 | (skip-chars-forward " ") 1073 | (backward-char 1))) 1074 | 1075 | (defun lpy-tag-name (tag) 1076 | "Return a pretty name for TAG." 1077 | (let* ((class (semantic-tag-class tag)) 1078 | (str (cl-case class 1079 | (function 1080 | (let ((args (semantic-tag-get-attribute tag :arguments))) 1081 | (format "%s %s (%s)" 1082 | (propertize "def" 'face 'font-lock-builtin-face) 1083 | (propertize (car tag) 'face 'font-lock-function-name-face) 1084 | (mapconcat #'car args ", ")))) 1085 | (variable 1086 | (car tag)) 1087 | (type 1088 | (propertize (car tag) 'face 'fa-face-type-definition)) 1089 | (include 1090 | (format "%s %s" 1091 | (propertize "import" 'face 'font-lock-preprocessor-face) 1092 | (car tag))) 1093 | (code 1094 | (let* ((beg) 1095 | (end) 1096 | (ov (semantic-tag-overlay tag)) 1097 | (buf (cond 1098 | ((and (overlayp ov) 1099 | (bufferp (overlay-buffer ov))) 1100 | (setq beg (overlay-start ov)) 1101 | (setq end (overlay-end ov)) 1102 | (overlay-buffer ov)) 1103 | ((arrayp ov) 1104 | (setq beg (aref ov 0)) 1105 | (setq end (aref ov 1)) 1106 | (current-buffer)))) 1107 | str) 1108 | (when (and buf 1109 | (setq str 1110 | (ignore-errors 1111 | (with-current-buffer buf 1112 | (buffer-substring-no-properties beg end)))) 1113 | (string-match (format "^%s ?=" (car tag)) str)) 1114 | (concat (car tag) "=")))) 1115 | (t 1116 | (error "Unknown class for tag: %S" tag))))) 1117 | str)) 1118 | 1119 | (defun lpy-beautify-strings () 1120 | "Replace 'foo' with \"foo\"." 1121 | (interactive) 1122 | (goto-char (point-min)) 1123 | (let (bnd) 1124 | (while (re-search-forward "'." nil t) 1125 | (unless (looking-at "''") 1126 | (when (setq bnd (lispy--bounds-string)) 1127 | (goto-char (car bnd)) 1128 | (delete-char 1) 1129 | (insert "\"") 1130 | (goto-char (cdr bnd)) 1131 | (delete-char -1) 1132 | (insert "\"")))))) 1133 | 1134 | (defun lpy-beautify-commas () 1135 | "Place spaces after commas." 1136 | (interactive) 1137 | (goto-char (point-min)) 1138 | (while (re-search-forward "\\(,\\)[^ ]" nil t) 1139 | (replace-match ", " t t nil 1))) 1140 | 1141 | (defun lpy-beautify-parens () 1142 | "Place spaces before parens." 1143 | (interactive) 1144 | (goto-char (point-min)) 1145 | (while (re-search-forward "[^ ]\\((\\)") 1146 | (replace-match " (" t t nil 1))) 1147 | 1148 | (defun lpy-quotes () 1149 | "Insert quotes." 1150 | (interactive) 1151 | (let (bnd) 1152 | (cond ((region-active-p) 1153 | (let ((beg (region-beginning)) 1154 | (end (region-end))) 1155 | (deactivate-mark) 1156 | (goto-char beg) 1157 | (insert "\"") 1158 | (goto-char (1+ end)) 1159 | (insert "\""))) 1160 | 1161 | ((and (setq bnd (lispy--bounds-string)) 1162 | (> (point) (car bnd))) 1163 | (cond 1164 | ((eq (char-after (car bnd)) ?') 1165 | (insert "\"\"") 1166 | (backward-char)) 1167 | ((and (looking-back "\"" (line-beginning-position)) 1168 | (looking-at "\"")) 1169 | (insert "\"\"\"\"") 1170 | (backward-char 2)) 1171 | ((string= (buffer-substring (car bnd) 1172 | (+ 3 (car bnd))) 1173 | "\"\"\"") 1174 | (insert "\"\"") 1175 | (backward-char 1)) 1176 | (t 1177 | (insert "\\\"\\\"") 1178 | (backward-char 2)))) 1179 | (t 1180 | (unless (or (looking-back "^ *" (line-beginning-position)) 1181 | (looking-back "\\s(" (line-beginning-position)) 1182 | (looking-back "[,(\n] *\\(\\sw\\|\\s_\\)+=" (line-beginning-position 0)) 1183 | (looking-back "\\bf" (line-beginning-position))) 1184 | (just-one-space)) 1185 | (insert "\"\"") 1186 | (backward-char))))) 1187 | 1188 | (defun lpy-split () 1189 | "Split the current string or comment." 1190 | (interactive) 1191 | (let ((bnd (lispy--bounds-string))) 1192 | (if bnd 1193 | (let ((quote-str (string (char-after (car bnd))))) 1194 | (when (looking-back " +" (line-beginning-position)) 1195 | (delete-region (match-beginning 0) (match-end 0))) 1196 | (insert quote-str "\n" quote-str) 1197 | (indent-for-tab-command)) 1198 | (indent-new-comment-line)))) 1199 | 1200 | (defun lpy-beginning-of-line () 1201 | "Go back to indentation or to the beginning of line." 1202 | (interactive) 1203 | (if (and (looking-at " ") 1204 | (looking-back " +" (line-beginning-position))) 1205 | (beginning-of-line) 1206 | (back-to-indentation) 1207 | (cond ((looking-back "^ +" (line-beginning-position)) 1208 | (backward-char)) 1209 | ((bolp)) 1210 | (t 1211 | (let ((inhibit-field-text-motion t)) 1212 | (beginning-of-line)))))) 1213 | 1214 | (defun lpy-kill-line (&optional arg) 1215 | "Kill the current line. 1216 | When in a string, kill until the end of the string. 1217 | When ARG is non-nil, kill until the beginning of the string." 1218 | (interactive "P") 1219 | (let (bnd) 1220 | (cond 1221 | ((and (setq bnd (lispy--bounds-string)) 1222 | (not (eq (point) (car bnd)))) 1223 | (if (member (buffer-substring-no-properties (- (cdr bnd) 3) (cdr bnd)) 1224 | '("\"\"\"" "'''")) 1225 | (if (> (cdr bnd) (line-end-position)) 1226 | (kill-region (point) (+ (line-end-position) 1227 | (if (eolp) 1 0))) 1228 | (kill-region (point) (- (cdr bnd) 3))) 1229 | (if arg 1230 | (kill-region 1231 | (1+ (car bnd)) 1232 | (point)) 1233 | (kill-region 1234 | (point) 1235 | (1- (cdr bnd)))))) 1236 | ((bolp) 1237 | (kill-line)) 1238 | ((eolp) 1239 | (delete-region (line-beginning-position) 1240 | (1+ (point))) 1241 | (back-to-indentation)) 1242 | (t 1243 | (kill-line))))) 1244 | 1245 | (defun lpy-delete () 1246 | "Delete the current item." 1247 | (interactive) 1248 | (cond 1249 | ((region-active-p) 1250 | (delete-active-region) 1251 | (delete-region (line-beginning-position) 1252 | (1+ (line-end-position))) 1253 | (back-to-indentation) 1254 | (unless (bolp) 1255 | (backward-char))) 1256 | ((looking-at " *$") 1257 | (let ((offset (mod (current-column) 4))) 1258 | (delete-region (point) (1+ (match-end 0))) 1259 | (when (looking-at " +") 1260 | (delete-region (match-beginning 0) 1261 | (match-end 0))) 1262 | (indent-for-tab-command) 1263 | (when (= offset 3) 1264 | (backward-char 1)))) 1265 | (t 1266 | (delete-char 1)))) 1267 | 1268 | (defun lpy-import-from-markdown () 1269 | "Use after jupyter nbconvert --to markdown." 1270 | (interactive) 1271 | (unless (eq major-mode 'markdown-mode) 1272 | (error "Please open a Markdown file")) 1273 | (let* ((md-name (file-name-nondirectory (buffer-file-name))) 1274 | (py-name (concat (file-name-sans-extension md-name) 1275 | ".py"))) 1276 | (find-file py-name) 1277 | (erase-buffer) 1278 | (insert-file-contents md-name) 1279 | (goto-char (point-min)) 1280 | (let ((idx -1) 1281 | (lvl 0)) 1282 | (while (re-search-forward "^\\(```\\|#+\\)" nil t) 1283 | (if (string= (match-string 1) "```") 1284 | (progn 1285 | (delete-region (line-beginning-position) 1286 | (line-end-position)) 1287 | (insert "#*" (make-string lvl ?*) 1288 | " " (number-to-string (cl-incf idx))) 1289 | (re-search-forward "^```") 1290 | (delete-region (line-beginning-position) 1291 | (1+ (line-end-position)))) 1292 | (let ((len (length (match-string 1)))) 1293 | (setq lvl len) 1294 | (replace-match (format "#%s" (make-string len ?*)) nil t) 1295 | (setq idx -1))))) 1296 | (save-buffer))) 1297 | 1298 | (defun lpy-outline-level () 1299 | "Compute the outline level of the heading at point." 1300 | (save-excursion 1301 | (save-match-data 1302 | (cond ((looking-at "^\\(class\\|def\\)") 1303 | (if (re-search-backward "^#\\*+" nil t) 1304 | (1+ (lpy-outline-level)) 1305 | 0)) 1306 | ((looking-at " +def") 1307 | (if (re-search-backward "\\(?:^#\\*+\\)\\|^class" nil t) 1308 | (1+ (lpy-outline-level)) 1309 | 0)) 1310 | (t 1311 | (end-of-line) 1312 | (if (re-search-backward outline-regexp nil t) 1313 | (max (cl-count ?* (match-string 0)) 1) 1314 | 0)))))) 1315 | 1316 | (defun lpy-occur-action (x) 1317 | "Goto X." 1318 | (goto-char lispy--occur-beg) 1319 | (forward-line (read x)) 1320 | (when (re-search-forward (ivy--regex ivy-text) (line-end-position) t) 1321 | (goto-char (match-beginning 0))) 1322 | (when (lispy-bolp) 1323 | (unless (bolp) 1324 | (backward-char)))) 1325 | 1326 | (defun lpy-occur () 1327 | "Swipe the current defun." 1328 | (interactive) 1329 | (swiper--init) 1330 | (unwind-protect 1331 | (ivy-read "pattern: " 1332 | (lispy--occur-candidates 1333 | (save-excursion 1334 | (unless (bolp) 1335 | (re-search-backward "^[^ \n]" nil t)) 1336 | (cons (point) 1337 | (progn 1338 | (end-of-defun) 1339 | (point))))) 1340 | :preselect (lispy--occur-preselect) 1341 | :require-match t 1342 | :update-fn (lambda () 1343 | (lispy--occur-update-input 1344 | ivy-text 1345 | (if (boundp 'ivy--current) 1346 | ivy--current 1347 | (ivy-state-current ivy-last)))) 1348 | :action #'lpy-occur-action 1349 | :caller 'lpy-occur) 1350 | (swiper--cleanup) 1351 | (when (null ivy-exit) 1352 | (goto-char swiper--opoint)))) 1353 | 1354 | (defun lpy-follow-dbg-links-filter (output) 1355 | "Filter OUTPUT. 1356 | Suitable for `comint-output-filter-functions'." 1357 | (let ((regex "^ File \"\\([^\"]+\\)\", line [0-9]+")) 1358 | (when (string-match regex output) 1359 | ;; wait for `comint-output-filter' to insert the string into the buffer 1360 | (run-at-time nil nil 1361 | (lambda () 1362 | (save-excursion 1363 | (when (and (re-search-backward regex nil t) 1364 | (file-exists-p (match-string 1))) 1365 | (save-selected-window 1366 | (compile-goto-error)))))))) 1367 | output) 1368 | 1369 | (defun lpy-switch-to-shell () 1370 | "Switch to the current process buffer." 1371 | (interactive) 1372 | (let ((buffer (process-buffer (lispy--python-proc)))) 1373 | (if buffer 1374 | (progn 1375 | (pop-to-buffer buffer) 1376 | (add-to-list 'completion-at-point-functions 'lispy-python-completion-at-point) 1377 | (add-hook 'comint-output-filter-functions 'lpy-follow-dbg-links-filter) 1378 | (when (equal (buffer-string) "") 1379 | (comint-send-input))) 1380 | (run-python "python") 1381 | (pop-to-buffer "*Python*")))) 1382 | 1383 | (defun lpy-yank () 1384 | "Yank while intelligently quoting strings." 1385 | (interactive) 1386 | (let (bnd) 1387 | (if (and (setq bnd (lispy--bounds-string)) 1388 | (not (= (point) (car bnd)))) 1389 | (cond 1390 | ((string= (buffer-substring (car bnd) 1391 | (min (point-max) 1392 | (+ 3 (car bnd)))) "\"\"\"") 1393 | (insert (current-kill 0))) 1394 | ((eq (char-after (car bnd)) ?\") 1395 | (insert (replace-regexp-in-string "\"" "\\\\\"" (current-kill 0)))) 1396 | (t 1397 | (insert (replace-regexp-in-string "'" "\\\\'" (current-kill 0))))) 1398 | (yank)))) 1399 | 1400 | (defun lpy-eval-buffer () 1401 | "Eval the current buffer." 1402 | (interactive) 1403 | (condition-case err 1404 | (if (process-live-p (lispy--python-proc)) 1405 | (let ((res 1406 | (progn 1407 | (lispy-python-middleware-reload) 1408 | (python-shell-send-string-no-output 1409 | (concat 1410 | ;; (format "lp.chfile('%s')\n" (buffer-file-name)) 1411 | (buffer-substring-no-properties 1412 | (point-min) 1413 | (point-max))) 1414 | (lispy--python-proc))))) 1415 | (setq res (concat res (lispy--eval 1416 | (format "lp.reload_module('%s')" (buffer-file-name))))) 1417 | (if (string= "" res) 1418 | (lispy-message "(ok)") 1419 | (lispy-message res))) 1420 | (error "No process")) 1421 | (eval-error 1422 | (lispy-message (cdr err))))) 1423 | 1424 | (declare-function iedit-mode "ext:iedit") 1425 | 1426 | (defun lpy-iedit () 1427 | "Edit all occurences of the current symbol." 1428 | (interactive) 1429 | (require 'iedit) 1430 | (if (or current-prefix-arg 1431 | (let ((re (concat "\\_<" (ivy-thing-at-point) "\\_>")) 1432 | (pt (point))) 1433 | (save-excursion 1434 | (save-restriction 1435 | (narrow-to-defun) 1436 | (if (/= (point) pt) 1437 | t 1438 | (goto-char (point-min)) 1439 | (not (re-search-forward re nil t 2))))))) 1440 | (iedit-mode) 1441 | (iedit-mode 0))) 1442 | 1443 | (defun lpy-import-last () 1444 | "Import the last symbol stored by `lispy-store-region-and-buffer'." 1445 | (interactive) 1446 | (let* ((buffer (get 'lispy-store-bounds 'buffer)) 1447 | (region (get 'lispy-store-bounds 'region)) 1448 | (code (with-current-buffer buffer 1449 | (lispy--string-dwim region))) 1450 | (temp-file-name (python-shell--save-temp-file code))) 1451 | (insert 1452 | (lispy--eval-python-plain 1453 | (format "lp.generate_import('%s','%s')" 1454 | temp-file-name 1455 | (expand-file-name (buffer-file-name buffer))))))) 1456 | 1457 | (defun lpy-eval () 1458 | "Eval expression." 1459 | (interactive) 1460 | (let* ((str (lispy-eval-python-str)) 1461 | (res (lispy--eval-python str t))) 1462 | (when lispy-eval-output 1463 | (setq res (concat lispy-eval-output res))) 1464 | (lispy-message res))) 1465 | 1466 | (defvar lpy-mode-map 1467 | (let ((map (make-sparse-keymap))) 1468 | (define-key map (kbd "M-o") 'lpy-back-to-special) 1469 | (define-key map (kbd "M-i") 'lpy-iedit) 1470 | (define-key map (kbd "M-r i") 'lpy-import-last) 1471 | (define-key map (kbd "C-y") 'lpy-yank) 1472 | (define-key map (kbd "C-c C-z") 'lpy-switch-to-shell) 1473 | (define-key map (kbd "C-c C-c") 'lispy-eval-current-outline) 1474 | (define-key map (kbd "C-c C-l") 'lpy-eval-buffer) 1475 | (define-key map (kbd "C-a") 'lpy-beginning-of-line) 1476 | (define-key map (kbd "C-k") 'lpy-kill-line) 1477 | (define-key map (kbd "C-d") 'lpy-delete) 1478 | (define-key map (kbd "M-m") 'lpy-mark-symbol) 1479 | (define-key map (kbd "M-RET") 'lpy-meta-return) 1480 | (define-key map (kbd "") 'lispy-shifttab) 1481 | (define-key map (kbd "M-") 'lispy-outline-demote) 1482 | (define-key map (kbd "M-") 'lispy-outline-promote) 1483 | (define-key map (kbd "C-1") 'lispy-describe-inline) 1484 | (define-key map (kbd "C-2") 'lispy-arglist-inline) 1485 | (define-key map (kbd "M-.") 'lispy-goto-symbol) 1486 | (define-key map (kbd "M-,") 'pop-tag-mark) 1487 | (define-key map (kbd "M-j") 'lpy-split) 1488 | (define-key map (kbd "SPC") 'lpy-space) 1489 | (define-key map "(" 'lpy-parens) 1490 | (define-key map "φ" 'lpy-parens) 1491 | (define-key map "\"" 'lpy-quotes) 1492 | (lpy-define-key map "a" 'lpy-avy-symbol) 1493 | (lpy-define-key map "b" 'lispy-back) 1494 | (lpy-define-key map "c" 'hydra-lispy-move/body) 1495 | (lpy-define-key map "d" 'lpy-different) 1496 | (lpy-define-key map "e" 'lispy-eval) 1497 | (lpy-define-key map "E" 'lispy-eval-and-insert) 1498 | (lpy-define-key map "f" 'lpy-flow) 1499 | (lpy-define-key map "g" 'lpy-goto) 1500 | (lpy-define-key map "h" 'lpy-left) 1501 | (lpy-define-key map "i" 'lpy-tab) 1502 | (lpy-define-key map "/" 'lpy-contents) 1503 | (lpy-define-key map "j" 'lpy-down) 1504 | (lpy-define-key map "k" 'lpy-up) 1505 | (lpy-define-key map "l" 'lpy-right) 1506 | (lpy-define-key map "m" 'lpy-mark) 1507 | (lpy-define-key map "n" 'lispy-new-copy) 1508 | (lpy-define-key map "o" 'lpy-open) 1509 | (lpy-define-key map "p" 'lpy-eval) 1510 | (lpy-define-key map "q" 'lpy-avy) 1511 | (lpy-define-key map "r" 'self-insert-command) 1512 | (lpy-define-key map "s" 'self-insert-command) 1513 | (lpy-define-key map "t" 'lpy-teleport) 1514 | (lpy-define-key map "u" 'undo) 1515 | (lpy-define-key map "v" 'lpy-view) 1516 | (lpy-define-key map "w" 'self-insert-command) 1517 | (lpy-define-key map "x" 'lispy-x) 1518 | (lpy-define-key map "y" 'lpy-occur) 1519 | (lpy-define-key map "z" 'self-insert-command) 1520 | (lpy-define-key map "B" 'lispy-ediff-regions) 1521 | (lpy-define-key map "C" 'lpy-clean) 1522 | (lpy-define-key map "D" 'pop-tag-mark) 1523 | (lpy-define-key map "F" 'lispy-goto-symbol) 1524 | (lpy-define-key map "I" 'lispy-shifttab) 1525 | (lpy-define-key map "J" 'lispy-outline-next) 1526 | (lpy-define-key map "K" 'lispy-outline-prev) 1527 | (lpy-define-key map "N" 'lispy-narrow) 1528 | (lpy-define-key map "W" 'lispy-widen) 1529 | (define-key map ">" 'lpy-slurp) 1530 | (define-key map "<" 'lpy-soap-command) 1531 | (define-key map "+" 'lpy-plus) 1532 | (define-key map "-" 'lpy-minus) 1533 | (dolist (x (number-sequence 0 9)) 1534 | (lpy-define-key map (format "%d" x) 'digit-argument)) 1535 | (dolist (x '("%" "&" "|" "=" ",")) 1536 | (define-key map x 'lpy-soap-command)) 1537 | map)) 1538 | 1539 | ;;;###autoload 1540 | (define-minor-mode lpy-mode "Minor mode for navigating Python code similarly to LISP." 1541 | :keymap lpy-mode-map 1542 | :lighter " LPY" 1543 | (if lpy-mode 1544 | (progn 1545 | (setq lispy-outline-header "#") 1546 | (setq-local outline-regexp "# ?\\*+") 1547 | (setq-local lispy-outline (concat "^" outline-regexp)) 1548 | (setq-local outline-heading-end-regexp "\n") 1549 | (setq-local outline-level 'lpy-outline-level) 1550 | (setq-local fill-paragraph-function 'lpy-fill-paragraph) 1551 | (setq-local fill-forward-paragraph-function 'lpy-fill-forward-paragraph-function) 1552 | (setq-local completion-at-point-functions '(lispy-python-completion-at-point t)) 1553 | (setq-local lispy-no-space t) 1554 | (font-lock-add-keywords major-mode lpy-font-lock-keywords) 1555 | (setq-local lpy-offset 1556 | (if (memq major-mode '(js2-mode yaml-mode)) 1557 | 2 1558 | 4))) 1559 | (font-lock-remove-keywords major-mode lpy-font-lock-keywords))) 1560 | 1561 | (provide 'lpy) 1562 | 1563 | ;;; lpy.el ends here 1564 | -------------------------------------------------------------------------------- /targets/plain.el: -------------------------------------------------------------------------------- 1 | (advice-add 2 | 'save-buffers-kill-emacs 3 | :around 4 | (lambda (orig-fn &rest args) 5 | (cl-letf (((symbol-function #'process-list) #'ignore)) 6 | (apply orig-fn args)))) 7 | (setq backup-by-copying t) 8 | (setq backup-directory-alist '(("." . "~/.emacs.d/backups"))) 9 | 10 | (require 'lpy) 11 | 12 | (use-package ivy 13 | :config (ivy-mode)) 14 | 15 | (use-package company-jedi) 16 | 17 | (use-package jedi 18 | :config 19 | (setq jedi:setup-function nil) 20 | (setf (symbol-function #'jedi:handle-post-command) (lambda nil nil))) 21 | 22 | (defun lpy-python-hook () 23 | (lpy-mode) 24 | (company-mode) 25 | (jedi:setup) 26 | (setq-local company-backends '(company-jedi company-dabbrev-code company-keywords)) 27 | (setq-local completion-at-point-functions '(lispy-python-completion-at-point t)) 28 | (electric-indent-mode -1)) 29 | 30 | (add-hook 'python-mode-hook 'lpy-python-hook) 31 | 32 | (find-file "targets/tutor.py") 33 | -------------------------------------------------------------------------------- /targets/tutor.py: -------------------------------------------------------------------------------- 1 | #* Welcome to the `lpy-mode' Tutor 2 | # 3 | # `lpy-mode' is a powerful way to interact with a Python REPL. 4 | # 5 | # ATTENTION: 6 | # The commands in the lessons will modify the code. You can always 7 | # revert the file using git. 8 | # 9 | # It is important to remember that this tutor is set up to teach by 10 | # use. That means that you need to execute the commands to learn 11 | # them properly. If you only read the text, you will forget the 12 | # commands! 13 | # 14 | # Remember that this is Emacs, you can rebind any binding that you 15 | # don't like. However, a lot of thought has been put into the current 16 | # bindings so don't be surprised if you find stuff inconvenient after 17 | # rebinding without thinking it through. 18 | # 19 | # Your point should now be at beginning of the first line. 20 | # Press =M-<= if it isn't. 21 | # 22 | # Now press =j= to go to Lesson 1. 23 | # 24 | # Press =i= to show/hide the lesson. 25 | 26 | #* Lesson 1 27 | # You are now on an outline, since your point is before "#*". 28 | # Since there is one "*", this is a first level outline. 29 | # 30 | # You can press =k= to go back to the introduction. 31 | # Or press =l= to go to the first child outline. 32 | 33 | #** Python version: 34 | # You are now on an outline, since your point is before "#**". 35 | # Since there are two "*", this is a second level outline, a child of Lesson 1. 36 | # 37 | # You can press =h= to go back to Lesson 1. 38 | # 39 | # Or press =e= to eval the current outline and move to the next one. 40 | # 41 | # Since this outline ends in ":", the result will be inserted into the 42 | # buffer as a comment. 43 | import sys 44 | print(sys.version) 45 | 46 | #** Your OS version: 47 | # You can press =e= to eval this outline. 48 | # 49 | # Or press =h= to go to the parent and =e= to eval the parent. 50 | # Evaluating the parent means evaluating all children outlines. 51 | import os 52 | os.uname() 53 | 54 | #** Clean up results 55 | # You can press =C= to clean up the evaluated results 56 | # Try pressing =CkkeeC=. 57 | # 58 | # You can use =j= and =k= to traverse outlines in a structured way, 59 | # i.e. go from outline level 2 to the next outline level 2, even 60 | # though the next closest outlint is an outline level 3. 61 | # 62 | # To traverse outlines in an unstructured way, use =J= and =K=. This 63 | # is useful now to move from a level 2 outline to Lesson 2, which is a 64 | # level 1 outline. 65 | 66 | #* Lesson 2 67 | # In the same way as you use =l= to descend into a child outline, you 68 | # can descent into code. 69 | # You can step through the code with =j= and eval it with =e=. 70 | x = 10 71 | x + x 72 | if x > 1: 73 | print("x:", x) 74 | --------------------------------------------------------------------------------