[Top][All Lists]
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
flyspell-babel.el, v. 1.2
From: |
Peter Heslin |
Subject: |
flyspell-babel.el, v. 1.2 |
Date: |
Mon, 13 Sep 2004 09:52:54 -0500 |
User-agent: |
slrn/0.9.8.0 (Linux) |
This version removes the dependency on AUCTeX, and fixes a bug.
;; 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.2
;;
;; 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 to be installed (version 1.7f or
;; better) and flyspell-mode to be active.
;;
;; 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.
;;
;; I have only tested this with GNU Emacs.
;;; 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.
;;
;; 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. If you map a
;; language to 'nil, that means not to spell-check that language,
;; which can be useful for languages without an ispell dictionary.
;;; Customization:
;;
;; The code that follows is an example of my customization of this
;; package. The first form tells the package to turn on debugging
;; messages to see when we switch dictionaries as we move from place
;; to place. The second tells it not to spell-check the languages
;; "latin" and "ibycus" (an encoding for ancient Greek), since I don't
;; have ispell dictionaries for them; it also tells it to translate
;; the Babel language "french" to the ispell dictionary "francais".
;; The third form defines some language-switching shortcut commands,
;; so that I can more easily say \fr{merci} and \itl{grazie}. The
;; fourth defines some short-cut environments, since \begin{german} is
;; a lot easier to write than \begin{otherlanguage}{german}. The last
;; form defines some shortcut declarations for switching between
;; American and British spelling.
;;
;; (setq flyspell-babel-verbose t)
;;
;; (setq flyspell-babel-to-ispell-alist
;; '(("latin" nil)
;; ("ibycus" nil)
;; ("french" "francais")))
;;
;; (setq flyspell-babel-command-alist
;; '(("lat" "latin")
;; ("gk" "ibycus")
;; ("fr" "french")
;; ("ger" "german")
;; ("itl" "italian")))
;;
;; (setq flyspell-babel-environment-alist
;; '(("latin" "latin")
;; ("greek" "ibycus")
;; ("french" "french")
;; ("german" "german")
;; ("italian" "italian")))
;;
;; (setq flyspell-babel-declaration-alist
;; '(("yank" "american")
;; ("brit" "british")))
;;
;; Here is the LaTeX code that defines these short-cuts:
;;
;; \usepackage[ibycus,latin,french,german,italian,british,american]{babel}
;;
;; \newcommand{\lat}[1]{\foreignlanguage{latin}{\emph{#1}}}
;; \newenvironment{latin}{\begin{otherlanguage}{latin}}{\end{otherlanguage}}
;;
;; \newcommand{\fr}[1]{\foreignlanguage{french}{\emph{#1}}}
;; \newenvironment{french}{\begin{otherlanguage}{french}}{\end{otherlanguage}}
;;
;; \newcommand{\ger}[1]{\foreignlanguage{german}{\emph{#1}}}
;; \newenvironment{german}{\begin{otherlanguage}{german}}{\end{otherlanguage}}
;;
;; \newcommand{\itl}[1]{\foreignlanguage{italian}{\emph{#1}}}
;; \newenvironment{italian}{\begin{otherlanguage}{italian}}{\end{otherlanguage}}
;;
;; \newcommand{\yank}{\selectlanguage{american}}
;; \newcommand{\brit}{\selectlanguage{british}}
;;; Implementation
;;
;; 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 added or removed. 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.
;;
;; I also tried a simpler version of the current implementation that
;; went back through the file, trying each Babel command until it
;; found one that was in scope, but for me it was too slow when
;; dealing with large or complex buffers, since the time taken to
;; spell-check each word went up with each language-switch intervening
;; between point and the location of the Babel command currently in
;; force. In order to place a bound on that time-lag, I have
;; implemented this version to require the strict nesting of
;; declarations, environments and commands. This means that there is
;; now a maximum of 4 backward searches in the document to find Babel
;; commands and a maximum of 2 forward searches to see if things are
;; in scope. For me, this runs acceptably fast, even though this
;; happens every time point moves to a new word. At any rate, the
;; time-lag seems less than the one introduced when I switched from
;; ispell to aspell (which gives better suggested spellings).
;;
;; 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. If there is
;; no such declaration, do nothing.
;; * 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 there exist any language switching commands in the preamble (for
;; example, using them to define your own switching commands with
;; \newcommand), 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.
;;; Changes
;;
;; 1.2 Removed dependency on AUCTeX and newcomment and fixed bug when
;; disabling flyspell-large-region
;; 1.1 Removed error report when \usepackage{babel} not present
;; 1.0 Initial public release
(require 'flyspell)
(defgroup flyspell-babel nil
"Switch flyspell language according to LaTeX babel commands"
:tag "Switch flyspell language according to Babel commands"
:group 'tex
: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)
(defcustom flyspell-babel-verbose nil
"Whether routinely to report changing from one language to another"
:type 'boolean
:group 'flyspell-babel)
(defvar flyspell-babel-valid-dictionary-list ()
"Cached value of ispell-valid-dictionary-list")
(setq flyspell-babel-valid-dictionary-list
(ispell-valid-dictionary-list))
(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-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-search-decl-env-com ()
(let ((stop))
(while (not stop)
(if (re-search-backward flyspell-babel-decl-env-com-regexp nil t)
(unless (flyspell-babel-in-comment-p)
(flyspell-babel-check-com)
(setq stop t))
(setq stop t)))))
(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))
(if (re-search-forward "\\=\\\\\\([^{ \t\n]+\\)[ \t\n]*{" nil t)
(setq lang (cadr
(assoc (match-string 1)
flyspell-babel-command-alist-all)))
(flyspell-babel-message "internal error")))
(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-search-decl-env ()
(let ((stop))
(while (not stop)
(if (re-search-backward flyspell-babel-decl-env-regexp nil t)
(unless (flyspell-babel-in-comment-p)
(flyspell-babel-check-env)
(setq stop t))
(setq stop t)))))
(defun flyspell-babel-check-env ()
(if (looking-at flyspell-babel-env-regexp)
(let ((env))
(if (looking-at "\\=\\\\begin{otherlanguage}[ \t\n]*{\\([^}]+\\)}")
(setq env "otherlanguage" lang (match-string 1))
(if (looking-at "\\=\\\\begin[ \t\n]*{\\([^}]+\\)}")
(setq env (match-string 1)
lang (cadr
(assoc env flyspell-babel-environment-alist-all)))
(flyspell-babel-message "internal error")))
(flyspell-babel-find-matching-end env)
(backward-char)
(if (< here (point))
(flyspell-babel-switch-dict lang)
(flyspell-babel-search-decl)))
(flyspell-babel-check-decl)))
(defun flyspell-babel-search-decl ()
(let ((stop))
(while (not stop)
(if (re-search-backward flyspell-babel-decl-regexp nil t)
(unless (flyspell-babel-in-comment-p)
(flyspell-babel-check-decl)
(setq stop t))
(setq stop t)))))
(defun flyspell-babel-check-decl ()
(let ((proceed t))
(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}")
(if (re-search-backward
"\\\\usepackage.*[[,]\\([^]]+\\)\\]{babel}" nil t)
(setq lang (match-string 1))
(setq proceed nil))
(when (looking-at "\\\\\\([^{ \t\n]+\\)")
(setq lang
(cadr (assoc (match-string 1)
flyspell-babel-declaration-alist-all))))))
(when proceed
(flyspell-babel-switch-dict lang)))
(flyspell-babel-message "internal error"))))
(defun flyspell-babel-switch-dict (lang)
(if (not lang)
(flyspell-babel-message "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)))
(if (not lang)
(progn
(setq spellcheck 'nil)
(flyspell-babel-message
"current dictionary is set to nil: not checking"))
(if (string= ispell-local-dictionary lang)
(flyspell-babel-message
(concat "current dictionary remains: " 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 "dictionary changed to: " lang)))
(setq spellcheck 'nil)
(flyspell-babel-message
(concat "Warning: no dictionary installed for "
lang) t)))))))
(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 (env)
"Find end of current environment, or end of file when there is
no matching \end."
(interactive)
(let ((regexp (concat "\\\\\\(begin\\|end\\)[ \t\n]*{" env "}"))
(level 0)
(proceed t))
(while proceed
(if (re-search-forward regexp nil t)
(let ((match (match-string 1)))
(unless (flyspell-babel-in-comment-p)
(if (string= match "begin")
(setq level (1+ level))
(if (string= match "end")
(setq level (1- level))
(flyspell-babel-message "internal error")))))
(goto-char (point-max))
(setq proceed nil))
(when (= 0 level)
(setq proceed nil)))))
(defun flyspell-babel-in-comment-p ()
"Are we in a Latex comment? (Taken from auctex' tex.el)"
(if (or (bolp)
(null comment-start-skip)
(eq (preceding-char) ?\r))
nil
(save-excursion
(let ((pos (point)))
(re-search-backward "^\\|\r" nil t)
(or (looking-at comment-start-skip)
(re-search-forward comment-start-skip pos t))))))
(defun flyspell-babel-message (mess &optional force)
(when (or flyspell-babel-verbose force)
(message "Flyspell-babel -- %s" mess)))
;;;;;;;;;; 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 'nil)))
;;
;; ... 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 'nil))
(provide 'flyspell-babel)
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- flyspell-babel.el, v. 1.2,
Peter Heslin <=