changeset 405: |
1816f9c53453 |
parent: |
d876b572b5b9
|
child: |
71baf0d4768d |
author: |
Richard Westhaver <ellis@rwest.io> |
date: |
Tue, 04 Jun 2024 18:53:40 -0400 |
permissions: |
-rw-r--r-- |
description: |
work on sk.el |
1 ;;; sk.el --- skel Emacs Mode -*- lexical-binding: t; -*- 3 ;; skel-mode, skel-minor-mode,skt-minor-mode, sk-classes 5 ;; Copyright (C) 2023 The Compiler Company 7 ;; Author: ellis <ellis@rwest.io> 8 ;; Keywords: languages, lisp 10 ;; This program is free software; you can redistribute it and/or modify 11 ;; it under the terms of the GNU General Public License as published by 12 ;; the Free Software Foundation, either version 3 of the License, or 13 ;; (at your option) any later version. 15 ;; This program is distributed in the hope that it will be useful, 16 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 17 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 ;; GNU General Public License for more details. 20 ;; You should have received a copy of the GNU General Public License 21 ;; along with this program. If not, see <https://www.gnu.org/licenses/>. 27 (eval-and-compile (require 'eieio) 29 (require 'sxp (expand-file-name "sxp.el" (join-paths user-emacs-directory "lib/"))) 33 (defvar skel-debug nil) 34 (when skel-debug (require 'ede))) 36 (defvar skel-version "0.1.0") 39 "skel customization group." 42 (defcustom skel-minor-mode-map-prefix "C-c C-." 43 "Prefix for `skel-minor-mode' keymap." 47 (defcustom skel-triggers nil 48 "Association of symbols to a specific condition which can be used 49 to trigger `skel-actions' based on the `skel-behavior' value." 50 :type '(list function) 53 (defcustom skel-actions nil 54 "Array of actions which may be performed on skeletons." 58 (defcustom skel-id-prefix "sk" 59 "Default prefix for `make-id'." 63 (defvar-keymap skel-minor-mode-map 64 :doc "skel-minor-mode keymap." 66 :prefix 'skel-minor-mode-map) 68 (define-minor-mode skel-minor-mode 73 :keymap skel-minor-mode-map 75 (keymap-local-set skel-minor-mode-map-prefix skt-minor-mode-map)) 78 (define-derived-mode skel-mode lisp-mode "SKEL" 82 (defun maybe-skel-minor-mode () 83 "Check the current environment and determine if `skel-minor-mode' should 84 be enabled. This function is added as a hook to 85 `lisp-data-mode-hook'.") 87 (defvar skel-hashtable (make-hash-table :test #'equal) 88 "Internal table of available skeletons.") 90 (defvar skel-stack nil "Internal stack of skeletons.") 92 (defcustom skel-state 'passive 93 "State toggle for the `skel' system. Base states are passive and 98 (defvar skel-active-map nil 99 "List of cons cells of the form (SYM . BODY...) where SYM is a member of 102 (defvar skel-passive-map nil 103 "list of cons cells of the form (SYM . BODY...) where SYM is a member of 106 (defmacro make-id (&optional pre) 107 `(let ((pre ,(if-let (pre) (concat skel-id-prefix "-" pre "-") (concat skel-id-prefix "-"))) 108 (current-time-list nil)) 109 (symb pre (prog1 gensym-counter (setq gensym-counter (1+ gensym-counter))) (format "%x" (car (current-time)))))) 111 (defmacro defcmd (name &rest body) `(defun ,name nil (interactive) ,@body)) 114 ((id :initarg :id :initform (make-id))) 115 :documentation "Base class for skeleton objects. Inherits from `sxp'." 118 (defcmd sk-classes (eieio-class-children 'sk)) 120 (defmacro def-sk-class (name doc &optional slots superclasses) 121 "Define a new class with superclass of `skel'+SUPERCLASSES, SLOTS, 124 `(defclass ,(symb "sk-" name) 125 ,(if superclasses `(sk ,@superclasses) '(sk)) 128 (:id :initarg :id :initform (make-id ,(symbol-name name)) :accessor id)) 129 `((:id :initarg :id :initform (make-id ,(symbol-name name)) :accessor id))) 130 :documentation ,doc)) 132 (def-sk-class target "Target skeleton class.") 133 (def-sk-class source "Source skeleton class.") 135 "Config skeleton class." 136 ((target :initarg :target :initform nil :type (or null sk-target)) 137 (rules :initarg :source :initform nil :type (or null sk-source)))) 139 (def-sk-class project 140 "Project skeleton class." 141 ((type :initarg :type :initform nil :accessor sk-project-type :type (or null symbol)) 142 (rules :initarg :rules :initform nil :accessor sk-project-rules :type list))) 145 "Initialize the skel library." 147 (add-to-list 'auto-mode-alist '("skelfile" . skel-mode)) 148 (add-to-list 'auto-mode-alist '("\\.sk\\'" . skel-mode))) 151 ;; ref: https://raw.githubusercontent.com/xFA25E/skempo/master/skempo.el 153 ;; (defun modify-lisp-syntax-tables () 154 ;; (modify-syntax-entry ?* "w" (syntax-table)) 155 ;; (modify-syntax-entry ?- "w" (syntax-table))) 156 ;; (dolist (hook '(lisp-mode-hook emacs-lisp-mode-hook)) 157 ;; (add-hook hook #'modify-lisp-syntax-tables)) 159 (defun skt--tags-variable (mode) 160 "Return a tempo tags variable's symbol for MODE." 162 (intern (replace-regexp-in-string 163 (rx "-mode" eos) "-skt-tags" 164 (symbol-name mode))))) 166 (defun skt--remove-tag-list (tag-list) 167 "Remove TAG-LIST from `tempo-local-tags'." 168 (setf (alist-get tag-list tempo-local-tags nil t) nil)) 170 (defun skt--insert-mark (marker) 171 "Insert a MARKER to `tempo-marks' while keeping it sorted. 172 Remove duplicate marks from `tempo-marks'. Set to nil removed 173 markers. This function is used as an :override advice to 174 `tempo-insert-mark', because the original function does not 175 remove duplicate elements. Duplicate markers appear when the 176 buffer gets smaller, markers start pointing to the same location. 177 We don't want that, because a lot of useless markers can slow 179 (if (not tempo-marks) 180 (setq tempo-marks (list marker)) 181 (let ((markers tempo-marks)) 183 ((< marker (car markers)) 184 (setq tempo-marks (cons marker tempo-marks))) 186 (while (and (cdr markers) (<= (cadr markers) marker)) 187 (if (/= (car markers) (cadr markers)) 188 (setq markers (cdr markers)) 189 (when (markerp (cadr markers)) (set-marker (cadr markers) nil)) 190 (setcdr markers (cddr markers)))) 192 (if (= marker (car markers)) 193 (when (markerp marker) (set-marker marker nil)) 194 (setcdr markers (cons marker (cdr markers)))))) 197 (if (/= (car markers) (cadr markers)) 198 (setq markers (cdr markers)) 199 (when (markerp (cadr markers)) (set-marker (cadr markers) nil)) 200 (setcdr markers (cddr markers))))))) 202 (defun skt-add-tag (tag template &optional tag-list) 203 "Add a TEMPLATE TAG to TAG-LIST or to `tempo-tags'. 204 It is an :override function for `tempo-add-tag'. The original 205 function does not update identical tags." 206 (interactive "sTag: \nCTemplate: ") 207 (let ((tag-list (or tag-list 'tempo-tags))) 208 (if-let ((value (assoc tag (symbol-value tag-list)))) 209 (setcdr value template) 210 (set tag-list (cons (cons tag template) (symbol-value tag-list)))) 211 (tempo-invalidate-collection))) 213 (defun skt--list-derived-modes (mode) 214 "List all derived modes of MODE + MODE itself." 217 (when-let ((alias (symbol-function mode))) 218 (when (symbolp alias) 221 (setq mode (get mode 'derived-mode-parent)) ) 225 (defvar-keymap skt-minor-mode-map 226 :doc "skt-minor-mode keymap." 228 :prefix 'skt-minor-mode-map 231 (define-minor-mode skt-minor-mode 232 "Minor mode for skt-templates." 235 :keymap skt-minor-mode-map 236 (let* ((modes (skt--list-derived-modes major-mode)) 237 (tag-vars (mapcar #'skt--tags-variable modes)) 238 (bound-tag-vars (cl-delete-if-not #'boundp tag-vars))) 240 (mapc #'tempo-use-tag-list bound-tag-vars) 241 (mapc #'skt--remove-tag-list bound-tag-vars)))) 243 (defun skt-register-auto-insert (rx template) 244 "Associate a template with a file regexp and insert into `auto-insert-alist'." 245 (cl-pushnew (cons rx template) auto-insert-alist)) 247 (defun skt--define-template (function-symbol body &optional docstring) 248 "Define a tempo template with BODY. 249 This will generate a function with FUNCTION-SYMBOL and 252 The main purpose of this function is to have a better controlled 253 alternative to `tempo-define-template'." 254 (let ((template-symbol (gensym (symbol-name function-symbol)))) 255 (set template-symbol body) 256 (defalias function-symbol 257 (lambda (&optional arg) 259 (tempo-insert-template template-symbol (xor tempo-insert-region arg))) 262 (defun skt--define-skeleton (function-symbol body &optional docstring) 263 "Define a skeleton template with BODY. 264 This will generate a function with FUNCTION-SYMBOL and 267 The main purpose of this function is to have a better controlled 268 alternative to `define-skeleton', especially because it is a 269 function instead of a macro." 270 (defalias function-symbol 271 (lambda (&optional str arg) 272 (interactive "*P\nP") 273 (skeleton-proxy-new body str arg)) 276 (defun skt--define-function (function-symbol function &optional docstring) 277 "This will generate an alias to FUNCTION with FUNCTION-SYMBOL. 278 DOCSTRING is used as a docstring to FUNCTION-SYMBOL." 279 (defalias function-symbol function docstring)) 281 (defun skt--mode-name (mode) 282 "Get MODE name without a -mode suffix." 283 (string-trim-right (symbol-name mode) (rx "-mode" eos))) 285 (defun skt--function-name (name modes) 286 "Generate a name for a skt template function. 287 NAME and MODES are used to generate unique, but consistent 289 (concat "skt-template-" 290 (mapconcat (lambda (mode) (concat (skt--mode-name mode) "-")) 291 (sort modes #'string<) "") 294 (defun skt--mode-abbrev-table (mode) 295 "Get abbrev table for MODE or `global-abbrev-table' if nil." 297 (derived-mode-abbrev-table-name mode) 298 'global-abbrev-table)) 300 (defun skt--abbrev-table (mode) 301 "Get skt abbrev table for MODE." 302 (intern (concat "skt-" (symbol-name (skt--mode-abbrev-table mode))))) 304 (defun skt--abbrev-table-names (table) 305 "Return abbrev TABLE names." 307 (mapatoms (lambda (abbrev) 308 (when (symbol-value abbrev) 309 (push (symbol-name abbrev) names))) 310 (symbol-value table)) 313 (defun skt--modes (mode) 314 "Normalize MODE argument." 315 (cond ((consp mode) mode) 317 ((symbolp mode) (list mode)))) 320 (defun skt--define (define-function name modes tag abbrev docstring body) 321 "Define a skt template. 323 DEFINE-FUNCTION is a function that takes a function symbol, BODY 324 and DOCSTRING as its arguments. It must define a new function 325 with that symbol and that docstring. 327 NAME is a string used in generating a function symbol, TAG and 330 MODES is a list of modes for which TAG and ABBREV will be 331 created. If it's nil, TAG and ABBREV will be generated 334 TAG/ABBREV is a boolean, which indicates whether a tag/abbrev 335 must be created for this template. 337 DOCSTRING is a string (or nil) which will be supplied to 340 BODY is an arbitrary argument passed to DEFINE-FUNCTION." 341 (let* ((function-symbol (intern (skt--function-name name modes))) 342 (modes (or modes '(nil)))) 343 (funcall define-function function-symbol body docstring) 344 (put function-symbol 'no-self-insert t) 347 (let ((tag-symbol (gensym (symbol-name function-symbol)))) 348 (if (eq #'skt--define-template define-function) 349 (set tag-symbol body) 350 (set tag-symbol `((ignore (,function-symbol))))) 352 (let ((var (skt--tags-variable mode))) 355 (tempo-add-tag name tag-symbol var))) 356 (dolist (buffer (buffer-list)) 357 (with-current-buffer buffer 358 (when (and (or (equal '(nil) modes) (apply #'derived-mode-p modes)) 361 (skt-minor-mode 1)))))) 365 (let ((mode-table (skt--mode-abbrev-table mode)) 366 (table (skt--abbrev-table mode))) 367 (define-abbrev-table mode-table nil) 368 (define-abbrev-table table nil :case-fixed t :skt t) 369 (define-abbrev (symbol-value table) name "" function-symbol 370 :case-fixed t :system t :skt t) 372 (let* ((names (skt--abbrev-table-names table)) 373 (regexp (concat (regexp-opt names "\\_<\\(") " *"))) 374 (abbrev-table-put (symbol-value table) :regexp regexp)) 376 (let ((parents (abbrev-table-get (symbol-value mode-table) :parents))) 377 (cl-pushnew (symbol-value table) parents :test #'eq) 378 (abbrev-table-put (symbol-value mode-table) :parents parents))))) 383 (cl-defmacro skt-define-template (name (&key mode tag abbrev docstring) &body body) 384 "Define a tempo template. 385 This macro defines a new tempo template or updates the old one. 386 NAME is a symbol. ARGS is a list of the form ([KEY VALUE]...) 387 where each KEY can be one of :tag, :abbrev, :docstring or :mode. 389 If KEY is :tag, VALUE should be a boolean. If VALUE is non-nil, 390 then a tempo tag with NAME will be created for this template. 392 If KEY is :abbrev, VALUE should be a boolean. If VALUE is 393 non-nil, then a NAME abbrev will be created for this template. 395 If KEY is :docstring, VALUE should be a string. It will be a 396 docstring of the generated function. 398 If KEY is :mode, VALUE should be a list of modes or single mode. 399 If this option is provided, than a tempo tag and an abbrev will 400 be created for these modes, otherwise they will be global (if 401 :tag and :abbrev options were provided, of course). 403 BODY is a sequence of tempo elements that will be passed as a 404 list directly to `tempo-define-template's second argument. 407 \(skt-define-template defvar (:mode emacs-lisp-mode :tag t :abbrev t 408 :docstring \"defvar template\") 409 \"(defvar \" (string-trim-right (buffer-name) (rx \".el\" eos)) \"-\" p n> 411 `(skt--define 'skt--define-template ,(symbol-name name) 412 ',(skt--modes mode) ,tag ,abbrev ,docstring ',body)) 415 (cl-defmacro skt-define-skeleton (name (&key mode tag abbrev docstring) &rest body) 416 "Define skeleton template. 417 See `skt-define-template' for explanation of NAME, MODE, TAG, 418 ABBREV and DOCSTRING. 420 BODY is a sequence of skeleton elements that will be passed 421 directly to `define-skeleton'. 424 \(skt-define-skeleton defun (:mode (emacs-lisp-mode lisp-interaction-mode) 426 :docstring \"defun template\") 427 \"(defun \" str \" (\" @ - \")\" \n 429 `(skt--define #'skt--define-skeleton ,(symbol-name name) 430 ',(skt--modes mode) ,tag ,abbrev ,docstring ',body)) 433 (cl-defmacro skt-define-function (name (&key mode tag abbrev docstring) function) 434 "Define FUNCTION template. 435 See `skt-define-template' for explanation of NAME, MODE, TAG, 436 ABBREV and DOCSTRING. 438 The main purpose of this macro, is to create tempo tags and 439 abbrevs for existing skeleton templates, such as `sh-case'. 442 \(skt-define-function shcase (:tag t :abbrev t :mode `sh-mode') `sh-case')" 443 `(skt--define #'skt--define-function ,(symbol-name name) 444 ',(skt--modes mode) ,tag ,abbrev ,docstring ',function)) 446 (defun skt--complete-template (string tag-list) 447 "An :override advice function for `tempo-display-completions'. 448 Show completion for STRING in a TAG-LIST. After selection 451 Rewritten because the original function uses an old way of 452 displaying completions in a separate buffer, which is not 453 clickable anyway. Now it uses new (compared to the originial 454 tempo package) and shiny `completing-read' interface." 455 (let* ((tags (mapcar #'car tag-list)) 456 (tag (completing-read "Skt: " tags nil t string))) 457 (delete-char (- (length string))) 458 (tempo-insert-template (cdr (assoc tag tag-list)) nil))) 461 (defcustom skt-enable-tempo-elements nil 462 "Enable extra tempo elements. 463 These elements add conditionals and looping support for tempo 464 like those in skeleton, making skeleton pretty much obsolete. 466 If you want to set this option from ELisp, you have to remove 467 `skt-tempo-user-elements' from `tempo-user-elements' on nil 468 and add it on non-nil." 469 :type '(boolean :tag "Enable tempo elements?") 470 :set (lambda (variable value) 472 (add-hook 'tempo-user-functions #'skt-tempo-user-elements) 473 (remove-hook 'tempo-user-functions #'skt-tempo-user-elements)) 474 (set-default variable value)) 477 (defcustom skt-completing-read nil 478 "Override default `tempo-display-completions'. 479 By default it uses a completion buffer to show completions. This 480 option overrides this function to use `completing-read' to select 481 partial skt tag or complete tag on region. 483 If you wish to set this variable from ELisp code, you have to 484 remove `skt--complete-template' advice from 485 `tempo-display-completions' on nil and add it as on :override 487 :type '(boolean :tag "Override?") 488 :set (lambda (variable value) 490 (advice-add 'tempo-display-completions :override #'skt--complete-template) 491 (advice-remove 'tempo-display-completions #'skt--complete-template)) 492 (set-default variable value)) 495 (defcustom skt-delete-duplicate-marks nil 496 "Override default `tempo-insert-mark'. 497 Marks are used to jump on points of interest in a template. By 498 default `tempo-insert-mark' does not remove duplicate marks. 499 Duplicate marks might appear when the buffer shrinks and some of 500 the marks start pointing to the same location. This option tries 501 to fix this by checking for duplicate marks every time the 502 function is called. Emacs might get slower with a lot of 505 If you want to set this option from ELisp, you have to remove 506 `skt--insert-mark' advice from `tempo-insert-mark' on nil and 507 add it as on :override advice on non-nil." 508 :type '(boolean :tag "Override?") 509 :set (lambda (variable value) 511 (advice-add 'tempo-insert-mark :override #'skt--insert-mark) 512 (advice-remove 'tempo-insert-mark #'skt--insert-mark)) 513 (set-default variable value)) 517 (put 'tempo-define-template 'lisp-indent-function 1) 518 (put 'skt-define-template 'lisp-indent-function 2) 519 (put 'skt-define-skeleton 'lisp-indent-function 2) 520 (put 'skt-define-function 'lisp-indent-function 2)) 523 (defvar skt-tempo-else-key (kbd "C-M-g") 524 "Key used to execute else branch in tempo conditional.") 526 (defun skt-tempo--prompt (prompt) 527 "Make prompt for tempo conditional. 528 PROMPT is preceded with `skt-tempo-else-key'." 529 (concat "(" (key-description skt-tempo-else-key) " to quit) " prompt)) 531 (defun skt-tempo-user-elements (element) 532 "Support for conditional and looping tempo elements. 533 The following forms are supported for ELEMENT: 535 \(:if (PROMPT VAR) THEN ELSE) 537 \(:when (PROMPT VAR) BODY...) 539 \(:while (PROMPT VAR) BODY...) 541 PROMPT is a string used to read value for VAR. VAR is a tempo 542 variable symbol. Its value can be read with s, as usual. BODY, 543 THEN and ELSE are tempo elements. To abort the execution of 544 these elements, user must press `skt-tempo-else-key'. 546 The main purpose of this extension is to mimic skeleton 547 conditionals and iterative templats. Skeleton becomes pretty 548 much obsolete with this extension." 550 (`(:if (,(and (pred stringp) prompt) ,(and (pred symbolp) var)) ,then ,else) 551 (let ((prompt (skt-tempo--prompt prompt)) 552 (map (make-sparse-keymap))) 553 (set-keymap-parent map minibuffer-local-map) 554 (define-key map skt-tempo-else-key 555 (lambda () (interactive) (throw 'else else))) 557 (tempo-save-named var (read-from-minibuffer prompt nil map)) 559 (`(:when (,(and (pred stringp) prompt) ,(and (pred symbolp) var)) . ,body) 560 `(:if (,prompt ,var) (l ,@body) (l))) 561 (`(:while (,(and (pred stringp) prompt) ,(and (pred symbolp) var)) . ,body) 562 `(:when (,prompt ,var) ,@body ,element))))