├── README.md └── litable.el /README.md: -------------------------------------------------------------------------------- 1 | litable 2 | ======= 3 | 4 | On-the-fly evaluation/substitution of emacs lisp code 5 | 6 | Inspired by Light Table similar feature. 7 | 8 | You should only use this with "pure functions", that is functions that do not touch the buffers, file systems, open network connections or any such thing. This is becuase litable will evaluate the form under the point repeatedly and this can lead to very unfortunate accidents (especially if you try eval `shell command "rm -rf"` ;). We'll try to implement some sort of safeguard system in the future. 9 | 10 | To start this up, simply enable the litable minor mode in the buffer by calling `M-x litable-mode`. 11 | 12 | Not ment to be used in production yet, be warned! 13 | 14 | In action 15 | ======= 16 | 17 | 1. Presentation of the basic *prototype*: http://www.youtube.com/watch?v=TgHvRcbYJ-8 [2:32] \(you don't have to watch this\) 18 | 2. New features, less slow awkward typing: https://www.youtube.com/watch?v=mNO-vgq3Avg [1:50] 19 | 20 | Contribute 21 | ======= 22 | 23 | * If you feel like contributing, there are **TODO** annotations in the code. Mostly basic/trivial stuff, good exercise for people starting with elisp. 24 | * If you have more substantial ideas, start an issue so we can discuss it. I'm open to all ideas, this is simply a precaution for people to not work on the same feature. 25 | * If you want, you can [throw a couple bucks my way](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=CEYP5YVHDRX8C) \(we have a long way to beat that $300k goal people!\). 26 | 27 | *note: the link is for [smartparens](https://github.com/Fuco1/smartparens) donations, but don't worry, I'm the same guy ;)* 28 | -------------------------------------------------------------------------------- /litable.el: -------------------------------------------------------------------------------- 1 | ;;; litable.el --- dynamic evaluation replacement with emacs 2 | 3 | ;; Copyright (C) 2013 Matus Goljer 4 | 5 | ;; Author: Matus Goljer 6 | ;; Maintainer: Matus Goljer 7 | ;; Keywords: lisp 8 | ;; Version: 0.0.20130408 9 | ;; Created: 8th April 2013 10 | ;; Package-requires: ((dash "1.1.0")) 11 | 12 | ;; This program is free software; you can redistribute it and/or modify 13 | ;; it under the terms of the GNU General Public License as published by 14 | ;; the Free Software Foundation, either version 3 of the License, or 15 | ;; (at your option) any later version. 16 | 17 | ;; This program is distributed in the hope that it will be useful, 18 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | ;; GNU General Public License for more details. 21 | 22 | ;; You should have received a copy of the GNU General Public License 23 | ;; along with this program. If not, see . 24 | 25 | ;;; Commentary: 26 | 27 | ;; This allows light-table like dynamic evaluation with Emacs. It's 28 | ;; fun for investigating lisp or for particular light problems before 29 | ;; you delve in and start hacking serious functions together. 30 | 31 | ;; It's very much a work in progress, please heed that warning. 32 | 33 | ;;; Code: 34 | 35 | (require 'dash) 36 | (require 'thingatpt) 37 | 38 | ;;;;; global TODO 39 | ;; 1. investigate: http://lists.gnu.org/archive/html/gnu-emacs-sources/2009-04/msg00032.html 40 | ;; and merge relevant parts. 41 | ;; 42 | ;; 2. update free variable bindings when `setq' call is made on them. 43 | 44 | (defgroup litable nil 45 | "On-the-fly evaluation/substitution of emacs lisp code." 46 | :group 'completion 47 | :prefix "litable-") 48 | 49 | (defvar litable-exceptions '( 50 | (setq . 2) 51 | ) 52 | "A list of cons pairs (form-name . nth argument) where the 53 | substitution should not occur. The number includes the first 54 | item, counting starts at 1. 55 | 56 | For example: 57 | 58 | (setq . 2) ;; first argument is target name, do not substitute.") 59 | 60 | 61 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 62 | ;; let-form annotation 63 | 64 | (defun litable--annotate-let-form (subs &optional point) 65 | "Annotate the let form following point. 66 | 67 | Add an overlay over the let form that will keep track of the 68 | variables bound there. If an overlay is already oresent around 69 | point, merge the variables into this overlay." 70 | (setq point (or point (point))) 71 | (let* ((let-form (sexp-at-point)) 72 | (bounds (bounds-of-thing-at-point 'sexp)) 73 | (var-form-bounds (save-excursion 74 | (down-list) 75 | (forward-list) 76 | (backward-list) 77 | (bounds-of-thing-at-point 'sexp))) 78 | (cvars (litable--extract-variables-with-defs (cadr let-form))) ; vars defined in current form 79 | (pvars (litable-get-let-bound-variable-values point)) ; vars defined in the very previous form 80 | (nvars (litable--merge-variables ; merged vars 81 | (litable--get-active-overlay point) subs cvars)) 82 | ov) 83 | (setq ov (make-overlay (car bounds) (cdr bounds))) 84 | (push ov litable-overlays) 85 | (overlay-put ov 'litable-let-form t) 86 | (overlay-put ov 'litable-let-form-type (car let-form)) 87 | ;; TODO: this still ignores the `setq' updated local bindings. 88 | (overlay-put ov 'litable-let-form-cur nvars) 89 | (overlay-put ov 'litable-let-form-prev pvars) 90 | (overlay-put ov 'litable-var-form-bounds var-form-bounds))) 91 | 92 | (defun litable--extract-variables (varlist) 93 | "Extract the variable names from VARLIST. 94 | VARLIST is a list of the same format `let' accept as first 95 | argument." 96 | (let (vars) 97 | (while varlist 98 | (let ((current (car varlist))) 99 | (pop varlist) 100 | (if (listp current) 101 | (push (car current) vars) 102 | (push current vars)))) 103 | (nreverse vars))) 104 | 105 | (defun litable--extract-variables-with-defs (varlist) 106 | "Extract the variable names from VARLIST. 107 | VARLIST is a list of the same format `let' accept as first 108 | argument." 109 | (let (vars) 110 | (while varlist 111 | (let ((current (car varlist))) 112 | (pop varlist) 113 | (if (listp current) 114 | (push (cons (car current) (cdr current)) vars) 115 | (push (cons current nil) vars)))) 116 | (nreverse vars))) 117 | 118 | (defun litable--overlays-at (&optional pos) 119 | "Simple wrapper of `overlays-at' to get only let-form overlays 120 | from litable." 121 | (--filter (overlay-get it 'litable-let-form) (overlays-at (or pos (point))))) 122 | 123 | (defun litable--point-in-overlay-p (overlay) 124 | "Return t if point is in OVERLAY." 125 | (and (< (point) (overlay-end overlay)) 126 | (> (point) (overlay-start overlay)))) 127 | 128 | (defun litable--get-overlay-length (overlay) 129 | "Compute the length of OVERLAY." 130 | (- (overlay-end overlay) (overlay-start overlay))) 131 | 132 | (defun litable--get-active-overlay (&optional pos) 133 | "Get active overlay. Active overlay is the shortest overlay at 134 | point." 135 | (let ((overlays (litable--overlays-at pos))) 136 | (cond 137 | ((not overlays) nil) 138 | ((not (cdr overlays)) (car overlays)) 139 | (t 140 | (--reduce (if (< (litable--get-overlay-length it) 141 | (litable--get-overlay-length acc)) it acc) overlays))))) 142 | 143 | (defun litable--in-var-form-p (&optional pos) 144 | "Return non-nil if POS is inside a var-form of some let-form." 145 | (setq pos (or pos (point))) 146 | (let* ((active (litable--get-active-overlay pos)) 147 | (bounds (and active (overlay-get active 'litable-var-form-bounds)))) 148 | (when bounds 149 | (and (> pos (car bounds)) 150 | (< pos (cdr bounds)))))) 151 | 152 | (defun litable-get-let-bound-variables (&optional point symbols) 153 | "Get a list of let-bound variables at POINT." 154 | (let ((active (litable--get-active-overlay point))) 155 | (when active 156 | (--map (if symbols (car it) (symbol-name (car it))) (overlay-get active 'litable-let-form-cur))))) 157 | 158 | (defun litable-get-let-bound-parent-variables (&optional point symbols) 159 | "Get a list of let-bound variables in the parent form at POINT." 160 | (let ((active (litable--get-active-overlay point))) 161 | (when active 162 | (--map (if symbols (car it) (symbol-name (car it))) (overlay-get active 'litable-let-form-prev))))) 163 | 164 | (defun litable-get-let-bound-variable-values (&optional point) 165 | (let ((active (litable--get-active-overlay point))) 166 | (when active 167 | (overlay-get active 'litable-let-form-cur)))) 168 | 169 | (defun litable-get-let-bound-parent-variable-values (&optional point) 170 | (let ((active (litable--get-active-overlay point))) 171 | (when active 172 | (overlay-get active 'litable-let-form-prev)))) 173 | 174 | (defun litable-annotate-let-forms (subs &optional point) 175 | "Annotate all let and let* forms in the defun at point." 176 | (setq point (or point (point))) 177 | (save-excursion 178 | (save-restriction 179 | (widen) 180 | (narrow-to-defun) 181 | ;; TODO: this can be made more efficient somehow -- just reuse 182 | ;; the overlays, or keep them be and skip evaling this function 183 | ;; alltogether. Will need a list of already "instrumented" 184 | ;; functions somewhere. 185 | (remove-overlays (point-min) (point-max) 'litable-let-form t) 186 | (goto-char (point-min)) 187 | (while (re-search-forward "(let\\*?" nil t) 188 | (save-excursion 189 | (goto-char (match-beginning 0)) 190 | ;; this gen error if the let form is invalid, or inside 191 | ;; macro etc. Just ignore it 192 | (ignore-errors (litable--annotate-let-form subs))))))) 193 | 194 | 195 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 196 | ;; fake eval (eval with enviroment) 197 | 198 | (defun litable--fake-eval (form enviroment &optional type) 199 | "Evaluate the FORM in ENVIROMENT using the enviroment binding of TYPE. 200 | 201 | TYPE can be a symbol `let' or `let*'." 202 | (setq type (or type 'let)) 203 | (ignore-errors (eval `(,type ,enviroment ,form)))) 204 | 205 | (defun litable--alist-to-list (alist) 206 | "Change (a . b) into (a b)" 207 | (--map (list (car it) (cdr it)) alist)) 208 | 209 | (defun litable--merge-variables (overlay subs varlist) 210 | "Merge the varlist with the variables stored in overlays. 211 | 212 | This will also evaluate the newly-bound variables." 213 | (let* ((pvars (or (and overlay (overlay-get overlay 'litable-let-form-cur)) subs)) 214 | (enviroment (litable--alist-to-list pvars))) 215 | ;; TODO: THIS DOESN'T WORK WITH let*!! We need to update the 216 | ;; bindings one by one in that case, and merge after each update. 217 | (litable--alist-merge 218 | pvars 219 | (mapcar (lambda (it) 220 | (cons (car it) 221 | (litable--fake-eval (cadr it) enviroment 'let))) 222 | varlist)))) 223 | 224 | ;; TODO: this just sucks... make it better :P 225 | (defun litable--alist-merge (al1 al2) 226 | "Merge alists AL1 and AL2. 227 | 228 | Return a new copy independent of AL1 and AL2. 229 | 230 | If the same key is present in both alists, use the value from AL2 231 | in the result." 232 | (let ((re (--map (cons (car it) (cdr it)) al1))) 233 | (mapc (lambda (it) 234 | (let ((c (assoc (car it) re))) 235 | (if c 236 | (setcdr c (cdr it)) 237 | (!cons (cons (car it) (cdr it)) re)))) 238 | al2) 239 | re)) 240 | 241 | 242 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 243 | ;; argument propagation in the defuns 244 | 245 | (defun litable--make-subs-list (arg-names values) 246 | "Return the list of cons pairs with symbol name in car and value in cdr." 247 | (let (r) 248 | (--each (-zip arg-names values) 249 | ;; do we want to eval here? TODO: Make it a customizable option! 250 | (!cons (cons (car it) 251 | (if (and (listp (cdr it)) (eq 'quote (cadr it))) 252 | (cdr it) 253 | (eval (cdr it)))) r)) 254 | r)) 255 | 256 | (defun litable--in-exception-form () 257 | "Test if the point is in an exception form." 258 | (save-excursion 259 | (litable-backward-up-list) 260 | (let* ((s (sexp-at-point)) 261 | (ex-form (assq (car s) litable-exceptions))) 262 | (when ex-form 263 | (down-list) 264 | (forward-sexp (cdr ex-form)) 265 | (>= (point) me))))) 266 | 267 | (defun litable--at-let-variable-def-p (me) 268 | "Test if the point is after a let variable definition." 269 | (/= me (save-excursion 270 | (litable-backward-up-list) 271 | (down-list) 272 | (forward-sexp) 273 | (point)))) 274 | 275 | (defun litable--construct-needle (variables) 276 | "Return a regexp that will search for the variable symbols." 277 | (concat "\\_<" 278 | (regexp-opt (--map (regexp-quote (symbol-name it)) variables)) 279 | "\\_>")) 280 | 281 | ;; - maybe add different colors for different arguments that get 282 | ;; substituted. This might result in rainbows sometimes, maybe 283 | ;; undersirable 284 | ;; TODO: general warning: LONGASS FUNCTION! Refactor this into 285 | ;; something more managable. 286 | (defun litable-find-function-subs-arguments (form &optional depth) 287 | "Find the definition of \"form\" and substitute the arguments. 288 | 289 | If depth = 0, also evaluate the current form and print the result." 290 | (setq depth (or depth 0)) 291 | (let* ((symbol (and (listp form) (car form))) 292 | (name (and (symbolp symbol) (symbol-name symbol))) 293 | subs args needle) 294 | (when (and symbol 295 | (symbolp symbol) 296 | (not (keywordp symbol))) 297 | ;; recursively evaluate the arguments first 298 | (--each (cdr form) (litable-find-function-subs-arguments it (1+ depth))) 299 | (when (not (subrp (symbol-function symbol))) 300 | (save-excursion 301 | (save-restriction 302 | (widen) 303 | (goto-char 1) 304 | (when (re-search-forward (regexp-quote (concat "(defun " name)) nil t) 305 | (forward-list) (backward-list) 306 | ;; TODO: &rest, &key should be handled in some special 307 | ;; way when doing the substitution 308 | (setq args (->> (sexp-at-point) 309 | (delete '&optional) 310 | (delete '&rest))) 311 | (setq subs (litable--make-subs-list args (cdr form))) 312 | (save-restriction 313 | (narrow-to-defun) 314 | (litable-annotate-let-forms subs) 315 | (setq needle (litable--construct-needle args)) 316 | (let (mb me ms ignore) 317 | (while (re-search-forward needle nil t) 318 | (setq mb (match-beginning 0)) 319 | (setq me (match-end 0)) 320 | (setq ms (match-string 0)) 321 | ;; figure out the context here. If the sexp we're in is 322 | ;; on the exception list, move along. Maybe we shouldn't 323 | ;; censor some results though. TODO: Meditate on this 324 | (when (litable--in-exception-form) 325 | (setq ignore t)) 326 | ;; test the let form. TODO: this will go to special 327 | ;; function when we decide to do let-def-eval? 328 | (let ((in-var-form (litable--in-var-form-p))) 329 | (when in-var-form 330 | ;; we can still be at the "definition" 331 | ;; instance, that is: (>x< (blabla x)). This 332 | ;; should not get replaced by the normal value 333 | ;; but by the newly eval'd value 334 | (if (litable--at-let-variable-def-p me) 335 | (setq ignore 'let) 336 | (setq ignore 'let-def)))) 337 | (cond 338 | ((eq ignore 'let) 339 | (let* ((in-var-form (litable--in-var-form-p)) 340 | (vars (or (if in-var-form 341 | (litable-get-let-bound-parent-variable-values) 342 | (litable-get-let-bound-variable-values)) subs))) 343 | (litable--create-substitution-overlay mb me (cdr (assoc (intern ms) vars))))) 344 | ;; TODO: make this configurable too 345 | ((eq ignore 'let-def) 346 | (let ((vars (litable-get-let-bound-variable-values))) 347 | (when vars 348 | ;; TODO: make the face customizable 349 | (litable--create-substitution-overlay 350 | mb me (cdr (assoc (intern ms) vars)) 'font-lock-warning-face)))) 351 | ((not ignore) 352 | (let ((vars (or (litable-get-let-bound-variable-values) subs))) 353 | (litable--create-substitution-overlay mb me (cdr (assoc (intern ms) vars)))))) 354 | (setq ignore nil) 355 | ;; TODO: this can be precomputed and stored in the 356 | ;; let-form overlay. I think `regexp-opt' can be 357 | ;; fairly slow at times. 358 | (setq needle (litable--construct-needle 359 | (or (litable-get-let-bound-variables nil t) args))))) 360 | ;; if depth > 0 means we're updating a defun, print the 361 | ;; end result after the end of the defun 362 | ;; TODO: add a customize to print the partial result 363 | ;; also if depth = 0 (it would be same as the final 364 | ;; result, but maybe the defun is on different screen 365 | ;; and so it will be invisible otherwise.) 366 | (when (> depth 0) 367 | (save-excursion 368 | (end-of-defun) 369 | (backward-char) 370 | ;; TODO: make the face customizable 371 | (litable--print-result (eval form) (point) 'font-lock-constant-face))) 372 | ;; TODO: make the printing of input customizable 373 | (save-excursion 374 | (beginning-of-defun) 375 | (end-of-line) 376 | ;; TODO: make the face customizable 377 | (litable--print-input (cdr form) (point) 'font-lock-variable-name-face)))))))) 378 | (when (and (= depth 0) 379 | (nth 1 (syntax-ppss))) 380 | (let ((ostart (save-excursion 381 | (litable-goto-toplevel-form) 382 | (forward-list) 383 | (point)))) 384 | ;; TODO: make the face customizable 385 | (litable--print-result (eval form) ostart 'font-lock-warning-face))))) 386 | 387 | ;; TODO: both print-result and print-input should accumulate the 388 | ;; results in a variable (for each defun? -- alist?) and then only 389 | ;; print int in single overlay after all the updating is done 390 | ;; (i.e. when the final result is printed as well). Right now, each 391 | ;; input/output has its own overlay. 392 | 393 | ;; TODO: shorten the result if too long? Add customize limit for 394 | ;; cut-off. Maybe echo the full thing in the echo area/print in msg 395 | ;; log <- maybe not a good idea, it will produce tons of spam. 396 | (defun litable--print-result (result pos face) 397 | "Print the RESULT of evaluating form at POS. 398 | Fontify the result using FACE." 399 | (let* ((o (make-overlay pos pos)) 400 | (print-quoted t) 401 | (s (format " => %s" result))) 402 | (push o litable-overlays) 403 | (litable--set-result-overlay-priority o) 404 | (put-text-property 0 1 'cursor t s) 405 | (overlay-put o 406 | 'before-string 407 | (propertize 408 | ;; TODO: extract this format into customize 409 | s 410 | 'face face)))) 411 | 412 | (defun litable--print-input (input pos face) 413 | "Print the INPUT for the evaluated form at POS. 414 | Fontify the input using FACE." 415 | (let ((o (make-overlay pos pos)) 416 | (print-quoted t)) 417 | (push o litable-overlays) 418 | (litable--set-result-overlay-priority o) 419 | (overlay-put o 420 | ;; TODO: add customize to reverse the order of input args. Now it 421 | ;; resemples LIFO, should be FIFO! 422 | ;; 423 | ;; (defun foo (x) <= (- 10 5) <= 2 424 | ;; (1+ x)) => 6 => 3 425 | ;; 426 | ;; (+ (foo (- 10 5)) (foo 2) 3 4) => 16 427 | ;; before-string <-> after-string 428 | 'before-string 429 | (propertize 430 | ;; TODO: extract this format into customize 431 | (format " <= %s" (mapconcat 'prin1-to-string input ", ")) 432 | 'face face)))) 433 | 434 | (defun litable--create-substitution-overlay (start end value &optional face) 435 | "Create the overlay that shows the substituted value." 436 | ;; TODO: make the face customizable 437 | (setq face (or face 'font-lock-type-face)) 438 | (let (o (print-quoted t)) 439 | (setq o (make-overlay start end)) 440 | (push o litable-overlays) 441 | (litable--set-overlay-priority o) 442 | (overlay-put o 'display 443 | (propertize 444 | ;; TODO: extract this format into customize 445 | ;; TODO: customize max-length 446 | ;; for the subexpression, then 447 | ;; cut off and replace with 448 | ;; "bla..." 449 | (concat ms "{" 450 | (prin1-to-string value) "}") 451 | 'face face)))) 452 | 453 | 454 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 455 | ;; navigation 456 | 457 | (defun litable--next-sexp () 458 | (ignore-errors 459 | (forward-sexp)) 460 | (ignore-errors 461 | (forward-sexp)) 462 | (ignore-errors 463 | (backward-sexp))) 464 | 465 | ;; stolen from mastering emacs comments 466 | (defun litable-backward-up-list () 467 | "Stupid backward-up-list doesn't work from inside a string and 468 | I got tired of having to move outside the string to use it." 469 | (interactive) 470 | (when (in-string-p) 471 | (while (in-string-p) 472 | (backward-char))) 473 | (backward-up-list)) 474 | 475 | 476 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 477 | ;; updating the overlays 478 | 479 | (defun litable-goto-toplevel-form () 480 | (while (/= (car (syntax-ppss)) 0) (litable-backward-up-list))) 481 | 482 | (defun litable-update-defs (&optional a b c) 483 | (litable-remove-overlays) 484 | (when a 485 | (ignore-errors 486 | (let ((form (save-excursion 487 | (litable-goto-toplevel-form) 488 | (sexp-at-point)))) 489 | (litable-find-function-subs-arguments form))))) 490 | 491 | 492 | (defun litable-refresh () 493 | (interactive) 494 | (litable-update-defs 1)) 495 | 496 | ;; TODO: if the same function is eval'd twice, also all the overlays 497 | ;; are created twice. Maybe we should keep an alist (defun . overlays 498 | ;; in defun) and reuse/update them. But, until we hit performance 499 | ;; issues, doesn't matter -- for now fixed with priorities. 500 | (defvar litable-overlays nil) 501 | 502 | (defcustom litable-overlay-priority 0 503 | "Overlay priority" 504 | :type 'integer 505 | :group 'litable) 506 | 507 | (defcustom litable-result-overlay-priority 0 508 | "Restult overlay priority" 509 | :type 'integer 510 | :group 'litable) 511 | 512 | ;; internal variables 513 | (defvar litable--overlay-priority litable-overlay-priority) 514 | (defvar litable--result-overlay-priority litable-result-overlay-priority) 515 | 516 | (defun litable--set-overlay-priority (overlay) 517 | (setq litable--overlay-priority (1+ litable--overlay-priority)) 518 | (overlay-put overlay 'priority litable--overlay-priority)) 519 | 520 | (defun litable--set-result-overlay-priority (overlay) 521 | (setq litable--result-overlay-priority (1+ litable--result-overlay-priority)) 522 | (overlay-put overlay 'priority litable--result-overlay-priority)) 523 | 524 | (defun litable-remove-overlays () 525 | (--each litable-overlays (delete-overlay it)) 526 | (setq litable-overlays nil) 527 | (setq litable--overlay-priority litable-overlay-priority) 528 | (setq litable--result-overlay-priority litable-result-overlay-priority)) 529 | 530 | (defvar litable-mode-map (make-sparse-keymap) 531 | "litable mode map.") 532 | 533 | (defcustom litable-mode-hook nil 534 | "Hook for `litable-mode'." 535 | :type 'hook 536 | :group 'litable) 537 | 538 | (defun litable-init () 539 | "Initialize litable in the buffer." 540 | (add-hook 'after-change-functions 'litable-update-defs nil t) 541 | (run-hooks 'litable-mode-hook)) 542 | 543 | (defun litable-stop () 544 | "Stop litable in the buffer." 545 | (remove-hook 'after-change-functions 'litable-update-defs t) 546 | (litable-remove-overlays)) 547 | 548 | ;;;###autoload 549 | (define-minor-mode litable-mode 550 | "Toggle litable-mode" 551 | :lighter " litable" 552 | :keymap litable-mode-map 553 | :group 'litable 554 | (if litable-mode 555 | (litable-init) 556 | (litable-stop))) 557 | 558 | (provide 'litable) 559 | 560 | ;;; litable.el ends here 561 | --------------------------------------------------------------------------------