Personal emacs config
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

867 lines
31 KiB

  1. ;;; format-all.el --- Auto-format C, C++, JS, Python, Ruby and 40 other languages -*- lexical-binding: t -*-
  2. ;;
  3. ;; Author: Lassi Kortela <lassi@lassi.io>
  4. ;; URL: https://github.com/lassik/emacs-format-all-the-code
  5. ;; Package-Version: 0.3.0
  6. ;; Package-Commit: 8c8c47a863a397d947999fff4358caf20bafca0a
  7. ;; Version: 0.3.0
  8. ;; Package-Requires: ((emacs "24") (cl-lib "0.5") (language-id "0.4"))
  9. ;; Keywords: languages util
  10. ;; SPDX-License-Identifier: MIT
  11. ;;
  12. ;; This file is not part of GNU Emacs.
  13. ;;
  14. ;;; Commentary:
  15. ;;
  16. ;; Lets you auto-format source code in many languages using the same
  17. ;; command for all languages, instead of learning a different Emacs
  18. ;; package and formatting command for each language.
  19. ;;
  20. ;; Just do M-x format-all-buffer and it will try its best to do the
  21. ;; right thing. To auto-format code on save, use the minor mode
  22. ;; format-all-mode. Please see the documentation for that function
  23. ;; for instructions.
  24. ;;
  25. ;; Supported languages:
  26. ;;
  27. ;; - Angular/Vue (prettier)
  28. ;; - Assembly (asmfmt)
  29. ;; - Bazel Starlark (buildifier)
  30. ;; - BibTeX (emacs)
  31. ;; - C/C++/Objective-C (clang-format)
  32. ;; - Clojure/ClojureScript (node-cljfmt)
  33. ;; - CMake (cmake-format)
  34. ;; - Crystal (crystal tool format)
  35. ;; - CSS/Less/SCSS (prettier)
  36. ;; - D (dfmt)
  37. ;; - Dart (dartfmt)
  38. ;; - Dhall (dhall format)
  39. ;; - Dockerfile (dockfmt)
  40. ;; - Elixir (mix format)
  41. ;; - Elm (elm-format)
  42. ;; - Emacs Lisp (emacs)
  43. ;; - Fish Shell (fish_indent)
  44. ;; - Fortran 90 (fprettify)
  45. ;; - Go (gofmt)
  46. ;; - GraphQL (prettier)
  47. ;; - Haskell (brittany)
  48. ;; - HTML/XHTML/XML (tidy)
  49. ;; - Java (clang-format)
  50. ;; - JavaScript/JSON/JSX (prettier)
  51. ;; - Kotlin (ktlint)
  52. ;; - LaTeX (latexindent)
  53. ;; - Ledger (ledger-mode)
  54. ;; - Lua (lua-fmt)
  55. ;; - Markdown (prettier)
  56. ;; - Nix (nixfmt)
  57. ;; - OCaml (ocp-indent)
  58. ;; - Perl (perltidy)
  59. ;; - PHP (prettier plugin-php)
  60. ;; - Protocol Buffers (clang-format)
  61. ;; - PureScript (purty)
  62. ;; - Python (black)
  63. ;; - R (styler)
  64. ;; - Ruby (rufo)
  65. ;; - Rust (rustfmt)
  66. ;; - Scala (scalafmt)
  67. ;; - Shell script (shfmt)
  68. ;; - Solidity (prettier prettier-plugin-solidity)
  69. ;; - SQL (sqlformat)
  70. ;; - Swift (swiftformat)
  71. ;; - Terraform (terraform fmt)
  72. ;; - TypeScript/TSX (prettier)
  73. ;; - Verilog (iStyle)
  74. ;; - YAML (prettier)
  75. ;;
  76. ;; You will need to install external programs to do the formatting.
  77. ;; If `format-all-buffer` can't find the right program, it will try to
  78. ;; tell you how to install it.
  79. ;;
  80. ;; There are currently no customize variables, since it's not clear
  81. ;; what approach should be taken. Please see
  82. ;; https://github.com/lassik/emacs-format-all-the-code/issues for
  83. ;; discussion.
  84. ;;
  85. ;; Many of the external formatters support configuration files in the
  86. ;; source code directory to control their formatting. Please see the
  87. ;; documentation for each formatter.
  88. ;;
  89. ;; New external formatters can be added easily if they can read code
  90. ;; from standard input and format it to standard output. Feel free to
  91. ;; submit a pull request or ask for help in GitHub issues.
  92. ;;
  93. ;;; Code:
  94. (require 'language-id)
  95. (defvar format-all-debug nil
  96. "When non-nil, format-all writes debug info using `message'.")
  97. (defvar format-all-after-format-functions nil
  98. "Hook run after each time `format-all-buffer' has formatted a buffer.
  99. The value is a list of hook functions. Use `add-hook' to add a
  100. function. The function is called with two arguments: (FORMATTER
  101. STATUS). FORMATTER is a symbol naming the formatter, as given to
  102. `define-format-all-formatter'. STATUS is one of the following
  103. keywords:
  104. * :reformatted -- The formatter made changes to the buffer.
  105. * :already-formatted -- The buffer was already formatted
  106. correctly so the formatter didn't make any changes to it.
  107. * :error -- The formatter encountered an error (usually a syntax
  108. error). The buffer contents are the same as before formatting.
  109. The current buffer is the buffer that was just formatted. Point
  110. is not guaranteed to be in any particular place, so `goto-char'
  111. before editing the buffer. Narrowing may be in effect unless
  112. STATUS is :reformatted.")
  113. (eval-when-compile
  114. (defconst format-all--system-type
  115. (cl-case system-type
  116. (windows-nt 'windows)
  117. (cygwin 'windows)
  118. (darwin 'macos)
  119. (gnu/linux 'linux)
  120. (berkeley-unix
  121. (save-match-data
  122. (let ((case-fold-search t))
  123. (cond ((string-match "freebsd" system-configuration) 'freebsd)
  124. ((string-match "openbsd" system-configuration) 'openbsd)
  125. ((string-match "netbsd" system-configuration) 'netbsd))))))
  126. "Current operating system according to the format-all package."))
  127. (eval-when-compile
  128. (defun format-all--resolve-system (choices)
  129. "Get first choice matching `format-all--system-type' from CHOICES."
  130. (cl-dolist (choice choices)
  131. (cond ((atom choice)
  132. (cl-return choice))
  133. ((eql format-all--system-type (car choice))
  134. (cl-return (cadr choice)))))))
  135. (defun format-all--fix-trailing-whitespace ()
  136. "Fix trailing whitespace since some formatters don't do that."
  137. (save-match-data
  138. (goto-char (point-min))
  139. (while (re-search-forward "[ \t]+$" nil t)
  140. (replace-match ""))
  141. (goto-char (point-max))
  142. (delete-region
  143. (if (re-search-backward "[^ \t\n]" nil t) (match-end 0) (point-min))
  144. (point-max))
  145. (unless (= (point-min) (point-max))
  146. (goto-char (point-max))
  147. (insert "\n"))))
  148. (defun format-all--remove-ansi-color (string)
  149. "Internal helper function to remove terminal color codes from STRING."
  150. (save-match-data (replace-regexp-in-string "\x1b\\[[0-9]+m" "" string t)))
  151. (defun format-all--flatten-once (list)
  152. "Internal helper function to remove nested lists in LIST."
  153. (cl-mapcan (lambda (x) (if (listp x) x (list x)))
  154. list))
  155. (defun format-all--buffer-extension-p (&rest extensions)
  156. "Internal helper function to test file name EXTENSIONS."
  157. (and (buffer-file-name)
  158. (save-match-data
  159. (let ((case-fold-search t))
  160. (cl-some (lambda (ext)
  161. (string-match (concat "\\." (regexp-quote ext) "\\'")
  162. (buffer-file-name)))
  163. extensions)))))
  164. (defun format-all--buffer-thunk (thunk)
  165. "Internal helper function to implement formatters.
  166. THUNK is a function that implements a particular formatter. It
  167. takes INPUT (the unformatted source code as a string). THUNK is
  168. invoked such that the current buffer is an empty temp buffer. It
  169. should call the formatter on INPUT and write the formatted source
  170. code output to the temp buffer. It should return (ERRORP
  171. ERRPUT). ERRORP is a boolean indicating whether the formatter
  172. caused an error and hence the contents of the temp buffer should
  173. be discarded. ERRPUT is a string containing all error/warning
  174. output from the formatter.
  175. Note that in some cases we can use the output of the formatter
  176. even if it produced warnings. Not all warnings are errors."
  177. (save-excursion
  178. (save-restriction
  179. (widen)
  180. (let ((inbuf (current-buffer))
  181. (input (buffer-string)))
  182. (with-temp-buffer
  183. (cl-destructuring-bind (errorp errput) (funcall thunk input)
  184. (let* ((no-chg (or errorp
  185. (= 0 (let ((case-fold-search nil))
  186. (compare-buffer-substrings
  187. inbuf nil nil nil nil nil)))))
  188. (output (cond (errorp nil)
  189. (no-chg t)
  190. (t (buffer-string)))))
  191. (list output errput))))))))
  192. (defun format-all--buffer-native (mode &rest funcs)
  193. "Internal helper function to implement formatters.
  194. In a new temp buffer, switches to MODE then calls FUNCS in order
  195. to format the code. MODE and FUNCS should be symbols instead of
  196. functions to avoid warnings from the Emacs byte compiler."
  197. (format-all--buffer-thunk
  198. (lambda (input)
  199. (funcall mode)
  200. (insert input)
  201. (mapc #'funcall funcs)
  202. (format-all--fix-trailing-whitespace)
  203. (list nil ""))))
  204. (defun format-all--locate-default-directory (root-files)
  205. "Internal helper function to find working directory for formatter.
  206. ROOT-FILES is a list of strings which are the filenames to look
  207. for using `locate-dominating-file'. Details in documentation for
  208. `format-all--buffer-hard'."
  209. (let ((found-dirs
  210. (when (and root-files (buffer-file-name))
  211. (mapcan (lambda (root-file)
  212. (let ((found-file (locate-dominating-file
  213. (buffer-file-name) root-file)))
  214. (when found-file
  215. (list (file-name-directory found-file)))))
  216. root-files))))
  217. (or (car (sort found-dirs (lambda (a b) (> (length a) (length b)))))
  218. (and (buffer-file-name) (file-name-directory (buffer-file-name)))
  219. default-directory)))
  220. (defun format-all--buffer-hard
  221. (ok-statuses error-regexp root-files executable &rest args)
  222. "Internal helper function to implement formatters.
  223. Runs the external program EXECUTABLE. The program shall read
  224. unformatted code from stdin, write its formatted equivalent to
  225. stdout, and write errors/warnings to stderr.
  226. The program should exit with status zero for the formatting to be
  227. considered successful. If a list of OK-STATUSES is given, all of
  228. those are actually considered successful. But if ERROR-REGEXP is
  229. given, and the program's stderr contains that regexp, then the
  230. formatting is considered failed even if the exit status is in
  231. OK-STATUSES. OK-STATUSES and ERROR-REGEXP are hacks to work
  232. around formatter programs that don't make sensible use of their
  233. exit status.
  234. If ARGS are given, those are arguments to EXECUTABLE. They should
  235. not be shell-quoted.
  236. If ROOT-FILES are given, the working directory of the formatter
  237. will be the deepest directory (starting from the file being
  238. formatted) containing one of these files. If ROOT-FILES is nil,
  239. or none of ROOT-FILES are found in any parent directories, the
  240. working directory will be the one where the formatted file is.
  241. ROOT-FILES is ignored for buffers that are not visiting a file."
  242. (let ((ok-statuses (or ok-statuses '(0)))
  243. (args (format-all--flatten-once args))
  244. (default-directory (format-all--locate-default-directory root-files)))
  245. (when format-all-debug
  246. (message "Format-All: Running: %s"
  247. (mapconcat #'shell-quote-argument (cons executable args) " "))
  248. (message "Format-All: Directory: %s" default-directory))
  249. (format-all--buffer-thunk
  250. (lambda (input)
  251. (let* ((errfile (make-temp-file "format-all-"))
  252. (status (apply #'call-process-region input nil
  253. executable nil (list t errfile)
  254. nil args))
  255. (errput (with-temp-buffer
  256. (insert-file-contents errfile)
  257. (delete-file errfile)
  258. (buffer-string)))
  259. (errorp (or (not (member status ok-statuses))
  260. (and error-regexp
  261. (save-match-data
  262. (string-match error-regexp errput))))))
  263. (list errorp errput))))))
  264. (defun format-all--buffer-easy (executable &rest args)
  265. "Internal helper function to implement formatters.
  266. Runs the external program EXECUTABLE. The program shall read
  267. unformatted code from stdin, write its formatted equivalent to
  268. stdout, write errors/warnings to stderr, and exit zero/non-zero
  269. on success/failure.
  270. If ARGS are given, those are arguments to EXECUTABLE. They don't
  271. need to be shell-quoted."
  272. (apply 'format-all--buffer-hard nil nil nil executable args))
  273. (defvar format-all--executable-table (make-hash-table)
  274. "Internal table of formatter executable names for format-all.")
  275. (defvar format-all--install-table (make-hash-table)
  276. "Internal table of formatter install commands for format-all.")
  277. (defvar format-all--language-table (make-hash-table :test 'equal)
  278. "Internal table of major mode formatter lists for format-all.")
  279. (defvar format-all--format-table (make-hash-table)
  280. "Internal table of formatter formatting functions for format-all.")
  281. (defun format-all--pushhash (key value table)
  282. "Push VALUE onto the list under KEY in hash table TABLE."
  283. (puthash key (cons value (remove value (gethash key table))) table))
  284. (defmacro define-format-all-formatter (formatter &rest body)
  285. "Define a new source code formatter for use with format-all.
  286. FORMATTER is a symbol naming the formatter. The name of the
  287. command used to run the formatter is usually a good choice.
  288. Consult the existing formatters for examples of BODY."
  289. (let (executable install languages format)
  290. (cl-assert
  291. (equal (mapcar 'car body) '(:executable :install :languages :format)))
  292. (cl-dolist (part body)
  293. (cl-ecase (car part)
  294. (:executable
  295. (setq executable
  296. (unless (null (cdr part))
  297. (or (format-all--resolve-system (cdr part))
  298. (error "Executable not specified for %S system %S"
  299. formatter format-all--system-type)))))
  300. (:install
  301. (setq install (format-all--resolve-system (cdr part))))
  302. (:languages
  303. (setq languages
  304. (mapcar (lambda (language)
  305. `(format-all--pushhash
  306. ',language ',formatter format-all--language-table))
  307. (cdr part))))
  308. (:format
  309. (setq format `(lambda (executable language)
  310. (ignore language ,@(unless executable '(executable)))
  311. ,(cadr part))))))
  312. `(progn (puthash ',formatter ,executable format-all--executable-table)
  313. (puthash ',formatter ,install format-all--install-table)
  314. ,@languages
  315. (puthash ',formatter ,format format-all--format-table)
  316. ',formatter)))
  317. (define-format-all-formatter asmfmt
  318. (:executable "asmfmt")
  319. (:install)
  320. (:languages "Assembly")
  321. (:format (format-all--buffer-easy executable)))
  322. (define-format-all-formatter bibtex-mode
  323. (:executable)
  324. (:install)
  325. (:languages "BibTeX")
  326. (:format (format-all--buffer-native
  327. 'bibtex-mode 'bibtex-reformat 'bibtex-sort-buffer)))
  328. (define-format-all-formatter black
  329. (:executable "black")
  330. (:install "pip install black")
  331. (:languages "Python")
  332. (:format (format-all--buffer-easy
  333. executable "-q"
  334. (when (format-all--buffer-extension-p "pyi") "--pyi")
  335. "-")))
  336. (define-format-all-formatter brittany
  337. (:executable "brittany")
  338. (:install "stack install brittany")
  339. (:languages "Haskell" "Literate Haskell")
  340. (:format (format-all--buffer-easy executable)))
  341. (define-format-all-formatter buildifier
  342. (:executable "buildifier")
  343. (:install
  344. (macos "brew install buildifier")
  345. "go get github.com/bazelbuild/buildtools/buildifier")
  346. (:languages "Bazel")
  347. (:format (format-all--buffer-easy executable)))
  348. (define-format-all-formatter clang-format
  349. (:executable "clang-format")
  350. (:install
  351. (macos "brew install clang-format")
  352. (windows "scoop install llvm"))
  353. (:languages "C" "C++" "Java" "Objective-C" "Protocol Buffer")
  354. (:format
  355. (format-all--buffer-easy
  356. executable
  357. (concat "-assume-filename="
  358. (or (buffer-file-name)
  359. (cdr (assoc language
  360. '(("C" . ".c")
  361. ("C++" . ".cpp")
  362. ("Java" . ".java")
  363. ("Objective-C" . ".m")
  364. ("Protocol Buffer" . ".proto")))))))))
  365. (define-format-all-formatter cljfmt
  366. (:executable "cljfmt")
  367. (:install "npm install --global node-cljfmt")
  368. (:languages "Clojure")
  369. (:format (format-all--buffer-easy executable)))
  370. (define-format-all-formatter cmake-format
  371. (:executable "cmake-format")
  372. (:install "pip install cmake-format")
  373. (:languages "CMake")
  374. (:format (format-all--buffer-easy executable "-")))
  375. (define-format-all-formatter crystal
  376. (:executable "crystal")
  377. (:install (macos "brew install crystal"))
  378. (:languages "Crystal")
  379. (:format (format-all--buffer-easy executable "tool" "format" "-")))
  380. (define-format-all-formatter dartfmt
  381. (:executable "dartfmt")
  382. (:install (macos "brew tap dart-lang/dart && brew install dart"))
  383. (:languages "Dart")
  384. (:format
  385. (format-all--buffer-easy
  386. executable
  387. (when (buffer-file-name)
  388. (list "--stdin-name" (buffer-file-name))))))
  389. (define-format-all-formatter dfmt
  390. (:executable "dfmt")
  391. (:install (macos "brew install dfmt"))
  392. (:languages "D")
  393. (:format
  394. (format-all--buffer-hard nil (regexp-quote "[error]") nil executable)))
  395. (define-format-all-formatter dhall
  396. (:executable "dhall")
  397. (:install (macos "brew install dhall"))
  398. (:languages "Dhall")
  399. (:format (format-all--buffer-easy executable "format")))
  400. (define-format-all-formatter dockfmt
  401. (:executable "dockfmt")
  402. (:install "go get github.com/jessfraz/dockfmt")
  403. (:languages "Dockerfile")
  404. (:format (format-all--buffer-easy executable "fmt")))
  405. (define-format-all-formatter elm-format
  406. (:executable "elm-format")
  407. (:install (macos "brew install elm"))
  408. (:languages "Elm")
  409. (:format
  410. (cl-destructuring-bind (output errput)
  411. (format-all--buffer-hard nil nil '("elm.json" "elm-package.json")
  412. executable "--yes" "--stdin")
  413. (let ((errput (format-all--remove-ansi-color errput)))
  414. (list output errput)))))
  415. (define-format-all-formatter emacs-lisp
  416. (:executable)
  417. (:install)
  418. (:languages "Emacs Lisp")
  419. (:format
  420. (format-all--buffer-native
  421. 'emacs-lisp-mode
  422. (lambda () (indent-region (point-min) (point-max))))))
  423. (define-format-all-formatter fish-indent
  424. (:executable "fish_indent")
  425. (:install (macos "brew install fish OR port install fish"))
  426. (:languages "Fish")
  427. (:format (format-all--buffer-easy executable)))
  428. (define-format-all-formatter fprettify
  429. (:executable "fprettify")
  430. (:install "pip install fprettify")
  431. (:languages "_Fortran 90")
  432. (:format (format-all--buffer-easy executable "--silent")))
  433. (define-format-all-formatter gofmt
  434. (:executable "gofmt")
  435. (:install
  436. (macos "brew install go")
  437. (windows "scoop install go"))
  438. (:languages "Go")
  439. (:format (format-all--buffer-easy executable)))
  440. (define-format-all-formatter html-tidy
  441. (:executable "tidy")
  442. (:install
  443. (macos "brew install tidy-html5")
  444. (windows "scoop install tidy"))
  445. (:languages "HTML" "XML")
  446. (:format
  447. (format-all--buffer-hard
  448. '(0 1) nil nil
  449. executable
  450. "-q"
  451. "--tidy-mark" "no"
  452. "-indent"
  453. (when (equal language "XML") "-xml"))))
  454. (define-format-all-formatter istyle-verilog
  455. (:executable "iStyle")
  456. (:install)
  457. (:languages "Verilog")
  458. (:format (format-all--buffer-easy executable)))
  459. (define-format-all-formatter ktlint
  460. (:executable "ktlint")
  461. (:install (macos "brew install ktlint"))
  462. (:languages "Kotlin")
  463. (:format (format-all--buffer-easy executable "--format" "--stdin")))
  464. (define-format-all-formatter latexindent
  465. (:executable "latexindent")
  466. (:install)
  467. (:languages "LaTeX")
  468. (:format (format-all--buffer-easy executable)))
  469. (define-format-all-formatter ledger-mode
  470. (:executable)
  471. (:install)
  472. (:languages "_Ledger")
  473. (:format
  474. (format-all--buffer-native 'ledger-mode 'ledger-mode-clean-buffer)))
  475. (define-format-all-formatter lua-fmt
  476. (:executable "luafmt")
  477. (:install "npm install --global lua-fmt")
  478. (:languages "Lua")
  479. (:format (format-all--buffer-easy executable "--stdin")))
  480. (define-format-all-formatter mix-format
  481. (:executable "mix")
  482. (:install (macos "brew install elixir"))
  483. (:languages "Elixir")
  484. (:format
  485. (format-all--buffer-hard
  486. nil nil '("mix.exs")
  487. executable
  488. "format"
  489. (let* ((file ".formatter.exs")
  490. (dir (and (buffer-file-name)
  491. (locate-dominating-file (buffer-file-name) file))))
  492. (when dir (list "--dot-formatter" (concat dir file))))
  493. "-")))
  494. (define-format-all-formatter nixfmt
  495. (:executable "nixfmt")
  496. (:install "nix-env -f https://github.com/serokell/nixfmt/archive/master.tar.gz -i")
  497. (:languages "Nix")
  498. (:format (format-all--buffer-easy executable)))
  499. (define-format-all-formatter ocp-indent
  500. (:executable "ocp-indent")
  501. (:install "opam install ocp-indent")
  502. (:languages "OCaml")
  503. (:format (format-all--buffer-easy executable)))
  504. (define-format-all-formatter perltidy
  505. (:executable "perltidy")
  506. (:install "cpan install Perl::Tidy")
  507. (:languages "Perl")
  508. (:format (format-all--buffer-easy executable)))
  509. (define-format-all-formatter prettier
  510. (:executable "prettier")
  511. (:install "npm install --global prettier @prettier/plugin-php prettier-plugin-solidity")
  512. (:languages
  513. "CSS" "GraphQL" "JavaScript" "JSON" "JSX" "Less" "Markdown" "PHP"
  514. "SCSS" "Solidity" "TSX" "TypeScript" "Vue" "YAML"
  515. ;; TODO: Use html-tidy instead of prettier for plain HTML. Enable
  516. ;; prettier's HTML support once we have multi-formatter support.
  517. ;; "HTML"
  518. "_Angular" "_Flow")
  519. (:format
  520. (format-all--buffer-easy
  521. executable
  522. "--parser" (let ((pair (assoc language
  523. '(("_Angular" . "angular")
  524. ("_Flow" . "flow")
  525. ("JavaScript" . "babel")
  526. ("JSX" . "babel")
  527. ("Solidity" . "solidity-parse")
  528. ("TSX" . "typescript")))))
  529. (if pair (cdr pair) (downcase language)))
  530. (when (buffer-file-name) (list "--stdin-filepath" (buffer-file-name))))))
  531. (define-format-all-formatter purty
  532. (:executable "purty")
  533. (:install "npm install --global purty")
  534. (:languages "PureScript")
  535. (:format (format-all--buffer-easy executable "-")))
  536. (define-format-all-formatter rufo
  537. (:executable "rufo")
  538. (:install "gem install rufo")
  539. (:languages "Ruby")
  540. (:format
  541. (format-all--buffer-easy
  542. executable
  543. "--simple-exit"
  544. (when (buffer-file-name)
  545. (list "--filename" (buffer-file-name))))))
  546. (define-format-all-formatter rustfmt
  547. (:executable "rustfmt")
  548. (:install "cargo install rustfmt")
  549. (:languages "Rust")
  550. (:format (format-all--buffer-easy executable)))
  551. (define-format-all-formatter scalafmt
  552. (:executable "scalafmt")
  553. (:install "coursier bootstrap org.scalameta:scalafmt-cli_2.12:2.4.0-RC1 -r sonatype:snapshots -o /usr/local/bin/scalafmt --standalone --main org.scalafmt.cli.Cli")
  554. (:languages "Scala")
  555. (:format
  556. (format-all--buffer-easy
  557. executable "--stdin" "--non-interactive" "--quiet")))
  558. (define-format-all-formatter shfmt
  559. (:executable "shfmt")
  560. (:install
  561. (macos "brew install shfmt")
  562. (windows "scoop install shfmt"))
  563. (:languages "Shell")
  564. (:format
  565. (format-all--buffer-easy
  566. executable
  567. "-ln" (cl-case (and (eql major-mode 'sh-mode)
  568. (boundp 'sh-shell)
  569. (symbol-value 'sh-shell))
  570. (bash "bash")
  571. (mksh "mksh")
  572. (t "posix")))))
  573. (define-format-all-formatter sqlformat
  574. (:executable "sqlformat")
  575. (:install "pip install sqlparse")
  576. (:languages "SQL")
  577. (:format
  578. (let* ((ic (car default-process-coding-system))
  579. (oc (cdr default-process-coding-system))
  580. (ienc (symbol-name (or (coding-system-get ic :mime-charset)
  581. 'utf-8)))
  582. (oenc (symbol-name (or (coding-system-get oc :mime-charset)
  583. 'utf-8)))
  584. (process-environment (cons (concat "PYTHONIOENCODING=" oenc)
  585. process-environment)))
  586. (format-all--buffer-easy
  587. executable
  588. "--keywords" "upper"
  589. "--reindent_aligned"
  590. "--encoding" ienc
  591. "-"))))
  592. (define-format-all-formatter styler
  593. (:executable "Rscript")
  594. (:install "Rscript -e 'install.packages(\"styler\")'")
  595. (:languages "R")
  596. (:format
  597. (format-all--buffer-easy
  598. executable "--vanilla"
  599. "-e" (concat
  600. "options(styler.colored_print.vertical=FALSE);"
  601. " con <- file(\"stdin\");"
  602. " out <- styler::style_text(readLines(con));"
  603. " close(con);"
  604. " out"))))
  605. (define-format-all-formatter swiftformat
  606. (:executable "swiftformat")
  607. (:install (macos "brew install swiftformat"))
  608. (:languages "Swift")
  609. (:format (format-all--buffer-easy executable "--quiet")))
  610. (define-format-all-formatter terraform-fmt
  611. (:executable "terraform")
  612. (:install (macos "brew install terraform"))
  613. (:languages "Terraform")
  614. (:format (format-all--buffer-easy executable "fmt" "-no-color" "-")))
  615. (defun format-all--language-id-buffer ()
  616. "Return the language used in the current buffer, or NIL.
  617. Prefer getting the ID from the language-id library. Some
  618. languages do not yet have official GitHub Linguist identifiers,
  619. yet format-all needs to know about them anyway. That's why we
  620. have this custom language-id function in format-all. The
  621. unofficial languages IDs are prefixed with \"_\"."
  622. (or (language-id-buffer)
  623. (and (or (equal major-mode 'angular-html-mode)
  624. (and (equal major-mode 'web-mode)
  625. (equal (symbol-value 'web-mode-content-type) "html")
  626. (equal (symbol-value 'web-mode-engine) "angular")))
  627. "_Angular")
  628. (and (member major-mode '(js-mode js2-mode js3-mode))
  629. (boundp 'flow-minor-mode)
  630. (not (null (symbol-value 'flow-minor-mode)))
  631. "_Flow")
  632. (and (equal major-mode 'f90-mode) "_Fortran 90")
  633. (and (equal major-mode 'ledger-mode) "_Ledger")))
  634. (defun format-all--please-install (executable installer)
  635. "Internal helper function for error about missing EXECUTABLE and INSTALLER."
  636. (concat (format "You need the %S command." executable)
  637. (if (not installer) ""
  638. (format " You may be able to install it via %S." installer))))
  639. (defun format-all--probe ()
  640. "Internal helper function to get the formatter for the current buffer."
  641. (let ((language (format-all--language-id-buffer)))
  642. (cl-dolist (formatter (gethash language format-all--language-table)
  643. (list nil nil))
  644. (cl-return (list formatter language)))))
  645. (defun format-all--formatter-executable (formatter)
  646. "Internal helper function to get the external program for FORMATTER."
  647. (let ((executable (gethash formatter format-all--executable-table)))
  648. (when executable
  649. (or (executable-find executable)
  650. (error (format-all--please-install
  651. executable
  652. (gethash formatter format-all--install-table)))))))
  653. (defun format-all--show-or-hide-errors (error-output)
  654. "Internal helper function to update *format-all-errors* with ERROR-OUTPUT."
  655. (save-selected-window
  656. (with-current-buffer (get-buffer-create "*format-all-errors*")
  657. (erase-buffer)
  658. (cond ((not (= 0 (length error-output)))
  659. (insert error-output)
  660. (display-buffer (current-buffer)))
  661. (t
  662. (let ((error-window (get-buffer-window (current-buffer))))
  663. (when error-window (quit-window nil error-window))))))))
  664. (defun format-all--save-line-number (thunk)
  665. "Internal helper function to run THUNK and go back to the same line."
  666. (let ((old-line-number (line-number-at-pos))
  667. (old-column (current-column)))
  668. (funcall thunk)
  669. (goto-char (point-min))
  670. (forward-line (1- old-line-number))
  671. (let ((line-length (- (point-at-eol) (point-at-bol))))
  672. (goto-char (+ (point) (min old-column line-length))))))
  673. (defun format-all-buffer--with (formatter language)
  674. "Internal helper function to format the current buffer.
  675. Relies on FORMATTER and LANGUAGE from `format-all--probe'."
  676. (when format-all-debug
  677. (message "Format-All: Formatting %s using %S"
  678. (buffer-name) (list formatter language)))
  679. (let ((f-function (gethash formatter format-all--format-table))
  680. (executable (format-all--formatter-executable formatter)))
  681. (cl-destructuring-bind (output errput)
  682. (funcall f-function executable language)
  683. (let ((status (cond ((null output) :error)
  684. ((equal t output) :already-formatted)
  685. (t :reformatted))))
  686. (when (equal :reformatted status)
  687. (widen)
  688. (format-all--save-line-number
  689. (lambda ()
  690. (erase-buffer)
  691. (insert output))))
  692. (format-all--show-or-hide-errors errput)
  693. (run-hook-with-args 'format-all-after-format-functions
  694. formatter status)
  695. (message (cl-ecase status
  696. (:error "Formatting error")
  697. (:already-formatted "Already formatted")
  698. (:reformatted "Reformatted!")))))))
  699. (defun format-all-buffer--from-hook ()
  700. "Internal helper function to auto-format current buffer from a hook.
  701. Format-All installs this function into `before-save-hook' to
  702. format buffers on save. This is a lenient version of
  703. `format-all-buffer' that silently succeeds instead of signaling
  704. an error if the current buffer has no formatter."
  705. (cl-destructuring-bind (formatter language) (format-all--probe)
  706. (when formatter
  707. (format-all-buffer--with formatter language))))
  708. ;;;###autoload
  709. (defun format-all-buffer ()
  710. "Auto-format the source code in the current buffer.
  711. No disk files are touched - the buffer doesn't even need to be
  712. saved. If you don't like the results of the formatting, you can
  713. use ordinary undo to get your code back to its previous state.
  714. You will need to install external programs to do the formatting.
  715. If the command can't find the program that it needs, it will try
  716. to tell you how you might be able to install it on your operating
  717. system. Only BibTeX, Emacs Lisp and Ledger are formatted without an
  718. external program.
  719. A suitable formatter is selected according to the `major-mode' of
  720. the buffer. Many popular programming languages are supported.
  721. It is fairly easy to add new languages that have an external
  722. formatter.
  723. If any errors or warnings were encountered during formatting,
  724. they are shown in a buffer called *format-all-errors*."
  725. (interactive)
  726. (cl-destructuring-bind (formatter language) (format-all--probe)
  727. (if formatter
  728. (format-all-buffer--with formatter language)
  729. (error "Don't know how to format %S code" major-mode))))
  730. ;;;###autoload
  731. (define-minor-mode format-all-mode
  732. "Toggle automatic source code formatting before save.
  733. When this minor mode (FmtAll) is enabled, `format-all-buffer' is
  734. automatically called to format your code each time before you
  735. save the buffer.
  736. The mode is buffer-local and needs to be enabled separately each
  737. time a file is visited. You may want to use `add-hook' to add a
  738. function to your personal `after-change-major-mode-hook' in your
  739. `user-init-file' to enable the mode based on the buffer's
  740. `major-mode' and some `buffer-file-name' patterns. For example:
  741. (defvar my-auto-format-modes '(js-mode python-mode))
  742. (defvar my-auto-format-dirs '(\"foo\" \"bar\"))
  743. (defun my-auto-format-buffer-p ()
  744. (and (member major-mode my-auto-format-modes)
  745. (buffer-file-name)
  746. (save-match-data
  747. (let ((dir (file-name-directory (buffer-file-name))))
  748. (cl-some (lambda (regexp) (string-match regexp dir))
  749. my-auto-format-dirs)))))
  750. (defun my-after-change-major-mode ()
  751. (format-all-mode (if (my-auto-format-buffer-p) 1 0)))
  752. (add-hook 'after-change-major-mode-hook 'my-after-change-major-mode)
  753. When `format-all-mode' is called as a Lisp function, the mode is
  754. toggled if ARG is toggle, disabled if ARG is a negative integer
  755. or zero, and enabled otherwise."
  756. :lighter " FmtAll"
  757. :global nil
  758. (if format-all-mode
  759. (add-hook 'before-save-hook
  760. 'format-all-buffer--from-hook
  761. nil 'local)
  762. (remove-hook 'before-save-hook
  763. 'format-all-buffer--from-hook
  764. 'local)))
  765. (provide 'format-all)
  766. ;;; format-all.el ends here