diff --git a/init.example.el b/init.example.el index c1be24b80..58a5e0fb2 100644 --- a/init.example.el +++ b/init.example.el @@ -47,6 +47,7 @@ window-select ; visually switch windows :editor + ;(format +onsave) ; automated prettiness multiple-cursors ; editing in many places at once ;parinfer ; turn lisp into python, sort of rotate-text ; cycle region at point between text candidates diff --git a/modules/editor/format/autoload.el b/modules/editor/format/autoload.el new file mode 100644 index 000000000..b13b0cfda --- /dev/null +++ b/modules/editor/format/autoload.el @@ -0,0 +1,154 @@ +;;; editor/format/autoload.el -*- lexical-binding: t; -*- + +;;;###autodef +(cl-defun set-formatter! (modes formatter &key + name + install + filter + ok-statuses + error-regexp) + "Define a FORMATTER for MODES. + +MODES can be a major mode symbol, a vector of major modes, or a vector of +two-element vectors made up of [MAJOR-MODE FORM]. FORM is evaluated when the +buffer is formatted and its return value is a predicate for this formatter. Its +return value is stored in If it is non-nil, this formatter is used. Its return +value is stored in the `mode-result' variable for FORMATTER (if it's not a +string). + +FORMATTER can be a function, string or nested vector. + + If a function, it should be a formatter function that + `format-all-buffer-thunk' will accept. + If a string, it is assumed to be a shell command that the text will be piped + to (stdin). + If a vector, it should represent a shell command as a list of arguments. Each + element is either a string or vector [STRING ARG] where STRING is a format + string and ARG is both a predicate and argument for STRING. If ARG is nil, + STRING will be omitted from the vector. + +NAME is the identifier for this formatter. If FORMATTER is a lambda, NAME will +default to \"default\". + +INSTALL should be a string representing the shell command necessary to install +this formatter's dependencies. INSTALL can also be a list of lists made up of +two items: (OS COMMAND). + +Basic examples: + + (set-formatter! '(asm-mode nasm-mode) \"asmfmt\") + (set-formatter! 'python-mode \"black -q -\" :install \"pip install black\") + +Advanced examples: + + (set-formatter! + '((c-mode \".c\") + (c++-mode \".cpp\") + (java-mode \".java\") + (objc-mode \".m\") + (protobuf-mode \".proto\")) + '(\"clang-format\" + (\"-assume-filename=%S\" (or buffer-file-name mode-result \"\"))) + :install '(macos \"brew install clang-format\")) + + (set-formatter! + '(html-mode + (web-mode (and (equal \"none\" web-mode-engine) + (car (member web-mode-content-type '(\"xml\" \"html\")))))) + '(\"tidy\" \"-q\" \"-indent\" + (\"-xml\" (memq major-mode '(nxml-mode xml-mode)))) + :ok-statuses '(0 1) + :install '(macos \"brew install tidy-html5\")) + + (set-formatter! 'elm-mode + \"elm-format --yes --stdin\" + :install '(macos \"brew install elm\") + :filter + (lambda (output errput first-diff) + (list output + (format-all-remove-ansi-color errput) + first-diff)))" + (declare (indent defun)) + (let* ((command-list (cond ((stringp formatter) ; shell command + (split-string formatter " " t)) + ((listp formatter) ; shell command in lists + formatter))) + (name (cond ((or name (car command-list))) + ((symbolp formatter) (symbol-name formatter)) + ("default")))) + (after! format-all + (puthash + name + (lambda (executable mode-result) + (ignore mode-result executable) + (apply (or filter #'identity) + (cond ((commandp formatter) + (let ((mode major-mode) + (file buffer-file-name)) + (format-all-buffer-thunk + (lambda (input) + (setq buffer-file-name file) + (funcall mode) + (insert input) + (condition-case e + (progn + (call-interactively formatter) + (list nil "")) + (error (list t (error-message-string e)))))))) + ((functionp formatter) + (format-all-buffer-thunk formatter)) + ((let ((args (cl-loop for arg in command-list + if (stringp arg) collect arg + else if + (and (listp arg) + (eval (cadr arg) t)) + collect (format (car arg) it)))) + (if (or ok-statuses error-regexp) + (apply #'format-all-buffer-hard ok-statuses error-regexp args) + (apply #'format-all-buffer-easy args))))))) format-all-format-table) + (puthash name (car command-list) format-all-executable-table) + (puthash name (format-all-resolve-system install) format-all-install-table) + (dolist (mode (doom-enlist modes)) + (cl-destructuring-bind (m &optional probe) + (doom-enlist mode) + (format-all-pushhash + m (cons name (if probe `(lambda () ,probe))) + format-all-mode-table)))) + name)) + +;;;###autoload +(defun +format-region (beg end) + "TODO" + (cl-check-type beg integer) + (cl-check-type end integer) + (save-restriction + (let* ((beg (save-excursion (goto-char beg) (line-beginning-position))) + (end (save-excursion (goto-char end) (line-end-position))) + (file buffer-file-name) + (input (buffer-substring-no-properties beg end))) + (with-temp-buffer + (setq buffer-file-name file) + (insert input) + (format-all-buffer))))) + + +;; +;; Commands +;; + +;;;###autoload +(defalias '+format/buffer 'format-all-buffer) + +;;;###autoload +(defun +format/region (beg end) + "TODO" + (interactive "r") + (+format-region beg end)) + +;;;###autoload +(defun +format/region-or-buffer (beg end) + "TODO" + (interactive "r") + (if (use-region-p) + (+format-region beg end) + (format-all-buffer))) diff --git a/modules/editor/format/config.el b/modules/editor/format/config.el new file mode 100644 index 000000000..e3e4fa05f --- /dev/null +++ b/modules/editor/format/config.el @@ -0,0 +1,32 @@ +;;; editor/format/config.el -*- lexical-binding: t; -*- + +(defvar +format-on-save-enabled-modes t + "A list of major modes in which to enable `format-all-mode'. + +This mode will auto-format buffers when you save them. + +If this list begins with `not', then it negates the list. +If it is `t', it is enabled in all modes. +If nil, it is disabled in all modes, the same as if the +onsave flag wasn't + used at all.") + + +;; +;; Plugins +;; + +(defun +format|enable-on-save-maybe () + "Enable `format-all-mode' in buffers. See `+format-on-save-enabled-modes' to +control which major modes to target." + (unless (or (eq major-mode 'fundamental-mode) + (cond ((booleanp +format-on-save-enabled-modes) + (null +format-on-save-enabled-modes)) + ((eq (car +format-on-save-enabled-modes) 'not) + (memq major-mode (cdr +format-on-save-enabled-modes))) + ((not (memq major-mode +format-on-save-enabled-modes)))) + (require 'format-all nil t) + (not (format-all-probe))) + (format-all-mode +1))) + +(when (featurep! +onsave) + (add-hook 'after-change-major-mode-hook #'+format|enable-on-save-maybe)) diff --git a/modules/editor/format/packages.el b/modules/editor/format/packages.el new file mode 100644 index 000000000..858b6d9ee --- /dev/null +++ b/modules/editor/format/packages.el @@ -0,0 +1,4 @@ +;; -*- no-byte-compile: t; -*- +;;; editor/format/packages.el + +(package! format-all) diff --git a/modules/lang/go/config.el b/modules/lang/go/config.el index ec26c9a38..82e74217e 100644 --- a/modules/lang/go/config.el +++ b/modules/lang/go/config.el @@ -12,15 +12,13 @@ :references #'go-guru-referrers :documentation #'godoc-at-point) + (set-formatter! 'go-mode #'gofmt) (when-let* ((goimports (executable-find "goimports"))) (setq gofmt-command goimports)) - (when (featurep! :feature syntax-checker) (setq gofmt-show-errors nil)) ; Leave it to flycheck (add-hook 'go-mode-hook #'go-eldoc-setup) - (add-hook! go-mode - (add-hook 'before-save-hook #'gofmt-before-save nil t)) (def-menu! +go/refactor-menu "Refactoring commands for `go-mode' buffers." diff --git a/modules/lang/php/config.el b/modules/lang/php/config.el index bb5aaeddf..493f21e06 100644 --- a/modules/lang/php/config.el +++ b/modules/lang/php/config.el @@ -9,6 +9,7 @@ (set-repl-handler! 'php-mode #'php-boris) (set-lookup-handlers! 'php-mode :documentation #'php-search-documentation) + (set-formatter! 'php-mode #'php-cs-fixer-fix) ;; `+php-company-backend' uses `company-phpactor', `php-extras-company' or ;; `company-dabbrev-code', in that order. diff --git a/modules/lang/sh/config.el b/modules/lang/sh/config.el index c0743dd14..77abb3e8a 100644 --- a/modules/lang/sh/config.el +++ b/modules/lang/sh/config.el @@ -67,6 +67,4 @@ (def-package! fish-mode :when (featurep! +fish) :defer t - :config - (add-hook! fish-mode - (add-hook 'before-save-hook #'fish_indent-before-save))) + :config (set-formatter! 'fish-mode #'fish_indent))