jscheid / prettier.el

Prettier code formatting for Emacs.
GNU General Public License v3.0
163 stars 11 forks source link

maybe implement `prettier-diff`? #99

Closed greg-minshall closed 2 years ago

greg-minshall commented 2 years ago

Is your feature request related to a problem? Please describe.

i would like to train my brain/fingers to type "prettier-conformant" code. (one of the reasons for my #98.)

Describe the solution you'd like

my suggestion is a way to bring up a diff-style pair of buffers, where one is my un-conformant code, and the other, a diff listing (-c or -u, if possible -- magit is great for this). then, allow the user to n/p their way through the code/diffs, and r, or something, to pull in the new formatting.

Describe alternatives you've considered

i have some shell code that does the diff part in the shell. it's useful, but not as convenient as one might like.

# what is the diff between current file contents and post-prettified
function prettier-diff {
    local fname=${!#}                 # get the filename
    local -a opts
    for i in $(seq 1 $(($#-1))); do
        opts+=( "${!i}" )
    done

    diff "${opts[@]}" "${fname}" <(npx prettier --stdin-filepath "$(basename "${fname}")" < "${fname}")
}

i also have some elisp code that uses vdiff, and assumes a locally installed prettier executable. it works nicely, is a hack, and doesn't seem to allow what i think magit allows: highlighting the individual characters within a hunk that have changed. since it's long, i'll put it below.

Additional context here is my hacky elisp code. as the comments indicate, part of the complexity is dealing with Org Src edit (or, presumably, other non-file-specific) buffers.

;; somewhat based on prettier.el, give a diff of the current buffer, and
;; how prettier would prettify it
;;
;; to run the command line version of prettier, and pass it something
;; via stdin, we need to either tell it the name of the file coming
;; from stdin or (if we are in an Org Src buffer, for example), the
;; name of a parser.  here is a dictionary mapping languages (for some
;; loose definition of "language") and parsers that we might specify.
;; i have no idea what some of these might be.  notice that some keys
;; are duplicated.  in an ideal world, these would be customizable,
;; and you could choose which you liked, or stuff in your own, for a
;; given "language".  https://prettier.io/docs/en/options.html#parser
(setq prettied-langs-to-parsers
      '(
        (js . babel)
        (js . flow)
        (js . babel-flow)
        (typescript . babel-ts)
        (typescript . typescript)
        (js . espree)
        (js . meriyah)
        (css . css)
        (less . less)
        (scss . scss)
        (json . json)
        (json . json5)
        (json . json-stringify)
        (graphql . graphql)
        (markdown . markdown)
        (mdx . mdx)
        (vue . vue)
        (yaml . yaml)
        (glimmer . glimmer)
        (html . html)
        (angluar . angular)
        (lwc . lwc)))
;; then, here is a table of correspondances between major modes (which
;; is all we have in an Org Src edit buffer) and languages.  double
;; lookup.
(setq prettied-modes-to-langs
      '((typescript-mode . typescript)
        (js2-mode . js )))

(defun prettied-parser (&optional buf)
    "return the parser (if any) corresponding to the
 major mode in BUF (default: `(current-buffer)`)"
  (with-current-buffer (or buf (current-buffer))
    (let ((lang (assoc major-mode prettied-modes-to-langs)))
      (if (not lang)
          (error (format "\"%s\": unknown language for this mode" major-mode))
        (let ((parser (assoc (cdr lang) prettied-langs-to-parsers)))
          (if (not parser)
              (error (format "\"%s\": unknown parser for language \"%s\" for mode \"%s\"" major-mode (cdr lang)))
              (cdr parser)))))))

(defun prettied-diff ()
  "Show the difference between the current buffer, or the part it 
is narrowed to, and its \"prettified\" version.

XXX NOT YET -- get from prettier-prettify: With prefix, ask for the parser to use"
  (interactive "*")
  (let* ((fname (buffer-file-name))
         (mode major-mode)
         ;; XXX search: should we look for (and prefer?) "npx prettier"?
         (command "prettier")
         (args (if fname
                   (list "--stdin-filepath" fname)
                 (list "--parser" (symbol-name (prettied-parser)))))
         (stdout-buffer (generate-new-buffer "prettied-output"))
         (stderr-file (make-temp-file "emacs-prettied-errors"))
         ;; capture exit status
         (exit-status (apply 'call-process-region
                             nil nil command nil
                             (list stdout-buffer stderr-file)
                             nil args))
         (message "")                   ; used to hold error string(s)
         (message-separator ""))                ; 
    ;; check exit status of process
    (unless (zerop exit-status)
      (setq message
            (concat
             message
             message-separator
             ;; XXX give the arguments of the failing command
             (format "bad exit status \"%s\" from \"%s\"" exit-status command)))
      (setq message-separator "\n"))

    ;; deal with any errors on stderr
    (with-temp-buffer
          (goto-char (point-min))
          (insert-file stderr-file)
          (when (not (zerop (buffer-size)))       ; oops
            (setq message
                  (concat
                   message
                   message-separator
                   (format "error from prettier: %s" (buffer-string))))
            (setq message-separator "\n"))
          (delete-file stderr-file))
    (if (not (zerop (length message)))
        (progn
          (message-box message)
          nil)
      ;; put stdout-buffer in language-specific mode
      (with-current-buffer stdout-buffer
        ;; XXX using non-'exported' function `auto-mode-0`?
        (set-auto-mode-0 mode))
      (if t
          (vdiff-buffers (current-buffer)
                         stdout-buffer
                         nil
                         ;; delete stdout-buffer
                         (lambda (buf1 buf2) (kill-buffer buf2)))
        (diff-buffers stdout-buffer (current-buffer))
        )
      t)))
jscheid commented 2 years ago

Pervasive automatic code formatting has been one of the greatest quality of life improvements for me as a software developer. Now that I'm used to it I sorely miss it when I can't use it, such as when writing SQL.

It's liberating not having to spend a moment's thought on the boring minutiae of syntax. Sure, your code changing shape on save takes some getting used to, but it's a minuscule price to pay compared to the benefits. I urge you to give it a try again.

At any rate, your example code doesn't seem to depend on this package (it's invoking Prettier proper as an external one-off process) so I don't see a reason for it to be added here anyway. Good luck!

greg-minshall commented 2 years ago

hi, Julian, thanks for the reply. yes, probably if i would get used to the shape-shifting. that may happen. the rough code i showed above is "lazy" and uses whatever prettier is around. were you to integrate it into prettier.el, then i assumed you'd make use of your own internal prettier.

anyway, maybe this is my "chance" to write/publish my own micro-package. (but, is it an 'lpu'? :)

cheers.

jscheid commented 2 years ago

Have you tried binding prettier-prettify to a key and using that to reformat on demand?

greg-minshall commented 2 years ago

no, i haven't. maybe it's stone-age-related short term memory problems -- but, i want to see the diff bewteen what i typed and what prettier wants, on a case-by-case basis. vdiff gives me the comparision (and that's a lot better for me than waving the magic wand), but i'd rather have something like diff mode (which i couldn't figure out how to get to work with Org src edit buffers; it seems to really want to have a backing file).

greg-minshall commented 2 years ago

Julian, for your Entertainment, Experimentation, if you have a chance:

prettied-diff -- the package

and, for my Edification, if you have any comments, critiques about the code, packaging, documentation -- i'm all ears! cheers.