From 4ecd616cd8f60640d6b0be2ffc3d762e88099bb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ellis=20Keny=C5=91?= Date: Tue, 16 Aug 2022 08:13:55 +0100 Subject: [PATCH] refactor(format): replace with apheleia Initial refactor of format module to replace format-all with apheleia --- modules/editor/format/autoload/format.el | 319 ++++----------------- modules/editor/format/autoload/settings.el | 161 ++--------- modules/editor/format/config.el | 89 ++---- modules/editor/format/packages.el | 3 +- modules/editor/format/test/test-format.el | 103 ------- modules/lang/ocaml/config.el | 2 - modules/lang/php/config.el | 1 - modules/lang/sh/config.el | 15 +- modules/lang/web/+html.el | 18 +- 9 files changed, 126 insertions(+), 585 deletions(-) delete mode 100644 modules/editor/format/test/test-format.el diff --git a/modules/editor/format/autoload/format.el b/modules/editor/format/autoload/format.el index b06290da9..6f5ee252b 100644 --- a/modules/editor/format/autoload/format.el +++ b/modules/editor/format/autoload/format.el @@ -1,276 +1,85 @@ ;;; editor/format/autoload.el -*- lexical-binding: t; -*- -(defvar +format-region-p nil - "Is non-nil if currently reformatting a selected region, rather than the whole -buffer.") - -;;;###autoload -(autoload 'format-all--probe "format-all") - -(defun +format--delete-whole-line (&optional arg) - "Delete the current line without putting it in the `kill-ring'. -Derived from function `kill-whole-line'. ARG is defined as for that -function. - -Stolen shamelessly from go-mode" - (setq arg (or arg 1)) - (if (and (> arg 0) - (eobp) - (save-excursion (forward-visible-line 0) (eobp))) - (signal 'end-of-buffer nil)) - (if (and (< arg 0) - (bobp) - (save-excursion (end-of-visible-line) (bobp))) - (signal 'beginning-of-buffer nil)) - (cond ((zerop arg) - (delete-region (progn (forward-visible-line 0) (point)) - (progn (end-of-visible-line) (point)))) - ((< arg 0) - (delete-region (progn (end-of-visible-line) (point)) - (progn (forward-visible-line (1+ arg)) - (unless (bobp) - (backward-char)) - (point)))) - ((delete-region (progn (forward-visible-line 0) (point)) - (progn (forward-visible-line arg) (point)))))) - -;;;###autoload -(defun +format--apply-rcs-patch (patch-buffer) - "Apply an RCS-formatted diff from PATCH-BUFFER to the current buffer. - -Stolen shamelessly from go-mode" - (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) - (column (current-column))) - (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 +format--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 - (cl-decf line-offset len) - (goto-char (point-min)) - (forward-line (- from len line-offset)) - (insert text))))) - ((equal action "d") - (with-current-buffer target-buffer - (goto-char (point-min)) - (forward-line (1- (- from line-offset))) - (cl-incf line-offset len) - (+format--delete-whole-line len))) - ((error "Invalid rcs patch or internal error in +format--apply-rcs-patch"))))))) - (move-to-column column))) - (defun +format--current-indentation () (save-excursion (goto-char (point-min)) (skip-chars-forward " \t\n") (current-indentation))) - -;; -;; Public library - -(defun +format-completing-read () - "TODO" - (require 'format-all) - (let* ((fmtlist (mapcar #'symbol-name (hash-table-keys format-all--format-table))) - (fmt (completing-read "Formatter: " fmtlist))) - (if fmt (intern fmt)))) - -;;;###autoload -(defun +format-probe-a (fn) - "Use `+format-with' instead, if it is set. -Prompts for a formatter if universal arg is set." - (cond ((or buffer-read-only (eq +format-with :none)) - (list nil nil)) - (current-prefix-arg - (list (or (+format-completing-read) - (user-error "Aborted")) - t)) - (+format-with - (list +format-with t)) - ((and +format-with-lsp - (bound-and-true-p lsp-managed-mode) - (lsp-feature? "textDocument/formatting")) - (list 'lsp nil)) - ((and +format-with-lsp - (bound-and-true-p eglot--managed-mode) - (eglot--server-capable :documentFormattingProvider)) - (list 'eglot nil)) - ((funcall fn)))) - -;;;###autoload -(defun +format-buffer-a (formatter mode-result) - "Advice that extends `format-all-buffer--with' to: - -1. Enable partial/region reformatting, while preserving leading indentation, -2. Applies changes via RCS patch, line by line, to protect buffer markers and - reduce cursor movement or window scrolling. - -See `+format/buffer' for the interactive version of this function, and -`+format-buffer-h' to use as a `before-save-hook' hook." - (cond - ((eq formatter 'lsp) - (call-interactively - (if +format-region-p #'lsp-format-region #'lsp-format-buffer))) - ((eq formatter 'eglot) - (call-interactively - (if +format-region-p #'eglot-format #'eglot-format-buffer))) - ((let ((f-function (gethash formatter format-all--format-table)) - (executable (format-all--formatter-executable formatter)) - (indent 0) - (old-line-number (line-number-at-pos)) - (old-column (current-column))) - (pcase-let* - ((`(,output ,errput) - ;; To reliably format regions, rather than the whole buffer, and - ;; `format-all' (and various formatting functions, like `gofmt') widen - ;; the buffer, we must copy the region first. - (let ((output (buffer-substring-no-properties (point-min) (point-max))) - (origin-buffer (or (buffer-base-buffer) (current-buffer))) - ;; Fixes #5133: some packages (like lsp-mode) can do a bunch - ;; of complicated stuff in these hooks. Better to not have to - ;; deal with any of them at all. - write-file-functions - before-save-hook - after-save-hook - kill-buffer-query-functions - kill-buffer-hook) - (with-temp-buffer - (with-silent-modifications - (insert output) - ;; Ensure this temp buffer seems as much like the origin - ;; buffer as possible, in case the formatter is an elisp - ;; function, like `gofmt'. - (cl-loop for (var . val) - in (cl-remove-if-not #'listp (buffer-local-variables origin-buffer)) - ;; Making enable-multibyte-characters buffer-local - ;; causes an error. - unless (eq var 'enable-multibyte-characters) - ;; Fixes #5133: don't deal with complicated hook - ;; functionality! This isn't a real buffer anyway. - unless (string-match-p (symbol-name var) "-\\(hook\\|functions\\)$") - ;; Using setq-local would quote var. - do (set (make-local-variable var) val)) - ;; Since we're piping a region of text to the formatter, remove - ;; any leading indentation to make it look like a file. - (setq indent (+format--current-indentation)) - (when (> indent 0) - (indent-rigidly (point-min) (point-max) (- indent))) - (funcall f-function executable mode-result))))) - (`,status - (cond ((null output) :error) - ((eq output t) :already-formatted) - (t :reformatted)))) - (unwind-protect - (when (eq status :reformatted) - (let ((tmpfile (make-temp-file "doom-format")) - (patchbuf (get-buffer-create " *doom format patch*")) - (coding-system-for-read coding-system-for-read) - (coding-system-for-write coding-system-for-write)) - (unless IS-WINDOWS - (setq coding-system-for-read 'utf-8 - coding-system-for-write 'utf-8)) - (unwind-protect - (progn - (with-current-buffer patchbuf - (erase-buffer)) - (with-temp-file tmpfile - (erase-buffer) - (insert output) - (when (> indent 0) - ;; restore indentation without affecting new - ;; indentation - (indent-rigidly (point-min) (point-max) - (max 0 (- indent (+format--current-indentation)))))) - (if (zerop (call-process-region (point-min) (point-max) "diff" nil patchbuf nil "-n" "-" tmpfile)) - (setq status :already-formatted) - (+format--apply-rcs-patch patchbuf) - (list output errput))) - (kill-buffer patchbuf) - (delete-file tmpfile)))) - (format-all--show-or-hide-errors errput) - (goto-char (point-min)) - (forward-line (1- old-line-number)) - (let ((line-length (- (point-at-eol) (point-at-bol)))) - (goto-char (+ (point) (min old-column line-length)))) - (run-hook-with-args 'format-all-after-format-functions formatter status) - (message (pcase status - (:error "Formatting error") - (:already-formatted "Already formatted") - (:reformatted (format "Reformatted with %s" formatter)))))))))) +(defun +format-region (start end &optional callback) + "Format from START to END with `apheleia'." + (when-let* ((command (apheleia--get-formatter-command + (if current-prefix-arg + 'prompt + 'interactive))) + (cur-buffer (current-buffer)) + (formatted-buffer (get-buffer-create " *apheleia-formatted*")) + (indent 0)) + (with-current-buffer formatted-buffer + (erase-buffer) + (unless IS-WINDOWS + (setq-local coding-system-for-read 'utf-8) + (setq-local coding-system-for-write 'utf-8)) + ;; Ensure this temp buffer seems as much like the origin buffer as + ;; possible, in case the formatter is an elisp function, like `gofmt'. + (cl-loop for (var . val) + in (cl-remove-if-not #'listp (buffer-local-variables origin-buffer)) + ;; Making enable-multibyte-characters buffer-local causes an + ;; error. + unless (eq var 'enable-multibyte-characters) + ;; Using setq-local would quote var. + do (set (make-local-variable var) val)) + ;; + (insert-buffer-substring-no-properties cur-buffer start end) + ;; Since we're piping a region of text to the formatter, remove any + ;; leading indentation to make it look like a file. + (setq indent (+format--current-indentation)) + (when (> indent 0) + (indent-rigidly (point-min) (point-max) (- indent))) + ;; + (apheleia-format-buffer + command + (lambda () + (with-current-buffer formatted-buffer + (when (> indent 0) + ;; restore indentation without affecting new + ;; indentation + (indent-rigidly (point-min) (point-max) + (max 0 (- indent (+format--current-indentation)))))) + (with-current-buffer cur-buffer + (delete-region start end) + (insert-buffer-substring-no-properties formatted-buffer) + (when callback (funcall callback)) + (kill-buffer formatted-buffer))))))) ;; ;;; Commands -(defun +format--org-region (beg end) - "Reformat the region within BEG and END. -If nil, BEG and/or END will default to the boundaries of the src block at point." - (let ((element (org-element-at-point))) - (save-excursion - (let* ((block-beg (save-excursion - (goto-char (org-babel-where-is-src-block-head element)) - (line-beginning-position 2))) - (block-end (save-excursion - (goto-char (org-element-property :end element)) - (skip-chars-backward " \t\n") - (line-beginning-position))) - (beg (if beg (max beg block-beg) block-beg)) - (end (if end (min end block-end) block-end)) - (lang (org-element-property :language element)) - (major-mode (org-src-get-lang-mode lang))) - (if (eq major-mode 'org-mode) - (user-error "Cannot reformat an org src block in org-mode") - (+format/region beg end)))))) - -(defun +format--buffer () - (if (and (eq major-mode 'org-mode) - (org-in-src-block-p t)) - (+format--org-region (point-min) (point-max)) - (if (called-interactively-p 'any) - (format-all-buffer) - (ignore-errors (format-all-buffer))))) - ;;;###autoload -(defun +format/buffer () +(defun +format/buffer (&optional arg) "Reformat the current buffer using LSP or `format-all-buffer'." - (interactive) - (+format--buffer)) + (interactive "P") + (call-interactively + (if (and +format-with-lsp + (bound-and-true-p lsp-mode) + (lsp-feature? "textDocument/formatting")) + #'lsp-format-buffer + #'apheleia-format-buffer))) ;;;###autoload -(defun +format/region (beg end) +(defun +format/region (beg end &optional arg) "Runs the active formatter on the lines within BEG and END. WARNING: this may not work everywhere. It will throw errors if the region contains a syntax error in isolation. It is mostly useful for formatting snippets or single lines." (interactive "rP") - (let ((+format-region-p t)) - (save-restriction - (narrow-to-region beg end) - (+format--buffer)))) + (if (and +format-with-lsp + (bound-and-true-p lsp-mode) + (lsp-feature? "textDocument/rangeFormatting")) + (call-interactively #'lsp-format-region) + (+format-region beg end))) ;;;###autoload (defun +format/region-or-buffer () @@ -281,13 +90,3 @@ is selected)." (if (doom-region-active-p) #'+format/region #'+format/buffer))) - - -;; -;; Hooks - -;;;###autoload -(defalias '+format-buffer-h #'+format/buffer - "Format the source code in the current buffer with minimal feedback. - -Meant for `before-save-hook'.") diff --git a/modules/editor/format/autoload/settings.el b/modules/editor/format/autoload/settings.el index 0261c175f..0406facdb 100644 --- a/modules/editor/format/autoload/settings.el +++ b/modules/editor/format/autoload/settings.el @@ -1,97 +1,11 @@ ;;; editor/format/autoload/settings.el -*- lexical-binding: t; -*- -;; This must be redefined here because `format-all' only makes it available at -;; compile time. -(defconst +format-system-type - (cl-case system-type - (windows-nt 'windows) - (cygwin 'windows) - (darwin 'macos) - (gnu/linux 'linux) - (berkeley-unix - (save-match-data - (let ((case-fold-search t)) - (cond ((string-match "freebsd" system-configuration) 'freebsd) - ((string-match "openbsd" system-configuration) 'openbsd) - ((string-match "netbsd" system-configuration) 'netbsd)))))) - "Current operating system according to the format-all package.") - -(defun +format--resolve-system (choices) - "Get first choice matching `format-all-system-type' from CHOICES." - (cl-loop for choice in choices - if (atom choice) return choice - else if (eql +format-system-type (car choice)) - return (cadr choice))) - - -(defun +format--make-command (formatter &rest _) - `(format-all--buffer-thunk - (lambda (input) - (with-silent-modifications - (setq buffer-file-name ,(buffer-file-name (buffer-base-buffer)) - default-directory ,default-directory) - (delay-mode-hooks (funcall ',major-mode)) - (insert input) - (condition-case e - (progn - (doom-log "formatter (commandp) %s" #',formatter) - (call-interactively #',formatter) - (list nil "")) - (error (list t (error-message-string e)))))))) - -(defun +format--make-function (formatter &rest _) - `(progn - (doom-log "formatter (functionp) %s" #',formatter) - (format-all--buffer-thunk #',formatter))) - -(defun +format--make-shell-command (command ok-statuses error-regexp) - (+format--make-shell-command-list (split-string command " " t) - ok-statuses error-regexp)) - -(defun +format--make-shell-command-list (command-list ok-statuses error-regexp) - `(let (args) - (dolist (arg ',command-list) - (cond ((stringp arg) - (push arg args)) - ((listp arg) - (catch 'skip - (let (subargs this) - (while (setq this (pop arg)) - (cond ((not (stringp (car arg))) - (let ((val (eval (pop arg) t))) - (unless val (throw 'skip nil)) - (push (format this val) subargs))) - ((stringp this) - (push this subargs)))) - (setq args (append subargs args))))))) - (doom-log "formatter (arglist) %s" args) - (if ,(and (or ok-statuses error-regexp) t) - (apply #'format-all--buffer-hard - ',ok-statuses ,error-regexp nil - (reverse args)) - (apply #'format-all--buffer-easy (reverse args))))) - -(cl-defun +format--set (name &key function modes unset) - (declare (indent defun)) - (when (and unset (not (gethash name format-all--format-table))) - (error "'%s' formatter does not exist to be unset" name)) - (puthash name function format-all--format-table) - (dolist (mode (ensure-list modes)) - (cl-destructuring-bind (m &optional probe) - (ensure-list mode) - (if unset - (puthash m (assq-delete-all name (gethash key format-all-mode-table)) - format-all-mode-table) - (format-all--pushhash - m (cons name (if probe `(lambda () ,probe))) - format-all--mode-table))))) - ;;;###autodef (cl-defun set-formatter! - (name formatter &key modes filter ok-statuses error-regexp) + (name &rest args &key modes filter &allow-other-keys) "Define (or modify) a formatter named NAME. -Supported keywords: :modes :filter :ok-statuses :error-regexp +Supported keywords: :modes :filter NAME is a symbol that identifies this formatter. @@ -99,7 +13,7 @@ FORMATTER can be a symbol referring to another formatter, a function, string or nested list. If a function, it should be a formatter function that - `format-all--buffer-thunk' will accept. + `apheleia--run-formatter-function' will accept. If a string, it is assumed to be a shell command that the buffer's text will be piped to (through stdin). If a list, it should represent a shell command as a list of arguments. Each @@ -107,6 +21,9 @@ nested list. string and ARG is both a predicate and argument for STRING. If ARG is nil, STRING will be omitted from the vector. +For more information on how to structure the list to be +compatible, see `apheleia--run-formatter-function'. + MODES is a major mode, a list thereof, or a list of two-element sublists with the structure: (MAJOR-MODE FORM). FORM is evaluated when the buffer is formatted and its return value serves two purposes: @@ -116,27 +33,12 @@ and its return value serves two purposes: 2. It's return value is made available to FORMATTER if it is a function or list of shell arguments via the `mode-result' variable. -FILTER is a function that takes three arguments: the formatted output, any error -output and the position of the first change. This function must return these -three after making whatever changes you like to them. This might be useful if -the output contains ANSI color codes that need to be stripped out (as is the -case with elm-format). - -OK-STATUSES and ERROR-REGEXP are ignored if FORMATTER is not a shell command. - -OK-STATUSES is a list of integer exit codes that should be treated as success -codes. However, if ERROR-REGEXP is given, and the program's stderr contains that -regexp, then the formatting is considered failed even if the exit status is in -OK-STATUSES. - Basic examples: - (set-formatter! 'asmfmt \"asmfmt\" :modes '(asm-mode nasm-mode)) (set-formatter! 'black \"black -q -\") (set-formatter! 'html-tidy \"tidy -q -indent\" :modes '(html-mode web-mode)) Advanced examples: - (set-formatter! 'clang-format '(\"clang-format\" @@ -154,9 +56,7 @@ Advanced examples: :modes '(html-mode (web-mode (and (equal \"none\" web-mode-engine) - (car (member web-mode-content-type '(\"xml\" \"html\")))))) - :ok-statuses '(0 1) - :executable \"tidy\") + (car (member web-mode-content-type '(\"xml\" \"html\"))))))) (set-formatter! 'html-tidy ; overwrite predefined html-tidy formatter '(\"tidy\" \"-q\" \"-indent\" @@ -165,39 +65,22 @@ Advanced examples: \"--show-body-only\" \"auto\" (\"--indent-spaces\" \"%d\" tab-width) (\"--indent-with-tabs\" \"%s\" (if indent-tabs-mode \"yes\" \"no\")) - (\"-xml\" (memq major-mode '(nxml-mode xml-mode)))) - :ok-statuses '(0 1))) + (\"-xml\" (memq major-mode '(nxml-mode xml-mode))))) (set-formatter! 'elm-format - \"elm-format --yes --stdin\" - :filter - (lambda (output errput first-diff) - (list output - (format-all--remove-ansi-color errput) - first-diff)))" + \"elm-format --yes --stdin\") +" (declare (indent defun)) (cl-check-type name symbol) - (after! format-all - (if (null formatter) - (+format--set name - :unset t - :modes modes) - (let ((fn (funcall (cond ((stringp formatter) - #'+format--make-shell-command) - ((listp formatter) - #'+format--make-shell-command-list) - ((and (commandp formatter) - (not (stringp formatter))) - #'+format--make-command) - ((functionp formatter) - #'+format--make-function)) - formatter - ok-statuses - error-regexp))) - (cl-check-type filter (or function null)) - (+format--set name - :function - `(lambda (executable mode-result) - ,(if filter `(apply #',filter ,fn) fn)) - :modes modes) - name)))) + (after! apheleia + (if (null args) + (progn + (setq apheleia-formatters + (assq-delete-all name apheleia-formatters)) + (while (rassoc name apheleia-mode-alist) + (setq apheleia-mode-alist + (assq-delete-all (car (rassoc name apheleia-mode-alist)) apheleia-mode-alist)))) + (setf (alist-get name apheleia-formatters) args) + (when modes + (dolist (mode modes) + (setf (alist-get mode apheleia-mode-alist) name)))))) diff --git a/modules/editor/format/config.el b/modules/editor/format/config.el index 0e634adbe..1075a98f2 100644 --- a/modules/editor/format/config.el +++ b/modules/editor/format/config.el @@ -1,89 +1,54 @@ ;;; editor/format/config.el -*- lexical-binding: t; -*- -(defvar +format-on-save-enabled-modes - '(not emacs-lisp-mode ; elisp's mechanisms are good enough - sql-mode ; sqlformat is currently broken - tex-mode ; latexindent is broken - latex-mode - org-msg-edit-mode) ; doesn't need a formatter - "A list of major modes in which to reformat the buffer upon saving. - -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. - -Irrelevant if you do not have the +onsave flag enabled for this module.") - (defvar +format-preserve-indentation t "If non-nil, the leading indentation is preserved when formatting the whole buffer. This is particularly useful for partials. Indentation is always preserved when formatting regions.") -(defvar-local +format-with nil - "Set this to explicitly use a certain formatter for the current buffer.") - (defvar +format-with-lsp t "If non-nil, format with LSP formatter if it's available. This can be set buffer-locally with `setq-hook!' to disable LSP formatting in select buffers.") +(defvaralias '+format-with 'apheleia-formatter + "Set this to explicitly use a certain formatter for the current buffer.") + ;; ;;; Bootstrap -(add-to-list 'doom-debug-variables 'format-all-debug) - -(defun +format-enable-on-save-maybe-h () - "Enable formatting on save in certain major modes. - -This is controlled by `+format-on-save-enabled-modes'." - (or (cond ((eq major-mode 'fundamental-mode)) - ((string-prefix-p " " (buffer-name))) - ((and (booleanp +format-on-save-enabled-modes) - (not +format-on-save-enabled-modes))) - ((and (listp +format-on-save-enabled-modes) - (if (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))))) - ((not (require 'format-all nil t)))) - (format-all-mode +1))) - (when (modulep! +onsave) - (add-hook 'after-change-major-mode-hook #'+format-enable-on-save-maybe-h)) + (add-hook 'doom-first-file-hook #'apheleia-global-mode)) ;; ;;; Hacks -;; Allow a specific formatter to be used by setting `+format-with', either -;; buffer-locally or let-bound. -(advice-add #'format-all--probe :around #'+format-probe-a) +(defadvice! +format--inhibit-reformat-on-prefix-arg-a (orig-fn &optional arg) + "Make it so \\[save-buffer] with prefix arg inhibits reformatting." + :around #'save-buffer + (let ((apheleia-mode (and apheleia-mode (member arg '(nil 1))))) + (funcall orig-fn))) -;; Doom uses a modded `format-all-buffer', which -;; 1. Enables partial reformatting (while preserving leading indentation), -;; 2. Applies changes via RCS patch, line by line, to protect buffer markers -;; and avoid any jarring cursor+window scrolling. -(advice-add #'format-all-buffer--with :override #'+format-buffer-a) +(add-hook! 'apheleia-post-format-hook + ;; HACK `web-mode' doesn't update syntax highlighting after arbitrary buffer + ;; modifications, so we must trigger refontification manually. + (defun +format--fix-web-mode-fontification-h () + (when (eq major-mode 'web-mode) + (setq web-mode-fontification-off nil) + (when (and web-mode-scan-beg web-mode-scan-end global-font-lock-mode) + (save-excursion + (font-lock-fontify-region web-mode-scan-beg web-mode-scan-end))))) + (defun +format--refresh-git-gutter-h () + (when (bound-and-true-p git-gutter-mode) + (git-gutter)))) -;; format-all-mode "helpfully" raises an error when it doesn't know how to -;; format a buffer. -(add-to-list 'debug-ignored-errors "^Don't know how to format ") -;; Don't pop up imposing warnings about missing formatters, but still log it in -;; to *Messages*. -(defadvice! +format--all-buffer-from-hook-a (fn &rest args) - :around #'format-all-buffer--from-hook - (letf! (defun format-all-buffer--with (formatter mode-result) - (when (or (eq formatter 'lsp) - (eq formatter 'eglot) - (condition-case-unless-debug e - (format-all--formatter-executable formatter) - (error - (message "Warning: cannot reformat buffer because %S isn't installed" - (gethash formatter format-all--executable-table)) - nil))) - (funcall format-all-buffer--with formatter mode-result))) - (apply fn args))) +;; +;;; Additional formatters + +(after! apheleia-mode + ;; TODO html-tidy + ) diff --git a/modules/editor/format/packages.el b/modules/editor/format/packages.el index eada328ac..0f1fd9933 100644 --- a/modules/editor/format/packages.el +++ b/modules/editor/format/packages.el @@ -1,4 +1,5 @@ ;; -*- no-byte-compile: t; -*- ;;; editor/format/packages.el -(package! format-all :pin "47d862d40a088ca089c92cd393c6dca4628f87d3") +;; TODO Pin when this is close to finish +(package! apheleia) diff --git a/modules/editor/format/test/test-format.el b/modules/editor/format/test/test-format.el deleted file mode 100644 index dc2306838..000000000 --- a/modules/editor/format/test/test-format.el +++ /dev/null @@ -1,103 +0,0 @@ -;; -*- no-byte-compile: t; -*- -;;; editor/format/test/test-format.el - -(load! "../autoload/settings") -(load! "../autoload/format") -(require! :editor format) -(require 'format-all) - -;; -(describe "editor/format" - :var (format-all--format-table - format-all--mode-table) - - (before-each - (setq format-all--format-table (make-hash-table) - format-all--mode-table (make-hash-table))) - - (describe "set-formatter!" - (before-each - (set-formatter! 'test (lambda () (interactive)))) - - (it "defines a formatter" - (set-formatter! 'new (lambda () (interactive))) - (expect (gethash 'new format-all--mode-table) :to-equal nil) - (expect (functionp (gethash 'new format-all--format-table)))) - - (it "defines a formatter with modes" - (set-formatter! 'new (lambda () (interactive)) - :modes '(a-mode (b-mode "x"))) - (expect (gethash 'a-mode format-all--mode-table) - :to-equal '((new))) - (expect (gethash 'b-mode format-all--mode-table) - :to-equal '((new . (lambda () "x"))))) - - (it "replaces a pre-existing formatter" - (let ((old-fn (gethash 'test format-all--format-table))) - (set-formatter! 'test "echo") - (expect (gethash 'test format-all--format-table) :not :to-equal old-fn))) - - (it "unsets a pre-existing formatter" - (set-formatter! 'test nil) - (expect (gethash 'test format-all--format-table) :to-be nil)) - - (it "errors when unsetting non-existent formatter" - (expect (set-formatter! 'doesnt-exist nil) :to-throw))) - - - ;; TODO - (xdescribe "hooks" - (describe "format|enable-on-save-maybe") - (describe "format|enable-on-save")) - - - ;; TODO - (xdescribe "formatting" - (before-each - (set-formatter! 'command - (lambda () - (interactive) - (let ((first-line (car (split-string (buffer-string) "\n")))) - (erase-buffer) - (insert first-line))) - :modes '(text-mode)) - (set-formatter! 'faulty-command - (lambda () - (interactive) - (error "This is a test")) - :modes '(text-mode)) - (set-formatter! 'function - (lambda (input) - (insert (car (split-string input "\n"))) - (list nil nil)) - :modes '(text-mode)) - (set-formatter! 'shellcmd "head -n 1" - :modes '(text-mode)) - (set-formatter! 'cmdlist '("head" "-n" "1") - :modes '(text-mode))) - - (describe "with an interactive command" - (it "formats a buffer" ) - (it "formats a region" ) - (it "no-ops if no change" ) - (it "doesn't modify the buffer in case of errors" ) - (it "preserves indentation" )) - - (describe "with a function" - (it "formats a buffer" ) - (it "formats a region" ) - (it "no-ops if no change" ) - (it "doesn't modify the buffer in case of errors" ) - (it "preserves indentation" )) - - (describe "with a shell command") - - (describe "with a shell command list" - (it "formats a buffer" ) - (it "formats a region" ) - (it "no-ops if no change" ) - (it "doesn't modify the buffer in case of errors" ) - (it "preserves indentation" ) - - (it "interpolates non-strings into format strings" ) - (it "conditionally appends sublisted options" )))) diff --git a/modules/lang/ocaml/config.el b/modules/lang/ocaml/config.el index 965955dce..cb5cce429 100644 --- a/modules/lang/ocaml/config.el +++ b/modules/lang/ocaml/config.el @@ -107,8 +107,6 @@ :commands ocamlformat :hook (tuareg-mode-local-vars . +ocaml-init-ocamlformat-h) :config - (set-formatter! 'ocamlformat #'ocamlformat - :modes '(caml-mode tuareg-mode)) ;; TODO Fix region-based formatting support (defun +ocaml-init-ocamlformat-h () (setq +format-with 'ocp-indent) diff --git a/modules/lang/php/config.el b/modules/lang/php/config.el index 20132e1da..5f9d711f3 100644 --- a/modules/lang/php/config.el +++ b/modules/lang/php/config.el @@ -30,7 +30,6 @@ (set-docsets! 'php-mode "PHP" "PHPUnit" "Laravel" "CakePHP" "CodeIgniter" "Doctrine_ORM") (set-repl-handler! 'php-mode #'+php/open-repl) (set-lookup-handlers! 'php-mode :documentation #'php-search-documentation) - (set-formatter! 'php-mode #'php-cs-fixer-fix) (set-ligatures! 'php-mode ;; Functional :lambda "function()" :lambda "fn" diff --git a/modules/lang/sh/config.el b/modules/lang/sh/config.el index 34b7032d5..f87925e3e 100755 --- a/modules/lang/sh/config.el +++ b/modules/lang/sh/config.el @@ -17,10 +17,13 @@ :config (set-docsets! 'sh-mode "Bash") (set-electric! 'sh-mode :words '("else" "elif" "fi" "done" "then" "do" "esac" ";;")) - (set-formatter! 'shfmt - '("shfmt" "-ci" - ("-i" "%d" (unless indent-tabs-mode tab-width)) - ("-ln" "%s" (pcase sh-shell (`bash "bash") (`mksh "mksh") (_ "posix"))))) + (after! apheleia + (setf (alist-get 'shfmt apheleia-formatters) + '("shfmt" "-ci" + (unless indent-tabs-mode + (list "-i" (number-to-string tab-width))) + (list "-ln" (pcase sh-shell (`bash "bash") (`mksh "mksh") (_ "posix")))))) + (set-repl-handler! 'sh-mode #'+sh/open-repl) (set-lookup-handlers! 'sh-mode :documentation #'+sh-lookup-documentation-handler) (set-ligatures! 'sh-mode @@ -85,10 +88,6 @@ ;; whatis lookups are exceptionally slow on macOS (#5860) company-shell-dont-fetch-meta IS-MAC)) -(use-package! fish-mode - :when (modulep! +fish) - :defer t - :config (set-formatter! 'fish-mode #'fish_indent)) (use-package! powershell :when (modulep! +powershell) diff --git a/modules/lang/web/+html.el b/modules/lang/web/+html.el index 2dd0a881d..7baff19db 100644 --- a/modules/lang/web/+html.el +++ b/modules/lang/web/+html.el @@ -27,15 +27,15 @@ ;; tidy is already defined by the format-all package. We redefine it to add ;; more sensible arguments to the tidy command. - (set-formatter! 'html-tidy - '("tidy" "-q" "-indent" - "--tidy-mark" "no" - "--drop-empty-elements" "no" - ("--show-body-only" "%s" (if +format-region-p "true" "auto")) - ("--indent-spaces" "%d" tab-width) - ("--indent-with-tabs" "%s" (if indent-tabs-mode "yes" "no")) - ("-xml" (memq major-mode '(nxml-mode xml-mode)))) - :ok-statuses '(0 1)) + ;; (set-formatter! 'html-tidy + ;; '("tidy" "-q" "-indent" + ;; "--tidy-mark" "no" + ;; "--drop-empty-elements" "no" + ;; ("--show-body-only" "%s" (if +format-region-p "true" "auto")) + ;; ("--indent-spaces" "%d" tab-width) + ;; ("--indent-with-tabs" "%s" (if indent-tabs-mode "yes" "no")) + ;; ("-xml" (memq major-mode '(nxml-mode xml-mode)))) + ;; :ok-statuses '(0 1)) (setq web-mode-enable-html-entities-fontification t web-mode-auto-close-style 1)