Merge remote-tracking branch 'origin/scratch/emacs-editorconfig'
* doc/emacs/custom.texi (EditorConfig support): New node. * lisp/editorconfig-conf-mode.el, lisp/editorconfig-core-handle.el, * lisp/editorconfig-core.el, lisp/editorconfig-fnmatch.el, * lisp/editorconfig-tools.el, lisp/editorconfig.el: New files.
This commit is contained in:
commit
45f6cfb89e
8 changed files with 1724 additions and 0 deletions
|
@ -1550,6 +1550,44 @@ variables are handled in the same way as unsafe file-local variables
|
|||
do not visit a file directly but perform work within a directory, such
|
||||
as Dired buffers (@pxref{Dired}).
|
||||
|
||||
@node EditorConfig support
|
||||
@subsubsection Per-Directory Variables via EditorConfig
|
||||
@cindex EditorConfig support
|
||||
|
||||
The EditorConfig standard is an alternative to the @file{.dir-locals.el}
|
||||
files, which can control only a very small number of variables, but
|
||||
has the advantage of being editor-neutral. Those settings are stored in
|
||||
files named @file{.editorconfig}.
|
||||
|
||||
If you want Emacs to obey those settings, you need to enable
|
||||
the @code{editorconfig-mode} minor mode. This is usually all that is
|
||||
needed: when the mode is activated, Emacs will look for @file{.editorconfig}
|
||||
files whenever a file is visited, just as it does for @file{.dir-locals.el}.
|
||||
When both @file{.editorconfig} and @file{.dir-locals.el} files are
|
||||
found, their settings are combined, and in case of a conflict, the
|
||||
setting coming from the closest file takes precedence.
|
||||
If they are equally close, @file{.dir-locals.el} takes precedence.
|
||||
In terms of security, those settings are subject to the same checks
|
||||
as those coming from @file{.dir-locals.el} (and also honor
|
||||
@code{enable-local-variables}).
|
||||
|
||||
The @code{indent_size} setting of the EditorConfig standard does not
|
||||
correspond to a fixed variable in Emacs, but instead needs to set
|
||||
different variables depending on the major mode. Ideally all major
|
||||
modes should set the corresponding @code{editorconfig-indent-size-vars},
|
||||
but if you use a major mode in which @code{indent_size} does not take
|
||||
effect because the major mode does not yet support it, you can customize
|
||||
the @code{editorconfig-indentation-alist} variable to tell Emacs which
|
||||
variables need to be set in that major mode.
|
||||
|
||||
Similarly, there are several different ways to trim whitespace at
|
||||
the end of lines. When the EditorConfig @code{trim_trailing_whitespace}
|
||||
setting is used, by default @code{editorconfig-mode} simply calls
|
||||
@code{delete-trailing-whitespace} every time you save your file.
|
||||
If you prefer some other behavior, You can customize
|
||||
@code{editorconfig-trim-whitespaces-mode} to the minor mode of
|
||||
your preference, such as @code{ws-butler-mode}.
|
||||
|
||||
@node Connection Variables
|
||||
@subsection Per-Connection Local Variables
|
||||
@cindex local variables, for all remote connections
|
||||
|
|
8
etc/NEWS
8
etc/NEWS
|
@ -1984,6 +1984,14 @@ to give to "ping".
|
|||
|
||||
* New Modes and Packages in Emacs 30.1
|
||||
|
||||
** New package EditorConfig.
|
||||
This package provides support for the EditorConfig standard,
|
||||
an editor-neutral way to provide directory local (project-wide) settings.
|
||||
It is enabled via a new global minor mode 'editorconfig-mode'
|
||||
which makes Emacs obey the '.editorconfig' files.
|
||||
There is also a new major mode 'editorconfig-conf-mode'
|
||||
to edit those configuration files.
|
||||
|
||||
+++
|
||||
** New package Track-Changes.
|
||||
This library is a layer of abstraction above 'before-change-functions'
|
||||
|
|
96
lisp/editorconfig-conf-mode.el
Normal file
96
lisp/editorconfig-conf-mode.el
Normal file
|
@ -0,0 +1,96 @@
|
|||
;;; editorconfig-conf-mode.el --- Major mode for editing .editorconfig files -*- lexical-binding: t -*-
|
||||
|
||||
;; Copyright (C) 2011-2024 Free Software Foundation, Inc.
|
||||
|
||||
;; Author: EditorConfig Team <editorconfig@googlegroups.com>
|
||||
|
||||
;; See
|
||||
;; https://github.com/editorconfig/editorconfig-emacs/graphs/contributors or
|
||||
;; https://github.com/editorconfig/editorconfig-emacs/blob/master/CONTRIBUTORS
|
||||
;; for the list of contributors.
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; GNU Emacs is free software: you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
|
||||
;; GNU Emacs is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
;; See the GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; Major mode for editing .editorconfig files.
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'conf-mode)
|
||||
|
||||
(defvar editorconfig-conf-mode-syntax-table
|
||||
(let ((table (make-syntax-table conf-unix-mode-syntax-table)))
|
||||
(modify-syntax-entry ?\; "<" table)
|
||||
table)
|
||||
"Syntax table in use in `editorconfig-conf-mode' buffers.")
|
||||
|
||||
(defvar editorconfig-conf-mode-abbrev-table nil
|
||||
"Abbrev table in use in `editorconfig-conf-mode' buffers.")
|
||||
(define-abbrev-table 'editorconfig-conf-mode-abbrev-table ())
|
||||
|
||||
;;;###autoload
|
||||
(define-derived-mode editorconfig-conf-mode conf-unix-mode "Conf[EditorConfig]"
|
||||
"Major mode for editing .editorconfig files."
|
||||
(set-variable 'indent-line-function 'indent-relative)
|
||||
(let ((key-property-list
|
||||
'("charset"
|
||||
"end_of_line"
|
||||
"file_type_emacs"
|
||||
"file_type_ext"
|
||||
"indent_size"
|
||||
"indent_style"
|
||||
"insert_final_newline"
|
||||
"max_line_length"
|
||||
"root"
|
||||
"tab_width"
|
||||
"trim_trailing_whitespace"))
|
||||
(key-value-list
|
||||
'("unset"
|
||||
"true"
|
||||
"false"
|
||||
"lf"
|
||||
"cr"
|
||||
"crlf"
|
||||
"space"
|
||||
"tab"
|
||||
"latin1"
|
||||
"utf-8"
|
||||
"utf-8-bom"
|
||||
"utf-16be"
|
||||
"utf-16le"))
|
||||
(font-lock-value
|
||||
'(("^[ \t]*\\[\\(.+?\\)\\]" 1 font-lock-type-face)
|
||||
("^[ \t]*\\(.+?\\)[ \t]*[=:]" 1 font-lock-variable-name-face))))
|
||||
|
||||
;; Highlight all key values
|
||||
(dolist (key-value key-value-list)
|
||||
(push `(,(format "[=:][ \t]*\\(%s\\)\\([ \t]\\|$\\)" key-value)
|
||||
1 font-lock-constant-face)
|
||||
font-lock-value))
|
||||
;; Highlight all key properties
|
||||
(dolist (key-property key-property-list)
|
||||
(push `(,(format "^[ \t]*\\(%s\\)[ \t]*[=:]" key-property)
|
||||
1 font-lock-builtin-face)
|
||||
font-lock-value))
|
||||
|
||||
(conf-mode-initialize "#" font-lock-value)))
|
||||
|
||||
;;;###autoload
|
||||
(add-to-list 'auto-mode-alist '("\\.editorconfig\\'" . editorconfig-conf-mode))
|
||||
|
||||
(provide 'editorconfig-conf-mode)
|
||||
;;; editorconfig-conf-mode.el ends here
|
216
lisp/editorconfig-core-handle.el
Normal file
216
lisp/editorconfig-core-handle.el
Normal file
|
@ -0,0 +1,216 @@
|
|||
;;; editorconfig-core-handle.el --- Handle Class for EditorConfig File -*- lexical-binding: t -*-
|
||||
|
||||
;; Copyright (C) 2011-2024 Free Software Foundation, Inc.
|
||||
|
||||
;; Author: EditorConfig Team <editorconfig@googlegroups.com>
|
||||
|
||||
;; See
|
||||
;; https://github.com/editorconfig/editorconfig-emacs/graphs/contributors or
|
||||
;; https://github.com/editorconfig/editorconfig-emacs/blob/master/CONTRIBUTORS
|
||||
;; for the list of contributors.
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; GNU Emacs is free software: you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
|
||||
;; GNU Emacs is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
;; See the GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; Handle structures for EditorConfig config file. This library is used
|
||||
;; internally from editorconfig-core.el .
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'cl-lib)
|
||||
|
||||
(require 'editorconfig-fnmatch)
|
||||
|
||||
(defvar editorconfig-core-handle--cache-hash
|
||||
(make-hash-table :test 'equal)
|
||||
"Hash of EditorConfig filename and its `editorconfig-core-handle' instance.")
|
||||
|
||||
(cl-defstruct editorconfig-core-handle-section
|
||||
"Structure representing one section in a .editorconfig file.
|
||||
|
||||
Slots:
|
||||
|
||||
`name'
|
||||
String of section name (glob string).
|
||||
|
||||
`props'
|
||||
Alist of properties: (KEY . VALUE)."
|
||||
(name nil)
|
||||
(props nil))
|
||||
|
||||
(defun editorconfig-core-handle-section-get-properties (section file)
|
||||
"Return properties alist when SECTION name match FILE.
|
||||
|
||||
FILE should be a relative file name, relative to the directory where
|
||||
the `.editorconfig' file which has SECTION lives.
|
||||
If not match, return nil."
|
||||
(when (editorconfig-core-handle--fnmatch-p
|
||||
file (editorconfig-core-handle-section-name section))
|
||||
(editorconfig-core-handle-section-props section)))
|
||||
|
||||
(cl-defstruct editorconfig-core-handle
|
||||
"Structure representing an .editorconfig file.
|
||||
|
||||
Slots:
|
||||
`top-props'
|
||||
Alist of top properties like ((\"root\" . \"true\"))
|
||||
|
||||
`sections'
|
||||
List of `editorconfig-core-handle-section' structure objects.
|
||||
|
||||
`mtime'
|
||||
Last modified time of .editorconfig file.
|
||||
|
||||
`path'
|
||||
Absolute path to .editorconfig file."
|
||||
(top-props nil)
|
||||
(sections nil)
|
||||
(mtime nil)
|
||||
(path nil))
|
||||
|
||||
|
||||
(defun editorconfig-core-handle (conf)
|
||||
"Return EditorConfig handle for CONF, which should be a file path.
|
||||
|
||||
If CONF does not exist return nil."
|
||||
(when (file-readable-p conf)
|
||||
(let ((cached (gethash conf editorconfig-core-handle--cache-hash))
|
||||
(mtime (nth 5 (file-attributes conf))))
|
||||
(if (and cached
|
||||
(equal (editorconfig-core-handle-mtime cached) mtime))
|
||||
cached
|
||||
(let ((parsed (editorconfig-core-handle--parse-file conf)))
|
||||
(puthash conf parsed editorconfig-core-handle--cache-hash))))))
|
||||
|
||||
(defun editorconfig-core-handle-root-p (handle)
|
||||
"Return non-nil if HANDLE represent root EditorConfig file.
|
||||
|
||||
If HANDLE is nil return nil."
|
||||
(when handle
|
||||
(string-equal "true"
|
||||
(downcase (or (cdr (assoc "root"
|
||||
(editorconfig-core-handle-top-props handle)))
|
||||
"")))))
|
||||
|
||||
(defun editorconfig-core-handle-get-properties (handle file)
|
||||
"Return list of alist of properties from HANDLE for FILE.
|
||||
The list returned will be ordered by the lines they appear.
|
||||
|
||||
If HANDLE is nil return nil."
|
||||
(declare (obsolete editorconfig-core-handle-get-properties-hash "0.8.0"))
|
||||
(when handle
|
||||
(let* ((dir (file-name-directory (editorconfig-core-handle-path handle)))
|
||||
(file (file-relative-name file dir)))
|
||||
(cl-loop for section in (editorconfig-core-handle-sections handle)
|
||||
for props = (editorconfig-core-handle-section-get-properties
|
||||
section file)
|
||||
when props collect (copy-alist props)))))
|
||||
|
||||
|
||||
(defun editorconfig-core-handle-get-properties-hash (handle file)
|
||||
"Return hash of properties from HANDLE for FILE.
|
||||
|
||||
If HANDLE is nil return nil."
|
||||
(when handle
|
||||
(let ((hash (make-hash-table))
|
||||
(file (file-relative-name file (editorconfig-core-handle-path
|
||||
handle))))
|
||||
(dolist (section (editorconfig-core-handle-sections handle))
|
||||
(cl-loop for (key . value) in (editorconfig-core-handle-section-get-properties section file)
|
||||
do (puthash (intern key) value hash)))
|
||||
hash)))
|
||||
|
||||
(defun editorconfig-core-handle--fnmatch-p (name pattern)
|
||||
"Return non-nil if NAME match PATTERN.
|
||||
If pattern has slash, pattern should be relative to DIR.
|
||||
|
||||
This function is a fnmatch with a few modification for EditorConfig usage."
|
||||
(if (string-match-p "/" pattern)
|
||||
(let ((pattern (replace-regexp-in-string "\\`/" "" pattern)))
|
||||
(editorconfig-fnmatch-p name pattern))
|
||||
(editorconfig-fnmatch-p (file-name-nondirectory name) pattern)))
|
||||
|
||||
(defun editorconfig-core-handle--parse-file (conf)
|
||||
"Parse EditorConfig file CONF.
|
||||
|
||||
This function returns a `editorconfig-core-handle'.
|
||||
If CONF is not found return nil."
|
||||
(when (file-readable-p conf)
|
||||
(with-temp-buffer
|
||||
;; NOTE: Use this instead of insert-file-contents-literally to enable
|
||||
;; code conversion
|
||||
(insert-file-contents conf)
|
||||
(goto-char (point-min))
|
||||
(let ((sections ())
|
||||
(top-props nil)
|
||||
|
||||
;; nil when pattern not appeared yet, "" when pattern is empty ("[]")
|
||||
(pattern nil)
|
||||
;; Alist of properties for current PATTERN
|
||||
(props ())
|
||||
|
||||
;; Current line num
|
||||
(current-line-number 1))
|
||||
(while (not (eobp))
|
||||
(skip-chars-forward " \t\f")
|
||||
(cond
|
||||
((looking-at "\\(?:[#;].*\\)?$")
|
||||
nil)
|
||||
|
||||
;; Start of section
|
||||
((looking-at "\\[\\(.*\\)\\][ \t]*$")
|
||||
(let ((newpattern (match-string 1)))
|
||||
(when pattern
|
||||
(push (make-editorconfig-core-handle-section
|
||||
:name pattern
|
||||
:props (nreverse props))
|
||||
sections))
|
||||
(setq props nil)
|
||||
(setq pattern newpattern)))
|
||||
|
||||
((looking-at "\\([^=: \t]+\\)[ \t]*[=:][ \t]*\\(.*?\\)[ \t]*$")
|
||||
(let ((key (downcase (match-string 1)))
|
||||
(value (match-string 2)))
|
||||
(when (and (< (length key) 51)
|
||||
(< (length value) 256))
|
||||
(if pattern
|
||||
(when (< (length pattern) 4097) ;;FIXME: 4097?
|
||||
(push `(,key . ,value)
|
||||
props))
|
||||
(push `(,key . ,value)
|
||||
top-props)))))
|
||||
|
||||
(t (error "Error while reading config file: %s:%d:\n %s\n"
|
||||
conf current-line-number
|
||||
(buffer-substring-no-properties (line-beginning-position)
|
||||
(line-end-position)))))
|
||||
(setq current-line-number (1+ current-line-number))
|
||||
(goto-char (point-min))
|
||||
(forward-line (1- current-line-number)))
|
||||
(when pattern
|
||||
(push (make-editorconfig-core-handle-section
|
||||
:name pattern
|
||||
:props (nreverse props))
|
||||
sections))
|
||||
(make-editorconfig-core-handle
|
||||
:top-props (nreverse top-props)
|
||||
:sections (nreverse sections)
|
||||
:mtime (nth 5 (file-attributes conf))
|
||||
:path conf)))))
|
||||
|
||||
(provide 'editorconfig-core-handle)
|
||||
;;; editorconfig-core-handle.el ends here
|
157
lisp/editorconfig-core.el
Normal file
157
lisp/editorconfig-core.el
Normal file
|
@ -0,0 +1,157 @@
|
|||
;;; editorconfig-core.el --- EditorConfig Core library -*- lexical-binding: t -*-
|
||||
|
||||
;; Copyright (C) 2011-2024 Free Software Foundation, Inc.
|
||||
|
||||
;; Author: EditorConfig Team <editorconfig@googlegroups.com>
|
||||
|
||||
;; See
|
||||
;; https://github.com/editorconfig/editorconfig-emacs/graphs/contributors or
|
||||
;; https://github.com/editorconfig/editorconfig-emacs/blob/master/CONTRIBUTORS
|
||||
;; for the list of contributors.
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; GNU Emacs is free software: you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
|
||||
;; GNU Emacs is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; This library is one implementation of EditorConfig Core, which parses
|
||||
;; .editorconfig files and returns properties for given files.
|
||||
;; This can be used in place of, for example, editorconfig-core-c.
|
||||
|
||||
|
||||
;; Use from EditorConfig Emacs Plugin
|
||||
|
||||
;; Emacs plugin (v0.5 or later) can utilize this implementation.
|
||||
;; By default, the plugin first search for any EditorConfig executable,
|
||||
;; and fallback to this library if not found.
|
||||
;; If you always want to use this library, add following lines to your init.el:
|
||||
|
||||
;; (setq editorconfig-get-properties-function
|
||||
;; 'editorconfig-core-get-properties-hash)
|
||||
|
||||
|
||||
;; Functions
|
||||
|
||||
;; editorconfig-core-get-properties-hash (&optional file confname confversion)
|
||||
|
||||
;; Get EditorConfig properties for FILE.
|
||||
|
||||
;; This function is almost same as `editorconfig-core-get-properties', but
|
||||
;; returns hash object instead.
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'cl-lib)
|
||||
|
||||
(require 'editorconfig-core-handle)
|
||||
|
||||
(eval-when-compile
|
||||
(require 'subr-x))
|
||||
|
||||
|
||||
(defun editorconfig-core--get-handles (dir confname &optional result)
|
||||
"Get list of EditorConfig handlers for DIR from CONFNAME.
|
||||
|
||||
In the resulting list, the handle for root config file comes first, and the
|
||||
nearest comes last.
|
||||
The list may contains nil when no file was found for directories.
|
||||
RESULT is used internally and normally should not be used."
|
||||
(setq dir (expand-file-name dir))
|
||||
(let ((handle (editorconfig-core-handle (concat (file-name-as-directory dir)
|
||||
confname)))
|
||||
(parent (file-name-directory (directory-file-name dir))))
|
||||
(if (or (string= parent dir)
|
||||
(and handle (editorconfig-core-handle-root-p handle)))
|
||||
(cl-remove-if-not #'identity (cons handle result))
|
||||
(editorconfig-core--get-handles parent
|
||||
confname
|
||||
(cons handle result)))))
|
||||
|
||||
(defun editorconfig-core-get-nearest-editorconfig (directory)
|
||||
"Return path to .editorconfig file that is closest to DIRECTORY."
|
||||
(when-let* ((handle (car (last
|
||||
(editorconfig-core--get-handles directory
|
||||
".editorconfig")))))
|
||||
(editorconfig-core-handle-path handle)))
|
||||
|
||||
(defun editorconfig-core--hash-merge (into update)
|
||||
"Merge two hashes INTO and UPDATE.
|
||||
|
||||
This is a destructive function, hash INTO will be modified.
|
||||
When the same key exists in both two hashes, values of UPDATE takes precedence."
|
||||
(maphash (lambda (key value) (puthash key value into)) update)
|
||||
into)
|
||||
|
||||
(defun editorconfig-core-get-properties-hash (&optional file confname confversion)
|
||||
"Get EditorConfig properties for FILE.
|
||||
If FILE is not given, use currently visiting file.
|
||||
Give CONFNAME for basename of config file other than .editorconfig.
|
||||
If need to specify config format version, give CONFVERSION.
|
||||
|
||||
This function is almost same as `editorconfig-core-get-properties', but returns
|
||||
hash object instead."
|
||||
(setq file
|
||||
(expand-file-name (or file
|
||||
buffer-file-name
|
||||
(error "FILE is not given and `buffer-file-name' is nil"))))
|
||||
(setq confname (or confname ".editorconfig"))
|
||||
(setq confversion (or confversion "0.12.0"))
|
||||
(let ((result (make-hash-table)))
|
||||
(dolist (handle (editorconfig-core--get-handles (file-name-directory file)
|
||||
confname))
|
||||
(editorconfig-core--hash-merge result
|
||||
(editorconfig-core-handle-get-properties-hash handle
|
||||
file)))
|
||||
|
||||
;; Downcase known boolean values
|
||||
;; FIXME: Why not do that in `editorconfig-core-handle--parse-file'?
|
||||
(dolist (key '( end_of_line indent_style indent_size insert_final_newline
|
||||
trim_trailing_whitespace charset))
|
||||
(when-let* ((val (gethash key result)))
|
||||
(puthash key (downcase val) result)))
|
||||
|
||||
;; Add indent_size property
|
||||
;; FIXME: Why? Which part of the spec requires that?
|
||||
;;(let ((v-indent-size (gethash 'indent_size result))
|
||||
;; (v-indent-style (gethash 'indent_style result)))
|
||||
;; (when (and (not v-indent-size)
|
||||
;; (string= v-indent-style "tab")
|
||||
;; ;; If VERSION < 0.9.0, indent_size should have no default value
|
||||
;; (version<= "0.9.0"
|
||||
;; confversion))
|
||||
;; (puthash 'indent_size
|
||||
;; "tab"
|
||||
;; result)))
|
||||
;; Add tab_width property
|
||||
;; FIXME: Why? Which part of the spec requires that?
|
||||
;;(let ((v-indent-size (gethash 'indent_size result))
|
||||
;; (v-tab-width (gethash 'tab_width result)))
|
||||
;; (when (and v-indent-size
|
||||
;; (not v-tab-width)
|
||||
;; (not (string= v-indent-size "tab")))
|
||||
;; (puthash 'tab_width v-indent-size result)))
|
||||
;; Update indent-size property
|
||||
;; FIXME: Why? Which part of the spec requires that?
|
||||
;;(let ((v-indent-size (gethash 'indent_size result))
|
||||
;; (v-tab-width (gethash 'tab_width result)))
|
||||
;; (when (and v-indent-size
|
||||
;; v-tab-width
|
||||
;; (string= v-indent-size "tab"))
|
||||
;; (puthash 'indent_size v-tab-width result)))
|
||||
|
||||
result))
|
||||
|
||||
(provide 'editorconfig-core)
|
||||
;;; editorconfig-core.el ends here
|
293
lisp/editorconfig-fnmatch.el
Normal file
293
lisp/editorconfig-fnmatch.el
Normal file
|
@ -0,0 +1,293 @@
|
|||
;;; editorconfig-fnmatch.el --- Glob pattern matching -*- lexical-binding: t -*-
|
||||
|
||||
;; Copyright (C) 2011-2024 Free Software Foundation, Inc.
|
||||
|
||||
;; Author: EditorConfig Team <editorconfig@googlegroups.com>
|
||||
|
||||
;; See
|
||||
;; https://github.com/editorconfig/editorconfig-emacs/graphs/contributors or
|
||||
;; https://github.com/editorconfig/editorconfig-emacs/blob/master/CONTRIBUTORS
|
||||
;; for the list of contributors.
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; GNU Emacs is free software: you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
|
||||
;; GNU Emacs is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
;; See the GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; editorconfig-fnmatch.el provides a fnmatch implementation with a few
|
||||
;; extensions.
|
||||
;; The main usage of this library is glob pattern matching for EditorConfig, but
|
||||
;; it can also act solely.
|
||||
|
||||
;; editorconfig-fnmatch-p (name pattern)
|
||||
|
||||
;; Test whether NAME match PATTERN.
|
||||
|
||||
;; PATTERN should be a shell glob pattern, and some zsh-like wildcard matchings
|
||||
;; can be used:
|
||||
|
||||
;; * Matches any string of characters, except path separators (/)
|
||||
;; ** Matches any string of characters
|
||||
;; ? Matches any single character
|
||||
;; [name] Matches any single character in name
|
||||
;; [^name] Matches any single character not in name
|
||||
;; {s1,s2,s3} Matches any of the strings given (separated by commas)
|
||||
;; {min..max} Matches any number between min and max
|
||||
|
||||
|
||||
;; This library is a port from editorconfig-core-py library.
|
||||
;; https://github.com/editorconfig/editorconfig-core-py/blob/master/editorconfig/fnmatch.py
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'cl-lib)
|
||||
|
||||
(defvar editorconfig-fnmatch--cache-hashtable
|
||||
nil
|
||||
"Cache of shell pattern and its translation.")
|
||||
;; Clear cache on file reload
|
||||
(setq editorconfig-fnmatch--cache-hashtable
|
||||
(make-hash-table :test 'equal))
|
||||
|
||||
|
||||
(defconst editorconfig-fnmatch--left-brace-regexp
|
||||
"\\(^\\|[^\\]\\){"
|
||||
"Regular expression for left brace ({).")
|
||||
|
||||
(defconst editorconfig-fnmatch--right-brace-regexp
|
||||
"\\(^\\|[^\\]\\)}"
|
||||
"Regular expression for right brace (}).")
|
||||
|
||||
|
||||
(defconst editorconfig-fnmatch--numeric-range-regexp
|
||||
"\\([+-]?[0-9]+\\)\\.\\.\\([+-]?[0-9]+\\)"
|
||||
"Regular expression for numeric range (like {-3..+3}).")
|
||||
|
||||
(defun editorconfig-fnmatch--match-num (regexp string)
|
||||
"Return how many times REGEXP is found in STRING."
|
||||
(let ((num 0))
|
||||
;; START arg does not work as expected in this case
|
||||
(while (string-match regexp string)
|
||||
(setq num (1+ num)
|
||||
string (substring string (match-end 0))))
|
||||
num))
|
||||
|
||||
(defun editorconfig-fnmatch-p (string pattern)
|
||||
"Test whether STRING match PATTERN.
|
||||
|
||||
Matching ignores case if `case-fold-search' is non-nil.
|
||||
|
||||
PATTERN should be a shell glob pattern, and some zsh-like wildcard matchings can
|
||||
be used:
|
||||
|
||||
* Matches any string of characters, except path separators (/)
|
||||
** Matches any string of characters
|
||||
? Matches any single character
|
||||
[name] Matches any single character in name
|
||||
[^name] Matches any single character not in name
|
||||
{s1,s2,s3} Matches any of the strings given (separated by commas)
|
||||
{min..max} Matches any number between min and max"
|
||||
(string-match (editorconfig-fnmatch-translate pattern)
|
||||
string))
|
||||
|
||||
;;(editorconfig-fnmatch-translate "{a,{-3..3}}.js")
|
||||
;;(editorconfig-fnmatch-p "1.js" "{a,{-3..3}}.js")
|
||||
|
||||
(defun editorconfig-fnmatch-translate (pattern)
|
||||
"Translate a shell PATTERN to a regular expression.
|
||||
|
||||
Translation result will be cached, so same translation will not be done twice."
|
||||
(let ((cached (gethash pattern
|
||||
editorconfig-fnmatch--cache-hashtable)))
|
||||
(or cached
|
||||
(puthash pattern
|
||||
(editorconfig-fnmatch--do-translate pattern)
|
||||
editorconfig-fnmatch--cache-hashtable))))
|
||||
|
||||
|
||||
(defun editorconfig-fnmatch--do-translate (pattern &optional nested)
|
||||
"Translate a shell PATTERN to a regular expression.
|
||||
|
||||
Set NESTED to t when this function is called from itself.
|
||||
|
||||
This function is called from `editorconfig-fnmatch-translate', when no cached
|
||||
translation is found for PATTERN."
|
||||
(let ((index 0)
|
||||
(length (length pattern))
|
||||
(brace-level 0)
|
||||
(in-brackets nil)
|
||||
;; List of strings of resulting regexp, in reverse order.
|
||||
(result ())
|
||||
(is-escaped nil)
|
||||
(matching-braces (= (editorconfig-fnmatch--match-num
|
||||
editorconfig-fnmatch--left-brace-regexp
|
||||
pattern)
|
||||
(editorconfig-fnmatch--match-num
|
||||
editorconfig-fnmatch--right-brace-regexp
|
||||
pattern)))
|
||||
|
||||
current-char
|
||||
pos
|
||||
has-slash
|
||||
has-comma
|
||||
num-range)
|
||||
|
||||
(while (< index length)
|
||||
(if (and (not is-escaped)
|
||||
(string-match "[^]\\*?[{},/\\-]+"
|
||||
;;(string-match "[^]\\*?[{},/\\-]+" "?.a")
|
||||
pattern
|
||||
index)
|
||||
(eq index (match-beginning 0)))
|
||||
(progn
|
||||
(push (regexp-quote (match-string 0 pattern)) result)
|
||||
(setq index (match-end 0)
|
||||
is-escaped nil))
|
||||
|
||||
(setq current-char (aref pattern index)
|
||||
index (1+ index))
|
||||
|
||||
(cl-case current-char
|
||||
(?*
|
||||
(setq pos index)
|
||||
(if (and (< pos length)
|
||||
(= (aref pattern pos) ?*))
|
||||
(push ".*" result)
|
||||
(push "[^/]*" result)))
|
||||
|
||||
(??
|
||||
(push "[^/]" result))
|
||||
|
||||
(?\[
|
||||
(if in-brackets
|
||||
(push "\\[" result)
|
||||
(if (= (aref pattern index) ?/)
|
||||
;; Slash after an half-open bracket
|
||||
(progn
|
||||
(push "\\[/" result)
|
||||
(setq index (+ index 1)))
|
||||
(setq pos index
|
||||
has-slash nil)
|
||||
(while (and (< pos length)
|
||||
(not (= (aref pattern pos) ?\]))
|
||||
(not has-slash))
|
||||
(if (and (= (aref pattern pos) ?/)
|
||||
(not (= (aref pattern (- pos 1)) ?\\)))
|
||||
(setq has-slash t)
|
||||
(setq pos (1+ pos))))
|
||||
(if has-slash
|
||||
(progn
|
||||
(push (concat "\\["
|
||||
(substring pattern
|
||||
index
|
||||
(1+ pos))
|
||||
"\\]")
|
||||
result)
|
||||
(setq index (+ pos 2)))
|
||||
(if (and (< index length)
|
||||
(memq (aref pattern index)
|
||||
'(?! ?^)))
|
||||
(progn
|
||||
(setq index (1+ index))
|
||||
(push "[^" result))
|
||||
(push "[" result))
|
||||
(setq in-brackets t)))))
|
||||
|
||||
(?-
|
||||
(if in-brackets
|
||||
(push "-" result)
|
||||
(push "\\-" result)))
|
||||
|
||||
(?\]
|
||||
(push "]" result)
|
||||
(setq in-brackets nil))
|
||||
|
||||
(?{
|
||||
(setq pos index
|
||||
has-comma nil)
|
||||
(while (and (or (and (< pos length)
|
||||
(not (= (aref pattern pos) ?})))
|
||||
is-escaped)
|
||||
(not has-comma))
|
||||
(if (and (eq (aref pattern pos) ?,)
|
||||
(not is-escaped))
|
||||
(setq has-comma t)
|
||||
(setq is-escaped (and (eq (aref pattern pos)
|
||||
?\\)
|
||||
(not is-escaped))
|
||||
pos (1+ pos))))
|
||||
(if (and (not has-comma)
|
||||
(< pos length))
|
||||
(let ((pattern-sub (substring pattern index pos)))
|
||||
(setq num-range (string-match editorconfig-fnmatch--numeric-range-regexp
|
||||
pattern-sub))
|
||||
(if num-range
|
||||
(let ((number-start (string-to-number (match-string 1
|
||||
pattern-sub)))
|
||||
(number-end (string-to-number (match-string 2
|
||||
pattern-sub))))
|
||||
(push (concat "\\(?:"
|
||||
(mapconcat #'number-to-string
|
||||
(cl-loop for i from number-start to number-end
|
||||
collect i)
|
||||
"\\|")
|
||||
"\\)")
|
||||
result))
|
||||
(let ((inner (editorconfig-fnmatch--do-translate pattern-sub t)))
|
||||
(push (format "{%s}" inner) result)))
|
||||
(setq index (1+ pos)))
|
||||
(if matching-braces
|
||||
(progn
|
||||
(push "\\(?:" result)
|
||||
(setq brace-level (1+ brace-level)))
|
||||
(push "{" result))))
|
||||
|
||||
(?,
|
||||
(if (and (> brace-level 0)
|
||||
(not is-escaped))
|
||||
(push "\\|" result)
|
||||
(push "\\," result)))
|
||||
|
||||
(?}
|
||||
(if (and (> brace-level 0)
|
||||
(not is-escaped))
|
||||
(progn
|
||||
(push "\\)" result)
|
||||
(setq brace-level (- brace-level 1)))
|
||||
(push "}" result)))
|
||||
|
||||
(?/
|
||||
(if (and (<= (+ index 3) (length pattern))
|
||||
(string= (substring pattern index (+ index 3)) "**/"))
|
||||
(progn
|
||||
(push "\\(?:/\\|/.*/\\)" result)
|
||||
(setq index (+ index 3)))
|
||||
(push "/" result)))
|
||||
|
||||
(t
|
||||
(unless (= current-char ?\\)
|
||||
(push (regexp-quote (char-to-string current-char)) result))))
|
||||
|
||||
(if (= current-char ?\\)
|
||||
(progn (when is-escaped
|
||||
(push "\\\\" result))
|
||||
(setq is-escaped (not is-escaped)))
|
||||
(setq is-escaped nil))))
|
||||
(unless nested
|
||||
(setq result `("\\'" ,@result "\\`")))
|
||||
(apply #'concat (reverse result))))
|
||||
|
||||
(provide 'editorconfig-fnmatch)
|
||||
;;; editorconfig-fnmatch.el ends here
|
120
lisp/editorconfig-tools.el
Normal file
120
lisp/editorconfig-tools.el
Normal file
|
@ -0,0 +1,120 @@
|
|||
;;; editorconfig-tools.el --- Editorconfig tools -*- lexical-binding: t -*-
|
||||
|
||||
;; Copyright (C) 2011-2024 Free Software Foundation, Inc.
|
||||
|
||||
;; Author: EditorConfig Team <editorconfig@googlegroups.com>
|
||||
|
||||
;; See
|
||||
;; https://github.com/editorconfig/editorconfig-emacs/graphs/contributors or
|
||||
;; https://github.com/editorconfig/editorconfig-emacs/blob/master/CONTRIBUTORS
|
||||
;; for the list of contributors.
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; GNU Emacs is free software: you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
|
||||
;; GNU Emacs is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
;; See the GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; Some utility commands for users, not used from editorconfig-mode.
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'cl-lib)
|
||||
|
||||
(eval-when-compile
|
||||
(require 'subr-x))
|
||||
|
||||
|
||||
(require 'editorconfig)
|
||||
|
||||
;;;###autoload
|
||||
(defun editorconfig-apply ()
|
||||
"Get and apply EditorConfig properties to current buffer.
|
||||
|
||||
This function does not respect the values of `editorconfig-exclude-modes' and
|
||||
`editorconfig-exclude-regexps' and always applies available properties.
|
||||
Use `editorconfig-mode-apply' instead to make use of these variables."
|
||||
(interactive)
|
||||
(when buffer-file-name
|
||||
(condition-case err
|
||||
(progn
|
||||
(let ((props (editorconfig-call-get-properties-function buffer-file-name)))
|
||||
(condition-case err
|
||||
(run-hook-with-args 'editorconfig-hack-properties-functions props)
|
||||
(error
|
||||
(display-warning '(editorconfig editorconfig-hack-properties-functions)
|
||||
(format "Error while running editorconfig-hack-properties-functions, abort running hook: %S"
|
||||
err)
|
||||
:warning)))
|
||||
(setq editorconfig-properties-hash props)
|
||||
(editorconfig-set-local-variables props)
|
||||
(editorconfig-set-coding-system-revert
|
||||
(gethash 'end_of_line props)
|
||||
(gethash 'charset props))
|
||||
(condition-case err
|
||||
(run-hook-with-args 'editorconfig-after-apply-functions props)
|
||||
(error
|
||||
(display-warning '(editorconfig editorconfig-after-apply-functions)
|
||||
(format "Error while running editorconfig-after-apply-functions, abort running hook: %S"
|
||||
err)
|
||||
:warning)))))
|
||||
(error
|
||||
(display-warning '(editorconfig editorconfig-apply)
|
||||
(format "Error in editorconfig-apply, styles will not be applied: %S" err)
|
||||
:error)))))
|
||||
|
||||
(defun editorconfig-mode-apply ()
|
||||
"Get and apply EditorConfig properties to current buffer.
|
||||
|
||||
This function does nothing when the major mode is listed in
|
||||
`editorconfig-exclude-modes', or variable `buffer-file-name' matches
|
||||
any of regexps in `editorconfig-exclude-regexps'."
|
||||
(interactive)
|
||||
(when (and major-mode buffer-file-name)
|
||||
(editorconfig-apply)))
|
||||
|
||||
|
||||
;;;###autoload
|
||||
(defun editorconfig-find-current-editorconfig ()
|
||||
"Find the closest .editorconfig file for current file."
|
||||
(interactive)
|
||||
(eval-and-compile (require 'editorconfig-core))
|
||||
(when-let* ((file (editorconfig-core-get-nearest-editorconfig
|
||||
default-directory)))
|
||||
(find-file file)))
|
||||
|
||||
;;;###autoload
|
||||
(defun editorconfig-display-current-properties ()
|
||||
"Display EditorConfig properties extracted for current buffer."
|
||||
(interactive)
|
||||
(if editorconfig-properties-hash
|
||||
(let ((buf (get-buffer-create "*EditorConfig Properties*"))
|
||||
(file buffer-file-name)
|
||||
(props editorconfig-properties-hash))
|
||||
(with-current-buffer buf
|
||||
(erase-buffer)
|
||||
(insert (format "# EditorConfig for %s\n" file))
|
||||
(maphash (lambda (k v)
|
||||
(insert (format "%S = %s\n" k v)))
|
||||
props))
|
||||
(display-buffer buf))
|
||||
(message "Properties are not applied to current buffer yet.")
|
||||
nil))
|
||||
;;;###autoload
|
||||
(defalias 'describe-editorconfig-properties
|
||||
#'editorconfig-display-current-properties)
|
||||
|
||||
|
||||
(provide 'editorconfig-tools)
|
||||
;;; editorconfig-tools.el ends here
|
796
lisp/editorconfig.el
Normal file
796
lisp/editorconfig.el
Normal file
|
@ -0,0 +1,796 @@
|
|||
;;; editorconfig.el --- EditorConfig Plugin -*- lexical-binding: t -*-
|
||||
|
||||
;; Copyright (C) 2011-2024 Free Software Foundation, Inc.
|
||||
|
||||
;; Author: EditorConfig Team <editorconfig@googlegroups.com>
|
||||
;; Version: 0.11.0
|
||||
;; URL: https://github.com/editorconfig/editorconfig-emacs#readme
|
||||
;; Package-Requires: ((emacs "26.1"))
|
||||
;; Keywords: convenience editorconfig
|
||||
|
||||
;; See
|
||||
;; https://github.com/editorconfig/editorconfig-emacs/graphs/contributors or
|
||||
;; https://github.com/editorconfig/editorconfig-emacs/blob/master/CONTRIBUTORS
|
||||
;; for the list of contributors.
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; GNU Emacs is free software: you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
|
||||
;; GNU Emacs is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
;; See the GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; EditorConfig helps developers define and maintain consistent
|
||||
;; coding styles between different editors and IDEs.
|
||||
|
||||
;; The EditorConfig project consists of a file format for defining
|
||||
;; coding styles and a collection of text editor plugins that enable
|
||||
;; editors to read the file format and adhere to defined styles.
|
||||
;; EditorConfig files are easily readable and they work nicely with
|
||||
;; version control systems.
|
||||
|
||||
;;; News:
|
||||
|
||||
;; - In `editorconfig-indentation-alist', if a mode is associated to a function
|
||||
;; that function should not set the vars but should instead *return* them.
|
||||
;; - New var `editorconfig-indent-size-vars' for major modes to set.
|
||||
;; - New hook `editorconfig-get-local-variables-functions' to support
|
||||
;; additional settings.
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'cl-lib)
|
||||
|
||||
(eval-when-compile (require 'subr-x))
|
||||
|
||||
(require 'editorconfig-core)
|
||||
|
||||
(defgroup editorconfig nil
|
||||
"EditorConfig Emacs Plugin.
|
||||
|
||||
EditorConfig helps developers define and maintain consistent
|
||||
coding styles between different editors and IDEs."
|
||||
:tag "EditorConfig"
|
||||
:prefix "editorconfig-"
|
||||
:group 'tools)
|
||||
|
||||
(when (< emacs-major-version 30)
|
||||
(define-obsolete-variable-alias
|
||||
'edconf-custom-hooks
|
||||
'editorconfig-after-apply-functions
|
||||
"0.5")
|
||||
(define-obsolete-variable-alias
|
||||
'editorconfig-custom-hooks
|
||||
'editorconfig-after-apply-functions
|
||||
"0.7.14")
|
||||
(defcustom editorconfig-after-apply-functions ()
|
||||
"A list of functions after loading common EditorConfig settings.
|
||||
|
||||
Each element in this list is a hook function. This hook function
|
||||
takes one parameter, which is a property hash table. The value
|
||||
of properties can be obtained through gethash function.
|
||||
|
||||
The hook does not have to be coding style related; you can add
|
||||
whatever functionality you want. For example, the following is
|
||||
an example to add a new property emacs_linum to decide whether to
|
||||
show line numbers on the left:
|
||||
|
||||
(add-hook \\='editorconfig-after-apply-functions
|
||||
\\='(lambda (props)
|
||||
(let ((show-line-num (gethash \\='emacs_linum props)))
|
||||
(cond ((equal show-line-num \"true\") (linum-mode 1))
|
||||
((equal show-line-num \"false\") (linum-mode 0))))))
|
||||
|
||||
This hook will be run even when there are no matching sections in
|
||||
\".editorconfig\", or no \".editorconfig\" file was found at all."
|
||||
:type 'hook))
|
||||
|
||||
(when (< emacs-major-version 30)
|
||||
(defcustom editorconfig-hack-properties-functions ()
|
||||
"A list of function to alter property values before applying them.
|
||||
|
||||
These functions will be run after loading \".editorconfig\" files and before
|
||||
applying them to current buffer, so that you can alter some properties from
|
||||
\".editorconfig\" before they take effect.
|
||||
\(Since 2021/08/30 (v0.9.0): Buffer coding-systems are set before running
|
||||
this functions, so this variable cannot be used to change coding-systems.)
|
||||
|
||||
For example, Makefiles always use tab characters for indentation: you can
|
||||
overwrite \"indent_style\" property when current `major-mode' is a
|
||||
`makefile-mode' with following code:
|
||||
|
||||
(add-hook \\='editorconfig-hack-properties-functions
|
||||
\\='(lambda (props)
|
||||
(when (derived-mode-p \\='makefile-mode)
|
||||
(puthash \\='indent_style \"tab\" props))))
|
||||
|
||||
This hook will be run even when there are no matching sections in
|
||||
\".editorconfig\", or no \".editorconfig\" file was found at all."
|
||||
:type 'hook))
|
||||
(make-obsolete-variable 'editorconfig-hack-properties-functions
|
||||
'editorconfig-get-local-variables-functions
|
||||
"2024")
|
||||
|
||||
(define-obsolete-variable-alias
|
||||
'edconf-indentation-alist
|
||||
'editorconfig-indentation-alist
|
||||
"0.5")
|
||||
(defcustom editorconfig-indentation-alist
|
||||
;; For contributors: Sort modes in alphabetical order
|
||||
'((apache-mode apache-indent-level)
|
||||
(awk-mode c-basic-offset)
|
||||
(bash-ts-mode sh-basic-offset)
|
||||
(bpftrace-mode c-basic-offset)
|
||||
(c++-mode c-basic-offset)
|
||||
(c++-ts-mode c-basic-offset
|
||||
c-ts-mode-indent-offset)
|
||||
(c-mode c-basic-offset)
|
||||
(c-ts-mode c-basic-offset
|
||||
c-ts-mode-indent-offset)
|
||||
(cmake-mode cmake-tab-width)
|
||||
(cmake-ts-mode cmake-tab-width
|
||||
cmake-ts-mode-indent-offset)
|
||||
(coffee-mode coffee-tab-width)
|
||||
(cperl-mode cperl-indent-level)
|
||||
(crystal-mode crystal-indent-level)
|
||||
(csharp-mode c-basic-offset)
|
||||
(csharp-ts-mode c-basic-offset
|
||||
csharp-ts-mode-indent-offset)
|
||||
(css-mode css-indent-offset)
|
||||
(css-ts-mode css-indent-offset)
|
||||
(d-mode c-basic-offset)
|
||||
(elixir-ts-mode elixir-ts-indent-offset)
|
||||
(emacs-lisp-mode . editorconfig--get-indentation-lisp-mode)
|
||||
(enh-ruby-mode enh-ruby-indent-level)
|
||||
(erlang-mode erlang-indent-level)
|
||||
(ess-mode ess-indent-offset)
|
||||
(f90-mode f90-associate-indent
|
||||
f90-continuation-indent
|
||||
f90-critical-indent
|
||||
f90-do-indent
|
||||
f90-if-indent
|
||||
f90-program-indent
|
||||
f90-type-indent)
|
||||
(feature-mode feature-indent-offset
|
||||
feature-indent-level)
|
||||
(fsharp-mode fsharp-continuation-offset
|
||||
fsharp-indent-level
|
||||
fsharp-indent-offset)
|
||||
(gdscript-mode gdscript-indent-offset)
|
||||
(go-ts-mode go-ts-mode-indent-offset)
|
||||
(graphql-mode graphql-indent-level)
|
||||
(groovy-mode groovy-indent-offset)
|
||||
(haskell-mode haskell-indent-spaces
|
||||
haskell-indent-offset
|
||||
haskell-indentation-layout-offset
|
||||
haskell-indentation-left-offset
|
||||
haskell-indentation-starter-offset
|
||||
haskell-indentation-where-post-offset
|
||||
haskell-indentation-where-pre-offset
|
||||
shm-indent-spaces)
|
||||
(haxor-mode haxor-tab-width)
|
||||
(hcl-mode hcl-indent-level)
|
||||
(html-ts-mode html-ts-mode-indent-offset)
|
||||
(idl-mode c-basic-offset)
|
||||
(jade-mode jade-tab-width)
|
||||
(java-mode c-basic-offset)
|
||||
(java-ts-mode c-basic-offset
|
||||
java-ts-mode-indent-offset)
|
||||
(js-mode js-indent-level)
|
||||
(js-ts-mode js-indent-level)
|
||||
(js-jsx-mode js-indent-level sgml-basic-offset)
|
||||
(js2-mode js2-basic-offset)
|
||||
(js2-jsx-mode js2-basic-offset sgml-basic-offset)
|
||||
(js3-mode js3-indent-level)
|
||||
(json-mode js-indent-level)
|
||||
(json-ts-mode json-ts-mode-indent-offset)
|
||||
(jsonian-mode jsonian-default-indentation)
|
||||
(julia-mode julia-indent-offset)
|
||||
(kotlin-mode kotlin-tab-width)
|
||||
(kotlin-ts-mode kotlin-ts-mode-indent-offset)
|
||||
(latex-mode . editorconfig--get-indentation-latex-mode)
|
||||
(lisp-mode . editorconfig--get-indentation-lisp-mode)
|
||||
(livescript-mode livescript-tab-width)
|
||||
(lua-mode lua-indent-level)
|
||||
(lua-ts-mode lua-ts-indent-offset)
|
||||
(magik-mode magik-indent-level)
|
||||
(magik-ts-mode magik-indent-level)
|
||||
(matlab-mode matlab-indent-level)
|
||||
(meson-mode meson-indent-basic)
|
||||
(mips-mode mips-tab-width)
|
||||
(mustache-mode mustache-basic-offset)
|
||||
(nasm-mode nasm-basic-offset)
|
||||
(nginx-mode nginx-indent-level)
|
||||
(nxml-mode nxml-child-indent (nxml-attribute-indent . 2))
|
||||
(objc-mode c-basic-offset)
|
||||
(octave-mode octave-block-offset)
|
||||
(perl-mode perl-indent-level)
|
||||
;; No need to change `php-mode-coding-style' value for php-mode
|
||||
;; since we run editorconfig later than it resets `c-basic-offset'.
|
||||
;; See https://github.com/editorconfig/editorconfig-emacs/issues/116
|
||||
;; for details.
|
||||
(php-mode c-basic-offset)
|
||||
(php-ts-mode php-ts-mode-indent-offset)
|
||||
(pike-mode c-basic-offset)
|
||||
(protobuf-mode c-basic-offset)
|
||||
(ps-mode ps-mode-tab)
|
||||
(pug-mode pug-tab-width)
|
||||
(puppet-mode puppet-indent-level)
|
||||
(python-mode . editorconfig-set-indentation-python-mode)
|
||||
(python-ts-mode . editorconfig-set-indentation-python-mode)
|
||||
(rjsx-mode js-indent-level sgml-basic-offset)
|
||||
(ruby-mode ruby-indent-level)
|
||||
(ruby-ts-mode ruby-indent-level)
|
||||
(rust-mode rust-indent-offset)
|
||||
(rust-ts-mode rust-indent-offset
|
||||
rust-ts-mode-indent-offset)
|
||||
(rustic-mode rustic-indent-offset)
|
||||
(scala-mode scala-indent:step)
|
||||
(scss-mode css-indent-offset)
|
||||
(sgml-mode sgml-basic-offset)
|
||||
(sh-mode sh-basic-offset)
|
||||
(slim-mode slim-indent-offset)
|
||||
(sml-mode sml-indent-level)
|
||||
(svelte-mode svelte-basic-offset)
|
||||
(swift-mode swift-mode:basic-offset)
|
||||
(terra-mode terra-indent-level)
|
||||
(tcl-mode tcl-indent-level
|
||||
tcl-continued-indent-level)
|
||||
(toml-ts-mode toml-ts-mode-indent-offset)
|
||||
(typescript-mode typescript-indent-level)
|
||||
(typescript-ts-base-mode typescript-ts-mode-indent-offset)
|
||||
(verilog-mode verilog-indent-level
|
||||
verilog-indent-level-behavioral
|
||||
verilog-indent-level-declaration
|
||||
verilog-indent-level-module
|
||||
verilog-cexp-indent
|
||||
verilog-case-indent)
|
||||
(web-mode . editorconfig--get-indentation-web-mode)
|
||||
(yaml-mode yaml-indent-offset)
|
||||
(yaml-ts-mode yaml-indent-offset)
|
||||
(zig-mode zig-indent-offset)
|
||||
)
|
||||
"Alist of indentation setting methods by modes.
|
||||
|
||||
This is a fallback used for those modes which don't set
|
||||
`editorconfig-indent-size-vars'.
|
||||
|
||||
Each element should look like (MODE . SETTING) where SETTING
|
||||
should obey the same rules as `editorconfig-indent-size-vars'."
|
||||
:type '(alist :key-type symbol
|
||||
:value-type (choice function (repeat symbol)))
|
||||
:risky t)
|
||||
|
||||
(defcustom editorconfig-trim-whitespaces-mode nil
|
||||
"Buffer local minor-mode to use to trim trailing whitespaces.
|
||||
|
||||
If set, enable that mode when `trim_trailing_whitespace` is set to true.
|
||||
Otherwise, use `delete-trailing-whitespace'."
|
||||
:type 'symbol)
|
||||
|
||||
(defvar editorconfig-properties-hash nil
|
||||
"Hash object of EditorConfig properties that was enabled for current buffer.
|
||||
Set by `editorconfig-apply' and nil if that is not invoked in
|
||||
current buffer yet.")
|
||||
(make-variable-buffer-local 'editorconfig-properties-hash)
|
||||
(put 'editorconfig-properties-hash
|
||||
'permanent-local
|
||||
t)
|
||||
|
||||
(defvar editorconfig-lisp-use-default-indent nil
|
||||
"Selectively ignore the value of indent_size for Lisp files.
|
||||
Prevents `lisp-indent-offset' from being set selectively.
|
||||
|
||||
nil - `lisp-indent-offset' is always set normally.
|
||||
t - `lisp-indent-offset' is never set normally
|
||||
(always use default indent for lisps).
|
||||
number - `lisp-indent-offset' is not set only if indent_size is
|
||||
equal to this number. For example, if this is set to 2,
|
||||
`lisp-indent-offset' will not be set only if indent_size is 2.")
|
||||
|
||||
(define-error 'editorconfig-error
|
||||
"Error thrown from editorconfig lib")
|
||||
|
||||
(defun editorconfig-error (&rest args)
|
||||
"Signal an `editorconfig-error'.
|
||||
Make a message by passing ARGS to `format-message'."
|
||||
(signal 'editorconfig-error (list (apply #'format-message args))))
|
||||
|
||||
(defun editorconfig-string-integer-p (string)
|
||||
"Return non-nil if STRING represents integer."
|
||||
(and (stringp string)
|
||||
(string-match-p "\\`[0-9]+\\'" string)))
|
||||
|
||||
(defun editorconfig--get-indentation-web-mode (size)
|
||||
`((web-mode-indent-style . 2)
|
||||
(web-mode-attr-indent-offset . ,size)
|
||||
(web-mode-attr-value-indent-offset . ,size)
|
||||
(web-mode-code-indent-offset . ,size)
|
||||
(web-mode-css-indent-offset . ,size)
|
||||
(web-mode-markup-indent-offset . ,size)
|
||||
(web-mode-sql-indent-offset . ,size)
|
||||
(web-mode-block-padding . ,size)
|
||||
(web-mode-script-padding . ,size)
|
||||
(web-mode-style-padding . ,size)))
|
||||
|
||||
(defun editorconfig--get-indentation-latex-mode (size)
|
||||
"Vars to set `latex-mode' indent size to SIZE."
|
||||
`((tex-indent-basic . ,size)
|
||||
(tex-indent-item . ,size)
|
||||
(tex-indent-arg . ,(* 2 size))
|
||||
;; For AUCTeX
|
||||
(TeX-brace-indent-level . ,size)
|
||||
(LaTeX-indent-level . ,size)
|
||||
(LaTeX-item-indent . ,(- size))))
|
||||
|
||||
(defun editorconfig--get-indentation-lisp-mode (size)
|
||||
"Set indent size to SIZE for Lisp mode(s)."
|
||||
(when (cond ((null editorconfig-lisp-use-default-indent) t)
|
||||
((eql t editorconfig-lisp-use-default-indent) nil)
|
||||
((numberp editorconfig-lisp-use-default-indent)
|
||||
(not (eql size editorconfig-lisp-use-default-indent)))
|
||||
(t t))
|
||||
`((lisp-indent-offset . ,size))))
|
||||
|
||||
(cl-defun editorconfig--should-set (symbol)
|
||||
"Determine if editorconfig should set SYMBOL."
|
||||
(display-warning '(editorconfig editorconfig--should-set)
|
||||
(format "symbol: %S"
|
||||
symbol)
|
||||
:debug)
|
||||
(when (assq symbol file-local-variables-alist)
|
||||
(cl-return-from editorconfig--should-set
|
||||
nil))
|
||||
|
||||
(when (assq symbol dir-local-variables-alist)
|
||||
(cl-return-from editorconfig--should-set
|
||||
nil))
|
||||
|
||||
t)
|
||||
|
||||
(defvar editorconfig-indent-size-vars
|
||||
#'editorconfig--default-indent-size-function
|
||||
"Rule to use to set a given `indent_size'.
|
||||
Can take the form of a function, in which case we call it with a single SIZE
|
||||
argument (an integer) and it should return a list of (VAR . VAL) pairs.
|
||||
Otherwise it can be a list of symbols (those which should be set to SIZE).
|
||||
Major modes are expected to set this buffer-locally.")
|
||||
|
||||
(defun editorconfig--default-indent-size-function (size)
|
||||
(let ((parents (if (fboundp 'derived-mode-all-parents) ;Emacs-30
|
||||
(derived-mode-all-parents major-mode)
|
||||
(let ((modes nil)
|
||||
(mode major-mode))
|
||||
(while mode
|
||||
(push mode modes)
|
||||
(setq mode (get mode 'derived-mode--parent)))
|
||||
(nreverse modes))))
|
||||
entry)
|
||||
(let ((parents parents))
|
||||
(while (and parents (not entry))
|
||||
(setq entry (assq (pop parents) editorconfig-indentation-alist))))
|
||||
(or
|
||||
(when entry
|
||||
(let ((rule (cdr entry)))
|
||||
;; Filter out settings of unknown vars.
|
||||
(delq nil
|
||||
(mapcar (lambda (elem)
|
||||
(let ((v (car elem)))
|
||||
(cond
|
||||
((not (symbolp v))
|
||||
(message "Unsupported element in `editorconfig-indentation-alist': %S" elem))
|
||||
((or (eq 'eval v) (boundp v)) elem))))
|
||||
(if (functionp rule)
|
||||
(funcall rule size)
|
||||
(mapcar (lambda (elem) `(,elem . ,size)) rule))))))
|
||||
;; Fallback, let's try and guess.
|
||||
(let ((suffixes '("-indent-level" "-basic-offset" "-indent-offset"
|
||||
"-block-offset"))
|
||||
(guess ()))
|
||||
(while (and parents (not guess))
|
||||
(let* ((mode (pop parents))
|
||||
(modename (symbol-name mode))
|
||||
(name (substring modename 0
|
||||
(string-match "-mode\\'" modename))))
|
||||
(dolist (suffix suffixes)
|
||||
(let ((sym (intern-soft (concat name suffix))))
|
||||
(when (and sym (boundp sym))
|
||||
(setq guess sym))))))
|
||||
(when guess `((,guess . ,size))))
|
||||
(and (local-variable-p 'smie-rules-function)
|
||||
`((smie-indent-basic . ,size))))))
|
||||
|
||||
(defun editorconfig--get-indentation (props)
|
||||
"Get indentation vars according to STYLE, SIZE, and TAB_WIDTH."
|
||||
(let ((style (gethash 'indent_style props))
|
||||
(size (gethash 'indent_size props))
|
||||
(tab_width (gethash 'tab_width props)))
|
||||
(when tab_width
|
||||
(setq tab_width (string-to-number tab_width)))
|
||||
|
||||
(setq size
|
||||
(cond ((editorconfig-string-integer-p size)
|
||||
(string-to-number size))
|
||||
((equal size "tab")
|
||||
(or tab_width tab-width))
|
||||
(t
|
||||
nil)))
|
||||
`(,@(if tab_width `((tab-width . ,tab_width)))
|
||||
|
||||
,@(pcase style
|
||||
("space" `((indent-tabs-mode . nil)))
|
||||
("tab" `((indent-tabs-mode . t))))
|
||||
|
||||
,@(when (and size (featurep 'evil))
|
||||
`((evil-shift-width . ,size)))
|
||||
|
||||
,@(cond
|
||||
((null size) nil)
|
||||
((functionp editorconfig-indent-size-vars)
|
||||
(funcall editorconfig-indent-size-vars size))
|
||||
(t (mapcar (lambda (v) `(,v . ,size)) editorconfig-indent-size-vars))))))
|
||||
|
||||
(defvar-local editorconfig--apply-coding-system-currently nil
|
||||
"Used internally.")
|
||||
(put 'editorconfig--apply-coding-system-currently
|
||||
'permanent-local
|
||||
t)
|
||||
|
||||
(defun editorconfig-merge-coding-systems (end-of-line charset)
|
||||
"Return merged coding system symbol of END-OF-LINE and CHARSET."
|
||||
(let ((eol (cond
|
||||
((equal end-of-line "lf") 'undecided-unix)
|
||||
((equal end-of-line "cr") 'undecided-mac)
|
||||
((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))))
|
||||
(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.
|
||||
|
||||
This function will revert buffer when the coding-system has been changed."
|
||||
;; `editorconfig--advice-find-file-noselect' does not use this function
|
||||
(let ((coding-system (editorconfig-merge-coding-systems end-of-line
|
||||
charset)))
|
||||
(display-warning '(editorconfig editorconfig-set-coding-system-revert)
|
||||
(format "editorconfig-set-coding-system-revert: buffer-file-name: %S | buffer-file-coding-system: %S | coding-system: %S | apply-currently: %S"
|
||||
buffer-file-name
|
||||
buffer-file-coding-system
|
||||
coding-system
|
||||
editorconfig--apply-coding-system-currently)
|
||||
:debug)
|
||||
(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
|
||||
(coding-system-aliases (merge-coding-systems coding-system
|
||||
buffer-file-coding-system))))
|
||||
(cl-return-from editorconfig-set-coding-system-revert))
|
||||
(unless (file-readable-p buffer-file-name)
|
||||
(set-buffer-file-coding-system coding-system)
|
||||
(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
|
||||
;; 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)
|
||||
;; Revert without query if buffer is not modified
|
||||
(let ((revert-without-query '(".")))
|
||||
(revert-buffer-with-coding-system coding-system)))
|
||||
(setq editorconfig--apply-coding-system-currently nil)))))
|
||||
|
||||
(defun editorconfig--get-trailing-nl (props)
|
||||
"Get the vars to require final newline according to PROPS."
|
||||
(pcase (gethash 'insert_final_newline props)
|
||||
("true"
|
||||
;; Keep prefs around how/when the nl is added, if set.
|
||||
`((require-final-newline
|
||||
. ,(or require-final-newline mode-require-final-newline t))))
|
||||
("false"
|
||||
`((require-final-newline . nil)))))
|
||||
|
||||
(defun editorconfig--delete-trailing-whitespace ()
|
||||
"Call `delete-trailing-whitespace' unless the buffer is read-only."
|
||||
(unless buffer-read-only (delete-trailing-whitespace)))
|
||||
|
||||
;; Arrange for our (eval . (add-hook ...)) "local var" to be considered safe.
|
||||
(defun editorconfig--add-hook-safe-p (exp)
|
||||
(equal exp '(add-hook 'before-save-hook
|
||||
#'editorconfig--delete-trailing-whitespace nil t)))
|
||||
(let ((predicates (get 'add-hook 'safe-local-eval-function)))
|
||||
(when (functionp predicates)
|
||||
(setq predicates (list predicates)))
|
||||
(unless (memq #'editorconfig--add-hook-safe-p predicates)
|
||||
(put 'add-hook 'safe-local-eval-function #'editorconfig--add-hook-safe-p)))
|
||||
|
||||
(defun editorconfig--get-trailing-ws (props)
|
||||
"Get vars to trim of trailing whitespace according to PROPS."
|
||||
(pcase (gethash 'trim_trailing_whitespace props)
|
||||
("true"
|
||||
`((eval
|
||||
. ,(if editorconfig-trim-whitespaces-mode
|
||||
`(,editorconfig-trim-whitespaces-mode 1)
|
||||
'(add-hook 'before-save-hook
|
||||
#'editorconfig--delete-trailing-whitespace nil t)))))
|
||||
("false"
|
||||
;; Just do it right away rather than return a (VAR . VAL), which
|
||||
;; would be probably more trouble than it's worth.
|
||||
(when editorconfig-trim-whitespaces-mode
|
||||
(funcall editorconfig-trim-whitespaces-mode 0))
|
||||
(remove-hook 'before-save-hook
|
||||
#'editorconfig--delete-trailing-whitespace t)
|
||||
nil)))
|
||||
|
||||
(defun editorconfig--get-line-length (props)
|
||||
"Get the max line length (`fill-column') to PROPS."
|
||||
(let ((length (gethash 'max_line_length props)))
|
||||
(when (and (editorconfig-string-integer-p length)
|
||||
(> (string-to-number length) 0))
|
||||
`((fill-column . ,(string-to-number length))))))
|
||||
|
||||
(defun editorconfig-call-get-properties-function (filename)
|
||||
"Call `editorconfig-core-get-properties-hash' with FILENAME and return result.
|
||||
|
||||
This function also removes `unset' properties and calls
|
||||
`editorconfig-hack-properties-functions'."
|
||||
(if (stringp filename)
|
||||
(setq filename (expand-file-name filename))
|
||||
(editorconfig-error "Invalid argument: %S" filename))
|
||||
(let ((props nil))
|
||||
(condition-case-unless-debug err
|
||||
(setq props (editorconfig-core-get-properties-hash filename))
|
||||
(error
|
||||
(editorconfig-error "Error from editorconfig-core-get-properties-hash: %S"
|
||||
err)))
|
||||
(cl-loop for k being the hash-keys of props using (hash-values v)
|
||||
when (equal v "unset") do (remhash k props))
|
||||
props))
|
||||
|
||||
(defvar editorconfig-get-local-variables-functions
|
||||
'(editorconfig--get-indentation
|
||||
editorconfig--get-trailing-nl
|
||||
editorconfig--get-trailing-ws
|
||||
editorconfig--get-line-length)
|
||||
"Special hook run to convert EditorConfig settings to their Emacs equivalent.
|
||||
Every function is called with one argument, a hash-table indexed by
|
||||
EditorConfig settings represented as symbols and whose corresponding value
|
||||
is represented as a string. It should return a list of (VAR . VAL) settings
|
||||
where VAR is an ELisp variable and VAL is the value to which it should be set.")
|
||||
|
||||
(defun editorconfig--get-local-variables (props)
|
||||
"Get variables settings according to EditorConfig PROPS."
|
||||
(let ((alist ()))
|
||||
(run-hook-wrapped 'editorconfig-get-local-variables-functions
|
||||
(lambda (fun props)
|
||||
(setq alist (append (funcall fun props) alist))
|
||||
nil)
|
||||
props)
|
||||
alist))
|
||||
|
||||
(defun editorconfig-set-local-variables (props)
|
||||
"Set buffer variables according to EditorConfig PROPS."
|
||||
(pcase-dolist (`(,var . ,val) (editorconfig--get-local-variables props))
|
||||
(if (eq 'eval var)
|
||||
(eval val t)
|
||||
(when (editorconfig--should-set var)
|
||||
(set (make-local-variable var) val)))))
|
||||
|
||||
(defun editorconfig-major-mode-hook ()
|
||||
"Function to run when `major-mode' has been changed.
|
||||
|
||||
This functions does not reload .editorconfig file, just sets local variables
|
||||
again. Changing major mode can reset these variables.
|
||||
|
||||
This function also executes `editorconfig-after-apply-functions' functions."
|
||||
(display-warning '(editorconfig editorconfig-major-mode-hook)
|
||||
(format "editorconfig-major-mode-hook: editorconfig-mode: %S, major-mode: %S, -properties-hash: %S"
|
||||
(and (boundp 'editorconfig-mode)
|
||||
editorconfig-mode)
|
||||
major-mode
|
||||
editorconfig-properties-hash)
|
||||
:debug)
|
||||
(when (and (bound-and-true-p editorconfig-mode)
|
||||
editorconfig-properties-hash)
|
||||
(editorconfig-set-local-variables editorconfig-properties-hash)
|
||||
(condition-case err
|
||||
(run-hook-with-args 'editorconfig-after-apply-functions editorconfig-properties-hash)
|
||||
(error
|
||||
(display-warning '(editorconfig editorconfig-major-mode-hook)
|
||||
(format "Error while running `editorconfig-after-apply-functions': %S"
|
||||
err))))))
|
||||
|
||||
(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)
|
||||
(ret nil))
|
||||
(condition-case err
|
||||
(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))
|
||||
|
||||
(condition-case err
|
||||
(with-current-buffer ret
|
||||
(when props
|
||||
|
||||
;; NOTE: hack-properties-functions cannot affect coding-system value,
|
||||
;; because it has to be set before initializing buffers.
|
||||
(condition-case err
|
||||
(run-hook-with-args 'editorconfig-hack-properties-functions props)
|
||||
(error
|
||||
(display-warning '(editorconfig editorconfig-hack-properties-functions)
|
||||
(format "Error while running editorconfig-hack-properties-functions, abort running hook: %S"
|
||||
err)
|
||||
:warning)))
|
||||
(setq editorconfig-properties-hash props)
|
||||
;; When initializing buffer, `editorconfig-major-mode-hook'
|
||||
;; will be called before setting `editorconfig-properties-hash', so
|
||||
;; execute this explicitly here.
|
||||
(editorconfig-set-local-variables props)
|
||||
|
||||
(condition-case err
|
||||
(run-hook-with-args 'editorconfig-after-apply-functions props)
|
||||
(error
|
||||
(display-warning '(editorconfig editorconfig--advice-find-file-noselect)
|
||||
(format "Error while running `editorconfig-after-apply-functions': %S"
|
||||
err))))))
|
||||
(error
|
||||
(display-warning '(editorconfig editorconfig--advice-find-file-noselect)
|
||||
(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)))))
|
||||
|
||||
(defun editorconfig--get-dir-local-variables ()
|
||||
"Return the directory local variables specified via EditorConfig.
|
||||
Meant to be used on `hack-dir-local-get-variables-functions'."
|
||||
(when (stringp buffer-file-name)
|
||||
(let* ((props (editorconfig-call-get-properties-function buffer-file-name))
|
||||
(alist (editorconfig--get-local-variables props)))
|
||||
;; FIXME: If there's `/foo/.editorconfig', `/foo/bar/.dir-locals.el',
|
||||
;; and `/foo/bar/baz/.editorconfig', it would be nice to return two
|
||||
;; pairs here, so that hack-dir-local can give different priorities
|
||||
;; to the `/foo/.editorconfig' settings compared to those of
|
||||
;; `/foo/bar/baz/.editorconfig', but we can't just convert the
|
||||
;; settings from each file individually and let hack-dir-local merge
|
||||
;; them because hack-dir-local doesn't have the notion of "unset",
|
||||
;; and because the conversion of `indent_size' depends on `tab_width'.
|
||||
(when alist
|
||||
(cons
|
||||
(file-name-directory (editorconfig-core-get-nearest-editorconfig
|
||||
buffer-file-name))
|
||||
alist)))))
|
||||
|
||||
;;;###autoload
|
||||
(define-minor-mode editorconfig-mode
|
||||
"Toggle EditorConfig feature."
|
||||
:global t
|
||||
(if (boundp 'hack-dir-local-get-variables-functions) ;Emacs≥30
|
||||
(if editorconfig-mode
|
||||
(progn
|
||||
(add-hook 'hack-dir-local-get-variables-functions
|
||||
;; Give them slightly lower precedence than settings from
|
||||
;; `dir-locals.el'.
|
||||
#'editorconfig--get-dir-local-variables t)
|
||||
;; `auto-coding-functions' also exists in Emacs<30 but without
|
||||
;; access to the file's name via `auto-coding-file-name'.
|
||||
(add-hook 'auto-coding-functions
|
||||
#'editorconfig--get-coding-system))
|
||||
(remove-hook 'hack-dir-local-get-variables-functions
|
||||
#'editorconfig--get-dir-local-variables)
|
||||
(remove-hook 'auto-coding-functions
|
||||
#'editorconfig--get-coding-system))
|
||||
;; Emacs<30
|
||||
(let ((modehooks '(prog-mode-hook
|
||||
text-mode-hook
|
||||
;; Some modes call `kill-all-local-variables' in their init
|
||||
;; code, which clears some values set by editorconfig.
|
||||
;; For those modes, editorconfig-apply need to be called
|
||||
;; explicitly through their hooks.
|
||||
rpm-spec-mode-hook)))
|
||||
(if editorconfig-mode
|
||||
(progn
|
||||
(advice-add 'find-file-noselect :around #'editorconfig--advice-find-file-noselect)
|
||||
(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 'find-auto-coding
|
||||
#'editorconfig--advice-find-auto-coding)
|
||||
(dolist (hook modehooks)
|
||||
(remove-hook hook #'editorconfig-major-mode-hook))))))
|
||||
|
||||
|
||||
;; (defconst editorconfig--version
|
||||
;; (eval-when-compile
|
||||
;; (require 'lisp-mnt)
|
||||
;; (declare-function lm-version "lisp-mnt" nil)
|
||||
;; (lm-version))
|
||||
;; "EditorConfig version.")
|
||||
|
||||
;; ;;;###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-full
|
||||
;; (if (fboundp 'package-get-version)
|
||||
;; (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))
|
||||
|
||||
(provide 'editorconfig)
|
||||
;;; editorconfig.el ends here
|
Loading…
Reference in a new issue