Giter Club home page Giter Club logo

radian-software / apheleia Goto Github PK

View Code? Open in Web Editor NEW
516.0 516.0 74.0 495 KB

๐ŸŒท Run code formatter on buffer contents without moving point, using RCS patches and dynamic programming.

License: MIT License

Emacs Lisp 77.81% Makefile 2.58% Shell 11.07% Dockerfile 0.44% Python 2.26% Haskell 0.77% C 0.36% Go 0.47% Java 0.18% TeX 0.23% Erlang 0.06% OCaml 0.17% CSS 0.30% HTML 0.55% JavaScript 0.68% SCSS 0.40% TypeScript 0.66% Rust 0.21% HCL 0.31% Dart 0.47%

apheleia's People

Contributors

adimit avatar alternateved avatar andersk avatar cxa avatar dalugm avatar danielpza avatar dpassen avatar edslocomb avatar elken avatar itshoff avatar jsmestad avatar leonidborisenko avatar leotaku avatar leungbk avatar meliache avatar michzappa avatar mohkale avatar offbyone avatar primarycanary avatar rassie avatar raxod502 avatar scop avatar semeninrussia avatar sgherdao avatar sohumb avatar stonekyx avatar strake7 avatar terlar avatar tpeacock19 avatar xfa25e avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

apheleia's Issues

Error buffers are hidden, which makes it hard to see the problem

When formatting fails, we have to:

  1. Navigate to *Messages*
  2. Search for "Failed to run ...: exit status ... (see hidden buffer apheleia-...-stderr)"
  3. Copy the " *apheleia-...-stderr" buffer name, including the the initial space
  4. Type M-S-; (switch-to-buffer " *apheleia ... stderr") RET

Should there be a less painful way?

Global mode conflicts with php-mode

emacsrc:

(require 'php-mode)
(require 'apheleia)

(add-to-list 'apheleia-formatters '(cat "cat"))
(add-to-list 'apheleia-mode-alist '(php-mode . cat))

(apheleia-global-mode +1)

(setq debug-on-message "while locally")

test.php:

Something here

Run this (you should have apheleia, php-mode installed with straight, or clone the repos elsewhere and change the paths below):

emacs -Q -L ~/.emacs.d/straight/repos/apheleia -L ~/.emacs.d/straight/repos/php-mode/lisp -l emacsrc test.php

After changing something and saving, you get:

Debugger entered--Lisp error: "Making after-save-hook buffer-local while locally ..."
  make-local-variable(after-save-hook)
  add-hook(after-save-hook apheleia--format-after-save nil local)
  (if apheleia-mode (add-hook 'after-save-hook #'apheleia--format-after-save nil 'local) (remove-hook 'after-save-hook #'apheleia--format-after-save 'local))
  (let ((last-message (current-message))) (setq apheleia-mode (if (eq arg 'toggle) (not apheleia-mode) (> (prefix-numeric-value arg) 0))) (if apheleia-mode (add-hook 'after-save-hook #'apheleia--format-after-save nil 'local) (remove-hook 'after-save-hook #'apheleia--format-after-save 'local)) (run-hooks 'apheleia-mode-hook (if apheleia-mode 'apheleia-mode-on-hook 'apheleia-mode-off-hook)) (if (called-interactively-p 'any) (progn nil (if (and (current-message) (not (equal last-message (current-message)))) nil (let ((local " in current buffer")) (message "Apheleia mode %sabled%s" (if apheleia-mode "en" "dis") local))))))
  apheleia-mode()
  (if apheleia-mode (progn (apheleia-mode -1) (apheleia-mode)) (apheleia-mode))
  (if (eq apheleia-mode-major-mode major-mode) nil (if apheleia-mode (progn (apheleia-mode -1) (apheleia-mode)) (apheleia-mode)))
  (if apheleia-mode-set-explicitly nil (if (eq apheleia-mode-major-mode major-mode) nil (if apheleia-mode (progn (apheleia-mode -1) (apheleia-mode)) (apheleia-mode))))
  (save-current-buffer (set-buffer buf) (if apheleia-mode-set-explicitly nil (if (eq apheleia-mode-major-mode major-mode) nil (if apheleia-mode (progn (apheleia-mode -1) (apheleia-mode)) (apheleia-mode)))) (setq apheleia-mode-major-mode major-mode))
  (progn (save-current-buffer (set-buffer buf) (if apheleia-mode-set-explicitly nil (if (eq apheleia-mode-major-mode major-mode) nil (if apheleia-mode (progn (apheleia-mode -1) (apheleia-mode)) (apheleia-mode)))) (setq apheleia-mode-major-mode major-mode)))
  (if (buffer-live-p buf) (progn (save-current-buffer (set-buffer buf) (if apheleia-mode-set-explicitly nil (if (eq apheleia-mode-major-mode major-mode) nil (if apheleia-mode (progn (apheleia-mode -1) (apheleia-mode)) (apheleia-mode)))) (setq apheleia-mode-major-mode major-mode))))
  (let ((buf (car --dolist-tail--))) (if (buffer-live-p buf) (progn (save-current-buffer (set-buffer buf) (if apheleia-mode-set-explicitly nil (if (eq apheleia-mode-major-mode major-mode) nil (if apheleia-mode (progn ... ...) (apheleia-mode)))) (setq apheleia-mode-major-mode major-mode)))) (setq --dolist-tail-- (cdr --dolist-tail--)))
  (while --dolist-tail-- (let ((buf (car --dolist-tail--))) (if (buffer-live-p buf) (progn (save-current-buffer (set-buffer buf) (if apheleia-mode-set-explicitly nil (if (eq apheleia-mode-major-mode major-mode) nil (if apheleia-mode ... ...))) (setq apheleia-mode-major-mode major-mode)))) (setq --dolist-tail-- (cdr --dolist-tail--))))
  (let ((--dolist-tail-- buffers)) (while --dolist-tail-- (let ((buf (car --dolist-tail--))) (if (buffer-live-p buf) (progn (save-current-buffer (set-buffer buf) (if apheleia-mode-set-explicitly nil (if ... nil ...)) (setq apheleia-mode-major-mode major-mode)))) (setq --dolist-tail-- (cdr --dolist-tail--)))))
  (let ((buffers apheleia-global-mode-buffers)) (setq apheleia-global-mode-buffers nil) (let ((--dolist-tail-- buffers)) (while --dolist-tail-- (let ((buf (car --dolist-tail--))) (if (buffer-live-p buf) (progn (save-current-buffer (set-buffer buf) (if apheleia-mode-set-explicitly nil ...) (setq apheleia-mode-major-mode major-mode)))) (setq --dolist-tail-- (cdr --dolist-tail--))))))
  apheleia-global-mode-enable-in-buffers()
  run-hooks(after-change-major-mode-hook)
  run-mode-hooks(php-mode-hook)
  php-mode()
  funcall(php-mode)
  php-mode-maybe()
  set-auto-mode-0(php-mode-maybe t)
  set-auto-mode(t)
  set-visited-file-name("/tmp/tmp.PViU3Ex3tP/test.php" t)
  write-file("/tmp/tmp.PViU3Ex3tP/test.php")
  (let ((after-save-hook (remq #'apheleia--format-after-save after-save-hook))) (write-file (or filename buffer-file-name)))
  (progn (fset #'message vnew) (let ((after-save-hook (remq #'apheleia--format-after-save after-save-hook))) (write-file (or filename buffer-file-name))))
  (unwind-protect (progn (fset #'message vnew) (let ((after-save-hook (remq #'apheleia--format-after-save after-save-hook))) (write-file (or filename buffer-file-name)))) (fset #'message old))
  (let* ((vnew #'(lambda (format &rest args) (if (equal format "Saving file %s...") nil (apply message format args)))) (old (symbol-function #'message))) (unwind-protect (progn (fset #'message vnew) (let ((after-save-hook (remq #'apheleia--format-after-save after-save-hook))) (write-file (or filename buffer-file-name)))) (fset #'message old)))
  (let ((message (symbol-function #'message))) (let* ((vnew #'(lambda (format &rest args) (if (equal format "Saving file %s...") nil (apply message format args)))) (old (symbol-function #'message))) (unwind-protect (progn (fset #'message vnew) (let ((after-save-hook (remq ... after-save-hook))) (write-file (or filename buffer-file-name)))) (fset #'message old))))
  (progn (fset #'write-region vnew) (let ((message (symbol-function #'message))) (let* ((vnew #'(lambda (format &rest args) (if ... nil ...))) (old (symbol-function #'message))) (unwind-protect (progn (fset #'message vnew) (let ((after-save-hook ...)) (write-file (or filename buffer-file-name)))) (fset #'message old)))))
  (unwind-protect (progn (fset #'write-region vnew) (let ((message (symbol-function #'message))) (let* ((vnew #'(lambda ... ...)) (old (symbol-function #'message))) (unwind-protect (progn (fset #'message vnew) (let (...) (write-file ...))) (fset #'message old))))) (fset #'write-region old))
  (let* ((vnew #'(lambda (start end filename &optional append _visit lockname mustbenew) (apheleia--write-region-silently start end filename append t lockname mustbenew write-region))) (old (symbol-function #'write-region))) (unwind-protect (progn (fset #'write-region vnew) (let ((message (symbol-function #'message))) (let* ((vnew #'...) (old (symbol-function ...))) (unwind-protect (progn (fset ... vnew) (let ... ...)) (fset #'message old))))) (fset #'write-region old)))
  (let ((write-region (symbol-function #'write-region))) (let* ((vnew #'(lambda (start end filename &optional append _visit lockname mustbenew) (apheleia--write-region-silently start end filename append t lockname mustbenew write-region))) (old (symbol-function #'write-region))) (unwind-protect (progn (fset #'write-region vnew) (let ((message (symbol-function ...))) (let* ((vnew ...) (old ...)) (unwind-protect (progn ... ...) (fset ... old))))) (fset #'write-region old))))
  apheleia--write-file-silently("/tmp/tmp.PViU3Ex3tP/test.php")
  (progn (apheleia--write-file-silently buffer-file-name) (run-hooks 'apheleia-post-format-hook))
  (condition-case err (progn (apheleia--write-file-silently buffer-file-name) (run-hooks 'apheleia-post-format-hook)) ((debug error) (message "Apheleia: %s" err) nil))
  (closure ((command "cat") apheleia-mode t) nil (condition-case err (progn (apheleia--write-file-silently buffer-file-name) (run-hooks 'apheleia-post-format-hook)) ((debug error) (message "Apheleia: %s" err) nil)))()
  funcall((closure ((command "cat") apheleia-mode t) nil (condition-case err (progn (apheleia--write-file-silently buffer-file-name) (run-hooks 'apheleia-post-format-hook)) ((debug error) (message "Apheleia: %s" err) nil))))
  (progn (funcall callback))
  (if callback (progn (funcall callback)))
  (progn (apheleia--apply-rcs-patch (current-buffer) patch-buffer) (if callback (progn (funcall callback))))
  (if (equal apheleia--buffer-hash (apheleia--buffer-hash)) (progn (apheleia--apply-rcs-patch (current-buffer) patch-buffer) (if callback (progn (funcall callback)))))
  (save-current-buffer (set-buffer cur-buffer) (if (equal apheleia--buffer-hash (apheleia--buffer-hash)) (progn (apheleia--apply-rcs-patch (current-buffer) patch-buffer) (if callback (progn (funcall callback))))))
  (closure ((formatted-buffer . #<buffer  *apheleia-cat-stdout*>) (cur-buffer . #<buffer test.php>) (callback closure ((command "cat") apheleia-mode t) nil (condition-case err (progn (apheleia--write-file-silently buffer-file-name) (run-hooks 'apheleia-post-format-hook)) ((debug error) (message "Apheleia: %s" err) nil))) (command "cat") t) (patch-buffer) (save-current-buffer (set-buffer cur-buffer) (if (equal apheleia--buffer-hash (apheleia--buffer-hash)) (progn (apheleia--apply-rcs-patch (current-buffer) patch-buffer) (if callback (progn (funcall callback)))))))(#<buffer  *apheleia-diff-stdout*>)
  funcall((closure ((formatted-buffer . #<buffer  *apheleia-cat-stdout*>) (cur-buffer . #<buffer test.php>) (callback closure ((command "cat") apheleia-mode t) nil (condition-case err (progn (apheleia--write-file-silently buffer-file-name) (run-hooks 'apheleia-post-format-hook)) ((debug error) (message "Apheleia: %s" err) nil))) (command "cat") t) (patch-buffer) (save-current-buffer (set-buffer cur-buffer) (if (equal apheleia--buffer-hash (apheleia--buffer-hash)) (progn (apheleia--apply-rcs-patch (current-buffer) patch-buffer) (if callback (progn (funcall callback))))))) #<buffer  *apheleia-diff-stdout*>)
  (progn (funcall callback stdout))
  (if callback (progn (funcall callback stdout)))
  (if (funcall (or exit-status #'(lambda (status) (= 0 status))) (process-exit-status proc)) (if callback (progn (funcall callback stdout))) (message (concat "Failed to run %s: exit status %s " "(see hidden buffer%s)") (car command) (process-exit-status proc) stderr))
  (if (process-live-p proc) nil (save-current-buffer (set-buffer stderr) (if (= 0 (buffer-size)) (progn (insert "[No output received on stderr]\n")))) (if (funcall (or exit-status #'(lambda (status) (= 0 status))) (process-exit-status proc)) (if callback (progn (funcall callback stdout))) (message (concat "Failed to run %s: exit status %s " "(see hidden buffer%s)") (car command) (process-exit-status proc) stderr)))
  (closure ((stderr . #<buffer  *apheleia-diff-stderr*>) (stdout . #<buffer  *apheleia-diff-stdout*>) (name . "diff") (exit-status closure ((new-fname) (old-fname . "/tmp/tmp.PViU3Ex3tP/test.php") (callback closure ((formatted-buffer . #<buffer  *apheleia-cat-stdout*>) (cur-buffer . #<buffer test.php>) (callback closure ... nil ...) (command "cat") t) (patch-buffer) (save-current-buffer (set-buffer cur-buffer) (if ... ...))) (new-buffer . #<buffer  *apheleia-cat-stdout*>) (old-buffer . #<buffer test.php>) t) (status) (memq status '(0 1))) (callback closure ((formatted-buffer . #<buffer  *apheleia-cat-stdout*>) (cur-buffer . #<buffer test.php>) (callback closure ((command "cat") apheleia-mode t) nil (condition-case err (progn ... ...) (... ... nil))) (command "cat") t) (patch-buffer) (save-current-buffer (set-buffer cur-buffer) (if (equal apheleia--buffer-hash (apheleia--buffer-hash)) (progn (apheleia--apply-rcs-patch ... patch-buffer) (if callback ...))))) (stdin . #<buffer  *apheleia-cat-stdout*>) (command "diff" "--rcs" "--" "/tmp/tmp.PViU3Ex3tP/test.php" "-") (--cl-rest-- :command ("diff" "--rcs" "--" "/tmp/tmp.PViU3Ex3tP/test.php" "-") :stdin #<buffer  *apheleia-cat-stdout*> :callback (closure ((formatted-buffer . #<buffer  *apheleia-cat-stdout*>) (cur-buffer . #<buffer test.php>) (callback closure (... apheleia-mode t) nil (condition-case err ... ...)) (command "cat") t) (patch-buffer) (save-current-buffer (set-buffer cur-buffer) (if (equal apheleia--buffer-hash ...) (progn ... ...)))) :exit-status (closure ((new-fname) (old-fname . "/tmp/tmp.PViU3Ex3tP/test.php") (callback closure (... ... ... ... t) (patch-buffer) (save-current-buffer ... ...)) (new-buffer . #<buffer  *apheleia-cat-stdout*>) (old-buffer . #<buffer test.php>) t) (status) (memq status '(0 1)))) t) (proc _event) (if (process-live-p proc) nil (save-current-buffer (set-buffer stderr) (if (= 0 (buffer-size)) (progn (insert "[No output received on stderr]\n")))) (if (funcall (or exit-status #'(lambda ... ...)) (process-exit-status proc)) (if callback (progn (funcall callback stdout))) (message (concat "Failed to run %s: exit status %s " "(see hidden buffer%s)") (car command) (process-exit-status proc) stderr))))(#<process aphelieia-diff> "finished\n")

This is due to apheleia let-binding after-save-hook while writing the file. Writing the file triggers auto mode, auto mode triggers php mode, php mode triggers the hook installed by apheleia global mode which should add apheleia's after save hook, and that breaks because after-save-hook is locally let bound at that point.

https://github.com/raxod502/apheleia/blob/8a1e68441ca418c2a277d7aef663790f26208dd8/apheleia.el#L351-L354

Instead of binding after-save-hook itself, should we not better bind a flag, eg, apheleia--format-after-save-in-progress, to break the infinite loop?

I will submit a PR for your consideration.

Better handling for non-file buffers

Atm apheleia has filepath and file command specifiers that're replaced with the value of the current buffers file. Quite a lot of the time I'm working with virtual files (such as by creating an org src-block for some c code) and apheliea will refuse to format that buffer (when called interactively through apheleia-format-buffer) because its been modified and it doesn't have a file-name. There should be a way to format buffers as well as files.

.pretterignore is ignored

Hi there, very happy with apheleia overall, and I apologize if this isn't really the right spot to make this request / open a discussion about this, but I've found that apheleia doesn't seem to follow along with my .prettierignore file in my project (using projectile if that's of relevance). It will adhere to the rules in my .pretterrc.js file in the project root, but happily skips over the ignore file. Is there something I am able to do to get it to adhere to that file as well?

Thanks very much, big fan of your many contributions to the Emacs ecosystem as well!

Is there a way to add a non stdin/stdout formatter?

Hi,

So I tried to get flutter format working in apheleia. The problem is, this command
directly writes to the file. AFAIK, there is no option to write to stdout.

My question is, is there a way to add a formatter that only writes directly
to the file?

[help] How can I integrate `latexindent ` into alpheleia?

I have alias for latexindent: alias latexindent="~/tools/latexindent.pl/latexindent.pl -l=~/indentconfig.yaml"

I am able to run it as:

~/tools/latexindent.pl/latexindent.pl -l=~/indentconfig.yaml myfile.tex > output.tex

But inside emacs I am having following error related to latexindent:

*apheleia-latexindent-log*

$ latexindent --logfile\=/dev/null
Can't open /dev/null at /usr/local/texlive/2021/texmf-dist/scripts/latexindent/LatexIndent/Document.pm line 165, <> line 328.
Command failed with exit code 2.
Fri Jan 14 12:41:34 2022 :: /Users/user/folder
$ latexindent --logfile\=/dev/null
Can't open /dev/null at /usr/local/texlive/2021/texmf-dist/scripts/latexindent/LatexIndent/Document.pm line 165, <> line 328.
Command failed with exit code 2.

How can I fix this error in order to run latexindent with my given config file?

Processes that fail stick around

From what I can tell, formatter processes that fail never get freed. I do not have clang-format installed so when saving a C file I the message

Failed to run clang-format: Searching for program: No such file or directory, clang-format

is echoed, which is fine. However, looking at the output of M-x list-processes it seems that the process list is getting clogged up:

Process [v]     PID     Status  Buffer                    TTY          Thread       Command
apheleia-cla... --      open     *apheleia-clang-forma... --           Main
apheleia-cla... --      open     *apheleia-clang-forma... --           Main
...
apheleia-cla... --      open     *apheleia-clang-forma... --           Main
server          --      listen  --                        --           Main         (network server on /run/user/1000/emacs/server)
server <1>      --      open    --                        --           Main         (network connection to t:/run/user/1000/emacs/server)

I do believe this is what caused my Emacs instance to start failing with "Too many open files" until killed.

For formatters I do have installed this is not an issue.

I am on commit 8e022c6.

Support formatting when committing indirect buffers

Follows from #52, but will require either #43 or the region facing part of #34 before it can be finished.

The gist of the issue is emacs lets you have indirect buffers within a buffer. Such as in org-mode you can edit a src code block and get a new buffer which can be edited and whose changes can be saved back to the original buffer. This issue wants formatting to be possible when commiting an indirect-buffer back to the original buffer.

After formatting a file in web-mode, mode is switched to mhtml-mode

I am running adamzapasnik/prettier-plugin-eex on an index.html.eex file which is opened with a major mode of web-mode. When I edit a file and hit save, the formatter runs great but I am switched into mhtml-mode (HTML+) after the formatter is run.

Is there some configuration I am missing? Happy to help debug if you can guide me on where/how to look.

Thanks for this great project btw ๐Ÿป This is a great step forward in formatters for the emacs ecosystem

Debugger entered--Lisp error: (void-variable apheleia-formatters)

When I add following lines into my init.el file:

(setf (alist-get 'isort apheleia-formatters)
      '("isort" "--stdout" "-"))
(setf (alist-get 'python-mode apheleia-mode-alist)
      '(isort black))

I am getting following error:

Debugger entered--Lisp error: (void-variable apheleia-formatters)
  (assq 'isort apheleia-formatters)
  (if (and nil (not (eq nil 'eq))) (assoc 'isort apheleia-formatters nil) (assq 'isort apheleia-formatters))
  (let* ((p (if (and nil (not (eq nil 'eq))) (assoc 'isort apheleia-formatters nil) (assq 'isort apheleia-formatters)))) (progn
(if p (setcdr p '("isort" "--stdout" "-")) (setq apheleia-formatters (cons (setq p (cons 'isort '...)) apheleia-formatters)))
'("isort" "--stdout" "-")))
  load-with-code-conversion("/home/alper/.emacs" "/home/alper/.emacs" t t)
  load("~/.emacs" noerror nomessage)
  startup--load-user-init-file(#f(compiled-function () #<bytecode 0x10f36531c68d12d2>) #f(compiled-function () #<bytecode
-0x1f3c686ddc0ca9b5>) t)
  command-line()
  normal-top-level()

How can I prevent this error?

Seems to wreck undo history

When a lot of formatting is going on I seem to lose the ability to undo back very far. This is extremely bad for UX. Probably a consequence of undo-limit and the reformatting generating large volumes of undo data (not sure why).

Formatters ran by apheleia doesn't respect per-project configuration

Thank you for making Apheleia!

I've found a little issue in the way Apheleia is using temporary file. Some formatter reads a configuration from a directory hierarchy until a file is found (e.g. prettier reads .editorconfig, and black reads pyproject.toml). Right now Apheleia uses make-temp-file which creates a temporary file in /tmp, which cause a per-project configuration to not get read during formatting.

Steps to reproduce

  1. Create an .editorconfig file with the following content:

    root = true
    
    [*]
    indent_size = 4
    
  2. Create test.js with the following content:

    function foo() {
    return true;
    }
    
  3. Format the file with npx prettier test.js

  4. Format the file with Apheleia

  5. Compare results between (3) and (4)

Expected behavior

Apheleia-run formatted file becomes (4 spaces):

function foo() {
    return true;
}

Actual behavior

Apheleia-run formatted file becomes (2 spaces):

function foo() {
  return true;
}

Fails for buffers greater than 2^16 characters

Unfortunately, we get errors

Failed to run prettier: process aphelieia-prettier no longer connected to pipe; closed it
Failed to run prettier: exit status 256 (see hidden buffer *apheleia-prettier-stderr*)

when a buffer's size exceeds 65,536 characters. I am not aware of any workaround other than disabling Apheleia. Note that the error is unrelated to any particular code formatter, since it reproduces with cat.

Migrate back to CircleCI

Semaphore CI turns out to be unsuitable for open-source (for example, logs are private and you can't build pull requests without leaking any secrets that may be used for the project). Travis CI is likely dying. The best option is CircleCI in my opinion, so I'd like to import my configuration for that from Radian.

Use replace-buffer-contents?

Sorry, this is just a question, I'm not reporting a bug or anything.

Emacs 26 and later come with the function replace-buffer-contents which can be used to efficiently replace the contents of the current buffer by the contests of a different buffer, trying to make minimal edits and preserving marks and the point as much as possible. Underneath it uses code from https://github.com/coreutils/gnulib/blob/master/lib/diffseq.h which is based on well knows algorithms.

Is there a reason why this function is not used, instead of writing a new diff implementation or working with RCS patches? It seems much simpler to use replace-buffer-contents.

I use it daily for automatically formatting OCaml code with ocamlformat. I don't remember it ever moved my point where I didn't expect it (for example, I never saw something like issue #2 ) and I never noticed any slowness.

You can see example use here
https://github.com/ocaml-ppx/ocamlformat/blob/02ea48f0d09a48fb2a25fdba75a8bf20872dadb5/emacs/ocamlformat.el#L211

Point is sometimes moved small distances

Example:

  if (node.type === "CallExpression" && (node.callee.type === "Import" || (node.callee.type === "Identifier" && node.callee.name === "require"))) {
    //@
  }

with point represented by @ is reformatted by Prettier to:

  if (
    node.type === "CallExpression" &&
    (node.callee.type === "Import" ||
      (node.callee.type === "Identifier" && node.callee.name === "require"))
  ) {@
    //
  }

instead of:

  if (
    node.type === "CallExpression" &&
    (node.callee.type === "Import" ||
      (node.callee.type === "Identifier" && node.callee.name === "require"))
  ) {
    //@
  }

I suspect that this problem could be solved by tweaking the weights used in the dynamic programming algorithm.

apheleia messes with `*jka-compr-wr-temp*` buffer somehow

I noticed that after editing a TSX file in Emacs and saving, a buffer called *jka-compr-wr-temp* is created and is linked to a file located in undo-fu-session-directory and when I try and close Emacs, naturally the editor tells me if I want to save that buffer/file before quitting which is annoying:

Save file /home/jorge/.emacs.d/.local/cache/undo-fu-session/60bb5b95f70b5c8292b03b6db987f789ce8c7c76.zst (buffer  *jka-compr-wr-temp*)? (y, n, !, ., q, C-r, C-f, d or C-h)

I know this is somehow related to apheleia because if I disable it in my configuration and restart Emacs, try to reproduce and then check the buffer list, the buffer *jka-compr-wr-temp* is not linked to any file:

image

this happens with the latest commit f865c16

My config is based on Doom Emacs, but Apheleia is not provided by Doom (I added it myself)

RFC: Support formatting only changed regions

What I have in mind is using something like what highlight-changes-mode does on save to track the modified lines. We could then use the current RCS patch strategy to match diff hunks to modified regions (essentially if the diff is contained within the modified region plus some configurable context value) and only apply these hunks.

I am happy to develop this but I want to first here this would be of interest to people. Also I am relatively new to elisp so it might take some time and I might need some guidance navigating the existing source.

Support formatters that output a diff

Hi! Thank you for writing Apheleia!

I was integrating Apheleia with Darker yesterday, a Python reformatter. Darker is just Black, but with the key difference that Darker only reformats the portions of the file you've changed in your local Git repository. This is useful for me while I'm working in a legacy code base that is mostly unformatted today, but which aspires to eventually format everything with Black.

Darker really wants to be given the original input file, in place (so it can look at git diff I assume), and Apheleia can handle that just fine. However, Darker's only output options are:

  1. Update the file in place
  2. Output a diff

1 is not acceptable while you're editing the file, so we're left with 2.

Of course, a third option would be to modify Darker to support writing output to stdout or another file rather than updating the input file. However, I suspect there are other formatters that want to output a diff.

Since Apheleia is conveniently already operating on a diff, would you be interesting in adding support for formatters that output a diff?

I've actually hacked this up alreadyโ€”emphasis on "hacked"โ€”and it seems to work fine in my very limited initial testing. I used el-patch (๐Ÿ”ฅ) on apheleia-format-buffer to make a case where, if the first element of the formatter's command is rcs, the call to diff --rcs is simply skipped and we go straight to apheleia--apply-rcs-patch. Of course, Darker can only output unified diff format, so I wrote a little Python script to transform unified diff to RCS diff. I could port that unified โ†’ RCS diff format to Elisp easily enough.

I'd be happy to clean that up and submit a PR, but I won't bore you with it you're not interested.

Also, before I did rip off a PR, I wanted to check in to ask how you'd like the configuration for formatters to work? As I said above, I'm currently doing something like this in my hacky POC:

(defcustom apheleia-formatters
  '((darker . (rcs "darker" "--diff" filepath))
    ...) ...)

I think that's kind of ugly. It also looks too much like the existing npx semi-special value for my tastes.

Instead, my first choice would be to turn the values of the apheleia-formatters alist into alists of parameters, something like:

(defcustom apheleia-formatters
  '((black       . ((:command . ("black" "-"))))
    (brittany    . ((:command . ("brittany" file))))
    (darker      . ((:command . ("darker" "--diff" filepath))
                    (:output-format . unified-diff)))
    (prettier    . ((:command . (npx "prettier" "--stdin-filepath" filepath))))
    (gofmt       . ((:command . ("gofmt"))))
    (ocamlformat . ((:command . ("ocamlformat" file))))
    (terraform   . ((:command . ("terraform" "fmt" "-")))))
  ...)

This would allow for additional configuration parameters for formatters in the future, too. However (1) I don't know how you feel about breaking compatibility, and (2) I don't know if you like the aesthetics of that. Better suggestions very welcome!

Integrating `rustfmt`

Background

I wanted to use Apheleia for Rust code, too, as rustic-mode's own formatter is rather buggy and nowhere near as good as Apheleia's handling.

However, rustfmt behaves in a way that confuses Apheleia. The existing PR #24 is not going to integrate it successfully. There are several problems:

  • rustfmt defaults to checking all dependent files, not just the file it is passed. This means that its output with the --emit stdout option contains multiple files. To output only one file, there's an unstable --skip-children flag, which requires us to also allow such flags.
  • (in my eyes at least) --skip-children has a bug: it still outputs the file name in the first line, and two additional newlines.

Questions to Apheleia

  • Is there a provision to make changes to the output of formatting commands? I'd like to drop the first two lines without shell magic.

Of course, I think that the underlying issue should be patched out of rust-analyzer. I'll see if I can do that.

Workaround

In the meantime, I've created a workaround. First, you need to register rustfmt with Apheleia:

    (add-to-list 'apheleia-formatters
                 '(rustfmt  . ("rustfmt-helper" file)))
    (add-to-list 'apheleia-mode-alist
                 '(rustic-mode . rustfmt)

I've made a shell script called rustfmt-helper to work around the above issues:

#!/bin/bash

rustfmt --emit stdout --unstable-features --skip-children $1 | tail -n +3
exit ${PIPESTATUS[0]}

Put this on your $PATH and chmod +x it.

Formatting a buffer changes the mode

Using the format on save, or manual formatting of the file causes the mode to revert to whatever is the default. i.e. if you open a buffer and set it to web-mode, after the formatting is done it returns to fundamental mode.

Also, it has a bug relating to php-mode. If php-mode is active, Apheleia attempts to format it using clang-formatter, which then obviously causes an error, in my case, it says clang-formatter cannot be found.

Impose maximum length for dynamic programming algorithm

With point inside a sufficiently large diff region, Apheleia can cause Emacs to lock up. We should impose a maximum diff region size to use the smart algorithm on, and fall back to a simpler heuristic (preserve offset into the region?) otherwise.

apheleia-hide-old-log-entries is deleted

apheleia-hide-old-log-entries is deleted, apheleia-formatter-exited-hook is added, you mean apheleia-hide-old-log-entries is replaced by apheleia-formatter-exited-hook?

I don't understand how to use apheleia-formatter-exited-hook.

Could you please give an example how to replace apheleia-hide-old-log-entries?

Trying to add a new formatter for eslint_d

I am trying to add a new formatter for eslint_d and looking at the examples for black and prettier I can't understand why it doesn't work.

I have done some step through debugging and see that it creates the process properly and sends the buffer on stdin, only thing I noted there was that the buffer seemed to include faces and font-lock styling etc.

Not sure if this is causing the issue. After it is executed I receive nothing, no output to indicate error or success. I guess if it succeded it would run the diff and apply function next.

The command I have configured is:

(cl-pushnew '(eslint . (npx "eslint_d" "--fix-to-stdout" "--stdin" "--stdin-filename" file)) apheleia-formatters :test #'equal)

Don't change default-directory?

What's the reason that default-directory is set to the project root instead of left unchanged as the directory that the file is in? If the project contains different formatter configs in different subfolders, the wrong one (or none) will get used.

In my init.el, I added:

(defun apheleia--project-root () default-directory)

But I'm wondering whether this functionality is not better removed altogether? Most if not all formatters should automatically find the right config in parent folders.

Automated formatter testing and CI

Do you want me to set up a testing framework for supported formatters? I'm imagining a system similar to flycheck's where a Docker container installs all checkers and runs a test suite against them.

We could discuss details if you think this is something worth working on.

Apheleia conflicts with undo-tree-auto-save-history

When undo-tree-auto-save-history set to t and apheleia-global-mode is on, apheleia and undo-tree will go into a save buffer race causing the buffer to never be saved.


Steps to reproduce:

  1. Enable global-undo-tree-mode and apheleia-global-mode
  2. Set undo-tree-auto-save-history to t
  3. Visit a file with apheleia autoformatting (e.g. test.py)
  4. Type something and save a file (C-x C-s)

Expected behavior:

The is saved and formatted

Actual behavior:

The file is formatted, but become modified once again and cannot be saved unless I disable apheleia or undo-tree (or C-u 0 C-x C-s in case radian's hook)


Reduced init.el:

(setq user-init-file (or load-file-name (buffer-file-name)))
(setq user-emacs-directory (file-name-directory user-init-file))

(defvar bootstrap-version)
(let ((bootstrap-file
       (expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory))
      (bootstrap-version 5))
  (unless (file-exists-p bootstrap-file)
    (with-current-buffer
        (url-retrieve-synchronously
         "https://raw.githubusercontent.com/raxod502/straight.el/develop/install.el"
         'silent 'inhibit-cookies)
      (goto-char (point-max))
      (eval-print-last-sexp)))
  (load bootstrap-file nil 'nomessage))

(straight-use-package '(apheleia :host github :repo "raxod502/apheleia"))
(straight-use-package 'undo-tree)

(require 'apheleia)
(require 'undo-tree)

(apheleia-global-mode +1)
(global-undo-tree-mode +1)

(setq undo-tree-auto-save-history t)

(I run this reduced init.el with: /usr/bin/emacs -nw -q --load "/path/to/init.el")


Not sure whose bug this might be, please let me know if it's more appropriate to report to undo-tree instead (and sorry for the noise in such case). As always, HUGE thank you for straight.el, apheleia and radian โค๏ธ

Functional formatters (for use with lsp-mode/eglot)

I've noticed apheleia atm doesn't support functional formatters, such as pp-buffer.

I'd like to add support for using language servers (eglot, or lsp-mode) for formatting a buffer on save through apheleia so I'd like apheleia to support functional formatters.

I'm interested in what your thoughts on this would be. Implementation wise it might be more convenient to clone the current buffer, or copy it and then run the formatter on the cloned buffer and continue with the changes we were already doing. This would also allow chaining with the existing formatter system. So we could say format with lsp-mode, then run python-isort, etc.

Interface wise I was thinking we just allow the cdr of an entry in apheleia-formatters to be a symbol, and if it is then its a function to call. Luckily atm their all lists, even gofmt.

TRAMP support

As noted in the README, TRAMP is currently unsupported. I am considering adding support since I use TRAMP extensively. I'm interested in your thoughts on this before I potentially embark on creating a PR. ๐Ÿ™‚

Run formatter under project root directory

I am able to use this package mostly successfully with my Elixir code with the following config:

  (add-to-list 'apheleia-formatters '(mix . ("mix" "format" "-")))
  (add-to-list 'apheleia-mode-alist '(elixir-mode . mix))

There is one issue though. When I format a file under a project that contains a .formatter.exs config, the formatter configuration is not respected. In the documentation of mix format it states that

The formatter will read a .formatter.exs in the current directory for formatter configuration.

So it seems like it was caused by that mix format - is not executed in the project directory but the directory of the current buffer file.

Is there a way to specify apheleia to run the formatter under the project directory? The project root directory can be found by either (project-root (project-current)) or (projectile-project-root).

Wrong type argument error on save

I've been using apheleia with prettier for a while now and recently started seeing this error on save, and I'm not sure exactly what's changed (I did update apheleia a few weeks ago and maybe I just started noticing now).

Debugger entered--Lisp error: (wrong-type-argument bufferp nil)
  buffer-local-value(buffer-file-coding-system nil)
  apheleia--make-process(:command ("/Users/[...]/node_module..." "--print-width" "88" "/Users/[...]/resources/j...") :stdin nil :callback #f(compiled-function (stdout) #<bytecode 0x5da82447>) :ensure #f(compiled-function () #<bytecode 0x5da8245f>))
  apheleia--run-formatters(((npx "/Users/[...]/node_module..." "--print-width" "88" file)) #<buffer ExpenseForm.js> #f(compiled-function (formatted-buffer) #<bytecode 0x5da823c9>))
  apheleia-format-buffer(((npx "/Users/[...]/node_module..." "--print-width" "88" file)) #f(compiled-function () #<bytecode 0x5d56a885>))
  apheleia--format-after-save()
  run-hooks(after-save-hook)
  basic-save-buffer(t)
  save-buffer(1)
  funcall-interactively(save-buffer 1)
  call-interactively(save-buffer nil nil)
  command-execute(save-buffer)

Here's my apheleia config:

(defun my-apheleia-mode ()
  (interactive)
  (if (and (not apheleia-mode)
           (buffer-file-name)
           (locate-dominating-file (buffer-file-name) ".apheleia"))
    (apheleia-mode +1)))

(use-package apheleia
  :straight (apheleia
             :host github
             :repo "raxod502/apheleia")
  :diminish "aph"
  :commands (apheleia-mode)
  :hook ((css-mode js-mode php-mode python-mode rjsx-mode web-mode) . my-apheleia-mode)
  :custom
  (apheleia-formatters
   `((black . ("black" "-l" ,(number-to-string my-python-line-length) "-"))
     (prettier . (npx "prettier"
                      "--print-width" ,(number-to-string my-python-line-length)
                      file))
     (prettier-html . (npx "prettier"
                           "--parser" "html"
                           "--print-width" ,(number-to-string my-python-line-length)
                           file))))
  :config
  (add-to-list 'apheleia-mode-alist '(php-mode . prettier))
  (add-to-list 'apheleia-mode-alist '(rjsx-mode . prettier))
  (add-to-list 'apheleia-mode-alist '(web-mode . prettier-html)))

apheleia is actually working when this happens, the buffer does still get formatted properly.

After post-save auto-formatting, buffer visits a Flycheck temp file

(let ((bootstrap-file (concat user-emacs-directory "straight/repos/straight.el/bootstrap.el"))
      (bootstrap-version 5))
  (unless (file-exists-p bootstrap-file)
    (with-current-buffer
        (url-retrieve-synchronously
         "https://raw.githubusercontent.com/raxod502/straight.el/develop/install.el"
         'silent 'inhibit-cookies)
      (goto-char (point-max))
      (eval-print-last-sexp)))
  (load bootstrap-file nil 'nomessage))

(straight-use-package 'use-package)
(setq straight-use-package-by-default t)

(setq debug-on-error t)

(use-package apheleia
  :straight (:repo "raxod502/apheleia" :host github)
  :demand t
  :config
  (apheleia-global-mode 1))

(use-package flycheck
  :demand t
  :config
  (global-flycheck-mode 1))

Repro:

  1. emacs -q -l above-init.el. The Python-formatter black should also be installed.
  2. C-x C-f hello.py
  3. Write
def hi():


    return 42

and then save the file, allowing Apheleia to clean up the extra whitespace.

  1. M-: (buffer-file-name). The output is /tmp/flycheckpqHf80/hello.py, instead of the expected /home/alice/hello.py.

Funny enough, M-x apheleia-format-buffer on the above whitespace-heavy file does not permanently visit the temp file in this manner. However, manually saving the formatted buffer (which does not trigger an auto-format of the file, since it was just formatted manually) does cause the temp file to be visited.

I can reproduce this on both Emacs 27 (on macOS, installed via Nix) and Emacs 28 (on NixOS).

How can I change line length settings for the black formatter?

Thank you for the awesome package. This is the only formatted that doesn't open all my folded code when I run it in the buffer.

I like to use blackwith the traditional line length. With blacken, I do this: (setq blacken-line-length 79). I tried editing apheleia-formatters, but after many attempts I wasn't able to get the syntax right.

Mixed line ending after formatting in windows + msys

Hi, first of all thanks for your great work. I have been using this package for a while now. Recently i have also been using it on windows machine. On windows i have msys64 setup for various unix/linux tools, that includes the diff tool as well. so apheleia is using diff from msys setup. The code's line ending is CRLF, but when i edit some and format code, it inserts ^M as line ending. its probably the diff tool's doing. i took screenshots from various buffers to show you here, hope you can find out the problem,

main code after formatting -

main-code

prettier-stdout buffer -

prettier-stdout

diff-stdout buffer -
diff-stdout

sorry i can not show you the full code. my emacs config is here - emacs configuration. i am using windows 10, emacs 28, msys64, diff tool was install via base-devel package of msys64.

Allow running multiple formatters in sequence

First, thank you for this great package.

Unless I'm mistaken, there is currently no way to configure apheleia-mode to run multiple formatters on save. When working with Python files, I often run isort followed by black. It would be nice to automate this workflow using this package. Maybe the apheleia-mode-alist could be changed to, for instance:

((python-mode . (isort black))
 (js-mode . prettier))

and the package would run the list entries sequentially.

Does apheleia support formatting of narrowed buffer?

Hi, thank you for this package.
I use python black formatter and one day I wanted to edit narrowed region.

class A:
    pass

|class B:
    pass|

class C:
    pass

Select the code inside | call M-x narrow-to-region - you will see only:

class B:
    pass

After editing and saving the narrowed buffer - the code disappears.
Call M-x widen and the content of the buffer will be

class A:
    pass



class C:
    pass

Emacs 27.1, apheleia-20210723.516

Infinite symlink lookup recursion when using apheleia--format-after-save

If I have configured:
(setq vc-follow-symlinks t) and are visiting a file under version control and then edit this file and then save with apheleia-mode enabled it enters some kind of infinite symlink follow recursion.

I will see output like this (and it will continue until I press C-g, at which point I get thrown out from the previous buffer which was formatted successfully):

Followed link to /dev/shm/buffer-content-ui2Z3V
Followed link to /dev/shm/buffer-content-BIEWUp
Followed link to /dev/shm/buffer-content-ORmfZq
Followed link to /dev/shm/buffer-content-MSk9TR
Followed link to /dev/shm/buffer-content-MG1VLT
Followed link to /dev/shm/buffer-content-Hhjuxl
Followed link to /dev/shm/buffer-content-gdLxGo
Followed link to /dev/shm/buffer-content-QsdyEP
Followed link to /dev/shm/buffer-content-sFHU8R
Followed link to /dev/shm/buffer-content-vJWO3i
Followed link to /dev/shm/buffer-content-CPrtWo
Followed link to /dev/shm/buffer-content-yJBDBX
Followed link to /dev/shm/buffer-content-4nU2f1
Followed link to /dev/shm/buffer-content-jTkcmA
Followed link to /dev/shm/buffer-content-Mfbp9E
Followed link to /dev/shm/buffer-content-utF0ud
Followed link to /dev/shm/buffer-content-uCuUYi
Followed link to /dev/shm/buffer-content-RyZ7kO
Followed link to /dev/shm/buffer-content-yhqNTT
Followed link to /dev/shm/buffer-content-CBcCwo
Followed link to /dev/shm/buffer-content-itulmu
Followed link to /dev/shm/buffer-content-R4XDZY
Followed link to /dev/shm/buffer-content-kDbsj5
Followed link to /dev/shm/buffer-content-Cx1QPB
Followed link to /dev/shm/buffer-content-MxSF8J
Followed link to /dev/shm/buffer-content-H3tsTg
Followed link to /dev/shm/buffer-content-MGU45o
Followed link to /dev/shm/buffer-content-AiPN6U
Followed link to /dev/shm/buffer-content-idLNM2
Followed link to /dev/shm/buffer-content-9vFLOy
Followed link to /dev/shm/buffer-content-knWU5C
Followed link to /dev/shm/buffer-content-07XDc6
Followed link to /dev/shm/buffer-content-stIYId

If I don't enable vc-follow-symlinks it won't happen, but then instead I will end up in a buffer with the correct file-name, but which is not actually the file in the project and it will lose the VC status information.

I am not sure this error stems from apheleia, but it might be a regression introduced in the latest Emacs HEAD, I am currently running:
GNU Emacs 28.0.50 (build 1, x86_64-pc-linux-gnu, GTK+ Version 3.24.13, cairo version 1.16.0)
on commit https://github.com/emacs-mirror/emacs/tree/ffb89ed5f07491e33fc79d8b4be49d9deba2ad4a

It does not seem to happen when I trigger apheleia-format-buffer manually.

Any ideas what can be wrong and if this is something that needs to be reported to Emacs itself, which I suspect might be the case.

Thank you for your assistance.

Race condition when saving multiple buffers in a short time

Currently if you save a buffer and then switch to a different buffer before Apheleia can finish reformatting, the contents of the new buffer can get overwritten. We need to save the buffer that was current when apheleia-format-buffer was called, and be sure to update that buffer.

How can I apply diminish-mode to aphelei-mode?

I am using diminish for major modes. But I was not able to apply it for apheleia-mode.

(apheleia-global-mode +1)
(with-eval-after-load 'apheleia
(setf (alist-get 'isort apheleia-formatters)
      '("isort" "--stdout" "-"))
(setf (alist-get 'python-mode apheleia-mode-alist)
      '(isort black))
  )
(diminish 'apheleia-mode)
(diminish 'apheleia-global-mode)

I keep see following Apheleia at bottom line, how can I suppress it:

Driver.py ~/program/user Git:dev (home) [Py] Top (15, 0) Apheleia------------

Confusion about use of log buffer

I received the error Failed to run rustfmt: exit status 1 (see hidden buffer *apheleia-rustfmt-log*).

But I cannot find *apheleia-rustfmt-log* buffer.

BTW: rustfmt binary is already installed

Be less obtrusive in large repos with different styles

Working on a repository with a different style guide results in a lot of noise in the commits as the entire contents of a buffer are being reformatted. What would be the best way to circumvent that?

I would like some functionality similar to ws-butler that would only change lines that have actually been touched by the user.

The best solution would be to autodetect the style and format it accordingly, although that would have to be done on the formatters side and but would also defeat the purpose of it to some extent.

My current solution would be to specify the project or files in that project manually where the formatting should be disabled, although I would prefer an automatic way to do that.

Cannot save many files at once because process buffers are tied to formatters

Okay, so I've been using apheleia pretty heavily over the past few days/weeks and more than once I've run into issues with formatters failing or even worse my buffers being overwritten with the contents of both that buffer and another buffer.

Reproduction Instructions

  1. Create a temporary directory foo.
  2. In foo create 3 files: a.py, b.py, c.py.
  3. Open all of them in emacs and enable apheleia-mode.
  4. Insert into each of them the following src-code block.
  5. Save all of them at once (example using :wa with evil-mode).
import csv

# need to define cmp function in Python 3
def cmp(a, b):
    return (a > b) - (a < b)

# write stocks data as comma-separated values
with open('stocks.csv', 'w', newline='') as stocksFileW:
    writer = csv.writer(stocksFileW)
    writer.writerows([
        ['GOOG', 'Google, Inc.', 505.24, 0.47, 0.09],
        ['YHOO', 'Yahoo! Inc.', 27.38, 0.33, 1.22],
        ['CNET', 'CNET Networks, Inc.', 8.62, -0.13, -1.4901]
    ])

# read stocks data, print status messages
with open('stocks.csv', 'r') as stocksFile:
    stocks = csv.reader(stocksFile)

    status_labels = {-1: 'down', 0: 'unchanged', 1: 'up'}
    for ticker, name, price, change, pct in stocks:
        status = status_labels[cmp(float(change), 0.0)]
        print ('%s is %s (%.2f)' % (name, status, float(pct)))

Chances are at least one of these attempts to format the buffer on save will fail. In my case they both failed Failed to run isort: exit status 2 (see hidden buffer *apheleia-isort-stderr*) [2 times] and the stderr buffer for isort contains:

Traceback (most recent call last):
  File "/home/mohkale/.local/bin/isort", line 5, in <module>
    from isort.main import main
  File "/home/mohkale/.local/lib/python3.10/site-packages/isort/main.py", line 2, in <module>
    import argparse
  File "/usr/lib/python3.10/argparse.py", line 92, in <module>
    from gettext import gettext as _, ngettext
  File "<frozen importlib._bootstrap>", line 1027, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1006, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 674, in _load_unlocked
  File "<frozen importlib._bootstrap>", line 577, in module_from_spec
  File "<frozen importlib._bootstrap>", line 541, in _init_module_attrs
KeyboardInterrupt

Note: this example also shows how the output of two buffers could end up mixed. I run a formatter, it starts formatting and outputting, then it's interrupted and another formatter is begun outputting to the same stdout buffer, then apheleia concatenates the partial output from the first and the full output of the second into the second buffer.

Analysis

I think this is pretty clearly because apheleia uses the same buffer-names for every formatters stderr and stdout buffer & we explicitly kill any process in that buffer when running the formatter.

I like that apheleia keeps the stderr and stdout buffer around after the process finishes, it helps with debugging, but the current approach doesn't scale well to many files or workflows that touch many files before saving them. What I'd suggest is 2 things:

  1. Don't keep the stdout buffer around. I don't really see the point of this at least from a user POV. Better to use it to format the current buffer and then delete it.
  2. Create separate buffers for stdout and stderr whenever you run a formatter and then insert any stderr output to a shared stderr buffer for that formatter.

So we still keep the *apheleia-isort-stderr* buffer, but instead of connecting it directly to a process, we give each process a unique stderr buffer and then append the output to this buffer at the end. If there's a worry it's getting too large in the background we can even only add to it if the formatter fails.

Conflicts with lsp-mode diagnostics

Currently, if Apheleia reformats a buffer, then any Flycheck diagnostics provided by lsp-mode disappear until the next edit that causes them to be recomputed. I am not sure which package is at fault for this interaction.

ktfmt and other formatters which output nothing to stdout on success clobber buffer

I don't particularly know why ktfmt behaves in this way, but if stdin is already formatted properly, nothing will be printed to stdout, causing apheleia to delete the contents of the entire buffer. I see three ways to approach fixing this:

  • Change apheleia to be able to handle cases like this. (ideal)
  • Add an option to ktfmt to always print something to stdout. (semi-ideal)
  • Write a wrapper script around ktfmt which always prints something. (not very ideal at all)

I'm going to be raising an issue on their tracker as well, since I feel like this behavior is unexpected and should probably have an option to disable it. That said, I do think there are probably other formatters that work like this somewhere out there and that apheleia should be able to handle them ideally. I wanted to raise this issue before implementing, since I wanted your feedback before creating a patch.

That all said: maybe we could just not touch the buffer at all if stdout is empty, and the return code is 0? I can't imagine a scenario in which an empty/entirely whitespace buffer would need special formatting at all (unless there's someone silly enough to have written a formatter for the whitespace programming language.

apheleia--write-file-silently ignores visit in write-region

This is a bit of a weird issue, I admit, but after a long debugging session of my Radian config I finally tracked it down to apheleia.

I am using undo-fu-session which has a before-save-hook to save the undo data. In this hook, there's code like this:

(with-auto-compression-mode
        (with-temp-buffer
           ...
          (write-region nil nil undo-file nil 0)
          t))

where the function write-region is called on a temporary buffer. Note that 0 is passed to visit of write-region. The buffer content should be written to a file but the temporary buffer should not visit the file. I was surprised to see that the buffer was in fact visiting the file in undo-file causing annoying dialogue boxes of "buffer changed.. save anyways?" everytime auto-save runs and if I select Yes to save, the undo file is overwritten!

I found out that the reason is apheleia--write-file-silently where the function write-region is bound to a lambda function that ignores the visit argument and instead always passes t in its place causing the file to be always visited (even if the buffer is temporary).

I can see that in the commit 8112041 visit is always set to t instead of 0 to fix an issue with undo-tree, but I am wondering why the visit argument is ignored at all? It seems to me that ignoring an argument of a such a basic function is a radical measure.

Add to MELPA

It would be nice if this were hosted on MELPA.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.