Skip to content

Commit

Permalink
editorconfig.el: Hook elsewhere for the coding-system
Browse files Browse the repository at this point in the history
Until now, the file's coding system was set via an advice on
`insert-file-contents`, which depends on being called from within
the `find-file-noselect` advice.
While it mostly works, it doesn't account for all the cases (e.g. it
fails to be used when doing `write-region`), is a bit cumbersome, and
does not interact well with Emacs's other ways to specify
a coding-system, such as `auto-coding-alist`.

This patch replaces it with an advice on `find-auto-coding`, so as
to integrate better with the rest of Emacs's handling of files' coding
systems.  An immediate benefit is that we don't need to special
case jar and zip files any more because `auto-coding-alist` does
it for us already (and for many more file types like tar, exe, ...).

* editorconfig.el (editorconfig-merge-coding-systems):
Use `merge-coding-systems` only when merging is actually necessary.
(editorconfig-set-coding-system-revert): Adjust accordingly.
(editorconfig--filename-codingsystem-hash): Delete variable.
(editorconfig--advice-insert-file-contents): Delete function.
(editorconfig--advice-find-auto-coding): New function.
(editorconfig--advice-find-file-noselect): Don't mess with coding
systems any more.
(editorconfig--getting-coding-system): New var.
(editorconfig--get-coding-system): New function.
(editorconfig-mode): Advise `find-auto-coding` instead of
`insert-file-contents`.
(editorconfig-version): Use `package-get-version` when available.
(find-library-name, lm-version): Move their declaration to the place
where we have a good reason to think they're defined.
(editorconfig-exclude-regexps): Remove jar and zip patterns, not
needed any more.
  • Loading branch information
monnier committed Aug 16, 2024
1 parent 648f0cf commit 1b3217d
Showing 1 changed file with 59 additions and 88 deletions.
147 changes: 59 additions & 88 deletions editorconfig.el
Original file line number Diff line number Diff line change
Expand Up @@ -349,12 +349,7 @@ NOTE: Only the **buffer local** value of VARIABLE will be set."
"Modes in which `editorconfig-mode-apply' will not run."
:type '(repeat (symbol :tag "Major Mode")))

(defcustom editorconfig-exclude-regexps
;; With current implementation `editorconfig--disabled-for-majormode'
;; cannot stop this lib from trying to configure buffer coding-systems
;; because coding-system-for-read is set before deciding buffer major-mode.
;; So exclude some archive files here.
'("\\.jar\\'" "\\.zip\\'")
(defcustom editorconfig-exclude-regexps ()
"List of regexp for buffer filenames `editorconfig-mode-apply' will not run.
When variable `buffer-file-name' matches any of the regexps, then
Expand Down Expand Up @@ -543,16 +538,16 @@ Make a message by passing ARGS to `format-message'."
(let ((eol (cond
((equal end-of-line "lf") 'undecided-unix)
((equal end-of-line "cr") 'undecided-mac)
((equal end-of-line "crlf") 'undecided-dos)
(t 'undecided)))
((equal end-of-line "crlf") 'undecided-dos)))
(cs (cond
((equal charset "latin1") 'iso-latin-1)
((equal charset "utf-8") 'utf-8)
((equal charset "utf-8-bom") 'utf-8-with-signature)
((equal charset "utf-16be") 'utf-16be-with-signature)
((equal charset "utf-16le") 'utf-16le-with-signature)
(t 'undecided))))
(merge-coding-systems cs eol)))
((equal charset "utf-16le") 'utf-16le-with-signature))))
(if (and eol cs)
(merge-coding-systems cs eol)
(or eol cs))))

(cl-defun editorconfig-set-coding-system-revert (end-of-line charset)
"Set buffer coding system by END-OF-LINE and CHARSET.
Expand All @@ -568,7 +563,7 @@ This function will revert buffer when the coding-system has been changed."
coding-system
editorconfig--apply-coding-system-currently)
:debug)
(when (eq coding-system 'undecided)
(when (memq coding-system '(nil undecided))
(cl-return-from editorconfig-set-coding-system-revert))
(when (and buffer-file-coding-system
(memq buffer-file-coding-system
Expand All @@ -580,7 +575,9 @@ This function will revert buffer when the coding-system has been changed."
(cl-return-from editorconfig-set-coding-system-revert))
(unless (memq coding-system
(coding-system-aliases editorconfig--apply-coding-system-currently))
;; Revert functions might call editorconfig-apply again
;; Revert functions might call `editorconfig-apply' again
;; FIXME: I suspect `editorconfig--apply-coding-system-currently'
;; gymnastics is not needed now that we hook into `find-auto-coding'.
(unwind-protect
(progn
(setq editorconfig--apply-coding-system-currently coding-system)
Expand Down Expand Up @@ -746,81 +743,33 @@ This function also executes `editorconfig-after-apply-functions' functions."
(format "Error while running `editorconfig-after-apply-functions': %S"
err))))))

(defvar editorconfig--filename-codingsystem-hash (make-hash-table :test 'equal)
"Used interally.
`editorconfig--advice-find-file-noselect' will put value to this hash, and
`editorconfig--advice-insert-file-contents' will use the value to set
`coding-system-for-read' value.")

(defun editorconfig--advice-insert-file-contents (f filename &rest args)
"Set `coding-system-for-read'.
This function should be added as an advice function to `insert-file-contents'.
F is that function, and FILENAME and ARGS are arguments passed to F."
;; This function uses `editorconfig--filename-codingsystem-hash' to decide what coding-system
;; should be used, which will be set by `editorconfig--advice-find-file-noselect'.
(display-warning '(editorconfig editorconfig--advice-insert-file-contents)
(format "editorconfig--advice-insert-file-contents: filename: %S args: %S codingsystem: %S bufferfilename: %S"
filename args
editorconfig--filename-codingsystem-hash
buffer-file-name)
:debug)
(let ((coding-system (and (stringp filename)
(gethash (expand-file-name filename)
editorconfig--filename-codingsystem-hash))))
(if (and coding-system
(not (eq coding-system
'undecided)))
(let ((coding-system-for-read coding-system))
(apply f filename args))
(apply f filename args))))
(defun editorconfig--advice-find-auto-coding (filename &rest _args)
"Consult `charset' setting of EditorConfig."
(let ((cs (dlet ((auto-coding-file-name filename))
(editorconfig--get-coding-system))))
(when cs (cons cs 'EditorConfig))))

(defun editorconfig--advice-find-file-noselect (f filename &rest args)
"Get EditorConfig properties and apply them to buffer to be visited.
This function should be added as an advice function to `find-file-noselect'.
F is that function, and FILENAME and ARGS are arguments passed to F."
(let ((props nil)
(coding-system nil)
(ret nil))
(condition-case err
(when (and (stringp filename)
(not (editorconfig--disabled-for-filename filename)))
(setq props (editorconfig-call-get-properties-function filename))
(setq coding-system
(editorconfig-merge-coding-systems (gethash 'end_of_line props)
(gethash 'charset props)))
(puthash (expand-file-name filename)
coding-system
editorconfig--filename-codingsystem-hash))
(when (stringp filename)
(setq props (editorconfig-call-get-properties-function filename)))
(error
(display-warning '(editorconfig editorconfig--advice-find-file-noselect)
(format "Failed to get properties, styles will not be applied: %S"
err)
:warning)))

(setq ret (apply f filename args))
(clrhash editorconfig--filename-codingsystem-hash)

(condition-case err
(with-current-buffer ret
(when (and props
;; filename has already been checked
(not (editorconfig--disabled-for-majormode major-mode)))

;; When file path indicates it is a remote file and it actually
;; does not exists, `buffer-file-coding-system' will not be set.
;; (Seems `insert-file-contents' will not be called)
;; For this case, explicitly set this value so that saving will be done
;; with expected coding system.
(when (and (file-remote-p filename)
(not (local-variable-p 'buffer-file-coding-system))
(not (file-exists-p filename))
coding-system
(not (eq coding-system
'undecided)))
(set-buffer-file-coding-system coding-system))
(when props

;; NOTE: hack-properties-functions cannot affect coding-system value,
;; because it has to be set before initializing buffers.
Expand Down Expand Up @@ -848,6 +797,23 @@ F is that function, and FILENAME and ARGS are arguments passed to F."
(format "Error while setting variables from EditorConfig: %S" err))))
ret))

(defvar editorconfig--getting-coding-system nil)

(defun editorconfig--get-coding-system (&optional _size)
"Return the coding system to use according to EditorConfig.
Meant to be used on `auto-coding-functions'."
(defvar auto-coding-file-name) ;; Emacs≥30
(when (and (stringp auto-coding-file-name)
(file-name-absolute-p auto-coding-file-name)
;; Don't recurse infinitely.
(not (member auto-coding-file-name
editorconfig--getting-coding-system)))
(let* ((editorconfig--getting-coding-system
(cons auto-coding-file-name editorconfig--getting-coding-system))
(props (editorconfig-call-get-properties-function
auto-coding-file-name)))
(editorconfig-merge-coding-systems (gethash 'end_of_line props)
(gethash 'charset props)))))

;;;###autoload
(define-minor-mode editorconfig-mode
Expand All @@ -867,13 +833,15 @@ To disable EditorConfig in some buffers, modify
(if editorconfig-mode
(progn
(advice-add 'find-file-noselect :around #'editorconfig--advice-find-file-noselect)
(advice-add 'insert-file-contents :around #'editorconfig--advice-insert-file-contents)
(advice-add 'find-auto-coding :after-until
#'editorconfig--advice-find-auto-coding)
(dolist (hook modehooks)
(add-hook hook
#'editorconfig-major-mode-hook
t)))
(advice-remove 'find-file-noselect #'editorconfig--advice-find-file-noselect)
(advice-remove 'insert-file-contents #'editorconfig--advice-insert-file-contents)
(advice-remove 'find-auto-coding
#'editorconfig--advice-find-auto-coding)
(dolist (hook modehooks)
(remove-hook hook #'editorconfig-major-mode-hook)))))

Expand All @@ -885,30 +853,33 @@ To disable EditorConfig in some buffers, modify
;; (lm-version))
;; "EditorConfig version.")

(declare-function find-library-name "find-func" (library))
(declare-function lm-version "lisp-mnt" nil)

;;;###autoload
(defun editorconfig-version (&optional show-version)
"Get EditorConfig version as string.
If called interactively or if SHOW-VERSION is non-nil, show the
version in the echo area and the messages buffer."
(interactive (list t))
(let* ((version (with-temp-buffer
(require 'find-func)
(insert-file-contents (find-library-name "editorconfig"))
(require 'lisp-mnt)
(lm-version)))
(pkg (and (eval-and-compile (require 'package nil t))
(cadr (assq 'editorconfig
package-alist))))
(pkg-version (and pkg
(package-version-join (package-desc-version pkg))))
(version-full (if (and pkg-version
(not (string= version pkg-version)))
(concat version "-" pkg-version)
version)))
(let ((version-full
(if (fboundp 'package-get-version) ;Emacs≥27
(package-get-version)
(let* ((version
(with-temp-buffer
(require 'find-func)
(declare-function find-library-name "find-func" (library))
(insert-file-contents (find-library-name "editorconfig"))
(require 'lisp-mnt)
(declare-function lm-version "lisp-mnt" nil)
(lm-version)))
(pkg (and (eval-and-compile (require 'package nil t))
(cadr (assq 'editorconfig
package-alist))))
(pkg-version (and pkg (package-version-join
(package-desc-version pkg)))))
(if (and pkg-version
(not (string= version pkg-version)))
(concat version "-" pkg-version)
version)))))
(when show-version
(message "EditorConfig Emacs v%s" version-full))
version-full))
Expand Down

0 comments on commit 1b3217d

Please sign in to comment.