├── .ert-runner ├── .gitignore ├── Cask ├── README.md ├── evalator-config.el ├── evalator-context.el ├── evalator-elisp.el ├── evalator-faces.el ├── evalator-history.el ├── evalator-key-map.el ├── evalator-state.el ├── evalator-utils.el ├── evalator.el ├── example-gifs ├── practical.gif └── walkthrough.gif └── test ├── evalator-config-test.el ├── evalator-context-test.el ├── evalator-elisp-test.el ├── evalator-history-test.el ├── evalator-state-test.el ├── evalator-test.el ├── evalator-utils-test.el └── test-helper.el /.ert-runner: -------------------------------------------------------------------------------- 1 | -L ./ -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | test/helm -------------------------------------------------------------------------------- /Cask: -------------------------------------------------------------------------------- 1 | (source gnu) 2 | (source melpa) 3 | 4 | (package-file "evalator.el") 5 | 6 | (files "*.el") 7 | 8 | (depends-on "helm-core" "1.9.1") 9 | 10 | (development 11 | (depends-on "f") 12 | (depends-on "ecukes") 13 | (depends-on "ert-runner") 14 | (depends-on "el-mock") 15 | (depends-on "noflet") 16 | ) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![MELPA](https://melpa.org/packages/evalator-badge.svg)](https://melpa.org/#/evalator) 2 | [![MELPA Stable](http://stable.melpa.org/packages/evalator-badge.svg)](http://stable.melpa.org/#/evalator) 3 | 4 | 5 | # EVALATOR # 6 | 7 | ## Introduction ## 8 | 9 | Evalator is a new kind of REPL for Emacs. 10 | 11 | What makes it different from a normal REPL? With evalator you can: 12 | 13 | * Evaluate expressions using any language* 14 | * See the results of an expression as it is typed 15 | * Transform data in a stepwise fashion 16 | * Generate code 17 | * Interact with Emacs using a language other than Elisp 18 | 19 | * Provided you've installed the appropriate extension package. 20 | 21 | ### Language Support ### 22 | By default, evalator uses Elisp as its evaluation language, but it 23 | supports other languages through extension packages. These packages 24 | define an evaluation context that can be passed to the ```evalator``` 25 | command in order to set the evaluation language for that particular 26 | session. 27 | 28 | #### Currently available evaluation contexts #### 29 | * Clojure - via [evalator-clojure](https://github.com/seanirby/evalator-clojure) 30 | 31 | Don't see a context package for your favorite language? Write one! 32 | See 33 | [evalator-elisp.el](https://github.com/seanirby/evalator/blob/master/evalator-elisp.el) 34 | and 35 | [evalator-clojure.el](https://github.com/seanirby/evalator-clojure/blob/master/evalator-clojure.el) 36 | for examples. Open an issue in this repo with any questions and I'll 37 | be happy to help. 38 | 39 | Here's a practical example to demonstrate what evalator's capable of: 40 | 41 | ### A Practical Example ### 42 | Suppose you have several Emacs packages and want to generate an elisp 43 | configuration file for each one in your current directory. Each file 44 | should be named ```setup-$PACKAGE.el``` with ```$PACKAGE``` equaling 45 | the package name. Each file should contain the following line. 46 | 47 | ``` 48 | (provide 'setup-$PACKAGE) 49 | ``` 50 | 51 | Watch the example below to see how easy this is with evalator. 52 | 53 | ![a-practical-example](example-gifs/practical.gif) 54 | 55 | Here's what happened: 56 | 57 | * Copy all package names 58 | * Call ```M-x evalator``` to start an evalator session 59 | * Paste the package names into a lisp list ```'()``` 60 | * Build a string for the shell command I want executed for each package 61 | * Call the Emacs command ```shell-command``` with each of the strings from before 62 | * Exit evalator using C-g 63 | * Verify the files are created with the right contents 64 | 65 | See the [Basic Usage](#basic-usage) section for a detailed 66 | walkthrough. 67 | 68 | ## Installation ## 69 | 70 | Evalator is available through the [MELPA](http://melpa.org/) 71 | repository. You can install it by using Emacs's built-in package 72 | manager ```package.el```. 73 | 74 | If you haven't configured ```package.el```, see 75 | [here](https://github.com/milkypostman/melpa#usage) for a short guide 76 | on setting it up to use MELPA. 77 | 78 | Once configured, you can run ```M-x package-install``` then enter 79 | ```evalator``` to install evalator. 80 | 81 | ## Setup ## 82 | 83 | ### Minimal ### 84 | 85 | Add the following to your init file to setup key bindings for the 86 | primary evalator commands. 87 | 88 | ``` 89 | ;;Suggested keybindings 90 | (global-set-key (kbd "C-c e e") 'evalator) 91 | (global-set-key (kbd "C-c e x") 'evalator-explicit) 92 | (global-set-key (kbd "C-c e r") 'evalator-resume) 93 | (global-set-key (kbd "C-c e i") 'evalator-insert-equiv-expr) 94 | ``` 95 | 96 | ### Auto Detect Context### 97 | 98 | If you've installed packages that provide other evalaution context's 99 | for evalator, you may want it to automatically detect what evalaution 100 | context to use. For example, if you're in a buffer using 101 | ```ruby-mode``` then you'd like your ```evalator``` session to use 102 | Ruby. Likewise, if you're in a buffer using ```clojure-mode``` then 103 | use Clojure. 104 | 105 | ``` 106 | (setq evalator-config-mode-context-alist nil) 107 | (add-to-list 'evalator-config-mode-context-alist '(ruby-mode . evalator-ruby-context)) 108 | (add-to-list 'evalator-config-mode-context-alist '(clojure-mode . evalator-clojure-context)) 109 | ``` 110 | 111 | ```evalator-config-mode-context-alist``` is an association list(alist) of 112 | pairs with the following form: 113 | 114 | ```'(MAJOR-MODE . CONTEXT)``` 115 | 116 | ```evalator``` uses this alist in determining what context to use for 117 | a session. If a context wasn't passed explicity in a call to 118 | ```evalator``` or ```evalator-explicit```, then the current buffer's 119 | major-mode is searched for within the alist. If an entry is found, the 120 | context associated with that major-mode is used for the session. 121 | Otherwise an elisp evaluation context is used. 122 | 123 | ## Basic Usage ## 124 | 125 | ![walkthrough](example-gifs/walkthrough.gif) 126 | 127 | ### Basic Usage ### 128 | 129 | *Help is available anytime in an evalator session by running C-h 130 | m*. This will open a buffer containg help for evalator followed 131 | by help for helm. 132 | 133 | Start evalator by typing ```M-x evalator```. A helm buffer will be 134 | opened with a prompt to enter an expression. Enter a valid expression 135 | to generate the initial "candidates". A candidate is simply a result 136 | that is displayed. We call them candidates to stay consistent with 137 | helm, which evalator uses as a front-end. Once you've entered a valid 138 | expression press RET to confirm your expression. 139 | 140 | Evalator provides immediate feedback by attempting to evaluate your 141 | expression as it is typed. As soon as you've entered a valid 142 | expression the candidates are updated to show what their new value 143 | will be if you confirm your expression. Your expression prompt also 144 | will change color from red to green to indicate its validity. 145 | 146 | Obviously you can only confirm your expression when the expression is 147 | valid. If you try confirming an invalid expression the error message 148 | is displayed in the echo area. 149 | 150 | You can further transform the candidates you've generated by typing 151 | more expressions. Subsequent expressions are executed on each 152 | candidate. That is evalator's behavior in normal mode. You may 153 | refer to the current candidate by inserting the special arg character 154 | into your expression by pressing C-c ;. 155 | 156 | Modes and special args will be explained in more detail in the 157 | following sections. 158 | 159 | #### Modes #### 160 | You can start an evalator session with either ```M-x evalator``` or 161 | ```M-x evalator-explicit```. These commands start an evalator session 162 | in either normal mode or explicit mode, respectively. 163 | 164 | To explain the difference between these two modes, we first need to 165 | define what a "candidate" is. If evalator was started in normal mode 166 | (```M-x evalator```) and the result of an expression is an atomic 167 | value like an integer or string, then the result will be displayed as 168 | a single candidate. If the result is a collection like a list or 169 | vector, then each of the elements of that collection are displayed as 170 | a separate candidate. 171 | 172 | If evalator was started in explicit mode (```M-x evalator-explicit```) 173 | then the result is always displayed as a single candidate. One 174 | benefit to using explicit mode is that you can generate the equivalent 175 | code of a session using ```M-x evalator-insert-equiv-expr``` and 176 | insert that into a buffer*. 177 | 178 | So how do you choose what mode to use? 179 | 180 | Use normal mode if you want to transform collection data and you don't 181 | care about code generation. 182 | 183 | Use explicit mode otherwise. 184 | 185 | * The implementation of ```evalator-insert-equiv-expr``` is 186 | pretty naive currently so it's output may be sub-optimal if you used 187 | numbered special args like ```Ⓔ0``` in any of your expressions. 188 | 189 | #### Special Args #### 190 | 191 | When writing an expression in evalator, you use a special arg to refer 192 | to a particular candidate. You can easily insert this arg into the 193 | the expression prompt by pressing C-c ;, which is the key 194 | binding for ```evalator-action-insert-special-arg```. This arg is the 195 | character ```Ⓔ``` by default. If the last element was a collection 196 | then adding an integer N after the special arg, causes the Nth element 197 | from that candidate to be substituted. 198 | 199 | Here's what you would add to make the elisp context use ```@``` as 200 | its special arg: 201 | 202 | ``` 203 | (setq evalator-elisp-special-arg "@") 204 | ``` 205 | 206 | Or you could change the default special arg globally with the 207 | following: 208 | ``` 209 | (setq evalator-context-special-arg-default "@") 210 | ``` 211 | 212 | ### Public API ### 213 | 214 | The commands below can be bound to a shortcut or executed using ```M-x``` 215 | 216 | ```evalator``` - Starts an evalator session 217 | 218 | ```evalator-explicit``` - Starts an evalator session in explicit mode 219 | 220 | ```evalator-resume``` - Resumes last evalator session 221 | 222 | ```evalator-insert-equiv-expr``` - Inserts the equivalent expression of the last evalator session into the current buffer. NOTE: The last session must have been run in explicit mode for this to work. 223 | 224 | 225 | ### Key Actions ### 226 | Below is a table of evalator specific key actions that can be used 227 | within the evalator session. For helm specific commands such as 228 | candidate navigation and marking, refer to 229 | [helm's](https://github.com/emacs-helm/helm) documentation or run 230 | C-h m to open a help buffer that contains evalator help 231 | followed by helm help. 232 | 233 | Shortcut | Command and Description 234 | ------------------------------- | ----------------------- 235 | RET | ``uevalator-action-confirm-make-or-transformu``

Accepts initial candidates or transformation then updates history. If transforming candidates, the expression is evaluated on each candidate with the special arg referring to the candidate's value 236 | C-c ; C-; | ```evalator-action-insert-special-arg```

Insert's the current evaluation context's special arg. 237 | C-c C-c | ```evalator-action-confirm-transform-collection```

Accepts transformation and updates history. Expression is evaluated once awith the special arg referring to the selected candidates list 238 | C-c C-e | ```evalator-action-execute-in-elisp```

Executes the expression in Elisp on each selected candidate. The history is not updated and the candidates are not transformed 239 | C-j | ```evalator-action-next```

Goes to next history state 240 | C-l | ```evalator-action-previous```

Goes to previous history state 241 | 242 | You can exit the evalator session by quitting the minibuffer. This command is usually bound to C-g. 243 | -------------------------------------------------------------------------------- /evalator-config.el: -------------------------------------------------------------------------------- 1 | ;;; evalator-config.el --- User configuration vars for evalator 2 | ;; 3 | ;; Author: Sean Irby 4 | ;; Copyright © , Sean Irby 5 | ;; 6 | ;; This program is free software: you can redistribute it and/or modify 7 | ;; it under the terms of the GNU General Public License as published by 8 | ;; the Free Software Foundation, either version 3 of the License, or (at 9 | ;; your option) any later version. 10 | ;; 11 | ;; This program is distributed in the hope that it will be useful, but 12 | ;; WITHOUT ANY WARRANTY; without even the implied warranty of 13 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | ;; General Public License for more details. 15 | ;; 16 | ;; You should have received a copy of the GNU General Public License 17 | ;; along with GNU Emacs. If not, see . 18 | ;; 19 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 20 | ;; 21 | ;;; This file is not a part of GNU Emacs 22 | ;; 23 | ;;; Commentary: 24 | ;; 25 | ;;; Code: 26 | 27 | (defvar evalator-config-mode-context-alist nil 28 | "An association list that consists of (MAJOR-MODE . CONTEXT) pairs. 29 | 30 | When an evalator session is started, this list is used to determine 31 | the evaluation context to use. If the buffer's current major-mode 32 | matches a major-mode in this list, then the associated context will be 33 | used. 34 | ") 35 | 36 | (defvar evalator-config-prompt-f 37 | (lambda () 38 | (format "%s of %s" (+ 1 (evalator-history-index)) (length (evalator-history)))) 39 | "Var's value is a function used to generate the evalator prompt.") 40 | 41 | (provide 'evalator-config) 42 | 43 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 44 | ;;; evalator-config.el ends here 45 | -------------------------------------------------------------------------------- /evalator-context.el: -------------------------------------------------------------------------------- 1 | ;;; evalator-context.el --- Definition for evalator-context class 2 | ;; 3 | ;; Author: Sean Irby 4 | ;; Copyright © , Sean Irby 5 | ;; 6 | ;; This program is free software: you can redistribute it and/or modify 7 | ;; it under the terms of the GNU General Public License as published by 8 | ;; the Free Software Foundation, either version 3 of the License, or (at 9 | ;; your option) any later version. 10 | ;; 11 | ;; This program is distributed in the hope that it will be useful, but 12 | ;; WITHOUT ANY WARRANTY; without even the implied warranty of 13 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | ;; General Public License for more details. 15 | ;; 16 | ;; You should have received a copy of the GNU General Public License 17 | ;; along with GNU Emacs. If not, see . 18 | ;; 19 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 20 | ;; 21 | ;;; This file is not a part of GNU Emacs 22 | ;; 23 | ;;; Commentary: 24 | ;; 25 | ;;; Code: 26 | 27 | 28 | (require 'eieio) 29 | (require 'evalator-config) 30 | 31 | (defvar evalator-context-special-arg-default "Ⓔ") ;; Unicode character x24ba 32 | 33 | ;; References to data types in the docstrings below are assumed to be elisp types. 34 | (defclass evalator-context () 35 | ((name 36 | :initarg :name 37 | :custom string 38 | :documentation 39 | "Name of the evaluation context (elisp, cider, etc..)") 40 | 41 | (special-arg 42 | :initarg :special-arg 43 | :custom string 44 | :documentation 45 | "Special arg used for substitution in evalator expressions.") 46 | 47 | (init 48 | :initarg :init 49 | :custom function 50 | :documentation 51 | "() => t or nil 52 | 53 | Performs any setup needed before any context evaluation functions 54 | are called. All slot functions below are context evaluation 55 | functions. If init returns non-nil then initialization was 56 | successful and evalator can be started. Otherwise, evalator is 57 | aborted. Function should print an error message if initialization 58 | failed.") 59 | 60 | (make-equiv-expr 61 | :initarg :make-equiv-expr 62 | :custom function 63 | :documentation 64 | "(exprs) => string 65 | 66 | This function accepts a single argument, EXPRS, which is the list 67 | of expression strings used in the most recent evalator session. 68 | It should combine them and return a single expression string as a 69 | result.") 70 | 71 | (make-candidates 72 | :initarg :make-candidates 73 | :custom function 74 | :documentation 75 | "(input mode initial-p) => cons 76 | 77 | Function converts INPUT into a valid list of helm candidates. In 78 | other words, a list of the stringified representation of the 79 | input. How INPUT is converted depends on both the MODE argument 80 | and the INITIAL-P flag. 81 | 82 | If INITIAL-P is non-nil then it is assumed that INPUT came from 83 | user input and first needs to be read and evaluated to an elisp 84 | object. If INITIAL-P is nil then it is treated as an elisp 85 | object. If MODE is :explicit then the function will always return 86 | a candidate list of one element. If MODE is some other value then 87 | the function will return a candidate list equivalent to the size 88 | of the input object. That means scalars will be returned in a 89 | size 1 candidates list. Vectors and lists will be returned in a 90 | candidates list whose size is equal to the size of the 91 | collection.") 92 | 93 | (transform-candidates 94 | :initarg :transform-candidates 95 | :custom function 96 | :documentation 97 | "(cands expr-str collect-p) => cons 98 | 99 | Function accepts a list of candidates, CANDS, and transforms it 100 | according to the expression string EXPR-STR. How CANDS is 101 | transformed depends on the flag COLLECT-P. If COLLECT-P is 102 | non-nil then EXPR-STR will be evaluated on the entire CANDS list. 103 | If COLLECT-P is nil then EXPR-STR will be evaluated on each 104 | candidate in CANDS. The result is then processed so it's a valid 105 | helm candidate list then returned."))) 106 | 107 | (defmethod evalator-context-get-special-arg ((context evalator-context)) 108 | (or (eval (slot-value context :special-arg)) 109 | evalator-context-special-arg-default)) 110 | 111 | (defun evalator-context-get (&optional context) 112 | "Find an evaluation context to use for an evalator session. 113 | 114 | If CONTEXT is non-nil, then the result of calling CONTEXT's function 115 | definition will be used as the session's evaluation context. 116 | 117 | If CONTEXT is nil, then the current buffer's major mode will be 118 | searched for in `evalator-config-mode-context-alist'. If a match is found, 119 | the context associated with that major mode is used in the evalator 120 | session. If no match is found, an elisp evaluation context is used 121 | instead. 122 | " 123 | (let* ((mm (buffer-local-value 'major-mode (current-buffer))) 124 | (context-f-sym (or context 125 | (cdr (assoc mm evalator-config-mode-context-alist)) 126 | 'evalator-elisp-context)) 127 | (context-f (symbol-function context-f-sym))) 128 | (if (autoloadp context-f) 129 | (progn 130 | (autoload-do-load context-f context-f-sym) 131 | (funcall (symbol-function context-f-sym))) 132 | (funcall context-f)) 133 | context-f-sym)) 134 | 135 | (provide 'evalator-context) 136 | 137 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 138 | ;;; evalator-context.el ends here 139 | -------------------------------------------------------------------------------- /evalator-elisp.el: -------------------------------------------------------------------------------- 1 | ;;; evalator-elisp.el --- Elisp evaluation context definition for Evalator 2 | ;; 3 | ;; Author: Sean Irby 4 | ;; Copyright © , Sean Irby 5 | ;; 6 | ;; This program is free software: you can redistribute it and/or modify 7 | ;; it under the terms of the GNU General Public License as published by 8 | ;; the Free Software Foundation, either version 3 of the License, or (at 9 | ;; your option) any later version. 10 | ;; 11 | ;; This program is distributed in the hope that it will be useful, but 12 | ;; WITHOUT ANY WARRANTY; without even the implied warranty of 13 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | ;; General Public License for more details. 15 | ;; 16 | ;; You should have received a copy of the GNU General Public License 17 | ;; along with GNU Emacs. If not, see . 18 | ;; 19 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 20 | ;; 21 | ;;; This file is not a part of GNU Emacs 22 | ;; 23 | ;;; Commentary: 24 | ;; 25 | ;;; Code: 26 | 27 | 28 | (require 'cl-lib) 29 | (require 'eieio) 30 | (require 'evalator-context) 31 | 32 | (defvar evalator-elisp-special-arg nil) 33 | (defvar evalator-elisp-context nil) 34 | 35 | ;;;###autoload 36 | (defun evalator-elisp-context () 37 | (if evalator-elisp-context 38 | evalator-elisp-context 39 | (progn 40 | (setq evalator-elisp-context 41 | (make-instance 42 | 'evalator-context 43 | 44 | :name 45 | "elisp" 46 | 47 | :special-arg 48 | 'evalator-elisp-special-arg 49 | 50 | :init 51 | 'evalator-elisp-init 52 | 53 | :make-equiv-expr 54 | 'evalator-elisp-make-equiv-expr 55 | 56 | :make-candidates 57 | 'evalator-elisp-make-candidates 58 | 59 | 60 | :transform-candidates 61 | 'evalator-elisp-transform-candidates))))) 62 | 63 | (defun evalator-elisp-get-special-arg () 64 | "Return special arg from elisp context." 65 | (evalator-context-get-special-arg (evalator-elisp-context))) 66 | 67 | (defun evalator-elisp-init () 68 | "No Elisp initialization needed since this is EMACS." 69 | t) 70 | 71 | ;; This function is gross because I can't use a regex matching function 72 | ;; I'd like to, but this function is called from within a regex match handler, 73 | ;; Calling one from within another produces unexpected behavior. 74 | (defun evalator-elisp-numbered-arg-num (str) 75 | "Function initially accepts a string STR of a numbered special arg. 76 | It is called recursively until it has extracted the number following 77 | the special arg." 78 | (let* ((first-char-str (cl-subseq str 0 1)) 79 | (not-num-p (and (equal 0 (string-to-number first-char-str)) 80 | (not (equal "0" first-char-str))))) 81 | (if not-num-p 82 | (evalator-elisp-numbered-arg-num (cl-subseq str 1)) 83 | (string-to-number str)))) 84 | 85 | (defun evalator-elisp-numbered-arg-pattern (&optional quote-p) 86 | "Return the regex pattern used to match numbered special args. 87 | If QUOTE-P is non-nil then a pattern is returned that can also match a 88 | quoted numbered special arg like `'ⒺN'." 89 | (let ((frmt (if quote-p "'?%s[0-9]+" "%s[0-9]+"))) 90 | (format frmt (evalator-elisp-get-special-arg)))) 91 | 92 | (defun evalator-elisp-subst-numbered-special-args (expr-str c) 93 | "Substitute any special args of the form `ⒺN' in EXPR-STR with the Nth element in C." 94 | (let ((pattern (evalator-elisp-numbered-arg-pattern)) 95 | (match-f (lambda (m) 96 | (prin1-to-string (elt c (string-to-number (cl-subseq m 1))))))) 97 | (replace-regexp-in-string pattern match-f expr-str t t))) 98 | 99 | (defun evalator-elisp-subst-identity-special-args (expr-str c) 100 | "Substitute any special args of the form `Ⓔ' in EXPR-STR with C." 101 | (let ((sa (evalator-elisp-get-special-arg))) 102 | (replace-regexp-in-string sa (prin1-to-string c) expr-str t t))) 103 | 104 | (defun evalator-elisp-subst-special-args (expr-str c) 105 | "Substitute any special args in EXPR-STR. 106 | Identity special args like `Ⓔ' are substituted with the value of C. 107 | Numbered special args like `ⒺN' are substituted with the Nth element 108 | in C." 109 | (let* ((num-args-replaced (evalator-elisp-subst-numbered-special-args expr-str c)) 110 | (num-and-identity-args-replaced 111 | (evalator-elisp-subst-identity-special-args num-args-replaced c))) 112 | num-and-identity-args-replaced)) 113 | 114 | (defun evalator-elisp-eval (expr-str) 115 | "Evaluate the expression string, EXPR-STR." 116 | (eval (read expr-str))) 117 | 118 | (defun evalator-elisp-make-equiv-expr (exprs) 119 | "See slot documentation in evalator-context.el." 120 | (let* ((pattern-numbered (evalator-elisp-numbered-arg-pattern t)) 121 | (pattern-identity (format "'?%s" (evalator-elisp-get-special-arg))) 122 | (match-f (lambda (str m) 123 | (concat "(elt " 124 | str 125 | " " 126 | (number-to-string (evalator-elisp-numbered-arg-num m)) 127 | ")"))) 128 | (sub (lambda (expr-acc expr) 129 | (let* ((match-f (apply-partially match-f expr-acc)) 130 | 131 | (num-args-replaced 132 | (replace-regexp-in-string pattern-numbered match-f expr t)) 133 | 134 | (num-and-identity-args-replaced 135 | (replace-regexp-in-string pattern-identity expr-acc num-args-replaced t t))) 136 | num-and-identity-args-replaced)))) 137 | (cl-reduce sub exprs))) 138 | 139 | (defun evalator-elisp-make-candidates (input mode) 140 | "See slot documentation in evalator-context.el." 141 | (let* ((data (eval (read input))) 142 | (to-obj-string (lambda (x) 143 | (prin1-to-string x)))) 144 | (cond 145 | ((equal :explicit mode) (list (funcall to-obj-string data))) 146 | ((and (not (stringp data)) (sequencep data)) (mapcar to-obj-string data)) 147 | (t (list (funcall to-obj-string data)))))) 148 | 149 | (defun evalator-elisp-transform-candidates (cands expr-str collect-p) 150 | "See slot documentation in evalator-context.el." 151 | (let ((cands-xfrmd (if collect-p 152 | (list (evalator-elisp-eval 153 | (evalator-elisp-subst-special-args 154 | expr-str (mapcar 'read cands)))) 155 | (mapcar (lambda (c) 156 | (evalator-elisp-eval 157 | (evalator-elisp-subst-special-args 158 | expr-str (read c)))) cands)))) 159 | (mapcar `prin1-to-string cands-xfrmd))) 160 | 161 | (provide 'evalator-elisp) 162 | 163 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 164 | ;;; evalator-elisp.el ends here 165 | -------------------------------------------------------------------------------- /evalator-faces.el: -------------------------------------------------------------------------------- 1 | ;;; evalator-faces.el --- Evalator faces 2 | ;; 3 | ;; Author: Sean Irby 4 | ;; Copyright © , Sean Irby 5 | ;; 6 | ;; This program is free software: you can redistribute it and/or modify 7 | ;; it under the terms of the GNU General Public License as published by 8 | ;; the Free Software Foundation, either version 3 of the License, or (at 9 | ;; your option) any later version. 10 | ;; 11 | ;; This program is distributed in the hope that it will be useful, but 12 | ;; WITHOUT ANY WARRANTY; without even the implied warranty of 13 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | ;; General Public License for more details. 15 | ;; 16 | ;; You should have received a copy of the GNU General Public License 17 | ;; along with GNU Emacs. If not, see . 18 | ;; 19 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 20 | ;; 21 | ;;; This file is not a part of GNU Emacs 22 | ;; 23 | ;;; Commentary: 24 | ;; 25 | ;;; Code: 26 | 27 | 28 | (defgroup evalator-faces nil 29 | "Customize the appearance of evalator." 30 | :prefix "evalator-" 31 | :group 'faces 32 | :group 'evalator) 33 | 34 | (defface evalator-success 35 | '((((background dark)) 36 | :background "green4" 37 | :foreground "white" 38 | ) 39 | (((background light)) 40 | :background "green4" 41 | :foreground "white" 42 | )) 43 | "Face for source header in the evalator buffer." 44 | :group 'evalator-faces) 45 | 46 | (defface evalator-error 47 | '((((background dark)) 48 | :background "red4" 49 | :foreground "white" 50 | ) 51 | (((background light)) 52 | :background "red4" 53 | :foreground "white" 54 | )) 55 | "Face for source header in the evalator buffer." 56 | :group 'evalator-faces) 57 | 58 | (provide 'evalator-faces) 59 | 60 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 61 | ;;; evalator-faces.el ends here 62 | -------------------------------------------------------------------------------- /evalator-history.el: -------------------------------------------------------------------------------- 1 | ;;; evalator-history.el --- History helpers 2 | ;; 3 | ;; Author: Sean Irby 4 | ;; Copyright © , Sean Irby 5 | ;; 6 | ;; This program is free software: you can redistribute it and/or modify 7 | ;; it under the terms of the GNU General Public License as published by 8 | ;; the Free Software Foundation, either version 3 of the License, or (at 9 | ;; your option) any later version. 10 | ;; 11 | ;; This program is distributed in the hope that it will be useful, but 12 | ;; WITHOUT ANY WARRANTY; without even the implied warranty of 13 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | ;; General Public License for more details. 15 | ;; 16 | ;; You should have received a copy of the GNU General Public License 17 | ;; along with GNU Emacs. If not, see . 18 | ;; 19 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 20 | ;; 21 | ;;; This file is not a part of GNU Emacs 22 | ;; 23 | ;;; Commentary: 24 | ;; 25 | ;;; Code: 26 | 27 | 28 | (require 'cl-lib) 29 | (require 'evalator-utils) 30 | (require 'evalator-state) 31 | (require 'helm) 32 | 33 | (defun evalator-history () 34 | "Return history vector." 35 | (plist-get evalator-state :history)) 36 | 37 | (defun evalator-history-index () 38 | "Return current history index." 39 | (plist-get evalator-state :history-index)) 40 | 41 | (defun evalator-history-push! (candidates expression) 42 | "Push the CANDIDATES and EXPRESSION onto history. 43 | Increments the history index." 44 | (evalator-utils-put! evalator-state 45 | :history 46 | (vconcat (cl-subseq (evalator-history) 0 (+ 1 (evalator-history-index))) 47 | (list (list :candidates candidates :expression expression)))) 48 | (evalator-utils-put! evalator-state :history-index (+ 1 (evalator-history-index)))) 49 | 50 | (defun evalator-history-current (&optional k) 51 | "Retrieve active history element. Accepts an optional keyword K." 52 | (let ((h (elt (evalator-history) (evalator-history-index)))) 53 | (if k (plist-get h k) h))) 54 | 55 | (defun evalator-history-expression-chain () 56 | "Return a list of all expressions in history except for the first. 57 | The first expression is always an empty string so it is ignored." 58 | (cdr (mapcar 59 | (lambda (h) 60 | (plist-get h :expression)) 61 | (plist-get evalator-state :history)))) 62 | 63 | (provide 'evalator-history) 64 | 65 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 66 | ;;; evalator-history.el ends here 67 | -------------------------------------------------------------------------------- /evalator-key-map.el: -------------------------------------------------------------------------------- 1 | ;;; evalator-key-map.el --- Key map for evalator actions 2 | ;; 3 | ;; Author: Sean Irby 4 | ;; Copyright © , Sean Irby 5 | ;; 6 | ;; This program is free software: you can redistribute it and/or modify 7 | ;; it under the terms of the GNU General Public License as published by 8 | ;; the Free Software Foundation, either version 3 of the License, or (at 9 | ;; your option) any later version. 10 | ;; 11 | ;; This program is distributed in the hope that it will be useful, but 12 | ;; WITHOUT ANY WARRANTY; without even the implied warranty of 13 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | ;; General Public License for more details. 15 | ;; 16 | ;; You should have received a copy of the GNU General Public License 17 | ;; along with GNU Emacs. If not, see . 18 | ;; 19 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 20 | ;; 21 | ;;; This file is not a part of GNU Emacs 22 | ;; 23 | ;;; Commentary: 24 | ;; 25 | ;;; Code: 26 | 27 | 28 | (require 'helm) 29 | 30 | (defvar evalator-key-map 31 | (let ((map (make-sparse-keymap))) 32 | (define-key map (kbd "RET") 'evalator-action-confirm-make-or-transform) 33 | (define-key map (kbd "C-c ;") 'evalator-action-insert-special-arg) 34 | (define-key map (kbd "C-;") 'evalator-action-insert-special-arg) 35 | (define-key map (kbd "C-c C-c") 'evalator-action-confirm-transform-collect) 36 | (define-key map (kbd "C-c C-e") 'evalator-action-execute-in-elisp) 37 | (define-key map (kbd "C-j") 'evalator-action-next) 38 | (define-key map (kbd "C-l") 'evalator-action-previous) 39 | map)) 40 | 41 | (provide 'evalator-key-map) 42 | 43 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 44 | ;;; evalator-key-map.el ends here 45 | -------------------------------------------------------------------------------- /evalator-state.el: -------------------------------------------------------------------------------- 1 | ;;; evalator-state.el --- evalator-state var and helpers 2 | ;; 3 | ;; Author: Sean Irby 4 | ;; Copyright © , Sean Irby 5 | ;; 6 | ;; This program is free software: you can redistribute it and/or modify 7 | ;; it under the terms of the GNU General Public License as published by 8 | ;; the Free Software Foundation, either version 3 of the License, or (at 9 | ;; your option) any later version. 10 | ;; 11 | ;; This program is distributed in the hope that it will be useful, but 12 | ;; WITHOUT ANY WARRANTY; without even the implied warranty of 13 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | ;; General Public License for more details. 15 | ;; 16 | ;; You should have received a copy of the GNU General Public License 17 | ;; along with GNU Emacs. If not, see . 18 | ;; 19 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 20 | ;; 21 | ;;; This file is not a part of GNU Emacs 22 | ;; 23 | ;;; Commentary: 24 | ;; 25 | ;;; Code: 26 | 27 | 28 | (require 'helm) 29 | (require 'evalator-utils) 30 | (require 'evalator-context) 31 | (require 'evalator-elisp) 32 | 33 | (defvar evalator-state-default (list :context 'evalator-elisp-context 34 | :mode :normal 35 | :history [] 36 | :history-index -1)) 37 | 38 | (defvar evalator-state (copy-sequence evalator-state-default)) 39 | 40 | (defun evalator-state-init (&optional mode context) 41 | "Initialize the `evalator-state' var. 42 | 43 | First, `evalator-state-default' is copied to `evalator-state'. 44 | Then, the state's `:mode' is set to MODE if MODE is non-nil. 45 | Finally the function defined in the context's `:init' slot is called 46 | to perform any context specific initialization. 47 | " 48 | (evalator-elisp-context) ;;Initialize elisp context if not already. 49 | (setq evalator-state (copy-sequence evalator-state-default)) 50 | (evalator-utils-put! evalator-state :context (evalator-context-get context)) 51 | (when mode 52 | (evalator-utils-put! evalator-state :mode mode)) 53 | (funcall (slot-value (evalator-state-context) :init))) 54 | 55 | (defun evalator-state-context () 56 | "Return the state's context object." 57 | (funcall (plist-get evalator-state :context))) 58 | 59 | (provide 'evalator-state) 60 | 61 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 62 | ;;; evalator-state.el ends here 63 | -------------------------------------------------------------------------------- /evalator-utils.el: -------------------------------------------------------------------------------- 1 | ;;; evalator-utils.el --- Utilities for evalator requiring no dependencies 2 | ;; 3 | ;; Author: Sean Irby 4 | ;; Copyright © , Sean Irby 5 | ;; 6 | ;; This program is free software: you can redistribute it and/or modify 7 | ;; it under the terms of the GNU General Public License as published by 8 | ;; the Free Software Foundation, either version 3 of the License, or (at 9 | ;; your option) any later version. 10 | ;; 11 | ;; This program is distributed in the hope that it will be useful, but 12 | ;; WITHOUT ANY WARRANTY; without even the implied warranty of 13 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | ;; General Public License for more details. 15 | ;; 16 | ;; You should have received a copy of the GNU General Public License 17 | ;; along with GNU Emacs. If not, see . 18 | ;; 19 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 20 | ;; 21 | ;;; This file is not a part of GNU Emacs 22 | ;; 23 | ;;; Commentary: 24 | ;; 25 | ;;; Code: 26 | 27 | 28 | (defun evalator-utils-put! (plst p v) 29 | "Set PLST property P with value V." 30 | (setq plst (plist-put plst p v))) 31 | 32 | (defun evalator-utils-get-file-string (filepath) 33 | "Return contents of file at FILEPATH." 34 | (with-temp-buffer 35 | (insert-file-contents filepath) 36 | (buffer-string))) 37 | 38 | (provide 'evalator-utils) 39 | 40 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 41 | ;;; evalator-utils.el ends here 42 | -------------------------------------------------------------------------------- /evalator.el: -------------------------------------------------------------------------------- 1 | ;;; evalator.el --- Package for interactive transformation of data with helm 2 | ;; 3 | ;; Copyright © , Sean Irby 4 | ;; Author: Sean Irby 5 | ;; Maintainer: Sean Irby 6 | ;; URL: http://www.github.com/seanirby/evalator 7 | ;; Version: 1.0.0 8 | ;; Keywords: languages, elisp, helm 9 | ;; Package-Requires: ((helm-core "1.9.1")) 10 | ;; 11 | ;; This program is free software: you can redistribute it and/or modify 12 | ;; it under the terms of the GNU General Public License as published by 13 | ;; the Free Software Foundation, either version 3 of the License, or (at 14 | ;; your option) any later version. 15 | ;; 16 | ;; This program is distributed in the hope that it will be useful, but 17 | ;; WITHOUT ANY WARRANTY; without even the implied warranty of 18 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 19 | ;; General Public License for more details. 20 | ;; 21 | ;; You should have received a copy of the GNU General Public License 22 | ;; along with GNU Emacs. If not, see . 23 | ;; 24 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 25 | ;; 26 | ;;; This file is not a part of GNU Emacs 27 | ;; 28 | ;;; Commentary: 29 | ;; 30 | ;;; Code: 31 | 32 | (require 'cl-lib) 33 | (require 'evalator-config) 34 | (require 'evalator-context) 35 | (require 'evalator-elisp) 36 | (require 'evalator-faces) 37 | (require 'evalator-history) 38 | (require 'evalator-key-map) 39 | (require 'evalator-state) 40 | (require 'evalator-utils) 41 | (require 'helm) 42 | 43 | (defvar evalator-help-message 44 | "* Evalator Help 45 | 46 | Evalator is a new kind of REPL for Emacs. It lets you interactively 47 | transform data and can support many different languages. 48 | 49 | Since the evalator documentation is in a state of flux currently, this 50 | help will only reference things that aren't likely to change. 51 | 52 | NOTE: This help also includes the help for helm, which evalator depends 53 | on as a front-end. Knowing some basic helm commands is helpful to using 54 | evalator to its full potential. The generic helm help is located directly 55 | under this help section. 56 | 57 | ** Evalator Session Shortcuts 58 | 59 | The shortcuts in the table below are used while in the evalator buffer. 60 | 61 | |----------+-------------------------------------------+------------------------------------------| 62 | | Shortcut | Command | Description | 63 | |----------+-------------------------------------------+------------------------------------------| 64 | | | | | 65 | | RETURN | evalator-action-confirm-make-or-transform | Confirm the expression and either | 66 | | | | make the initial candidates or transform | 67 | | | | the existing ones. | 68 | | | | | 69 | | | | If this action is executed at the first | 70 | | | | evalator prompt then confirming will | 71 | | | | generate the initial candidates. | 72 | | | | | 73 | | | | Otherwise, confirmation evaluates the | 74 | | | | expression on each candidate. | 75 | | | | | 76 | |----------+-------------------------------------------+------------------------------------------| 77 | | | | | 78 | | C-c ; | evalator-action-insert-special-arg | Insert the special arg character | 79 | | | | | 80 | | or | | See the \"Special Args\" section below | 81 | | | | for details on using special args. | 82 | | C-; | | | 83 | | | | | 84 | |----------+-------------------------------------------+------------------------------------------| 85 | | | | | 86 | | C-c C-c | evalator-action-confirm-transform-collect | This command causes the candidates to | 87 | | | | be treated as a single collection. | 88 | | | | | 89 | | | | This means that the special arg will | 90 | | | | refer to the entire collection of | 91 | | | | candidates. The expression will only be | 92 | | | | evaluated on this collection and only | 93 | | | | one candidate will be produced as a | 94 | | | | result. | 95 | | | | | 96 | | | | This command is useful for generating | 97 | | | | an aggregate result. | 98 | | | | | 99 | |----------+-------------------------------------------+------------------------------------------| 100 | | | | | 101 | | C-c C-e | evalator-action-execute-in-elisp | Execute the expression on each candidate | 102 | | | | using Elisp. | 103 | | | | | 104 | | | | NOTE: This action does not transform the | 105 | | | | candidates | 106 | | | | | 107 | |----------+-------------------------------------------+------------------------------------------| 108 | | | | | 109 | | C-j | evalator-action-next | Go to next history state | 110 | | | | | 111 | |----------+-------------------------------------------+------------------------------------------| 112 | | | | | 113 | | C-l | evalator-action-previous | Go to previous history state | 114 | | | | | 115 | |----------+-------------------------------------------+------------------------------------------| 116 | 117 | ** Global commands 118 | 119 | Below are the global evalator commands that can be run using M-x. 120 | 121 | You should probably bind them to shortcut. 122 | 123 | |----------------------------+-----------------------------------------------------------------------------------| 124 | | Command | Description | 125 | |----------------------------+-----------------------------------------------------------------------------------| 126 | | | | 127 | | evalator | Starts an evalator session. | 128 | | | | 129 | | | Command accepts an optional mode and evaluation context as arguments. | 130 | | | | 131 | | | If the mode arg is nil, then normal mode is used. | 132 | | | | 133 | | | If the evaluation context arg is nil, then the felisp evaluation context is used. | 134 | | | | 135 | |----------------------------+-----------------------------------------------------------------------------------| 136 | | | | 137 | | evalator-explicit | Starts an evalator session in explicit mode. | 138 | | | | 139 | | | Accepts an optional evaluation context | 140 | | | | 141 | |----------------------------+-----------------------------------------------------------------------------------| 142 | | | | 143 | | evalator-resume | Resumes your last evalator session | 144 | | | | 145 | |----------------------------+-----------------------------------------------------------------------------------| 146 | | | | 147 | | evalator-insert-equiv-expr | Inserts the equivalent expression of the last evalator session into the | 148 | | | current buffer. | 149 | | | | 150 | | | NOTE: The last session must have been run in explicit mode for this to work. | 151 | | | | 152 | |----------------------------+-----------------------------------------------------------------------------------| 153 | 154 | ** Special Args 155 | 156 | Special arguments are used to refer to the value of the candidate being 157 | transformed. The default special character is Ⓔ which is the unicode 158 | character x24ba. If a candidate was equal to the number 1 and your 159 | expression was (+ 1 Ⓔ) then 1 would be substituted for the special arg 160 | and the result would evaluate to 2. 161 | 162 | You can insert the special arg into the expression prompt by using the 163 | shortcut C-c ; which executes `evalator-action-insert-special-arg'. 164 | An abbreviated shortcut C-; has been provided for GUI Emacs users has 165 | 166 | If the candidate is a collection you can refer to an element within it 167 | by adding an integer after the special arg. So if a candidate is the 168 | vector [1 2 3 4] and your expression is (+ 1 Ⓔ0) then Ⓔ0 would be 169 | substituted with 1 and the result would evaluate to 2. 170 | 171 | ") 172 | 173 | (defvar evalator-expression-prompt "Enter Expression: ") 174 | (defvar evalator-candidates-initial '("Enter an expression below to generate initial data") 175 | "Informational candidate used on evalator startup.") 176 | 177 | (defvar evalator-error nil 178 | "Error symbol used for signaling evalator errors.") 179 | (put 'evalator-error 'error-conditions '(error)) 180 | 181 | (defun evalator-action-previous () 182 | "Go to the previous state and update the evalator session." 183 | (interactive) 184 | (when (not (equal 0 (evalator-history-index))) 185 | (evalator-utils-put! evalator-state :history-index (+ -1 (evalator-history-index))) 186 | (helm-unmark-all) 187 | (helm-set-pattern "") 188 | (helm-update))) 189 | 190 | (defun evalator-action-next () 191 | "Go to the next history state and update the evalator session." 192 | (interactive) 193 | (when (not (equal (+ -1 (length (evalator-history))) (evalator-history-index))) 194 | (evalator-utils-put! evalator-state :history-index (+ 1 (evalator-history-index))) 195 | (helm-unmark-all) 196 | (helm-set-pattern "") 197 | (helm-update))) 198 | 199 | (defun evalator-action-execute-in-elisp () 200 | "Execute expression in elisp context. 201 | This function is useful if you want to execute an Emacs command or 202 | Elisp function from within an evalator session that uses a different 203 | evaluation context. This action does not transform the candidates." 204 | (interactive) 205 | (let* ((spec-arg-elisp (evalator-context-get-special-arg (evalator-elisp-context))) 206 | (spec-arg-curr (evalator-context-get-special-arg (evalator-state-context))) 207 | (expr-str (if (equal spec-arg-elisp spec-arg-curr) 208 | helm-pattern 209 | (replace-regexp-in-string spec-arg-curr spec-arg-elisp helm-pattern)))) 210 | (condition-case err 211 | (message 212 | (prin1-to-string 213 | (evalator-elisp-transform-candidates (evalator-get-candidates) expr-str nil))) 214 | (error 215 | (evalator-message err))))) 216 | 217 | (defun evalator-action-confirm-make-or-transform (&optional f-and-args) 218 | "Make initial candidates or transform candidates then update history. 219 | Accepts an optional arg F-AND-ARGS to pass to `evalator-make-or-transform'." 220 | (interactive) 221 | (let* ((err-handler (lambda (err-str) 222 | (evalator-message (concat "Error: " err-str)) 223 | nil)) 224 | (cands (evalator-candidate-make-or-transform f-and-args err-handler))) 225 | (when cands 226 | (evalator-history-push! cands helm-pattern) 227 | (helm-unmark-all) 228 | (helm-set-pattern "")))) 229 | 230 | (defun evalator-action-confirm-transform-collect () 231 | "Transform the entire candidate selection then update history. 232 | Normally candidates are transformed by evaluating the current 233 | expression on each candidate with the special arg referring to the 234 | value of the candidate. This action changes that behavior in two 235 | ways. First, it changes the meaning of the special arg so it refers 236 | to the entire candidate selection. Second, the current expression is 237 | evaluated only once to produce a single candidate. This action is 238 | used for when you need to produce an aggregate result." 239 | (interactive) 240 | (let* ((f (slot-value (evalator-state-context) :transform-candidates)) 241 | (expr-str helm-pattern) 242 | (args (list (evalator-get-candidates) expr-str t))) 243 | (evalator-action-confirm-make-or-transform (list f args)))) 244 | 245 | (defun evalator-action-insert-special-arg () 246 | "Insert the evalator special arg into the expression prompt." 247 | (interactive) 248 | (insert (evalator-context-get-special-arg (evalator-state-context)))) 249 | 250 | (defun evalator-message (msg) 251 | "Output MSG and append a newline and an instruction to continue." 252 | (read-char (concat msg "\n" "Press any key to return to minibuffer.")) 253 | ;; Hack needed because minibuffer window doesn't resize after printing a multiline message 254 | (with-current-buffer (window-buffer (active-minibuffer-window)) 255 | (let ((txt (minibuffer-contents))) 256 | (delete-minibuffer-contents) 257 | (insert txt)))) 258 | 259 | (defun evalator-flash (status) 260 | "Change the evalator expression prompt face according to STATUS." 261 | (let ((f (if (equal :success status) 'evalator-success 'evalator-error))) 262 | (with-current-buffer (window-buffer (active-minibuffer-window)) 263 | (face-remap-add-relative 'minibuffer-prompt f)))) 264 | 265 | (cl-defun evalator-marked-candidates (&key with-wildcard) 266 | "Same as 'helm-marked-candidates' except it returns nil if no candidates were marked." 267 | (with-current-buffer helm-buffer 268 | (let ((candidates 269 | (cl-loop with current-src = (helm-get-current-source) 270 | for (source . real) in (reverse helm-marked-candidates) 271 | when (equal (assq 'name source) (assq 'name current-src)) 272 | append (helm--compute-marked real source with-wildcard) 273 | into cands 274 | finally return cands))) 275 | candidates))) 276 | 277 | (defun evalator-persistent-help () 278 | "Return persistent help string." 279 | (cl-flet ((f (command) 280 | (key-description (where-is-internal command evalator-key-map t)))) 281 | (concat "History forward, " 282 | (f 'evalator-action-previous) ": History backward, " 283 | (f 'evalator-action-confirm-make-or-transform) ": Accept transformation, " 284 | (f 'evalator-action-insert-special-arg) ": Insert special arg"))) 285 | 286 | (defun evalator-get-candidates () 287 | "Return current evalator candidates. 288 | If there are marked candidates, return the list of those. Otherwise, 289 | return a list of all the candidates." 290 | (let ((cands-all (evalator-history-current :candidates)) 291 | (cands-marked (evalator-marked-candidates))) 292 | (or cands-marked cands-all))) 293 | 294 | (defun evalator-try-context-f (context-f args &optional err-handler) 295 | "Try executing the given evaluation context function CONTEXT-F. 296 | Calls TRANSFORM-F with the given ARGS. Returns the result if the 297 | operation was successful. If there was an error and the optional arg 298 | ERR-HANDLER is nil, then return all current evalator candidates. If 299 | ERR-HANDLER is non-nil, then it is executed and its value is returned." 300 | (condition-case err 301 | (progn 302 | (if (equal "" helm-pattern) 303 | (signal 'evalator-error '("Empty Expression")) 304 | (progn (evalator-flash :success) 305 | (apply context-f args)))) 306 | (error 307 | (evalator-flash :error) 308 | (if err-handler 309 | (funcall err-handler (prin1-to-string err)) 310 | (evalator-history-current :candidates))))) 311 | 312 | (defun evalator-candidate-make-or-transform (&optional f-and-args err-handler) 313 | "Make initial candidates or transform current candidates. 314 | If current history index is 0 then the context's `:make-candidates' 315 | slot function and appropriate args are passed to 316 | `evalator-try-context-f' for evaluation. Otherwise the context's 317 | `:transform-candidates' slot function is used. If optional arg 318 | F-AND-ARGS is non-nil then it will be used instead. Function also 319 | accept's an optional ERR-HANDLER to pass to `evalator-try-context-f'." 320 | (with-helm-current-buffer 321 | (if f-and-args 322 | (apply 'evalator-try-context-f (append f-and-args (list err-handler))) 323 | (let* ((make-f (slot-value (evalator-state-context) :make-candidates)) 324 | (transform-f (slot-value (evalator-state-context) :transform-candidates)) 325 | (expr-str helm-pattern) 326 | (mode (plist-get evalator-state :mode)) 327 | (f-and-args (if (equal 0 (evalator-history-index)) 328 | (list make-f (list expr-str mode) err-handler) 329 | (list transform-f (list (evalator-get-candidates) expr-str nil) err-handler)))) 330 | (apply 'evalator-try-context-f f-and-args))))) 331 | 332 | (defun evalator-build-source (candidates mode) 333 | "Build the source for a evalator session using a CANDIDATES list and a MODE." 334 | (helm-build-sync-source (concat "Evaluation Result" (when (equal :explicit mode) "(Explicit)")) 335 | :candidates candidates 336 | :filtered-candidate-transformer (lambda (_c _s) (evalator-candidate-make-or-transform)) 337 | :help-message evalator-help-message 338 | :keymap evalator-key-map 339 | :multiline t 340 | :nohighlight t 341 | :nomark (equal :explicit mode) 342 | :persistent-help (evalator-persistent-help) 343 | :volatile t)) 344 | 345 | (defun evalator-build-history-source () 346 | "Build a source that will show the current point in history." 347 | (helm-build-dummy-source "Evalator History" 348 | :filtered-candidate-transformer (lambda (_c _s) 349 | (list (funcall evalator-config-prompt-f))) 350 | :header-line "History" 351 | :nomark t 352 | :nohighlight t)) 353 | 354 | ;;;###autoload 355 | (defun evalator-insert-equiv-expr () 356 | "Insert the equivalent expression of the previous evalator session into the current buffer." 357 | (interactive) 358 | (if (equal :explicit (plist-get evalator-state :mode)) 359 | (insert (funcall 360 | (slot-value (evalator-state-context) :make-equiv-expr) 361 | (evalator-history-expression-chain))) 362 | (message "Error: This command is only allowed when the last evalator session in explicit mode."))) 363 | 364 | ;;;###autoload 365 | (defun evalator-resume () 366 | "Resume last evalator session." 367 | (interactive) 368 | (let ((print-circle t)) ;; Necessary to support circular lists 369 | (helm-resume "*helm-evalator*"))) 370 | 371 | ;;;###autoload 372 | (defun evalator (&optional mode context) 373 | "Start an evalator session. 374 | 375 | Function accepts an optional MODE keyword and a CONTEXT symbol. 376 | 377 | If MODE is non-nil and a currently supported mode value then that mode 378 | will be used for the session. 379 | 380 | Below are currently supported values for MODE: 381 | 382 | `:explicit' 383 | 384 | If MODE is nil evalator will start in normal mode. 385 | 386 | If CONTEXT is non-nil, then the result of calling CONTEXT's function 387 | definition will be used as the session's evaluation context. 388 | 389 | If CONTEXT is nil, then the current buffer's major mode will be 390 | searched for in `evalator-config-mode-context-alist'. If a match is 391 | found, the context associated with that major mode is used in the 392 | evalator session. If no match is found, an elisp evaluation context 393 | is used instead. 394 | " 395 | (interactive) 396 | (when (evalator-state-init mode (evalator-context-get context)) 397 | (add-hook 'minibuffer-setup-hook (lambda () 398 | (setq-local minibuffer-message-timeout nil) t nil)) 399 | (evalator-history-push! evalator-candidates-initial "") 400 | (let* ((print-circle t) ;; Necessary to support circular lists 401 | (evalator-after-update-hook (copy-sequence helm-after-update-hook)) 402 | (history-source (evalator-build-history-source)) 403 | (result-source (evalator-build-source evalator-candidates-initial mode))) 404 | 405 | ;; Prevent history candidate from being selected 406 | (add-hook 'evalator-after-update-hook (lambda () 407 | (helm-next-line))) 408 | 409 | (helm :sources (list history-source result-source) 410 | :buffer "*helm-evalator*" 411 | :prompt "Enter Expression: " 412 | :helm-after-update-hook evalator-after-update-hook)))) 413 | 414 | ;;;###autoload 415 | (defun evalator-explicit (&optional context) 416 | "Helper function to start an evalator-session in explicit mode. 417 | 418 | In explicit mode the data generated will always be represented as a 419 | single candidate. This is the only mode that allows an equivalent 420 | expression of the session to be generated through 421 | `evalator-insert-equiv-expr'. 422 | " 423 | (interactive) 424 | (evalator :explicit context)) 425 | 426 | (provide 'evalator) 427 | 428 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 429 | ;;; evalator.el ends here 430 | -------------------------------------------------------------------------------- /example-gifs/practical.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seanirby/evalator/f30da4da48c0b3f3cfa1fc1c7cfdb53ffe79df36/example-gifs/practical.gif -------------------------------------------------------------------------------- /example-gifs/walkthrough.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seanirby/evalator/f30da4da48c0b3f3cfa1fc1c7cfdb53ffe79df36/example-gifs/walkthrough.gif -------------------------------------------------------------------------------- /test/evalator-config-test.el: -------------------------------------------------------------------------------- 1 | ;;; evalator-config-test.el --- 2 | ;; 3 | ;; Author: Sean Irby 4 | ;; Copyright © , Sean Irby 5 | ;; 6 | ;; This program is free software: you can redistribute it and/or modify 7 | ;; it under the terms of the GNU General Public License as published by 8 | ;; the Free Software Foundation, either version 3 of the License, or (at 9 | ;; your option) any later version. 10 | ;; 11 | ;; This program is distributed in the hope that it will be useful, but 12 | ;; WITHOUT ANY WARRANTY; without even the implied warranty of 13 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | ;; General Public License for more details. 15 | ;; 16 | ;; You should have received a copy of the GNU General Public License 17 | ;; along with GNU Emacs. If not, see . 18 | ;; 19 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 20 | ;; 21 | ;;; This file is not a part of GNU Emacs 22 | ;; 23 | ;;; Commentary: 24 | ;; 25 | ;;; Code: 26 | 27 | 28 | (require 'el-mock) 29 | (eval-when-compile 30 | (require 'cl)) 31 | 32 | (require 'evalator-config) 33 | 34 | ;; (ert-deftest evalator-config-prompt-f-tests () 35 | ;; (with-mock 36 | ;; (stub evalator-history-index => 0) 37 | ;; (stub evalator-history => '(length of 3)) 38 | ;; (should (equal "1 of 3" 39 | ;; (funcall evalator-config-prompt-f))))) 40 | 41 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 42 | ;;; evalator-config-test.el ends here 43 | -------------------------------------------------------------------------------- /test/evalator-context-test.el: -------------------------------------------------------------------------------- 1 | ;;; evalator-context-test.el --- 2 | ;; 3 | ;; Author: Sean Irby 4 | ;; Copyright © , Sean Irby 5 | ;; 6 | ;; This program is free software: you can redistribute it and/or modify 7 | ;; it under the terms of the GNU General Public License as published by 8 | ;; the Free Software Foundation, either version 3 of the License, or (at 9 | ;; your option) any later version. 10 | ;; 11 | ;; This program is distributed in the hope that it will be useful, but 12 | ;; WITHOUT ANY WARRANTY; without even the implied warranty of 13 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | ;; General Public License for more details. 15 | ;; 16 | ;; You should have received a copy of the GNU General Public License 17 | ;; along with GNU Emacs. If not, see . 18 | ;; 19 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 20 | ;; 21 | ;;; This file is not a part of GNU Emacs 22 | ;; 23 | ;;; Commentary: 24 | ;; 25 | ;;; Code: 26 | 27 | (require 'el-mock) 28 | (eval-when-compile 29 | (require 'cl)) 30 | 31 | (require 'evalator-context) 32 | 33 | ;; (ert-deftest evalator-context-get-test () 34 | ;; (let ((evalator-elisp-context "elisp") 35 | ;; (evalator-foo-context "foo") 36 | ;; (evalator-mode-context-alist '((foo-mode . evalator-foo-context)))) 37 | ;; (with-mock 38 | ;; (stub buffer-local-value) 39 | ;; (should (equal "foo" 40 | ;; (evalator-context-get 'evalator-foo-context)))) 41 | ;; (with-mock 42 | ;; (stub buffer-local-value => 'foo-mode) 43 | ;; (should (equal "foo" 44 | ;; (evalator-context-get)))) 45 | ;; (with-mock 46 | ;; (stub buffer-local-value => 'bar-mode) 47 | ;; (should (equal "elisp" 48 | ;; (evalator-context-get)))))) 49 | 50 | 51 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 52 | ;;; evalator-context-test.el ends here 53 | -------------------------------------------------------------------------------- /test/evalator-elisp-test.el: -------------------------------------------------------------------------------- 1 | ;;; evalator-elisp-test.el --- Test for evalator-elisp-test.el 2 | ;; 3 | ;; Author: Sean Irby 4 | ;; Copyright © , Sean Irby 5 | ;; 6 | ;; This program is free software: you can redistribute it and/or modify 7 | ;; it under the terms of the GNU General Public License as published by 8 | ;; the Free Software Foundation, either version 3 of the License, or (at 9 | ;; your option) any later version. 10 | ;; 11 | ;; This program is distributed in the hope that it will be useful, but 12 | ;; WITHOUT ANY WARRANTY; without even the implied warranty of 13 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | ;; General Public License for more details. 15 | ;; 16 | ;; You should have received a copy of the GNU General Public License 17 | ;; along with GNU Emacs. If not, see . 18 | ;; 19 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 20 | ;; 21 | ;;; This file is not a part of GNU Emacs 22 | ;; 23 | ;;; Commentary: 24 | ;; 25 | ;;; Code: 26 | 27 | 28 | (require 'ert) 29 | (require 'evalator-elisp) 30 | 31 | (defalias 'ssa 'evalator-elisp-subst-special-args) 32 | (defalias 'mc 'evalator-elisp-make-candidates) 33 | (defalias 'tc 'evalator-elisp-transform-candidates) 34 | 35 | (ert-deftest evalator-elisp-get-special-arg-tests () 36 | (let ((evalator-context-special-arg-default "@")) 37 | (let ((evalator-elisp-special-arg "$")) 38 | (should (equal "$" 39 | (evalator-elisp-get-special-arg)))) 40 | (should (equal "@" 41 | (evalator-elisp-get-special-arg))))) 42 | 43 | (ert-deftest evalator-elisp-numbered-arg-num-tests () 44 | (should (equal 123 45 | (evalator-elisp-numbered-arg-num "@123"))) 46 | 47 | (should (equal 123 48 | ;; Not expected input 49 | (evalator-elisp-numbered-arg-num "123"))) 50 | 51 | (should (equal 123 52 | ;; Not expected input 53 | (evalator-elisp-numbered-arg-num "foo bar baz @123")))) 54 | 55 | (ert-deftest evalator-elisp-numbered-arg-pattern-tests () 56 | (let ((evalator-elisp-special-arg "@")) 57 | (should (equal "@[0-9]+" 58 | (evalator-elisp-numbered-arg-pattern))) 59 | (should (equal "'?@[0-9]+" 60 | (evalator-elisp-numbered-arg-pattern t))))) 61 | 62 | (ert-deftest evalator-elisp-make-equiv-expr-tests () 63 | (let ((evalator-elisp-special-arg "@")) 64 | (should (equal "(cons (elt (list 1 2 3) 0) (list 1 2 3))" 65 | (evalator-elisp-make-equiv-expr '("(list 1 2 3)" "(cons @0 '@)")))))) 66 | 67 | (ert-deftest evalator-elisp-subst-numbered-special-args-tests () 68 | (let ((evalator-elisp-special-arg "@")) 69 | (should (equal "(+ @ 3 7)" 70 | (evalator-elisp-subst-numbered-special-args "(+ @ @0 @1)" '(3 7)))))) 71 | 72 | (ert-deftest evalator-elisp-subst-identity-special-args-tests () 73 | (let ((evalator-elisp-special-arg "@")) 74 | (should (equal "(+ 1 1)" 75 | (evalator-elisp-subst-identity-special-args "(+ @ @)" 1))))) 76 | 77 | (ert-deftest evalator-elisp-subst-special-args-tests () 78 | (let ((evalator-elisp-special-arg "@")) 79 | (should (equal "'(+ 1 1)" 80 | (ssa "'(+ 1 1)" nil))) 81 | 82 | (should (equal "'(+ 1 1)" 83 | (ssa "'(+ 1 @0)" '(1 2 3)))) 84 | 85 | (should (equal "'(+ 1 (+ 1 (+ 1 (+ 1 (+ 1 1)))))" 86 | (ssa "'(+ 1 (+ 1 (+ 1 (+ 1 (+ 1 @0)))))" [1 2 3]))) 87 | 88 | (should (equal "1" 89 | (ssa "@" 1))))) 90 | 91 | (ert-deftest evalator-elisp-make-candidates-tests () 92 | ;;normal mode 93 | (should (equal '("1" "2" "3") 94 | (mc "'(1 2 3)" :normal))) 95 | 96 | (should (equal '("1" "2" "3") 97 | (mc "[1 2 3]" :normal))) 98 | 99 | (should (equal '("\"foo\"" "\"bar\"" "\"baz\"") 100 | (mc "'(\"foo\" \"bar\" \"baz\")" :normal))) 101 | 102 | (should (equal '("\"foo\"" "\"bar\"" "\"baz\"") 103 | (mc "[\"foo\" \"bar\" \"baz\"]" :normal))) 104 | 105 | (should (equal '("\"foo\"") 106 | (mc "\"foo\"" :normal))) 107 | 108 | (should (equal '(":foo") 109 | (mc ":foo" :normal))) 110 | 111 | ;;explicit mode 112 | (should (equal '("(1 2 3)") 113 | (mc "'(1 2 3)" :explicit))) 114 | 115 | (should (equal '("[1 2 3]") 116 | (mc "[1 2 3]" :explicit))) 117 | 118 | (should (equal '("(\"foo\" \"bar\" \"baz\")") 119 | (mc "'(\"foo\" \"bar\" \"baz\")" :explicit))) 120 | 121 | (should (equal '("[\"foo\" \"bar\" \"baz\"]") 122 | (mc "[\"foo\" \"bar\" \"baz\"]" :explicit))) 123 | 124 | (should (equal '("\"foo\"") 125 | (mc "\"foo\"" :explicit))) 126 | 127 | (should (equal '(":foo") 128 | (mc ":foo" :explicit)))) 129 | 130 | (ert-deftest evalator-elisp-transform-candidates-tests () 131 | (let ((evalator-elisp-special-arg "@")) 132 | (should (equal '("1" "2" "3" "4") 133 | (tc '("0" "1" "2" "3") "(+ 1 @)" nil))) 134 | 135 | (should (equal '("\"foo1\"" "\"bar1\"" "\"baz1\"") 136 | (tc '("\"foo\"" "\"bar\"" "\"baz\"") "(concat @ \"1\")" nil))) 137 | 138 | (should (equal '("4") 139 | (tc '("1" "3") "(cl-reduce '+ '@)" t))) 140 | 141 | (should (equal '("\"foobar\"") 142 | (tc '("\"foo\"" "\"bar\"") "(concat @0 @1)" t))))) 143 | 144 | (ert-deftest evalator-elisp-eval-tests () 145 | (should (equal 2 146 | (evalator-elisp-eval "(+ 1 1)")))) 147 | 148 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 149 | ;;; evalator-elisp-test.el ends here 150 | -------------------------------------------------------------------------------- /test/evalator-history-test.el: -------------------------------------------------------------------------------- 1 | ;;; evalator-history-test.el --- Tests for evalator-history-test.el 2 | ;; 3 | ;; Author: Sean Irby 4 | ;; Copyright © , Sean Irby 5 | ;; 6 | ;; This program is free software: you can redistribute it and/or modify 7 | ;; it under the terms of the GNU General Public License as published by 8 | ;; the Free Software Foundation, either version 3 of the License, or (at 9 | ;; your option) any later version. 10 | ;; 11 | ;; This program is distributed in the hope that it will be useful, but 12 | ;; WITHOUT ANY WARRANTY; without even the implied warranty of 13 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | ;; General Public License for more details. 15 | ;; 16 | ;; You should have received a copy of the GNU General Public License 17 | ;; along with GNU Emacs. If not, see . 18 | ;; 19 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 20 | ;; 21 | ;;; This file is not a part of GNU Emacs 22 | ;; 23 | ;;; Commentary: 24 | ;; 25 | ;;; Code: 26 | 27 | 28 | (require 'evalator-history) 29 | (require 'cl-lib) 30 | (require 'noflet) 31 | 32 | (ert-deftest evalator-history-test () 33 | "Test that 'evalator-history' returns the ':history' value of 34 | 'evalator-state'" 35 | (let ((state-copy (copy-sequence evalator-state))) 36 | (unwind-protect 37 | (progn (setq evalator-state '(:history :foo)) 38 | (should (equal :foo (evalator-history)))) 39 | (setq evalator-state state-copy)))) 40 | 41 | (ert-deftest evalator-history-index-test () 42 | "Tests that 'evalator-history-index' returns the ':history-index' 43 | property of 'evalator-state'" 44 | (let ((evalator-state '(:history-index 0))) 45 | (should (equal 0 (evalator-history-index))))) 46 | 47 | (ert-deftest evalator-history-push!-test () 48 | "Tests that 'evalator-history-push!' can push onto state's history 49 | and update state's index." 50 | (let ((evalator-state '(:history [] :history-index -1))) 51 | (progn 52 | (evalator-history-push! '("foo") "bar") 53 | (should (equal 54 | '(:history [(:candidates ("foo") :expression "bar")] :history-index 0) 55 | evalator-state))))) 56 | 57 | (ert-deftest evalator-history-current-test () 58 | (noflet ((evalator-history () ["foo" "bar" "baz"]) 59 | (evalator-history-index () 1)) 60 | (should (equal "bar" 61 | (evalator-history-current))))) 62 | 63 | (ert-deftest evalator-history-expression-chain-tests () 64 | "Tests the 'evalator-history-expression-chain' returns all 65 | expressions in history except for the first" 66 | (let ((evalator-state '(:history [(:expression nil) 67 | (:expression "(expr1)") 68 | (:expression "(expr2)") 69 | (:expression "(expr3)")]))) 70 | (should (equal '("(expr1)" "(expr2)" "(expr3)") 71 | (evalator-history-expression-chain))))) 72 | 73 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 74 | ;;; evalator-history-test.el ends here 75 | -------------------------------------------------------------------------------- /test/evalator-state-test.el: -------------------------------------------------------------------------------- 1 | ;;; evalator-state-test.el --- Tests for evalator-state.el 2 | ;; 3 | ;; Author: Sean Irby 4 | ;; Copyright © , Sean Irby 5 | ;; 6 | ;; This program is free software: you can redistribute it and/or modify 7 | ;; it under the terms of the GNU General Public License as published by 8 | ;; the Free Software Foundation, either version 3 of the License, or (at 9 | ;; your option) any later version. 10 | ;; 11 | ;; This program is distributed in the hope that it will be useful, but 12 | ;; WITHOUT ANY WARRANTY; without even the implied warranty of 13 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | ;; General Public License for more details. 15 | ;; 16 | ;; You should have received a copy of the GNU General Public License 17 | ;; along with GNU Emacs. If not, see . 18 | ;; 19 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 20 | ;; 21 | ;;; This file is not a part of GNU Emacs 22 | ;; 23 | ;;; Commentary: 24 | ;; 25 | ;;; Code: 26 | 27 | 28 | (require 'ert) 29 | (require 'el-mock) 30 | (require 'evalator-state) 31 | 32 | (ert-deftest evalator-state-init-test () 33 | "Tests that 'evalator state' is initialized with the value of 34 | 'evalator-state-default'" 35 | (with-mock 36 | (stub evalator-elisp-context) 37 | (mock (evalator-utils-put! * * *)) 38 | (stub evalator-context-get) 39 | (mock (slot-value * :init) => (lambda () t)) 40 | (stub evalator-state-context) 41 | (let ((evalator-foo-context t) 42 | (evalator-state-default (copy-sequence evalator-state-default))) 43 | (with-mock 44 | (should (evalator-state-init))) 45 | (with-mock 46 | (should (evalator-state-init :explicit))) 47 | (with-mock 48 | (should (evalator-state-init :explicit evalator-foo-context)))))) 49 | 50 | (ert-deftest evalator-state-context-test () 51 | (let* ((evalator-state (list :context 'evalator-foo-context))) 52 | (with-mock 53 | (stub evalator-foo-context => (lambda () t)) 54 | (should (evalator-state-context))))) 55 | 56 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 57 | ;;; evalator-state-test.el ends here 58 | -------------------------------------------------------------------------------- /test/evalator-test.el: -------------------------------------------------------------------------------- 1 | ;;; evalator-test.el --- Tests for evalator.el 2 | ;; 3 | ;; Author: Sean Irby 4 | ;; Copyright © , Sean Irby 5 | ;; 6 | ;; This program is free software: you can redistribute it and/or modify 7 | ;; it under the terms of the GNU General Public License as published by 8 | ;; the Free Software Foundation, either version 3 of the License, or (at 9 | ;; your option) any later version. 10 | ;; 11 | ;; This program is distributed in the hope that it will be useful, but 12 | ;; WITHOUT ANY WARRANTY; without even the implied warranty of 13 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | ;; General Public License for more details. 15 | ;; 16 | ;; You should have received a copy of the GNU General Public License 17 | ;; along with GNU Emacs. If not, see . 18 | ;; 19 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 20 | ;; 21 | ;;; This file is not a part of GNU Emacs 22 | ;; 23 | ;;; Commentary: 24 | ;; 25 | ;;; Code: 26 | 27 | 28 | (require 'noflet) 29 | (require 'el-mock) 30 | (eval-when-compile 31 | (require 'cl)) 32 | (require 'evalator) 33 | 34 | (ert-deftest evalator-action-previous-and-next-tests () 35 | (let ((evalator-state (list :history [(:candidates '()) 36 | (:candidates '())] 37 | :history-index 0))) 38 | (noflet ((helm-unmark-all () nil) 39 | (helm-set-pattern (_) nil) 40 | (helm-update () nil)) 41 | 42 | (evalator-action-previous) 43 | (should (equal 0 (evalator-history-index))) 44 | 45 | (evalator-action-next) 46 | (should (equal 1 (evalator-history-index))) 47 | 48 | (evalator-action-next) 49 | (should (equal 1 (evalator-history-index))) 50 | 51 | (evalator-action-previous) 52 | (should (equal 0 (evalator-history-index)))))) 53 | 54 | (ert-deftest evalator-action-execute-in-elisp-test () 55 | (let ((helm-pattern "@ !")) 56 | (noflet ((evalator-context-get-special-arg (arg) arg)) 57 | (with-mock 58 | (stub evalator-elisp-context => "!") 59 | (stub evalator-state-context => "@") 60 | (stub evalator-get-candidates => '("cand-1")) 61 | (mock (evalator-elisp-transform-candidates '("cand-1") "! !" nil) => '("cand-1-xfrmd")) 62 | (mock (message "(\"cand-1-xfrmd\")") => t) 63 | (evalator-action-execute-in-elisp)) 64 | (with-mock 65 | (stub evalator-elisp-context => "@") 66 | (stub evalator-state-context => "@") 67 | (stub evalator-get-candidates => '("cand-1")) 68 | (mock (evalator-elisp-transform-candidates '("cand-1") "@ !" nil) => '("cand-1-xfrmd")) 69 | (mock (message "(\"cand-1-xfrmd\")") => t) 70 | (evalator-action-execute-in-elisp)) 71 | (with-mock 72 | (stub evalator-elisp-context) 73 | (stub evalator-state-context) 74 | (stub evalator-get-candidates) 75 | (stub evalator-elisp-transform-candidates => (signal 'evalator-error '("error"))) 76 | (stub evalator-message => nil) 77 | (stub error => t) 78 | (evalator-action-execute-in-elisp)) ))) 79 | 80 | (ert-deftest evalator-action-confirm-make-or-transform-test () 81 | ;;Successful transformation 82 | (with-mock 83 | (mock (evalator-candidate-make-or-transform * *) => t) 84 | (mock (evalator-history-push! * *) :times 1) 85 | (mock (helm-unmark-all) :times 1) 86 | (mock (helm-set-pattern "") :times 1 => t) 87 | (evalator-action-confirm-make-or-transform)) 88 | ;;Unsuccessful transformation 89 | (with-mock 90 | (mock (evalator-candidate-make-or-transform * *) => nil) 91 | (should (equal nil 92 | (evalator-action-confirm-make-or-transform))))) 93 | 94 | (ert-deftest evalator-action-confirm-transform-collect-test () 95 | (let ((helm-pattern "pattern")) 96 | (with-mock 97 | (stub slot-value => "f") 98 | (stub evalator-get-candidates => '("cand-1")) 99 | (mock (evalator-action-confirm-make-or-transform '("f" (("cand-1") "pattern" t)))) 100 | (evalator-action-confirm-transform-collect)))) 101 | 102 | (ert-deftest evalator-action-insert-special-arg-test () 103 | (with-mock 104 | (stub evalator-state-context) 105 | (stub evalator-context-get-special-arg => "@") 106 | (should (equal "@" 107 | (with-temp-buffer 108 | (evalator-action-insert-special-arg) 109 | (buffer-string)))))) 110 | 111 | (ert-deftest evalator-flash-test () 112 | (with-mock 113 | (mock (face-remap-add-relative * 'evalator-success) => t) 114 | (evalator-flash :success)) 115 | (with-mock 116 | (mock (face-remap-add-relative * 'evalator-error) => t) 117 | (evalator-flash :error))) 118 | 119 | ;; TODO 120 | (ert-deftest evalator-unmark-all-test ()) 121 | 122 | ;; TODO 123 | (ert-deftest evalator-marked-candidates-test ()) 124 | 125 | (ert-deftest evalator-persistent-help-test () 126 | (let ((evalator-key-map (list 'evalator-action-previous "C-l" 127 | 'evalator-action-confirm-make-or-transform "RET" 128 | 'evalator-action-insert-special-arg "C-c ;"))) 129 | (noflet ((where-is-internal (command key-map _) (plist-get key-map command)) 130 | (key-description (str) str)) 131 | (should (equal (concat "History forward, " 132 | "C-l: History backward, " 133 | "RET: Accept transformation, " 134 | "C-c ;: Insert special arg") 135 | (evalator-persistent-help)))))) 136 | 137 | (ert-deftest evalator-get-candidates-test () 138 | (with-mock 139 | (mock (evalator-history-current :candidates) => '("cand-1" "cand-2")) 140 | (mock (evalator-marked-candidates) => nil) 141 | (should (equal '("cand-1" "cand-2") 142 | (evalator-get-candidates)))) 143 | (with-mock 144 | (mock (evalator-history-current :candidates) => '("cand-1" "cand-2")) 145 | (mock (evalator-marked-candidates) => '("cand-2")) 146 | (should (equal '("cand-2") 147 | (evalator-get-candidates))))) 148 | 149 | (ert-deftest evalator-try-context-f-test () 150 | ;; successful call 151 | (let ((helm-pattern "(non-empty expression)") 152 | (context-f (lambda (&rest args) t))) 153 | (with-mock 154 | (mock (evalator-flash :success) :times 1) 155 | (evalator-try-context-f context-f nil nil))) 156 | ;; trigger error because of empty pattern 157 | (let ((helm-pattern "") 158 | (context-f (lambda (&rest args) t))) 159 | (with-mock 160 | (mock (evalator-flash :error) :times 1) 161 | (mock (evalator-history-current :candidates) :times 1 => t) 162 | (evalator-try-context-f context-f nil nil))) 163 | ;; error handler gets called 164 | (let ((helm-pattern "(non-empty expression)") 165 | (context-f (lambda (&rest args) (signal 'evalator-error '("")))) 166 | (err-handler (lambda (_) t))) 167 | (with-mock 168 | ;; Flashes :success then :error 169 | (mock (evalator-flash *) :times 2) 170 | (evalator-try-context-f context-f nil err-handler)))) 171 | 172 | (ert-deftest evalator-make-or-transform-test () 173 | (let ((helm-pattern "pattern")) 174 | (with-mock 175 | (stub slot-value) 176 | (mock (evalator-try-context-f "f" "args" "err-handler") => t) 177 | (evalator-candidate-make-or-transform '("f" "args") "err-handler")) 178 | (with-mock 179 | (stub slot-value) 180 | (stub evalator-history-index => 0) 181 | (mock (evalator-try-context-f * '("pattern" :normal) *)) 182 | (evalator-candidate-make-or-transform)) 183 | (with-mock 184 | (stub slot-value) 185 | (stub evalator-history-index => 1) 186 | (stub evalator-get-candidates => '("cand-1")) 187 | (mock (evalator-try-context-f * '(("cand-1") "pattern" nil) *)) 188 | (evalator-candidate-make-or-transform)))) 189 | 190 | ;; Tried to mock the helm-build-sync-source macro but ran into issues 191 | ;; This works for now... 192 | (ert-deftest evalator-build-source-test () 193 | (let ((args-normal (evalator-build-source nil :normal)) 194 | (args-explicit (evalator-build-source nil :explicit))) 195 | (should (equal "Evaluation Result" 196 | (cdr (assoc 'name args-normal)))) 197 | (should (equal "Evaluation Result(Explicit)" 198 | (cdr (assoc 'name args-explicit)))))) 199 | 200 | (ert-deftest evalator-build-history-source-test () 201 | (with-mock 202 | (stub helm-build-dummy-source => t) 203 | (evalator-build-history-source))) 204 | 205 | (ert-deftest evalator-insert-equiv-expr-test () 206 | (with-mock 207 | (stub slot-value => (lambda (exprs) (car exprs))) 208 | (stub evalator-history-expression-chain => '("(+ 1 1)")) 209 | (stub message => "Error message output") 210 | (stub evalator-state-context) 211 | (let ((evalator-state (list :mode :explicit))) 212 | (with-temp-buffer 213 | (evalator-insert-equiv-expr) 214 | (should (equal "(+ 1 1)" 215 | (buffer-string))))) 216 | (let ((evalator-state (list :mode nil))) 217 | (should (equal "Error message output" 218 | (evalator-insert-equiv-expr)))))) 219 | 220 | (ert-deftest evalator-resume-test () 221 | (with-mock 222 | (mock (helm-resume "*helm-evalator*")) 223 | (evalator-resume))) 224 | 225 | (ert-deftest evalator-test () 226 | (let ((evalator-foo-context "foo") 227 | (state-init-p nil) 228 | (history nil) 229 | (evalator-candidates-initial '("foo"))) 230 | (with-mock 231 | (mock (evalator-context-get 'evalator-foo-context) => evalator-foo-context) 232 | (mock (evalator-state-init :explicit "foo") => t) 233 | (mock (evalator-history-push! * *) :times 1) 234 | (stub evalator-build-history-source => t) 235 | (mock (evalator-build-source '("foo") :explicit) => t) 236 | (mock (helm :sources '(t t) 237 | :buffer "*helm-evalator*" 238 | :prompt "Enter Expression: " 239 | :helm-after-update-hook *) => t) 240 | (should (evalator :explicit 'evalator-foo-context))))) 241 | 242 | (ert-deftest evalator-explicit-test () 243 | (with-mock 244 | (mock (evalator :explicit *) => t) 245 | (evalator-explicit))) 246 | 247 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 248 | ;;; evalator-test.el ends here 249 | -------------------------------------------------------------------------------- /test/evalator-utils-test.el: -------------------------------------------------------------------------------- 1 | ;;; evalator-utils-test.el --- Tests for evalator-utils.el 2 | ;; 3 | ;; Author: Sean Irby 4 | ;; Copyright © , Sean Irby 5 | ;; 6 | ;; This program is free software: you can redistribute it and/or modify 7 | ;; it under the terms of the GNU General Public License as published by 8 | ;; the Free Software Foundation, either version 3 of the License, or (at 9 | ;; your option) any later version. 10 | ;; 11 | ;; This program is distributed in the hope that it will be useful, but 12 | ;; WITHOUT ANY WARRANTY; without even the implied warranty of 13 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | ;; General Public License for more details. 15 | ;; 16 | ;; You should have received a copy of the GNU General Public License 17 | ;; along with GNU Emacs. If not, see . 18 | ;; 19 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 20 | ;; 21 | ;;; This file is not a part of GNU Emacs 22 | ;; 23 | ;;; Commentary: 24 | ;; 25 | ;;; Code: 26 | 27 | 28 | (require 'evalator-utils) 29 | (require 'noflet) 30 | 31 | (ert-deftest evalator-utils-put!-test () 32 | (let ((plst '(:foo :bar))) 33 | (evalator-utils-put! plst :foo :baz) 34 | (should (equal '(:foo :baz) plst)))) 35 | 36 | (ert-deftest evalator-utils-get-file-string () 37 | (noflet ((insert-file-contents (filepath) (insert "foo"))) 38 | (should (equal "foo" (evalator-utils-get-file-string "/dummy/file/path"))))) 39 | 40 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 41 | ;;; evalator-utils-test.el ends here 42 | -------------------------------------------------------------------------------- /test/test-helper.el: -------------------------------------------------------------------------------- 1 | ;;; test-helper.el --- Test helpers 2 | ;; 3 | ;; Author: Sean Irby 4 | ;; Copyright © , Sean Irby 5 | ;; 6 | ;; This program is free software: you can redistribute it and/or modify 7 | ;; it under the terms of the GNU General Public License as published by 8 | ;; the Free Software Foundation, either version 3 of the License, or (at 9 | ;; your option) any later version. 10 | ;; 11 | ;; This program is distributed in the hope that it will be useful, but 12 | ;; WITHOUT ANY WARRANTY; without even the implied warranty of 13 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | ;; General Public License for more details. 15 | ;; 16 | ;; You should have received a copy of the GNU General Public License 17 | ;; along with GNU Emacs. If not, see . 18 | ;; 19 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 20 | ;; 21 | ;;; This file is not a part of GNU Emacs 22 | ;; 23 | ;;; Commentary: 24 | ;; 25 | ;;; Code: 26 | 27 | 28 | 29 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 30 | ;;; test-helper.el ends here 31 | --------------------------------------------------------------------------------