[Top][All Lists]
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
flyspell-babel.el -- Switch flyspell language according to LaTeX command
From: |
public |
Subject: |
flyspell-babel.el -- Switch flyspell language according to LaTeX commands |
Date: |
Tue, 28 Jun 2005 16:25:47 +0100 |
User-agent: |
Gnus/5.11 (Gnus v5.11) Emacs/22.0.50 (gnu/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/Software/Emacs/Download/flyspell-babel.el
;; Version: 3.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 package makes
;; flyspell use the correct dictionary for the language used in each
;; part of a LaTeX file. There are some restrictions on the usage of
;; Babel commands, on which see below.
;;
;; flyspell-babel requires ispell-multi to be installed; it may be
;; found in the same directory indicated by the URL above. It also
;; needs flyspell to be installed (version 1.7f or better) and
;; flyspell-mode to be active. If you are using Emacs 21.3 or
;; earlier, you will need to upgrade to ispell.el version 3.6,
;; available from http://www.kdstevens.com/~stevens/ispell-page.html.
;;
;; To use this file, put it somewhere in your load-path, and add this
;; to your .emacs file:
;;
;; (autoload 'flyspell-babel-setup "flyspell-babel")
;; (add-hook 'latex-mode-hook 'flyspell-babel-setup)
;;
;; 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:
;;
;; This package examines the LaTeX code around point to discover the
;; language of the current text, and then it caches that value in a
;; text-property. This means that, if you modify or add a Babel
;; command to change the language of some text, the current language
;; may be out of sync with the cached value. In this case, you can
;; just stop typing for a bit, and the surrounding text will be
;; re-parsed, and the new, correct language should be determined. The
;; length of time that Emacs is idle before this re-parsing happens is
;; configurable via the variable flyspell-babel-delay (default is 5
;; seconds).
;;
;; The parsing done by this package has its limits limited, and so it
;; will not work with arbitrary LaTeX code. I hope that these
;; restrictions will not in practice impinge on the typical usage of
;; most people. The first language declaration is usually determined
;; by the final language option passed to the babel \usepackage
;; command, which takes effect after \begin{document}. Thereafter,
;; you can switch the declared language with \selectlanguage
;; statements, otherlanguage environments, and \foreignlanguage
;; commands. You can also define your own language-switching
;; commands, and register these with flyspell-babel.
;;
;; 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. You can
;; limit the scope of a \selectlanguage declaration by putting an
;; opening brace immediately before it, and flyspell-babel will
;; respect that scoping, but not otherwise, since that would make the
;; task of parsing too complex.
;;
;; 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}}
;;; Bugs:
;;
;; If a word comes to be erroneously highlighted as misspelled because
;; you have recently changed a Babel command, then the normal thing to
;; do is to wait a few seconds until the idle-timer runs the code to
;; re-parse the text. This works, except when the new Babel language
;; is mapped to 'nil, meaning that it shouldn't be spell-checked at
;; all. When this is the case, the spell-checking mechanism is
;; by-passed completely, so the erroneous highlighting will not be
;; removed, since those words will not be re-recognized as "correctly"
;; spelled. The best thing to do in this case is to switch
;; flyspell-mode off and on again, which will remove all flyspell
;; highlighting.
;;; Changes
;;
;; 3.0 Re-factored to use new ispell-multi.el.
;; 2.0 Major re-write. Instead of partially-parsing every time a word
;; is spell-checked, we now cache the language in a text property,
;; and use an idle timer to re-parse in case the language has
;; changed.
;; 1.3 Only use ispell-valid-dictionary-list if it's available
;; 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 'ispell-multi)
(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)
(defcustom flyspell-babel-delay 5
"Seconds of idleness before current Babel block is re-parsed."
:type 'integer
:group 'flyspell-nxml-mode)
(defvar flyspell-babel-declaration-alist-all nil)
(defvar flyspell-babel-decl-regexp nil)
(defvar flyspell-babel-environment-alist-all nil)
(defvar flyspell-babel-env-regexp nil)
(defvar flyspell-babel-command-alist-all nil)
(defvar flyspell-babel-com-regexp nil)
(defvar flyspell-babel-decl-env-com-regexp nil)
(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-com-regexp
(mapconcat 'identity (list flyspell-babel-decl-regexp
flyspell-babel-env-regexp
flyspell-babel-com-regexp) "\\|"))
(defun flyspell-babel-parse ()
(save-excursion
(let ((current-position (point))
; (inhibit-redisplay 't))
)
(save-excursion
(flyspell-babel-parse-block)))))
(defun flyspell-babel-parse-block ()
(let (beg end lang macro-begin)
(if (flyspell-babel-find-previous-macro nil)
(progn
;; We standardize on having the language switch happening
;; just *after* regexp-match, since re-search-backward
;; will fail to match when we are in the middle of a macro.
(setq beg (match-end 0))
(setq macro-begin (point))
(if (looking-at flyspell-babel-com-regexp)
(flyspell-babel-check-com)
(if (looking-at flyspell-babel-env-regexp)
(flyspell-babel-check-env)
(if (looking-at flyspell-babel-decl-regexp)
(flyspell-babel-check-decl)
(flyspell-babel-message "internal error 1"))))
(setq end (point))
(if (< current-position end)
(progn
;; Got it
;; As an optimization, we flag the text after point
;; as far as the start of the next Babel command.
(goto-char current-position)
(when (and (flyspell-babel-find-next-macro end)
(< current-position (point))
(< (point) end))
(setq end (1+ (point)))))
(goto-char macro-begin)
(flyspell-babel-parse-block))
;; Wind up the calling stack
(flyspell-babel-flag-region beg end lang))
;; No Babel command found
(flyspell-babel-flag-region (point-min) (1+ current-position)
"no-command-found"))))
(defun flyspell-babel-find-previous-macro (end)
(let ((found))
(while (and (not found)
(re-search-backward flyspell-babel-decl-env-com-regexp end t))
(setq found (not (flyspell-babel-in-comment-p))))
found))
(defun flyspell-babel-find-next-macro (end)
(let ((found))
(while (and (not found)
(re-search-forward flyspell-babel-decl-env-com-regexp end t))
(setq found (not (flyspell-babel-in-comment-p))))
found))
(defun flyspell-babel-check-com ()
(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 2")))
(backward-char)
(flyspell-babel-forward-sexp))
(defun flyspell-babel-check-env ()
(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 3")))
(flyspell-babel-find-matching-end env)
; (backward-char)))
))
(defun flyspell-babel-check-decl ()
(if (looking-at "\\\\begin[ \t\n]*{document}")
(progn
(if (save-excursion
(re-search-backward
"\\\\usepackage.*[[,]\\([^]]+\\)\\]{babel}" nil t))
(setq lang (match-string 1))
(setq lang "no-command-found"))
(goto-char (point-max)))
(if (looking-at "\\\\selectlanguage[ \t\n]*{\\([^}]+\\)}")
(setq lang (match-string 1))
(if (and (looking-at "\\\\\\([^{ \t\n]+\\)")
(cadr (assoc (match-string 1)
flyspell-babel-declaration-alist-all)))
(setq lang
(cadr (assoc (match-string 1)
flyspell-babel-declaration-alist-all)))
(flyspell-babel-message "internal error 4")))
(unless (bobp)
(backward-char))
(if (looking-at "{")
(flyspell-babel-forward-sexp)
(goto-char (point-max)))))
(defun flyspell-babel-flag-region (beg end lang)
(let ((trans (assoc lang flyspell-babel-to-ispell-alist))
(buffer-modified-before (buffer-modified-p)))
(when trans
;; We have a translation of a Babel language name to ispell
;; nomenclature
(setq lang (cadr trans)))
(cond
((not lang)
;; A parsed region with a nil dict -- don't spell-check.
(setq lang "void"))
((equal lang "no-command-found")
(setq lang "default"))
((and ispell-multi-valid-dictionary-list
(not (member lang ispell-multi-valid-dictionary-list)))
;; A parsed region with an uninstalled dict
(message "Flyspell-babel warning: no dictionary installed for %s" lang)
(setq lang "void")))
(put-text-property (1+ beg) end 'ispell-multi-lang lang)
(set-buffer-modified-p buffer-modified-before)))
(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)
(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 5")))))
(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? (Stolen from auctex tex.el)"
(save-match-data
(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)))
(defun flyspell-babel-start ()
(setq ispell-multi-flyspell-callback 'flyspell-babel-parse)
(make-local-variable 'flyspell-large-region)
(setq flyspell-large-region 'nil)
(flyspell-mode 1)
(setq flyspell-generic-check-word-p 'ispell-multi-verify)
(ispell-multi-idler-setup flyspell-babel-delay)
(ispell-multi-hack-flyspell-modeline))
(defun flyspell-babel-stop ()
; (ispell-multi-idler-cancel)
(setq flyspell-generic-check-word-p nil)
(ispell-multi-unhack-flyspell-modeline)
(flyspell-mode -1))
(define-minor-mode flyspell-babel-mode
"Mode to make flyspell language selection obey LaTeX Babel commands" nil
:group 'flyspell-babel
(if flyspell-babel-mode
(flyspell-babel-start)
(flyspell-babel-stop)))
(defun flyspell-babel-setup ()
(flyspell-babel-mode 1))
(provide 'flyspell-babel)
;;; TODO:
;; check for arrival of input when parsing and quit?
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- flyspell-babel.el -- Switch flyspell language according to LaTeX commands,
public <=