├── .dir-locals.el ├── .github └── FUNDING.yml ├── .gitignore ├── .travis.yml ├── Cookbook.py ├── Makefile ├── README.md ├── deps.edn ├── docs ├── clojure.org ├── demo-1.html ├── demo-1.org ├── demo-2.html ├── demo-2.org ├── demo-style.css ├── develop.org ├── reference.html ├── reference.org └── style.css ├── elpa.el ├── images ├── arglist-clojure.png ├── arglist-elisp.png ├── doc-clojure.png ├── doc-elisp.png └── lispy-logo.png ├── le-clojure.el ├── le-hy.el ├── le-js.el ├── le-julia.el ├── le-lisp.el ├── le-python.el ├── le-racket.el ├── le-scheme.el ├── lispy-clojure-test.clj ├── lispy-clojure.clj ├── lispy-clojure.cljs ├── lispy-inline.el ├── lispy-occur.el ├── lispy-python.py ├── lispy-tags.el ├── lispy-test.el ├── lispy.el ├── lispytutor └── lispytutor.el ├── mypy.ini ├── targets ├── check-declare.el ├── checkdoc.el ├── compile.el ├── install-deps.el ├── interactive-init.el └── tlc.clj └── test └── test_lispy-python.py /.dir-locals.el: -------------------------------------------------------------------------------- 1 | ;;; Directory Local Variables 2 | ;;; For more information see (info "(emacs) Directory Variables") 3 | 4 | ((emacs-lisp-mode 5 | (bug-reference-url-format . "https://github.com/abo-abo/lispy/issues/%s") 6 | (indent-tabs-mode . nil))) 7 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | liberapay: abo-abo 2 | patreon: abo_abo 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /init.el 2 | /gh-pages/ 3 | /.projectile 4 | /.cask/ 5 | *.elc 6 | \#*# 7 | .#* 8 | *~ 9 | *$py.class -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: xenial 2 | sudo: required 3 | language: emacs-lisp 4 | env: 5 | matrix: 6 | - emacs=emacs25 7 | - emacs=emacs-snapshot 8 | 9 | before_install: 10 | - sudo add-apt-repository -y ppa:kelleyk/emacs 11 | - sudo add-apt-repository -y ppa:ubuntu-elisp 12 | - sudo apt-get update -qq 13 | - sudo apt-get install -qq $emacs 14 | 15 | script: 16 | - make update 17 | - make test 18 | -------------------------------------------------------------------------------- /Cookbook.py: -------------------------------------------------------------------------------- 1 | def test(recipe): 2 | return ["pytest test/test_lispy-python.py"] 3 | 4 | def typecheck(recipe): 5 | return "dmypy run -- lispy-python.py" 6 | 7 | # del typecheck 8 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | emacs ?= emacs 2 | BEMACS = $(emacs) -batch -l elpa.el 3 | LOAD = -l lispy-inline.el -l lispy.el 4 | QEMACS = $(emacs) -Q -l elpa.el -l targets/interactive-init.el 5 | 6 | all: compile test 7 | 8 | update: 9 | $(emacs) -batch -l targets/install-deps.el 10 | 11 | compile: 12 | $(BEMACS) $(LOAD) -l targets/compile.el 13 | 14 | checkdoc: 15 | $(emacs) -batch -l elpa.el $(LOAD) -l targets/checkdoc.el 16 | 17 | check-declare: 18 | $(BEMACS) $(LOAD) -l targets/check-declare.el 19 | 20 | test: 21 | @echo "Using $(shell which $(emacs))..." 22 | $(BEMACS) -l lispy-test.el $(LOAD) -f ert-run-tests-batch-and-exit 23 | 24 | plain: 25 | $(QEMACS) -l elpa.el lispy.el 26 | 27 | clojure: 28 | clojure -M -e '(load-file "targets/tlc.clj")' 29 | 30 | clean: 31 | rm -f *.elc 32 | 33 | .PHONY: all clean elisp check-declare test 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![License GPL 3][badge-license]](http://www.gnu.org/licenses/gpl-3.0.txt) 2 | [![Build Status](https://travis-ci.org/abo-abo/lispy.svg?branch=master)](https://travis-ci.org/abo-abo/lispy) 3 | [![Coverage Status](https://coveralls.io/repos/abo-abo/lispy/badge.svg?branch=master)](https://coveralls.io/r/abo-abo/lispy?branch=master) 4 | [![MELPA](http://melpa.org/packages/lispy-badge.svg)](http://melpa.org/#/lispy) 5 | [![MELPA Stable](http://stable.melpa.org/packages/lispy-badge.svg)](http://stable.melpa.org/#/lispy) 6 | 7 |

8 | lispy logo 10 |

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

lispy.el demo 1: practice generating code

20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 |
Back to githubThis file in org-modeFunction reference
38 |
39 |

Intro

40 |
41 |

42 | Comes from this emacs.stackexchange question. 43 |

44 |
45 |
46 |
47 |

Task summary

48 |
49 |

50 | For LaTeX-mode, swap - to _, and swap 6 to ^. 51 |

52 |
53 |
54 |
55 |

Screencast

56 |
57 |

58 | The screencast for this demo is here: https://www.youtube.com/watch?v=2w1h48CYOMo 59 |

60 |
61 |
62 |
63 |

Resulting code

64 |
65 |
66 |
(define-key LaTeX-mode-map "_" (lambda () (interactive) (insert "-")))
 67 | (define-key LaTeX-mode-map "-" (lambda () (interactive) (insert "_")))
 68 | (define-key LaTeX-mode-map "^" (lambda () (interactive) (insert "6")))
 69 | (define-key LaTeX-mode-map "6" (lambda () (interactive) (insert "^")))
 70 | 
71 |
72 |
73 |
74 |
75 |

How to generate this code:

76 |
77 |
78 |
79 |

step one

80 |
81 |

82 | Write: 83 |

84 |
85 |
"-" "_"
 86 | "6" "^"
 87 | 
88 |
89 |
    90 |
  • position the point at the start
  • 91 |
  • C-7 to add one cursor
  • 92 |
  • M-m to mark the first string
  • 93 |
  • > to extend the region to the second string
  • 94 |
  • c to clone region
  • 95 |
  • i to select the first element of the region
  • 96 |
  • s to move the region down
  • 97 |
  • C-7 to cancel multiple-cursors.
  • 98 |
99 |

100 | You should now have: 101 |

102 |
103 |
"_" "-"
104 | "-" "_"
105 | "^" "6"
106 | "6" "^"
107 | 
108 |
109 |
110 |
111 |
112 |

step two

113 |
114 |

115 | Write: 116 |

117 |
118 |
(define-key LaTeX-mode-map (lambda () (interactive) (insert)))
119 | 
120 |
121 |

122 | Kill it with C-, or C-k. 123 |

124 |
125 |
126 |
127 |

step three

128 |
129 |
    130 |
  • position the point at the start
  • 131 |
  • M-3 C-7 to add three cursors
  • 132 |
  • C-y to paste the code from before
  • 133 |
  • add one space
  • 134 |
  • M-m mark
  • 135 |
  • > grow
  • 136 |
  • ok insert up
  • 137 |
  • iw select first and move up
  • 138 |
  • jj go down twice
  • 139 |
  • okok insert up twice
  • 140 |
  • C-7 to cancel multiple-cursors
  • 141 |
142 | 143 |
144 |

145 | <br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br> 146 | <br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br> 147 | <br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br> 148 |

149 | 150 |
151 |
152 |
153 |
154 |
155 | 156 | 157 | -------------------------------------------------------------------------------- /docs/demo-1.org: -------------------------------------------------------------------------------- 1 | #+TITLE: lispy.el demo 1: practice generating code 2 | #+LANGUAGE: en 3 | #+OPTIONS: H:3 num:nil toc:nil 4 | #+HTML_HEAD: 5 | 6 | | [[https://github.com/abo-abo/lispy][Back to github]] | [[https://raw.githubusercontent.com/abo-abo/lispy/gh-pages/demo-1.org][This file in org-mode]] | [[http://abo-abo.github.io/lispy/][Function reference]] | 7 | 8 | * Intro 9 | Comes from [[http://emacs.stackexchange.com/questions/3881/changing-the-role-of-the-underline-and-the-minus-sign-just-in-latex-mode][this emacs.stackexchange question]]. 10 | * Task summary 11 | For =LaTeX-mode=, swap ~-~ to ~_~, and swap ~6~ to ~^~. 12 | * Screencast 13 | The screencast for this demo is here: https://www.youtube.com/watch?v=2w1h48CYOMo 14 | * Resulting code 15 | #+begin_src emacs-lisp 16 | (define-key LaTeX-mode-map "_" (lambda () (interactive) (insert "-"))) 17 | (define-key LaTeX-mode-map "-" (lambda () (interactive) (insert "_"))) 18 | (define-key LaTeX-mode-map "^" (lambda () (interactive) (insert "6"))) 19 | (define-key LaTeX-mode-map "6" (lambda () (interactive) (insert "^"))) 20 | #+end_src 21 | * How to generate this code: 22 | ** step one 23 | Write: 24 | #+begin_src emacs-lisp 25 | "-" "_" 26 | "6" "^" 27 | #+end_src 28 | - position the point at the start 29 | - ~C-7~ to add one cursor 30 | - ~M-m~ to mark the first string 31 | - ~>~ to extend the region to the second string 32 | - ~c~ to clone region 33 | - ~i~ to select the first element of the region 34 | - ~s~ to move the region down 35 | - ~C-7~ to cancel =multiple-cursors=. 36 | You should now have: 37 | #+begin_src emacs-lisp 38 | "_" "-" 39 | "-" "_" 40 | "^" "6" 41 | "6" "^" 42 | #+end_src 43 | ** step two 44 | Write: 45 | #+begin_src emacs-lisp 46 | (define-key LaTeX-mode-map (lambda () (interactive) (insert))) 47 | #+end_src 48 | Kill it with ~C-,~ or ~C-k~. 49 | ** step three 50 | - position the point at the start 51 | - ~M-3 C-7~ to add three cursors 52 | - ~C-y~ to paste the code from before 53 | - add one space 54 | - ~M-m~ mark 55 | - ~>~ grow 56 | - ~ok~ insert up 57 | - ~iw~ select first and move up 58 | - ~jj~ go down twice 59 | - ~okok~ insert up twice 60 | - ~C-7~ to cancel =multiple-cursors= 61 | 62 | #+BEGIN_HTML 63 |
















64 |
















65 |
















66 | #+END_HTML 67 | -------------------------------------------------------------------------------- /docs/demo-2.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | lispy.el demo 2: the substitution model for procedure application 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 |
18 |
19 |

lispy.el demo 2: the substitution model for procedure application

20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 |
Back to githubThis file in org-modeFunction reference
38 |
39 |

Intro

40 |
41 |

42 | Comes from this emacs.stackexchange question. 43 |

44 |
45 |
46 |
47 |

Task summary

48 |
49 |

50 | Understand how this code works: 51 |

52 |
53 |
(defun triangle-using-cond (number)
 54 |   (cond ((<= number 0) 0)
 55 |         ((= number 1) 1)
 56 |         ((> number 1)
 57 |          (+ number (triangle-using-cond (1- number))))))
 58 | 
59 |
60 |

61 | I'll use The Substitution Model for Procedure Application from SICP. 62 |

63 |
64 |
65 |
66 |

Screencast

67 |
68 |

69 | The screencast for this demo is here: https://www.youtube.com/watch?v=D7mK2q3yve4 70 |

71 |
72 |
73 |
74 |

Step-by-step expansion

75 |
76 |
77 |
78 |

step 1

79 |
80 |

81 | Start with this code: 82 |

83 |
84 |
(defun triangle-using-cond (number)
 85 |   (cond ((<= number 0) 0)
 86 |         ((= number 1) 1)
 87 |         ((> number 1)
 88 |          (+ number (triangle-using-cond (1- number))))))
 89 | (triangle-using-cond 4)
 90 | 
91 |
92 | 93 |

94 | Eval the defun with e. Then do je to eval the next expression to 95 | get 10. 96 |

97 |
98 |
99 |
100 |

step 2

101 |
102 |

103 | Press xfM to get: 104 |

105 |
106 |
(cond ((<= 4 0)
107 |        0)
108 |       ((= 4 1)
109 |        1)
110 |       ((> 4 1)
111 |        (+ 4 (triangle-using-cond (1- 4)))))
112 | 
113 |
114 |

115 | With e check that the result is still 10. 116 |

117 |
118 |
119 |
120 |

step 3

121 |
122 |

123 | Evaluate the cond branches in your mind and simplify with qhrr. 124 |

125 |
126 |
(+ 4 (triangle-using-cond (1- 4)))
127 | 
128 |
129 |

130 | Then ffxr. 131 |

132 |
133 |
(+ 4 (triangle-using-cond 3))
134 | 
135 |
136 |
137 |
138 |
139 |

step 4

140 |
141 |

142 | Press xfM again: 143 |

144 |
145 |
(+ 4 (cond ((<= 3 0)
146 |               0)
147 |              ((= 3 1)
148 |               1)
149 |              ((> 3 1)
150 |               (+ 3 (triangle-using-cond (1- 3))))))
151 | 
152 |
153 |

154 | Evaluate the cond branches in your mind and simplify with qirr. 155 |

156 |
157 |
(+ 4 (+ 3 (triangle-using-cond (1- 3))))
158 | 
159 |
160 |

161 | Simplify further with ffxr. 162 |

163 |
164 |
(+ 4 (+ 3 (triangle-using-cond 2)))
165 | 
166 |
167 |
168 |
169 |
170 |

step 5

171 |
172 |

173 | Press xfM again: 174 |

175 |
176 |
(+ 4 (+ 3 (cond ((<= 2 0)
177 |                    0)
178 |                   ((= 2 1)
179 |                    1)
180 |                   ((> 2 1)
181 |                    (+ 2 (triangle-using-cond (1- 2)))))))
182 | 
183 |
184 |

185 | Evaluate the cond branches in your mind and simplify with qjrr. 186 |

187 |
188 |
(+ 4 (+ 3 (+ 2 (triangle-using-cond (1- 2)))))
189 | 
190 |
191 |

192 | Simplify further with ffxr. 193 |

194 |
195 |
(+ 4 (+ 3 (+ 2 (triangle-using-cond 1))))
196 | 
197 |
198 |
199 |
200 |
201 |

step 6

202 |
203 |

204 | Press xfM again: 205 |

206 |
207 |
(+ 4 (+ 3 (+ 2 (cond ((<= 1 0)
208 |                         0)
209 |                        ((= 1 1)
210 |                         1)
211 |                        ((> 1 1)
212 |                         (+ 1 (triangle-using-cond (1- 1))))))))
213 | 
214 |
215 |

216 | Evaluate the cond branches in your mind and simplify with akrr. 217 |

218 |
219 |
(+ 4 (+ 3 (+ 2 1)))
220 | 
221 |
222 |

223 | C-e e to check that the result is still 10. That's it. 224 |

225 | 226 |
227 |

228 | <br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br> 229 | <br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br> 230 | <br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br> 231 |

232 | 233 |
234 |
235 |
236 |
237 |
238 | 239 | 240 | -------------------------------------------------------------------------------- /docs/demo-2.org: -------------------------------------------------------------------------------- 1 | #+TITLE: lispy.el demo 2: the substitution model for procedure application 2 | #+LANGUAGE: en 3 | #+OPTIONS: H:3 num:nil toc:nil 4 | #+HTML_HEAD: 5 | 6 | | [[https://github.com/abo-abo/lispy][Back to github]] | [[https://raw.githubusercontent.com/abo-abo/lispy/gh-pages/demo-2.org][This file in org-mode]] | [[http://abo-abo.github.io/lispy/][Function reference]] | 7 | 8 | * Setup :noexport: 9 | #+begin_src emacs-lisp :exports results :results silent 10 | (defun make-html-cursor--replace (x) 11 | (if (string= "||\n" x) 12 | " \n" 13 | (if (string= "||[" x) 14 | "[" 15 | (format "%s" 16 | (regexp-quote 17 | (substring x 2)))))) 18 | 19 | (defun make-html-cursor (str x y) 20 | (replace-regexp-in-string 21 | "||\\(.\\|\n\\)" 22 | #'make-html-cursor--replace 23 | str)) 24 | 25 | (setq org-export-filter-src-block-functions '(make-html-cursor)) 26 | (setq org-html-validation-link nil) 27 | (setq org-html-postamble nil) 28 | (setq org-html-preamble "") 29 | (setq org-html-text-markup-alist 30 | '((bold . "%s") 31 | (code . "%s") 32 | (italic . "%s") 33 | (strike-through . "%s") 34 | (underline . "%s") 35 | (verbatim . "%s"))) 36 | (setq org-html-style-default nil) 37 | (setq org-html-head-include-scripts nil) 38 | #+end_src 39 | 40 | * Intro 41 | Comes from [[http://emacs.stackexchange.com/questions/3203/how-to-understand-this-recursion-code][this emacs.stackexchange question]]. 42 | * Task summary 43 | Understand how this code works: 44 | #+begin_src emacs-lisp 45 | (defun triangle-using-cond (number) 46 | (cond ((<= number 0) 0) 47 | ((= number 1) 1) 48 | ((> number 1) 49 | (+ number (triangle-using-cond (1- number)))))) 50 | #+end_src 51 | I'll use [[http://mitpress.mit.edu/sicp/full-text/book/book-Z-H-10.html#%25_sec_1.1.5][The Substitution Model for Procedure Application]] from SICP. 52 | * Screencast 53 | The screencast for this demo is here: https://www.youtube.com/watch?v=D7mK2q3yve4 54 | * Step-by-step expansion 55 | ** step 1 56 | Start with this code: 57 | #+begin_src emacs-lisp 58 | ||(defun triangle-using-cond (number) 59 | (cond ((<= number 0) 0) 60 | ((= number 1) 1) 61 | ((> number 1) 62 | (+ number (triangle-using-cond (1- number)))))) 63 | (triangle-using-cond 4) 64 | #+end_src 65 | 66 | Eval the defun with ~e~. Then do ~je~ to eval the next expression to 67 | get =10=. 68 | ** step 2 69 | Press ~xfM~ to get: 70 | #+begin_src emacs-lisp 71 | ||(cond ((<= 4 0) 72 | 0) 73 | ((= 4 1) 74 | 1) 75 | ((> 4 1) 76 | (+ 4 (triangle-using-cond (1- 4))))) 77 | #+end_src 78 | With ~e~ check that the result is still =10=. 79 | ** step 3 80 | Evaluate the =cond= branches in your mind and simplify with ~qhrr~. 81 | #+begin_src emacs-lisp 82 | ||(+ 4 (triangle-using-cond (1- 4))) 83 | #+end_src 84 | Then ~ffxr~. 85 | #+begin_src emacs-lisp 86 | (+ 4 ||(triangle-using-cond 3)) 87 | #+end_src 88 | 89 | ** step 4 90 | Press ~xfM~ again: 91 | #+begin_src emacs-lisp 92 | (+ 4 ||(cond ((<= 3 0) 93 | 0) 94 | ((= 3 1) 95 | 1) 96 | ((> 3 1) 97 | (+ 3 (triangle-using-cond (1- 3)))))) 98 | #+end_src 99 | Evaluate the =cond= branches in your mind and simplify with ~qirr~. 100 | #+begin_src emacs-lisp 101 | (+ 4 ||(+ 3 (triangle-using-cond (1- 3)))) 102 | #+end_src 103 | Simplify further with ~ffxr~. 104 | #+begin_src emacs-lisp 105 | (+ 4 (+ 3 ||(triangle-using-cond 2))) 106 | #+end_src 107 | 108 | ** step 5 109 | Press ~xfM~ again: 110 | #+begin_src emacs-lisp 111 | (+ 4 (+ 3 ||(cond ((<= 2 0) 112 | 0) 113 | ((= 2 1) 114 | 1) 115 | ((> 2 1) 116 | (+ 2 (triangle-using-cond (1- 2))))))) 117 | #+end_src 118 | Evaluate the =cond= branches in your mind and simplify with ~qjrr~. 119 | #+begin_src emacs-lisp 120 | (+ 4 (+ 3 ||(+ 2 (triangle-using-cond (1- 2))))) 121 | #+end_src 122 | Simplify further with ~ffxr~. 123 | #+begin_src emacs-lisp 124 | (+ 4 (+ 3 (+ 2 ||(triangle-using-cond 1)))) 125 | #+end_src 126 | 127 | ** step 6 128 | Press ~xfM~ again: 129 | #+begin_src emacs-lisp 130 | (+ 4 (+ 3 (+ 2 ||(cond ((<= 1 0) 131 | 0) 132 | ((= 1 1) 133 | 1) 134 | ((> 1 1) 135 | (+ 1 (triangle-using-cond (1- 1)))))))) 136 | #+end_src 137 | Evaluate the =cond= branches in your mind and simplify with ~akrr~. 138 | #+begin_src emacs-lisp 139 | (+ 4 (+ 3 (+ 2 ||1))) 140 | #+end_src 141 | ~C-e~ ~e~ to check that the result is still =10=. That's it. 142 | 143 | #+BEGIN_HTML 144 |
















145 |
















146 |
















147 | #+END_HTML 148 | -------------------------------------------------------------------------------- /docs/demo-style.css: -------------------------------------------------------------------------------- 1 | body { 2 | color: #333; 3 | background-color: #ffffff; 4 | margin-left: 1em; 5 | margin-right: auto; 6 | font-family: 'Ubuntu Mono', sans-serif; 7 | max-width: 50em; 8 | } 9 | 10 | body a { 11 | color: blue; 12 | } 13 | 14 | h2 { 15 | font-weight: normal; 16 | text-indent: 0; 17 | border-radius: 15px; 18 | background-color: #d6d8ec; 19 | text-align: left; 20 | padding: 3px 3px 3px 3px; 21 | } 22 | 23 | kbd { 24 | padding:0.1em 0.6em; 25 | border:1px solid #ccc; 26 | font-size:13px; 27 | font-weight:bold; 28 | font-family:monospace; 29 | background-color:#d6d8ec; 30 | color:#333; 31 | -moz-box-shadow:0 1px 0px rgba(0, 0, 0, 0.2),0 0 0 2px #ffffff inset; 32 | -webkit-box-shadow:0 1px 0px rgba(0, 0, 0, 0.2),0 0 0 2px #ffffff inset; 33 | box-shadow:0 1px 0px rgba(0, 0, 0, 0.2),0 0 0 2px #ffffff inset; 34 | -moz-border-radius:3px; 35 | -webkit-border-radius:3px; 36 | border-radius:3px; 37 | display:inline-block; 38 | margin:0 0.1em; 39 | text-shadow:0 1px 0 #fff; 40 | line-height:1.4; 41 | white-space:nowrap; 42 | } 43 | 44 | body a code { 45 | color: black; 46 | } 47 | 48 | code { 49 | border: 1px solid Silver; 50 | background-color: #ffffdc; 51 | } 52 | 53 | pre { 54 | border: 1px solid Silver; 55 | background-color: #eeeeee; 56 | padding: 3px; 57 | margin-left: 1em; 58 | } 59 | 60 | cursor { 61 | color: #fff; 62 | background-color: #000; 63 | overflow: hidden; 64 | } 65 | 66 | h3 { 67 | counter-reset: chapter; 68 | } 69 | 70 | h4 { 71 | margin-left: auto; 72 | } 73 | 74 | table, td, th { 75 | border: 0px; 76 | } 77 | 78 | th { 79 | background-color:#d6d8ec; 80 | } 81 | 82 | tr:nth-child(odd) { 83 | background-color:#fff; 84 | } 85 | tr:nth-child(even) { 86 | background-color:#d6d8ec; 87 | } 88 | 89 | .region { 90 | color: #ffffff; 91 | background-color: #f9b593; 92 | } 93 | 94 | /* 95 | FILE ARCHIVED ON 13:07:38 Feb 14, 2025 AND RETRIEVED FROM THE 96 | INTERNET ARCHIVE ON 15:45:25 Apr 24, 2025. 97 | JAVASCRIPT APPENDED BY WAYBACK MACHINE, COPYRIGHT INTERNET ARCHIVE. 98 | 99 | ALL OTHER CONTENT MAY ALSO BE PROTECTED BY COPYRIGHT (17 U.S.C. 100 | SECTION 108(a)(3)). 101 | */ 102 | /* 103 | playback timings (ms): 104 | captures_list: 0.691 105 | exclusion.robots: 0.033 106 | exclusion.robots.policy: 0.019 107 | esindex: 0.012 108 | cdx.remote: 72.665 109 | LoadShardBlock: 481.767 (3) 110 | PetaboxLoader3.datanode: 125.735 (4) 111 | PetaboxLoader3.resolve: 445.637 (3) 112 | load_resource: 1736.507 113 | */ 114 | -------------------------------------------------------------------------------- /docs/develop.org: -------------------------------------------------------------------------------- 1 | * Introduction 2 | ** Installing dependencies 3 | Run this to install/upgrade dependecies from MELPA. 4 | #+begin_src sh 5 | make update 6 | #+end_src 7 | 8 | To use MELPA-Stable, adjust the command: 9 | #+begin_src sh 10 | MELPA_STABLE=1 make update 11 | #+end_src 12 | 13 | ** Running tests 14 | #+begin_src sh 15 | make test 16 | #+end_src 17 | 18 | ** Running interactively 19 | #+begin_src sh 20 | make elisp 21 | #+end_src 22 | 23 | or: 24 | #+begin_src sh 25 | MELPA_STABLE=1 make elisp 26 | #+end_src 27 | -------------------------------------------------------------------------------- /docs/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | color: #333; 3 | background-color: #ffffff; 4 | margin-left: 1em; 5 | margin-right: auto; 6 | font-family: 'Ubuntu Mono', sans-serif; 7 | max-width: 50em; 8 | } 9 | 10 | body a { 11 | color: blue; 12 | } 13 | 14 | h2 { 15 | font-weight: normal; 16 | text-indent: 0; 17 | border-radius: 15px; 18 | background-color: #d6d8ec; 19 | text-align: left; 20 | padding: 3px 3px 3px 3px; 21 | } 22 | 23 | h2 a[id^="unnumbered"] { 24 | background-color: #d6d8ec; 25 | } 26 | 27 | h2 a { 28 | color: white; 29 | background-color:#777777; 30 | font-size:18px; 31 | border-radius:3px; 32 | padding: 0px 5px 0px 5px; 33 | } 34 | 35 | h2 a:empty { 36 | display: none; 37 | } 38 | 39 | kbd { 40 | padding:0.1em 0.6em; 41 | border:1px solid #ccc; 42 | font-size:13px; 43 | font-weight:bold; 44 | font-family:monospace; 45 | background-color:#d6d8ec; 46 | color:#333; 47 | -moz-box-shadow:0 1px 0px rgba(0, 0, 0, 0.2),0 0 0 2px #ffffff inset; 48 | -webkit-box-shadow:0 1px 0px rgba(0, 0, 0, 0.2),0 0 0 2px #ffffff inset; 49 | box-shadow:0 1px 0px rgba(0, 0, 0, 0.2),0 0 0 2px #ffffff inset; 50 | -moz-border-radius:3px; 51 | -webkit-border-radius:3px; 52 | border-radius:3px; 53 | display:inline-block; 54 | margin:-0.2em 0.1em; 55 | text-shadow:0 1px 0 #fff; 56 | line-height:1.4; 57 | white-space:nowrap; 58 | } 59 | 60 | body a code { 61 | color: black; 62 | border: 1px solid Blue; 63 | border-radius:3px; 64 | } 65 | 66 | code { 67 | border: 1px solid Silver; 68 | background-color: #ffffdc; 69 | } 70 | 71 | pre { 72 | border: 1px solid Silver; 73 | background-color: #eeeeee; 74 | padding: 3px; 75 | margin-left: 1em; 76 | } 77 | 78 | cursor { 79 | color: #fff; 80 | background-color: #000; 81 | overflow: hidden; 82 | } 83 | 84 | h3 { 85 | counter-reset: chapter; 86 | } 87 | 88 | h4:before { 89 | content: "case " counter(chapter) ": "; 90 | counter-increment: chapter; 91 | } 92 | 93 | h4 { 94 | margin-left: auto; 95 | } 96 | 97 | table, td, th { 98 | border: 0px; 99 | } 100 | 101 | td { 102 | vertical-align:top; 103 | } 104 | 105 | th.org-left { 106 | text-align: left; 107 | } 108 | 109 | th { 110 | background-color:#d6d8ec; 111 | } 112 | 113 | tr:nth-child(odd) { 114 | background-color:#fff; 115 | border-bottom: 1pt solid black; 116 | } 117 | tr:nth-child(even) { 118 | background-color:#d6d8ec; 119 | border-bottom: 1pt solid black; 120 | } 121 | 122 | .region { 123 | color: #ffffff; 124 | background-color: #f9b593; 125 | } 126 | 127 | /* 128 | FILE ARCHIVED ON 12:15:01 Feb 14, 2025 AND RETRIEVED FROM THE 129 | INTERNET ARCHIVE ON 15:20:51 Apr 24, 2025. 130 | JAVASCRIPT APPENDED BY WAYBACK MACHINE, COPYRIGHT INTERNET ARCHIVE. 131 | 132 | ALL OTHER CONTENT MAY ALSO BE PROTECTED BY COPYRIGHT (17 U.S.C. 133 | SECTION 108(a)(3)). 134 | */ 135 | /* 136 | playback timings (ms): 137 | captures_list: 1.961 138 | exclusion.robots: 0.055 139 | exclusion.robots.policy: 0.039 140 | esindex: 0.014 141 | cdx.remote: 15.406 142 | LoadShardBlock: 86.831 (3) 143 | PetaboxLoader3.datanode: 77.467 (4) 144 | load_resource: 806.696 145 | PetaboxLoader3.resolve: 729.209 146 | */ 147 | -------------------------------------------------------------------------------- /elpa.el: -------------------------------------------------------------------------------- 1 | ;; (setq package-user-dir 2 | ;; (expand-file-name 3 | ;; (format "~/.elpa/%s/elpa" 4 | ;; (concat emacs-version (when (getenv "MELPA_STABLE") "-stable"))))) 5 | ;; (package-initialize) 6 | ;; (setq package-archives 7 | ;; (list (if (getenv "MELPA_STABLE") 8 | ;; '("melpa-stable" . "https://stable.melpa.org/packages/") 9 | ;; '("melpa" . "http://melpa.org/packages/")) 10 | ;; '("gnu" . "http://elpa.gnu.org/packages/"))) 11 | 12 | 13 | (add-to-list 'load-path default-directory) 14 | 15 | ;; Silence the loading message 16 | (setq iedit-toggle-key-default nil) 17 | 18 | (defun straight-reload-all () 19 | (interactive) 20 | (let ((build-dir (expand-file-name "straight/build/" user-emacs-directory))) 21 | (dolist (pkg (delete "cl-lib" (delete ".." (delete "." (directory-files build-dir))))) 22 | (let* ((dir (expand-file-name pkg build-dir)) 23 | (autoloads (car (directory-files dir t "-autoloads.el")))) 24 | (add-to-list 'load-path dir) 25 | (when autoloads 26 | (load autoloads t 'nomessage)))))) 27 | (straight-reload-all) 28 | (message "load-path: %S" load-path) 29 | -------------------------------------------------------------------------------- /images/arglist-clojure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enzuru/lispy/b6e1d5c02c0d506a003731dfc310e330094f6749/images/arglist-clojure.png -------------------------------------------------------------------------------- /images/arglist-elisp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enzuru/lispy/b6e1d5c02c0d506a003731dfc310e330094f6749/images/arglist-elisp.png -------------------------------------------------------------------------------- /images/doc-clojure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enzuru/lispy/b6e1d5c02c0d506a003731dfc310e330094f6749/images/doc-clojure.png -------------------------------------------------------------------------------- /images/doc-elisp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enzuru/lispy/b6e1d5c02c0d506a003731dfc310e330094f6749/images/doc-elisp.png -------------------------------------------------------------------------------- /images/lispy-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enzuru/lispy/b6e1d5c02c0d506a003731dfc310e330094f6749/images/lispy-logo.png -------------------------------------------------------------------------------- /le-clojure.el: -------------------------------------------------------------------------------- 1 | ;;; le-clojure.el --- lispy support for Clojure. -*- lexical-binding: t -*- 2 | 3 | ;; Copyright (C) 2014-2019 Oleh Krehel 4 | 5 | ;; This file is not part of GNU Emacs 6 | 7 | ;; This file is free software; you can redistribute it and/or modify 8 | ;; it under the terms of the GNU General Public License as published by 9 | ;; the Free Software Foundation; either version 3, or (at your option) 10 | ;; any later version. 11 | 12 | ;; This program is distributed in the hope that it will be useful, 13 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | ;; GNU General Public License for more details. 16 | 17 | ;; For a full copy of the GNU General Public License 18 | ;; see . 19 | 20 | ;;; Commentary: 21 | ;; 22 | 23 | ;;; Code: 24 | 25 | ;;* Requires 26 | (require 'cider-client nil t) 27 | (require 'cider-connection nil t) 28 | (require 'cider-eval nil t) 29 | (require 'cider-find nil t) 30 | (require 'cider-debug nil t) 31 | 32 | (defcustom lispy-clojure-eval-method 'cider 33 | "REPL used for eval." 34 | :type '(choice 35 | (const :tag "CIDER" cider) 36 | (const :tag "UNREPL" spiral)) 37 | :group 'lispy) 38 | 39 | (defcustom lispy-cider-connect-method 'cider-jack-in 40 | "Function used to create a CIDER connection." 41 | :type '(choice 42 | (const cider-jack-in) 43 | (const cider-connect) 44 | (function :tag "Custom")) 45 | :group 'lispy) 46 | 47 | ;;* Namespace 48 | (defvar lispy--clojure-ns "user" 49 | "Store the last evaluated *ns*.") 50 | 51 | (defvar lispy--clojure-namespace-name-regex 52 | "^(\\(clojure.core/\\)?\\(in-\\)?ns\\+?[ 53 | [:space:]]+\\(?:\\(?:\\(#?\\^{[^}]*}\\)\\|\\(?:\\^:[^[:space:]]+\\)*\\)[ 54 | [:space:]]+\\)*[':]?\\([^\"()[:space:]]+\\_>\\)" 55 | "Store the obsoleted `clojure-namespace-name-regex'.") 56 | 57 | (defun lispy--clojure-detect-ns () 58 | "When there's only one (ns ...) in the buffer, use it." 59 | (save-excursion 60 | (goto-char (point-min)) 61 | (when (re-search-forward lispy--clojure-namespace-name-regex nil t) 62 | (let ((ns (match-string-no-properties 4))) 63 | (when (not (re-search-forward lispy--clojure-namespace-name-regex nil t)) 64 | (setq lispy--clojure-ns ns)))))) 65 | 66 | ;;* User wrapper for eval 67 | (defvar lispy--clojure-middleware-loaded-hash (make-hash-table :test #'equal) 68 | "Nil if the Clojure middleware in \"lispy-clojure.clj\" wasn't loaded yet.") 69 | 70 | (defun lispy--clojure-process-buffer () 71 | (if (or org-src-mode (eq major-mode 'org-mode)) 72 | (cadr (first (sesman--all-system-sessions 'CIDER))) 73 | (let ((cur-type (cider-repl-type-for-buffer))) 74 | (car (cider-repls cur-type nil))))) 75 | 76 | (defun lispy--clojure-middleware-loaded-p () 77 | (let ((conn (lispy--clojure-process-buffer))) 78 | (and conn (gethash conn lispy--clojure-middleware-loaded-hash)))) 79 | 80 | (defun lispy--clojure-babashka-p () 81 | (ignore-errors (cider--babashka-version))) 82 | 83 | (defun lispy--eval-clojure-context (e-str) 84 | (cond 85 | ((or (eq major-mode 'clojurescript-mode) 86 | (lispy--clojure-babashka-p)) 87 | e-str) 88 | ((string-match-p "#break" e-str) 89 | e-str) 90 | ((lispy--clojure-middleware-loaded-p) 91 | (let ((context-str 92 | (condition-case nil 93 | (let ((deactivate-mark nil)) 94 | (save-mark-and-excursion 95 | (lispy--out-backward 1 t) 96 | (deactivate-mark) 97 | (lispy--string-dwim))) 98 | (error "")))) 99 | (when (and (lispy--leftp) 100 | (looking-back "(for[ \t\n]*" (line-beginning-position -1))) 101 | (let* ((e-str-1 (save-excursion 102 | (forward-char 1) 103 | (forward-sexp 2) 104 | (lispy--string-dwim))) 105 | (coll (read (lispy--eval-clojure-1 106 | (format "(lispy.clojure/with-shadows (map str %s))" e-str-1) 107 | nil))) 108 | (idx (lispy--idx-from-list coll)) 109 | (sym (save-excursion 110 | (forward-char 1) 111 | (lispy--string-dwim)))) 112 | (setq e-str (format "%s (nth %s %d)" sym e-str-1 idx)))) 113 | (format (if (memq this-command '(special-lispy-eval 114 | special-lispy-eval-and-insert 115 | lispy-eval-current-outline)) 116 | "(lispy.clojure/pp (lispy.clojure/reval %S %S :file %S :line %S))" 117 | "(lispy.clojure/reval %S %S :file %S :line %S)") 118 | e-str 119 | context-str 120 | (buffer-file-name) 121 | (line-number-at-pos)))) 122 | (t 123 | e-str))) 124 | 125 | (defun lispy-eval-clojure (str) 126 | "Eval STR as a Clojure expression." 127 | (lispy--clojure-detect-ns) 128 | (if (eq lispy-clojure-eval-method 'spiral) 129 | (lispy--eval-clojure-spiral str) 130 | (lispy--eval-clojure-cider str))) 131 | 132 | ;;* Start REPL wrapper for eval 133 | (defvar lispy--clojure-hook-lambda nil 134 | "Store a lambda to call.") 135 | 136 | (defun lispy--clojure-eval-hook-lambda () 137 | "Call `lispy--clojure-hook-lambda'." 138 | (when lispy--clojure-hook-lambda 139 | (funcall lispy--clojure-hook-lambda) 140 | (setq lispy--clojure-hook-lambda nil)) 141 | (remove-hook 'nrepl-connected-hook 142 | 'lispy--clojure-eval-hook-lambda)) 143 | 144 | (defvar lispy-cider-jack-in-dependencies nil) 145 | 146 | (defvar cider-jack-in-cljs-dependencies) 147 | (defvar cider-jack-in-dependencies) 148 | 149 | (declare-function cider-connections "ext:cider-connection") 150 | (defvar cider-allow-jack-in-without-project) 151 | 152 | (defvar lispy-clojure-projects-alist nil 153 | "Use `cider-connect' instead of `cider-jack-in' for some projects. 154 | Each entry is (DIRECTORY :host HOSTNAME :port PORT). 155 | Example: '((\"~/git/luminous-1\" :host \"localhost\" :port 7000))") 156 | 157 | (defun lispy--clojure-middleware-load-hook () 158 | "Don't load the middleware too early for a ClojureScript REPL. 159 | It will cause an error, since before the init finishes it's a Clojure REPL." 160 | (unless (eq (lispy--clojure-process-type) 'cljs) 161 | (lispy--clojure-middleware-load))) 162 | 163 | (defun lispy--eval-clojure-cider (e-str) 164 | "Eval STR as Clojure code and return a string. 165 | Add the standard output to the result." 166 | (require 'cider) 167 | (let ((f-str (lispy--eval-clojure-context e-str)) 168 | deactivate-mark) 169 | (cond ((null (lispy--clojure-process-buffer)) 170 | (unless (eq major-mode 'clojurescript-mode) 171 | (setq lispy--clojure-hook-lambda 172 | `(lambda () 173 | (set-window-configuration 174 | ,(current-window-configuration)) 175 | (lispy--clojure-middleware-load) 176 | (lispy-message 177 | (lispy--eval-clojure-1 ,f-str ,e-str)))) 178 | (add-hook 'nrepl-connected-hook 179 | 'lispy--clojure-eval-hook-lambda t)) 180 | (let ((project-cfg (assoc (clojure-project-dir (cider-current-dir)) 181 | lispy-clojure-projects-alist))) 182 | (cond (project-cfg 183 | (cider-connect (cons :project-dir project-cfg)) 184 | "Using cider-connect") 185 | ((eq major-mode 'clojurescript-mode) 186 | (let ((cider-jack-in-cljs-dependencies nil)) 187 | (call-interactively #'cider-jack-in-cljs)) 188 | "Starting CIDER using cider-jack-in-cljs ...") 189 | (t 190 | (let ((cider-allow-jack-in-without-project t) 191 | (cider-jack-in-dependencies 192 | (delete-dups 193 | (append 194 | cider-jack-in-dependencies 195 | (and (eq major-mode 'clojure-mode) 196 | lispy-cider-jack-in-dependencies))))) 197 | (call-interactively lispy-cider-connect-method)) 198 | (format "Starting CIDER using %s ..." lispy-cider-connect-method))))) 199 | ((eq current-prefix-arg 7) 200 | (kill-new f-str)) 201 | ((and (eq current-prefix-arg 0) 202 | (lispy--eval-clojure-cider 203 | "(lispy.clojure/shadow-unmap *ns*)") 204 | nil)) 205 | (t 206 | (lispy--clojure-middleware-load) 207 | (lispy--eval-clojure-1 f-str e-str))))) 208 | 209 | ;;* Base eval 210 | (defun lispy--eval-clojure-1 (f-str e-str) 211 | (or 212 | (and (stringp e-str) 213 | (lispy--eval-clojure-handle-ns e-str)) 214 | (let* ((res (lispy--eval-nrepl-clojure f-str lispy--clojure-ns)) 215 | (status (nrepl-dict-get res "status")) 216 | (res (cond ((or (member "namespace-not-found" status)) 217 | (lispy--eval-nrepl-clojure f-str)) 218 | ((member "eval-error" status) 219 | (signal 'eval-error (lispy--clojure-pretty-string 220 | (nrepl-dict-get res "err")))) 221 | (t 222 | res))) 223 | (val 224 | (nrepl-dict-get res "value")) 225 | (out (nrepl-dict-get res "out"))) 226 | (when out 227 | (setq lispy-eval-output 228 | (concat (propertize out 'face 'font-lock-string-face) "\n"))) 229 | (if (string-match "\\`(lispy.clojure/\\(pp\\|reval\\)" f-str) 230 | (condition-case nil 231 | (string-trim (read val)) 232 | (error val)) 233 | (if (stringp val) 234 | (string-trim val)))))) 235 | 236 | (defun lispy--eval-clojure-handle-ns (str) 237 | (when (or (string-match "\\`(ns \\([a-z-_0-9\\.]+\\)" str) 238 | (string-match "\\`(in-ns '\\([a-z-_0-9\\.]+\\)" str)) 239 | (setq lispy--clojure-ns (match-string 1 str)) 240 | (let* ((res (lispy--eval-nrepl-clojure str "user")) 241 | (status (nrepl-dict-get res "status"))) 242 | (when (member "eval-error" status) 243 | (error (nrepl-dict-get res "err")))) 244 | lispy--clojure-ns)) 245 | 246 | ;;* Handle NREPL version incompat 247 | (defun lispy--eval-nrepl-clojure (str &optional namespace) 248 | (nrepl-sync-request:eval 249 | str 250 | (or (cider-current-connection) 251 | (lispy--clojure-process-buffer)) 252 | namespace)) 253 | 254 | (defvar spiral-conn-id) 255 | (defvar spiral-aux-sync-request-timeout) 256 | (declare-function spiral-projects-as-list "ext:spiral-project") 257 | (declare-function spiral-pending-eval-add "ext:spiral-project") 258 | (declare-function spiral-ast-unparse-to-string "ext:spiral-ast") 259 | (declare-function spiral-loop--send "ext:spiral-loop") 260 | 261 | (defun lispy--eval-clojure-spiral (str) 262 | (let* ((start (current-time)) 263 | (repl-buf (cdr (assoc :repl-buffer (car (spiral-projects-as-list))))) 264 | (conn-id (with-current-buffer repl-buf spiral-conn-id)) 265 | (unparse-no-properties 266 | (lambda (node) (substring-no-properties 267 | (spiral-ast-unparse-to-string node)))) 268 | stdout 269 | result) 270 | (spiral-loop--send conn-id :aux str) 271 | (spiral-pending-eval-add 272 | :aux conn-id 273 | :status :sent 274 | :eval-callback (lambda (eval-payload) 275 | (setq result (funcall unparse-no-properties eval-payload))) 276 | :stdout-callback (lambda (stdout-payload &rest _) 277 | (setq stdout 278 | (concat stdout 279 | (funcall unparse-no-properties stdout-payload))))) 280 | (while (and (not result) 281 | (not (input-pending-p)) ;; do not hang UI 282 | (or (not spiral-aux-sync-request-timeout) 283 | (< (cadr (time-subtract (current-time) start)) 284 | spiral-aux-sync-request-timeout))) 285 | (accept-process-output nil 0.01)) 286 | (if stdout 287 | (concat stdout "\n" result) 288 | result))) 289 | 290 | ;;* Rest 291 | (defun lispy--clojure-debug-quit () 292 | (interactive) 293 | (let ((pt (save-excursion 294 | (if (lispy--leftp) 295 | (forward-list) 296 | (lispy--out-forward 1)) 297 | (lispy-up 1) 298 | (lispy-different) 299 | (point))) 300 | (str (format "(do %s)" 301 | (mapconcat 302 | (lambda (x) 303 | (format "(lispy.clojure/shadow-def '%s %s)" (car x) (cadr x))) 304 | (nrepl-dict-get cider--debug-mode-response "locals") 305 | "\n")))) 306 | (catch 'exit 307 | (cider-debug-mode-send-reply ":quit")) 308 | (lispy--eval-clojure-1 str nil) 309 | (goto-char pt))) 310 | 311 | (when (boundp 'cider--debug-mode-map) 312 | (define-key cider--debug-mode-map "Z" 'lispy--clojure-debug-quit)) 313 | 314 | (defun lispy--clojure-resolve (symbol) 315 | "Return resolved SYMBOL. 316 | Return 'special or 'keyword appropriately. 317 | Otherwise try to resolve in current namespace first. 318 | If it doesn't work, try to resolve in all available namespaces." 319 | (let ((str (lispy--eval-clojure-cider 320 | (format "(lispy.clojure/resolve-sym '%s)" symbol)))) 321 | (cond 322 | ((string-match "^#'\\(.*\\)$" str) 323 | (match-string 1 str)) 324 | (t 325 | (read str))))) 326 | 327 | (defun lispy--clojure-symbol-to-args (symbol) 328 | (cond 329 | ((eq major-mode 'clojurescript-mode) 330 | (let (info) 331 | (and (cider-nrepl-op-supported-p "info") 332 | (setq info (cider-sync-request:info symbol)) 333 | (let ((args (nrepl-dict-get info "arglists-str"))) 334 | (if args 335 | (split-string args "\n") 336 | (nrepl-dict-get info "forms-str")))))) 337 | ((string= symbol ".") 338 | (lispy--clojure-dot-args)) 339 | ((string-match "\\`\\(.*\\)\\.\\'" symbol) 340 | (lispy--clojure-constructor-args (match-string 1 symbol))) 341 | (t 342 | (let ((sym (lispy--clojure-resolve symbol))) 343 | (cond 344 | ((eq sym 'special) 345 | (read 346 | (lispy--eval-clojure-cider 347 | (format "(lispy.clojure/arglist '%s)" symbol)))) 348 | ((eq sym 'keyword) 349 | (list "[map]")) 350 | ((eq sym 'undefined) 351 | (error "Undefined")) 352 | ((and (listp sym) (eq (car sym) 'variable)) 353 | (list "variable")) 354 | (t 355 | (read 356 | (lispy--eval-clojure-cider 357 | (format "(lispy.clojure/arglist '%s)" symbol))))))))) 358 | 359 | (defun lispy--clojure-args (symbol) 360 | "Return a pretty string with arguments for SYMBOL. 361 | Besides functions, handles specials, keywords, maps, vectors and sets." 362 | (let ((args (lispy--clojure-symbol-to-args symbol))) 363 | (if (listp args) 364 | (format 365 | "(%s %s)" 366 | (propertize symbol 'face 'lispy-face-hint) 367 | (mapconcat 368 | #'identity 369 | (mapcar (lambda (x) (propertize (downcase x) 370 | 'face 'lispy-face-req-nosel)) 371 | args) 372 | (concat "\n" 373 | (make-string (+ 2 (length symbol)) ?\ )))) 374 | (propertize args 'face 'lispy-face-hint)))) 375 | 376 | (defun lispy--describe-clojure-java (sym) 377 | "Return description for Clojure Java symol SYM." 378 | (read 379 | (lispy--eval-clojure-cider 380 | (format 381 | "(let [[_ cname mname] (re-find #\"(.*)/(.*)\" \"%s\") 382 | methods (and cname 383 | (try (load-string (format \"(.getMethods %%s)\" cname)) 384 | (catch Exception e))) 385 | methods (filter #(= (.getName %%) mname) methods)] 386 | (if (= 0 (count methods)) 387 | nil 388 | (clojure.string/join 389 | \"\\n\" (map (fn [m] (.toString m)) 390 | methods))))" 391 | sym)))) 392 | 393 | (defun lispy--clojure-macrop (symbol) 394 | "Test if SYMBOL is a macro." 395 | (equal (lispy--eval-clojure-cider 396 | (format "(:macro (meta #'%s))" symbol)) 397 | "true")) 398 | 399 | (defun lispy--clojure-middleware-unload () 400 | "Mark the Clojure middleware in \"lispy-clojure.clj\" as not loaded." 401 | (puthash (lispy--clojure-process-buffer) nil lispy--clojure-middleware-loaded-hash)) 402 | 403 | (defun lispy-cider-load-file (filename) 404 | (let ((ns-form (cider-ns-form))) 405 | (cider-map-repls :auto 406 | (lambda (connection) 407 | (when ns-form 408 | (cider-repl--cache-ns-form ns-form connection)) 409 | (cider-request:load-file (cider--file-string filename) 410 | (funcall cider-to-nrepl-filename-function 411 | (cider--server-filename filename)) 412 | (file-name-nondirectory filename) 413 | connection))))) 414 | 415 | (defcustom lispy-clojure-middleware-tests nil 416 | "When non-nil, run the tests from lispy-clojure.clj when loading it." 417 | :type 'boolean 418 | :group 'lispy) 419 | 420 | (defun lispy--clojure-process-type (&optional conn) 421 | (let ((conn (or conn (lispy--clojure-process-buffer)))) 422 | (if (string-match "(.*cljs" (buffer-name conn)) 423 | 'cljs 424 | 'clj))) 425 | 426 | (defun lispy--clojure-middleware-load () 427 | "Load the custom Clojure code in \"lispy-clojure.clj\"." 428 | (let* ((access-time (lispy--clojure-middleware-loaded-p)) 429 | (conn (lispy--clojure-process-buffer)) 430 | (conn-type (lispy--clojure-process-type conn)) 431 | (middleware-fname 432 | (expand-file-name 433 | (if (eq conn-type 'cljs) "lispy-clojure.cljs" "lispy-clojure.clj") 434 | lispy-site-directory)) 435 | (middleware-access-time (file-attribute-access-time 436 | (file-attributes middleware-fname)))) 437 | (when (or (null access-time) (time-less-p access-time middleware-access-time)) 438 | (setq lispy--clojure-ns "user") 439 | (unless (lispy--clojure-babashka-p) 440 | (save-window-excursion 441 | (lispy-cider-load-file 442 | (expand-file-name middleware-fname lispy-site-directory)))) 443 | (puthash conn middleware-access-time lispy--clojure-middleware-loaded-hash) 444 | (add-hook 'nrepl-disconnected-hook #'lispy--clojure-middleware-unload) 445 | (when (equal conn-type 'clj) 446 | (let ((test-fname (expand-file-name "lispy-clojure-test.clj" 447 | lispy-site-directory))) 448 | (when (and lispy-clojure-middleware-tests 449 | (file-exists-p test-fname)) 450 | (lispy-message 451 | (lispy--eval-clojure-cider (format "(load-file \"%s\")" test-fname))))))))) 452 | 453 | (defun lispy-flatten--clojure (_arg) 454 | "Inline a Clojure function at the point of its call." 455 | (let* ((begp (if (looking-at lispy-left) 456 | t 457 | (if (lispy-right-p) 458 | (progn (backward-list) 459 | nil) 460 | (lispy-left 1)))) 461 | (bnd (lispy--bounds-list)) 462 | (str (lispy--string-dwim bnd)) 463 | (expr (lispy--read str)) 464 | (result 465 | (if (and (symbolp (car expr)) 466 | (lispy--clojure-macrop (symbol-name (car expr)))) 467 | (lispy--eval-clojure-cider 468 | (format "(macroexpand '%s)" str)) 469 | (lispy--eval-clojure-cider 470 | (format "(lispy.clojure/flatten-expr '%s)" str))))) 471 | (goto-char (car bnd)) 472 | (delete-region (car bnd) (cdr bnd)) 473 | (insert result) 474 | (when begp 475 | (goto-char (car bnd)))) 476 | (lispy-alt-multiline)) 477 | 478 | (defun lispy--clojure-debug-step-in () 479 | "Inline a Clojure function at the point of its call." 480 | (lispy--clojure-detect-ns) 481 | (let* ((e-str (format "(lispy.clojure/debug-step-in\n'%s)" 482 | (lispy--string-dwim))) 483 | (str (substring-no-properties 484 | (lispy--eval-clojure-1 e-str nil))) 485 | (old-session (sesman-current-session 'CIDER))) 486 | (lispy-follow) 487 | (when (string-match "(clojure.core/in-ns (quote \\([^)]+\\))" str) 488 | (setq lispy--clojure-ns (match-string 1 str))) 489 | (when (equal (file-name-nondirectory (buffer-file-name)) "lispy-clojure.clj") 490 | (sesman-link-session 'CIDER old-session)) 491 | (lispy--eval-clojure-cider str) 492 | (lispy-flow 1))) 493 | 494 | (defun lispy-goto-line (line) 495 | (goto-char (point-min)) 496 | (forward-line (1- line))) 497 | 498 | (declare-function archive-zip-extract "arc-mode") 499 | 500 | (defun lispy-find-archive (archive path) 501 | (require 'arc-mode) 502 | (let ((name (format "%s:%s" archive path))) 503 | (switch-to-buffer 504 | (or (find-buffer-visiting name) 505 | (with-current-buffer (generate-new-buffer name) 506 | (archive-zip-extract archive path) 507 | (set-visited-file-name name) 508 | (setq-local default-directory (file-name-directory archive)) 509 | (setq-local buffer-read-only t) 510 | (set-buffer-modified-p nil) 511 | (set-auto-mode) 512 | (current-buffer)))))) 513 | 514 | (defun lispy-goto-symbol-clojure (symbol) 515 | "Goto SYMBOL." 516 | (lispy--clojure-detect-ns) 517 | (let* ((r (read (lispy--eval-clojure-cider 518 | (format "(lispy.clojure/location '%s)" symbol)))) 519 | (url (car r)) 520 | (line (cadr r)) 521 | archive) 522 | (cond 523 | ((file-exists-p url) 524 | (find-file url) 525 | (lispy-goto-line line)) 526 | ((and (string-match "\\`file:\\([^!]+\\)!/\\(.*\\)\\'" url) 527 | (file-exists-p (setq archive (match-string 1 url)))) 528 | (let ((path (match-string 2 url))) 529 | (lispy-find-archive archive path) 530 | (lispy-goto-line line))) 531 | (t 532 | (warn "unexpected: %S" symbol) 533 | (cider-find-var symbol))))) 534 | 535 | (defun lispy-goto-symbol-clojurescript (symbol) 536 | "Goto SYMBOL." 537 | (cider-find-var nil symbol)) 538 | 539 | (defun lispy--clojure-dot-object (&optional bnd) 540 | (let* ((bnd (or bnd 541 | (bounds-of-thing-at-point 'symbol) 542 | (cons (point) (point)))) 543 | (nested-p (eq (char-before (car bnd)) ?\())) 544 | (when (save-excursion (lispy--out-backward (if nested-p 2 1) t) (looking-at "(\\.+")) 545 | (string-trim 546 | (let ((str 547 | (concat 548 | (buffer-substring-no-properties (match-beginning 0) (1- (car bnd))) 549 | ")"))) 550 | (if (<= (save-excursion 551 | (when nested-p 552 | (lispy--out-backward 1 t)) 553 | (lispy-dotimes 100 (backward-sexp 1))) 554 | (if (or nested-p (= (car bnd) (cdr bnd))) 2 3)) 555 | (string-trim str "[(.]+" ")") 556 | str)))))) 557 | 558 | (defun lispy-clojure-complete-at-point () 559 | (cond ((lispy-complete-fname-at-point)) 560 | ((and (memq major-mode lispy-clojure-modes) 561 | (lispy--clojure-middleware-loaded-p)) 562 | (ignore-errors 563 | (lispy--clojure-detect-ns) 564 | (let* ((bnd (or (bounds-of-thing-at-point 'symbol) 565 | (cons (point) (point)))) 566 | (obj (lispy--clojure-dot-object bnd)) 567 | res) 568 | (cond ((and obj 569 | (setq res (lispy--eval-clojure-cider-noerror 570 | (format "(lispy.clojure/object-members %s)" obj)))) 571 | (let ((cands (read res))) 572 | (when (> (cdr bnd) (car bnd)) 573 | (setq cands (all-completions (lispy--string-dwim bnd) cands))) 574 | (list (car bnd) (cdr bnd) cands))) 575 | ((eq (lispy--clojure-process-type) 'cljs) 576 | nil))))))) 577 | 578 | (defun lispy--eval-clojure-cider-noerror (e-str) 579 | (condition-case nil 580 | (lispy--eval-clojure-cider e-str) 581 | (eval-error nil))) 582 | 583 | (defun lispy--clojure-dot-args () 584 | (save-excursion 585 | (lispy--back-to-paren) 586 | (let* ((object (save-mark-and-excursion 587 | (lispy-mark-list 2) 588 | (lispy--string-dwim))) 589 | (method (save-mark-and-excursion 590 | (lispy-mark-list 3) 591 | (lispy--string-dwim))) 592 | (sig (read 593 | (lispy--eval-clojure-cider 594 | (format "(lispy.clojure/method-signature (lispy.clojure/reval \"%s\" nil) \"%s\")" object method))))) 595 | (when (> (length sig) 0) 596 | (if (string-match "\\`public \\(.*\\)(\\(.*\\))\\'" sig) 597 | (let ((name (match-string 1 sig)) 598 | (args (match-string 2 sig))) 599 | (format "%s\n(. %s %s%s)" 600 | name object method 601 | (if (> (length args) 0) 602 | (concat " " args) 603 | ""))) 604 | sig))))) 605 | 606 | (defun lispy--clojure-constructor-args (symbol) 607 | (read (lispy--eval-clojure-cider 608 | (format "(lispy.clojure/ctor-args %s)" symbol)))) 609 | 610 | (defun lispy--clojure-pretty-string (str) 611 | "Return STR fontified in `clojure-mode'." 612 | (cond ((string-match "\\`\"error: \\([^\0]+\\)\"\\'" str) 613 | (concat (propertize "error: " 'face 'error) 614 | (match-string 1 str))) 615 | ((> (length str) 4000) 616 | str) 617 | (t 618 | (condition-case nil 619 | (with-temp-buffer 620 | (clojure-mode) 621 | (insert str) 622 | (lispy-font-lock-ensure) 623 | (buffer-string)) 624 | (error str))))) 625 | 626 | (defun lispy-clojure-apropos-action (s) 627 | (cider-doc-lookup 628 | (substring 629 | (car (split-string s "\\\\n")) 630 | 2))) 631 | 632 | (defun lispy-clojure-apropos () 633 | (interactive) 634 | (let ((cands 635 | (split-string (lispy--eval-clojure-cider 636 | "(lispy.clojure/all-docs 'clojure.core)") 637 | "::"))) 638 | (ivy-read "var: " cands 639 | :action #'lispy-clojure-apropos-action))) 640 | 641 | (provide 'le-clojure) 642 | 643 | ;;; le-clojure.el ends here 644 | -------------------------------------------------------------------------------- /le-hy.el: -------------------------------------------------------------------------------- 1 | ;;; le-hy.el --- lispy support for Hy. -*- lexical-binding: t -*- 2 | 3 | ;; Copyright (C) 2016 Oleh Krehel 4 | 5 | ;; This file is not part of GNU Emacs 6 | 7 | ;; This file is free software; you can redistribute it and/or modify 8 | ;; it under the terms of the GNU General Public License as published by 9 | ;; the Free Software Foundation; either version 3, or (at your option) 10 | ;; any later version. 11 | 12 | ;; This program is distributed in the hope that it will be useful, 13 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | ;; GNU General Public License for more details. 16 | 17 | ;; For a full copy of the GNU General Public License 18 | ;; see . 19 | 20 | ;;; Commentary: 21 | ;; 22 | 23 | ;;; Code: 24 | 25 | (require 'hy-mode nil t) 26 | (require 'inf-lisp) 27 | 28 | (defun lispy--hy-proc () 29 | (let ((proc-name "hy")) 30 | (if (process-live-p proc-name) 31 | (get-process proc-name) 32 | (get-buffer-process 33 | (make-comint proc-name "hy"))))) 34 | 35 | (defun lispy--comint-eval (command) 36 | "Collect output of COMMAND without changing point." 37 | (let ((command-output-begin nil) 38 | (str nil) 39 | (last-cmd nil) 40 | (last-cmd-with-prompt nil) 41 | (buffer (process-buffer (lispy--hy-proc)))) 42 | (with-current-buffer buffer 43 | (let ((inhibit-field-text-motion t) 44 | (inhibit-read-only t)) 45 | ;; save the last command and delete the old prompt 46 | (beginning-of-line) 47 | (setq last-cmd-with-prompt 48 | (buffer-substring (point) (line-end-position))) 49 | (setq last-cmd (replace-regexp-in-string 50 | "=> " "" last-cmd-with-prompt)) 51 | (delete-region (point) (line-end-position)) 52 | ;; send the command 53 | (setq command-output-begin (point)) 54 | (comint-simple-send (get-buffer-process (current-buffer)) 55 | command) 56 | ;; collect the output 57 | (while (null (save-excursion 58 | (let ((inhibit-field-text-motion t)) 59 | (goto-char command-output-begin) 60 | (re-search-forward "^[. ]*=> \\s-*$" nil t)))) 61 | (accept-process-output (get-buffer-process buffer)) 62 | (goto-char (point-max))) 63 | (goto-char (point-max)) 64 | (when (looking-back "^[. ]*=> *" (line-beginning-position)) 65 | (goto-char (1- (match-beginning 0)))) 66 | ;; save output to string 67 | (setq str (buffer-substring-no-properties command-output-begin (point))) 68 | ;; delete the output from the command line 69 | (delete-region command-output-begin (point-max)) 70 | ;; restore prompt and insert last command 71 | (goto-char (point-max)) 72 | (comint-send-string (get-buffer-process (current-buffer)) "\n") 73 | (insert last-cmd) 74 | ;; return the shell output 75 | str)))) 76 | 77 | (defun lispy--eval-hy (str) 78 | "Eval STR as Hy code." 79 | (let ((res (lispy--comint-eval str))) 80 | (if (member res '("" "\n")) 81 | "(ok)" 82 | res))) 83 | 84 | (provide 'le-hy) 85 | 86 | ;;; le-hy.el ends here 87 | -------------------------------------------------------------------------------- /le-js.el: -------------------------------------------------------------------------------- 1 | (require 'indium) 2 | 3 | (defun lispy--eval-js (str) 4 | (let ((r nil)) 5 | (indium-eval 6 | str (lambda (value) (setq r (indium-render-remote-object-to-string value)))) 7 | (while (not r) 8 | (accept-process-output)) 9 | (substring-no-properties r))) 10 | 11 | (defun lispy--eval-js-str () 12 | (if (region-active-p) 13 | (lispy--string-dwim) 14 | (lispy--string-dwim 15 | (lispy-bounds-python-block)))) 16 | 17 | (defun lispy--js-completion-at-point () 18 | (let* ((prefix (buffer-substring-no-properties 19 | (let ((bol (point-at-bol)) 20 | (prev-delimiter (1+ (save-excursion 21 | (re-search-backward "[([:space:]]" nil t))))) 22 | (if prev-delimiter 23 | (max bol prev-delimiter) 24 | bol)) 25 | (point))) 26 | (expression (if (string-match-p "\\." prefix) 27 | (replace-regexp-in-string "\\.[^\\.]*$" "" prefix) 28 | "this")) 29 | (cands nil)) 30 | (indium-client-get-completion 31 | expression 32 | indium-debugger-current-frame 33 | (lambda (candidates) 34 | (setq cands candidates))) 35 | (while (null cands) 36 | (accept-process-output)) 37 | (list (- (point) (length (company-grab-symbol))) 38 | (point) 39 | (mapcar #'identity cands)))) 40 | 41 | (provide 'le-js) 42 | -------------------------------------------------------------------------------- /le-julia.el: -------------------------------------------------------------------------------- 1 | ;;; le-julia.el --- lispy support for Julia. -*- lexical-binding: t -*- 2 | 3 | ;; Copyright (C) 2016-2019 Oleh Krehel 4 | 5 | ;; This file is not part of GNU Emacs 6 | 7 | ;; This file is free software; you can redistribute it and/or modify 8 | ;; it under the terms of the GNU General Public License as published by 9 | ;; the Free Software Foundation; either version 3, or (at your option) 10 | ;; any later version. 11 | 12 | ;; This program is distributed in the hope that it will be useful, 13 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | ;; GNU General Public License for more details. 16 | 17 | ;; For a full copy of the GNU General Public License 18 | ;; see . 19 | 20 | ;;; Commentary: 21 | ;; 22 | 23 | ;;; Code: 24 | 25 | (require 'julia-mode nil t) 26 | (require 'julia-shell nil t) 27 | 28 | (declare-function julia-shell-collect-command-output "ext:julia-shell") 29 | 30 | (defun lispy--eval-julia (str) 31 | "Eval STR as Julia code." 32 | (string-trim-right (julia-shell-collect-command-output str))) 33 | 34 | ;; TODO: simplify 35 | (defun lispy-eval-julia-str (&optional bnd) 36 | (save-excursion 37 | (cond ((region-active-p) 38 | ;; get rid of "unexpected indent" 39 | (replace-regexp-in-string 40 | (concat 41 | "^" 42 | (save-excursion 43 | (goto-char (region-beginning)) 44 | (buffer-substring-no-properties 45 | (line-beginning-position) 46 | (point)))) 47 | "" (lispy--string-dwim))) 48 | ((looking-at lispy-outline) 49 | (string-trim-right 50 | (lispy--string-dwim 51 | (lispy--bounds-dwim)))) 52 | ((lispy-bolp) 53 | (lispy--string-dwim 54 | (lispy--bounds-c-toplevel))) 55 | (t 56 | (cond ((lispy-left-p)) 57 | ((lispy-right-p) 58 | (backward-list)) 59 | (t 60 | (error "Unexpected"))) 61 | (setq bnd (lispy--bounds-dwim)) 62 | (ignore-errors (backward-sexp)) 63 | (while (eq (char-before) ?.) 64 | (backward-sexp)) 65 | (setcar bnd (point)) 66 | (lispy--string-dwim bnd))))) 67 | 68 | (defun lispy-eval-julia () 69 | (lispy--eval-julia (lispy-eval-julia-str))) 70 | 71 | (provide 'le-julia) 72 | 73 | ;;; le-julia.el ends here 74 | -------------------------------------------------------------------------------- /le-lisp.el: -------------------------------------------------------------------------------- 1 | ;;; le-lisp.el --- lispy support for Common Lisp. -*- lexical-binding: t -*- 2 | 3 | ;; Copyright (C) 2014-2015 Oleh Krehel 4 | 5 | ;; This file is not part of GNU Emacs 6 | 7 | ;; This file is free software; you can redistribute it and/or modify 8 | ;; it under the terms of the GNU General Public License as published by 9 | ;; the Free Software Foundation; either version 3, or (at your option) 10 | ;; any later version. 11 | 12 | ;; This program is distributed in the hope that it will be useful, 13 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | ;; GNU General Public License for more details. 16 | 17 | ;; For a full copy of the GNU General Public License 18 | ;; see . 19 | 20 | ;;; Commentary: 21 | ;; 22 | 23 | ;;; Code: 24 | (declare-function slime-output-buffer "ext:slime-repl") 25 | (declare-function slime "ext:slime") 26 | (declare-function slime-current-connection "ext:slime") 27 | (declare-function slime-eval "ext:slime") 28 | (declare-function slime-edit-definition "ext:slime") 29 | (declare-function sly-mrepl--find-buffer "ext:sly-mrepl") 30 | (declare-function sly "ext:sly") 31 | (declare-function sly-current-connection "ext:sly") 32 | (declare-function sly-eval "ext:sly") 33 | (declare-function sly-edit-definition "ext:sly") 34 | 35 | (defcustom lispy-use-sly nil 36 | "Whether to use SLY instead of SLIME." 37 | :group 'lispy 38 | :type 'boolean) 39 | 40 | (defun lispy--use-sly-p () 41 | (if lispy-use-sly 42 | (require 'sly) 43 | (unless (require 'slime nil t) 44 | (require 'sly) 45 | (setq lispy-use-sly t)))) 46 | 47 | (defun lispy--eval-lisp (str) 48 | "Eval STR as Common Lisp code." 49 | (let* ((deactivate-mark nil) 50 | (result (if (lispy--use-sly-p) 51 | (with-current-buffer (process-buffer (lispy--cl-process)) 52 | (sly-eval `(slynk:eval-and-grab-output ,str))) 53 | (slime-eval `(swank:eval-and-grab-output ,str))))) 54 | (pcase result 55 | (`("" "") "(ok)") 56 | (`("" ,val) val) 57 | (`(,out ,val) 58 | (concat (propertize (string-trim-left out) 'face 'font-lock-string-face) "\n\n" val))))) 59 | 60 | (defun lispy--cl-process () 61 | (unless (lispy--use-sly-p) 62 | (require 'slime-repl)) 63 | (or (if (lispy--use-sly-p) 64 | (sly-current-connection) 65 | (slime-current-connection)) 66 | (let (conn) 67 | (let ((wnd (current-window-configuration))) 68 | (if (lispy--use-sly-p) 69 | (sly) 70 | (slime)) 71 | (while (not (if (lispy--use-sly-p) 72 | (and (setq conn (sly-current-connection)) 73 | (sly-mrepl--find-buffer conn)) 74 | (and 75 | (setq conn (slime-current-connection)) 76 | (get-buffer-window (slime-output-buffer))))) 77 | (sit-for 0.2)) 78 | (set-window-configuration wnd) 79 | conn)))) 80 | 81 | (defun lispy--lisp-args (symbol) 82 | "Return a pretty string with arguments for SYMBOL." 83 | (let ((args 84 | (list 85 | (mapconcat 86 | #'prin1-to-string 87 | (read (lispy--eval-lisp 88 | (format (if (lispy--use-sly-p) 89 | "(slynk-backend:arglist #'%s)" 90 | "(swank-backend:arglist #'%s)") 91 | symbol))) 92 | " ")))) 93 | (if (listp args) 94 | (format 95 | "(%s %s)" 96 | (propertize symbol 'face 'lispy-face-hint) 97 | (mapconcat 98 | #'identity 99 | (mapcar (lambda (x) (propertize (downcase x) 100 | 'face 'lispy-face-req-nosel)) 101 | args) 102 | (concat "\n" 103 | (make-string (+ 2 (length symbol)) ?\ )))) 104 | (propertize args 'face 'lispy-face-hint)))) 105 | 106 | (defun lispy--lisp-describe (symbol) 107 | "Return documentation for SYMBOL." 108 | (read 109 | (lispy--eval-lisp 110 | (substring-no-properties 111 | (format 112 | "(let ((x '%s)) 113 | (or (if (boundp x) 114 | (documentation x 'variable) 115 | (documentation x 'function)) 116 | \"undocumented\"))" 117 | symbol))))) 118 | 119 | (defun lispy-flatten--lisp () 120 | (let* ((bnd (lispy--bounds-list)) 121 | (str (lispy--string-dwim bnd)) 122 | (expr (read str)) 123 | (fexpr (read (lispy--eval-lisp 124 | (format "(function-lambda-expression #'%S)" (car expr)))))) 125 | (if (not (eq (car-safe fexpr) 'SB-INT:NAMED-LAMBDA)) 126 | (error "Could not find the body of %S" (car expr)) 127 | (setq fexpr (downcase 128 | (prin1-to-string 129 | `(lambda ,(nth 2 fexpr) ,(cl-caddr (nth 3 fexpr)))))) 130 | (goto-char (car bnd)) 131 | (delete-region (car bnd) (cdr bnd)) 132 | (let* ((e-args (cdr expr)) 133 | (body (lispy--flatten-function fexpr e-args))) 134 | (lispy--insert body))))) 135 | 136 | (defun lispy-goto-symbol-lisp (symbol) 137 | ;; start SLY or SLIME if necessary 138 | (lispy--cl-process) 139 | (if (lispy--use-sly-p) 140 | (sly-edit-definition symbol) 141 | (slime-edit-definition symbol))) 142 | 143 | (provide 'le-lisp) 144 | 145 | ;;; le-lisp.el ends here 146 | -------------------------------------------------------------------------------- /le-racket.el: -------------------------------------------------------------------------------- 1 | ;;; le-racket.el --- lispy support for Racket. -*- lexical-binding: t -*- 2 | 3 | ;; Copyright (C) 2014-2019 Oleh Krehel 4 | 5 | ;; This file is not part of GNU Emacs 6 | 7 | ;; This file is free software; you can redistribute it and/or modify 8 | ;; it under the terms of the GNU General Public License as published by 9 | ;; the Free Software Foundation; either version 3, or (at your option) 10 | ;; any later version. 11 | 12 | ;; This program is distributed in the hope that it will be useful, 13 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | ;; GNU General Public License for more details. 16 | 17 | ;; For a full copy of the GNU General Public License 18 | ;; see . 19 | 20 | ;;; Commentary: 21 | ;; 22 | 23 | ;;; Code: 24 | 25 | (require 'racket-mode nil t) 26 | 27 | (declare-function racket--cmd/async "ext:racket-repl") 28 | (declare-function racket--repl-session-id "ext:racket-repl") 29 | 30 | (defun lispy-goto-symbol-racket (symbol) 31 | "Go to the definition of the SYMBOL." 32 | (xref-find-definitions symbol)) 33 | 34 | (defun lispy--eval-racket (str) 35 | "Evaluate STR in the context of the current racket repl session." 36 | (let* ((awaiting 'RACKET-REPL-AWAITING) 37 | (response awaiting)) 38 | (racket--cmd/async (racket--repl-session-id) 39 | `(eval ,str) 40 | (lambda (v) 41 | (setq response v))) 42 | (with-timeout (1 43 | (error "racket-command process timeout")) 44 | (while (eq response awaiting) 45 | (accept-process-output nil 0.001)) 46 | response))) 47 | 48 | (defun lispy-eval-racket () 49 | (lispy--eval-racket (lispy--string-dwim))) 50 | 51 | (provide 'le-racket) 52 | 53 | ;;; le-racket.el ends here 54 | -------------------------------------------------------------------------------- /le-scheme.el: -------------------------------------------------------------------------------- 1 | ;;; le-scheme.el --- lispy support for Scheme. -*- lexical-binding: t -*- 2 | 3 | ;; Copyright (C) 2014-2015 Oleh Krehel 4 | 5 | ;; This file is not part of GNU Emacs 6 | 7 | ;; This file is free software; you can redistribute it and/or modify 8 | ;; it under the terms of the GNU General Public License as published by 9 | ;; the Free Software Foundation; either version 3, or (at your option) 10 | ;; any later version. 11 | 12 | ;; This program is distributed in the hope that it will be useful, 13 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | ;; GNU General Public License for more details. 16 | 17 | ;; For a full copy of the GNU General Public License 18 | ;; see . 19 | 20 | ;;; Commentary: 21 | ;; 22 | 23 | ;;; Code: 24 | 25 | (eval-and-compile 26 | (require 'geiser-eval nil t)) 27 | 28 | (require 'lispy-inline) 29 | 30 | (defvar geiser-impl--implementation) 31 | (declare-function geiser-repl--connection* "geiser-repl") 32 | (declare-function run-geiser "geiser-repl") 33 | (declare-function geiser-repl--buffer-name "geiser-repl") 34 | (declare-function geiser-eval--send/wait "geiser-eval") 35 | (declare-function geiser-eval--retort-error "geiser-eval") 36 | (declare-function geiser-mode "geiser-mode") 37 | (declare-function geiser-edit-symbol "geiser-edit") 38 | (declare-function geiser-racket--language "geiser-racket") 39 | 40 | (defun lispy--eval-scheme (str) 41 | "Eval STR as Scheme code." 42 | (unless (geiser-repl--connection*) 43 | (save-window-excursion 44 | (if geiser-impl--implementation 45 | (run-geiser geiser-impl--implementation) 46 | (call-interactively 'run-geiser)) 47 | (geiser-mode 1))) 48 | (when (and (fboundp 'geiser-racket--language) 49 | (not (member (geiser-racket--language) '(plait))) 50 | (string-match "(\\(?:define\\|set!\\|struct\\)[ (]+\\(\\(?:\\w\\|\\s_\\)+\\)" str)) 51 | (let ((name (match-string 1 str))) 52 | (setq str (format "(begin %s %s)" str name)))) 53 | (with-current-buffer (geiser-repl--buffer-name geiser-impl--implementation) 54 | (let* ((code `(:eval (:scm ,str))) 55 | (ret (geiser-eval--send/wait code)) 56 | (err (geiser-eval--retort-error ret)) 57 | (output-str (cdr (assoc 'output ret))) 58 | (result-str (cadr (assoc 'result ret)))) 59 | (cond (err 60 | (format "Error: %s" (string-trim output-str))) 61 | ((not (equal "" output-str)) 62 | (concat 63 | (propertize 64 | output-str 65 | 'face 'font-lock-string-face) 66 | "\n" 67 | result-str)) 68 | (t 69 | result-str))))) 70 | 71 | (defun lispy-goto-symbol-scheme (symbol) 72 | (geiser-edit-symbol (make-symbol symbol))) 73 | 74 | (provide 'le-scheme) 75 | 76 | ;;; le-scheme.el ends here 77 | -------------------------------------------------------------------------------- /lispy-clojure-test.clj: -------------------------------------------------------------------------------- 1 | ;;; lispy-clojure-test.clj --- lispy support for Clojure. 2 | 3 | ;; Copyright (C) 2018 Oleh Krehel 4 | 5 | ;; This file is not part of GNU Emacs 6 | 7 | ;; This file is free software; you can redistribute it and/or modify 8 | ;; it under the terms of the GNU General Public License as published by 9 | ;; the Free Software Foundation; either version 3, or (at your option) 10 | ;; any later version. 11 | 12 | ;; This program is distributed in the hope that it will be useful, 13 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | ;; GNU General Public License for more details. 16 | 17 | ;; For a full copy of the GNU General Public License 18 | ;; see . 19 | 20 | (ns lispy-clojure-test 21 | (:require 22 | [clojure.test :refer [is deftest]] 23 | [lispy.clojure :refer [add-location-to-defn 24 | add-location-to-def 25 | debug-step-in 26 | dest 27 | get-func-args 28 | get-func-args-def 29 | guess-intent 30 | object-members 31 | position 32 | reader= 33 | reval 34 | symbol-function]])) 35 | 36 | (deftest get-func-args-test 37 | (is (= (get-func-args (symbol-function 'string?) 1) '[x])) 38 | (is (= (get-func-args (symbol-function 'to-array) 1) '[coll]))) 39 | 40 | (deftest get-func-args-def-test 41 | (is (= (get-func-args-def (symbol-function 'defn) 2) 42 | '[name & fdecl]))) 43 | 44 | (deftest object-members-test 45 | (is (= ((into #{} (object-members Thread)) "run") "run"))) 46 | 47 | (deftest dest-test 48 | (is (= (eval (dest '[[x y] (list 1 2 3)])) 49 | {:x 1, :y 2})) 50 | (is (= (eval (dest '[[x & y] [1 2 3]])) 51 | {:x 1, :y '(2 3)})) 52 | (is (= (eval (dest '[[x y] (list 1 2 3) [a b] [y x]])) 53 | {:x 1, :y 2, :a 2, :b 1})) 54 | (is (= (eval (dest '[[x y z] [1 2]])) 55 | {:x 1, :y 2, :z nil})) 56 | (is (= (eval (dest '[[x & tail :as all] [1 2 3]])) 57 | {:x 1, 58 | :tail '(2 3), 59 | :all [1 2 3]})) 60 | (is (= (eval (dest '[[x & tail :as all] "Clojure"])) 61 | {:x \C, 62 | :tail '(\l \o \j \u \r \e), 63 | :all "Clojure"})) 64 | (is (= (eval (dest '[{x 1 y 2} {1 "one" 2 "two" 3 "three"}])) 65 | {:x "one", :y "two"})) 66 | (is (= (eval (dest '[{x 1 y 2 :or {x "one" y "two"} :as all} {2 "three"}])) 67 | {:all {2 "three"}, 68 | :x "one", 69 | :y "three"})) 70 | (is (= (eval (dest '[{:keys [x y]} {:x "one" :z "two"}])) 71 | {:x "one", :y nil})) 72 | (is (= (eval (dest '[{:strs [x y]} {"x" "one" "z" "two"}])) 73 | {:x "one", :y nil})) 74 | (is (= (eval (dest '[{:syms [x y]} {'x "one" 'z "two"}])) 75 | {:x "one", :y nil}))) 76 | 77 | (deftest debug-step-in-test 78 | (is (= (debug-step-in 79 | '(lispy.clojure/expand-home (str "/foo" "/bar"))) 80 | {:path "/foo/bar"})) 81 | (is 82 | (= 83 | ((juxt :file :line) 84 | (debug-step-in 85 | '(lispy.clojure/add-location-to-def '(def x 1) "/foo/bar.clj" 42))) 86 | ["/foo/bar.clj" 42]))) 87 | 88 | (deftest reader=-test 89 | (is (reader= '(map #(* % %) '(1 2 3)) 90 | '(map #(* % %) '(1 2 3)))) 91 | (is (reader= #"regex" #"regex")) 92 | (is (not (= #"regex" #"regex")))) 93 | 94 | (deftest position-test 95 | (let [x (read-string "(map #(* % %) as)") 96 | c (read-string "[as (range 10) bs (map #(* % %) as)]")] 97 | (is (= (position x c =) nil)) 98 | (is (= (position x c reader=) 3)))) 99 | 100 | (deftest add-location-to-defn-test 101 | (is (= (add-location-to-defn 102 | '(defn fun 103 | "doc" 104 | ([x1 x2]) 105 | ([x1])) 106 | "/foo/bar.clj" 42) 107 | '(defn fun 108 | "doc" 109 | {:l-file "/foo/bar.clj", 110 | :l-line 42} 111 | ([x1 x2]) 112 | ([x1])))) 113 | (is (= (add-location-to-defn 114 | '(defn fun 115 | "doc" 116 | [x] 117 | x) 118 | "/foo/bar.clj" 42) 119 | '(defn fun 120 | "doc" 121 | {:l-file "/foo/bar.clj", 122 | :l-line 42} 123 | [x] 124 | x))) 125 | (is (= (add-location-to-defn 126 | '(defn fun [x] 127 | x) 128 | "/foo/bar.clj" 42) 129 | '(defn fun 130 | "" 131 | {:l-file "/foo/bar.clj", 132 | :l-line 42} 133 | [x] 134 | x)))) 135 | 136 | (deftest add-location-to-def-test 137 | (let [e (add-location-to-def 138 | '(def asdf 1) "/foo/bar.clj" 42) 139 | e2 (add-location-to-def 140 | '(def asdf "doc" 1) "/foo/bar.clj" 42)] 141 | (is (= e '(def asdf "" 1))) 142 | (is (= e2 '(def asdf "doc" 1))) 143 | (is (= ((juxt :l-file :l-line) (meta (eval e))) 144 | ((juxt :l-file :l-line) (meta (eval e2))) 145 | ["/foo/bar.clj" 42])))) 146 | 147 | (deftest guess-intent-test 148 | (is (= (guess-intent 'x '[x y]) 'x)) 149 | (is (= (guess-intent '*ns* '*ns*) '*ns*)) 150 | (is (= (guess-intent '(+ 1 2) '[(+ 1 2) (+ 3 4) (+ 5 6)]) 151 | '(+ 1 2)))) 152 | 153 | (deftest reval-test 154 | (let [s "(->> 5 155 | (range) 156 | (map (fn [x] (* x x))) 157 | (map (fn [x] (+ x x))))"] 158 | (is (= (reval "(range)" s) 159 | '(0 1 2 3 4))) 160 | (is (= (reval "(map (fn [x] (* x x)))" s) 161 | '(0 1 4 9 16))) 162 | (is (= (reval "(map (fn [x] (+ x x)))" s) 163 | '(0 2 8 18 32)))) 164 | (is (= (reval "x (+ 2 2)" "[x (+ 2 2)]") {:x 4})) 165 | (is (= (reval "(+ 2 2)" "[x (+ 2 2)]") {:x 4})) 166 | (is (= (reval "(+ 2 2)" "[x (+ 1 2) y (+ 2 2)]") {:y 4})) 167 | (is (= (reval "(range 5)" "[[x & y] (range 5)]") 168 | {:x 0, :y '(1 2 3 4)})) 169 | (is (= (reval "[c [(* 2 21) 0]]" "(doseq [c [(* 2 21) 0]]\n ())") {:c 42})) 170 | (is (= (reval "[i 3]" "(dotimes [i 3])") {:i 0})) 171 | (is (= (reval "[a b] (range 5)" "[[a b] (range 5)]") {:a 0, :b 1})) 172 | (let [js "(doto (new java.util.HashMap) (.put \"a\" 1) (.put \"b\" 2))"] 173 | (is (= (reval "(.put \"a\" 1)" js) {"a" 1})) 174 | (is (= (reval "(.put \"b\" 2)" js) {"a" 1, "b" 2}))) 175 | (is (= (reval "(def x1 1)\n(+ x1 x1)" nil) 2))) 176 | 177 | (deftest format-ctor-test 178 | (is (= (lispy.clojure/format-ctor "protected java.awt.Graphics2D()") "java.awt.Graphics2D."))) 179 | 180 | (deftest read-string-all-test 181 | (is (= (lispy.clojure/read-string-all "(foo) (bar) \"baz\"") 182 | '[(foo) (bar) "baz"]))) 183 | 184 | (clojure.test/run-tests 'lispy-clojure-test) 185 | -------------------------------------------------------------------------------- /lispy-clojure.clj: -------------------------------------------------------------------------------- 1 | ;;; lispy-clojure.clj --- lispy support for Clojure. 2 | 3 | ;; Copyright (C) 2015-2018 Oleh Krehel 4 | 5 | ;; This file is not part of GNU Emacs 6 | 7 | ;; This file is free software; you can redistribute it and/or modify 8 | ;; it under the terms of the GNU General Public License as published by 9 | ;; the Free Software Foundation; either version 3, or (at your option) 10 | ;; any later version. 11 | 12 | ;; This program is distributed in the hope that it will be useful, 13 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | ;; GNU General Public License for more details. 16 | 17 | ;; For a full copy of the GNU General Public License 18 | ;; see . 19 | 20 | (ns lispy.clojure 21 | (:require [clojure.repl :as repl] 22 | [clojure.pprint] 23 | [clojure.java.io :as io] 24 | [clojure.string :as str] 25 | [clojure.tools.namespace.file :as nfile] 26 | [clojure.tools.namespace.find :as nf] 27 | [clojure.java.classpath :as cp]) 28 | (:import 29 | (java.io File LineNumberReader InputStreamReader 30 | PushbackReader FileInputStream) 31 | (java.util.jar JarFile) 32 | (clojure.lang RT))) 33 | 34 | (defmacro xcond 35 | "Common Lisp style `cond'. 36 | 37 | It's more structured than `cond', thus exprs that use it are lot more 38 | malleable to refactoring." 39 | [& clauses] 40 | (when clauses 41 | (let [clause (first clauses)] 42 | (if (= (count clause) 1) 43 | `(or ~(first clause) 44 | (xcond 45 | ~@(next clauses))) 46 | `(if ~(first clause) 47 | (do ~@(next clause)) 48 | (xcond 49 | ~@(next clauses))))))) 50 | 51 | (defn file-exists? [f] 52 | (. (io/file f) exists)) 53 | 54 | (defn expand-file-name [name dir] 55 | (. (io/file dir name) getCanonicalPath)) 56 | 57 | (defn expand-home 58 | [path] 59 | (if (.startsWith path "~") 60 | (let [sep (.indexOf path File/separator)] 61 | (str (io/file (System/getProperty "user.home") 62 | (subs path (inc sep))))) 63 | path)) 64 | 65 | (defn source-fn 66 | "Returns a string of the source code for the given symbol, if it can 67 | find it. This requires that the symbol resolve to a Var defined in 68 | a namespace for which the .clj is in the classpath. Returns nil if 69 | it can't find the source. 70 | 71 | Example: (source-fn 'filter)" 72 | [x] 73 | (let [v (resolve x) 74 | m (and v (meta v)) 75 | file (or (:l-file m) (:file m)) 76 | line (or (:l-line m) (:line m))] 77 | (when (and file line (> line 1)) 78 | (let [filepath (expand-home file) 79 | strm (or (.getResourceAsStream (RT/baseLoader) filepath) 80 | (FileInputStream. filepath))] 81 | (with-open [rdr (LineNumberReader. (InputStreamReader. strm))] 82 | (dotimes [_ (dec line)] (.readLine rdr)) 83 | (let [text (StringBuilder.) 84 | pbr (proxy [PushbackReader] [rdr] 85 | (read [] (let [i (proxy-super read)] 86 | (.append text (char i)) 87 | i)))] 88 | (if (= :unknown *read-eval*) 89 | (throw (IllegalStateException. "Unable to read source while *read-eval* is :unknown.")) 90 | (read (PushbackReader. pbr))) 91 | (str text))))))) 92 | 93 | (defn symbol-function 94 | "Return the source code for function SYM." 95 | [sym] 96 | (read-string 97 | (source-fn 98 | sym))) 99 | 100 | (defn macro? [x] 101 | (:macro (meta (resolve x)))) 102 | 103 | (defn arity [args] 104 | (if (some #{'&} args) 105 | 1000 106 | (count args))) 107 | 108 | (defn flatten-expr 109 | "Flatten a function call EXPR by substituting the arguments." 110 | [expr] 111 | (let [func-name (first expr) 112 | args (rest expr) 113 | func-def (symbol-function func-name) 114 | func-doc (when (string? (nth func-def 2)) 115 | (nth func-def 2)) 116 | func-rest (drop (if func-doc 3 2) func-def) 117 | func-rest (if (map? (first func-rest)) 118 | (rest func-rest) 119 | func-rest) 120 | func-bodies (if (vector? (first func-rest)) 121 | (list func-rest) 122 | func-rest) 123 | func-body (first (filter #(>= (arity (first %)) (count args)) 124 | (sort (fn [a b] (< (arity (first a)) 125 | (arity (first b)))) 126 | func-bodies))) 127 | func-args (first func-body) 128 | func-impl (rest func-body)] 129 | (cons 'let 130 | (cons (vec (if (some #{'&} [func-args]) 131 | (vector func-args (vec args)) 132 | (apply concat 133 | (filter (fn [[a b]] 134 | (not (= a b))) 135 | (partition 136 | 2 (interleave func-args args)))))) 137 | func-impl)))) 138 | 139 | (defn quote-maybe 140 | "Quote X that isn't self-quoting, like symbol or list." 141 | [x] 142 | (if (fn? x) 143 | x 144 | (if (or (symbol? x) 145 | (list? x)) 146 | (list 'quote x) 147 | x))) 148 | 149 | (defn dest 150 | "Transform `let'-style BINDINGS into a sequence of `def's." 151 | [bindings] 152 | (let [bs (partition 2 (destructure bindings)) 153 | as (filterv 154 | #(not (re-matches #"^(vec|map|seq|first)__.*" (name %))) 155 | (map first bs))] 156 | (concat '(do) 157 | (map (fn [[name val]] 158 | `(def ~name ~val)) 159 | bs) 160 | [(zipmap (map keyword as) as)]))) 161 | 162 | (defn get-func-args-defn [func-def n-args] 163 | (let [func-doc (when (string? (nth func-def 2)) 164 | (nth func-def 2)) 165 | func-rest (drop (if func-doc 3 2) func-def) 166 | func-rest (if (map? (first func-rest)) 167 | (rest func-rest) 168 | func-rest) 169 | func-bodies (if (vector? (first func-rest)) 170 | (list func-rest) 171 | func-rest) 172 | func-body (first (filter #(>= (arity (first %)) n-args) 173 | (sort (fn [a b] (< (arity (first a)) 174 | (arity (first b)))) 175 | func-bodies))) 176 | func-args (first func-body)] 177 | func-args)) 178 | 179 | (defn get-func-args-def [func-def n-args] 180 | (let [body (nth func-def 2)] 181 | (assert (= (first body) 'fn)) 182 | (let [args (first (filter vector? body)) 183 | args-count (count (vec (remove '#{& &form &env} args)))] 184 | (assert (or (= args-count n-args) 185 | (and (< args-count n-args) 186 | ((set args) '&)))) 187 | (vec (remove '#{&form &env} args))))) 188 | 189 | (defn get-func-args [func-def n-args] 190 | (xcond ((#{'defn 'defmacro} (first func-def)) 191 | (get-func-args-defn func-def n-args)) 192 | ((= (first func-def) 'def) 193 | (get-func-args-def func-def n-args)))) 194 | 195 | (defn shadow-map [] 196 | (or (ns-resolve *ns* 'shadows) 197 | (intern *ns* 'shadows {}))) 198 | 199 | (defn shadow-unmap [nspc] 200 | ;; (ns-unmap nspc 'shadows) 201 | (intern nspc 'shadows {})) 202 | 203 | (defmacro with-shadows [& forms] 204 | `(let ~(vec (mapcat (fn [[k _]] [(symbol k) `((shadow-map) ~k)]) 205 | (deref (shadow-map)))) 206 | ~@forms)) 207 | 208 | (defn shadow-def 209 | "Give SYM in *ns* shadow value EXPR. 210 | 211 | (with-shadows SYM) can be used to retrieve this value." 212 | [sym expr] 213 | (intern 214 | *ns* 215 | 'shadows 216 | (assoc (deref (shadow-map)) (name sym) expr))) 217 | 218 | (defn shadow-dest 219 | "Transform `let'-style BINDINGS into a sequence of `shadow-def's." 220 | ([bindings] 221 | (shadow-dest bindings *ns*)) 222 | ([bindings nspc] 223 | (let [[_do & forms] (dest bindings) 224 | [defs out] (partition-by map? forms)] 225 | `(let ~(vec (mapcat (fn [[_ n v]] [n v]) defs)) 226 | ~@(when (not= *ns* nspc) 227 | `((in-ns '~(ns-name nspc)))) 228 | ~@(map 229 | (fn [x] 230 | `(shadow-def '~(second x) ~(second x))) 231 | defs) 232 | ~@out)))) 233 | 234 | (defn debug-step-in 235 | "Evaluate the function call arugments and sub them into function arguments." 236 | [expr] 237 | (let [func-name (first expr) 238 | args (vec (rest expr)) 239 | func-def (symbol-function func-name) 240 | func-args (get-func-args func-def (count args)) 241 | func-ns (:ns (meta (resolve func-name))) 242 | eval-form (shadow-dest 243 | [func-args (if (macro? func-name) 244 | (list 'quote args) 245 | args)] 246 | func-ns)] 247 | (eval 248 | `(with-shadows 249 | ~eval-form)))) 250 | 251 | (defn object-methods [sym] 252 | (distinct 253 | (map #(.getName %) 254 | (xcond 255 | ((instance? java.lang.Class sym) 256 | (. sym getMethods)) 257 | 258 | ((instance? java.lang.Object sym) 259 | (. (type sym) getMethods)))))) 260 | 261 | (defn object-fields [sym] 262 | (map #(str "-" (.getName %)) 263 | (.getFields (type sym)))) 264 | 265 | (defmacro object-members [ob] 266 | `(with-shadows 267 | (concat (object-fields ~ob) 268 | (object-methods ~ob)))) 269 | 270 | (defn get-meth [obj method-name] 271 | (first (filter #(= (.getName %) method-name) 272 | (.getMethods (type obj))))) 273 | 274 | (defn method-signature [obj method-name] 275 | (str (get-meth obj method-name))) 276 | 277 | (defn get-ctors [obj] 278 | (. obj getDeclaredConstructors)) 279 | 280 | (defn format-ctor [s] 281 | (let [[_ name args] (re-find #"(?:public|protected) (.*)\((.*)\)" s)] 282 | (str name 283 | "." 284 | (if (= args "") 285 | "" 286 | (str " " (str/replace args #"," " ")))))) 287 | 288 | (defn ctor-args [sym] 289 | (str/join 290 | "\n" 291 | (map #(str "(" % ")") 292 | (map format-ctor 293 | (map str (get-ctors sym)))))) 294 | 295 | (defn resolve-sym [sym] 296 | (xcond 297 | [(symbol? sym) 298 | (if (special-symbol? sym) 299 | 'special 300 | (or 301 | (resolve sym) 302 | (first (keep #(ns-resolve % sym) (all-ns))) 303 | (when-let [val (try (load-string (str sym)) (catch Exception _e))] 304 | (list 'variable (str val)))))] 305 | 306 | [(keyword? sym) 'keyword] 307 | 308 | [:else 'unknown])) 309 | 310 | (defn class-name [cls] 311 | (str/replace (str cls) #"class " "")) 312 | 313 | (defn class-method-static? [method] 314 | (java.lang.reflect.Modifier/isStatic (.getModifiers method))) 315 | 316 | (defn class-methods [cname] 317 | (load-string (format "(.getMethods %s)" cname))) 318 | 319 | (defn find-method [sym] 320 | (let [[cname mname] (str/split (str sym) #"/") 321 | methods (->> 322 | (and cname 323 | (class-methods cname)) 324 | (filter #(= (.getName %) mname)))] 325 | (first methods))) 326 | 327 | (defn arglist [sym] 328 | (let [rsym (resolve-sym sym)] 329 | (xcond 330 | ((= 'special rsym) 331 | (->> (with-out-str 332 | (eval (list #'repl/doc sym))) 333 | (re-find #"\(.*\)") 334 | read-string rest 335 | (map str) 336 | (str/join " ") 337 | (format "[%s]") 338 | list)) 339 | ((and (nil? rsym) (re-find #"/" (str sym))) 340 | (let [method (find-method sym) 341 | args (->> method 342 | (.getParameterTypes) 343 | (map class-name) 344 | (str/join " "))] 345 | (format "(%s [%s]) -> %s" sym args 346 | (class-name (. method getReturnType))))) 347 | (:else 348 | (let [args (map str (:arglists (meta rsym)))] 349 | (if (empty? args) 350 | (condp #(%1 %2) (eval sym) 351 | map? "[key]" 352 | set? "[key]" 353 | vector? "[idx]" 354 | "is uncallable") 355 | args)))))) 356 | 357 | (defmacro ok 358 | "On getting an Exception, just print it." 359 | [& body] 360 | `(try 361 | (eval '~@body) 362 | (catch Exception ~'e (.getMessage ~'e)))) 363 | 364 | (defn classpath [] 365 | (map #(.getAbsolutePath (java.io.File. (.toURI %))) 366 | (.getURLs (java.lang.ClassLoader/getSystemClassLoader)))) 367 | 368 | (defn reader= 369 | "Equality accounting for reader-generated symbols." 370 | [a b] 371 | (try 372 | (xcond 373 | ((and (symbol? a) (symbol? b)) 374 | (or 375 | (= a b) 376 | (and 377 | (re-find #"[0-9]+#$" (name a)) 378 | (re-find #"[0-9]+#$" (name b)) 379 | true))) 380 | 381 | ((and (instance? java.util.regex.Pattern a) 382 | (instance? java.util.regex.Pattern b)) 383 | (= (. a toString) 384 | (. b toString))) 385 | 386 | ((and (empty? a) (empty? b)) 387 | true) 388 | 389 | (:else 390 | (and 391 | (reader= (first a) (first b)) 392 | (reader= (rest a) (rest b))))) 393 | (catch Exception _e 394 | (= a b)))) 395 | 396 | (defn position [x coll equality] 397 | (letfn [(iter [i coll] 398 | (xcond 399 | ((empty? coll) nil) 400 | ((equality x (first coll)) 401 | i) 402 | (:else 403 | (recur (inc i) (rest coll)))))] 404 | (iter 0 coll))) 405 | 406 | (defn guess-intent [expr context] 407 | (if (not (or (list? expr) 408 | (vector? expr))) 409 | expr 410 | (let [idx (position expr context reader=)] 411 | (xcond 412 | ((nil? idx) 413 | expr) 414 | 415 | ;; [x |(+ 1 2) y (+ 3 4)] => {:x 3} 416 | ((and (= (first context) 'let) (= idx 1)) 417 | (shadow-dest expr)) 418 | 419 | ((and (vector? context) 420 | (= 0 (rem (count context) 2)) 421 | (= 0 (rem (inc idx) 2)) 422 | (every? (some-fn symbol? vector? map?) (take-nth 2 context))) 423 | (shadow-dest 424 | (take 2 (drop (- idx 1) context)))) 425 | ((or (nil? context) 426 | (reader= expr context)) 427 | expr) 428 | ((and (#{'doseq 'for} (first context)) 429 | (vector? expr) 430 | (= 2 (count expr))) 431 | (shadow-dest 432 | [(first expr) (first (eval `(with-shadows ~(second expr))))])) 433 | ((and (#{'dotimes} (first context)) 434 | (vector? expr) 435 | (= 2 (count expr))) 436 | (shadow-dest 437 | [(first expr) 0])) 438 | ((#{'-> '->> 'doto} (first context)) 439 | (take (inc idx) context)) 440 | (:t 441 | expr))))) 442 | 443 | (defn add-location-to-defn [expr file line] 444 | (when (and (list? expr) 445 | (= 'defn (first expr)) 446 | file line) 447 | (let [arglist-pos (first (keep-indexed 448 | (fn [i x] (when (or (vector? x) (list? x)) 449 | i)) 450 | expr)) 451 | expr-head (take arglist-pos expr) 452 | expr-tail (drop arglist-pos expr) 453 | expr-doc (or (first (filter string? expr-head)) "") 454 | expr-map (or (first (filter map? expr-head)) {})] 455 | `(~'defn ~(nth expr 1) 456 | ~expr-doc 457 | ~(merge {:l-file file 458 | :l-line line} 459 | expr-map) 460 | ~@expr-tail)))) 461 | 462 | (defn add-location-to-def 463 | [[_def name & args] file line] 464 | (apply list 465 | _def 466 | (with-meta 467 | name 468 | {:l-file file 469 | :l-line line}) 470 | (if (> (count args) 1) 471 | args 472 | (cons "" args)))) 473 | 474 | (defn add-location-to-deflike [expr file line] 475 | (when (and file line (list? expr)) 476 | (xcond ((= (first expr) 'def) 477 | (add-location-to-def expr file line)) 478 | ((= (first expr) 'defn) 479 | (add-location-to-defn expr file line))))) 480 | 481 | (defn read-string-all 482 | "Read all objects from the string S." 483 | [s] 484 | (let [reader (java.io.PushbackReader. 485 | (java.io.StringReader. s))] 486 | (loop [res []] 487 | (if-let [x (try (read reader) 488 | (catch Exception _e))] 489 | (recur (conj res x)) 490 | res)))) 491 | 492 | (defn reval [e-str context-str & {:keys [file line]}] 493 | (let [expr (read-string e-str) 494 | context (try 495 | (read-string context-str) 496 | (catch Exception _)) 497 | full-expr (read-string (format "[%s]" e-str)) 498 | expr1 (xcond 499 | ((nil? context-str) 500 | (cons 'do full-expr)) 501 | ((= (count full-expr) 2) 502 | (shadow-dest full-expr)) 503 | ((add-location-to-deflike expr file line)) 504 | (:else 505 | (guess-intent expr context)))] 506 | (eval `(with-shadows 507 | (try 508 | (do ~expr1) 509 | (catch Exception ~'e 510 | (clojure.core/str "error: " ~ 'e))))))) 511 | 512 | (defn file->elisp [f] 513 | (if (file-exists? f) 514 | f 515 | (. (io/resource f) getPath))) 516 | 517 | (defonce ns-to-jar (atom {})) 518 | 519 | (defn ns-location [sym] 520 | (when (empty? @ns-to-jar) 521 | (reset! ns-to-jar 522 | (apply hash-map 523 | (->> 524 | (cp/classpath) 525 | (mapcat #(interleave 526 | (if (. % isFile) 527 | (nf/find-namespaces-in-jarfile (JarFile. %)) 528 | (nf/find-namespaces-in-dir % nil)) 529 | (repeat %))))))) 530 | (let [dir (get @ns-to-jar sym)] 531 | (if (. dir isFile) 532 | (let [jf (JarFile. dir) 533 | file-in-jar (first 534 | (filter 535 | (fn [f] 536 | (let [entry (nf/read-ns-decl-from-jarfile-entry jf f nil)] 537 | (when (and entry (= (first entry) sym)) 538 | f))) 539 | (nf/clojure-sources-in-jar jf)))] 540 | (list 541 | (str "file:" dir "!/" file-in-jar) 542 | 0)) 543 | (let [file-in-dir (first 544 | (filter 545 | (fn [f] 546 | (let [decl (nfile/read-file-ns-decl f nil)] 547 | (and decl (= (first decl) sym) 548 | f))) 549 | (nf/find-clojure-sources-in-dir dir)))] 550 | (list (.getCanonicalPath file-in-dir) 0))))) 551 | 552 | (defn location [sym] 553 | (let [rs (resolve sym) 554 | m (meta rs)] 555 | (xcond 556 | ((and (nil? rs) (ns-location sym))) 557 | ((:l-file m) 558 | (list (:l-file m) (:l-line m))) 559 | ((and (:file m) (not (re-matches #"^/tmp/" (:file m)))) 560 | (list (file->elisp (:file m)) (:line m)))))) 561 | 562 | (defn pp [expr] 563 | (with-out-str 564 | (clojure.pprint/pprint 565 | expr))) 566 | 567 | (defn all-docs [ns] 568 | (str/join 569 | "::" 570 | (->> (filter (fn [v] 571 | (and (var? v) 572 | (fn? (deref v)))) 573 | (vals (ns-map ns))) 574 | (map 575 | (fn [v] 576 | (let [m (meta v)] 577 | (str v "\n" (:arglists m) "\n" (:doc m)))))))) 578 | -------------------------------------------------------------------------------- /lispy-clojure.cljs: -------------------------------------------------------------------------------- 1 | ;;; lispy-clojure.cljs --- lispy support for ClojureScript. 2 | 3 | ;; Copyright (C) 2020 Oleh Krehel 4 | 5 | ;; This file is not part of GNU Emacs 6 | 7 | ;; This file is free software; you can redistribute it and/or modify 8 | ;; it under the terms of the GNU General Public License as published by 9 | ;; the Free Software Foundation; either version 3, or (at your option) 10 | ;; any later version. 11 | 12 | ;; This program is distributed in the hope that it will be useful, 13 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | ;; GNU General Public License for more details. 16 | 17 | ;; For a full copy of the GNU General Public License 18 | ;; see . 19 | 20 | (ns lispy.clojure 21 | (:require 22 | [goog.object])) 23 | 24 | (defn object-members [obj] 25 | (let [obj (if (string? obj) 26 | (js/Object.getPrototypeOf obj) 27 | obj)] 28 | (loop [o obj 29 | res ()] 30 | (if (nil? o) 31 | (into () (set res)) 32 | (recur 33 | (js/Object.getPrototypeOf o) 34 | (concat 35 | res 36 | (map 37 | (fn [n] 38 | (let [tp (type (goog.object/get obj n))] 39 | (if (= tp js/Function) 40 | n 41 | (str "-" n)))) 42 | (remove 43 | (fn [x] (or (re-find #"__|constructor|[$]" x) 44 | (re-matches #"[0-9]+" x))) 45 | (vec (js/Object.getOwnPropertyNames o)))))))))) 46 | 47 | (defn shadow-map [] 48 | (or 49 | (aget js/window "shadows") 50 | (set! (. js/window -shadows) {}))) 51 | 52 | (defn shadow-unmap [nspc] 53 | (set! (. js/window -shadows) {})) 54 | 55 | (defn shadow-def 56 | "Give SYM in *ns* shadow value EXPR." 57 | [sym expr] 58 | (set! (. js/window -shadows) 59 | (assoc (shadow-map) (name sym) expr)) 60 | expr) 61 | -------------------------------------------------------------------------------- /lispy-inline.el: -------------------------------------------------------------------------------- 1 | ;;; lispy-inline.el --- inline arglist and documentation. -*- lexical-binding: t -*- 2 | 3 | ;; Copyright (C) 2014-2015 Oleh Krehel 4 | 5 | ;; This file is not part of GNU Emacs 6 | 7 | ;; This file is free software; you can redistribute it and/or modify 8 | ;; it under the terms of the GNU General Public License as published by 9 | ;; the Free Software Foundation; either version 3, or (at your option) 10 | ;; any later version. 11 | 12 | ;; This program is distributed in the hope that it will be useful, 13 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | ;; GNU General Public License for more details. 16 | 17 | ;; For a full copy of the GNU General Public License 18 | ;; see . 19 | 20 | ;;; Commentary: 21 | ;; 22 | ;; Display current function arguments or docstring in an in-place 23 | ;; overlay. 24 | 25 | ;;; Code: 26 | 27 | (require 'thingatpt) 28 | 29 | (if (version< emacs-version "26.1") 30 | (progn 31 | (defsubst string-trim-left (string &optional regexp) 32 | "Trim STRING of leading string matching REGEXP. 33 | 34 | REGEXP defaults to \"[ \\t\\n\\r]+\"." 35 | (if (string-match (concat "\\`\\(?:" (or regexp "[ \t\n\r]+") "\\)") string) 36 | (replace-match "" t t string) 37 | string)) 38 | (defsubst string-trim-right (string &optional regexp) 39 | "Trim STRING of trailing string matching REGEXP. 40 | 41 | REGEXP defaults to \"[ \\t\\n\\r]+\"." 42 | (if (string-match (concat "\\(?:" (or regexp "[ \t\n\r]+") "\\)\\'") string) 43 | (replace-match "" t t string) 44 | string)) 45 | (defsubst string-trim (string &optional trim-left trim-right) 46 | "Trim STRING of leading and trailing strings matching TRIM-LEFT and TRIM-RIGHT. 47 | 48 | TRIM-LEFT and TRIM-RIGHT default to \"[ \\t\\n\\r]+\"." 49 | (string-trim-left (string-trim-right string trim-right) trim-left))) 50 | (require 'subr-x)) 51 | 52 | (defgroup lispy-faces nil 53 | "Font-lock faces for `lispy'." 54 | :group 'lispy 55 | :prefix "lispy-face-") 56 | 57 | (defface lispy-face-hint 58 | '((((class color) (background light)) 59 | :background "#fff3bc" :foreground "black") 60 | (((class color) (background dark)) 61 | :background "black" :foreground "#fff3bc")) 62 | "Basic hint face." 63 | :group 'lispy-faces) 64 | 65 | (defface lispy-face-req-nosel 66 | '((t (:inherit lispy-face-hint))) 67 | "Face for required unselected args." 68 | :group 'lispy-faces) 69 | 70 | (defface lispy-face-req-sel 71 | '((t (:inherit lispy-face-req-nosel :bold t))) 72 | "Face for required selected args." 73 | :group 'lispy-faces) 74 | 75 | (defface lispy-face-opt-nosel 76 | '((t (:inherit lispy-face-hint :slant italic))) 77 | "Face for optional unselected args." 78 | :group 'lispy-faces) 79 | 80 | (defface lispy-face-key-nosel 81 | '((t (:inherit lispy-face-hint :slant italic))) 82 | "Face for keyword unselected args." 83 | :group 'lispy-faces) 84 | 85 | (defface lispy-face-opt-sel 86 | '((t (:inherit lispy-face-opt-nosel :bold t))) 87 | "Face for optional selected args." 88 | :group 'lispy-faces) 89 | 90 | (defface lispy-face-key-sel 91 | '((t (:inherit lispy-face-opt-nosel :bold t))) 92 | "Face for keyword selected args." 93 | :group 'lispy-faces) 94 | 95 | (defface lispy-face-rst-nosel 96 | '((t (:inherit lispy-face-hint))) 97 | "Face for rest unselected args." 98 | :group 'lispy-faces) 99 | 100 | (defface lispy-face-rst-sel 101 | '((t (:inherit lispy-face-rst-nosel :bold t))) 102 | "Face for rest selected args." 103 | :group 'lispy-faces) 104 | 105 | (defcustom lispy-window-height-ratio 0.65 106 | "`lispy--show' will fail with string taller than window height times this. 107 | The caller of `lispy--show' might use a substitute e.g. `describe-function'." 108 | :type 'float 109 | :group 'lispy) 110 | 111 | (defvar lispy-elisp-modes 112 | '(emacs-lisp-mode lisp-interaction-mode eltex-mode minibuffer-inactive-mode 113 | suggest-mode) 114 | "Modes for which `lispy--eval-elisp' and related functions are appropriate.") 115 | 116 | (defvar lispy-clojure-modes 117 | '(clojure-mode clojurescript-mode clojurex-mode clojurec-mode) 118 | "Modes for which clojure related functions are appropriate.") 119 | 120 | (defvar lispy-overlay nil 121 | "Hint overlay instance.") 122 | 123 | (defvar lispy-hint-pos nil 124 | "Point position where the hint should be (re-) displayed.") 125 | 126 | (declare-function lispy--eval-clojure-cider "le-clojure") 127 | (declare-function lispy--clojure-args "le-clojure") 128 | (declare-function lispy--clojure-resolve "le-clojure") 129 | (declare-function lispy--describe-clojure-java "le-clojure") 130 | (declare-function lispy--eval-scheme "le-scheme") 131 | (declare-function lispy--eval-lisp "le-lisp") 132 | (declare-function lispy--lisp-args "le-lisp") 133 | (declare-function lispy--lisp-describe "le-lisp") 134 | (declare-function lispy--back-to-paren "lispy") 135 | (declare-function lispy--current-function "lispy") 136 | (declare-function lispy--in-comment-p "lispy") 137 | (declare-function lispy--bounds-string "lispy") 138 | 139 | ;; ——— Commands ———————————————————————————————————————————————————————————————— 140 | (defun lispy--back-to-python-function () 141 | "Move point from function call at point to the function name." 142 | (let ((pt (point)) 143 | bnd) 144 | (if (lispy--in-comment-p) 145 | (error "Not possible in a comment") 146 | (condition-case nil 147 | (progn 148 | (when (setq bnd (lispy--bounds-string)) 149 | (goto-char (car bnd))) 150 | (up-list -1)) 151 | (error (goto-char pt))) 152 | (unless (looking-at "\\_<") 153 | (re-search-backward "\\_<" (line-beginning-position)))))) 154 | 155 | (defun lispy-arglist-inline () 156 | "Display arglist for `lispy--current-function' inline." 157 | (interactive) 158 | (save-excursion 159 | (if (eq major-mode 'python-mode) 160 | (lispy--back-to-python-function) 161 | (lispy--back-to-paren)) 162 | (unless (and (prog1 (lispy--cleanup-overlay) 163 | (when (window-minibuffer-p) 164 | (window-resize (selected-window) -1))) 165 | (= lispy-hint-pos (point))) 166 | (cond ((memq major-mode lispy-elisp-modes) 167 | (let ((sym (intern-soft (lispy--current-function)))) 168 | (cond ((fboundp sym) 169 | (setq lispy-hint-pos (point)) 170 | (lispy--show (lispy--pretty-args sym)))))) 171 | ((or (memq major-mode '(cider-repl-mode)) 172 | (memq major-mode lispy-clojure-modes)) 173 | (require 'le-clojure) 174 | (setq lispy-hint-pos (point)) 175 | (lispy--show (lispy--clojure-args (lispy--current-function)))) 176 | 177 | ((eq major-mode 'lisp-mode) 178 | (require 'le-lisp) 179 | (setq lispy-hint-pos (point)) 180 | (lispy--show (lispy--lisp-args (lispy--current-function)))) 181 | 182 | ((eq major-mode 'python-mode) 183 | (require 'le-python) 184 | (setq lispy-hint-pos (point)) 185 | (let ((arglist (lispy--python-arglist 186 | (python-info-current-symbol) 187 | (buffer-file-name) 188 | (line-number-at-pos) 189 | (current-column)))) 190 | (while (eq (char-before) ?.) 191 | (backward-sexp)) 192 | (lispy--show arglist))) 193 | 194 | (t (error "%s isn't supported currently" major-mode)))))) 195 | 196 | (defvar lispy--di-window-config nil 197 | "Store window configuration before `lispy-describe-inline'.") 198 | 199 | (defun lispy--hint-pos () 200 | "Point position for the first column of the hint." 201 | (save-excursion 202 | (cond ((region-active-p) 203 | (goto-char (region-beginning))) 204 | ((eq major-mode 'python-mode) 205 | (condition-case nil 206 | (goto-char (beginning-of-thing 'sexp)) 207 | (error (up-list -1)))) 208 | (t 209 | (lispy--back-to-paren))) 210 | (point))) 211 | 212 | (defun lispy--cleanup-overlay () 213 | "Delete `lispy-overlay' if it's valid and return t." 214 | (when (overlayp lispy-overlay) 215 | (delete-overlay lispy-overlay) 216 | (setq lispy-overlay nil) 217 | t)) 218 | 219 | (declare-function geiser-doc-symbol-at-point "geiser-doc") 220 | 221 | (defun lispy--describe-inline (str pos) 222 | "Toggle the overlay hint." 223 | (condition-case nil 224 | (save-excursion 225 | (when (= 0 (count-lines (window-start) (point))) 226 | (recenter 1)) 227 | (setq lispy-hint-pos pos) 228 | (goto-char lispy-hint-pos) 229 | (lispy--show (propertize str 'face 'lispy-face-hint))) 230 | (error 231 | (lispy--cleanup-overlay)))) 232 | 233 | (declare-function cider-nrepl-op-supported-p "ext:cider-client") 234 | (declare-function cider-sync-request:info "ext:cider-client") 235 | (declare-function nrepl-dict-get "ext:nrepl-dict") 236 | 237 | (defun lispy--docstring (sym) 238 | "Get the docstring for SYM." 239 | (cond 240 | ((memq major-mode lispy-elisp-modes) 241 | (setq sym (intern-soft sym)) 242 | (cond ((fboundp sym) 243 | (or (documentation sym) 244 | "undocumented")) 245 | ((boundp sym) 246 | (or (documentation-property 247 | sym 'variable-documentation) 248 | "undocumented")) 249 | (t "unbound"))) 250 | ((eq major-mode 'clojurescript-mode) 251 | (let (info) 252 | (or 253 | (and (cider-nrepl-op-supported-p "info") 254 | (setq info (cider-sync-request:info sym)) 255 | (nrepl-dict-get info "doc")) 256 | (concat "No doc for " sym)))) 257 | ((or (memq major-mode lispy-clojure-modes) 258 | (memq major-mode '(cider-repl-mode))) 259 | (require 'le-clojure) 260 | (let ((rsymbol (lispy--clojure-resolve sym))) 261 | (string-trim-left 262 | (replace-regexp-in-string 263 | "^\\(?:-+\n\\|\n*.*$.*@.*\n*\\)" "" 264 | (cond ((stringp rsymbol) 265 | (lispy--eval-clojure-cider 266 | (format "(with-out-str (clojure.repl/doc %s))" rsymbol))) 267 | ((eq rsymbol 'special) 268 | (lispy--eval-clojure-cider 269 | (format "(with-out-str (clojure.repl/doc %s))" sym))) 270 | ((eq rsymbol 'keyword) 271 | "No docs for keywords") 272 | ((and (listp rsymbol) 273 | (eq (car rsymbol) 'variable)) 274 | (cadr rsymbol)) 275 | (t 276 | (or (lispy--describe-clojure-java sym) 277 | (format "Could't resolve '%s" sym)))))))) 278 | ((eq major-mode 'lisp-mode) 279 | (require 'le-lisp) 280 | (lispy--lisp-describe sym)) 281 | ((eq major-mode 'python-mode) 282 | (require 'le-python) 283 | (if sym 284 | (lispy--python-docstring sym) 285 | (require 'semantic) 286 | (semantic-mode 1) 287 | (let ((sym (semantic-ctxt-current-symbol))) 288 | (if sym 289 | (progn 290 | (setq sym (mapconcat #'identity sym ".")) 291 | (or 292 | (lispy--python-docstring sym) 293 | (progn 294 | (message "no doc: %s" sym) 295 | nil))) 296 | (error "The point is not on a symbol"))))) 297 | (t 298 | (format "%s isn't supported currently" major-mode)))) 299 | 300 | (declare-function semantic-ctxt-current-symbol "ctxt") 301 | 302 | (defun lispy-describe-inline () 303 | "Display documentation for `lispy--current-function' inline." 304 | (interactive) 305 | (cond ((cl-some 306 | (lambda (window) 307 | (equal (buffer-name (window-buffer window)) "*lispy-help*")) 308 | (window-list)) 309 | (when (window-configuration-p lispy--di-window-config) 310 | (set-window-configuration lispy--di-window-config))) 311 | ((eq major-mode 'scheme-mode) 312 | (geiser-doc-symbol-at-point)) 313 | (t 314 | (let ((new-hint-pos (lispy--hint-pos))) 315 | (if (and (eq lispy-hint-pos new-hint-pos) 316 | (overlayp lispy-overlay)) 317 | (lispy--cleanup-overlay) 318 | (lispy--describe-inline 319 | (lispy--docstring (lispy--current-function)) 320 | new-hint-pos)))))) 321 | 322 | (declare-function lispy--python-docstring "le-python") 323 | (declare-function lispy--python-arglist "le-python") 324 | (declare-function python-info-current-symbol "python") 325 | 326 | ;; ——— Utilities ——————————————————————————————————————————————————————————————— 327 | (defun lispy--arglist (symbol) 328 | "Get arglist for SYMBOL." 329 | (let (doc) 330 | (if (setq doc (help-split-fundoc (documentation symbol t) symbol)) 331 | (car doc) 332 | (prin1-to-string 333 | (cons symbol (help-function-arglist symbol t)))))) 334 | 335 | (defun lispy--join-pad (strs width) 336 | "Join STRS padding each line with WIDTH spaces." 337 | (let* ((maxw (apply #'max (mapcar #'length strs))) 338 | (padding (make-string width ?\ )) 339 | (fstring (format "%%- %ds" maxw))) 340 | (mapconcat 341 | (lambda (x) 342 | (concat 343 | padding 344 | (let ((str (format fstring x))) 345 | (font-lock-append-text-property 346 | 0 (length str) 'face 'lispy-face-hint str) 347 | str))) 348 | strs 349 | "\n"))) 350 | 351 | (defun lispy--show-fits-p (str) 352 | "Return nil if window isn't large enough to display STR whole." 353 | (let ((strs (split-string str "\n"))) 354 | (when (or (< (length strs) (* lispy-window-height-ratio (window-height))) 355 | (window-minibuffer-p)) 356 | strs))) 357 | 358 | (defun lispy--show (str) 359 | "Show STR hint when `lispy--show-fits-p' is t." 360 | (let ((last-point (point)) 361 | (strs (lispy--show-fits-p str))) 362 | (if strs 363 | (progn 364 | (setq str (lispy--join-pad 365 | strs 366 | (+ (if (window-minibuffer-p) 367 | (- (minibuffer-prompt-end) (point-min)) 368 | 0) 369 | (string-width (buffer-substring 370 | (line-beginning-position) 371 | (point)))))) 372 | (save-excursion 373 | (goto-char lispy-hint-pos) 374 | (if (= -1 (forward-line -1)) 375 | (setq str (concat str "\n")) 376 | (end-of-line) 377 | (setq str (concat "\n" str))) 378 | (setq str (concat str 379 | (buffer-substring (point) (1+ (point))))) 380 | (if lispy-overlay 381 | (progn 382 | (move-overlay lispy-overlay (point) (+ (point) 1)) 383 | (overlay-put lispy-overlay 'invisible nil)) 384 | (setq lispy-overlay (make-overlay (point) (+ (point) 1))) 385 | (overlay-put lispy-overlay 'priority 9999)) 386 | (overlay-put lispy-overlay 'display str) 387 | (overlay-put lispy-overlay 'after-string "") 388 | (put 'lispy-overlay 'last-point last-point))) 389 | (setq lispy--di-window-config (current-window-configuration)) 390 | (save-selected-window 391 | (pop-to-buffer (get-buffer-create "*lispy-help*")) 392 | (let ((inhibit-read-only t)) 393 | (delete-region (point-min) (point-max)) 394 | (insert str) 395 | (goto-char (point-min)) 396 | (help-mode)))))) 397 | 398 | (defun lispy--pretty-args (symbol) 399 | "Return a vector of fontified strings for function SYMBOL." 400 | (let* ((args (cdr (read (lispy--arglist symbol)))) 401 | (p-opt (cl-position '&optional args :test 'equal)) 402 | (p-rst (or (cl-position '&rest args :test 'equal) 403 | (cl-position-if (lambda (x) 404 | (and (symbolp x) 405 | (string-match 406 | "\\.\\.\\.\\'" 407 | (symbol-name x)))) 408 | args))) 409 | (a-req (cl-subseq args 0 (or p-opt p-rst (length args)))) 410 | (a-opt (and p-opt 411 | (cl-subseq args (1+ p-opt) (or p-rst (length args))))) 412 | (a-rst (and p-rst (last args)))) 413 | (format 414 | "(%s)" 415 | (mapconcat 416 | #'identity 417 | (append 418 | (list (propertize (symbol-name symbol) 'face 'lispy-face-hint)) 419 | (mapcar 420 | (lambda (x) 421 | (propertize (downcase (prin1-to-string x)) 'face 'lispy-face-req-nosel)) 422 | a-req) 423 | (mapcar 424 | (lambda (x) 425 | (propertize (downcase (prin1-to-string x)) 'face 'lispy-face-opt-nosel)) 426 | a-opt) 427 | (mapcar 428 | (lambda (x) 429 | (setq x (downcase (symbol-name x))) 430 | (unless (string-match "\\.\\.\\.$" x) 431 | (setq x (concat x "..."))) 432 | (propertize x 'face 'lispy-face-rst-nosel)) 433 | a-rst)) 434 | " ")))) 435 | 436 | (provide 'lispy-inline) 437 | 438 | ;;; Local Variables: 439 | ;;; outline-regexp: ";; ———" 440 | ;;; End: 441 | 442 | ;;; lispy-inline.el ends here 443 | -------------------------------------------------------------------------------- /lispy-occur.el: -------------------------------------------------------------------------------- 1 | ;;; lispy-occur.el --- Select a line within the current top level sexp. -*- lexical-binding: t -*- 2 | 3 | ;; Copyright (C) 2014-2021 Oleh Krehel 4 | 5 | ;; This file is not part of GNU Emacs 6 | 7 | ;; This file is free software; you can redistribute it and/or modify 8 | ;; it under the terms of the GNU General Public License as published by 9 | ;; the Free Software Foundation; either version 3, or (at your option) 10 | ;; any later version. 11 | 12 | ;; This program is distributed in the hope that it will be useful, 13 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | ;; GNU General Public License for more details. 16 | 17 | ;; For a full copy of the GNU General Public License 18 | ;; see . 19 | 20 | ;;; Commentary: 21 | ;; 22 | 23 | ;;; Code: 24 | (require 'swiper) 25 | 26 | (defcustom lispy-occur-backend 'ivy 27 | "Method to navigate to a line with `lispy-occur'." 28 | :type '(choice 29 | (const :tag "Ivy" ivy) 30 | (const :tag "Helm" helm))) 31 | 32 | (defvar lispy--occur-beg 1 33 | "Start position of the top level sexp during `lispy-occur'.") 34 | 35 | (defvar lispy--occur-end 1 36 | "End position of the top level sexp during `lispy-occur'.") 37 | 38 | (defun lispy--occur-candidates (&optional bnd) 39 | "Return the candidates for `lispy-occur'." 40 | (setq bnd (or bnd (save-excursion 41 | (unless (and (bolp) 42 | (lispy-left-p)) 43 | (beginning-of-defun)) 44 | (lispy--bounds-dwim)))) 45 | (let ((line-number -1) 46 | candidates) 47 | (setq lispy--occur-beg (car bnd)) 48 | (setq lispy--occur-end (cdr bnd)) 49 | (save-excursion 50 | (goto-char lispy--occur-beg) 51 | (while (< (point) lispy--occur-end) 52 | (push (format "%-3d %s" 53 | (cl-incf line-number) 54 | (buffer-substring 55 | (line-beginning-position) 56 | (line-end-position))) 57 | candidates) 58 | (forward-line 1))) 59 | (nreverse candidates))) 60 | 61 | (defun lispy--occur-preselect () 62 | "Initial candidate regex for `lispy-occur'." 63 | (format "^%d" 64 | (- 65 | (line-number-at-pos (point)) 66 | (line-number-at-pos lispy--occur-beg)))) 67 | 68 | (defvar helm-input) 69 | (declare-function helm "ext:helm") 70 | 71 | (defun lispy-occur-action-goto-paren (x) 72 | "Goto line X for `lispy-occur'." 73 | (setq x (read x)) 74 | (goto-char lispy--occur-beg) 75 | (let ((input (if (eq lispy-occur-backend 'helm) 76 | helm-input 77 | ivy-text)) 78 | str-or-comment) 79 | (cond ((string= input "") 80 | (forward-line x) 81 | (back-to-indentation) 82 | (when (re-search-forward lispy-left (line-end-position) t) 83 | (goto-char (match-beginning 0)))) 84 | 85 | ((setq str-or-comment 86 | (progn 87 | (forward-line x) 88 | (re-search-forward (ivy--regex input) 89 | (line-end-position) t) 90 | (lispy--in-string-or-comment-p))) 91 | (goto-char str-or-comment)) 92 | 93 | ((re-search-backward lispy-left (line-beginning-position) t) 94 | (goto-char (match-beginning 0))) 95 | 96 | ((re-search-forward lispy-left (line-end-position) t) 97 | (goto-char (match-beginning 0))) 98 | 99 | (t 100 | (back-to-indentation))))) 101 | 102 | (defun lispy-occur-action-goto-end (x) 103 | "Goto line X for `lispy-occur'." 104 | (setq x (read x)) 105 | (goto-char lispy--occur-beg) 106 | (forward-line x) 107 | (re-search-forward (ivy--regex ivy-text) (line-end-position) t)) 108 | 109 | (defun lispy-occur-action-goto-beg (x) 110 | "Goto line X for `lispy-occur'." 111 | (when (lispy-occur-action-goto-end x) 112 | (goto-char (match-beginning 0)))) 113 | 114 | (defun lispy-occur-action-mc (_x) 115 | "Make a fake cursor for each `lispy-occur' candidate." 116 | (let ((cands (nreverse ivy--old-cands)) 117 | cand) 118 | (while (setq cand (pop cands)) 119 | (goto-char lispy--occur-beg) 120 | (forward-line (read cand)) 121 | (re-search-forward (ivy--regex ivy-text) (line-end-position) t) 122 | (when cands 123 | (mc/create-fake-cursor-at-point)))) 124 | (multiple-cursors-mode 1)) 125 | 126 | (ivy-set-actions 127 | 'lispy-occur 128 | '(("m" lispy-occur-action-mc "multiple-cursors") 129 | ("j" lispy-occur-action-goto-beg "goto start") 130 | ("k" lispy-occur-action-goto-end "goto end"))) 131 | 132 | (defvar ivy-last) 133 | (declare-function ivy-state-window "ext:ivy") 134 | 135 | ;;;###autoload 136 | (defun lispy-occur () 137 | "Select a line within current top level sexp. 138 | See `lispy-occur-backend' for the selection back end." 139 | (interactive) 140 | (swiper--init) 141 | (cond ((eq lispy-occur-backend 'helm) 142 | (require 'helm) 143 | (add-hook 'helm-move-selection-after-hook 144 | #'lispy--occur-update-input-helm) 145 | (add-hook 'helm-update-hook 146 | #'lispy--occur-update-input-helm) 147 | (unwind-protect 148 | (helm :sources 149 | `((name . "this defun") 150 | (candidates . ,(lispy--occur-candidates)) 151 | (action . lispy-occur-action-goto-paren) 152 | (match-strict . 153 | (lambda (x) 154 | (ignore-errors 155 | (string-match 156 | (ivy--regex helm-input) x))))) 157 | :preselect (lispy--occur-preselect) 158 | :buffer "*lispy-occur*") 159 | (swiper--cleanup) 160 | (remove-hook 'helm-move-selection-after-hook 161 | #'lispy--occur-update-input-helm) 162 | (remove-hook 'helm-update-hook 163 | #'lispy--occur-update-input-helm))) 164 | ((eq lispy-occur-backend 'ivy) 165 | (unwind-protect 166 | (ivy-read "pattern: " 167 | (lispy--occur-candidates) 168 | :preselect (lispy--occur-preselect) 169 | :require-match t 170 | :update-fn (lambda () 171 | (lispy--occur-update-input 172 | ivy-text 173 | (ivy-state-current ivy-last))) 174 | :action #'lispy-occur-action-goto-paren 175 | :caller 'lispy-occur) 176 | (swiper--cleanup) 177 | (when (null ivy-exit) 178 | (goto-char swiper--opoint)))) 179 | (t 180 | (error "Bad `lispy-occur-backend': %S" lispy-occur-backend)))) 181 | 182 | (defun lispy--occur-update-input-helm () 183 | "Update selection for `lispy-occur' using `helm' back end." 184 | (lispy--occur-update-input 185 | helm-input 186 | (buffer-substring-no-properties 187 | (line-beginning-position) 188 | (line-end-position)))) 189 | 190 | (defun lispy--occur-update-input (input str) 191 | "Update selection for `ivy-occur'. 192 | INPUT is the current input text. 193 | STR is the full current candidate." 194 | (swiper--cleanup) 195 | (let ((re (ivy--regex input)) 196 | (num (if (string-match "^[0-9]+" str) 197 | (string-to-number (match-string 0 str)) 198 | 0))) 199 | (with-selected-window (ivy-state-window ivy-last) 200 | (goto-char lispy--occur-beg) 201 | (when (cl-plusp num) 202 | (forward-line num) 203 | (unless (<= (point) lispy--occur-end) 204 | (recenter))) 205 | (let ((ov (make-overlay (line-beginning-position) 206 | (1+ (line-end-position))))) 207 | (overlay-put ov 'face 'swiper-line-face) 208 | (overlay-put ov 'window (ivy-state-window ivy-last)) 209 | (push ov swiper--overlays)) 210 | (re-search-forward re (line-end-position) t) 211 | (swiper--add-overlays 212 | re 213 | lispy--occur-beg 214 | lispy--occur-end)))) 215 | 216 | ;;; lispy-occur.el ends here 217 | -------------------------------------------------------------------------------- /lispy-tags.el: -------------------------------------------------------------------------------- 1 | ;;; lispy-tags.el --- Facilitate getting a pretty list of tags for many files -*- lexical-binding: t -*- 2 | 3 | ;; Copyright (C) 2015 Oleh Krehel 4 | 5 | ;; This file is not part of GNU Emacs 6 | 7 | ;; This file is free software; you can redistribute it and/or modify 8 | ;; it under the terms of the GNU General Public License as published by 9 | ;; the Free Software Foundation; either version 3, or (at your option) 10 | ;; any later version. 11 | 12 | ;; This program is distributed in the hope that it will be useful, 13 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | ;; GNU General Public License for more details. 16 | 17 | ;; For a full copy of the GNU General Public License 18 | ;; see . 19 | 20 | ;;; Commentary: 21 | ;; 22 | 23 | ;;; Code: 24 | 25 | (require 'cl-lib) 26 | 27 | (defvar lispy-tag-arity) ;; defined in lispy 28 | 29 | (declare-function semantic-new-buffer-fcn "semantic") 30 | (declare-function semantic-parse-region "semantic") 31 | (declare-function semantic-mode "semantic") 32 | (declare-function semantic-parse-tree-state "semantic") 33 | (declare-function semantic-tag-overlay "tag") 34 | (declare-function semantic-tag-get-attribute "tag") 35 | (declare-function semantic-tag-name "tag") 36 | (declare-function semantic-current-tag "find") 37 | (declare-function lispy--tag-regexp "lispy") 38 | (declare-function lispy--modify-tag "lispy") 39 | (declare-function lispy--tag-name "lispy") 40 | 41 | (defvar lispy-db (make-hash-table :test 'equal) 42 | "An alist of file to a pretty list of tags.") 43 | 44 | (cl-defstruct lispy-dbfile 45 | file 46 | tags 47 | modtime 48 | plain-tags) 49 | 50 | (defun lispy--file-list () 51 | "Get the list of same type files in current directory." 52 | (and (buffer-file-name) 53 | (let ((ext (file-name-extension (buffer-file-name)))) 54 | (nreverse 55 | (cl-remove-if 56 | (lambda (x) (string-match "\\(?:^\\.?#\\|~$\\|loaddefs.el\\)" x)) 57 | (file-expand-wildcards (format "*.%s" ext))))))) 58 | 59 | (define-error 'no-semantic-support "No semantic support for major-mode") 60 | 61 | (defun lispy--fetch-this-file-tags (&optional file) 62 | "Fetch tags for FILE." 63 | (setq file (or file (buffer-file-name))) 64 | (semantic-new-buffer-fcn) 65 | (when (null semantic--parse-table) 66 | (signal 'no-semantic-support nil)) 67 | (let ((tags (semantic-parse-region (point-min) (point-max)))) 68 | (when (memq major-mode (cons 'lisp-mode lispy-elisp-modes)) 69 | (let ((arity (cdr (assoc major-mode lispy-tag-arity))) 70 | (tag-regex (lispy--tag-regexp))) 71 | (mapc (lambda (x) (lispy--modify-tag x tag-regex arity file)) tags))) 72 | tags)) 73 | 74 | (defun lispy-build-semanticdb (&optional dir) 75 | "Build and save semanticdb for DIR." 76 | (interactive) 77 | (setq dir (or dir default-directory)) 78 | (let ((default-directory dir)) 79 | (dolist (f (lispy--file-list)) 80 | (let ((buf (get-file-buffer f))) 81 | (with-current-buffer (find-file-noselect f) 82 | (semantic-mode 1) 83 | (let ((semantic-parse-tree-state 'needs-rebuild)) 84 | (lispy--fetch-this-file-tags)) 85 | (unless buf 86 | (kill-buffer)))))) 87 | (let ((db (semanticdb-directory-loaded-p dir))) 88 | (or (semanticdb-save-db db) db))) 89 | 90 | (defvar lispy-completion-method) 91 | (defvar lispy-helm-columns) 92 | 93 | (defun lispy--format-tag-line (x) 94 | "Add file name to (`lispy--tag-name' X)." 95 | (if (and (eq lispy-completion-method 'ido) 96 | (not (or (bound-and-true-p ido-vertical-mode) 97 | (bound-and-true-p ivy-mode)))) 98 | x 99 | (let* ((width (min (- (window-width) 100 | (if (and (boundp 'fringe-mode) 101 | (not (eq fringe-mode 0))) 0 1)) 102 | (cadr lispy-helm-columns))) 103 | (s1 (car x)) 104 | (s2 (file-name-nondirectory 105 | (cadr x)))) 106 | (cons (if (< width 50) 107 | (if (> (length s1) width) 108 | (concat (substring s1 0 (- width 3)) 109 | "...") 110 | s1) 111 | (format (format "%%s%% %ds" (- width 112 | (length s1))) 113 | s1 s2)) 114 | x)))) 115 | 116 | (defun lispy--file-fresh-p (actual-time stored-time) 117 | "Return t when ACTUAL-TIME isn't much larger than STORED-TIME." 118 | (and stored-time 119 | (< (time-to-seconds 120 | (time-subtract 121 | actual-time 122 | stored-time)) 123 | 1.0))) 124 | 125 | (defvar lispy-force-reparse nil 126 | "When non-nil, ignore that tags are up-to-date and parse anyway.") 127 | 128 | (defmacro lispy-with-mode-off (mode &rest body) 129 | "Run BODY with MODE off and re-enable it if it was on." 130 | (declare (indent 1) 131 | (debug (form body))) 132 | `(let ((mode-was-on (bound-and-true-p ,mode)) 133 | res) 134 | (if mode-was-on 135 | (progn 136 | (let ((inhibit-message t)) 137 | (,mode -1)) 138 | (unwind-protect 139 | (setq res (progn ,@body)) 140 | (let ((inhibit-message t)) 141 | (,mode 1))) 142 | res) 143 | ,@body))) 144 | 145 | (defun lispy--fetch-tags (&optional file-list) 146 | "Get a list of tags for FILE-LIST." 147 | (require 'semantic/bovine/el) 148 | (lispy-with-mode-off recentf-mode 149 | (setq file-list (or file-list (lispy--file-list))) 150 | (let (res dbfile db-to-save) 151 | (dolist (file file-list) 152 | (let ((file-modtime (nth 5 (file-attributes file 'integer))) 153 | (exfile (expand-file-name file))) 154 | (unless (and (null lispy-force-reparse) 155 | (setq dbfile 156 | (gethash exfile lispy-db)) 157 | (lispy--file-fresh-p 158 | file-modtime 159 | (lispy-dbfile-modtime dbfile)) 160 | (lispy-dbfile-tags dbfile)) 161 | (let ((table (semanticdb-create-table-for-file (expand-file-name file)))) 162 | (if (null table) 163 | (error "Couldn't open semanticdb for file: %S" file) 164 | (let ((db (car table)) 165 | (table (cdr table)) 166 | tags) 167 | (unless (and (null lispy-force-reparse) 168 | (lispy--file-fresh-p 169 | file-modtime 170 | (oref table lastmodtime)) 171 | (setq tags 172 | (ignore-errors 173 | (oref table tags))) 174 | (semantic-tag-overlay (car-safe tags)) 175 | (not (eq (cadr (car-safe tags)) 'code))) 176 | (let ((buf (get-file-buffer file))) 177 | (with-current-buffer (or buf (find-file-noselect file)) 178 | (semantic-new-buffer-fcn) 179 | (semantic-mode 1) 180 | (oset table tags 181 | (let ((semantic-parse-tree-state 'needs-update)) 182 | (lispy--fetch-this-file-tags file))) 183 | (oset table lastmodtime 184 | (current-time)) 185 | (semanticdb-set-dirty table) 186 | (cl-pushnew db db-to-save) 187 | (unless buf 188 | (kill-buffer))))) 189 | (puthash 190 | exfile 191 | (setq dbfile 192 | (make-lispy-dbfile 193 | :file file 194 | :modtime (oref table lastmodtime) 195 | :tags (mapcar 196 | (lambda (x) 197 | (lispy--make-tag x exfile)) 198 | (oref table tags)) 199 | :plain-tags (oref table tags))) 200 | lispy-db))))) 201 | (setq res (append (lispy-dbfile-tags dbfile) res)))) 202 | (dolist (db db-to-save) 203 | (semanticdb-save-db db)) 204 | res))) 205 | 206 | (defun lispy--make-tag (tag file) 207 | "Construct a modified TAG entry including FILE." 208 | (list (lispy--tag-name tag file) 209 | file 210 | (semantic-tag-overlay tag))) 211 | 212 | 213 | (provide 'lispy-tags) 214 | 215 | ;;; lispy-tags.el ends here 216 | -------------------------------------------------------------------------------- /lispytutor/lispytutor.el: -------------------------------------------------------------------------------- 1 | ;;* Welcome to the `lispy-mode' Tutor 2 | ;; 3 | ;; `lispy-mode' is a very powerful way to edit LISP code that has many 4 | ;; commands, too many to explain in a tutor such as this. This tutor 5 | ;; is designed to describe enough of the commands that you will be 6 | ;; able to easily use `lispy-mode' as an all-purpose LISP editor. 7 | ;; 8 | ;; The approximate time required to complete the tutor is 25-30 9 | ;; minutes, depending upon how much time is spent with 10 | ;; experimentation. 11 | ;; 12 | ;; ATTENTION: 13 | ;; The commands in the lessons will modify the code. You can always 14 | ;; revert the file using git. 15 | ;; 16 | ;; It is important to remember that this tutor is set up to teach by 17 | ;; use. That means that you need to execute the commands to learn 18 | ;; them properly. If you only read the text, you will forget the 19 | ;; commands! 20 | ;; 21 | ;; Remember that this is Emacs, you can rebind any binding that you 22 | ;; don't like. However, a lot of thought has been put into the current 23 | ;; bindings so don't be surprised if you find stuff inconvenient after 24 | ;; rebinding without thinking it through. 25 | ;; 26 | ;; Your point should now be at beginning of the first line. 27 | ;; Press =M-<= if it isn't. 28 | ;; 29 | ;; Press =J= to move to the next outline downwards. 30 | ;; 31 | ;; You should now be at Lesson 1.1. 32 | ;; 33 | ;; Press =i= to show the lesson. 34 | ;; Press =N= to narrow to the lesson. 35 | ;; 36 | ;;* Lesson 1.1: TUTORIAL NAVIGATION 37 | ;; This tutorial uses lispy outlines for navigation. 38 | ;; 39 | ;; Outlines provide a way of navigating code in a tree-like fashion. 40 | ;; The outlines themselves form the nodes of the tree. 41 | ;; 42 | ;; An outline begins with a comment ";;" followed by one or more 43 | ;; asterisks. The number of asterisks determines the outline's depth 44 | ;; in the tree. Your cursor should be at the start of the outline: 45 | ;; ";;* Lesson 1.1: TUTORIAL NAVIGATION" 46 | ;; 47 | ;; Place it there if it isn't already. 48 | ;; 49 | ;; Lispy enables special keybindings when your cursor is placed on an 50 | ;; object lispy recognizes. 51 | ;; 52 | ;; Here's a few to get started: 53 | ;; =J= moves to the next outline 54 | ;; =K= moves to the previous outline 55 | ;; =i= cycles the expansion of the outline and its children. 56 | ;; 57 | ;; Press =J= and =K= a few times to navigate the outlines in this lesson. 58 | ;; Try cycling the expansion of outlines with =i=. 59 | ;; 60 | ;; When you're finished proceed to Exercise 1. 61 | ;; 62 | ;;** Exercise 1 63 | ;; Lispy can "narrow" or "widen" outlines. 64 | ;; 65 | ;; Narrowing an outline hides all the outlines in the buffer except 66 | ;; for the outline at the cursor. This is useful when you want to 67 | ;; view or operate on the contents of a single outline. 68 | ;; 69 | ;; Press =N= to narrow to the outline for Exercise 1. 70 | ;; 71 | ;; Widening shows all outlines that have been hidden through 72 | ;; narrowing. 73 | ;; 74 | ;; Press =W= to widen the outlines. Notice how the rest of the 75 | ;; tutorial is displayed. 76 | ;; 77 | ;; Move to Exercise 2, show it, and narrow to it using the 78 | ;; shortcuts you've learned. 79 | ;; 80 | ;;** Exercise 2 81 | ;; Let's wrap up this lesson with one final shortcut. 82 | ;; 83 | ;; =2-I= will show the condensed outline structure for all the visible 84 | ;; outlines in the buffer. 85 | ;; 86 | ;; Press =W= to show the hidden outlines. 87 | ;; Press =2-I= and proceed to Lesson 1.2 88 | ;; 89 | ;;* Lesson 1.2: MOVING THE CURSOR 90 | ;; 91 | ;; Before we learn about moving the cursor, let's get some terminology 92 | ;; out the way: 93 | ;; 94 | ;; Def. 1: Point - The current cursor position. 95 | ;; 96 | ;; Def. 2: Opener - A term for the characters ( and [ and {. 97 | ;; 98 | ;; Def. 3: Closer - A term for the characters ) and ] and }. 99 | ;; 100 | ;; Def. 4: Short Binding - A command called by an uppercase or 101 | ;; lowercase letter. 102 | ;; 103 | ;; Def. 5: Special - A state the point can be in under certain 104 | ;; conditions. 105 | ;; 106 | ;; The point is 'special' when any of the conditions below are true: 107 | ;; 108 | ;; - the point is on an opener 109 | ;; - the point is on a closer 110 | ;; - the point is at the start of a comment 111 | ;; - the region is active 112 | ;; 113 | ;; When the point is special, lowercase and uppercase letters will 114 | ;; call short bindings instead of inserting characters. The digit 115 | ;; keys will call `digit-argument'. The behavior of these commands 116 | ;; can depend on the particular variation of special as described 117 | ;; above. 118 | ;; 119 | ;;** Exercise 1 120 | ;; 121 | ;; Let's test =f=, which is bound to `lispy-flow'. 122 | ;; 123 | ;; 1. Move into the special position: the first char of the line 124 | ;; indicated below. 125 | ;; 2. Press =f= to move the cursor to the next paren in current direction. 126 | ;; <--- special 127 | ;; 128 | ;; Hold =f= to move repeatedly until you reach "Skip a bit, Brother..." 129 | (with-output-to-string 130 | (defvar weapon "hand grenade") 131 | (let ((name "Saint Atilla")) 132 | (princ (format "And %s raised the %s up on high, saying, " name weapon))) 133 | (defun cite (str) 134 | (format "\"%s\"" str)) 135 | (princ (cite (concat 136 | "O Lord, bless this thy " 137 | weapon 138 | ", that with it thou mayst blow thine " 139 | "enemies to tiny bits, in thy mercy."))) 140 | (princ " And the Lord did grin. ") 141 | (princ "And the people did feast upon ") 142 | (princ (mapconcat (lambda (x) (format "the %ss" x)) 143 | '("lamb" "sloth" "carp" "anchovie" "orangutan" 144 | "breakfast cereal" "fruit bat") 145 | ", and ")) 146 | (princ ", and large chu...") 147 | (princ "\n\nSkip a bit, Brother...")) 148 | ;; 1. Now press =d= bound to `lispy-different' to switch to the different 149 | ;; side of the list. Press it a few times to get the feeling. 150 | ;; 151 | ;; 2. Making sure that you are at the right paren, press and hold =f= until 152 | ;; you reach "hand grenade". 153 | ;; 154 | ;; 3. Press =d= and hold =f=, making circles around the expressions until 155 | ;; you're comfortable. 156 | ;; 157 | ;; ** To move the cursor, press the =h=, =j=, =k=, =l= keys as indicated. ** 158 | ;; 159 | ;; ^ 160 | ;; k Hint: The =h= key is at the left and moves left. 161 | ;; < h l > The =l= key is at the right and moves right. 162 | ;; j The =j= key looks like a down arrow. 163 | ;; v 164 | ;; 165 | ;; Note that if it's impossible to move, the commands will fail silently. 166 | ;; In common code situations, if =j= doesn't work, it means that you've 167 | ;; reached the last expression of the parent list. The typical follow-up is 168 | ;; on =h= followed by either =j= or =k=. 169 | ;; 170 | ;; When on an outline, =j= and =k= are equivalent to =J= and =K=, respectively. 171 | ;; 172 | ;; Use your knowledge of =f= and =d= to setup the point in various places 173 | ;; and see what the arrow keys do there. Remember, the arrow keys will 174 | ;; behave differently depending on the side of the expression your cursor 175 | ;; is at. 176 | ;; 177 | ;; <--- To end the lesson, move the point here and press =W= (`widen'). 178 | ;;* Lesson 1.3: EXITING AND ENTERING SPECIAL 179 | ;; 180 | ;; There are a few ways to exit special. 181 | ;; If you're on an opener, closer, or comment character, just press 182 | ;; =C-f= or =C-n= or =C-p= or anything that will move your cursor away 183 | ;; from the special condition character. 184 | ;; 185 | ;;** Exercise 1: 186 | ;; 187 | ;; Let's quickly try exiting special. 188 | ;; 189 | ;; Hold =f= to move repeatedly until you reach "Skip a bit, Brother..." 190 | (with-output-to-string 191 | (defvar weapon "hand grenade") 192 | (let ((name "Saint Atilla")) 193 | (princ (format "And %s raised the %s up on high, saying, " name weapon))) 194 | (defun cite (str) 195 | (format "\"%s\"" str)) 196 | (princ (cite (concat 197 | "O Lord, bless this thy " 198 | weapon 199 | ", that with it thou mayst blow thine " 200 | "enemies to tiny bits, in thy mercy."))) 201 | (princ " And the Lord did grin. ") 202 | (princ "And the people did feast upon ") 203 | (princ (mapconcat (lambda (x) (format "the %ss" x)) 204 | '("lamb" "sloth" "carp" "anchovie" "orangutan" 205 | "breakfast cereal" "fruit bat") 206 | ", and ")) 207 | (princ ", and large chu...") 208 | (princ "\n\nSkip a bit, Brother...")) 209 | ;; Now press =C-f= to exit special mode. 210 | ;; You'll notice that pressing =f= now just self-inserts the character. 211 | ;; 212 | ;; Proceed to the next outline, LOCAL AND GLOBAL BINDINGS. 213 | ;; 214 | ;;** LOCAL AND GLOBAL BINDINGS 215 | ;; 216 | ;; You've seen how lispy's short bindings provide a concise way to 217 | ;; navigate lisp code, but they can only be used when the point is 218 | ;; special. These bindings are referred to as 'local'. 219 | ;; 220 | ;; Def. 6: Local - Bindings that only work in special. 221 | ;; 222 | ;; Lispy provides other bindings that can be used anytime when `lispy-mode' 223 | ;; is active. These bindings are referred to as 'global'. 224 | ;; 225 | ;; Def. 7: Global - Bindings that work anywhere, regardless of the 226 | ;; point location. 227 | ;; 228 | ;; Proceed to the next exercise. 229 | ;; 230 | ;;** Exercise 2: 231 | ;; 232 | ;; Let's experiment with some lispy globals. Below are some global 233 | ;; bindings that are particularly useful for entering special: 234 | ;; 235 | ;; - =[= calls `lispy-backward': when not in special, this command moves to the 236 | ;; left paren of the containing list. Otherwise, its behavior is similar to 237 | ;; the Emacs command `backward-list'. 238 | ;; 239 | ;; - =]= calls `lispy-forward': when not in special, this command moves to the 240 | ;; right paren of the containing list. Otherwise, its behavior is similar to 241 | ;; the Emacs command `forward-list'. 242 | ;; 243 | ;; - =M-m= calls `lispy-mark-symbol': This command marks the current symbol with 244 | ;; the region. When the region is already active, try to extend it by one 245 | ;; symbol. 246 | ;; 247 | ;; Hold =f= to move repeatedly until you reach "Skip a bit, Brother..." 248 | (with-output-to-string 249 | (defvar weapon "hand grenade") 250 | (let ((name "Saint Atilla")) 251 | (princ (format "And %s raised the %s up on high, saying, " name weapon))) 252 | (defun cite (str) 253 | (format "\"%s\"" str)) 254 | (princ (cite (concat 255 | "O Lord, bless this thy " 256 | weapon 257 | ", that with it thou mayst blow thine " 258 | "enemies to tiny bits, in thy mercy."))) 259 | (princ " And the Lord did grin. ") 260 | (princ "And the people did feast upon ") 261 | (princ (mapconcat (lambda (x) (format "the %ss" x)) 262 | '("lamb" "sloth" "carp" "anchovie" "orangutan" 263 | "breakfast cereal" "fruit bat") 264 | ", and ")) 265 | (princ ", and large chu...") 266 | (princ "\n\nSkip a bit, Brother...")) 267 | ;; Press =C-f= to exit special and move forward one character. 268 | ;; Edit the text in the string to your liking and then press =[= twice. 269 | ;; 270 | ;; Your point should be on the previous expression and it should be special. 271 | ;; Verify by moving around with =j= and =k=. 272 | ;; 273 | ;; Now navigate your point so you're on the expression with ", and large chu..." 274 | ;; Press =C-f= again to exit special. 275 | ;; Edit the string to your liking and then press =]= twice. 276 | ;; 277 | ;; Your point should now be at the end of the last expression. 278 | ;; 279 | ;; Take some time to experiment with =[= and =]= and the commands you've learned 280 | ;; previously. 281 | ;; 282 | ;; When you're finished, place your cursor back on "Skip a bit, Brother...". 283 | ;; Press =C-f= to exit special. 284 | ;; Press =M-m=. This should mark the current symbol `princ'. 285 | ;; 286 | ;; Marking will be covered in more detail later, but for now, take some time to 287 | ;; experiment with =M-m= then proceed to Lesson 1.3. 288 | ;; 289 | ;;* Lesson 1.4: LISP EDITING - DELETION 290 | ;; 291 | ;; To delete things, first you need to have a lot of them. 292 | ;; 293 | ;; 1. You can generate a lot of code by moving the point before 294 | ;; (with-output-to-string and pressing =c= bound to `lispy-clone' a 295 | ;; few times. 296 | ;; 297 | ;; 2. Now, press =C-d= bound to `lispy-delete' to delete the whole 298 | ;; sexp. Do this until you have only two top-level sexps left. 299 | ;; 300 | ;; 3. Press =f= to move inside the top-level sexp and hold =C-d= until 301 | ;; everything inside is deleted. Note how the top-level sexp is 302 | ;; deleted after all its children are gone. 303 | ;; 304 | ;; 4. Make another clone with =c= and switch to the different side 305 | ;; with =d=. Do a single =f= and practice pressing =DEL= bound to 306 | ;; `lispy-delete-backward'. 307 | ;; 308 | ;; 5. Use conventional means to position the point at either side of 309 | ;; the string and the quote and test what both =C-d= and =DEL= do. 310 | (with-output-to-string 311 | (defvar weapon "hand granade") 312 | (let ((name "Saint Atilla")) 313 | (princ (format "And %s raised the %s up on high, saying, " name weapon))) 314 | (defun cite (str) 315 | (format "\"%s\"" str)) 316 | (princ (cite (concat 317 | "O Lord, bless this thy " 318 | weapon 319 | ", that with it thou mayst blow thine " 320 | "enemies to tiny bits, in thy mercy."))) 321 | (princ " And the Lord did grin. ") 322 | (princ "And the people did feast upon ") 323 | (princ (mapconcat (lambda (x) (format "the %ss" x)) 324 | '("lamb" "sloth" "carp" "anchovie" "orangutan" 325 | "breakfast cereal" "fruit bat") 326 | ", and ")) 327 | (princ ", and large chu...") 328 | (princ "\n\nSkip a bit, Brother...")) 329 | ;;* Lesson 1.5: LISP EDITING - INSERTION 330 | ;; 331 | ;; Basic insertion bindings: 332 | ;; 333 | ;; =(= - inserts () and goes backward one char in code, self-inserts 334 | ;; in comments and strings. 335 | ;; ={= - inserts {} and goes backward one char everywhere 336 | ;; =}= - inserts [] and goes backward one char everywhere 337 | ;; ="= - inserts "" and goes backward one char, auto-quotes in strings 338 | ;; 339 | ;; All four of the pairs above will try to add once space where 340 | ;; appropriate, so to insert "(list ())", press =(list(=. 341 | ;; 342 | ;; If you want any of the mentined keys to self-insert once, remember 343 | ;; that prefixing any key with =C-q= will do that. 344 | ;; 345 | ;; All four pairs will wrap the current thing when the region is active. 346 | ;; All four pairs will wrap the current symbol when prefixed with =C-u=. 347 | ;; 348 | ;;* Lesson 1.6: LISP EDITING - APPENDING 349 | ;; 350 | ;; You can append the current list: 351 | ;; 352 | ;; - from the front with =2 SPC= 353 | ;; - from the back with =3 SPC= 354 | ;; - from the back with a newline =4 SPC= 355 | ;;* Lesson 1.7: OUTLINE NAVIGATION 356 | ;; 357 | ;; Here, several bindings depend on conditions additional to being in 358 | ;; special. Some of them are a superset of others, for example 359 | ;; COMMENT is a superset of OUTLINE, since all outlines are comments. 360 | ;; 361 | ;; When at COMMENT, you can: 362 | ;; 363 | ;; - navigate to the next outline with =j=, 364 | ;; - navigate to the previous outline with =k=. 365 | ;; - navigate to the first code paren of the outline with =f=. 366 | ;; 367 | ;; When at OUTLINE, you can: 368 | ;; 369 | ;; - fold and unfold the outline with =i=, 370 | ;; - promote the outline with =h=, 371 | ;; - demote the outline with =l=, 372 | ;; - recenter / undo recenter with =v=, 373 | ;; - create a new outline with =a=, 374 | ;; - move to the end of the oneline heading with =t=, 375 | ;; - narrow to the outline with =N=, 376 | ;; - widen with =W=. 377 | ;; 378 | ;; Anywhere, you can: 379 | ;; 380 | ;; - navigate to the next outline with =J=, 381 | ;; - navigate to the previous outline with =K=, 382 | ;; - toggle folding to all outlines of level 1 with =I=, 383 | ;; - get the table of contents with a prefix argument and =I=, e.g. =2I=, 384 | ;; 385 | ;; Also note that =I= has a global alias =S-TAB=, which you can use 386 | ;; even when not is special. It mirrors the popular `org-mode' 387 | ;; package, with the difference that it's a two-way toggle instead of 388 | ;; three-way. You can get the third option with a prefix argument. 389 | ;;* Lesson 1.8: MOVING THE REGION 390 | -------------------------------------------------------------------------------- /mypy.ini: -------------------------------------------------------------------------------- 1 | [mypy] 2 | # disallow_untyped_defs = True 3 | color_output = False 4 | 5 | [mypy-jedi.*] 6 | ignore_missing_imports = True 7 | 8 | [mypy-__builtin__] 9 | ignore_missing_imports = True -------------------------------------------------------------------------------- /targets/check-declare.el: -------------------------------------------------------------------------------- 1 | (require 'check-declare) 2 | (setq check-declare-ext-errors t) 3 | (setq files '("lispy.el" 4 | "lispy-inline.el" 5 | "le-clojure.el" 6 | "le-scheme.el" 7 | "le-lisp.el")) 8 | (add-to-list 'load-path 9 | (concat (file-name-directory 10 | (locate-library "slime")) 11 | "contrib/")) 12 | (require 'slime-repl) 13 | 14 | (defun find-library-file (lib) 15 | (save-window-excursion 16 | (let ((buf (find-library lib))) 17 | (when buf 18 | (buffer-file-name buf))))) 19 | 20 | 21 | (let ((sly-file (find-library-file "sly"))) 22 | (when sly-file 23 | (add-to-list 24 | 'load-path 25 | (expand-file-name 26 | "contrib" (file-name-directory sly-file))))) 27 | 28 | (apply #'check-declare-files files) 29 | -------------------------------------------------------------------------------- /targets/checkdoc.el: -------------------------------------------------------------------------------- 1 | (setq files '("lispy.el" 2 | "lispy-inline.el" 3 | "le-clojure.el" 4 | "le-scheme.el" 5 | "le-lisp.el")) 6 | (require 'checkdoc) 7 | (dolist (file files) 8 | (checkdoc-file file)) 9 | -------------------------------------------------------------------------------- /targets/compile.el: -------------------------------------------------------------------------------- 1 | (setq files '("lispy.el" 2 | "lispy-inline.el" 3 | "le-clojure.el" 4 | "le-scheme.el" 5 | "le-lisp.el" 6 | "le-python.el" 7 | "le-racket.el")) 8 | (setq byte-compile--use-old-handlers nil) 9 | (mapc #'byte-compile-file files) 10 | -------------------------------------------------------------------------------- /targets/install-deps.el: -------------------------------------------------------------------------------- 1 | (defconst lispy-dev-packages 2 | '(iedit 3 | multiple-cursors 4 | cider 5 | company 6 | spiral 7 | slime 8 | sly 9 | geiser 10 | clojure-mode 11 | swiper 12 | hydra 13 | ace-window 14 | helm 15 | projectile 16 | find-file-in-project 17 | undercover 18 | zoutline)) 19 | 20 | (defun package-install-packages (packages) 21 | (setq melpa-stable (getenv "MELPA_STABLE")) 22 | (setq package-user-dir 23 | (expand-file-name 24 | (format "~/.elpa/%s/elpa" 25 | (concat emacs-version (when melpa-stable "-stable"))))) 26 | (message "installing in %s ...\n" package-user-dir) 27 | (package-initialize) 28 | (setq package-archives 29 | (list (if melpa-stable 30 | '("melpa-stable" . "https://stable.melpa.org/packages/") 31 | '("melpa" . "http://melpa.org/packages/")) 32 | '("gnu" . "http://elpa.gnu.org/packages/"))) 33 | (package-refresh-contents) 34 | 35 | (dolist (package packages) 36 | (if (package-installed-p package) 37 | (message "%S: OK" package) 38 | (condition-case nil 39 | (progn 40 | (package-install package) 41 | (message "%S: OK" package)) 42 | (error 43 | (message "%S: FAIL" package))))) 44 | 45 | (save-window-excursion 46 | (package-list-packages t) 47 | (condition-case nil 48 | (progn 49 | (package-menu-mark-upgrades) 50 | (package-menu-execute t)) 51 | (error 52 | (message "All packages up to date"))))) 53 | 54 | (defun straight-install-packages (packages) 55 | (defvar bootstrap-version) 56 | (let ((bootstrap-file 57 | (expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory)) 58 | (bootstrap-version 5)) 59 | (message "user-emacs-directory: %S" user-emacs-directory) 60 | (unless (file-exists-p bootstrap-file) 61 | (with-current-buffer 62 | (url-retrieve-synchronously 63 | "https://raw.githubusercontent.com/raxod502/straight.el/develop/install.el" 64 | 'silent 'inhibit-cookies) 65 | (goto-char (point-max)) 66 | (eval-print-last-sexp))) 67 | (load bootstrap-file nil 'nomessage)) 68 | 69 | (dolist (package packages) 70 | (straight-use-package package))) 71 | 72 | (straight-install-packages lispy-dev-packages) 73 | -------------------------------------------------------------------------------- /targets/interactive-init.el: -------------------------------------------------------------------------------- 1 | ;;* Base 2 | (require 'lispy) 3 | (dolist (h '(emacs-lisp-mode-hook 4 | lisp-interaction-mode-hook 5 | clojure-mode-hook 6 | scheme-mode-hook 7 | lisp-mode-hook)) 8 | (add-hook h #'lispy-mode)) 9 | (ivy-mode) 10 | (defadvice save-buffers-kill-emacs (around no-query-kill-emacs activate) 11 | "Prevent annoying \"Active processes exist\" query when you quit Emacs." 12 | (lispy-flet (process-list ()) ad-do-it)) 13 | 14 | ;;* Common Lisp 15 | (setq inferior-lisp-program "sbcl") 16 | (setq slime-contribs '(slime-fancy)) 17 | (setq lispy-use-sly nil) 18 | ;; SLIME and SLY modify this hook even before they're required. Yuck. 19 | (setq lisp-mode-hook '(lispy-mode)) 20 | 21 | ;;* Clojure 22 | (require 'clojure-semantic nil t) 23 | -------------------------------------------------------------------------------- /targets/tlc.clj: -------------------------------------------------------------------------------- 1 | (defn disable-illegal-access-warnings [] 2 | (let [the-unsafe (. (Class/forName "sun.misc.Unsafe") getDeclaredField "theUnsafe") 3 | u (do 4 | (. the-unsafe setAccessible true) 5 | (. the-unsafe get nil)) 6 | cls (Class/forName "jdk.internal.module.IllegalAccessLogger") 7 | logger (. cls getDeclaredField "logger")] 8 | (. u putObjectVolatile cls (. u staticFieldOffset logger) nil))) 9 | 10 | (defn print-versions [] 11 | (println (str "Clojure: " (clojure-version))) 12 | (println (str "JVM: " (System/getProperty "java.version")))) 13 | 14 | (print-versions) 15 | (disable-illegal-access-warnings) 16 | (load-file "lispy-clojure.clj") 17 | (load-file "lispy-clojure-test.clj") 18 | -------------------------------------------------------------------------------- /test/test_lispy-python.py: -------------------------------------------------------------------------------- 1 | import io 2 | import os 3 | import re 4 | import ast 5 | import sys 6 | from importlib.machinery import SourceFileLoader 7 | from contextlib import redirect_stdout 8 | from textwrap import dedent 9 | 10 | lp = SourceFileLoader("lispy-python", os.path.dirname(__file__) + "/../lispy-python.py").load_module() 11 | 12 | def print_elisp_to_str(obj): 13 | with io.StringIO() as buf, redirect_stdout(buf): 14 | lp.print_elisp(obj) 15 | return buf.getvalue().strip() 16 | 17 | 18 | def with_output_to_string(code): 19 | with io.StringIO() as buf, redirect_stdout(buf): 20 | exec(code) 21 | return buf.getvalue().strip() 22 | 23 | def lp_eval(code, env={}): 24 | return with_output_to_string(f"__res__=lp.eval_code('''{code}''', {env})") 25 | 26 | def test_exec(): 27 | # Need to run this with globals(). 28 | # Otherwise, x will not be defined later 29 | exec("x=1", globals()) 30 | assert x == 1 31 | 32 | def test_tr_returns_1(): 33 | code_1 = dedent(""" 34 | x=1 35 | y=2 36 | return x + y""") 37 | parsed = ast.parse(code_1).body 38 | assert len(parsed) == 3 39 | translated = lp.tr_returns(parsed) 40 | assert len(translated) == 3 41 | assert parsed[0] == translated[0] 42 | assert parsed[1] == translated[1] 43 | assert ast.unparse(translated[2]) == "return locals() | {'__return__': x + y}" 44 | code = ast.unparse(lp.wrap_return(translated)) 45 | exec(code, globals()) 46 | assert x == 1 47 | assert y == 2 48 | assert __return__ == 3 49 | 50 | def test_tr_returns_2(): 51 | code_2 = dedent(""" 52 | if os.environ.get("FOO"): 53 | return 0 54 | x = 1 55 | y = 2 56 | return x + y""") 57 | parsed = ast.parse(code_2).body 58 | assert len(parsed) == 4 59 | translated = lp.tr_returns(parsed) 60 | assert len(translated) == 4 61 | assert ast.unparse(translated[3]) == "return locals() | {'__return__': x + y}" 62 | assert ast.unparse(translated[0].body) == "return locals() | {'__return__': 0}" 63 | code = ast.unparse(lp.wrap_return(translated)) 64 | exec(code, globals()) 65 | assert x == 1 66 | assert y == 2 67 | assert __return__ == 3 68 | os.environ["FOO"] = "BAR" 69 | exec(code, globals()) 70 | assert __return__ == 0 71 | 72 | def test_translate_def(): 73 | code = dedent(""" 74 | def add(x, y): 75 | return x + y 76 | """) 77 | tr = lp.translate(code) 78 | assert len(tr) == 1 79 | assert isinstance(tr[0], ast.FunctionDef) 80 | r = lp.eval_code(code) 81 | assert r["res"] == "'unset'" 82 | assert "" \\)', r) 227 | --------------------------------------------------------------------------------