From 351d0dbc69741067c14f309ef83cedd0e166777f Mon Sep 17 00:00:00 2001 From: John Doty Date: Wed, 21 Mar 2018 06:18:07 -0700 Subject: [PATCH] javascript stuff --- .emacs.d/core.el | 44 +++-- .emacs.d/custom.el | 7 +- .emacs.d/network-security.data | 2 +- site-lisp/company-flow.el | 208 +++++++++++++++++++++ site-lisp/flow-minor-mode.el | 322 ++++++++++++++++++++++++++++++++ site-lisp/flycheck-flow.el | 173 +++++++++++++++++ site-lisp/glsl-mode.el | 327 +++++++++++++++++++++++++++++++++ site-lisp/prettier-js.el | 213 +++++++++++++++++++++ 8 files changed, 1277 insertions(+), 19 deletions(-) create mode 100644 site-lisp/company-flow.el create mode 100644 site-lisp/flow-minor-mode.el create mode 100644 site-lisp/flycheck-flow.el create mode 100644 site-lisp/glsl-mode.el create mode 100644 site-lisp/prettier-js.el diff --git a/.emacs.d/core.el b/.emacs.d/core.el index 9bd248c..1502212 100644 --- a/.emacs.d/core.el +++ b/.emacs.d/core.el @@ -255,6 +255,9 @@ (when (memq window-system '(mac ns)) (exec-path-from-shell-initialize)) +;; Company mode everywhere. What could go wrong? +(global-company-mode) + ;; ================================================================= ;; Text mode configuration. ;; ================================================================= @@ -514,9 +517,6 @@ (add-to-list 'auto-mode-alist '("\\.cool$" . csharp-mode)) (add-to-list 'auto-mode-alist '("\\.cs$" . csharp-mode)) -(eval-after-load - 'company - '(add-to-list 'company-backends 'company-omnisharp)) ;; ================================================================= ;; "XML" Support @@ -579,13 +579,6 @@ ;; Flycheck ;; ================================================================= (require 'flycheck) -;; disable jshint since we prefer eslint checking -(setq-default flycheck-disabled-checkers - (append flycheck-disabled-checkers - '(javascript-jshint))) - -;; use eslint with web-mode for jsx files -(flycheck-add-mode 'javascript-eslint 'web-mode) ;; customize flycheck temp file prefix (setq-default flycheck-temp-prefix ".flycheck") @@ -625,17 +618,34 @@ ;; ================================================================= (autoload 'python-mode "python-mode" "Python editing mode." t) -(setq auto-mode-alist - (cons '("\\.py$" . python-mode) auto-mode-alist)) -(setq interpreter-mode-alist - (cons '("python" . python-mode) interpreter-mode-alist)) +(add-to-list 'auto-mode-alist '("\\.py$" . python-mode)) +(add-to-list 'interpreter-mode-alist '("python" . python-mode)) ;; ================================================================= ;; JavaScript Support ;; ================================================================= -(autoload 'js2-mode "js2-mode" nil t) -(add-to-list 'auto-mode-alist '("\\.js$" . js2-mode)) -(add-to-list 'auto-mode-alist '("\\.jsx$" . web-mode)) +(require 'rjsx-mode) +(require 'prettier-js) +(require 'flycheck-flow) +(require 'flow-minor-mode) + +;; disable jshint since we prefer eslint checking +(setq-default flycheck-disabled-checkers + (append flycheck-disabled-checkers + '(javascript-jshint))) +(flycheck-add-next-checker 'javascript-eslint 'javascript-flow) + +(add-to-list 'auto-mode-alist '("\\.js$" . rjsx-mode)) +(add-to-list 'auto-mode-alist '("\\.jsx$" . rjsx-mode)) + +(defun my-js-mode-hook () + "My custom javascript mode hook." + (add-node-modules-path) + (flow-minor-enable-automatically) + (prettier-js-mode)) + +(add-hook 'rjsx-mode-hook #'my-js-mode-hook) + ;; ================================================================= ;; Ruby Mode diff --git a/.emacs.d/custom.el b/.emacs.d/custom.el index 1f2c0f6..f1c7334 100644 --- a/.emacs.d/custom.el +++ b/.emacs.d/custom.el @@ -14,6 +14,11 @@ '(comint-input-ignoredups t) '(comint-prompt-read-only t) '(comint-scroll-to-bottom-on-input t) + '(company-backends + (quote + (company-bbdb company-nxml company-css company-eclim company-semantic company-clang company-xcode company-cmake company-capf company-files + (company-dabbrev-code company-gtags company-etags company-keywords) + company-oddmuse company-dabbrev company-flow))) '(css-indent-offset 2) '(debug-on-error nil) '(fast-lock-cache-directories (quote ("~/flc-cache"))) @@ -46,7 +51,7 @@ ((sequence "TODO" "|" "DONE" "ABANDONED" "DEFERRED")))) '(package-selected-packages (quote - (company omnisharp geiser cider clojure-mode graphviz-dot-mode multi-term xterm-color thrift markdown-mode tuareg merlin ag use-package flycheck dockerfile-mode js2-mode web-mode zencoding-mode tss switch-window python-mode paredit magit lua-mode go-mode go-autocomplete exec-path-from-shell csharp-mode color-theme-solarized color-theme-monokai auto-complete auto-complete-nxml flymake flyspell json-mode popup ruby-mode))) + (mocha add-node-modules-path rjsx-mode xref-js2 js2-refactor company omnisharp geiser cider clojure-mode graphviz-dot-mode multi-term xterm-color thrift markdown-mode tuareg merlin ag use-package flycheck dockerfile-mode js2-mode web-mode zencoding-mode tss switch-window python-mode paredit magit lua-mode go-mode go-autocomplete exec-path-from-shell csharp-mode color-theme-solarized color-theme-monokai auto-complete auto-complete-nxml flymake flyspell json-mode popup ruby-mode))) '(reb-re-syntax (quote string)) '(rmail-mail-new-frame t) '(safe-local-variable-values diff --git a/.emacs.d/network-security.data b/.emacs.d/network-security.data index f2329f5..ecf4eef 100644 --- a/.emacs.d/network-security.data +++ b/.emacs.d/network-security.data @@ -1,6 +1,6 @@ ( + (:id "sha1:24c7a1e6369fef1e50de1deb1a9e5bbc68f80c8b" :fingerprint "sha1:26:26:88:6a:58:94:85:2f:98:ce:97:a0:0d:93:75:63:0a:9e:44:b5" :host "orgmode.org:443" :conditions (:insecure :unknown-ca :invalid)) (:id "sha1:60f141fb7e2767fbc9d5f309850c9db8bfaadb86" :fingerprint "sha1:f3:eb:c5:20:3c:f3:c8:79:46:3b:2d:d4:b7:c2:12:09:54:0e:d9:3b" :host "github-production-release-asset-2e65be.s3.amazonaws.com:443" :conditions (:insecure :unknown-ca :invalid)) (:id "sha1:379522f41fcdf5d279b33c30780e7512c9348430" :fingerprint "sha1:d4:ee:9d:2a:67:12:b3:61:4c:27:2d:15:8b:04:fc:c8:ca:08:a0:b6" :host "github.com:443" :conditions (:insecure :unknown-ca :invalid)) - (:id "sha1:24c7a1e6369fef1e50de1deb1a9e5bbc68f80c8b" :fingerprint "sha1:fe:3b:22:d5:9b:33:20:74:3c:0d:7e:d2:7d:71:41:5d:84:e7:ca:82" :host "orgmode.org:443" :conditions (:insecure :unknown-ca :invalid)) (:id "sha1:85457c729378cc93c732b6a3941c8e4f9c2e60f3" :fingerprint "sha1:ab:a6:d7:6a:b3:d3:63:fa:19:0d:65:41:60:23:6e:ef:d3:2a:46:dc" :host "marmalade-repo.org:443" :conditions (:unknown-ca :invalid)) ) diff --git a/site-lisp/company-flow.el b/site-lisp/company-flow.el new file mode 100644 index 0000000..25bcc17 --- /dev/null +++ b/site-lisp/company-flow.el @@ -0,0 +1,208 @@ +;;; company-flow.el --- Flow backend for company-mode -*- lexical-binding: t -*- + +;; Copyright (C) 2016 by Aaron Jensen + +;; Author: Aaron Jensen +;; URL: https://github.com/aaronjensen/company-flow +;; Version: 0.1.0 +;; Package-Requires: ((company "0.8.0") (dash "2.13.0")) + +;;; Commentary: + +;; This package adds support for flow to company. It requires +;; flow to be in your path. + +;; To use it, add to your company-backends: + +;; (eval-after-load 'company +;; '(add-to-list 'company-backends 'company-flow)) + +;;; License: + +;; This file is not part of GNU Emacs. +;; However, it is distributed under the same license. + +;; GNU Emacs is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation; either version 3, or (at your option) +;; any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs; see the file COPYING. If not, write to the +;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +;; Boston, MA 02110-1301, USA. + +;;; Code: +(require 'company) +(require 'dash) + +(defgroup company-flow () + "Flow company backend." + :group 'company + :prefix "company-flow-") + +(defcustom company-flow-executable "flow" + "Flow executable to run." + :group 'company-flow + :type 'string) +(make-variable-buffer-local 'company-flow-executable) + +(defcustom company-flow-modes '( + js-mode + js-jsx-mode + js2-mode + js2-jsx-mode + rjsx-mode + web-mode + ) + "List of major modes where company-flow will be providing completions." + :type '(choice (const :tag "All" nil) + (repeat (symbol :tag "Major mode"))) + :group 'company-flow) + +(defun company-flow--handle-signal (process _event) + (when (memq (process-status process) '(signal exit)) + (let ((callback (process-get process 'company-flow-callback)) + (prefix (process-get process 'company-flow-prefix))) + (if (and (eq (process-status process) 'exit) + (eq (process-exit-status process) 0)) + (funcall callback (->> process + company-flow--get-output + company-flow--parse-output + ;; Remove nils + (--filter it))) + (funcall callback nil))))) + +(defun company-flow--make-candidate (line) + "Creates a candidate with a meta property from LINE. + +LINE is expected to look like: +registrationSuccess () => {type: 'REGISTRATION_SUCCESS'}" + (let ((first-space (string-match " " line))) + (when first-space + (let ((text (substring line 0 first-space)) + (meta (substring line (+ 1 first-space)))) + (propertize text 'meta meta))))) + +(defun company-flow--parse-output (output) + (when (not (equal output "Error: not enough type information to autocomplete\n")) + (mapcar 'company-flow--make-candidate + (split-string output "\n")))) + +(defun company-flow--get-output (process) + "Get the complete output of PROCESS." + (with-demoted-errors "Error while retrieving process output: %S" + (let ((pending-output (process-get process 'company-flow-pending-output))) + (apply #'concat (nreverse pending-output))))) + +(defun company-flow--receive-checker-output (process output) + "Receive a syntax checking PROCESS OUTPUT." + (push output (process-get process 'company-flow-pending-output))) + +(defun company-flow--process-send-buffer (process) + "Send all contents of current buffer to PROCESS. + +Sends all contents of the current buffer to the standard input of +PROCESS, and terminates standard input with EOF." + (save-restriction + (widen) + (process-send-region process (point-min) (point-max))) + ;; flow requires EOF be on its own line + (process-send-string process "\n") + (process-send-eof process)) + +(defun company-flow--candidates-query (prefix callback) + (let* ((line (line-number-at-pos (point))) + (col (+ 1 (current-column))) + (command (list (executable-find company-flow-executable) + "autocomplete" + "--quiet" + buffer-file-name + (number-to-string line) + (number-to-string col))) + (process-connection-type nil) + (process (apply 'start-process "company-flow" nil command))) + (set-process-sentinel process #'company-flow--handle-signal) + (set-process-filter process #'company-flow--receive-checker-output) + (process-put process 'company-flow-callback callback) + (process-put process 'company-flow-prefix prefix) + (company-flow--process-send-buffer process))) + +(defun company-flow--prefix () + "Grab prefix for flow." + (and (or (null company-flow-modes) + (-contains? company-flow-modes major-mode)) + company-flow-executable + (executable-find company-flow-executable) + buffer-file-name + (file-exists-p buffer-file-name) + (not (company-in-string-or-comment)) + (locate-dominating-file buffer-file-name ".flowconfig") + (or (company-grab-symbol-cons "\\." 1) + 'stop))) + +(defun company-flow--annotation (candidate) + (format " %s" (get-text-property 0 'meta candidate))) + +(defun company-flow--meta (candidate) + (format "%s: %s" candidate (get-text-property 0 'meta candidate))) + +(defvar-local company-flow--debounce-state nil) + +(defun company-flow--debounce-callback (prefix callback) + (lambda (candidates) + (let ((current-prefix (car company-flow--debounce-state)) + (current-callback (cdr company-flow--debounce-state))) + (when (and current-prefix + (company-flow--string-prefix-p prefix current-prefix)) + (setq company-flow--debounce-state nil) + (funcall current-callback (all-completions current-prefix candidates)))))) + +(defun company-flow--prefix-to-string (prefix) + "Return a string or nil from a prefix. + `company-grab-symbol-cons' can return (\"prefix\" . t) or just + \"prefix\", but we only care about the string." + (if (consp prefix) + (car prefix) + prefix)) + +(defun company-flow--string-prefix-p (a b) + (string-prefix-p (company-flow--prefix-to-string a) (company-flow--prefix-to-string b))) + +(defun company-flow--debounce-async (prefix candidate-fn) + "Return a function that will properly debounce candidate queries by comparing the +in-flight query's prefix to PREFIX. CANDIDATE-FN should take two arguments, PREFIX +and the typical async callback. + +Note that the candidate list provided to the callback by CANDIDATE-FN will be +filtered via `all-completions' with the most current prefix, so it is not necessary +to do this filtering in CANDIDATE-FN. + +Use like: + + (cons :async (company-flow--debounce-async arg 'your-query-fn))" + (lambda (callback) + (let ((current-prefix (car company-flow--debounce-state))) + (unless (and current-prefix + (company-flow--string-prefix-p prefix current-prefix)) + (funcall candidate-fn prefix (company-flow--debounce-callback prefix callback))) + (setq company-flow--debounce-state (cons (company-flow--prefix-to-string prefix) callback))))) + +;;;###autoload +(defun company-flow (command &optional arg &rest _args) + (interactive (list 'interactive)) + (pcase command + (`interactive (company-begin-backend 'company-flow)) + (`prefix (company-flow--prefix)) + (`annotation (company-flow--annotation arg)) + (`meta (company-flow--meta arg)) + (`sorted t) + (`candidates (cons :async (company-flow--debounce-async arg 'company-flow--candidates-query))))) + +(provide 'company-flow) +;;; company-flow.el ends here diff --git a/site-lisp/flow-minor-mode.el b/site-lisp/flow-minor-mode.el new file mode 100644 index 0000000..ecfd6fc --- /dev/null +++ b/site-lisp/flow-minor-mode.el @@ -0,0 +1,322 @@ +;;; flow-minor-mode.el --- Flow type mode based on web-mode. -*- lexical-binding: t -*- + +;; This source code is licensed under the BSD-style license found in +;; the LICENSE file in the root directory of this source tree. + +;; Version: 0.3 +;; URL: https://github.com/an-sh/flow-minor-mode + +;; Package-Requires: ((emacs "25.1")) + +;;; Commentary: + +;; Minor mode for flowtype.org, derived from web-mode. Essentially a +;; rewrite of an official flow-for-emacs snippet into a standalone +;; mode with an improved usability. +;; +;; To enable this mode, enable it in your preferred javascript mode's +;; hooks: +;; +;; (add-hook 'js2-mode-hook 'flow-minor-enable-automatically) +;; +;; This will enable flow-minor-mode for a file only when there is a +;; "// @flow" declaration at the first line and a `.flowconfig` file +;; is present in the project. If you wish to enable flow-minor-mode +;; for all javascript files, use this instead: +;; +;; (add-hook 'js2-mode-hook 'flow-minor-mode) +;; +;;; Code: + +(require 'xref) +(require 'json) +(require 'compile) + +(defconst flow-minor-buffer "*Flow Output*") + +(defcustom flow-minor-default-binary "flow" + "Flow executable to use when no project-specific binary is found." + :group 'flow-minor-mode + :type 'string) + +(defcustom flow-minor-jump-other-window nil + "Jump to definitions in other window." + :group 'flow-minor-mode + :type 'boolean) + +(defcustom flow-minor-stop-server-on-exit t + "Stop flow server when Emacs exits." + :group 'flow-minor-mode + :type 'boolean) + +(defun flow-minor-column-at-pos (position) + "Column number at position. +POSITION point" + (save-excursion (goto-char position) (current-column))) + +(defun flow-minor-region () + "Format region data." + (if (use-region-p) + (let ((begin (region-beginning)) + (end (region-end))) + (format ":%d:%d,%d:%d" + (line-number-at-pos begin) + (flow-minor-column-at-pos begin) + (line-number-at-pos end) + (flow-minor-column-at-pos end))) + "")) + +(defun flow-minor-binary () + "Search for a local or global flow binary." + (let* ((root (locate-dominating-file + (or (buffer-file-name) default-directory) + "node_modules")) + (flow (and root + (expand-file-name "node_modules/.bin/flow" + root)))) + (if (and flow (file-executable-p flow)) + flow + flow-minor-default-binary))) + +(defun flow-minor-cmd (&rest args) + "Run flow with arguments, outputs to flow buffer. +ARGS" + (apply #'call-process (flow-minor-binary) nil flow-minor-buffer t args)) + +(defun flow-minor-cmd-ignore-output (&rest args) + "Run flow with arguments, ignore output. +ARGS" + (apply #'call-process (flow-minor-binary) nil nil nil args)) + +(defun flow-minor-cmd-to-string (&rest args) + "Run flow with arguments, outputs to string. +ARGS" + (with-temp-buffer + (apply #'call-process (flow-minor-binary) nil t nil args) + (buffer-string))) + +(defmacro flow-minor-with-flow (&rest body) + "With flow. +BODY progn" + `(progn + (flow-minor-cmd-ignore-output "start") + ,@body)) + +(defmacro flow-minor-region-command (region-sym &rest body) + "Flow command on a region. +REGION-SYM symbol +BODY progn" + (declare (indent defun)) + `(flow-minor-with-flow + (let ((,region-sym (concat (buffer-file-name) (flow-minor-region)))) + (switch-to-buffer-other-window flow-minor-buffer) + (setf buffer-read-only nil) + (erase-buffer) + ,@body))) + +(defun flow-minor-status () + "Show errors." + (interactive) + (flow-minor-region-command region + (flow-minor-cmd "status" "--from" "emacs") + (compilation-mode) + (setf buffer-read-only t))) + +(defun flow-minor-suggest () + "Fill types." + (interactive) + (flow-minor-region-command region + (flow-minor-cmd "suggest" region) + (diff-mode) + (setf buffer-read-only t))) + +(defun flow-minor-coverage () + "Show coverage." + (interactive) + (flow-minor-region-command region + (message "%s" region) + (flow-minor-cmd "coverage" region) + (compilation-mode) + (setf buffer-read-only t))) + +(defvar flow-type-font-lock-highlight + '( + ("\\([-_[:alnum:]]+\\)\\??:" . font-lock-variable-name-face) + ("\\_<\\(true\\|false\\|null\\|undefined\\|void\\)\\_>" . font-lock-constant-face) + ("<\\([-_[:alnum:]]+\\)>" . font-lock-type-face) + )) + +(defun flow-minor-colorize-buffer () + (setq font-lock-defaults '(flow-type-font-lock-highlight)) + (font-lock-fontify-buffer)) + +(defun flow-minor-colorize-type (text) + (with-temp-buffer + (insert text) + (flow-minor-colorize-buffer) + (buffer-string))) + +(defun flow-minor-type-at-pos () + "Show type at position." + (interactive) + (flow-minor-with-flow + (let* ((file (buffer-file-name)) + (line (number-to-string (line-number-at-pos))) + (col (number-to-string (1+ (current-column)))) + (type (flow-minor-cmd-to-string "type-at-pos" file line col))) + (message "%s" (flow-minor-colorize-type (car (split-string type "\n"))))))) + +(defun flow-minor-jump-to-definition () + "Jump to definition." + (interactive) + (flow-minor-with-flow + (let* ((file (buffer-file-name)) + (line (number-to-string (line-number-at-pos))) + (col (number-to-string (1+ (current-column)))) + (location (json-read-from-string + (flow-minor-cmd-to-string "get-def" "--json" file line col))) + (path (alist-get 'path location)) + (line (alist-get 'line location)) + (offset-in-line (alist-get 'start location))) + (if (> (length path) 0) + (progn + (xref-push-marker-stack) + (funcall (if flow-minor-jump-other-window #'find-file-other-window #'find-file) path) + (goto-line line) + (when (> offset-in-line 0) + (forward-char (1- offset-in-line)))) + (message "Not found"))))) + +(defvar flow-minor-mode-map (make-sparse-keymap) + "Keymap for ‘flow-minor-mode’.") + +(define-key flow-minor-mode-map (kbd "M-.") 'flow-minor-jump-to-definition) +(define-key flow-minor-mode-map (kbd "M-,") 'xref-pop-marker-stack) + +(define-key flow-minor-mode-map (kbd "C-c C-c s") 'flow-minor-status) +(define-key flow-minor-mode-map (kbd "C-c C-c c") 'flow-minor-coverage) +(define-key flow-minor-mode-map (kbd "C-c C-c t") 'flow-minor-type-at-pos) +(define-key flow-minor-mode-map (kbd "C-c C-c f") 'flow-minor-suggest) + +(define-key flow-minor-mode-map [menu-bar flow-minor-mode] + (cons "Flow" flow-minor-mode-map)) + +(define-key flow-minor-mode-map [menu-bar flow-minor-mode flow-minor-mode-s] + '(menu-item "Flow status" flow-minor-status)) + +(define-key flow-minor-mode-map [menu-bar flow-minor-mode flow-minor-mode-c] + '(menu-item "Flow coverage" flow-minor-coverage)) + +(define-key flow-minor-mode-map [menu-bar flow-minor-mode flow-minor-mode-t] + '(menu-item "Type at point" flow-minor-type-at-pos)) + +(define-key flow-minor-mode-map [menu-bar flow-minor-mode flow-minor-mode-f] + '(menu-item "Type suggestions" flow-minor-suggest)) + +(defun flow-minor-stop-flow-server () + "Stop flow hook." + (if flow-minor-stop-server-on-exit (ignore-errors (flow-minor-cmd-ignore-output "stop")))) + +(add-hook 'kill-emacs-hook 'flow-minor-stop-flow-server t) + +(defun flow-minor-maybe-delete-process (name) + (when (get-process name) + (delete-process name))) + +(defun flow-minor-eldoc-sentinel (process _event) + (when (eq (process-status process) 'exit) + (if (eq (process-exit-status process) 0) + (with-current-buffer "*Flow Eldoc*" + (goto-char (point-min)) + (forward-line 1) + (delete-region (point) (point-max)) + (flow-minor-colorize-buffer) + (eldoc-message (car (split-string (buffer-substring (point-min) (point-max)) "\n"))))))) + +(defun flow-minor-eldoc-documentation-function () + "Display type at point with eldoc." + (flow-minor-maybe-delete-process "flow-minor-eldoc") + + (let* ((line (line-number-at-pos (point))) + (col (+ 1 (current-column))) + (buffer (get-buffer-create "*Flow Eldoc*")) + (errorbuffer (get-buffer-create "*Flow Eldoc Error*")) + (command (list (flow-minor-binary) + "type-at-pos" + "--path" buffer-file-name + (number-to-string line) + (number-to-string col))) + (process (make-process :name "flow-minor-eldoc" + :buffer buffer + :command command + :connection-type 'pipe + :sentinel 'flow-minor-eldoc-sentinel + :stderr errorbuffer))) + (with-current-buffer buffer + (erase-buffer)) + (with-current-buffer errorbuffer + (erase-buffer)) + (save-restriction + (widen) + (process-send-region process (point-min) (point-max))) + (process-send-string process "\n") + (process-send-eof process)) + nil) + +;;;###autoload +(define-minor-mode flow-minor-mode + "Flow mode" + nil " Flow" flow-minor-mode-map + (if flow-minor-mode + (progn + (setq-local eldoc-documentation-function 'flow-minor-eldoc-documentation-function) + (eldoc-mode)))) + +(defun flow-minor-tag-present-p () + "Return true if the '// @flow' tag is present in the current buffer." + (save-excursion + (goto-char (point-min)) + (let (stop found) + (while (not stop) + (when (not (re-search-forward "[^\n[:space:]]" nil t)) + (setq stop t)) + (if (equal (point) (point-min)) + (setq stop t) + (backward-char)) + (cond ((or (looking-at "//+[ ]*@flow") + (looking-at "/\\**[ ]*@flow")) + (setq found t) + (setq stop t)) + ((looking-at "//") + (forward-line)) + ((looking-at "/\\*") + (when (not (re-search-forward "*/" nil t)) + (setq stop t))) + (t (setq stop t)))) + found))) + +(defun flow-minor-configured-p () + "Predicate to check configuration." + (locate-dominating-file + (or (buffer-file-name) default-directory) + ".flowconfig")) + +;;;###autoload +(defun flow-minor-enable-automatically () + "Search for a flow marker and enable flow-minor-mode." + (when (and (flow-minor-configured-p) + (flow-minor-tag-present-p)) + (flow-minor-mode +1))) + +(defun flow-status () + "Invoke flow to check types" + (interactive) + (let ((cmd "flow status") + (regexp '(flow "^\\(Error:\\)[ \t]+\\(\\(.+\\):\\([[:digit:]]+\\)\\)" + 3 4 nil (1) 2 (1 compilation-error-face)))) + (add-to-list 'compilation-error-regexp-alist 'flow) + (add-to-list 'compilation-error-regexp-alist-alist regexp) + (compile cmd))) + +(provide 'flow-minor-mode) +;;; flow-minor-mode.el ends here diff --git a/site-lisp/flycheck-flow.el b/site-lisp/flycheck-flow.el new file mode 100644 index 0000000..e1c347d --- /dev/null +++ b/site-lisp/flycheck-flow.el @@ -0,0 +1,173 @@ +;;; flycheck-flow.el --- Support Flow in flycheck + +;; Copyright (C) 2015 Lorenzo Bolla +;; +;; Author: Lorenzo Bolla +;; Created: 16 Septermber 2015 +;; Version: 1.1 +;; Package-Requires: ((flycheck "0.18") (json "1.4")) + +;;; Commentary: + +;; This package adds support for flow to flycheck. It requires +;; flow>=0.20.0. + +;; To use it, add to your init.el: + +;; (require 'flycheck-flow) +;; (add-hook 'javascript-mode-hook 'flycheck-mode) + +;; You want to use flow in conjunction with other JS checkers. +;; E.g. to use with gjslint, add this to your init.el +;; (flycheck-add-next-checker 'javascript-gjslint 'javascript-flow) + +;; For coverage warnings add this to your init.el +;; (flycheck-add-next-checker 'javascript-flow 'javascript-flow-coverage) + +;;; License: + +;; This file is not part of GNU Emacs. +;; However, it is distributed under the same license. + +;; GNU Emacs is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation; either version 3, or (at your option) +;; any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs; see the file COPYING. If not, write to the +;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +;; Boston, MA 02110-1301, USA. + +;;; Code: +(require 'flycheck) +(require 'json) + +(flycheck-def-args-var flycheck-javascript-flow-args javascript-flow) +(customize-set-variable 'flycheck-javascript-flow-args '()) + +(defun flycheck-flow--parse-json (output checker buffer) + "Parse flycheck json OUTPUT generated by CHECKER on BUFFER." + (let* ((json-object-type 'alist) + (json-array-type 'list) + (flow-json-output (json-read-from-string output)) + (flow-errors-list (cdr (assq 'errors flow-json-output))) + message-kind + message-level + message-code-reason + message-filename + message-line + message-column + message-descr + errors) + (dolist (error-message flow-errors-list) + ;; The structure for each `error-message' in `flow-errors-list' is like this: + ;; ((kind . `message-kind') + ;; (level . `message-level') + ;; (message ((descr . `message-code-reason') + ;; (loc (source . `message-filename') + ;; (start (line . `message-line') (column . `message-column')))) + ;; ((descr . `message-descr')))) + (let-alist error-message + (setq message-kind .kind) + (setq message-level (intern .level)) + + (let-alist (car .message) + (setq message-code-reason .descr + message-filename .loc.source + message-line .loc.start.line + message-descr .descr + message-column .loc.start.column)) + + (let-alist (car (cdr .message)) + (when (string= .type "Comment") + (setq message-descr .descr)))) + + (when (string= message-kind "parse") + (setq message-descr message-kind)) + + (push (flycheck-error-new-at + message-line + message-column + message-level + message-descr + :id message-code-reason + :checker checker + :buffer buffer + :filename message-filename) + errors)) + (nreverse errors))) + +(defun flycheck-flow--predicate () + "Shall we run the checker?" + (and + buffer-file-name + (file-exists-p buffer-file-name) + (locate-dominating-file buffer-file-name ".flowconfig"))) + +(flycheck-define-checker javascript-flow + "A JavaScript syntax and style checker using Flow. + +See URL `http://flowtype.org/'." + :command ( + "flow" + "check-contents" + (eval flycheck-javascript-flow-args) + "--json" + "--from" "emacs" + "--color=never" + source-original) + :standard-input t + :predicate flycheck-flow--predicate + :error-parser flycheck-flow--parse-json + ;; js3-mode doesn't support jsx + :modes (js-mode js-jsx-mode js2-mode js2-jsx-mode js3-mode web-mode rjsx-mode)) + +(flycheck-define-checker javascript-flow-coverage + "A coverage checker for Flow. + +See URL `http://flowtype.org/'." + :command ( + "flow" + "coverage" + (eval flycheck-javascript-flow-args) + "--json" + "--from" "emacs" + "--path" source-original) + :standard-input t + :predicate flycheck-flow--predicate + :error-parser + (lambda (output checker buffer) + (let* ((json-array-type 'list) + (json-object-type 'alist) + (locs (condition-case nil + (let ((report (json-read-from-string output))) + (alist-get 'uncovered_locs (alist-get 'expressions report))) + (error nil)))) + (mapcar (lambda (loc) + (let ((start (alist-get 'start loc)) + (end (alist-get 'end loc))) + (flycheck-error-new + :buffer buffer + :checker 'javascript-flow-coverage + :filename buffer-file-name + :line (alist-get 'line start) + :column (alist-get 'column start) + :message (format "no-coverage-to (%s . %s)" + (alist-get 'line end) + (alist-get 'column end)) + :level 'warning))) + locs))) + ;; js3-mode doesn't support jsx + :modes (js-mode js-jsx-mode js2-mode js2-jsx-mode js3-mode rjsx-mode)) + +(add-to-list 'flycheck-checkers 'javascript-flow) +(add-to-list 'flycheck-checkers 'javascript-flow-coverage t) + +(provide 'flycheck-flow) +;;; flycheck-flow.el ends here diff --git a/site-lisp/glsl-mode.el b/site-lisp/glsl-mode.el new file mode 100644 index 0000000..bd1f379 --- /dev/null +++ b/site-lisp/glsl-mode.el @@ -0,0 +1,327 @@ +;;; glsl-mode.el --- major mode for Open GLSL shader files + +;; Copyright (C) 1999, 2000, 2001 Free Software Foundation, Inc. +;; Copyright (C) 2011, 2014 Jim Hourihan +;; +;; Authors: Xavier.Decoret@imag.fr, +;; Jim Hourihan (updated for 4.5, etc) +;; Keywords: languages +;; Version: 2.0 +;; X-URL: http://artis.inrialpes.fr/~Xavier.Decoret/resources/glsl-mode/ +;; +;; This software is distributed in the hope that it will be useful, but +;; WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +;; General Public License for more details. +;; +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs; see the file COPYING. If not, write to +;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + +;;; Commentary: + +;; Major mode for editing OpenGLSL grammar files, usually files ending with +;; `.vert', `.frag', `.glsl', `.geom'. Is is based on c-mode plus some +;; features and pre-specified fontifications. +;; +;; Modifications from the 1.0 version of glsl-mode (jimhourihan): +;; * Removed original optimized regexps for font-lock-keywords and +;; replaced with keyword lists for easier maintenance +;; * Added customization group and faces +;; * Preprocessor faces +;; * Updated to GLSL 4.5 +;; * Separate deprecated symbols +;; * Made _ part of a word +;; * man page lookup at opengl.org + +;; This package provides the following features: +;; * Syntax coloring (via font-lock) for grammar symbols and +;; builtin functions and variables for up to GLSL version 4.5 +;; * Indentation for the current line (TAB) and selected region (C-M-\). +;; * Switching between file.vert and file.frag +;; with S-lefttab (via ff-find-other-file) +;; * interactive function glsl-find-man-page prompts for glsl built +;; in function, formats opengl.org url and passes to w3m + +;;; Installation: + +;; This file requires Emacs-20.3 or higher and package cc-mode. + +;; If glsl-mode is not part of your distribution, put this file into your +;; load-path and the following into your ~/.emacs: +;; (autoload 'glsl-mode "glsl-mode" nil t) +;; (add-to-list 'auto-mode-alist '("\\.glsl\\'" . glsl-mode)) +;; (add-to-list 'auto-mode-alist '("\\.vert\\'" . glsl-mode)) +;; (add-to-list 'auto-mode-alist '("\\.frag\\'" . glsl-mode)) +;; (add-to-list 'auto-mode-alist '("\\.geom\\'" . glsl-mode)) + +;;; Code: + +(provide 'glsl-mode) + +(eval-when-compile ; required and optional libraries + (require 'cc-mode) + (require 'find-file)) + +(require 'align) + +(defgroup glsl nil + "OpenGL Shading Language Major Mode" + :group 'languages) + +(defconst glsl-language-version "4.5" + "GLSL language version number.") + +(defconst gl-version "4.5" + "OpenGL major mode version number.") + +(defvar glsl-type-face 'glsl-type-face) +(defface glsl-type-face + '((t (:inherit font-lock-type-face))) "glsl: type face" + :group 'glsl) + +(defvar glsl-builtin-face 'glsl-builtin-face) +(defface glsl-builtin-face + '((t (:inherit font-lock-builtin-face))) "glsl: builtin face" + :group 'glsl) + +(defvar glsl-deprecated-builtin-face 'glsl-deprecated-builtin-face) +(defface glsl-deprecated-builtin-face + '((t (:inherit glsl-builtin-face))) "glsl: deprecated builtin face" + :group 'glsl) + +(defvar glsl-keyword-face 'glsl-keyword-face) +(defface glsl-keyword-face + '((t (:inherit font-lock-keyword-face))) "glsl: keyword face" + :group 'glsl) + +(defvar glsl-deprecated-keyword-face 'glsl-deprecated-keyword-face) +(defface glsl-deprecated-keyword-face + '((t (:inherit glsl-keyword-face))) "glsl: deprecated keyword face" + :group 'glsl) + +(defvar glsl-variable-name-face 'glsl-variable-name-face) +(defface glsl-variable-name-face + '((t (:inherit font-lock-variable-name-face))) "glsl: variable face" + :group 'glsl) + +(defvar glsl-deprecated-variable-name-face 'glsl-deprecated-variable-name-face) +(defface glsl-deprecated-variable-name-face + '((t (:inherit glsl-variable-name-face))) "glsl: deprecated variable face" + :group 'glsl) + +(defvar glsl-preprocessor-face 'glsl-preprocessor-face) +(defface glsl-preprocessor-face + '((t (:inherit font-lock-preprocessor-face))) "glsl: preprocessor face" + :group 'glsl) + +(defvar glsl-mode-hook nil) + +(defvar glsl-mode-map + (let ((glsl-mode-map (make-sparse-keymap))) + (define-key glsl-mode-map [S-iso-lefttab] 'ff-find-other-file) + glsl-mode-map) + "Keymap for GLSL major mode") + +(defcustom glsl-man-pages-base-url "http://www.opengl.org/sdk/docs/man/html/" + "Location of GL man pages" + :group 'glsl) + +;;;###autoload +(progn + (add-to-list 'auto-mode-alist '("\\.vert\\'" . glsl-mode)) + (add-to-list 'auto-mode-alist '("\\.frag\\'" . glsl-mode)) + (add-to-list 'auto-mode-alist '("\\.geom\\'" . glsl-mode)) + (add-to-list 'auto-mode-alist '("\\.glsl\\'" . glsl-mode))) + +(eval-and-compile + ;; + ;; These vars are useful for completion so keep them around after + ;; compile as well. The goal here is to have the byte compiled code + ;; have optimized regexps so its not done at eval time. + ;; + + (defvar glsl-type-list + '("float" "double" "int" "void" "bool" "true" "false" "mat2" "mat3" + "mat4" "dmat2" "dmat3" "dmat4" "mat2x2" "mat2x3" "mat2x4" "dmat2x2" + "dmat2x3" "dmat2x4" "mat3x2" "mat3x3" "mat3x4" "dmat3x2" "dmat3x3" + "dmat3x4" "mat4x2" "mat4x3" "mat4x4" "dmat4x2" "dmat4x3" "dmat4x4" "vec2" + "vec3" "vec4" "ivec2" "ivec3" "ivec4" "bvec2" "bvec3" "bvec4" "dvec2" + "dvec3" "dvec4" "uint" "uvec2" "uvec3" "uvec4" "sampler1D" "sampler2D" + "sampler3D" "samplerCube" "sampler1DShadow" "sampler2DShadow" + "samplerCubeShadow" "sampler1DArray" "sampler2DArray" + "sampler1DArrayShadow" "sampler2DArrayShadow" "isampler1D" "isampler2D" + "isampler3D" "isamplerCube" "isampler1DArray" "isampler2DArray" + "usampler1D" "usampler2D" "usampler3D" "usamplerCube" "usampler1DArray" + "usampler2DArray" "sampler2DRect" "sampler2DRectShadow" "isampler2DRect" + "usampler2DRect" "samplerBuffer" "isamplerBuffer" "usamplerBuffer" + "sampler2DMS" "isampler2DMS" "usampler2DMS" "sampler2DMSArray" + "isampler2DMSArray" "usampler2DMSArray" "samplerCubeArray" + "samplerCubeArrayShadow" "isamplerCubeArray" "usamplerCubeArray" + "image1D" "iimage1D" "uimage1D" "image2D" "iimage2D" "uimage2D" "image3D" + "iimage3D" "uimage3D" "image2DRect" "iimage2DRect" "uimage2DRect" + "imageCube" "iimageCube" "uimageCube" "imageBuffer" "iimageBuffer" + "uimageBuffer" "image1DArray" "iimage1DArray" "uimage1DArray" + "image2DArray" "iimage2DArray" "uimage2DArray" "imageCubeArray" + "iimageCubeArray" "uimageCubeArray" "image2DMS" "iimage2DMS" "uimage2DMS" + "image2DMSArray" "iimage2DMSArray" "uimage2DMSArray" "long" "short" + "half" "fixed" "unsigned" "hvec2" "hvec3" "hvec4" "fvec2" "fvec3" "fvec4" + "sampler3DRect")) + + (defvar glsl-modifier-list + '("attribute" "const" "uniform" "varying" "buffer" "shared" "coherent" "volatile" "restrict" + "readonly" "writeonly" "atomic_uint" "layout" "centroid" "flat" "smooth" + "noperspective" "patch" "sample" "break" "continue" "do" "for" "while" + "switch" "case" "default" "if" "else" "subroutine" "in" "out" "inout" + "invariant" "discard" "return" "lowp" "mediump" "highp" "precision" + "struct" "common" "partition" "active" "asm" "class" "union" "enum" + "typedef" "template" "this" "packed" "resource" "goto" "inline" "noinline" + "public" "static" "extern" "external" "interface" "superp" "input" "output" + "filter" "sizeof" "cast" "namespace" "using" "row_major" + "early_fragment_tests")) + + (defvar glsl-deprecated-modifier-list + '("varying" "attribute")) ; centroid is deprecated when used with varying + + (defvar glsl-builtin-list + '("abs" "acos" "acosh" "all" "any" "asin" "asinh" "atan" "atanh" + "atomicCounter" "atomicCounterDecrement" "atomicCounterIncrement" + "barrier" "bitCount" "bitfieldExtract" "bitfieldInsert" "bitfieldReverse" + "ceil" "clamp" "cos" "cosh" "cross" "degrees" "determinant" "dFdx" "dFdy" + "dFdyFine" "dFdxFine" "dFdyCoarse" "dFdxCourse" + "fwidthFine" "fwidthCoarse" + "distance" "dot" "EmitStreamVertex" "EmitVertex" "EndPrimitive" + "EndStreamPrimitive" "equal" "exp" "exp2" "faceforward" "findLSB" + "findMSB" "floatBitsToInt" "floatBitsToUint" "floor" "fma" "fract" + "frexp" "fwidth" "greaterThan" "greaterThanEqual" "imageAtomicAdd" + "imageAtomicAnd" "imageAtomicCompSwap" "imageAtomicExchange" + "imageAtomicMax" "imageAtomicMin" "imageAtomicOr" "imageAtomicXor" + "imageLoad" "imageSize" "imageStore" "imulExtended" "intBitsToFloat" + "imageSamples" + "interpolateAtCentroid" "interpolateAtOffset" "interpolateAtSample" + "inverse" "inversesqrt" "isinf" "isnan" "ldexp" "length" "lessThan" + "lessThanEqual" "log" "log2" "matrixCompMult" "max" "memoryBarrier" "min" + "mix" "mod" "modf" "noise" "normalize" "not" "notEqual" "outerProduct" + "packDouble2x32" "packHalf2x16" "packSnorm2x16" "packSnorm4x8" + "packUnorm2x16" "packUnorm4x8" "pow" "radians" "reflect" "refract" + "round" "roundEven" "sign" "sin" "sinh" "smoothstep" "sqrt" "step" "tan" + "tanh" "texelFetch" "texelFetchOffset" "texture" "textureGather" + "textureGatherOffset" "textureGatherOffsets" "textureGrad" + "textureGradOffset" "textureLod" "textureLodOffset" "textureOffset" + "textureProj" "textureProjGrad" "textureProjGradOffset" "textureProjLod" + "textureProjLodOffset" "textureProjOffset" "textureQueryLevels" "textureQueryLod" + "textureSize" "transpose" "trunc" "uaddCarry" "uintBitsToFloat" + "umulExtended" "unpackDouble2x32" "unpackHalf2x16" "unpackSnorm2x16" + "unpackSnorm4x8" "unpackUnorm2x16" "unpackUnorm4x8" "usubBorrow")) + + (defvar glsl-deprecated-builtin-list + '("texture1D" "texture1DProj" "texture1DLod" "texture1DProjLod" + "texture2D" "texture2DProj" "texture2DLod" "texture2DProjLod" + "texture2DRect" "texture2DRectProj" + "texture3D" "texture3DProj" "texture3DLod" "texture3DProjLod" + "shadow1D" "shadow1DProj" "shadow1DLod" "shadow1DProjLod" + "shadow2D" "shadow2DProj" "shadow2DLod" "shadow2DProjLod" + "textureCube" "textureCubeLod")) + + (defvar glsl-deprecated-variables-list + '("gl_FragColor" "gl_FragData" "gl_MaxVarying" "gl_MaxVaryingFloats" + "gl_MaxVaryingComponents")) + + (defvar glsl-preprocessor-directive-list + '("define" "undef" "if" "ifdef" "ifndef" "else" "elif" "endif" + "error" "pragma" "extension" "version" "line")) + + (defvar glsl-preprocessor-expr-list + '("defined" "##")) + + (defvar glsl-preprocessor-builtin-list + '("__LINE__" "__FILE__" "__VERSION__")) + + (autoload 'w3m-browse-url "w3m" "View URL using w3m") + ) ; eval-and-compile + +(eval-when-compile + (defun glsl-ppre (re) + (format "\\<\\(%s\\)\\>" (regexp-opt re)))) + +(defvar glsl-font-lock-keywords-1 + (list + (cons (eval-when-compile + (format "^[ \t]*#[ \t]*\\<\\(%s\\)\\>" + (regexp-opt glsl-preprocessor-directive-list))) + glsl-preprocessor-face) + (cons (eval-when-compile + (glsl-ppre glsl-type-list)) + glsl-type-face) + (cons (eval-when-compile + (glsl-ppre glsl-deprecated-modifier-list)) + glsl-deprecated-keyword-face) + (cons (eval-when-compile + (glsl-ppre glsl-modifier-list)) + glsl-keyword-face) + (cons (eval-when-compile + (glsl-ppre glsl-preprocessor-builtin-list)) + glsl-keyword-face) + (cons (eval-when-compile + (glsl-ppre glsl-deprecated-builtin-list)) + glsl-deprecated-builtin-face) + (cons (eval-when-compile + (glsl-ppre glsl-builtin-list)) + glsl-builtin-face) + (cons (eval-when-compile + (glsl-ppre glsl-deprecated-variables-list)) + glsl-deprecated-variable-name-face) + (cons "gl_[A-Z][A-Za-z_]+" glsl-variable-name-face) + ) + "Minimal highlighting expressions for GLSL mode") + + +(defvar glsl-font-lock-keywords glsl-font-lock-keywords-1 + "Default highlighting expressions for GLSL mode") + +(defvar glsl-mode-syntax-table + (let ((glsl-mode-syntax-table (make-syntax-table))) + (modify-syntax-entry ?/ ". 124b" glsl-mode-syntax-table) + (modify-syntax-entry ?* ". 23" glsl-mode-syntax-table) + (modify-syntax-entry ?\n "> b" glsl-mode-syntax-table) + (modify-syntax-entry ?_ "w" glsl-mode-syntax-table) + glsl-mode-syntax-table) + "Syntax table for glsl-mode") + +(defvar glsl-other-file-alist + '(("\\.frag$" (".vert")) + ("\\.vert$" (".frag")) + ) + "Alist of extensions to find given the current file's extension") + + +(defun glsl-man-completion-list () + (append glsl-builtin-list glsl-deprecated-builtin-list)) + +(defun glsl-find-man-page (thing) + (interactive + (let ((word (current-word nil t))) + (list + (completing-read + (concat "OpenGL.org GLSL man page: (" word "): ") + (glsl-man-completion-list) + nil nil nil nil word)))) + (save-excursion + (browse-url + (concat glsl-man-pages-base-url thing ".xhtml")))) + +;;;###autoload +(define-derived-mode glsl-mode c-mode "GLSL" + "Major mode for editing OpenGLSL shader files." + (set (make-local-variable 'font-lock-defaults) '(glsl-font-lock-keywords)) + (set (make-local-variable 'ff-other-file-alist) 'glsl-other-file-alist) + (set (make-local-variable 'comment-start) "// ") + (set (make-local-variable 'comment-end) "") + (set (make-local-variable 'comment-padding) "") + (add-to-list 'align-c++-modes 'glsl-mode) + ) + + ;(easy-menu-define c-glsl-menu glsl-mode-map "GLSL Mode Commands" + ; (cons "GLSL" (c-lang-const c-mode-menu glsl))) + +;;; glsl-mode.el ends here diff --git a/site-lisp/prettier-js.el b/site-lisp/prettier-js.el new file mode 100644 index 0000000..08d90b8 --- /dev/null +++ b/site-lisp/prettier-js.el @@ -0,0 +1,213 @@ +;;; prettier-js.el --- Minor mode to format JS code on file save + +;; Version: 0.1.0 + +;; Copyright (c) 2014 The go-mode Authors. All rights reserved. +;; Portions Copyright (c) 2015-present, Facebook, Inc. All rights reserved. + +;; Redistribution and use in source and binary forms, with or without +;; modification, are permitted provided that the following conditions are +;; met: + +;; * Redistributions of source code must retain the above copyright +;; notice, this list of conditions and the following disclaimer. +;; * Redistributions in binary form must reproduce the above +;; copyright notice, this list of conditions and the following disclaimer +;; in the documentation and/or other materials provided with the +;; distribution. +;; * Neither the name of the copyright holder nor the names of its +;; contributors may be used to endorse or promote products derived from +;; this software without specific prior written permission. + +;; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +;; "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +;; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +;; A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +;; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +;; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +;; LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +;; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +;; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +;; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +;; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.) + +;; Author: James Long and contributors +;; Created: 10 January 2017 +;; Url: https://github.com/prettier/prettier-emacs +;; Keywords: convenience wp edit js + +;; This file is not part of GNU Emacs. + +;;; Commentary: +;; Formats your JavaScript code using 'prettier' on file save. + +;;; Code: + +(defgroup prettier-js nil + "Minor mode to format JS code on file save" + :group 'languages + :prefix "prettier-js" + :link '(url-link :tag "Repository" "https://github.com/prettier/prettier")) + +(defcustom prettier-js-command "prettier" + "The 'prettier' command." + :type 'string + :group 'prettier-js) + +(defcustom prettier-js-args '() + "List of args to send to prettier command." + :type '(repeat string) + :group 'prettier-js) + +(defcustom prettier-js-show-errors 'buffer + "Where to display prettier error output. +It can either be displayed in its own buffer, in the echo area, or not at all. +Please note that Emacs outputs to the echo area when writing +files and will overwrite prettier's echo output if used from inside +a `before-save-hook'." + :type '(choice + (const :tag "Own buffer" buffer) + (const :tag "Echo area" echo) + (const :tag "None" nil)) + :group 'prettier-js) + +(defcustom prettier-js-width-mode nil + "Specify width when formatting buffer contents." + :type '(choice + (const :tag "Window width" window) + (const :tag "Fill column" fill) + (const :tag "None" nil)) + :group 'prettier-js) + +(defun prettier-js--goto-line (line) + "Move cursor to line LINE." + (goto-char (point-min)) + (forward-line (1- line))) + +(defun prettier-js--apply-rcs-patch (patch-buffer) + "Apply an RCS-formatted diff from PATCH-BUFFER to the current buffer." + (let ((target-buffer (current-buffer)) + ;; Relative offset between buffer line numbers and line numbers + ;; in patch. + ;; + ;; Line numbers in the patch are based on the source file, so + ;; we have to keep an offset when making changes to the + ;; buffer. + ;; + ;; Appending lines decrements the offset (possibly making it + ;; negative), deleting lines increments it. This order + ;; simplifies the forward-line invocations. + (line-offset 0)) + (save-excursion + (with-current-buffer patch-buffer + (goto-char (point-min)) + (while (not (eobp)) + (unless (looking-at "^\\([ad]\\)\\([0-9]+\\) \\([0-9]+\\)") + (error "Invalid rcs patch or internal error in prettier-js--apply-rcs-patch")) + (forward-line) + (let ((action (match-string 1)) + (from (string-to-number (match-string 2))) + (len (string-to-number (match-string 3)))) + (cond + ((equal action "a") + (let ((start (point))) + (forward-line len) + (let ((text (buffer-substring start (point)))) + (with-current-buffer target-buffer + (setq line-offset (- line-offset len)) + (goto-char (point-min)) + (forward-line (- from len line-offset)) + (insert text))))) + ((equal action "d") + (with-current-buffer target-buffer + (prettier-js--goto-line (- from line-offset)) + (setq line-offset (+ line-offset len)) + (let ((beg (point))) + (forward-line len) + (delete-region (point) beg)))) + (t + (error "Invalid rcs patch or internal error in prettier-js--apply-rcs-patch"))))))))) + +(defun prettier-js--process-errors (filename errorfile errbuf) + "Process errors for FILENAME, using an ERRORFILE and display the output in ERRBUF." + (with-current-buffer errbuf + (if (eq prettier-js-show-errors 'echo) + (progn + (message "%s" (buffer-string)) + (prettier-js--kill-error-buffer errbuf)) + (insert-file-contents errorfile nil nil nil) + ;; Convert the prettier stderr to something understood by the compilation mode. + (goto-char (point-min)) + (insert "prettier errors:\n") + (while (search-forward-regexp "^stdin" nil t) + (replace-match (file-name-nondirectory filename))) + (compilation-mode) + (display-buffer errbuf)))) + +(defun prettier-js--kill-error-buffer (errbuf) + "Kill buffer ERRBUF." + (let ((win (get-buffer-window errbuf))) + (if win + (quit-window t win) + (with-current-buffer errbuf + (erase-buffer)) + (kill-buffer errbuf)))) + +(defun prettier-js () + "Format the current buffer according to the prettier tool." + (interactive) + (let* ((ext (file-name-extension buffer-file-name t)) + (bufferfile (make-temp-file "prettier" nil ext)) + (outputfile (make-temp-file "prettier" nil ext)) + (errorfile (make-temp-file "prettier" nil ext)) + (errbuf (if prettier-js-show-errors (get-buffer-create "*prettier errors*"))) + (patchbuf (get-buffer-create "*prettier patch*")) + (coding-system-for-read 'utf-8) + (coding-system-for-write 'utf-8) + (width-args + (cond + ((equal prettier-js-width-mode 'window) + (list "--print-width" (number-to-string (window-body-width)))) + ((equal prettier-js-width-mode 'fill) + (list "--print-width" (number-to-string fill-column))) + (t + '())))) + (unwind-protect + (save-restriction + (widen) + (write-region nil nil bufferfile) + (if errbuf + (with-current-buffer errbuf + (setq buffer-read-only nil) + (erase-buffer))) + (with-current-buffer patchbuf + (erase-buffer)) + (if (zerop (apply 'call-process + prettier-js-command bufferfile (list (list :file outputfile) errorfile) + nil (append prettier-js-args width-args (list "--stdin" "--stdin-filepath" buffer-file-name)))) + (progn + (call-process-region (point-min) (point-max) "diff" nil patchbuf nil "-n" "--strip-trailing-cr" "-" + outputfile) + (prettier-js--apply-rcs-patch patchbuf) + (message "Applied prettier with args `%s'" prettier-js-args) + (if errbuf (prettier-js--kill-error-buffer errbuf))) + (message "Could not apply prettier") + (if errbuf + (prettier-js--process-errors (buffer-file-name) errorfile errbuf)) + )) + (kill-buffer patchbuf) + (delete-file errorfile) + (delete-file bufferfile) + (delete-file outputfile)))) + +;;;###autoload +(define-minor-mode prettier-js-mode + "Runs prettier on file save when this mode is turned on" + :lighter " Prettier" + :global nil + (if prettier-js-mode + (add-hook 'before-save-hook 'prettier-js nil 'local) + (remove-hook 'before-save-hook 'prettier-js 'local))) + +(provide 'prettier-js) +;;; prettier-js.el ends here