diff --git a/yasnippet.el b/yasnippet.el index 78ef38ac..b0028cc4 100644 --- a/yasnippet.el +++ b/yasnippet.el @@ -1652,6 +1652,89 @@ Here's a list of currently recognized directives: (setq group (split-string group "\\."))) (list key template name condition group expand-env file binding uuid))) +(defun yas--parse-template-for-mulkey (&optional file) + "Parse the template in the current buffer. + +Optional FILE is the absolute file name of the file being +parsed. + +Optional GROUP is the group where the template is to go, +otherwise we attempt to calculate it from FILE. + +Return a snippet-definition, i.e. a list + + (KEY TEMPLATE NAME CONDITION GROUP VARS LOAD-FILE KEYBINDING UUID) + +If the buffer contains a line of \"# --\" then the contents above +this line are ignored. Directives can set most of these with the syntax: + +# directive-name : directive-value + +Here's a list of currently recognized directives: + + * type + * name + * contributor + * condition + * group + * key + * expand-env + * binding + * uuid" + (goto-char (point-min)) + (let* ((type 'snippet) + (name (and file + (file-name-nondirectory file))) + (key nil) + template + bound + condition + (group (and file + (yas--calculate-group file))) + expand-env + binding + uuid + results) + (if (re-search-forward "^# --\\s-*\n" nil t) + (progn (setq template + (buffer-substring-no-properties (point) + (point-max))) + (setq bound (point)) + (goto-char (point-min)) + (while (re-search-forward "^# *\\([^ ]+?\\) *: *\\(.*?\\)[[:space:]]*$" bound t) + (when (string= "uuid" (match-string-no-properties 1)) + (setq uuid (match-string-no-properties 2))) + (when (string= "type" (match-string-no-properties 1)) + (setq type (if (string= "command" (match-string-no-properties 2)) + 'command + 'snippet))) + (when (string= "key" (match-string-no-properties 1)) + (cl-pushnew (match-string-no-properties 2) key)) + (when (string= "name" (match-string-no-properties 1)) + (setq name (match-string-no-properties 2))) + (when (string= "condition" (match-string-no-properties 1)) + (setq condition (yas--read-lisp (match-string-no-properties 2)))) + (when (string= "group" (match-string-no-properties 1)) + (setq group (match-string-no-properties 2))) + (when (string= "expand-env" (match-string-no-properties 1)) + (setq expand-env (yas--read-lisp (match-string-no-properties 2) + 'nil-on-error))) + (when (string= "binding" (match-string-no-properties 1)) + (setq binding (match-string-no-properties 2))))) + (setq template + (buffer-substring-no-properties (point-min) (point-max)))) + (unless (or key binding) + (cl-pushnew (and file (file-name-nondirectory file)) key)) + (when (eq type 'command) + (setq template (yas--read-lisp (concat "(progn" template ")")))) + (when group + (setq group (split-string group "\\."))) + (if key + (dolist (k key results) + (push (list k template (format "%s (%s)" name k) condition group expand-env file binding uuid) results)) + (setq results (list (list key template name condition group expand-env file binding uuid)))) + results)) + (defun yas--calculate-group (file) "Calculate the group for snippet file path FILE." (let* ((dominating-dir (locate-dominating-file file @@ -1832,6 +1915,55 @@ defining snippets. UUID is the snippet's \"unique-id\". Loading a second snippet file with the same uuid would replace the previous snippet. +You can use `yas--parse-template' to return such lists based on +the current buffers contents." + (if yas--creating-compiled-snippets + (let ((print-length nil)) + (insert ";;; Snippet definitions:\n;;;\n") + (dolist (snippet snippets) + ;; Fill in missing elements with nil. + (setq snippet (append snippet (make-list (- 10 (length snippet)) nil))) + ;; Move LOAD-FILE to SAVE-FILE because we will load from the + ;; compiled file, not LOAD-FILE. + (let ((load-file (nth 6 snippet))) + (setcar (nthcdr 6 snippet) nil) + (setcar (nthcdr 9 snippet) load-file))) + (insert (pp-to-string + `(yas-define-snippets ',mode ',snippets))) + (insert "\n\n")) + ;; Normal case. + (let ((snippet-table (yas--table-get-create mode)) + (template nil)) + (dolist (snippet snippets) + (setq template (yas--define-snippets-1 snippet + snippet-table))) + template))) + +(defun yas-define-snippets-for-mulkey (mode snippets) + "Define SNIPPETS for MODE. + +SNIPPETS is a list of snippet definitions, each taking the +following form + + (KEY TEMPLATE NAME CONDITION GROUP EXPAND-ENV LOAD-FILE KEYBINDING UUID SAVE-FILE) + +Within these, only KEY and TEMPLATE are actually mandatory. + +TEMPLATE might be a Lisp form or a string, depending on whether +this is a snippet or a snippet-command. + +CONDITION, EXPAND-ENV and KEYBINDING are Lisp forms, they have +been `yas--read-lisp'-ed and will eventually be +`yas--eval-for-string'-ed. + +The remaining elements are strings. + +FILE is probably of very little use if you're programatically +defining snippets. + +UUID is the snippet's \"unique-id\". Loading a second snippet +file with the same uuid would replace the previous snippet. + You can use `yas--parse-template' to return such lists based on the current buffers contents." (if yas--creating-compiled-snippets @@ -1936,6 +2068,58 @@ With prefix argument USE-JIT do jit-loading of snippets." (when interactive (yas--message 3 "Loaded snippets from %s." top-level-dir))) +(defun yas-load-directory-for-mulkey (top-level-dir &optional use-jit interactive) + "Load snippets in directory hierarchy TOP-LEVEL-DIR. + +Below TOP-LEVEL-DIR each directory should be a mode name. + +With prefix argument USE-JIT do jit-loading of snippets." + (interactive + (list (read-directory-name "Select the root directory: " nil nil t) + current-prefix-arg t)) + (unless yas-snippet-dirs + (setq yas-snippet-dirs top-level-dir)) + (let ((impatient-buffers)) + (dolist (dir (yas--subdirs top-level-dir)) + (let* ((major-mode-and-parents (yas--compute-major-mode-and-parents + (concat dir "/dummy"))) + (mode-sym (car major-mode-and-parents)) + (parents (cdr major-mode-and-parents))) + ;; Attention: The parents and the menus are already defined + ;; here, even if the snippets are later jit-loaded. + ;; + ;; * We need to know the parents at this point since entering a + ;; given mode should jit load for its parents + ;; immediately. This could be reviewed, the parents could be + ;; discovered just-in-time-as well + ;; + ;; * We need to create the menus here to support the `full' + ;; option to `yas-use-menu' (all known snippet menus are shown to the user) + ;; + (yas--define-parents mode-sym parents) + (yas--menu-keymap-get-create mode-sym) + (let ((fun (apply-partially #'yas--load-directory-1-for-mulkey dir mode-sym))) + (if use-jit + (yas--schedule-jit mode-sym fun) + (funcall fun))) + ;; Look for buffers that are already in `mode-sym', and so + ;; need the new snippets immediately... + ;; + (when use-jit + (cl-loop for buffer in (buffer-list) + do (with-current-buffer buffer + (when (eq major-mode mode-sym) + (yas--message 4 "Discovered there was already %s in %s" buffer mode-sym) + (push buffer impatient-buffers))))))) + ;; ...after TOP-LEVEL-DIR has been completely loaded, call + ;; `yas--load-pending-jits' in these impatient buffers. + ;; + (cl-loop for buffer in impatient-buffers + do (with-current-buffer buffer (yas--load-pending-jits)))) + (when interactive + (yas--message 3 "Loaded snippets from %s." top-level-dir))) + + (defun yas--load-directory-1 (directory mode-sym) "Recursively load snippet templates from DIRECTORY." (if yas--creating-compiled-snippets @@ -1954,6 +2138,24 @@ With prefix argument USE-JIT do jit-loading of snippets." (yas--message 4 "Loading snippet files from %s" directory) (yas--load-directory-2 directory mode-sym))))) +(defun yas--load-directory-1-for-mulkey (directory mode-sym) + "Recursively load snippet templates from DIRECTORY." + (if yas--creating-compiled-snippets + (let ((output-file (expand-file-name ".yas-compiled-snippets.el" + directory))) + (with-temp-file output-file + (insert (format ";;; Compiled snippets and support files for `%s'\n" + mode-sym)) + (yas--load-directory-2-for-mulkey directory mode-sym) + (insert (format ";;; Do not edit! File generated at %s\n" + (current-time-string))))) + ;; Normal case. + (unless (file-exists-p (expand-file-name ".yas-skip" directory)) + (unless (and (load (expand-file-name ".yas-compiled-snippets" directory) 'noerror (<= yas-verbosity 3)) + (progn (yas--message 4 "Loaded compiled snippets from %s" directory) t)) + (yas--message 4 "Loading snippet files from %s" directory) + (yas--load-directory-2 directory mode-sym))))) + (defun yas--load-directory-2 (directory mode-sym) ;; Load .yas-setup.el files wherever we find them ;; @@ -1980,6 +2182,36 @@ With prefix argument USE-JIT do jit-loading of snippets." (yas--load-directory-2 subdir mode-sym)))) +(defun yas--load-directory-2-for-mulkey (directory mode-sym) + ;; Load .yas-setup.el files wherever we find them + ;; + (yas--load-yas-setup-file (expand-file-name ".yas-setup" directory)) + (let* ((default-directory directory) + (snippet-defs nil) + parsed) + ;; load the snippet files + ;; + (with-temp-buffer + (dolist (file (yas--subdirs directory 'no-subdirs-just-files)) + (when (file-readable-p file) + ;; Erase the buffer instead of passing non-nil REPLACE to + ;; `insert-file-contents' (avoids Emacs bug #23659). + (erase-buffer) + (insert-file-contents file) + (setq parsed (yas--parse-template-for-mulkey file)) + (if (listp (car parsed)) ;; If car is a list + (dolist (sp parsed) + (push sp snippet-defs)) + (push parsed snippet-defs))))) + (when snippet-defs + (yas-define-snippets-for-mulkey mode-sym + snippet-defs)) + ;; now recurse to a lower level + ;; + (dolist (subdir (yas--subdirs directory)) + (yas--load-directory-2-for-mulkey subdir + mode-sym)))) + (defun yas--load-snippet-dirs (&optional nojit) "Reload the directories listed in `yas-snippet-dirs' or prompt the user to select one." @@ -2093,6 +2325,15 @@ prefix argument." ;;; Snippet compilation function +(defun yas-compile-directory-for-mulkey (top-level-dir) + "Create .yas-compiled-snippets.el files under subdirs of TOP-LEVEL-DIR. + +This works by stubbing a few functions, then calling +`yas-load-directory'." + (interactive "DTop level snippet directory?") + (let ((yas--creating-compiled-snippets t)) + (yas-load-directory-for-mulkey top-level-dir nil))) + (defun yas-compile-directory (top-level-dir) "Create .yas-compiled-snippets.el files under subdirs of TOP-LEVEL-DIR. @@ -2107,6 +2348,11 @@ This works by stubbing a few functions, then calling (interactive) (mapc #'yas-compile-directory (yas-snippet-dirs))) +(defun yas-recompile-all-for-mulkey () + "Compile every dir in `yas-snippet-dirs'." + (interactive) + (mapc #'yas-compile-directory-for-mulkey (yas-snippet-dirs))) + ;;; JIT loading ;;;