[Top][All Lists]
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
flyspell-babel.el -- Switch flyspell lang according to LaTeX commands
From: |
Peter Heslin |
Subject: |
flyspell-babel.el -- Switch flyspell lang according to LaTeX commands |
Date: |
Wed, 14 Jul 2004 20:03:28 -0500 |
User-agent: |
slrn/0.9.8.0 (Linux) |
;; flyspell-babel.el -- Switch flyspell language according to LaTeX
;; Babel commands
;;
;; Copyright (C) 2004 P J Heslin
;;
;; Author: Peter Heslin <address@hidden>
;; URL: http://www.dur.ac.uk/p.j.heslin/emacs/download/flyspell-babel.el
;; Version: 1.0
;;
;; 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.
;;
;; If you do not have a copy of the GNU General Public License, you
;; can obtain one by writing to the Free Software Foundation, Inc., 59
;; Temple Place - Suite 330, Boston, MA 02111-1307, USA.
;;; Installation:
;;
;; Flyspell is an Emacs package that highlights misspelled words as
;; you type; Babel is the standard mechanism for switching languages
;; in LaTeX. There are a number of Emacs packages available that will
;; try to guess the current language of a buffer or part of a buffer,
;; and make flyspell switch to a different dictionary; but I didn't
;; find one that used the explicit language-switching commands
;; available in a LaTeX file for this purpose. This file makes
;; flyspell use the correct dictionary for the language used in each
;; part of a LaTeX file. It can slow up your editing session
;; considerably, but I find it usable. There are some restrictions on
;; the usage of Babel commands, on which see below.
;;
;; flyspell-babel requires flyspell and AUCTeX to be installed. I
;; have tested it with version 1.7e of flyspell, which I obtained at:
;;
http://www-sop.inria.fr/mimosa/personnel/Manuel.Serrano/flyspell/flyspell-1.7e.el,
;; and a CVS checkout of AUCTeX, which I got from
;; http://savannah.gnu.org/cvs/?group=auctex.
;;
;; To use this file, put it somewhere in your load-path, and add this
;; to your .emacs file:
;;
;; (add-hook 'LaTeX-mode-hook '(lambda ()
;; (require 'flyspell-babel)))
;;
;; You will need to reload flyspell-babel.el if you install any new
;; ispell languages or language aliases.
;;; Commentary:
;;
;; Every time flyspell spell-checks a word (when you type a new word
;; or move the cursor to a new word), the buffer is examined to find a
;; relevant Babel command, and if necessary, the ispell/aspell precess
;; is stopped, and a new one for the new language is started.
;;
;; It's possible that a better way to do this might be to parse and
;; tag the whole buffer in the background, which would mean that less
;; work would have to be done whenever flyspell is actively checking
;; words. One could imagine a version of flyspell-babel that would
;; parse the document periodically and use overlays to identify the
;; language in each part of the document. Flyspell would then only
;; need to check the overlay, rather than parse the LaTeX every time
;; it checked a word. I didn't know how to implement this reliably,
;; however, since the re-parsing would have to be triggered every time
;; a Babel command is opened or closed. So I tried it this way as a
;; proof of concept, and it works fast enough for my purposes. It
;; might not be fast enough on a slow machine or with a big buffer.
;;
;; The parsing done by this package is very limited, and it will not
;; work with arbitrary LaTeX code. In particular, certain types of
;; nesting will not work. I hope that these restrictions will not in
;; practice impinge on the typical usage of most people. Commands can
;; be nested within environments, and environments can be nested
;; within declarations, but none of these elements can be nested
;; within themselves. Declarations have a scope until the next
;; declaration or the end of the buffer. The first declaration is
;; determined by the final language option passed to the babel
;; \usepackage command.
;;
;; Thus, you can declare the language of a document in a
;; \usepackage[language1,language2]{babel} declaration, and thereafter
;; switch the declared language with \selectlanguage statements. You
;; can select a different language from the current declaration by
;; using the otherlanguage environment or a \foreignlanguage command.
;; You can even nest a \foreignlanguage command within an
;; otherlanguage environment and nest that within a \selectlanguage
;; declaration.
;;
;; This package does not understand complex LaTeX constructs, such as
;; \input. If you want to set the default language for a particular
;; file (for example, one that has no babel declaration, but is going
;; to be \input into a file that does), you can just put a redundant
;; \selectlanguage declaration at the start of the file.
;;
;; By default, \selectlanguage is recognized as a declaration,
;; otherlanguage and its starred variant are recognized as
;; environments, and \foreignlanguage is recognized as a command, all
;; of which are defined by Babel. You can customize this package by
;; specifying other custom LaTeX declarations, environments and
;; commands that you might use as shortcuts to switch languages.
;;
;; By default, an ispell dictionary is invoked with the same name as
;; the current Babel language or dialect, which works in many cases.
;; If your ispell has a different name for that language, you have two
;; options. You can make ispell recognize the Babel name by adding
;; symlinks under that name in your Ispell directory. Alternatively,
;; you can customize flyspell-babel-to-ispell-alist, which maps Babel
;; languages and dialects to Ispell language names.
;; Here is the flow of what happens every time a word is checked:
;;
;; 1. Search backwards for any babel declaration, environment or command.
;; Did we find anything?
;; * No. Stop; do nothing.
;; * Yes. Proceed to step 2.
;;
;; 2. Have we found a command?
;; * Yes. Is it in scope?
;; * Yes. Make sure language is set accordingly.
;; * No. Search backwards again for any babel declaration or
;; environment. Did we find anything?
;; * No. Stop; do nothing.
;; * Yes. Proceed to step 3.
;; * No. Proceed to step 3.
;;
;; 3. Have we found an environment?
;; * Yes. Is it in scope?
;; * Yes. Make sure language is set accordingly.
;; * No. Search backwards again for any babel declaration.
;; Did we find anything?
;; * No. Stop; do nothing.
;; * Yes. Proceed to step 4.
;; * No. Proceed to step 4.
;;
;; 4. Have we found a declaration or \begin{document}?
;; * Yes. Is it a declaration?
;; * Yes. Make sure language is set accordingly.
;; Is it \begin{document}?
;; * Yes. Search back through preamble to the \usepackage{babel}
;; declaration, and set language accordingly.
;; * No. Signal error.
;;; Bugs:
;;
;; flyspell-large-region, which is the fast mode of flyspell, used
;; when checking the entirety of a large buffer, does not work at
;; all, since it depends on launching a single ispell process,
;; whereas flyspell-babel kills and relaunches ispell every time you
;; move from one language to another. For this reason,
;; flyspell-large-region is disabled in buffers using this package.
;;
;; If you use any language switching commands in the preamble (for
;; example to define your own switching commands), then these may
;; interfere with the selection of the correct language when the
;; cursor is in the preamble. All should be well, however, after
;; beginning of the document proper.
;;
;; If you nest environments and declarations in a way that is not
;; allowed by the instructions above, then the wrong language
;; dictionary will probably be selected for text after the improperly
;; nested element.
(defgroup flyspell-babel nil
"Switch flyspell language according to LaTeX babel commands"
:tag "Switch flyspell language according to Babel commands"
:group 'emacs
:prefix "flyspell-babel-")
(defcustom flyspell-babel-to-ispell-alist ()
"Maps LaTeX babel language or dialect names to ispell
dictionaries"
:type 'alist
:group 'flyspell-babel)
(defcustom flyspell-babel-declaration-alist ()
"Maps LaTeX language-switching declarations (other than the
built-in babel \\selectlanguage declaration) to babel
languages"
:type 'alist
:group 'flyspell-babel)
(defcustom flyspell-babel-environment-alist ()
"Maps LaTeX language-switching environments (other than the
built-in babel \"otherlanguage\" environment) to babel languages"
:type 'alist
:group 'flyspell-babel)
(defcustom flyspell-babel-command-alist ()
"Maps LaTeX language-switching commands (other than the
built-in babel \\foreignlanguage command) to babel languages"
:type 'alist
:group 'flyspell-babel)
(defvar flyspell-babel-verbose t
"Whether routinely to report changing from one language to another"
:type 'boolean
:group 'flyspell-babel)
;; We need comment-beginning
(require 'newcomment)
(comment-normalize-vars)
(setq flyspell-babel-declaration-alist-all
(append '(("selectlanguage" "selectlanguage"))
flyspell-babel-declaration-alist))
(setq flyspell-babel-decl-regexp
(concat "\\\\begin[ \t\n]*{document}" "\\|"
(mapconcat (lambda (pair) (concat "\\\\" (car pair) "[ \t\n{]"))
flyspell-babel-declaration-alist-all "\\|")))
(setq flyspell-babel-environment-alist-all
(append '(("otherlanguage" "otherlanguage"))
flyspell-babel-environment-alist))
(setq flyspell-babel-env-regexp
(mapconcat (lambda (pair) (concat "\\\\begin{" (car pair) "}"))
flyspell-babel-environment-alist-all "\\|"))
(setq flyspell-babel-command-alist-all
(append '(("foreignlanguage" "foreignlanguage"))
flyspell-babel-command-alist))
(setq flyspell-babel-com-regexp
(mapconcat (lambda (pair) (concat "\\\\" (car pair) "[ \t\n{]"))
flyspell-babel-command-alist-all "\\|"))
(setq flyspell-babel-decl-env-regexp
(mapconcat 'identity (list flyspell-babel-decl-regexp
flyspell-babel-env-regexp) "\\|"))
(setq flyspell-babel-decl-env-com-regexp
(mapconcat 'identity (list flyspell-babel-decl-regexp
flyspell-babel-env-regexp
flyspell-babel-com-regexp) "\\|"))
(defun flyspell-babel-search-decl-env-com ()
(let ((stop))
(while (not stop)
(if (re-search-backward flyspell-babel-decl-env-com-regexp nil t)
(unless (save-excursion (comment-beginning))
(flyspell-babel-check-com)
(setq stop t))
(setq stop t)))))
(defun flyspell-babel-search-decl-env ()
(let ((stop))
(while (not stop)
(if (re-search-backward flyspell-babel-decl-env-regexp nil t)
(unless (save-excursion (comment-beginning))
(flyspell-babel-check-env)
(setq stop t))
(setq stop t)))))
(defun flyspell-babel-search-decl ()
(let ((stop))
(while (not stop)
(if (re-search-backward flyspell-babel-decl-regexp nil t)
(unless (save-excursion (comment-beginning))
(flyspell-babel-check-decl)
(setq stop t))
(setq stop t)))))
(defun flyspell-babel-switch-dict (lang)
(if (not lang)
(message "%s" "Flyspell-babel error: nil language detected")
(let ((trans (assoc lang flyspell-babel-to-ispell-alist)))
(when trans
;; We have a translation of a language name from babel to ispell
;; nomenclature
(setq lang (cadr trans)))
(when flyspell-babel-debug
(if lang
(flyspell-babel-message (concat "Current ispell dictionary is: "
lang))
(flyspell-babel-message
"Current ispell dictionary is set to nil: not checking")))
(if (not lang)
(setq spellcheck 'nil)
(unless (string= ispell-local-dictionary lang)
(if (member lang flyspell-babel-valid-dictionary-list)
(progn
(ispell-kill-ispell t)
(setq ispell-local-dictionary lang)
(ispell-init-process)
(flyspell-babel-message
(concat "Flyspell-babel -- dictionary changed to: " lang)))
(message
"%s" (concat "Flyspell-babel error: ispell dictionary not
installed for "
lang))))))))
(defun flyspell-babel-verify ()
(let ((here (point))
(lang 'nil)
(inhibit-redisplay 't) ;; Seems to be necessary
(spellcheck 't))
(save-excursion
(flyspell-babel-search-decl-env-com))
spellcheck))
(defun flyspell-babel-check-com ()
(if (looking-at flyspell-babel-com-regexp)
(progn
(if (re-search-forward "\\=\\\\foreignlanguage[ \t\n]*{\\([^}]+\\)}[
\t\n]*{"
nil t)
(setq lang (match-string 1))
(when (re-search-forward "\\=\\\\\\([^{ \t\n]+\\)[ \t\n]*{" nil t)
(setq lang (cadr (assoc (match-string 1)
flyspell-babel-command-alist-all)))))
(backward-char)
(flyspell-babel-forward-sexp)
(if (< here (point))
(flyspell-babel-switch-dict lang)
(flyspell-babel-search-decl-env)))
(flyspell-babel-check-env)))
(defun flyspell-babel-check-env ()
(if (looking-at flyspell-babel-env-regexp)
(progn
(if (re-search-forward "\\=\\\\begin{otherlanguage}[
\t\n]*{\\([^}]+\\)}" nil t)
(setq lang (match-string 1))
(when (re-search-forward "\\=\\\\begin[ \t\n]*{\\([^}]+\\)}" nil t)
(setq lang (cadr (assoc (match-string 1)
flyspell-babel-environment-alist-all)))))
(flyspell-babel-find-matching-end)
(backward-char)
(if (< here (point))
(flyspell-babel-switch-dict lang)
(flyspell-babel-search-decl)))
(flyspell-babel-check-decl)))
(defun flyspell-babel-check-decl ()
(if (looking-at flyspell-babel-decl-regexp)
(progn
(if (looking-at "\\\\selectlanguage[ \t\n]*{\\([^}]+\\)}")
(setq lang (match-string 1))
(if (looking-at "\\\\begin[ \t\n]*{document}")
(progn
(re-search-backward "\\\\usepackage.*[[,]\\([^]]+\\)\\]{babel}"
nil t)
(setq lang (match-string 1)))
(when (looking-at "\\\\\\([^{ \t\n]+\\)")
(setq lang (cadr (assoc (match-string 1)
flyspell-babel-declaration-alist-all))))))
(flyspell-babel-switch-dict lang))
(message "%s" "flyspell-babel internal error!")))
(defun flyspell-babel-forward-sexp (&optional arg)
"Makes sure to ignore comments when using forward-sexp, and
trap errors for unbalanced braces."
(interactive "p")
(let ((parse-sexp-ignore-comments t))
(condition-case nil
(forward-sexp arg)
(scan-error (goto-char (point-max))))))
(defun flyspell-babel-find-matching-end ()
"Find end of current environment, but trap error when there is
no matching \end. Unfortunately, LaTeX-find-matching-end does
not handle commented-out lines specially at all."
(interactive)
(condition-case nil
(LaTeX-find-matching-end)
(error (goto-char (point-max)))))
(defun flyspell-babel-message (mess)
(when flyspell-babel-verbose
(message "%s" mess)))
(defvar flyspell-babel-valid-dictionary-list ()
"Cached value of ispell-valid-dictionary-list")
(setq flyspell-babel-valid-dictionary-list
(ispell-valid-dictionary-list))
;;;;;;;;;; Here is our hook into flyspell:
;; This is right for future invocations of flyspell-mode ...
(put 'latex-mode 'flyspell-mode-predicate 'flyspell-babel-verify)
(add-hook 'LaTeX-mode-hook '(lambda ()
(make-local-variable 'flyspell-large-region)
(setq flyspell-large-region (point-max))))
;;
;; ... but what if we have been loaded from a mode hook, and flyspell-mode
;; has already been turned on?
(when (and flyspell-mode
(eq major-mode 'latex-mode))
;; already buffer-local
(setq flyspell-generic-check-word-p 'flyspell-babel-verify)
(make-local-variable 'flyspell-large-region)
(setq flyspell-large-region (point-max)))
;; Here's another possible way to do it, which has the advantage that
;; it doesn't add this to all Latex files, but only to those that use
;; babel. The problem is that \include'd files may not have the
;; necessary \usepackage{babel} declaration. It seems that the
;; TeX-master variable does not prompt hooks to be run based on a
;; preamble in another document.
;;
;;(TeX-add-style-hook "babel"
;; (function (lambda ()
;; ;; flyspell-generic-check-word-p is already buffer-local
;; (setq flyspell-generic-check-word-p 'flyspell-babel-verify)
;; (make-local-variable 'flyspell-large-region)
;; (setq flyspell-large-region (point-max)))))
(provide 'flyspell-babel)
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- flyspell-babel.el -- Switch flyspell lang according to LaTeX commands,
Peter Heslin <=