;;; any-ini-mode.el --- keyword highlighting for .ini files etc based on a 'source of truth'
;;; Copyright (C) 2002, 2003 Robert Fitzgerald
;;; Author: Robert Fitzgerald
;;; Created: Mar 2003
;;; Version: 1.0.4
;;; Keywords: convenience
;;; This file is not part of GNU Emacs.
;;; This program 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 2, or (at
;;; your option) any later version.
;;; This program 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 this program ; see the file COPYING. If not, write to
;;; the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
;;; Boston, MA 02111-1307, USA.
;;; Commentary:
;;; When a file is visted in .ini mode the _valid_ section and parameter names
;;; are highlighted as keywords, the comments are highlighted as comments and everything
;;; else is displayed in your normal font.
;;; The list of valid section and parameter names is built dynamically, based on a
;;; canonical file ('source of truth') that contains all valid options.
;;; In this way, you can easily spot a mis-typed name when you're editting your files,
;;; rather than having to wait for your application to misbehave.
;;; It's also useful if, like me, you have users who can't spell 'log_file_path' or
;;; colleagues who insist on creating parameters called 'defaulttofirstbackupdirectory'.
;;; You may also define, among other things, a valid comment character and a
;;; valid assignment charcter.
;;; Collectively, the definition of a canonical file and a comment character etc
;;; define a 'style' of .ini file.
;;;
;;; You may set up a default style for all .ini mode buffers, or, more usefully,
;;; you may set up several styles that will be automatically applied, based on the name
;;; of the file being visited.
;;; any-ini-mode now includes support for `imenu'.
;;;
;;; `imenu-generic-expression' is set up for each buffer to find the section names
;;; and, by default, a menu of section names is automatically added to the menubar.
;;;
;;; If you setup `speedbar' correctly, this means that you can also navigate the
;;; sections with `speedbar'.
;;;
;;; See the `any-ini-imenu' customization group and the documentation for `imenu'
;;; and `speedbar' for more details.
;;; Finally, you may want to try running the `any-ini-toggle-errorcheck-mode' command.
;;; See the docstring for this command for an explantion.
;;; Customization is via the `any-ini' group in the `local' customization section.
;;;
;;; To load any-ini-mode on startup, copy this file to your load-path and add this to
;;; your .emacs file -
;;;
;;; (require 'any-ini-mode)
;;;
;;; You may also want to specify that .ini mode should apply by default
;;; to all .ini and .conf files, for example. Here's how -
;;;
;;; (add-to-list 'auto-mode-alist '(".*\\.ini$" . any-ini-mode))
;;; (add-to-list 'auto-mode-alist '(".*\\.conf$" . any-ini-mode))
;;;
;;; Example:
;;;
;;; You deal with three applications - larry, mo and curly.
;;; Each requires a config file - larry.conf, mo.ini and .curlyrc - and none of the parameter
;;; names that are valid in a mo.ini file are valid in a larry.conf or a .curlyrc, etc. etc.
;;;
;;; No problem.
;;;
;;; First, create a canonical config file for each app (the names of these files are unimportant).
;;; This is simply a template containing all the valid sections and parameters.
;;; eg.
;;;
;;; --------------------------------------------------------------------------------------
;;; | # canon.larry.conf - canonical config file for the larry app |
;;; | # this file is maintained by the larry developers in ~devel/larry/etc |
;;; | # Applies to larry v6.6.6 and up |
;;; | # Last updated 12-24-2001 by groucho |
;;; | |
;;; | [USER] |
;;; | editor = |
;;; | #group = # deprecated v6.6.6 |
;;; | home = |
;;; | |
;;; | [BACKUP] |
;;; | path = |
;;; | always = # |
;;; | never = # NB - only one of these should be set TRUE in a live file |
;;; | sometimes = # |
;;; | |
;;; --------------------------------------------------------------------------------------
;;;
;;; Next, create 3 styles (call them larry.conf, mo.ini and .curlyrc) in `any-ini-styles-alist'
;;; and set the canonical file for each type. You can also specify, for example, that a mo.ini
;;; uses `<' and `>' to bracket section names and that a .curlyrc uses `:' as its assignment
;;; character.
;;;
;;; Finally, add this to your .emacs file -
;;;
;;; (require 'any-ini-mode)
;;; (add-to-list 'auto-mode-alist '(".*\\.ini$" . any-ini-mode))
;;; (add-to-list 'auto-mode-alist '(".*\\.conf$" . any-ini-mode))
;;; (add-to-list 'auto-mode-alist '("\\.curlyrc" . any-ini-mode))
;;;
;;; Change Log:
;;; Changes from 1.0.3 to 1.0.4:
;;; * Improved doc strings for `any-ini-param-name-regexp' and `any-ini-section-name-regexp'
;;; Based on feedback from Dave Pearson and others
;;; Changes from 1.0.2 to 1.0.3:
;;; * Fixed up for use with `describe-mode'.
;;; Thanks to Vagn Johansen
;;; Changes from 1.0.1 to 1.0.2:
;;; * Add an `imenu' menu for section names
;;; See `any-ini-setup-imenu' for details.
;;; Changes from 1.0.0 to 1.0.1:
;;; * Use new `any-ini-regexp-opt' function in place of standard `regexp-opt' function.
;;; Necessary because of occasional memory problems with `regexp-opt' during parsing
;;; of very large (1500+ lines) files.
;;; Customization:
(defgroup any-ini nil
"Major mode for editing .ini files with customizable keyword highlighting."
:tag ".ini"
:group 'local)
(defgroup any-ini-faces nil
"Faces for .ini mode.
Comments use `font-lock-comment-face'."
:tag ".ini faces"
:group 'any-ini)
(defgroup any-ini-imenu nil
".ini interaction with `imenu'.
See Info Node `(emacs)Imenu' for more details."
:tag ".ini imenu"
:group 'any-ini)
(defcustom any-ini-canonical-ini-files '(("~/.any.ini.canon" nil))
"*Alist of files to be parsed for valid section and parameter names.
Each description has the form (FILENAME TYPE).
Files may be parsed in three ways, based on TYPE :
:is-canonical-file (nil)
The file will be treated as a canonical file.
It will be parsed twice, once to extract the section names and once
to extract the parameter names.
The section names will be extracted using `any-ini-section-start-chars',
`any-ini-section-name-regexp' and `any-ini-section-end-chars'.
The parameter names will be extracted using `any-ini-param-name-regexp'.
Parameter names will be extracted only if they are followed by an
assignment character (see `any-ini-assignment-chars'), possibly with
intervening whitespace. It is not necessary, however, for the parameters
to have any values assigned.
:is-list-of-section-names (1)
The file will be treated as a simple list of valid section names.
The first whitespace-delimited word on each line will be checked against
`any-ini-section-name-regexp' and those that pass will be extracted.
:is-list-of-paramter-names (2)
The file will be treated as a simple list of valid parameter names.
The first whitespace-delimited word on each line will be checked against
`any-ini-param-name-regexp' and those that pass will be extracted.
In all cases, whitespace is ignored and `any-ini-comment-start-chars' is
used to disregard comments."
:group 'any-ini
:type '(repeat (list :tag "Canonical files" :value ("~/.any.ini.canon" nil)
(file :tag "Filename")
(choice :tag "Type"
(const :tag "Canonical file" nil)
(const :tag "List of section names" 1)
(const :tag "List of parameter names" 2)))))
(defcustom any-ini-comment-start-chars '( ?\# )
"Characters that may start a comment.
Multi-line comments are not handled; a comment is assumed
to end with a linefeed or formfeed.
Default value is `#'."
:group 'any-ini
:type '(repeat :tag "Comment chars" (character :value ?\# :tag "Char")))
(defcustom any-ini-param-name-regexp "[-_A-Za-z0-9]+"
"Regexp to describe valid parameter name.
This regexp is used only when a canonical file is parsed for valid
parameter names.
The actual keyword highlighting of parameter names is based on the
list of parameter names thus created, _not_ on this regexp.
The regexp should describe the characters that make up a valid
parameter _name_ only (eg. My_Param-11 ). The mode itself will
take care of handling any surrounding whitespace and/or assignment
character.
Default value is \"[-_A-Za-z0-9]+\".
See also `any-ini-assignment-chars'."
:group 'any-ini
:type '(regexp :tag "Params regexp" :value "[-_A-Za-z0-9]+"))
(defcustom any-ini-assignment-chars '( ?\= )
"Characters that show the assignment of a value to a parameter.
Parameter names will be extracted from a canonical file only if they are
followed by an assignment character, possibly with intervening whitespace.
It is not necessary, however, for the parameter to have any values assigned.
The same rules apply to highlighting a parameter name in an `any-ini-mode'
buffer.
Default value is `='."
:group 'any-ini
:type '(repeat :tag "Assignment chars" (character :value ?\= :tag "Char")))
(defcustom any-ini-section-name-regexp "[-_A-Za-z0-9]+"
"Regexp to describe valid section name.
This regexp is used only when a canonical file is parsed for valid
section names.
The actual keyword highlighting of section names is based on the
list of section names thus created, _not_ on this regexp.
This regexp should describe the characters that make up a valid
section _name_ only (eg. MY_SECTION-22 ). The mode itself will
take care of handling any surrounding brackets.
See `any-ini-section-start-chars' and `any-ini-section-end-chars' for
the definition of the bracketting characters.
Default value is \"[-_A-Za-z0-9]+\"."
:group 'any-ini
:type '(regexp :tag "Sections regexp" :value "[-_A-Za-z0-9]+"))
(defcustom any-ini-section-start-chars '( ?\[ )
"Characters that may begin a section heading.
NB - No attempt is made to pair-up the characters that start and end a
section heading. It's assumed that a section heading that is begun
by a valid starting character may be ended by _any_ valid ending character.
Default value is `['.
See also `any-ini-section-end-chars'."
:group 'any-ini
:type '(repeat :tag "Section start chars" (character :value ?\[ :tag "Char")))
(defcustom any-ini-section-end-chars '( ?\] )
"Characters that may end a section heading.
NB - No attempt is made to pair-up the characters that start and end a
section heading. It's assumed that a section heading that is begun
by a valid starting character may be ended by _any_ valid ending character.
Default value is `]'.
See also `any-ini-section-start-chars'."
:group 'any-ini
:type '(repeat :tag "Section end chars" (character :value ?\] :tag "Char")))
(defcustom any-ini-styles-alist nil
"List of .ini styles that may be used in preference to the default style.
Each item has the form -
\(STYLENAME CANON_FILES COMMENT_CHARS PARAM_REGEXP ASSIGNERS SECTION_REGEXP SECT_STARTERS SECT_ENDERS KEYWORDS)
STYLENAME: Each style list should be given a meaningful name. When a file is
visited in .ini mode, `any-ini-styles-alist' will be searched for a STYLENAME
that matches the name of the file. If a match is found, the style settings in
the list will be applied. If not, the default style will be used.
CANON_FILES: See the documentation for `any-ini-canonical-ini-files'
COMMENT_CHARS: See the documentation for `any-ini-comment-start-chars'
PARAM_REGEXP: See the documentation for `any-ini-param-name-regexp'
ASSIGNERS: See the documentation for `any-ini-assignment-chars'
SECTION_REGEXP: See the documentation for `any-ini-section-name-regexp'
SECT_STARTERS: See the documentation for `any-ini-section-start-chars'
SECT_ENDERS: See the documentation for `any-ini-section-end-chars'."
:group 'any-ini
:type '(repeat (list
(string :tag "Style name" :value "new.ini")
(repeat :tag "Canonical files" (list :value ("~/.new.ini.canon" nil)
(file :tag "Filename")
(choice :tag "Type"
(const :tag "Canonical file" nil)
(const :tag "List of section names" 1)
(const :tag "List of parameter names" 2))))
(repeat :tag "Comment chars" (character :value ?\# :tag "Char"))
(regexp :tag "Params regexp" :value "[-_A-Za-z0-9]+")
(repeat :tag "Assignment chars" (character :value ?\= :tag "Char"))
(regexp :tag "Sections regexp" :value "[-_A-Za-z0-9]+")
(repeat :tag "Section start chars" (character :value ?\[ :tag "Char"))
(repeat :tag "Section end chars" (character :value ?\] :tag "Char" )))))
(defcustom any-ini-mode-mode-hook nil
"Normal hook run when entering .ini mode."
:type 'hook
:group 'any-ini)
(defcustom any-ini-param-face 'font-lock-function-name-face
"*Font that .ini mode will use to highlight parameter names."
:type 'face
:group 'any-ini-faces)
(defcustom any-ini-section-face 'font-lock-keyword-face
"*Font that .ini mode will use to highlight section names."
:type 'face
:group 'any-ini-faces)
(defcustom any-ini-assigner-face 'default
"*Font that .ini mode will use to highlight the assignment character."
:type 'face
:group 'any-ini-faces)
(defcustom any-ini-value-face 'default
"*Font that .ini mode will use to highlight parameter values."
:type 'face
:group 'any-ini-faces)
(defcustom any-ini-section-chars-face 'default
"*Font that .ini mode will use to highlight section names."
:type 'face
:group 'any-ini-faces)
(defcustom any-ini-imenu-show-flag t
"Use `imenu' \(if available\) to add a menu of section names to menubar.
Non-nil means that a menu of section names will be automatically created
for each new .ini mode buffer through a call to `imenu-add-to-menubar'.
The menu name is defined by `any-ini-imenu-name'.
`imenu-generic-expression' is _always_ initialised in .ini mode
buffers, regardless of the state of this flag. This means that the
standard `imenu' commands should always be available.
See Info Node `(emacs)Imenu' for more details."
:group 'any-ini-imenu
:type 'boolean)
(defcustom any-ini-imenu-name "Sections"
"Name to use for menu of section names."
:group 'any-ini-imenu
:type 'string)
;;; Constants:
(defconst any-ini-buffer-errorcheck nil
"Buffer-local version of this var indicates if a buffer is using
any-ini-errorcheck-mode.")
(defconst any-ini-style nil
"Keyword highlighting style to apply to a file.
This variable should be set by `any-ini-set-my-style'.
Setting it directly won't have the desired effect.
When `any-ini-style' is nil, the default style is applied.
See also `any-ini-set-my-style' and `any-ini-styles-alist'.
*NB - You may be tempted to set this as a file variable
eg.
;; -*- mode: any-ini; any-ini-style: \"apache\" -*-
but, unfortunately, this won't have the desired affect either.
Hopefully, this technique will be available soon.
At the moment, the following _will_ work ...
;; -*- mode: any-ini; eval: (any-ini-set-my-style \"apache\") -*-
... but then you'll have to deal with `enable-local-eval' etc.")
;;; Code:
(defun any-ini-set-my-style (&optional mystyleoverride)
"*Set keyword highlighting style for a file.
MYSTYLEOVERRIDE is a string representing the name of a highlighting style.
If the style is found in `any-ini-styles-alist' it will be applied to the
current buffer.
If MYSTYLEOVERRIDE is `nil', the name of the currently-visited file will
be taken as the required style name and will be searched for in `any-ini-styles-alist'.
If a matching style is not found for MYSTYLEOVERRIDE or the filename, the
default style will be applied.
If this function is called with a prefix argument and MYSTYLEOVERRIDE is `nil',
the default style will be applied, regardless of name of the visited file.
The default style is the style described by the global values of -
`any-ini-canonical-ini-files',
`any-ini-comment-start-chars',
`any-ini-param-name-regexp'
`any-ini-assignment-chars',
`any-ini-section-name-regexp',
`any-ini-section-start-chars'
`any-ini-section-end-chars'
"
(interactive
(if any-ini-styles-alist
(list
(completing-read "Apply any-ini-style :" any-ini-styles-alist nil t))
(message "any-ini-mode : No styles defined.")
nil))
(if (not (eq major-mode 'any-ini-mode))
(message "Major mode is not any-ini-mode.")
(make-local-variable 'any-ini-style)
(if (and current-prefix-arg (string= mystyleoverride ""))
(setq any-ini-style nil)
(if (string= mystyleoverride "")
(setq any-ini-style nil)
(setq any-ini-style mystyleoverride))
(if (and any-ini-style (stringp any-ini-style))
()
(if buffer-file-name
(setq any-ini-style (file-name-nondirectory buffer-file-name)))))
(any-ini-apply-style)
(font-lock-mode -1)
(font-lock-mode t)))
(defun any-ini-apply-style (&optional re-read-canons)
"Setup and apply style for a buffer, based on local `any-ini-style'.
If `any-ini-style' is nil, or is not the name of a style from `any-ini-styles-alist',
then the default style will be applied.
If RE-READ-CANONS is non-nil, the relevant canon files will be re-parsed and the
stored font-lock-keywords for the style updated.
Otherwise, the canon files will only be parsed if they have not previously been
parsed in this Emacs session."
(let (mystylealist nofontlocksyet)
(if any-ini-style
(setq mystylealist (assoc any-ini-style any-ini-styles-alist)))
(cond ((not mystylealist)
(kill-local-variable 'any-ini-font-lock-keywords)
(kill-local-variable 'any-ini-canonical-ini-files)
(kill-local-variable 'any-ini-comment-start-chars)
(kill-local-variable 'any-ini-param-name-regexp)
(kill-local-variable 'any-ini-assignment-chars)
(kill-local-variable 'any-ini-section-name-regexp)
(kill-local-variable 'any-ini-section-start-chars)
(kill-local-variable 'any-ini-section-end-chars)
(setq any-ini-style nil))
(t
(make-local-variable 'any-ini-font-lock-keywords)
(make-local-variable 'any-ini-canonical-ini-files)
(make-local-variable 'any-ini-comment-start-chars)
(make-local-variable 'any-ini-param-name-regexp)
(make-local-variable 'any-ini-assignment-chars)
(make-local-variable 'any-ini-section-name-regexp)
(make-local-variable 'any-ini-section-start-chars)
(make-local-variable 'any-ini-section-end-chars)
(setq any-ini-font-lock-keywords nil)
(if re-read-canons
()
(let ((mysavedfontlock (get 'any-ini-styles-alist any-ini-style)))
(if mysavedfontlock
(setq any-ini-font-lock-keywords mysavedfontlock)
(setq nofontlocksyet t))))
(setq any-ini-canonical-ini-files (nth 1 mystylealist))
(setq any-ini-comment-start-chars (nth 2 mystylealist))
(setq any-ini-param-name-regexp (nth 3 mystylealist))
(setq any-ini-assignment-chars (nth 4 mystylealist))
(setq any-ini-section-name-regexp (nth 5 mystylealist))
(setq any-ini-section-start-chars (nth 6 mystylealist))
(setq any-ini-section-end-chars (nth 7 mystylealist))))
(any-ini-setup-local-syntax-table)
(any-ini-read-keywords re-read-canons)
(any-ini-setup-imenu)
(make-local-variable 'comment-start)
(if any-ini-comment-start-chars
(setq comment-start (string (nth 0 any-ini-comment-start-chars)))
(setq comment-start "#"))
(make-local-variable 'comment-end)
(setq comment-end "")
(setq mode-name (concat "" (if (not any-ini-style)
".ini"
(concat " " any-ini-style))
" file"))
(when (or nofontlocksyet re-read-canons)
(put 'any-ini-styles-alist any-ini-style any-ini-font-lock-keywords))))
(defvar any-ini-local-syntax-table nil
"Syntax table used while in .ini mode.
Spaces and tabs are defined as whitespace, linefeeds and formfeeds are defined
as comment-ending characters and the defined `any-ini-comment-start-chars' are
defined as comment-starting characters.")
(defun any-ini-setup-local-syntax-table ()
"Setup local syntax table based on settings for `any-ini-comment-start-chars'.
Spaces and tabs are defined as whitespace, linefeeds and formfeeds are defined
as comment-ending characters and the defined `any-ini-comment-start-chars' are
defined as comment-starting characters.
Syntax table is stored as `any-ini-local-syntax-table'."
(make-local-variable 'any-ini-local-syntax-table)
(setq any-ini-local-syntax-table (make-syntax-table))
(set-syntax-table any-ini-local-syntax-table)
(modify-syntax-entry ?\ " ")
(modify-syntax-entry ?\t " ")
(modify-syntax-entry ?\n ">")
(modify-syntax-entry ?\f ">")
(if (not any-ini-comment-start-chars)
(modify-syntax-entry ?\# "<")
(dolist (commentchar any-ini-comment-start-chars)
(modify-syntax-entry commentchar "<"))))
(defvar any-ini-map nil
"Keymap for .ini mode.")
(if any-ini-map
()
(setq any-ini-map (make-sparse-keymap)))
(defvar any-ini-abbrev-table nil
"Abbrev table used while in .ini mode.")
(define-abbrev-table 'any-ini-abbrev-table ())
(defvar any-ini-font-lock-keywords nil
"Current list of keywords for highlighting in default .ini mode.
See also `any-ini-read-keywords' and `any-ini-reread-keywords'.")
(defun any-ini-re-read-keywords-etc ()
"*Re-parse the canonical files for the current style and update all style
info based on the current customization settings.
Having updated the style info, this fuction will then cycle through
all buffers and also update those that may be affected by the re-read.
Calls `any-ini-apply-style' in this buffer with RE-READ-CANONS as t
and `any-ini-apply-style' for the other buffers with no argument."
(interactive)
(if (not (eq major-mode 'any-ini-mode))
(message "Major mode is not any-ini-mode.")
(message "any-ini-reread-keywords : working ....")
(any-ini-apply-style t)
(save-excursion
(let ((updating-style any-ini-style))
(dolist (somebuffer (buffer-list))
(set-buffer somebuffer)
(when (and (eq major-mode 'any-ini-mode) (eq any-ini-style updating-style))
(any-ini-apply-style)
(font-lock-mode -1)
(font-lock-mode t)))))
(message "any-ini-reread-keywords : DONE.")))
(defun any-ini-read-keywords (&optional forceread)
"Create keyword-map for .ini mode, based on current canon files.
Can be called from `any-ini-reread-keywords', in which case FORCEREAD
will be non-nil, the relevant canonical files will be re-read and any current
font-lock-keyword info for the current style will be overwritten.
Otherwise, no action will be taken if a current font-lock-keywords list exists
for the current style.
The font-lock-keyword lists for the default style are stored in the global version
of `any-ini-font-lock-keywords'. The keywords for the styles from `any-ini-styles-alist'
are stored in local versions of `any-ini-font-lock-keywords' and also in a plist
of `any-ini-styles-alist'.
At the moment, this data is not saved between Emacs sessions - instead, it is recreated
for each style, the first time the style is activated in a session."
(if (and any-ini-font-lock-keywords (not forceread))
()
(setq any-ini-font-lock-keywords nil)
(dolist (mykeyfile any-ini-canonical-ini-files)
(setq any-ini-font-lock-keywords
(append
(any-ini-append-keys-from mykeyfile (list nil
nil
any-ini-comment-start-chars
any-ini-param-name-regexp
any-ini-assignment-chars
any-ini-section-name-regexp
any-ini-section-start-chars
any-ini-section-end-chars)
)
any-ini-font-lock-keywords)))
(setq any-ini-font-lock-keywords
(append (list (cons
(concat "\\^.*\\(["
(if any-ini-comment-start-chars
any-ini-comment-start-chars
(string ?\#) )
"].*$\\)")
(list 2 'font-lock-comment-face nil nil))
)
any-ini-font-lock-keywords )))
(setq font-lock-defaults (list any-ini-font-lock-keywords nil t nil nil)))
(defun any-ini-append-keys-from (kfile mystylealist)
"Read a file and set the section and/or param names it contains as keywords
in `any-ini-font-lock-keywords'."
(let (mykeyfile myfiletype myparselist isinifile my-font-lock-keywords)
(setq mykeyfile (nth 0 kfile))
(setq myfiletype (nth 1 kfile))
(cond
( (eq myfiletype 1) (setq myparselist '(t))) ;; Sections list
( (eq myfiletype 2) (setq myparselist '(nil))) ;; Params list
( t (setq isinifile t) ;; .ini file, parse twice
(setq myparselist '(t nil)))
)
(when (file-exists-p mykeyfile)
(save-current-buffer
(let (mykeywordslist
mykeyword
font
(my-any-ini-comment-start-chars (nth 2 mystylealist))
(my-any-ini-param-name-regexp (nth 3 mystylealist))
(my-any-ini-assignment-chars (nth 4 mystylealist))
(my-any-ini-section-name-regexp (nth 5 mystylealist))
(my-any-ini-section-start-chars (nth 6 mystylealist))
(my-any-ini-section-end-chars (nth 7 mystylealist)))
(set-buffer (get-buffer-create "*any-ini-temp*"))
(insert-file-contents mykeyfile nil nil nil t)
(dolist (sectionfile myparselist)
(goto-char (point-min))
(condition-case e
(while t
(cond ((and isinifile sectionfile)
(re-search-forward
(concat "\\(^[ \t]*\\)"
"\\(["
(if my-any-ini-section-start-chars
my-any-ini-section-start-chars
(string ?\[))
"]\\)"
"\\("
(if my-any-ini-section-name-regexp
my-any-ini-section-name-regexp
"[-_A-Za-z0-9]+")
"\\)"
"\\(["
(if my-any-ini-section-end-chars
my-any-ini-section-end-chars
(string ?\]))
"]\\)"
"\\(.*$\\)"
)
)
(setq mykeyword (match-string 3)))
((and isinifile (not sectionfile))
(re-search-forward
(concat "\\(^[ \t]*\\)\\("
(if my-any-ini-param-name-regexp
my-any-ini-param-name-regexp
"[-_A-Za-z0-9]+")
"\\)\\([ \t]*"
"["
(if my-any-ini-assignment-chars
my-any-ini-assignment-chars
(string ?\=))
"]"
".*$\\)")
)
(setq mykeyword (match-string 2)))
(sectionfile
(re-search-forward
(concat "\\(^[ \t]*\\)\\("
(if my-any-ini-section-name-regexp
my-any-ini-section-name-regexp
"[-_A-Za-z0-9]+")
"\\)$")
)
(setq mykeyword (match-string 2)))
((not sectionfile)
(re-search-forward
(concat "\\(^[ \t]*\\)\\("
(if my-any-ini-param-name-regexp
my-any-ini-param-name-regexp
"[-_A-Za-z0-9]+")
"\\)$")
)
(setq mykeyword (match-string 2))))
(when mykeyword
(setq mykeywordslist (append (list mykeyword) mykeywordslist))))
(search-failed))
(setq my-font-lock-keywords
(append (list
(cons
(any-ini-keywords-regexp mykeywordslist sectionfile mystylealist)
(if sectionfile
(list
(list 1 'any-ini-section-chars-face nil nil)
(list 2 'any-ini-section-face nil nil)
(list 3 'any-ini-section-chars-face nil nil)
)
(list (list 2 'any-ini-param-face nil nil)
(list 4 'any-ini-assigner-face nil nil)
(list 6 'any-ini-value-face nil nil)
(list 7 'font-lock-comment-face nil nil)
))
)
)
my-font-lock-keywords )))
(kill-buffer (current-buffer)))))
my-font-lock-keywords))
(defun any-ini-keywords-regexp (keywords aresections mystylealist)
"Create regexp to describe sections/params for font-locking.
NB - First part of the regexps (searching for whitespace etc at the beginning of a line),
is wrapped in parentheses to ensure that the keyword part of the regexp is always be `subexp2'."
(let (
(my-any-ini-comment-start-chars (nth 2 mystylealist))
(my-any-ini-param-name-regexp (nth 3 mystylealist))
(my-any-ini-assignment-chars (nth 4 mystylealist))
(my-any-ini-section-name-regexp (nth 5 mystylealist))
(my-any-ini-section-start-chars (nth 6 mystylealist))
(my-any-ini-section-end-chars (nth 7 mystylealist)))
(if aresections
(concat "\\(^[ \t]*["
(if my-any-ini-section-start-chars
my-any-ini-section-start-chars
(string ?\[))
"]\\)"
(any-ini-regexp-opt keywords t)
"\\(["
(if my-any-ini-section-end-chars
my-any-ini-section-end-chars
(string ?\]))
"]\\)"
"\\([ \t]*$\\|[ \t]+["
(if my-any-ini-comment-start-chars
my-any-ini-comment-start-chars
(string ?\#))
"].*$\\)"
)
(concat "\\(^[ \t]*\\)"
(any-ini-regexp-opt keywords t)
"\\([ \t]*\\)"
"\\(["
(if my-any-ini-assignment-chars
my-any-ini-assignment-chars
(string ?\=))
"]\\)"
"\\([ \t]*\\)"
"\\([^"
(if my-any-ini-comment-start-chars
my-any-ini-comment-start-chars
(string ?\#))
"\n]*\\)"
"\\(["
(if my-any-ini-comment-start-chars
my-any-ini-comment-start-chars
(string ?\#))
"].*\\|[\n]\\)"
))))
(defun any-ini-regexp-opt (strings paren)
"Replacement for standard `regexp-opt' function.
This replacement is necessary since the standard function can occasionally cause a memory
error when setting a file's mode from the auto-mode-alist functionality.
Error was \"Variable binding depth exceeds max-specpdl-size\" and happened only with
large (1500+ lines) canonical files."
(let ((open-paren (if paren "\\(" ""))
(close-paren (if paren "\\)" "")))
(concat open-paren
(mapconcat 'regexp-quote strings "\\|")
close-paren)))
(defun any-ini-toggle-errorcheck-mode ()
"*Temorarily set fonts in this buffer to highlight spelling errors more clearly.
Resets the comment face and param-related faces in this buffer to `font-lock-string-face'.
The idea is that this will make it easier to spot errors in a large or heavily-commented
file, as they should show up in `default' face against a relatively neutral background.
The fonts affected are -
`font-lock-comment-face'
`any-ini-param-face'
`any-ini-assigner-face'
`any-ini-value-face'
"
(interactive)
(if (not (eq major-mode 'any-ini-mode))
(message "Major mode is not any-ini-mode.")
(make-variable-buffer-local 'any-ini-buffer-errorcheck)
(cond ((eq any-ini-buffer-errorcheck 1)
(kill-local-variable 'font-lock-comment-face)
(kill-local-variable 'any-ini-param-face)
(kill-local-variable 'any-ini-assigner-face)
(kill-local-variable 'any-ini-value-face)
(setq any-ini-buffer-errorcheck 0)
)
(t
(make-local-variable 'font-lock-comment-face)
(make-local-variable 'any-ini-param-face)
(make-local-variable 'any-ini-assigner-face)
(make-local-variable 'any-ini-value-face)
(setq font-lock-comment-face 'font-lock-string-face)
(setq any-ini-param-face 'font-lock-string-face)
(setq any-ini-assigner-face 'font-lock-string-face)
(setq any-ini-value-face 'font-lock-string-face)
(setq any-ini-buffer-errorcheck 1)
))
(any-ini-apply-style)
(font-lock-mode -1)
(font-lock-mode t)))
(defun any-ini-setup-imenu ()
"Setup and display an `imenu' menu of section names for current buffer.
Sets up `imenu-generic-expression' \(see Info Node `(emacs)Imenu'\)
based on current settings of
`any-ini-section-name-regexp',
`any-ini-section-start-chars',
`any-ini-section-end-chars'
The title of the menu is read from `any-ini-imenu-name'.
Display of the menu is controlled by `any-ini-imenu-show-flag'.
See Info Node `(emacs)Imenu' for more details."
(make-local-variable 'imenu-generic-expression)
(setq imenu-generic-expression
(list
(list nil
(concat "\\(^[ \t]*\\)"
"\\(["
(if any-ini-section-start-chars
any-ini-section-start-chars
(string ?\[))
"]\\)"
"\\("
(if any-ini-section-name-regexp
any-ini-section-name-regexp
"[-_A-Za-z0-9]+")
"\\)"
"\\(["
(if any-ini-section-end-chars
any-ini-section-end-chars
(string ?\]))
"]\\)"
"\\(.*$\\)"
)
3)
)
)
(if any-ini-imenu-show-flag
(if (featurep 'imenu)
(imenu-add-to-menubar 'any-ini-imenu-name)
(message "any-ini-mode: imenu NOT available."))))
;;;###autoload
(defun any-ini-mode ()
"*Major mode for editing config files with syntax highlighting based on a 'source of truth'.
You may set up a default style for all .ini mode buffers, or, more usefully,
you may set up several styles that will be automatically applied based on the name
of the file being visited.
See `any-ini-set-my-style' and `any-ini-styles-alist' for more details.
Turning on .ini mode runs the normal hook `any-ini-mode-hook'."
(interactive)
(kill-all-local-variables)
(use-local-map any-ini-map)
(setq local-abbrev-table any-ini-abbrev-table)
(setq major-mode 'any-ini-mode)
(any-ini-set-my-style)
(run-hooks 'any-ini-mode-hook))
;; Try to pull in imenu if it exists.
(condition-case nil
(require 'imenu)
(error nil))
(provide 'any-ini-mode)
;;; any-ini-mode.el ends here