From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.io!.POSTED.blaine.gmane.org!not-for-mail From: Stefan Monnier via "Bug reports for GNU Emacs, the Swiss army knife of text editors" Newsgroups: gmane.emacs.bugs Subject: bug#70105: 30.0.50; Emacs should support EditorConfig out of the box Date: Thu, 06 Jun 2024 19:51:37 -0400 Message-ID: References: <867chibhqi.fsf@gnu.org> Reply-To: Stefan Monnier Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" Injection-Info: ciao.gmane.io; posting-host="blaine.gmane.org:116.202.254.214"; logging-data="12138"; mail-complaints-to="usenet@ciao.gmane.io" User-Agent: Gnus/5.13 (Gnus v5.13) Cc: Jen-Chieh Shen , =?UTF-8?Q?Bj=C3=B6rn?= Bidar , 10sr <8slashes+git@gmail.com>, 70105@debbugs.gnu.org To: Eli Zaretskii Original-X-From: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane-mx.org@gnu.org Fri Jun 07 01:53:14 2024 Return-path: Envelope-to: geb-bug-gnu-emacs@m.gmane-mx.org Original-Received: from lists.gnu.org ([209.51.188.17]) by ciao.gmane.io with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1sFMuw-0002tf-68 for geb-bug-gnu-emacs@m.gmane-mx.org; Fri, 07 Jun 2024 01:53:14 +0200 Original-Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1sFMuX-0001P3-B9; Thu, 06 Jun 2024 19:52:49 -0400 Original-Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1sFMuW-0001Ot-Aw for bug-gnu-emacs@gnu.org; Thu, 06 Jun 2024 19:52:48 -0400 Original-Received: from debbugs.gnu.org ([2001:470:142:5::43]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1sFMuW-000134-3N for bug-gnu-emacs@gnu.org; Thu, 06 Jun 2024 19:52:48 -0400 Original-Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.84_2) (envelope-from ) id 1sFMuk-0006RO-FK for bug-gnu-emacs@gnu.org; Thu, 06 Jun 2024 19:53:02 -0400 X-Loop: help-debbugs@gnu.org Resent-From: Stefan Monnier Original-Sender: "Debbugs-submit" Resent-CC: bug-gnu-emacs@gnu.org Resent-Date: Thu, 06 Jun 2024 23:53:02 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: followup 70105 X-GNU-PR-Package: emacs Original-Received: via spool by 70105-submit@debbugs.gnu.org id=B70105.171771792624633 (code B ref 70105); Thu, 06 Jun 2024 23:53:02 +0000 Original-Received: (at 70105) by debbugs.gnu.org; 6 Jun 2024 23:52:06 +0000 Original-Received: from localhost ([127.0.0.1]:52790 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1sFMtp-0006PE-4k for submit@debbugs.gnu.org; Thu, 06 Jun 2024 19:52:06 -0400 Original-Received: from mailscanner.iro.umontreal.ca ([132.204.25.50]:47842) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1sFMtl-0006OZ-Sx for 70105@debbugs.gnu.org; Thu, 06 Jun 2024 19:52:03 -0400 Original-Received: from pmg1.iro.umontreal.ca (localhost.localdomain [127.0.0.1]) by pmg1.iro.umontreal.ca (Proxmox) with ESMTP id 06DB3100048; Thu, 6 Jun 2024 19:51:41 -0400 (EDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=iro.umontreal.ca; s=mail; t=1717717899; bh=JkCQKwex/8jGxgbqRoHaJHpTs+A/QOUAxZiDcF9x0+s=; h=From:To:Cc:Subject:In-Reply-To:References:Date:From; b=bvtOdp5H/jWCEjXkGsA/CnJpMmQCUFIyc8bhigibhp/vjzksET3nkNsBVHtBUbEki KQbbeWW/dtbmbtbL4gq4SxOHmSvRKsDJq4jDwx6jrK4rgu0SiympXnAvqIjzWo70Gw UsQDa6RvC8TXUaBnPN8XVSQvRS74Cf35EHunaYmwiilloYFvKkdxdolRxp+jfYJ/uL 7EC1LDyf89gHjME+TacgdGuJaXWp1Ub12dG5hhdJUb03/Bj5o6hL7MxsRRXFeLCUuj 1WvY0izAy4IEkYjXs6H5n/Hdwwzu3hiK6zQEjcR+rE+bu5HRnYlaZAbYNQYPxqJFlI dD5zZ8PvyCI9g== Original-Received: from mail01.iro.umontreal.ca (unknown [172.31.2.1]) by pmg1.iro.umontreal.ca (Proxmox) with ESMTP id 34D7C100035; Thu, 6 Jun 2024 19:51:39 -0400 (EDT) Original-Received: from pastel (unknown [24.140.236.196]) by mail01.iro.umontreal.ca (Postfix) with ESMTPSA id E7A1612024D; Thu, 6 Jun 2024 19:51:38 -0400 (EDT) In-Reply-To: <867chibhqi.fsf@gnu.org> (Eli Zaretskii's message of "Sun, 31 Mar 2024 17:25:57 +0300") X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list X-BeenThere: bug-gnu-emacs@gnu.org List-Id: "Bug reports for GNU Emacs, the Swiss army knife of text editors" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane-mx.org@gnu.org Original-Sender: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane-mx.org@gnu.org Xref: news.gmane.io gmane.emacs.bugs:286725 Archived-At: --=-=-= Content-Type: text/plain I think I have a clear enough idea of how support for Editorconfig should be integrated into Emacs, in terms of "how to hook it to the right places". There are still pending questions about how to adapt the code of the `editorconfig` package, but in the mean time: here's a first step to make sure Emacs offers the right hooks. Find below two patch which do that. The first patch slightly extends `auto-coding-functions` so that it can use the file name to decide which coding system to use. This is a fairly simple change: this clearly the right place to hook ourselves into. I make the change by adding a dynamically scoped `auto-coding-file-name` variable. The resulting API would be cleaner if we instead passed the filename as an additional argument, which we could do easily (even with some amount of backward compatibility), but the change proposed has the advantage of being 100% backward compatible. The cleaner API would replace: (let ((auto-coding-file-name filename)) (funcall (pop funcs) size)) with (let ((fun (pop funcs))) (condition-case nil (funcall fun size filename) (wrong-number-of-arguments (funcall fun size)))) In the long run the cleaner API is probably preferable, but it does come with a slight risk of backward incompatibility. The second patch adds a new hook `hack-dir-local-get-variables-functions`. This one is not as "obvious": there are other places we could hook ourselves into. E.g. it's tempting to hook deeper into the dir-locals machinery so as to reuse its caching mechanism. In the end I decided not to do that because the dir-locals machinery is not prepared to accept settings coming from different levels of the directory tree, so that trying to shoehorn Editorconfig into it would be a bit messy and/or unreliable. The downside is that this hook forces its users to take care of their own caching. But since the current examples all need to do their caching a bit differently, I think this is OK. There's one other design choice I made: `hack-dir-local-get-variables-functions` returns elements of the form (DIR . ALIST) where DIR is used to obey `safe-local-variable-directories' as well as to provide a bit more info to the user when prompting for confirmation in cache of risky settings. But that does not provide any actual info about the actual source of the ALIST, so the message we emit when prompting the user can't tell the user whether the settings come from a `.dir-locals.el` or something else. This is not a new problem: the current code already can't tell the user if the settings come from a `.dir-locals.el`, or a "project class" or a `.dir-locals-2.el`. So, apparently, this is a problem we have not deemed worthwhile to fix, so I left it like unsolved. I'm hoping this can get into Emacs-30. Comments/objections? Stefan --=-=-= Content-Type: text/x-diff Content-Disposition: inline; filename=0001-find-auto-coding-Provide-filename-to-auto-coding-fun.patch >From d12c9bc2a4c8383abdf7fa32b67f1ca0379227e3 Mon Sep 17 00:00:00 2001 From: Stefan Monnier Date: Tue, 4 Jun 2024 10:58:29 -0400 Subject: [PATCH 1/2] (find-auto-coding): Provide filename to `auto-coding-functions` Allow `auto-coding-functions` to know the file name. Motivated by the needs of Editorconfig support. * lisp/international/mule.el (auto-coding-file-name): New var. (find-auto-coding): Let-bind it for `auto-coding-functions`. Document the expectation that the arg be an absolute file name. * doc/lispref/nonascii.texi (Default Coding Systems): Mention `auto-coding-file-name`. --- doc/lispref/nonascii.texi | 3 +++ etc/NEWS | 5 +++++ lisp/international/mule.el | 17 ++++++++++++++--- 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/doc/lispref/nonascii.texi b/doc/lispref/nonascii.texi index b33082e2b24..1482becb9f5 100644 --- a/doc/lispref/nonascii.texi +++ b/doc/lispref/nonascii.texi @@ -1654,6 +1654,9 @@ Default Coding Systems starting from point. If the function succeeds in determining a coding system for the file, it should return that coding system. Otherwise, it should return @code{nil}. +Each function can also find the name of the file to which +the buffer's content belong in the variable +@code{auto-coding-file-name}. The functions in this list could be called either when the file is visited and Emacs wants to decode its contents, and/or when the file's diff --git a/etc/NEWS b/etc/NEWS index 808cd0562db..52486b7bbe9 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -2163,6 +2163,11 @@ completion candidate. * Lisp Changes in Emacs 30.1 ++++ +** 'auto-coding-functions' can know the name of the file. +The functions on this hook take can now find the name of the file to +which the text belongs by consulting the variable 'auto-coding-file-name'. + +++ ** New user option 'compilation-safety' to control safety of native code. It's now possible to control how safe is the code generated by native diff --git a/lisp/international/mule.el b/lisp/international/mule.el index a17221e6d21..ed74fdae755 100644 --- a/lisp/international/mule.el +++ b/lisp/international/mule.el @@ -1806,6 +1806,9 @@ auto-coding-regexp-alist-lookup (setq alist (cdr alist))))) coding-system))) +(defvar auto-coding-file-name nil + "Variable holding the name of the file for `auto-coding-functions'.") + ;; See the bottom of this file for built-in auto coding functions. (defcustom auto-coding-functions '(sgml-xml-auto-coding-function sgml-html-meta-auto-coding-function) @@ -1820,6 +1823,9 @@ auto-coding-functions its contents, and when the file's buffer is about to be saved and Emacs wants to determine how to encode its contents. +The name of the file is provided to the function via the variable +`auto-coding-file-name'. + If one of these functions succeeds in determining a coding system, it should return that coding system. Otherwise, it should return nil. @@ -1847,13 +1853,17 @@ auto-coding-alist-lookup coding-system)) (put 'enable-character-translation 'permanent-local t) -(put 'enable-character-translation 'safe-local-variable 'booleanp) +(put 'enable-character-translation 'safe-local-variable #'booleanp) (defun find-auto-coding (filename size) + ;; FIXME: Shouldn't we use nil rather than "" to mean that there's no file? + ;; FIXME: Clarify what the SOURCE is for in the return value? "Find a coding system for a file FILENAME of which SIZE bytes follow point. These bytes should include at least the first 1k of the file and the last 3k of the file, but the middle may be omitted. +FILENAME should be an absolute file name +or \"\" (which means that there is no associated file). The function checks FILENAME against the variable `auto-coding-alist'. If FILENAME doesn't match any entries in the variable, it checks the contents of the current buffer following point against @@ -1998,7 +2008,8 @@ find-auto-coding (setq coding-system (ignore-errors (save-excursion (goto-char (point-min)) - (funcall (pop funcs) size))))) + (let ((auto-coding-file-name filename)) + (funcall (pop funcs) size)))))) (if coding-system (cons coding-system 'auto-coding-functions))))) @@ -2013,7 +2024,7 @@ set-auto-coding (if (and found (coding-system-p (car found))) (car found)))) -(setq set-auto-coding-function 'set-auto-coding) +(setq set-auto-coding-function #'set-auto-coding) (defun after-insert-file-set-coding (inserted &optional visit) "Set `buffer-file-coding-system' of current buffer after text is inserted. -- 2.39.2 --=-=-= Content-Type: text/x-diff Content-Disposition: inline; filename=0002-hack-dir-local-get-variables-functions-New-hook.patch >From 71ac0af5d0191cdf8ebc56066c7ca58dd93487cf Mon Sep 17 00:00:00 2001 From: Stefan Monnier Date: Tue, 4 Jun 2024 11:00:32 -0400 Subject: [PATCH 2/2] (hack-dir-local-get-variables-functions): New hook Make it possible to provide more dir-local variables, such as done by the Editorconfig package. * lisp/files.el (hack-dir-local--get-variables): Make arg optional. (hack-dir-local-get-variables-functions): New hook. (hack-dir-local-variables): Run it instead of calling `hack-dir-local--get-variables`. * doc/lispref/variables.texi (Directory Local Variables): Document the new hook. --- doc/lispref/variables.texi | 29 +++++++++++++++ etc/NEWS | 4 ++ lisp/files.el | 76 ++++++++++++++++++++++++++++++-------- 3 files changed, 93 insertions(+), 16 deletions(-) diff --git a/doc/lispref/variables.texi b/doc/lispref/variables.texi index e05d3bb0f81..c63057e31ea 100644 --- a/doc/lispref/variables.texi +++ b/doc/lispref/variables.texi @@ -2277,6 +2277,35 @@ Directory Local Variables updates this list. @end defvar +@defvar hack-dir-local-get-variables-functions +This special hook holds the functions that gather the directory-local +variables to use for a given buffer. By default it contains just the +function that obeys the other settings described in the present section. +But it can be used to add support for more sources of directory-local +variables, such as those used by other text editors. + +The functions on this hook are called with no argument, in the buffer to +which we intend to apply the directory-local variables, after the +buffer's major mode function has been run, so it can use sources of +information such as @code{major-mode} or @code{buffer-file-name} to find +the variables that should be applied. + +It should return either a cons cell of the form @code{(@var{directory} +. @var{alist})} or a list of such cons-cells. A @code{nil} return value +means that it found no directory-local variables. @var{directory} +should be a string: the name of the directory to which the variables +apply. @var{alist} is a list of variables together with their values +that apply to the current buffer, where every element is of the form +@code{(@var{varname} . @var{value})}. + +The various @var{alist} returned by these functions will be combined, +and in case of conflicts, the settings coming from deeper directories +will take precedence over those coming from higher directories in the +directory hierarchy. Finally, since this hook is run every time we visit +a file it is important to try and keep those functions efficient, which +will usually require some of caching. +@end defvar + @defvar enable-dir-local-variables If @code{nil}, directory-local variables are ignored. This variable may be useful for modes that want to ignore directory-locals while diff --git a/etc/NEWS b/etc/NEWS index 52486b7bbe9..14e5dafbadc 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -2163,6 +2163,10 @@ completion candidate. * Lisp Changes in Emacs 30.1 +** New hook 'hack-dir-local-get-variables-functions'. +This can be used to provide support for other directory-local settings +beside '.dir-locals.el'. + +++ ** 'auto-coding-functions' can know the name of the file. The functions on this hook take can now find the name of the file to diff --git a/lisp/files.el b/lisp/files.el index 210cd0fa7ad..a36ac6b1318 100644 --- a/lisp/files.el +++ b/lisp/files.el @@ -3494,6 +3494,8 @@ set-auto-mode ;; Check for auto-mode-alist entry in dir-locals. (with-demoted-errors "Directory-local variables error: %s" ;; Note this is a no-op if enable-local-variables is nil. + ;; We don't use `hack-dir-local-get-variables-functions' here, because + ;; modes are specific to Emacs. (let* ((mode-alist (cdr (hack-dir-local--get-variables (lambda (key) (eq key 'auto-mode-alist)))))) (set-auto-mode--apply-alist mode-alist keep-mode-if-same t))) @@ -4769,7 +4771,7 @@ enable-remote-dir-locals (defvar hack-dir-local-variables--warned-coding nil) -(defun hack-dir-local--get-variables (predicate) +(defun hack-dir-local--get-variables (&optional predicate) "Read per-directory local variables for the current buffer. Return a cons of the form (DIR . ALIST), where DIR is the directory name (maybe nil) and ALIST is an alist of all variables @@ -4799,6 +4801,16 @@ hack-dir-local--get-variables (dir-locals-get-class-variables class) dir-name nil predicate)))))) +(defvar hack-dir-local-get-variables-functions + (list #'hack-dir-local--get-variables) + "Special hook to compute the set of dir-local variables. +Every function is called without arguments and should return either +a cons of the form (DIR . ALIST) or a (possibly empty) list of such conses, +where ALIST is an alist of (VAR . VAL) settings. +DIR should be a string (a directory name) and is used to obey +`safe-local-variable-directories'. +This hook is run after the major mode has been setup.") + (defun hack-dir-local-variables () "Read per-directory local variables for the current buffer. Store the directory-local variables in `dir-local-variables-alist' @@ -4806,21 +4818,53 @@ hack-dir-local-variables This does nothing if either `enable-local-variables' or `enable-dir-local-variables' are nil." - (let* ((items (hack-dir-local--get-variables nil)) - (dir-name (car items)) - (variables (cdr items))) - (when variables - (dolist (elt variables) - (if (eq (car elt) 'coding) - (unless hack-dir-local-variables--warned-coding - (setq hack-dir-local-variables--warned-coding t) - (display-warning 'files - "Coding cannot be specified by dir-locals")) - (unless (memq (car elt) '(eval mode)) - (setq dir-local-variables-alist - (assq-delete-all (car elt) dir-local-variables-alist))) - (push elt dir-local-variables-alist))) - (hack-local-variables-filter variables dir-name)))) + (let (items) + (when (and enable-local-variables + enable-dir-local-variables + (or enable-remote-dir-locals + (not (file-remote-p (or (buffer-file-name) + default-directory))))) + (run-hook-wrapped 'hack-dir-local-get-variables-functions + (lambda (fun) + (let ((res (funcall fun))) + (cond + ((null res)) + ((consp (car-safe res)) + (setq items (append res items))) + (t (push res items))))))) + ;; Sort the entries from nearest dir to furthest dir. + (setq items (sort (nreverse items) + :key (lambda (x) (length (car-safe x))) :reverse t)) + ;; Filter out duplicates, preferring the settings from the nearest dir + ;; and from the first hook function. + (let ((seen nil)) + (dolist (item items) + (when seen ;; Special case seen=nil since it's the most common case. + (setcdr item (seq-filter (lambda (vv) (not (memq (car-safe vv) seen))) + (cdr item)))) + (setq seen (nconc (seq-difference (mapcar #'car (cdr item)) + '(eval mode)) + seen)))) + ;; Rather than a loop, maybe we should handle all the dirs + ;; "together", e.g. prompting the user only once. But if so, we'd + ;; probably want to also merge the prompt for file-local vars, + ;; which comes from the call to `hack-local-variables-filter' in + ;; `hack-local-variables'. + (dolist (item items) + (let ((dir-name (car items)) + (variables (cdr items))) + (when variables + (dolist (elt variables) + (if (eq (car elt) 'coding) + (unless hack-dir-local-variables--warned-coding + (setq hack-dir-local-variables--warned-coding t) + (display-warning 'files + "Coding cannot be specified by dir-locals")) + (unless (memq (car elt) '(eval mode)) + (setq dir-local-variables-alist + (assq-delete-all (car elt) dir-local-variables-alist))) + (push elt dir-local-variables-alist))) + (hack-local-variables-filter variables dir-name)))))) (defun hack-dir-local-variables-non-file-buffer () "Apply directory-local variables to a non-file buffer. -- 2.39.2 --=-=-=--