diff --git a/core/core-defuns.el b/core/core-defuns.el index 97b0c8403..34ac2969a 100644 --- a/core/core-defuns.el +++ b/core/core-defuns.el @@ -82,36 +82,90 @@ Examples: (if (listp hook) hook (list hook))))) funcs) `(progn ,@forms))) -(cl-defmacro associate! (mode &key in +(cl-defmacro associate! (mode &key minor + &key in &key match &key files &allow-other-keys) "Associate a major or minor mode to certain patterns and project files." (declare (indent 1)) - (let* ((minor-p (assoc mode minor-mode-alist))) - `(progn - (,@(cond ((or files in) - (when (and files (not (or (listp files) - (stringp files)))) - (user-error "associate! :files expects a string or list of strings")) - (let ((hook-name (intern (format "narf--init-mode-%s" mode)))) - `(progn - (defun ,hook-name () - (when (and (if ,match (string-match-p ,match (buffer-file-name)) t) - (or ,(not files) - (and (boundp ',mode) - (not ,mode) - (narf/project-has-files ,@(-list files))))) - (,mode 1))) - ,@(if (and in (listp in)) - (mapcar (lambda (h) `(add-hook ',h ',hook-name)) - (mapcar (lambda (m) (intern (format "%s-hook" m))) in)) - `((add-hook 'find-file-hook ',hook-name)))))) - (match - `(add-to-list ',(if minor-p 'narf-auto-minor-mode-alist 'auto-mode-alist) - (cons ,match ',mode))) - (t (user-error "associate! invalid rules for mode [%s] (in %s) (match %s) (files %s)" - mode in match files))))))) + `(progn + (,@(cond ((or files in) + (when (and files (not (or (listp files) (stringp files)))) + (user-error "associate! :files expects a string or list of strings")) + (let ((hook-name (intern (format "narf--init-mode-%s" mode)))) + `(progn + (defun ,hook-name () + (when (and ,(if match `(string-match-p ,match buffer-file-name) t) + (or ,(not files) + (and (boundp ',mode) + (not ,mode) + (narf/project-has-files ,@(-list files))))) + (,mode 1))) + ,@(if (and in (listp in)) + (mapcar (lambda (h) `(add-hook ',h ',hook-name)) + (mapcar (lambda (m) (intern (format "%s-hook" m))) in)) + `((add-hook 'find-file-hook ',hook-name)))))) + (match + `(add-to-list ',(if minor 'narf-auto-minor-mode-alist 'auto-mode-alist) + (cons ,match ',mode))) + (t (user-error "associate! invalid rules for mode [%s] (in %s) (match %s) (files %s)" + mode in match files)))))) + +(defmacro define-project-type! (name lighter &rest body) + "Define a minor mode for a specific framework, library or project type. e.g. + +(define-project-type! angularjs \"angjs\" + :modes (js2-mode) + :files (\"package.json\"))" + (declare (indent 2)) + (let* ((mode-name (format "%s-project-mode" name)) + (mode (intern mode-name)) + (mode-map (intern (format "%s-map" mode-name)))) + (let ((modes (plist-get body :modes)) + (match (plist-get body :match)) + (files (plist-get body :files)) + (build (plist-get body :build)) + (bind (plist-get body :bind)) + elem) + (while (keywordp (car body)) + (pop body) + (pop body)) + `(progn + (define-minor-mode ,mode + "Auto-generated by `define-project-type!'" + :init-value nil + :lighter ,(concat " " lighter) + :keymap (make-sparse-keymap) + (after! yasnippet + (when (boundp 'yas--extra-modes) + (add-hook ',(intern (concat mode-name "-hook")) + (lambda () + (if (symbol-value ',mode) + (yas-activate-extra-mode ',mode) + (yas-deactivate-extra-mode ',mode))))))) + + (after! company-dict + (add-to-list 'company-dict-minor-mode-list ',mode)) + + ,(when build + (when (listp build) + (setq build (car-safe (cdr-safe build)))) + (let ((cmd (or (car-safe build) build)) + (file (car-safe (cdr-safe build)))) + `(define-builder! ,mode ,cmd ,file))) + ,(when bind + `(map! :map ,mode-map ,bind)) + + (associate! ,mode + :minor t + :in ,modes + :match ,match + :files ,files) + + ,@body + ',mode)))) + (after! evil ;; Register keywords for proper indentation (see `map!') diff --git a/modules/module-ansible.el b/modules/module-ansible.el index d7beb367e..900ece574 100644 --- a/modules/module-ansible.el +++ b/modules/module-ansible.el @@ -1,11 +1,8 @@ ;;; module-ansible.el -(after! company-dict - (add-to-list 'company-dict-minor-mode-list 'ansible-mode)) - -(define-minor-mode ansible-mode - :lighter " ans" :keymap (make-sparse-keymap)) -(associate! ansible-mode :in (yaml-mode) :files ("roles/")) +(define-project-type! ansible-mode "ans" + :modes (yaml-mode) + :files ("roles/")) (use-package company-ansible :commands (company-ansible) diff --git a/modules/module-apple.el b/modules/module-apple.el index 7d79c8346..8603819fb 100644 --- a/modules/module-apple.el +++ b/modules/module-apple.el @@ -7,18 +7,11 @@ ;; LaunchBar: https://www.obdev.at/products/launchbar ;; -(define-minor-mode lb6-mode - "Launchbar development mode." - :init-value nil - :lighter " lb6" - (add-yas-minor-mode! 'lb6-mode)) -(define-builder! lb6-mode narf-lb6-reload) -(associate! lb6-mode :match "\\.lb\\(action\\|ext\\)/.+$") - -(defun narf-lb6-reload () - (interactive) - (let ((dir (f-traverse-upwards (lambda (f) (string-suffix-p ".lbaction" f))))) - (shell-command (format "open '%s'" dir)))) +(define-project-type! lb6 "lb6" + :match "\\.lb\\(action\\|ext\\)/.+$" + :build (lambda () + (awhen (f-traverse-upwards (lambda (f) (f-ext? f "lbaction"))) + (shell-command (format "open '%s'" it))))) ;; diff --git a/modules/module-js.el b/modules/module-js.el index 4acaa0b6d..80b96ccdf 100644 --- a/modules/module-js.el +++ b/modules/module-js.el @@ -4,10 +4,6 @@ :mode "\\.js$" :interpreter "node" :init - (use-package nodejs-repl - :commands (nodejs-repl) - :config (evil-set-initial-state 'nodejs-repl-mode 'emacs)) - (define-repl! js2-mode nodejs-repl) (define-docset! js2-mode "js,javascript,nodejs,angularjs,express,jquery,mongoose") @@ -87,17 +83,9 @@ (forward-slurp "forward slurp" nil) (forward-barf "forward barf" nil))))) -(define-minor-mode nodejs-mode - :lighter " node" :keymap (make-sparse-keymap) - (add-yas-minor-mode! 'nodejs-mode)) -(associate! nodejs-mode :files ("package.json") :in (js2-mode)) - -(define-minor-mode electron-mode - :lighter " electron" :keymap (make-sparse-keymap) - (add-yas-minor-mode! 'electron-mode)) -(associate! electron-mode - :files ("package.json" "app/index.html" "app/main.js") - :in (web-mode js2-mode markdown-mode json-mode coffee-mode)) +(use-package nodejs-repl + :commands (nodejs-repl) + :config (evil-set-initial-state 'nodejs-repl-mode 'emacs)) (use-package unityjs-mode :mode "/Assets/.*\\.js$" @@ -107,5 +95,20 @@ :mode "\\.coffee$" :config (setq-default coffee-indent-like-python-mode t)) +;; +(define-project-type! nodejs "node" + :modes (js2-mode) + :files ("package.json")) + + +(define-project-type! electron "electron" + :modes (web-mode js-mode js2-mode markdown-mode json-mode coffee-mode scss-mode sass-mode) + :files ("package.json" "app/index.html" "app/main.js")) +;; TODO electron-compile support + +;; TODO angular +;; TODO react +;; TODO express + (provide 'module-js) ;;; module-js.el ends here diff --git a/modules/module-lisp.el b/modules/module-lisp.el index 99fbaef5b..e4eb6e026 100644 --- a/modules/module-lisp.el +++ b/modules/module-lisp.el @@ -63,8 +63,9 @@ "define-builder" "narf-space-setup" "define-env-command" "define-text-object" "add-yas-minor-mode" "define-docset" - "define-org-link!" "define-company-backend" - "define-org-section" "define-temp-ex-cmd")) + "define-org-link" "define-company-backend" + "define-org-section" "define-temp-ex-cmd" + "define-project-type")) "!\\)") (1 font-lock-keyword-face append)) ;; Ert @@ -75,26 +76,19 @@ (1 font-lock-keyword-face) (2 font-lock-function-name-face)))) -;; Real go-to-definition for elisp -(map! :map emacs-lisp-mode-map - :m "gd" 'narf/elisp-find-function-at-pt - :m "gD" 'narf/elisp-find-function-at-pt-other-window) - -(define-minor-mode emacs-ert-mode - "Ert test file minor mode" - :lighter " Ert" :keymap (make-sparse-keymap) - (add-yas-minor-mode! 'emacs-ert-mode)) -(associate! emacs-ert-mode :match "/test/.+-test\\.el$") - -(map! :map emacs-lisp-mode-map - (:localleader - :n "tr" 'narf/ert-rerun-test - :n "ta" 'narf/ert-run-all-tests - :n "ts" 'narf/ert-run-test)) - (use-package slime :defer t - :config - (setq inferior-lisp-program "clisp")) + :config (setq inferior-lisp-program "clisp")) + +;; Real go-to-definition for elisp +(map! :map emacs-lisp-mode-map :m "gd" 'narf/elisp-find-function-at-pt) + +(define-project-type! emacs-ert "ert" + :modes (emacs-lisp-mode) + :match "/test/.+-test\\.el$" + :bind (:localleader + :n "tr" 'narf/ert-rerun-test + :n "ta" 'narf/ert-run-all-tests + :n "ts" 'narf/ert-run-test)) (provide 'module-lisp) ;;; module-elisp.el ends here diff --git a/modules/module-lua.el b/modules/module-lua.el index f31bc1916..6c87854e9 100644 --- a/modules/module-lua.el +++ b/modules/module-lua.el @@ -7,8 +7,6 @@ (define-repl! lua-mode narf/inf-lua) (define-company-backend! lua-mode (yasnippet)) (add-hook 'lua-mode-hook 'flycheck-mode) - (after! company-dict - (add-to-list 'company-dict-minor-mode-list 'love-mode)) (add-hook! lua-mode (electric-indent-local-mode +1) (setq narf-electric-indent-words '("else" "end"))) @@ -29,26 +27,16 @@ ;; inline functions (sp-local-pair "function " " end" :unless '(sp-point-after-bol-p)))) -(define-minor-mode love-mode - "Buffer local minor mode for Love2D" - :init-value nil - :lighter " ♥" - :keymap (make-sparse-keymap) - (add-yas-minor-mode! 'love-mode)) -(associate! love-mode - :in (lua-mode markdown-mode json-mode) - :files ("main.lua" "conf.lua")) -(define-builder! love-mode "open -a love.app '%s'" "main.lua") +;; +(define-project-type! love "♥" + :modes (lua-mode markdown-mode json-mode) + :files ("main.lua" "conf.lua") + :build ("open -a love.app '%s'" "main.lua")) -(define-minor-mode hammerspoon-mode - :init-value nil - :lighter " hammer" - :keymap (make-sparse-keymap) - (add-yas-minor-mode! 'hammerspoon-mode)) -(associate! hammerspoon-mode - :in (lua-mode markdown-mode) - :match "/\\.?hammerspoon/.+\\.lua$") -(define-builder! hammerspoon-mode "open hammerspoon://reload") +(define-project-type! hammerspoon "hammer" + :modes (lua-mode markdown-mode) + :match "/\\.?hammerspoon/.+\\.lua$" + :build "open hammerspoon://reload") (provide 'module-lua) ;;; module-lua.el ends here diff --git a/modules/module-php.el b/modules/module-php.el index 606d19e3a..5bc54bc01 100644 --- a/modules/module-php.el +++ b/modules/module-php.el @@ -63,14 +63,9 @@ :init (define-repl! php-mode php-boris) :config (evil-set-initial-state 'php-boris-mode 'emacs)) -(define-minor-mode php-laravel-mode - "" - :init-value nil - :lighter " Laravel" - :keymap (make-sparse-keymap) - (add-yas-minor-mode! 'php-laravel-mode)) -(associate! php-laravel-mode - :in (php-mode json-mode yaml-mode web-mode nxml-mode js2-mode scss-mode) +;; +(define-project-type! laravel "laravel" + :modes (php-mode json-mode yaml-mode web-mode nxml-mode js2-mode scss-mode) :files ("artisan" "server.php")) (provide 'module-php) diff --git a/modules/module-web.el b/modules/module-web.el index a77ee1e43..4fbd3d6f8 100644 --- a/modules/module-web.el +++ b/modules/module-web.el @@ -100,31 +100,21 @@ :i "M-e" 'emmet-expand-yas :i "M-E" 'emmet-expand-line)) -(define-minor-mode jekyll-mode - "Jekyll development mode." - :init-value nil - :lighter " :{" - :keymap (make-sparse-keymap) - (add-yas-minor-mode! 'jekyll-mode)) -(associate! jekyll-mode +;; +(define-project-type! jekyll ":{" + :modes (web-mode scss-mode html-mode markdown-mode yaml-mode) :match "/\\(\\(css\\|_\\(layouts\\|posts\\|sass\\)\\)/.+\\|.+.html\\)$" :files ("config.yml" "_layouts/") - :in (web-mode scss-mode html-mode markdown-mode yaml-mode)) -(add-hook! jekyll-mode - (when (eq major-mode 'web-mode) - (web-mode-set-engine "django"))) -(after! company-dict (add-to-list 'company-dict-minor-mode-list 'jekyll-mode)) + (add-hook! it + (when (eq major-mode 'web-mode) + (web-mode-set-engine "django")))) -(define-minor-mode wordpress-mode - "Wordpress development mode." - :init-value nil - :lighter " wp" - :keymap (make-sparse-keymap) - (add-yas-minor-mode! 'wordpress-mode)) -(associate! wordpress-mode +(define-project-type! wordpress "wp" + :modes (php-mode web-mode css-mode scss-mode sass-mode) :match "/wp-\\(\\(content\\|admin\\|includes\\)/\\)?.+$" :files ("wp-config.php" "wp-content/")) -(after! company-dict (add-to-list 'company-dict-minor-mode-list 'wordpress-mode)) + +;; TODO Add stylus-mode (provide 'module-web) ;;; module-web.el ends here