diff --git a/modules/feature/file-templates/autoload.el b/modules/feature/file-templates/autoload.el index ce0d08930..2566434c0 100644 --- a/modules/feature/file-templates/autoload.el +++ b/modules/feature/file-templates/autoload.el @@ -1,11 +1,79 @@ ;;; feature/file-templates/autoload.el -*- lexical-binding: t; -*- +;;;###autoload +(def-setting! :file-template (pred &rest plist) + "Register a file template. + +PRED can either be a regexp string or a major mode symbol. PLIST may contain +these properties: + + :when FUNCTION + Provides a secondary predicate. This function takes no arguments and is + executed from within the target buffer. If it returns nil, this rule will be + skipped over. + :trigger + The yasnippet trigger keyword used to trigger the target snippet. If + omitted, `+file-templates-default-trigger' is used. + :mode SYMBOL + What mode to get the yasnippet snippet from. If omitted, either PRED (if + it's a major-mode symbol) or the mode of the buffer is used. + :project BOOL + If non-nil, ignore this template if this buffer isn't in a project. + :ignore BOOL + If non-nil, don't expand any template for this file and don't test any other + file template rule against this buffer." + `(push (list ,pred ,@plist) +file-templates-alist)) + +;;;###autoload +(def-setting! :file-templates (&rest templates) + "Like `doom--set:file-template', but register many file templates at once." + `(setq +file-templates-alist (append (list ,@templates) +file-templates-alist))) + + +;; +;; Library +;; + +;;;###autoload +(defun +file-templates--expand (pred &rest plist) + "Auto insert a yasnippet snippet into current file and enter insert mode (if +evil is loaded and enabled)." + (when (and pred (not (plist-get plist :ignore))) + (let ((project (plist-get plist :project)) + (mode (plist-get plist :mode)) + (trigger (plist-get plist :trigger))) + (when (if project (doom-project-p) t) + (unless mode + (setq mode (if (symbolp pred) pred major-mode))) + (unless mode + (user-error "Couldn't determine mode for %s file template" pred)) + (unless trigger + (setq trigger +file-templates-default-trigger)) + (require 'yasnippet) + (unless yas-minor-mode + (yas-minor-mode-on)) + (when (and yas-minor-mode + (yas-expand-snippet + (yas--template-content + (cl-find trigger (yas--all-templates (yas--get-snippet-tables mode)) + :key #'yas--template-key :test #'equal))) + (and (featurep 'evil) evil-mode) + (and yas--active-field-overlay + (overlay-buffer yas--active-field-overlay) + (overlay-get yas--active-field-overlay 'yas--field))) + (evil-initialize-state 'insert)))))) + ;;;###autoload (defun +file-templates-get-short-path () - "TODO" + "Fetches a short file path for the header in Doom module templates." (when (string-match "/modules/\\(.+\\)$" buffer-file-truename) (match-string 1 buffer-file-truename))) + +;; +;; Commands +;; + ;;;###autoload (defun +file-templates/insert-license () "Insert a license file template into the current file." @@ -21,3 +89,10 @@ (uuid (yas-choose-value (mapcar #'car templates)))) (when uuid (yas-expand-snippet (cdr (assoc uuid templates)))))) + +;;;###autoload +(defun +file-templates/debug () + "Tests the current buffer and outputs the file template rule most appropriate +for it. This is used for testing." + (interactive) + (message "Found %s" (cl-find-if #'+file-template-p +file-templates-alist))) diff --git a/modules/feature/file-templates/config.el b/modules/feature/file-templates/config.el index 38a47dd49..511bca956 100644 --- a/modules/feature/file-templates/config.el +++ b/modules/feature/file-templates/config.el @@ -6,136 +6,142 @@ (expand-file-name "templates/" (file-name-directory load-file-name)) "The path to a directory of yasnippet folders to use for file templates.") -(def-setting! :file-template (regexp trigger mode &optional project-only-p) - "Register a file template (associated with TRIGGER, the uuid of the target -snippet) for empty files that match REGEXP in MODE (a major mode symbol). +(defvar +file-templates-alist () + "An alist of file template rules. The CAR of each rule is either a major mode +symbol or regexp string. The CDR is a plist. See `doom--set:file-template' for +more information.") -If PROJECT-ONLY-P is non-nil, the template won't be expanded if the buffer isn't -in a project." - `(+file-templates-add (list ,regexp ,trigger ,mode ,project-only-p))) +(defvar +file-templates-default-trigger "__" + "The default yasnippet trigger key (a string) for file template rules that +don't have a :trigger property in `+file-templates-alist'.") + + +;; +;; Bootstrap +;; + +(after! yasnippet + (add-to-list 'yas-snippet-dirs '+file-templates-dir 'append #'eq)) + +(defun +file-template-p (rule) + "Return t if RULE applies to the current buffer." + (let ((pred (car rule)) + (plist (cdr rule))) + (and (cond ((stringp pred) (string-match-p pred)) + ((symbolp pred) (eq major-mode pred))) + (or (not (plist-member plist :when)) + (funcall (plist-get plist :when) buffer-file-name)) + rule))) + +(defun +file-templates|init () + "Check if the current buffer is a candidate for file template expansion. It +must be non-read-only, empty, and there must be a rule in +`+file-templates-alist' that applies to it." + (when (and (not buffer-read-only) + (bobp) (eobp)) + (when-let* ((rule (cl-find-if #'+file-template-p +file-templates-alist))) + (apply #'+file-templates--expand rule)))) + +(add-hook 'find-file-hook #'+file-templates|init) + + +;; +;; File templates +;; + +(defun +file-templates-in-emacs-dirs-p (file) + "Returns t if FILE is in Doom or your private directory." + (or (file-in-directory-p file doom-private-dir) + (file-in-directory-p file doom-emacs-dir))) + +(setq +file-templates-alist + `(;; General + (gitignore-mode) + (dockerfile-mode) + ("/docker-compose\\.yml$" :mode yaml-mode) + ("/Makefile$" :mode makefile-gmake-mode) + ;; elisp + ("/.dir-locals.el$") + ("/packages\\.el$" :when +file-templates-in-emacs-dirs-p + :trigger "__doom-packages" + :mode emacs-lisp-mode) + ("/doctor\\.el$" :when +file-templates-in-emacs-dirs-p + :trigger "__doom-doctor" + :mode emacs-lisp-mode) + ("/test/.+\\.el$" :when +file-templates-in-emacs-dirs-p + :trigger "__doom-test" + :mode emacs-lisp-mode) + ("\\.el$" :when +file-templates-in-emacs-dirs-p + :trigger "__doom-module" + :mode emacs-lisp-mode) + ("-test\\.el$" :mode emacs-ert-mode) + (emacs-lisp-mode :trigger "__initfile") + (snippet-mode) + ;; C/C++ + ("/main\\.c\\(?:c\\|pp\\)$" :trigger "__main.cpp" :mode c++-mode) + ("/win32_\\.c\\(?:c\\|pp\\)$" :trigger "__winmain.cpp" :mode c++-mode) + ("\\.c\\(?:c\\|pp\\)$" :trigger "__cpp" :mode c++-mode) + ("\\.h\\(?:h\\|pp\\|xx\\)$" :trigger "__hpp" :mode c++-mode) + ("\\.h$" :trigger "__h" :mode c-mode) + (c-mode :trigger "__c" :mode c-mode) + ;; go + ("/main\\.go$" :trigger "__main.go" :mode go-mode :project t) + (go-mode :trigger "__.go") + ;; web-mode + ("/normalize\\.scss$" :trigger "__normalize.scss" :mode scss-mode) + ("/master\\.scss$" :trigger "__master.scss" :mode scss-mode) + ("\\.html$" :trigger "__.html" :mode web-mode) + (scss-mode) + ;; java + ("/main\\.java$" :trigger "__main" :mode java-mode) + ("/build\\.gradle$" :trigger "__build.gradle" :mode android-mode) + ("/src/.+\\.java$" :mode java-mode) + ;; javascript + ("/package\\.json$" :trigger "__package.json" :mode json-mode) + ("/bower\\.json$" :trigger "__bower.json" :mode json-mode) + ("/gulpfile\\.js$" :trigger "__gulpfile.js" :mode js-mode) + ("/webpack\\.config\\.js$" :trigger "__webpack.config.js" :mode js-mode) + ("\\.js\\(?:on\\|hintrc\\)$" :mode json-mode) + ;; Lua + ("/main\\.lua$" :trigger "__main.lua" :mode love-mode) + ("/conf\\.lua$" :trigger "__conf.lua" :mode love-mode) + ;; Markdown + (markdown-mode) + ;; Org + ("\\.org$" :trigger "__" :mode org-mode) + ("/README\\.org$" + :when +file-templates-in-emacs-dirs-p + :trigger "__doom-readme" + :mode org-mode) + ;; PHP + ("\\.class\\.php$" :trigger "__.class.php" :mode php-mode) + (php-mode) + ;; Python + ;; TODO ("tests?/test_.+\\.py$" :trigger "__" :mode nose-mode) + ;; TODO ("/setup\\.py$" :trigger "__setup.py" :mode python-mode) + (python-mode) + ;; Ruby + ("/lib/.+\\.rb$" :trigger "__module" :mode ruby-mode :project t) + ("/spec_helper\\.rb$" :trigger "__helper" :mode rspec-mode :project t) + ("_spec\\.rb$" :mode rspec-mode :project t) + ("/\\.rspec$" :trigger "__.rspec" :mode rspec-mode :project t) + ("\\.gemspec$" :trigger "__.gemspec" :mode ruby-mode :project t) + ("/Gemfile$" :trigger "__Gemfile" :mode ruby-mode :project t) + ("/Rakefile$" :trigger "__Rakefile" :mode ruby-mode :project t) + (ruby-mode) + ;; Rust + ("/Cargo.toml$" :trigger "__Cargo.toml" :mode rust-mode) + ("/main\\.rs$" :trigger "__main.rs" :mode rust-mode) + ;; Slim + ("/\\(?:index\\|main\\)\\.slim$" :mode slim-mode) + ;; Shell scripts + ("\\.zunit$" :trigger "__zunit" :mode sh-mode) + (fish-mode) + (sh-mode) + )) ;; ;; Plugins ;; -(def-package! autoinsert ; built-in - :commands (auto-insert-mode auto-insert) - :init - (setq auto-insert-query nil ; Don't prompt before insertion - auto-insert-alist nil) ; Tabula rasa - - (after! yasnippet - (cl-pushnew '+file-templates-dir yas-snippet-dirs :test #'eq)) - - ;; load autoinsert as late as possible - (defun +file-templates|init () - (and (not buffer-read-only) - (bobp) (eobp) - (remove-hook 'find-file-hook #'+file-templates|init) - (auto-insert))) - (add-hook 'find-file-hook #'+file-templates|init) - - :config - (auto-insert-mode 1) - - (defun +file-templates--expand (key &optional mode project-only) - "Auto insert a yasnippet snippet into the blank file." - (when (if project-only (doom-project-p) t) - (require 'yasnippet) - (unless yas-minor-mode - (yas-minor-mode-on)) - (when (and yas-minor-mode - (yas-expand-snippet - (yas--template-content - (cl-find key (yas--all-templates (yas--get-snippet-tables mode)) - :key #'yas--template-key :test #'equal))) - (and (featurep 'evil) evil-mode) - (and yas--active-field-overlay - (overlay-buffer yas--active-field-overlay) - (overlay-get yas--active-field-overlay 'yas--field))) - (evil-initialize-state 'insert)))) - - (defun +file-templates-add (args) - (cl-destructuring-bind (regexp trigger &optional mode project-only-p) args - (push `(,regexp . (lambda () (+file-templates--expand ,trigger ',mode ,project-only-p))) - auto-insert-alist))) - - (mapc #'+file-templates-add - (let* ((dirs (mapcar (lambda (path) (string-remove-prefix (expand-file-name "~") path)) - (cl-remove-duplicates - (append (list doom-emacs-dir doom-private-dir) - (mapcar #'file-truename (list doom-emacs-dir doom-private-dir))) - :test 'string=))) - (doom (regexp-opt dirs))) - `(;; General - ("/\\.gitignore$" "__" gitignore-mode) - ("/Dockerfile$" "__" dockerfile-mode) - ("/docker-compose.yml$" "__" yaml-mode) - ("/Makefile$" "__" makefile-gmake-mode) - ;; elisp - ("\\.el$" "__initfile" emacs-lisp-mode) - ("/.dir-locals.el$" nil) - ("-test\\.el$" "__" emacs-ert-mode) - (,(concat doom ".+\\.el$") "__doom-module" emacs-lisp-mode) - (,(concat doom "\\(?:.+/\\)?packages\\.el$") "__doom-packages" emacs-lisp-mode) - (,(concat doom "\\(?:.+/\\)?test/.+\\.el$") "__doom-test" emacs-lisp-mode) - (snippet-mode "__" snippet-mode) - ;; C/C++ - ("\\.h$" "__h" c-mode) - ("\\.c$" "__c" c-mode) - ("\\.h\\(h\\|pp|xx\\)$" "__hpp" c++-mode) - ("\\.\\(cc\\|cpp\\)$" "__cpp" c++-mode) - ("/main\\.\\(cc\\|cpp\\)$" "__main.cpp" c++-mode) - ("/win32_\\.\\(cc\\|cpp\\)$" "__winmain.cpp" c++-mode) - ;; go - ("\\.go$" "__.go" go-mode) - ("/main\\.go$" "__main.go" go-mode t) - ;; web-mode - ("\\.html$" "__.html" web-mode) - ("\\.scss$" "__" scss-mode) - ("/master\\.scss$" "__master.scss" scss-mode) - ("/normalize\\.scss$" "__normalize.scss" scss-mode) - ;; java - ("/src/.+\\.java$" "__" java-mode) - ("/main\\.java$" "__main" java-mode) - ("/build\\.gradle$" "__build.gradle" android-mode) - ;; javascript - ("\\.\\(json\\|jshintrc\\)$" "__" json-mode) - ("/package\\.json$" "__package.json" json-mode) - ("/bower\\.json$" "__bower.json" json-mode) - ("/gulpfile\\.js$" "__gulpfile.js" js-mode) - ("/webpack\\.config\\.js$" "__webpack.config.js" js-mode) - ;; Lua - ("/main\\.lua$" "__main.lua" love-mode) - ("/conf\\.lua$" "__conf.lua" love-mode) - ;; Markdown - ("\\.md$" "__" markdown-mode) - ;; Org - ("\\.org$" "__" org-mode) - (,(concat doom "/README\\.org$") "__doom-readme" org-mode) - ;; PHP - ("\\.php$" "__" php-mode) - ("\\.class\\.php$" "__.class.php" php-mode) - ;; Python - ;;("tests?/test_.+\\.py$" "__" nose-mode) - ;;("/setup\\.py$" "__setup.py" python-mode) - ("\\.py$" "__" python-mode) - ;; Ruby - ("\\.rb$" "__" ruby-mode) - ("/Rakefile$" "__Rakefile" ruby-mode t) - ("/Gemfile$" "__Gemfile" ruby-mode t) - ("/\\.rspec$" "__.rspec" rspec-mode) - ("\\.gemspec$" "__.gemspec" ruby-mode t) - ("/spec_helper\\.rb$" "__helper" rspec-mode t) - ("/lib/.+\\.rb$" "__module" ruby-mode t) - ("_spec\\.rb$" "__" rspec-mode t) - ;; Rust - ("/main\\.rs$" "__main.rs" rust-mode) - ("/Cargo.toml$" "__Cargo.toml" rust-mode) - ;; Slim - ("/\\(index\\|main\\)\\.slim$" "__" slim-mode) - ;; Shell scripts - ("\\.z?sh$" "__" sh-mode) - ("\\.fish$" "__" fish-mode) - ("\\.zunit$" "__zunit" sh-mode))))) diff --git a/modules/feature/file-templates/templates/emacs-lisp-mode/__doom-doctor b/modules/feature/file-templates/templates/emacs-lisp-mode/__doom-doctor new file mode 100644 index 000000000..af30ef14b --- /dev/null +++ b/modules/feature/file-templates/templates/emacs-lisp-mode/__doom-doctor @@ -0,0 +1,3 @@ +;;; `(+file-templates-get-short-path)` -*- lexical-binding: t; -*- + +$0