cvsdist 65c016
;;; po-mode.el -- major mode for GNU gettext PO files
cvsdist 65c016
cvsdist 65c016
;; Copyright (C) 1995-1999, 2000-2002 Free Software Foundation, Inc.
cvsdist 65c016
cvsdist 65c016
;; Authors: François Pinard <pinard@iro.umontreal.ca>
cvsdist 65c016
;;          Greg McGary <gkm@magilla.cichlid.com>
cvsdist 65c016
;; Keywords: i18n gettext
cvsdist 65c016
;; Created: 1995
cvsdist c08065
cvsdist c08065
;; This file is part of GNU gettext.
cvsdist c08065
cvsdist c08065
;; GNU gettext is free software; you can redistribute it and/or modify
cvsdist c08065
;; it under the terms of the GNU General Public License as published by
cvsdist c08065
;; the Free Software Foundation; either version 2, or (at your option)
cvsdist c08065
;; any later version.
cvsdist c08065
cvsdist c08065
;; GNU gettext is distributed in the hope that it will be useful,
cvsdist c08065
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
cvsdist c08065
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
cvsdist c08065
;; GNU General Public License for more details.
cvsdist c08065
cvsdist c08065
;; You should have received a copy of the GNU General Public License
cvsdist c08065
;; along with GNU Emacs; see the file COPYING.  If not, write to the
cvsdist c08065
;; Free Software Foundation, 59 Temple Place - Suite 330, Boston,
cvsdist c08065
;; MA 02111-1307, USA.
cvsdist c08065
cvsdist 65c016
;;; Commentary:
cvsdist c08065
cvsdist 65c016
;; This package provides the tools meant to help editing PO files,
cvsdist 65c016
;; as documented in the GNU gettext user's manual.  See this manual
cvsdist 65c016
;; for user documentation, which is not repeated here.
cvsdist 65c016
cvsdist 65c016
;; To install, merely put this file somewhere GNU Emacs will find it,
cvsdist 65c016
;; then add the following lines to your .emacs file:
cvsdist 65c016
;;
cvsdist 65c016
;;   (autoload 'po-mode "po-mode"
cvsdist 65c016
;;             "Major mode for translators to edit PO files" t)
cvsdist 65c016
;;   (setq auto-mode-alist (cons '("\\.po\\'\\|\\.po\\." . po-mode)
cvsdist 65c016
;;				  auto-mode-alist))
cvsdist 65c016
;;
cvsdist d31aa3
;; To use the right coding system automatically under Emacs 20 or newer,
cvsdist d31aa3
;; also add:
cvsdist 65c016
;;
cvsdist d31aa3
;;   (autoload 'po-find-file-coding-system "po-compat")
cvsdist 65c016
;;   (modify-coding-system-alist 'file "\\.po\\'\\|\\.po\\."
cvsdist 65c016
;;				  'po-find-file-coding-system)
cvsdist 65c016
;;
cvsdist 65c016
;; You may also adjust some variables, below, by defining them in your
cvsdist 65c016
;; '.emacs' file, either directly or through command 'M-x customize'.
cvsdist 65c016
cvsdist 65c016
;;; Code:
cvsdist 65c016

cvsdist d31aa3
(defconst po-mode-version-string "2.01" "\
cvsdist d31aa3
Version number of this version of po-mode.el.")
cvsdist d31aa3
cvsdist 65c016
;;; Emacs portability matters - part I.
cvsdist 65c016
;;; Here is the minimum for customization to work.  See part II.
cvsdist c08065
cvsdist c08065
;; Identify which Emacs variety is being used.
cvsdist 65c016
;; This file supports:
cvsdist 65c016
;;   - XEmacs (version 19 and above) -> po-XEMACS = t,
cvsdist 65c016
;;   - GNU Emacs (version 20 and above) -> po-EMACS20 = t,
cvsdist 65c016
;;   - GNU Emacs (version 19) -> no flag.
cvsdist c08065
(eval-and-compile
cvsdist 65c016
  (cond ((string-match "XEmacs\\|Lucid" emacs-version)
cvsdist c08065
	 (setq po-EMACS20 nil po-XEMACS t))
cvsdist c08065
	((and (string-lessp "19" emacs-version) (featurep 'faces))
cvsdist c08065
	 (setq po-EMACS20 t po-XEMACS nil))
cvsdist c08065
	(t (setq po-EMACS20 nil po-XEMACS nil))))
cvsdist c08065
cvsdist c08065
;; Experiment with Emacs LISP message internationalisation.
cvsdist c08065
(eval-and-compile
cvsdist c08065
  (or (fboundp 'set-translation-domain)
cvsdist c08065
      (defsubst set-translation-domain (string) nil))
cvsdist c08065
  (or (fboundp 'translate-string)
cvsdist c08065
      (defsubst translate-string (string) string)))
cvsdist c08065
(defsubst _ (string) (translate-string string))
cvsdist c08065
(defsubst N_ (string) string)
cvsdist c08065
cvsdist 65c016
;; Handle missing 'customs' package.
cvsdist c08065
(eval-and-compile
cvsdist c08065
  (condition-case ()
cvsdist c08065
      (require 'custom)
cvsdist c08065
    (error nil))
cvsdist c08065
  (if (and (featurep 'custom) (fboundp 'custom-declare-variable))
cvsdist c08065
      nil
cvsdist c08065
    (defmacro defgroup (&rest args)
cvsdist c08065
      nil)
cvsdist c08065
    (defmacro defcustom (var value doc &rest args)
cvsdist c08065
      (` (defvar (, var) (, value) (, doc))))))
cvsdist 65c016

cvsdist 65c016
;;; Customisation.
cvsdist 65c016
cvsdist 65c016
(defgroup po nil
cvsdist 65c016
  "Major mode for editing PO files"
cvsdist 65c016
  :group 'i18n)
cvsdist 65c016
cvsdist 65c016
(defcustom po-auto-edit-with-msgid nil
cvsdist 65c016
  "*Automatically use msgid when editing untranslated entries."
cvsdist 65c016
  :type 'boolean
cvsdist 65c016
  :group 'po)
cvsdist 65c016
cvsdist 65c016
(defcustom po-auto-fuzzy-on-edit nil
cvsdist 65c016
  "*Automatically mark entries fuzzy when being edited."
cvsdist 65c016
  :type 'boolean
cvsdist 65c016
  :group 'po)
cvsdist 65c016
cvsdist 65c016
(defcustom po-auto-select-on-unfuzzy nil
cvsdist 65c016
  "*Automatically select some new entry while making an entry not fuzzy."
cvsdist 65c016
  :type 'boolean
cvsdist 65c016
  :group 'po)
cvsdist 65c016
cvsdist 65c016
(defcustom po-auto-replace-revision-date t
cvsdist 65c016
  "*Automatically revise date in headers.  Value is nil, t, or ask."
cvsdist 65c016
  :type '(choice (const nil)
cvsdist 65c016
		 (const t)
cvsdist 65c016
		 (const ask))
cvsdist 65c016
  :group 'po)
cvsdist 65c016
cvsdist 65c016
(defcustom po-default-file-header "\
cvsdist 65c016
# SOME DESCRIPTIVE TITLE.
cvsdist 65c016
# Copyright (C) YEAR Free Software Foundation, Inc.
cvsdist 65c016
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
cvsdist 65c016
#
cvsdist 65c016
#, fuzzy
cvsdist 65c016
msgid \"\"
cvsdist 65c016
msgstr \"\"
cvsdist 65c016
\"Project-Id-Version: PACKAGE VERSION\\n\"
cvsdist 65c016
\"PO-Revision-Date: YEAR-MO-DA HO:MI +ZONE\\n\"
cvsdist 65c016
\"Last-Translator: FULL NAME <EMAIL@ADDRESS>\\n\"
cvsdist 65c016
\"Language-Team: LANGUAGE <LL@li.org>\\n\"
cvsdist 65c016
\"MIME-Version: 1.0\\n\"
cvsdist 65c016
\"Content-Type: text/plain; charset=CHARSET\\n\"
cvsdist 65c016
\"Content-Transfer-Encoding: 8bit\\n\"
cvsdist 65c016
"
cvsdist 65c016
  "*Default PO file header."
cvsdist 65c016
  :type 'string
cvsdist 65c016
  :group 'po)
cvsdist 65c016
cvsdist d31aa3
(defcustom po-translation-project-address
cvsdist d31aa3
  "translation@iro.umontreal.ca"
cvsdist d31aa3
  "*Electronic mail address of the Translation Project.
cvsdist d31aa3
Typing \\[po-send-mail] (normally bound to `M') the user will send the PO file
cvsdist d31aa3
to this email address."
cvsdist d31aa3
  :type 'string
cvsdist d31aa3
  :group 'po)
cvsdist d31aa3
cvsdist d31aa3
(defcustom po-translation-project-mail-label "TP-Robot"
cvsdist d31aa3
  "*Subject label when sending the PO file to `po-translation-project-address'.
cvsdist d31aa3
Don't change it when you send PO files to \"translation@iro.umontreal.ca\", the
cvsdist d31aa3
Translation Project Robot at http://www.iro.umontreal.ca/contrib/po/HTML/.  If
cvsdist d31aa3
the label is different, your submission will be consiedered as a regular mail
cvsdist d31aa3
and not stored at the TP site and also not forwarded to the package maintainer."
cvsdist d31aa3
  :type 'string
cvsdist d31aa3
  :group 'po)
cvsdist d31aa3
cvsdist 65c016
(defcustom po-highlighting (or po-EMACS20 po-XEMACS)
cvsdist 65c016
  "*Highlight text whenever appropriate, when non-nil.
cvsdist 65c016
However, on older Emacses, a yet unexplained highlighting bug causes files
cvsdist 65c016
to get mangled."
cvsdist 65c016
  :type 'boolean
cvsdist 65c016
  :group 'po)
cvsdist 65c016
cvsdist 65c016
(defcustom po-highlight-face 'highlight
cvsdist 65c016
  "*The face used for PO mode highlighting.  For Emacses with overlays.
cvsdist 65c016
Possible values are 'highlight', 'modeline', 'secondary-selection',
cvsdist 65c016
'region', and 'underline'.
cvsdist 65c016
This variable can be set by the user to whatever face they desire.
cvsdist 65c016
It's most convenient if the cursor color and highlight color are
cvsdist 65c016
slightly different."
cvsdist 65c016
  :type 'face
cvsdist 65c016
  :group 'po)
cvsdist c08065
cvsdist 65c016
(defcustom po-team-name-to-code
cvsdist 65c016
  ;; All possible languages, a complete ISO 639 list and a little more.
cvsdist 65c016
  '(("LANGUAGE" . "LL")
cvsdist 65c016
    ("(Afan) Oromo" . "om")
cvsdist 65c016
    ("Abkhazian" . "ab")
cvsdist 65c016
    ("Afar" . "aa")
cvsdist 65c016
    ("Afrikaans" . "af")
cvsdist 65c016
    ("Albanian" . "sq")
cvsdist 65c016
    ("Amharic" . "am")
cvsdist 65c016
    ("Arabic" . "ar")
cvsdist d31aa3
    ("Argentinian" . "es_AR")
cvsdist 65c016
    ("Armenian" . "hy")
cvsdist 65c016
    ("Assamese" . "as")
cvsdist 65c016
    ("Avestan" . "ae")
cvsdist 65c016
    ("Aymara" . "ay")
cvsdist 65c016
    ("Azerbaijani" . "az")
cvsdist 65c016
    ("Bashkir" . "ba")
cvsdist 65c016
    ("Basque" . "eu")
cvsdist 65c016
    ("Belarusian" . "be")
cvsdist 65c016
    ("Bengali" . "bn")
cvsdist 65c016
    ("Bihari" . "bh")
cvsdist 65c016
    ("Bislama" . "bi")
cvsdist 65c016
    ("Bosnian" . "bs")
cvsdist 65c016
    ("Brazilian Portuguese" . "pt_BR")
cvsdist 65c016
    ("Breton" . "br")
cvsdist 65c016
    ("Bulgarian" . "bg")
cvsdist 65c016
    ("Burmese" . "my")
cvsdist 65c016
    ("Catalan" . "ca")
cvsdist 65c016
    ("Chamorro" . "ch")
cvsdist 65c016
    ("Chechen" . "ce")
cvsdist 65c016
    ("Chinese" . "zh")
cvsdist d31aa3
    ("Chinese (simplified)" . "zh_CN")
cvsdist d31aa3
    ("Chinese (traditional)" . "zh_TW")
cvsdist 65c016
    ("Church Slavic" . "cu")
cvsdist 65c016
    ("Chuvash" . "cv")
cvsdist 65c016
    ("Cornish" . "kw")
cvsdist 65c016
    ("Corsican" . "co")
cvsdist 65c016
    ("Croatian" . "hr")
cvsdist 65c016
    ("Czech" . "cs")
cvsdist 65c016
    ("Danish" . "da")
cvsdist 65c016
    ("Dutch" . "nl")
cvsdist 65c016
    ("Dzongkha" . "dz")
cvsdist 65c016
    ("English" . "en")
cvsdist 65c016
    ("Esperanto" . "eo")
cvsdist 65c016
    ("Estonian" . "et")
cvsdist 65c016
    ("Faroese" . "fo")
cvsdist 65c016
    ("Fijian" . "fj")
cvsdist 65c016
    ("Finnish" . "fi")
cvsdist 65c016
    ("French" . "fr")
cvsdist 65c016
    ("Frisian" . "fy")
cvsdist 65c016
    ("Galician" . "gl")
cvsdist 65c016
    ("Georgian" . "ka")
cvsdist 65c016
    ("German" . "de")
cvsdist 65c016
    ("Greek" . "el")
cvsdist 65c016
    ("Guarani" . "gn")
cvsdist 65c016
    ("Gujarati" . "gu")
cvsdist 65c016
    ("Hausa" . "ha")
cvsdist 65c016
    ("Hebrew" . "he")
cvsdist 65c016
    ("Herero" . "hz")
cvsdist 65c016
    ("Hindi" . "hi")
cvsdist 65c016
    ("Hiri Motu" . "ho")
cvsdist 65c016
    ("Hungarian" . "hu")
cvsdist 65c016
    ("Icelandic" . "is")
cvsdist d31aa3
    ("Ido" . "io")
cvsdist 65c016
    ("Indonesian" . "id")
cvsdist 65c016
    ("Interlingua" . "ia")
cvsdist 65c016
    ("Interlingue" . "ie")
cvsdist 65c016
    ("Inuktitut" . "iu")
cvsdist 65c016
    ("Inupiak" . "ik")
cvsdist 65c016
    ("Irish" . "ga")
cvsdist 65c016
    ("Italian" . "it")
cvsdist 65c016
    ("Japanese" . "ja")
cvsdist d31aa3
    ("Javanese" . "jv")
cvsdist 65c016
    ("Kalaallisut" . "kl")
cvsdist 65c016
    ("Kannada" . "kn")
cvsdist 65c016
    ("Kashmiri" . "ks")
cvsdist 65c016
    ("Kazakh" . "kk")
cvsdist 65c016
    ("Khmer" . "km")
cvsdist 65c016
    ("Kikuyu" . "ki")
cvsdist 65c016
    ("Kinyarwanda" . "rw")
cvsdist 65c016
    ("Kirghiz" . "ky")
cvsdist 65c016
    ("Kirundi" . "rn")
cvsdist 65c016
    ("Komi" . "kv")
cvsdist 65c016
    ("Konkani" . "kok")
cvsdist 65c016
    ("Korean" . "ko")
cvsdist 65c016
    ("Kuanyama" . "kj")
cvsdist 65c016
    ("Kurdish" . "ku")
cvsdist 65c016
    ("Laotian" . "lo")
cvsdist 65c016
    ("Latin" . "la")
cvsdist 65c016
    ("Latvian" . "lv")
cvsdist 65c016
    ("Letzeburgesch" . "lb")
cvsdist 65c016
    ("Lingala" . "ln")
cvsdist 65c016
    ("Lithuanian" . "lt")
cvsdist 65c016
    ("Macedonian" . "mk")
cvsdist 65c016
    ("Malagasy" . "mg")
cvsdist 65c016
    ("Malay" . "ms")
cvsdist 65c016
    ("Malayalam" . "ml")
cvsdist 65c016
    ("Maltese" . "mt")
cvsdist 65c016
    ("Manipuri" . "mni")
cvsdist 65c016
    ("Manx" . "gv")
cvsdist 65c016
    ("Maori" . "mi")
cvsdist 65c016
    ("Marathi" . "mr")
cvsdist 65c016
    ("Marshall" . "mh")
cvsdist 65c016
    ("Moldavian" . "mo")
cvsdist 65c016
    ("Mongolian" . "mn")
cvsdist 65c016
    ("Nauru" . "na")
cvsdist 65c016
    ("Navajo" . "nv")
cvsdist 65c016
    ("Ndonga" . "ng")
cvsdist 65c016
    ("Nepali" . "ne")
cvsdist 65c016
    ("North Ndebele" . "nd")
cvsdist 65c016
    ("Northern Sami" . "se")
cvsdist 65c016
    ("Norwegian Bokmal" . "nb")
cvsdist 65c016
    ("Norwegian Nynorsk" . "nn")
cvsdist 65c016
    ("Norwegian" . "no")
cvsdist 65c016
    ("Nyanja" . "ny")
cvsdist 65c016
    ("Occitan" . "oc")
cvsdist 65c016
    ("Oriya" . "or")
cvsdist 65c016
    ("Ossetian" . "os")
cvsdist 65c016
    ("Pali" . "pi")
cvsdist 65c016
    ("Pashto" . "ps")
cvsdist 65c016
    ("Persian" . "fa")
cvsdist 65c016
    ("Polish" . "pl")
cvsdist 65c016
    ("Portuguese" . "pt")
cvsdist 65c016
    ("Punjabi" . "pa")
cvsdist 65c016
    ("Quechua" . "qu")
cvsdist 65c016
    ("Rhaeto-Roman" . "rm")
cvsdist 65c016
    ("Romanian" . "ro")
cvsdist 65c016
    ("Russian" . "ru")
cvsdist 65c016
    ("Samoan" . "sm")
cvsdist 65c016
    ("Sango" . "sg")
cvsdist 65c016
    ("Sanskrit" . "sa")
cvsdist 65c016
    ("Sardinian" . "sc")
cvsdist 65c016
    ("Scots" . "gd")
cvsdist 65c016
    ("Serbian" . "sr")
cvsdist 65c016
    ("Sesotho" . "st")
cvsdist 65c016
    ("Setswana" . "tn")
cvsdist 65c016
    ("Shona" . "sn")
cvsdist 65c016
    ("Sindhi" . "sd")
cvsdist 65c016
    ("Sinhalese" . "si")
cvsdist 65c016
    ("Siswati" . "ss")
cvsdist 65c016
    ("Slovak" . "sk")
cvsdist 65c016
    ("Slovenian" . "sl")
cvsdist 65c016
    ("Somali" . "so")
cvsdist 65c016
    ("Sorbian" . "wen")
cvsdist 65c016
    ("South Ndebele" . "nr")
cvsdist 65c016
    ("Spanish" . "es")
cvsdist 65c016
    ("Sundanese" . "su")
cvsdist 65c016
    ("Swahili" . "sw")
cvsdist 65c016
    ("Swedish" . "sv")
cvsdist 65c016
    ("Tagalog" . "tl")
cvsdist 65c016
    ("Tahitian" . "ty")
cvsdist 65c016
    ("Tajik" . "tg")
cvsdist 65c016
    ("Tamil" . "ta")
cvsdist 65c016
    ("Tatar" . "tt")
cvsdist 65c016
    ("Telugu" . "te")
cvsdist 65c016
    ("Thai" . "th")
cvsdist 65c016
    ("Tibetan" . "bo")
cvsdist 65c016
    ("Tigrinya" . "ti")
cvsdist 65c016
    ("Tonga" . "to")
cvsdist 65c016
    ("Tsonga" . "ts")
cvsdist 65c016
    ("Turkish" . "tr")
cvsdist 65c016
    ("Turkmen" . "tk")
cvsdist 65c016
    ("Twi" . "tw")
cvsdist 65c016
    ("Uighur" . "ug")
cvsdist 65c016
    ("Ukrainian" . "uk")
cvsdist 65c016
    ("Urdu" . "ur")
cvsdist 65c016
    ("Uzbek" . "uz")
cvsdist 65c016
    ("Vietnamese" . "vi")
cvsdist 65c016
    ("Volapuk" . "vo")
cvsdist d31aa3
    ("Walloon" . "wa")
cvsdist 65c016
    ("Welsh" . "cy")
cvsdist 65c016
    ("Wolof" . "wo")
cvsdist 65c016
    ("Xhosa" . "xh")
cvsdist 65c016
    ("Yiddish" . "yi")
cvsdist 65c016
    ("Yoruba" . "yo")
cvsdist 65c016
    ("Zhuang" . "za")
cvsdist 65c016
    ("Zulu" . "zu")
cvsdist 65c016
    )
cvsdist 65c016
  "*Association list giving team codes from team names.
cvsdist 65c016
This is used for generating a submission file name for the 'M' command.
cvsdist 65c016
If a string instead of an alist, it is a team code to use unconditionnally."
cvsdist 65c016
  :type 'sexp
cvsdist 65c016
  :group 'po)
cvsdist 65c016
cvsdist 65c016
(defcustom po-gzip-uuencode-command "gzip -9 | uuencode -m"
cvsdist 65c016
  "*The filter to use for preparing a mail invoice of the PO file.
cvsdist 65c016
Normally \"gzip -9 | uuencode -m\", remove the -9 for lesser compression,
cvsdist 65c016
or remove the -m if you are not using the GNU version of 'uuencode'."
cvsdist 65c016
  :type 'string
cvsdist 65c016
  :group 'po)
cvsdist 65c016
cvsdist 65c016
(defvar po-subedit-mode-syntax-table
cvsdist 65c016
  (copy-syntax-table text-mode-syntax-table)
cvsdist 65c016
  "Syntax table used while in PO mode.")
cvsdist 65c016

cvsdist 65c016
;;; Emacs portability matters - part II.
cvsdist 65c016
cvsdist 65c016
;;; Many portability matters are addressed in this page.  The few remaining
cvsdist 65c016
;;; cases, elsewhere, all involve  'eval-and-compile', 'boundp' or 'fboundp'.
cvsdist 65c016
cvsdist 65c016
;; Protect string comparisons from text properties if possible.
cvsdist c08065
(eval-and-compile
cvsdist c08065
  (fset 'po-buffer-substring
cvsdist c08065
	(symbol-function (if (fboundp 'buffer-substring-no-properties)
cvsdist c08065
			     'buffer-substring-no-properties
cvsdist 65c016
			   'buffer-substring)))
cvsdist c08065
cvsdist 65c016
  (if (fboundp 'match-string-no-properties)
cvsdist 65c016
      (fset 'po-match-string (symbol-function 'match-string-no-properties))
cvsdist 65c016
    (defun po-match-string (number)
cvsdist 65c016
      "Return string of text matched by last search."
cvsdist 65c016
      (po-buffer-substring (match-beginning number) (match-end number)))))
cvsdist c08065
cvsdist 65c016
;; Handle missing 'with-temp-buffer' function.
cvsdist 65c016
(eval-and-compile
cvsdist 65c016
  (if (fboundp 'with-temp-buffer)
cvsdist c08065
      (fset 'po-with-temp-buffer (symbol-function 'with-temp-buffer))
cvsdist c08065
cvsdist c08065
    (defmacro po-with-temp-buffer (&rest forms)
cvsdist 65c016
      "Create a temporary buffer, and evaluate FORMS there like 'progn'."
cvsdist c08065
      (let ((curr-buffer (make-symbol "curr-buffer"))
cvsdist c08065
	    (temp-buffer (make-symbol "temp-buffer")))
cvsdist c08065
	`(let ((,curr-buffer (current-buffer))
cvsdist c08065
	       (,temp-buffer (get-buffer-create
cvsdist c08065
			      (generate-new-buffer-name " *po-temp*"))))
cvsdist c08065
	   (unwind-protect
cvsdist c08065
	       (progn
cvsdist c08065
		 (set-buffer ,temp-buffer)
cvsdist c08065
		 ,@forms)
cvsdist c08065
	     (set-buffer ,curr-buffer)
cvsdist c08065
	     (and (buffer-name ,temp-buffer)
cvsdist c08065
		  (kill-buffer ,temp-buffer))))))))
cvsdist c08065
cvsdist 65c016
;; Handle missing 'kill-new' function.
cvsdist c08065
(eval-and-compile
cvsdist c08065
  (if (fboundp 'kill-new)
cvsdist c08065
      (fset 'po-kill-new (symbol-function 'kill-new))
cvsdist c08065
cvsdist c08065
    (defun po-kill-new (string)
cvsdist c08065
      "Push STRING onto the kill ring, for Emacs 18 where kill-new is missing."
cvsdist c08065
      (po-with-temp-buffer
cvsdist c08065
	(insert string)
cvsdist c08065
	(kill-region (point-min) (point-max))))))
cvsdist c08065
cvsdist 65c016
;; Handle missing 'read-event' function.
cvsdist c08065
(eval-and-compile
cvsdist c08065
  (fset 'po-read-event
cvsdist c08065
	(cond ((fboundp 'read-event)
cvsdist c08065
	       ;; GNU Emacs.
cvsdist c08065
	       'read-event)
cvsdist c08065
	      ((fboundp 'next-command-event)
cvsdist c08065
	       ;; XEmacs.
cvsdist c08065
	       'next-command-event)
cvsdist c08065
	      (t
cvsdist c08065
	       ;; Older Emacses.
cvsdist c08065
	       'read-char))))
cvsdist c08065
cvsdist 65c016
;; Handle missing 'force-mode-line-update' function.
cvsdist c08065
(eval-and-compile
cvsdist c08065
  (if (fboundp 'force-mode-line-update)
cvsdist c08065
      (fset 'po-force-mode-line-update
cvsdist c08065
	    (symbol-function 'force-mode-line-update))
cvsdist c08065
cvsdist c08065
    (defun po-force-mode-line-update ()
cvsdist c08065
      "Force the mode-line of the current buffer to be redisplayed."
cvsdist c08065
      (set-buffer-modified-p (buffer-modified-p)))))
cvsdist c08065
cvsdist c08065
;; Handle portable highlighting.  Code has been adapted (OK... stolen! :-)
cvsdist 65c016
;; from 'ispell.el'.
cvsdist c08065
(eval-and-compile
cvsdist c08065
  (cond
cvsdist c08065
   (po-EMACS20
cvsdist c08065
cvsdist c08065
    (defun po-create-overlay ()
cvsdist c08065
      "Create and return a deleted overlay structure.
cvsdist 65c016
The variable 'po-highlight-face' selects the face to use for highlighting."
cvsdist c08065
      (let ((overlay (make-overlay (point) (point))))
cvsdist c08065
	(overlay-put overlay 'face po-highlight-face)
cvsdist c08065
	;; The fun thing is that a deleted overlay retains its face, and is
cvsdist c08065
	;; movable.
cvsdist c08065
	(delete-overlay overlay)
cvsdist c08065
	overlay))
cvsdist c08065
cvsdist c08065
    (defun po-highlight (overlay start end &optional buffer)
cvsdist c08065
      "Use OVERLAY to highlight the string from START to END.
cvsdist c08065
If limits are not relative to the current buffer, use optional BUFFER."
cvsdist c08065
      (move-overlay overlay start end (or buffer (current-buffer))))
cvsdist c08065
cvsdist c08065
    (defun po-rehighlight (overlay)
cvsdist c08065
      "Ensure OVERLAY is highlighted."
cvsdist c08065
      ;; There is nothing to do, as GNU Emacs allows multiple highlights.
cvsdist c08065
      nil)
cvsdist c08065
cvsdist c08065
    (defun po-dehighlight (overlay)
cvsdist c08065
      "Display normally the last string which OVERLAY highlighted.
cvsdist c08065
The current buffer should be in PO mode, when this function is called."
cvsdist c08065
      (delete-overlay overlay)))
cvsdist c08065
cvsdist c08065
   (po-XEMACS
cvsdist c08065
cvsdist c08065
    (defun po-create-overlay ()
cvsdist c08065
      "Create and return a deleted overlay structure."
cvsdist 65c016
      ;; The same as for GNU Emacs above, except the created extent is
cvsdist 65c016
      ;; already detached, so there's no need to "delete" it
cvsdist 65c016
      ;; explicitly.
cvsdist 65c016
      (let ((extent (make-extent nil nil)))
cvsdist 65c016
	(set-extent-face extent po-highlight-face)
cvsdist 65c016
	extent))
cvsdist 65c016
cvsdist 65c016
    (defun po-highlight (extent start end &optional buffer)
cvsdist 65c016
      "Use EXTENT to highlight the string from START to END.
cvsdist c08065
If limits are not relative to the current buffer, use optional BUFFER."
cvsdist 65c016
      (set-extent-endpoints extent start end (or buffer (current-buffer))))
cvsdist c08065
cvsdist 65c016
    (defun po-rehighlight (extent)
cvsdist 65c016
      "Ensure EXTENT is highlighted."
cvsdist 65c016
      ;; Nothing to do here.
cvsdist 65c016
      nil)
cvsdist c08065
cvsdist 65c016
    (defun po-dehighlight (extent)
cvsdist 65c016
      "Display normally the last string which EXTENT highlighted."
cvsdist 65c016
      (detach-extent extent)))
cvsdist c08065
cvsdist c08065
   (t
cvsdist c08065
cvsdist c08065
    (defun po-create-overlay ()
cvsdist c08065
      "Create and return a deleted overlay structure."
cvsdist c08065
      (cons (make-marker) (make-marker)))
cvsdist c08065
cvsdist c08065
    (defun po-highlight (overlay start end &optional buffer)
cvsdist c08065
      "Use OVERLAY to highlight the string from START to END.
cvsdist c08065
If limits are not relative to the current buffer, use optional BUFFER.
cvsdist c08065
No doubt that highlighting, when Emacs does not allow it, is a kludge."
cvsdist c08065
      (save-excursion
cvsdist c08065
	(and buffer (set-buffer buffer))
cvsdist c08065
	(let ((modified (buffer-modified-p))
cvsdist c08065
	      (buffer-read-only nil)
cvsdist c08065
	      (inhibit-quit t)
cvsdist c08065
	      (buffer-undo-list t)
cvsdist c08065
	      (text (buffer-substring start end)))
cvsdist c08065
	  (goto-char start)
cvsdist c08065
	  (delete-region start end)
cvsdist c08065
	  (insert-char ?  (- end start))
cvsdist c08065
	  (sit-for 0)
cvsdist c08065
	  (setq inverse-video (not inverse-video))
cvsdist c08065
	  (delete-region start end)
cvsdist c08065
	  (insert text)
cvsdist c08065
	  (sit-for 0)
cvsdist c08065
	  (setq inverse-video (not inverse-video))
cvsdist c08065
	  (set-buffer-modified-p modified)))
cvsdist c08065
      (set-marker (car overlay) start (or buffer (current-buffer)))
cvsdist c08065
      (set-marker (cdr overlay) end (or buffer (current-buffer))))
cvsdist c08065
cvsdist c08065
    (defun po-rehighlight (overlay)
cvsdist c08065
      "Ensure OVERLAY is highlighted."
cvsdist c08065
      (let ((buffer (marker-buffer (car overlay)))
cvsdist c08065
	    (start (marker-position (car overlay)))
cvsdist c08065
	    (end (marker-position (cdr overlay))))
cvsdist c08065
	(and buffer
cvsdist 65c016
	     (buffer-name buffer)
cvsdist c08065
	     (po-highlight overlay start end buffer))))
cvsdist c08065
cvsdist c08065
    (defun po-dehighlight (overlay)
cvsdist c08065
      "Display normally the last string which OVERLAY highlighted."
cvsdist c08065
      (let ((buffer (marker-buffer (car overlay)))
cvsdist c08065
	    (start (marker-position (car overlay)))
cvsdist c08065
	    (end (marker-position (cdr overlay))))
cvsdist c08065
	(if buffer
cvsdist c08065
	    (save-excursion
cvsdist c08065
	      (set-buffer buffer)
cvsdist c08065
	      (let ((modified (buffer-modified-p))
cvsdist c08065
		    (buffer-read-only nil)
cvsdist c08065
		    (inhibit-quit t)
cvsdist c08065
		    (buffer-undo-list t))
cvsdist c08065
		(let ((text (buffer-substring start end)))
cvsdist c08065
		  (goto-char start)
cvsdist c08065
		  (delete-region start end)
cvsdist c08065
		  (insert-char ?  (- end start))
cvsdist c08065
		  (sit-for 0)
cvsdist c08065
		  (delete-region start end)
cvsdist c08065
		  (insert text)
cvsdist c08065
		  (sit-for 0)
cvsdist c08065
		  (set-buffer-modified-p modified)))))
cvsdist c08065
	(setcar overlay (make-marker))
cvsdist c08065
	(setcdr overlay (make-marker))))
cvsdist c08065
cvsdist c08065
    )))
cvsdist c08065

cvsdist 65c016
;;; Buffer local variables.
cvsdist c08065
cvsdist 65c016
;; The following block of declarations has the main purpose of avoiding
cvsdist 65c016
;; byte compiler warnings.  It also introduces some documentation for
cvsdist 65c016
;; each of these variables, all meant to be local to PO mode buffers.
cvsdist c08065
cvsdist 65c016
;; Flag telling that MODE-LINE-STRING should be displayed.  See 'Window'
cvsdist c08065
;; page below.  Exceptionally, this variable is local to *all* buffers.
cvsdist c08065
(defvar po-mode-flag)
cvsdist c08065
cvsdist c08065
;; PO buffers are kept read-only to prevent random modifications.  READ-ONLY
cvsdist c08065
;; holds the value of the read-only flag before PO mode was entered.
cvsdist c08065
(defvar po-read-only)
cvsdist c08065
cvsdist c08065
;; The current entry extends from START-OF-ENTRY to END-OF-ENTRY, it
cvsdist c08065
;; includes preceding whitespace and excludes following whitespace.  The
cvsdist c08065
;; start of keyword lines are START-OF-MSGID and START-OF-MSGSTR.
cvsdist c08065
;; ENTRY-TYPE classifies the entry.
cvsdist c08065
(defvar po-start-of-entry)
cvsdist c08065
(defvar po-start-of-msgid)
cvsdist c08065
(defvar po-start-of-msgstr)
cvsdist c08065
(defvar po-end-of-entry)
cvsdist c08065
(defvar po-entry-type)
cvsdist c08065
cvsdist c08065
;; A few counters are usefully shown in the Emacs mode line.
cvsdist c08065
(defvar po-translated-counter)
cvsdist c08065
(defvar po-fuzzy-counter)
cvsdist c08065
(defvar po-untranslated-counter)
cvsdist c08065
(defvar po-obsolete-counter)
cvsdist c08065
(defvar po-mode-line-string)
cvsdist c08065
cvsdist c08065
;; PO mode keeps track of fields being edited, for one given field should
cvsdist c08065
;; have one editing buffer at most, and for exiting a PO buffer properly
cvsdist c08065
;; should offer to close all pending edits.  Variable EDITED-FIELDS holds an
cvsdist c08065
;; an list of "slots" of the form: (ENTRY-MARKER EDIT-BUFFER OVERLAY-INFO).
cvsdist c08065
;; To allow simultaneous edition of the comment and the msgstr of an entry,
cvsdist c08065
;; ENTRY-MARKER points to the msgid line if a comment is being edited, or to
cvsdist c08065
;; the msgstr line if the msgstr is being edited.  EDIT-BUFFER is the
cvsdist c08065
;; temporary Emacs buffer used to edit the string.  OVERLAY-INFO, when not
cvsdist c08065
;; nil, holds an overlay (or if overlays are not supported, a cons of two
cvsdist c08065
;; markers) for this msgid string which became highlighted for the edit.
cvsdist c08065
(defvar po-edited-fields)
cvsdist c08065
cvsdist c08065
;; We maintain a set of movable pointers for returning to entries.
cvsdist c08065
(defvar po-marker-stack)
cvsdist c08065
cvsdist c08065
;; SEARCH path contains a list of directories where files may be found,
cvsdist c08065
;; in a format suitable for read completion.  Each directory includes
cvsdist c08065
;; its trailing slash.  PO mode starts with "./" and "../".
cvsdist c08065
(defvar po-search-path)
cvsdist c08065
cvsdist c08065
;; The following variables are meaningful only when REFERENCE-CHECK
cvsdist c08065
;; is identical to START-OF-ENTRY, else they should be recomputed.
cvsdist c08065
;; REFERENCE-ALIST contains all known references for the current
cvsdist c08065
;; entry, each list element is (PROMPT FILE LINE), where PROMPT may
cvsdist c08065
;; be used for completing read, FILE is a string and LINE is a number.
cvsdist c08065
;; REFERENCE-CURSOR is a cycling cursor into REFERENCE-ALIST.
cvsdist c08065
(defvar po-reference-alist)
cvsdist c08065
(defvar po-reference-cursor)
cvsdist c08065
(defvar po-reference-check)
cvsdist c08065
cvsdist c08065
;; The following variables are for marking translatable strings in program
cvsdist c08065
;; sources.  KEYWORDS is the list of keywords for marking translatable
cvsdist c08065
;; strings, kept in a format suitable for reading with completion.
cvsdist 65c016
;; STRING-CONTENTS holds the value of the most recent string found in sources,
cvsdist 65c016
;; and when it is not nil, then STRING-BUFFER, STRING-START and STRING-END
cvsdist 65c016
;; describe where it is.  MARKING-OVERLAY, if not 'nil', holds the overlay
cvsdist 65c016
;; which highlight the last found string; for older Emacses, it holds the cons
cvsdist 65c016
;; of two markers around the highlighted region.
cvsdist c08065
(defvar po-keywords)
cvsdist 65c016
(defvar po-string-contents)
cvsdist 65c016
(defvar po-string-buffer)
cvsdist c08065
(defvar po-string-start)
cvsdist c08065
(defvar po-string-end)
cvsdist c08065
(defvar po-marking-overlay)
cvsdist c08065

cvsdist c08065
;;; PO mode variables and constants (usually not to customize).
cvsdist c08065
cvsdist c08065
;; The textdomain should really be "gettext", only trying it for now.
cvsdist c08065
;; All this requires more thinking, we cannot just do this like that.
cvsdist c08065
(set-translation-domain "po-mode")
cvsdist c08065
cvsdist c08065
(defun po-mode-version ()
cvsdist c08065
  "Show Emacs PO mode version."
cvsdist c08065
  (interactive)
cvsdist d31aa3
  (message (_"Emacs PO mode, version %s") po-mode-version-string))
cvsdist c08065
cvsdist c08065
(defconst po-help-display-string
cvsdist c08065
  (_"\
cvsdist c08065
PO Mode Summary           Next Previous            Miscellaneous
cvsdist c08065
*: Later, /: Docum        n    p    Any type       .     Redisplay
cvsdist 65c016
                          t    T    Translated     /v    Version info
cvsdist 65c016
Moving around             f    F    Fuzzy          ?, h  This help
cvsdist 65c016
<    First if any         o    O    Obsolete       =     Current index
cvsdist 65c016
>    Last if any          u    U    Untranslated   0     Other window
cvsdist c08065
/SPC Auto select                                   V     Validate
cvsdist c08065
                        Msgstr Comments            M     Mail officially
cvsdist 65c016
Modifying entries         RET  #    Call editor    _     Undo
cvsdist c08065
TAB   Remove fuzzy mark   k    K    Kill to        E     Edit out full
cvsdist 65c016
DEL   Fuzzy or fade out   w    W    Copy to        Q     Forceful quit
cvsdist c08065
LFD   Init with msgid     y    Y    Yank from      q     Confirm and quit
cvsdist c08065
cvsdist c08065
gettext Keyword Marking                            Position Stack
cvsdist c08065
,    Find next string     Compendiums              m  Mark and push current
cvsdist c08065
M-,  Mark translatable    *c    To compendium      r  Pop and return
cvsdist c08065
M-.  Change mark, mark    *M-C  Select, save       x  Exchange current/top
cvsdist c08065
cvsdist c08065
Program Sources           Auxiliary Files          Lexicography
cvsdist c08065
s    Cycle reference      a    Cycle file          *l    Lookup translation
cvsdist 65c016
M-s  Select reference     C-c C-a  Select file     *M-l  Add/edit translation
cvsdist c08065
S    Consider path        A    Consider PO file    *L    Consider lexicon
cvsdist c08065
M-S  Ignore path          M-A  Ignore PO file      *M-L  Ignore lexicon
cvsdist c08065
")
cvsdist c08065
  "Help page for PO mode.")
cvsdist c08065
cvsdist c08065
(defconst po-mode-menu-layout
cvsdist d31aa3
  `("PO"
cvsdist c08065
    ("Moving around"
cvsdist d31aa3
     ["Auto select" po-auto-select-entry
cvsdist d31aa3
      ,@(if (featurep 'xemacs) '(t)
cvsdist d31aa3
	  '(:help "Jump to next interesting entry"))]
cvsdist c08065
     "---"
cvsdist c08065
     "Forward"
cvsdist d31aa3
     ["Any next" po-next-entry
cvsdist d31aa3
      ,@(if (featurep 'xemacs) '(t)
cvsdist d31aa3
	  '(:help "Jump to next entry"))]
cvsdist d31aa3
     ["Next translated" po-next-translated-entry
cvsdist d31aa3
      ,@(if (featurep 'xemacs) '(t)
cvsdist d31aa3
	  '(:help "Jump to next translated entry"))]
cvsdist d31aa3
     ["Next fuzzy" po-next-fuzzy-entry
cvsdist d31aa3
      ,@(if (featurep 'xemacs) '(t)
cvsdist d31aa3
	  '(:help "Jump to next fuzzy entry"))]
cvsdist d31aa3
     ["Next obsolete" po-next-obsolete-entry
cvsdist d31aa3
      ,@(if (featurep 'xemacs) '(t)
cvsdist d31aa3
	  '(:help "Jump to next obsolete entry"))]
cvsdist d31aa3
     ["Next untranslated" po-next-untranslated-entry
cvsdist d31aa3
      ,@(if (featurep 'xemacs) '(t)
cvsdist d31aa3
	  '(:help "Jump to next untranslated entry"))]
cvsdist d31aa3
     ["Last file entry" po-last-entry
cvsdist d31aa3
      ,@(if (featurep 'xemacs) '(t)
cvsdist d31aa3
	  '(:help "Jump to last entry"))]
cvsdist c08065
     "---"
cvsdist c08065
     "Backward"
cvsdist d31aa3
     ["Any previous" po-previous-entry
cvsdist d31aa3
      ,@(if (featurep 'xemacs) '(t)
cvsdist d31aa3
	  '(:help "Jump to previous entry"))]
cvsdist d31aa3
     ["Previous translated" po-previous-translated-entry
cvsdist d31aa3
      ,@(if (featurep 'xemacs) '(t)
cvsdist d31aa3
	  '(:help "Jump to previous translated entry"))]
cvsdist d31aa3
     ["Previous fuzzy" po-previous-fuzzy-entry
cvsdist d31aa3
      ,@(if (featurep 'xemacs) '(t)
cvsdist d31aa3
	  '(:help "Jump to previous fuzzy entry"))]
cvsdist d31aa3
     ["Previous obsolete" po-previous-obsolete-entry
cvsdist d31aa3
      ,@(if (featurep 'xemacs) '(t)
cvsdist d31aa3
	  '(:help "Jump to previous obsolete entry"))]
cvsdist d31aa3
     ["Previous untranslated" po-previous-untranslated-entry
cvsdist d31aa3
      ,@(if (featurep 'xemacs) '(t)
cvsdist d31aa3
	  '(:help "Jump to previous untranslated entry"))]
cvsdist d31aa3
     ["First file entry" po-first-entry
cvsdist d31aa3
      ,@(if (featurep 'xemacs) '(t)
cvsdist d31aa3
	  '(:help "Jump to first entry"))]
cvsdist c08065
     "---"
cvsdist c08065
     "Position stack"
cvsdist d31aa3
     ["Mark and push current" po-push-location
cvsdist d31aa3
      ,@(if (featurep 'xemacs) '(t)
cvsdist d31aa3
	  '(:help "Remember current location"))]
cvsdist d31aa3
     ["Pop and return" po-pop-location
cvsdist d31aa3
      ,@(if (featurep 'xemacs) '(t)
cvsdist d31aa3
	  '(:help "Jump to last remembered location and forget about it"))]
cvsdist d31aa3
     ["Exchange current/top" po-exchange-location
cvsdist d31aa3
      ,@(if (featurep 'xemacs) '(t)
cvsdist d31aa3
	  '(:help "Jump to last remembered location and remember current location"))]
cvsdist c08065
     "---"
cvsdist d31aa3
     ["Redisplay" po-current-entry
cvsdist d31aa3
      ,@(if (featurep 'xemacs) '(t)
cvsdist d31aa3
	  '(:help "Make current entry properly visible"))]
cvsdist d31aa3
     ["Current index" po-statistics
cvsdist d31aa3
      ,@(if (featurep 'xemacs) '(t)
cvsdist d31aa3
	  '(:help "Statistical info on current translation file"))])
cvsdist c08065
    ("Modifying entries"
cvsdist d31aa3
     ["Undo" po-undo
cvsdist d31aa3
      ,@(if (featurep 'xemacs) '(t)
cvsdist d31aa3
	  '(:help "Revoke last changed entry"))]
cvsdist c08065
     "---"
cvsdist c08065
     "Msgstr"
cvsdist d31aa3
     ["Edit msgstr" po-edit-msgstr
cvsdist d31aa3
      ,@(if (featurep 'xemacs) '(t)
cvsdist d31aa3
	  '(:help "Edit current translation"))]
cvsdist d31aa3
     ["Ediff and merge msgstr" po-edit-msgstr-and-ediff
cvsdist d31aa3
      ,@(if (featurep 'xemacs) '(t)
cvsdist d31aa3
	  '(:help "Call `ediff' on current translation for merging"))]
cvsdist d31aa3
     ["Cut msgstr" po-kill-msgstr
cvsdist d31aa3
      ,@(if (featurep 'xemacs) '(t)
cvsdist d31aa3
	  '(:help "Cut (kill) current translation"))]
cvsdist d31aa3
     ["Copy msgstr" po-kill-ring-save-msgstr
cvsdist d31aa3
      ,@(if (featurep 'xemacs) '(t)
cvsdist d31aa3
	  '(:help "Copy current translation"))]
cvsdist d31aa3
     ["Paste msgstr" po-yank-msgstr
cvsdist d31aa3
      ,@(if (featurep 'xemacs) '(t)
cvsdist d31aa3
	  '(:help "Paste (yank) text most recently cut/copied translation"))]
cvsdist c08065
     "---"
cvsdist c08065
     "Comments"
cvsdist d31aa3
     ["Edit comment" po-edit-comment
cvsdist d31aa3
      ,@(if (featurep 'xemacs) '(t)
cvsdist d31aa3
	  '(:help "Edit current comment"))]
cvsdist d31aa3
     ["Ediff and merge comment" po-edit-comment-and-ediff
cvsdist d31aa3
      ,@(if (featurep 'xemacs) '(t)
cvsdist d31aa3
	  '(:help "Call `ediff' on current comment for merging"))]
cvsdist d31aa3
     ["Cut comment" po-kill-comment
cvsdist d31aa3
      ,@(if (featurep 'xemacs) '(t)
cvsdist d31aa3
	  '(:help "Cut (kill) current comment"))]
cvsdist d31aa3
     ["Copy comment" po-kill-ring-save-comment
cvsdist d31aa3
      ,@(if (featurep 'xemacs) '(t)
cvsdist d31aa3
	  '(:help "Copy current translation"))]
cvsdist d31aa3
     ["Paste comment" po-yank-comment
cvsdist d31aa3
      ,@(if (featurep 'xemacs) '(t)
cvsdist d31aa3
	  '(:help "Paste (yank) text most recently cut/copied"))]
cvsdist c08065
     "---"
cvsdist d31aa3
     ["Remove fuzzy mark" po-unfuzzy
cvsdist d31aa3
      ,@(if (featurep 'xemacs) '(t)
cvsdist d31aa3
	  '(:help "Remove \"#, fuzzy\""))]
cvsdist d31aa3
     ["Fuzzy or fade out" po-fade-out-entry
cvsdist d31aa3
      ,@(if (featurep 'xemacs) '(t)
cvsdist d31aa3
	  '(:help "Set current entry fuzzy, or if already fuzzy delete it"))]
cvsdist d31aa3
     ["Init with msgid" po-msgid-to-msgstr
cvsdist d31aa3
      ,@(if (featurep 'xemacs) '(t)
cvsdist d31aa3
	  '(:help "\
cvsdist d31aa3
Initialize or replace current translation with the original message"))])
cvsdist c08065
    ("Other files"
cvsdist d31aa3
     ["Other window" po-other-window
cvsdist d31aa3
      ,@(if (featurep 'xemacs) '(t)
cvsdist d31aa3
	  '(:help "Select other window; if necessay split current frame"))]
cvsdist c08065
     "---"
cvsdist c08065
     "Program sources"
cvsdist c08065
     ["Cycle reference" po-cycle-source-reference t]
cvsdist c08065
     ["Select reference" po-select-source-reference t]
cvsdist c08065
     ["Consider path" po-consider-source-path t]
cvsdist c08065
     ["Ignore path" po-ignore-source-path t]
cvsdist c08065
     "---"
cvsdist c08065
     "Compendiums"
cvsdist c08065
     ["To compendium" po-save-entry nil]
cvsdist c08065
     ["Select, save" po-select-and-save-entry nil]
cvsdist c08065
     "---"
cvsdist c08065
     "Auxiliary files"
cvsdist c08065
     ["Cycle file" po-cycle-auxiliary t]
cvsdist c08065
     ["Select file" po-select-auxiliary t]
cvsdist c08065
     ["Consider file" po-consider-as-auxiliary t]
cvsdist c08065
     ["Ignore file" po-ignore-as-auxiliary t]
cvsdist c08065
     "---"
cvsdist c08065
     "Lexicography"
cvsdist c08065
     ["Lookup translation" po-lookup-lexicons nil]
cvsdist c08065
     ["Add/edit translation" po-edit-lexicon-entry nil]
cvsdist c08065
     ["Consider lexicon" po-consider-lexicon-file nil]
cvsdist c08065
     ["Ignore lexicon" po-ignore-lexicon-file nil])
cvsdist c08065
    "---"
cvsdist c08065
    "Source marking"
cvsdist c08065
    ["Find first string" (po-tags-search '(nil)) t]
cvsdist c08065
    ["Prefer keyword" (po-select-mark-and-mark '(nil)) t]
cvsdist c08065
    ["Find next string" po-tags-search t]
cvsdist c08065
    ["Mark preferred" po-mark-translatable t]
cvsdist c08065
    ["Mark with keyword" po-select-mark-and-mark t]
cvsdist c08065
    "---"
cvsdist d31aa3
    ["Version info" po-mode-version
cvsdist d31aa3
     ,@(if (featurep 'xemacs) '(t)
cvsdist d31aa3
	  '(:help "Display version number of PO mode"))]
cvsdist d31aa3
    ["Help page" po-help
cvsdist d31aa3
     ,@(if (featurep 'xemacs) '(t)
cvsdist d31aa3
	  '(:help "Show the PO mode help screen"))]
cvsdist d31aa3
    ["Validate" po-validate
cvsdist d31aa3
     ,@(if (featurep 'xemacs) '(t)
cvsdist d31aa3
	  '(:help "Check validity of current translation file using `msgfmt'"))]
cvsdist d31aa3
    ["Mail officially" po-send-mail
cvsdist d31aa3
     ,@(if (featurep 'xemacs) '(t)
cvsdist d31aa3
	  '(:help "Send current translation file to the Translation Robot by mail"))]
cvsdist d31aa3
    ["Edit out full" po-edit-out-full
cvsdist d31aa3
     ,@(if (featurep 'xemacs) '(t)
cvsdist d31aa3
	  '(:help "Leave PO mode to edit translation file using fundamental mode"))]
cvsdist c08065
    "---"
cvsdist d31aa3
    ["Forceful quit" po-quit
cvsdist d31aa3
     ,@(if (featurep 'xemacs) '(t)
cvsdist d31aa3
	  '(:help "Close (kill) current translation file without saving"))]
cvsdist d31aa3
    ["Soft quit" po-confirm-and-quit
cvsdist d31aa3
     ,@(if (featurep 'xemacs) '(t)
cvsdist d31aa3
	  '(:help "Save current translation file, than close (kill) it"))])
cvsdist c08065
  "Menu layout for PO mode.")
cvsdist c08065
cvsdist 65c016
(defconst po-subedit-mode-menu-layout
cvsdist d31aa3
  `("PO-Edit"
cvsdist d31aa3
    ["Ediff and merge translation variants" po-subedit-ediff
cvsdist d31aa3
      ,@(if (featurep 'xemacs) '(t)
cvsdist d31aa3
	  '(:help "Call `ediff' for merging variants"))]
cvsdist 65c016
    ["Cycle through auxiliary files" po-subedit-cycle-auxiliary t]
cvsdist 65c016
    "---"
cvsdist d31aa3
    ["Abort edit" po-subedit-abort
cvsdist d31aa3
     ,@(if (featurep 'xemacs) '(t)
cvsdist d31aa3
	  '(:help "Don't change the translation"))]
cvsdist d31aa3
    ["Exit edit" po-subedit-exit
cvsdist d31aa3
     ,@(if (featurep 'xemacs) '(t)
cvsdist d31aa3
	 '(:help "Use this text as the translation and close current edit buffer"))])
cvsdist 65c016
  "Menu layout for PO subedit mode.")
cvsdist c08065
cvsdist c08065
(defconst po-subedit-message
cvsdist 65c016
  (_"Type 'C-c C-c' once done, or 'C-c C-k' to abort edit")
cvsdist c08065
  "Message to post in the minibuffer when an edit buffer is displayed.")
cvsdist c08065
cvsdist c08065
(defvar po-auxiliary-list nil
cvsdist c08065
  "List of auxiliary PO files, in completing read format.")
cvsdist c08065
cvsdist c08065
(defvar po-auxiliary-cursor nil
cvsdist 65c016
  "Cursor into the 'po-auxiliary-list'.")
cvsdist c08065
cvsdist c08065
(defvar po-compose-mail-function
cvsdist c08065
  (let ((functions '(compose-mail-other-window
cvsdist c08065
		     message-mail-other-window
cvsdist c08065
		     compose-mail
cvsdist c08065
		     message-mail))
cvsdist c08065
	result)
cvsdist c08065
    (while (and (not result) functions)
cvsdist c08065
      (if (fboundp (car functions))
cvsdist c08065
	  (setq result (car functions))
cvsdist c08065
	(setq functions (cdr functions))))
cvsdist c08065
    (cond (result)
cvsdist c08065
	  ((fboundp 'mail-other-window)
cvsdist c08065
	   (function (lambda (to subject)
cvsdist c08065
		       (mail-other-window nil to subject))))
cvsdist c08065
	  ((fboundp 'mail)
cvsdist c08065
	   (function (lambda (to subject)
cvsdist c08065
		       (mail nil to subject))))
cvsdist c08065
	  (t (function (lambda (to subject)
cvsdist 65c016
			 (error (_"I do not know how to mail to '%s'") to))))))
cvsdist c08065
  "Function to start composing an electronic message.")
cvsdist c08065
cvsdist c08065
(defvar po-any-msgid-regexp
cvsdist 65c016
  "^\\(#~[ \t]*\\)?msgid.*\n\\(\\(#~[ \t]*\\)?\".*\n\\)*"
cvsdist c08065
  "Regexp matching a whole msgid field, whether obsolete or not.")
cvsdist c08065
cvsdist c08065
(defvar po-any-msgstr-regexp
cvsdist 65c016
  ;; "^\\(#~[ \t]*\\)?msgstr.*\n\\(\\(#~[ \t]*\\)?\".*\n\\)*"
cvsdist 65c016
  "^\\(#~[ \t]*\\)?msgstr\\(\\[[0-9]\\]\\)?.*\n\\(\\(#~[ \t]*\\)?\".*\n\\)*"
cvsdist c08065
  "Regexp matching a whole msgstr or msgstr[] field, whether obsolete or not.")
cvsdist c08065
cvsdist c08065
(defvar po-msgstr-idx-keyword-regexp
cvsdist 65c016
  "^\\(#~[ \t]*\\)?msgstr\\[[0-9]\\]"
cvsdist c08065
  "Regexp matching an indexed msgstr keyword, whether obsolete or not.")
cvsdist c08065
cvsdist c08065
(defvar po-msgfmt-program "msgfmt"
cvsdist c08065
  "Path to msgfmt program from GNU gettext package.")
cvsdist c08065
cvsdist c08065
;; Font lock based highlighting code.
cvsdist c08065
(defconst po-font-lock-keywords
cvsdist c08065
  '(
cvsdist c08065
    ;; ("^\\(msgid \\|msgstr \\)?\"\\|\"$" . font-lock-keyword-face)
cvsdist c08065
    ;; (regexp-opt
cvsdist c08065
    ;;  '("msgid " "msgid_plural " "msgstr " "msgstr[0] " "msgstr[1] "))
cvsdist 65c016
    ("\
cvsdist 65c016
^\\(\\(msg\\(id\\(_plural\\)?\\|str\\(\\[[0-9]\\]\\)?\\)?\\) \\)?\"\\|\"$"
cvsdist c08065
     . font-lock-keyword-face)
cvsdist c08065
    ("\\\\.\\|%\\*?[-.0-9ul]*[a-zA-Z]" . font-lock-variable-name-face)
cvsdist c08065
    ("^# .*\\|^#[:,]?" . font-lock-comment-face)
cvsdist c08065
    ("^#:\\(.*\\)" 1 font-lock-reference-face)
cvsdist c08065
    ;; The following line does not work, and I wonder why.
cvsdist c08065
    ;;("^#,\\(.*\\)" 1 font-function-name-reference-face)
cvsdist c08065
    )
cvsdist c08065
  "Additional expressions to highlight in PO mode.")
cvsdist c08065
cvsdist 65c016
;; Old activator for 'font lock'.  Is it still useful?  I don't think so.
cvsdist c08065
;;(if (boundp 'font-lock-keywords)
cvsdist c08065
;;    (put 'po-mode 'font-lock-keywords 'po-font-lock-keywords))
cvsdist c08065
cvsdist 65c016
;; 'hilit19' based highlighting code has been disabled, as most probably
cvsdist c08065
;; nobody really needs it (it also generates ugly byte-compiler warnings).
cvsdist c08065
;;
cvsdist c08065
;;(if (fboundp 'hilit-set-mode-patterns)
cvsdist c08065
;;    (hilit-set-mode-patterns 'po-mode
cvsdist c08065
;;			     '(("^# .*\\|^#$" nil comment)
cvsdist c08065
;;			       ("^#[.,:].*" nil include)
cvsdist c08065
;;			       ("^\\(msgid\\|msgstr\\) *\"" nil keyword)
cvsdist c08065
;;			       ("^\"\\|\"$" nil keyword))))
cvsdist c08065

cvsdist c08065
;;; Mode activation.
cvsdist c08065
cvsdist d31aa3
;; Emacs 21.2 comes with po-find-file-coding-system. We give preference
cvsdist d31aa3
;; to the version shipped with Emacs.
cvsdist d31aa3
(if (not (fboundp 'po-find-file-coding-system))
cvsdist d31aa3
  (require 'po-compat))
cvsdist c08065
cvsdist 65c016
(defvar po-mode-abbrev-table nil
cvsdist 65c016
  "Abbrev table used while in PO mode.")
cvsdist 65c016
(define-abbrev-table 'po-mode-abbrev-table ())
cvsdist 65c016
cvsdist 65c016
(defvar po-mode-map
cvsdist 65c016
  ;; Use (make-keymap) because (make-sparse-keymap) does not work on Demacs.
cvsdist 65c016
  (let ((po-mode-map (make-keymap)))
cvsdist 65c016
    (suppress-keymap po-mode-map)
cvsdist 65c016
    (define-key po-mode-map "\C-i" 'po-unfuzzy)
cvsdist 65c016
    (define-key po-mode-map "\C-j" 'po-msgid-to-msgstr)
cvsdist 65c016
    (define-key po-mode-map "\C-m" 'po-edit-msgstr)
cvsdist 65c016
    (define-key po-mode-map " " 'po-auto-select-entry)
cvsdist 65c016
    (define-key po-mode-map "?" 'po-help)
cvsdist 65c016
    (define-key po-mode-map "#" 'po-edit-comment)
cvsdist 65c016
    (define-key po-mode-map "," 'po-tags-search)
cvsdist 65c016
    (define-key po-mode-map "." 'po-current-entry)
cvsdist 65c016
    (define-key po-mode-map "<" 'po-first-entry)
cvsdist 65c016
    (define-key po-mode-map "=" 'po-statistics)
cvsdist 65c016
    (define-key po-mode-map ">" 'po-last-entry)
cvsdist 65c016
    (define-key po-mode-map "a" 'po-cycle-auxiliary)
cvsdist c08065
;;;;  (define-key po-mode-map "c" 'po-save-entry)
cvsdist 65c016
    (define-key po-mode-map "f" 'po-next-fuzzy-entry)
cvsdist 65c016
    (define-key po-mode-map "h" 'po-help)
cvsdist 65c016
    (define-key po-mode-map "k" 'po-kill-msgstr)
cvsdist c08065
;;;;  (define-key po-mode-map "l" 'po-lookup-lexicons)
cvsdist 65c016
    (define-key po-mode-map "m" 'po-push-location)
cvsdist 65c016
    (define-key po-mode-map "n" 'po-next-entry)
cvsdist 65c016
    (define-key po-mode-map "o" 'po-next-obsolete-entry)
cvsdist 65c016
    (define-key po-mode-map "p" 'po-previous-entry)
cvsdist 65c016
    (define-key po-mode-map "q" 'po-confirm-and-quit)
cvsdist 65c016
    (define-key po-mode-map "r" 'po-pop-location)
cvsdist 65c016
    (define-key po-mode-map "s" 'po-cycle-source-reference)
cvsdist 65c016
    (define-key po-mode-map "t" 'po-next-translated-entry)
cvsdist 65c016
    (define-key po-mode-map "u" 'po-next-untranslated-entry)
cvsdist 65c016
    (define-key po-mode-map "v" 'po-mode-version)
cvsdist 65c016
    (define-key po-mode-map "w" 'po-kill-ring-save-msgstr)
cvsdist 65c016
    (define-key po-mode-map "x" 'po-exchange-location)
cvsdist 65c016
    (define-key po-mode-map "y" 'po-yank-msgstr)
cvsdist 65c016
    (define-key po-mode-map "A" 'po-consider-as-auxiliary)
cvsdist 65c016
    (define-key po-mode-map "E" 'po-edit-out-full)
cvsdist 65c016
    (define-key po-mode-map "F" 'po-previous-fuzzy-entry)
cvsdist 65c016
    (define-key po-mode-map "K" 'po-kill-comment)
cvsdist c08065
;;;;  (define-key po-mode-map "L" 'po-consider-lexicon-file)
cvsdist 65c016
    (define-key po-mode-map "M" 'po-send-mail)
cvsdist 65c016
    (define-key po-mode-map "O" 'po-previous-obsolete-entry)
cvsdist 65c016
    (define-key po-mode-map "T" 'po-previous-translated-entry)
cvsdist 65c016
    (define-key po-mode-map "U" 'po-previous-untranslated-entry)
cvsdist 65c016
    (define-key po-mode-map "Q" 'po-quit)
cvsdist 65c016
    (define-key po-mode-map "S" 'po-consider-source-path)
cvsdist 65c016
    (define-key po-mode-map "V" 'po-validate)
cvsdist 65c016
    (define-key po-mode-map "W" 'po-kill-ring-save-comment)
cvsdist 65c016
    (define-key po-mode-map "Y" 'po-yank-comment)
cvsdist 65c016
    (define-key po-mode-map "_" 'po-undo)
cvsdist 65c016
    (define-key po-mode-map "0" 'po-other-window)
cvsdist 65c016
    (define-key po-mode-map "\177" 'po-fade-out-entry)
cvsdist 65c016
    (define-key po-mode-map "\C-c\C-a" 'po-select-auxiliary)
cvsdist 65c016
    (define-key po-mode-map "\C-c\C-e" 'po-edit-msgstr-and-ediff)
cvsdist 65c016
    (define-key po-mode-map [?\C-c?\C-#] 'po-edit-comment-and-ediff)
cvsdist 65c016
    (define-key po-mode-map "\C-c\C-C" 'po-edit-comment-and-ediff)
cvsdist 65c016
    (define-key po-mode-map "\M-," 'po-mark-translatable)
cvsdist 65c016
    (define-key po-mode-map "\M-." 'po-select-mark-and-mark)
cvsdist c08065
;;;;  (define-key po-mode-map "\M-c" 'po-select-and-save-entry)
cvsdist c08065
;;;;  (define-key po-mode-map "\M-l" 'po-edit-lexicon-entry)
cvsdist 65c016
    (define-key po-mode-map "\M-s" 'po-select-source-reference)
cvsdist 65c016
    (define-key po-mode-map "\M-A" 'po-ignore-as-auxiliary)
cvsdist c08065
;;;;  (define-key po-mode-map "\M-L" 'po-ignore-lexicon-file)
cvsdist 65c016
    (define-key po-mode-map "\M-S" 'po-ignore-source-path)
cvsdist 65c016
    po-mode-map)
cvsdist 65c016
  "Keymap for PO mode.")
cvsdist c08065
cvsdist c08065
(defun po-mode ()
cvsdist c08065
  "Major mode for translators when they edit PO files.
cvsdist c08065
cvsdist 65c016
Special commands:
cvsdist 65c016
\\{po-mode-map}
cvsdist 65c016
Turning on PO mode calls the value of the variable 'po-mode-hook',
cvsdist 65c016
if that value is non-nil.  Behaviour may be adjusted through some variables,
cvsdist 65c016
all reachable through 'M-x customize', in group 'Emacs.Editing.I18n.Po'."
cvsdist c08065
  (interactive)
cvsdist c08065
  (kill-all-local-variables)
cvsdist c08065
  (setq major-mode 'po-mode
cvsdist c08065
	mode-name "PO")
cvsdist c08065
  (use-local-map po-mode-map)
cvsdist c08065
  (if (fboundp 'easy-menu-define)
cvsdist c08065
      (progn
cvsdist 65c016
	(easy-menu-define po-mode-menu po-mode-map "" po-mode-menu-layout)
cvsdist 65c016
	(and po-XEMACS (easy-menu-add po-mode-menu))))
cvsdist 65c016
  (set (make-local-variable 'font-lock-defaults) '(po-font-lock-keywords t))
cvsdist c08065
cvsdist 65c016
  (set (make-local-variable 'po-read-only) buffer-read-only)
cvsdist 65c016
  (setq buffer-read-only t)
cvsdist c08065
cvsdist c08065
  (make-local-variable 'po-start-of-entry)
cvsdist c08065
  (make-local-variable 'po-start-of-msgid)
cvsdist c08065
  (make-local-variable 'po-start-of-msgstr)
cvsdist c08065
  (make-local-variable 'po-end-of-entry)
cvsdist c08065
  (make-local-variable 'po-entry-type)
cvsdist c08065
cvsdist c08065
  (make-local-variable 'po-translated-counter)
cvsdist c08065
  (make-local-variable 'po-fuzzy-counter)
cvsdist c08065
  (make-local-variable 'po-untranslated-counter)
cvsdist c08065
  (make-local-variable 'po-obsolete-counter)
cvsdist c08065
  (make-local-variable 'po-mode-line-string)
cvsdist c08065
cvsdist c08065
  (setq po-mode-flag t)
cvsdist c08065
cvsdist c08065
  (po-check-file-header)
cvsdist c08065
  (po-compute-counters nil)
cvsdist c08065
cvsdist 65c016
  (set (make-local-variable 'po-edited-fields) nil)
cvsdist 65c016
  (set (make-local-variable 'po-marker-stack) nil)
cvsdist 65c016
  (set (make-local-variable 'po-search-path) '(("./") ("../")))
cvsdist 65c016
cvsdist 65c016
  (set (make-local-variable 'po-reference-alist) nil)
cvsdist 65c016
  (set (make-local-variable 'po-reference-cursor) nil)
cvsdist 65c016
  (set (make-local-variable 'po-reference-check) 0)
cvsdist 65c016
cvsdist 65c016
  (set (make-local-variable 'po-keywords)
cvsdist 65c016
       '(("gettext") ("gettext_noop") ("_") ("N_")))
cvsdist 65c016
  (set (make-local-variable 'po-string-contents) nil)
cvsdist 65c016
  (set (make-local-variable 'po-string-buffer) nil)
cvsdist 65c016
  (set (make-local-variable 'po-string-start) nil)
cvsdist 65c016
  (set (make-local-variable 'po-string-end) nil)
cvsdist 65c016
  (set (make-local-variable 'po-marking-overlay) (po-create-overlay))
cvsdist 65c016
cvsdist 65c016
  (add-hook 'write-contents-hooks 'po-replace-revision-date)
cvsdist 65c016
cvsdist 65c016
  (run-hooks 'po-mode-hook)
cvsdist 65c016
  (message (_"You may type 'h' or '?' for a short PO mode reminder.")))
cvsdist 65c016
cvsdist 65c016
(defvar po-subedit-mode-map
cvsdist 65c016
  ;; Use (make-keymap) because (make-sparse-keymap) does not work on Demacs.
cvsdist 65c016
  (let ((po-subedit-mode-map (make-keymap)))
cvsdist 65c016
    (define-key po-subedit-mode-map "\C-c\C-a" 'po-subedit-cycle-auxiliary)
cvsdist 65c016
    (define-key po-subedit-mode-map "\C-c\C-c" 'po-subedit-exit)
cvsdist 65c016
    (define-key po-subedit-mode-map "\C-c\C-e" 'po-subedit-ediff)
cvsdist 65c016
    (define-key po-subedit-mode-map "\C-c\C-k" 'po-subedit-abort)
cvsdist 65c016
    po-subedit-mode-map)
cvsdist 65c016
  "Keymap while editing a PO mode entry (or the full PO file).")
cvsdist c08065

cvsdist c08065
;;; Window management.
cvsdist c08065
cvsdist c08065
(make-variable-buffer-local 'po-mode-flag)
cvsdist c08065
cvsdist c08065
(defvar po-mode-line-entry '(po-mode-flag ("  " po-mode-line-string))
cvsdist c08065
  "Mode line format entry displaying MODE-LINE-STRING.")
cvsdist c08065
cvsdist c08065
;; Insert MODE-LINE-ENTRY in mode line, but on first load only.
cvsdist c08065
(or (member po-mode-line-entry mode-line-format)
cvsdist d31aa3
    ;; mode-line-format usually contains global-mode-string, but some
cvsdist d31aa3
    ;; people customize this variable. As a last resort, append at the end.
cvsdist d31aa3
    (let ((prev-entry (or (member 'global-mode-string mode-line-format)
cvsdist d31aa3
                          (member "   " mode-line-format)
cvsdist d31aa3
                          (last mode-line-format))))
cvsdist d31aa3
      (setcdr prev-entry (cons po-mode-line-entry (cdr prev-entry)))))
cvsdist c08065
cvsdist c08065
(defun po-update-mode-line-string ()
cvsdist c08065
  "Compute a new statistics string to display in mode line."
cvsdist c08065
  (setq po-mode-line-string
cvsdist c08065
	(concat (format "%dt" po-translated-counter)
cvsdist c08065
		(if (> po-fuzzy-counter 0)
cvsdist c08065
		    (format "+%df" po-fuzzy-counter))
cvsdist c08065
		(if (> po-untranslated-counter 0)
cvsdist c08065
		    (format "+%du" po-untranslated-counter))
cvsdist c08065
		(if (> po-obsolete-counter 0)
cvsdist c08065
		    (format "+%do" po-obsolete-counter))))
cvsdist c08065
  (po-force-mode-line-update))
cvsdist c08065
cvsdist c08065
(defun po-type-counter ()
cvsdist c08065
  "Return the symbol name of the counter appropriate for the current entry."
cvsdist c08065
  (cond ((eq po-entry-type 'obsolete) 'po-obsolete-counter)
cvsdist c08065
	((eq po-entry-type 'fuzzy) 'po-fuzzy-counter)
cvsdist c08065
	((eq po-entry-type 'translated) 'po-translated-counter)
cvsdist c08065
	((eq po-entry-type 'untranslated) 'po-untranslated-counter)
cvsdist c08065
	(t (error (_"Unknown entry type")))))
cvsdist c08065
cvsdist c08065
(defun po-decrease-type-counter ()
cvsdist c08065
  "Decrease the counter corresponding to the nature of the current entry."
cvsdist c08065
  (let ((counter (po-type-counter)))
cvsdist c08065
    (set counter (1- (eval counter)))))
cvsdist c08065
cvsdist c08065
(defun po-increase-type-counter ()
cvsdist c08065
  "Increase the counter corresponding to the nature of the current entry.
cvsdist c08065
Then, update the mode line counters."
cvsdist c08065
  (let ((counter (po-type-counter)))
cvsdist c08065
    (set counter (1+ (eval counter))))
cvsdist c08065
  (po-update-mode-line-string))
cvsdist c08065
cvsdist c08065
;; Avoid byte compiler warnings.
cvsdist c08065
(defvar po-fuzzy-regexp)
cvsdist c08065
(defvar po-untranslated-regexp)
cvsdist c08065
cvsdist c08065
(defun po-compute-counters (flag)
cvsdist c08065
  "Prepare counters for mode line display.  If FLAG, also echo entry position."
cvsdist c08065
  (and flag (po-find-span-of-entry))
cvsdist c08065
  (setq po-translated-counter 0
cvsdist c08065
	po-fuzzy-counter 0
cvsdist c08065
	po-untranslated-counter 0
cvsdist c08065
	po-obsolete-counter 0)
cvsdist 65c016
  (let ((position 0) (total 0) current here)
cvsdist 65c016
    ;; FIXME 'here' looks obsolete / 2001-08-23 03:54:26 CEST -ke-
cvsdist c08065
    (save-excursion
cvsdist 65c016
      (po-find-span-of-entry)
cvsdist 65c016
      (setq current po-start-of-msgstr)
cvsdist c08065
      (goto-char (point-min))
cvsdist 65c016
      ;; While counting, skip the header entry, for consistency with msgfmt.
cvsdist 65c016
      (po-find-span-of-entry)
cvsdist 65c016
      (if (string-equal (po-get-msgid nil) "")
cvsdist d31aa3
	  (goto-char po-end-of-entry))
cvsdist d31aa3
      (if (re-search-forward "^msgid" (point-max) t)
cvsdist d31aa3
	  (progn
cvsdist d31aa3
	    ;; Start counting
cvsdist d31aa3
	    (while (re-search-forward po-any-msgstr-regexp nil t)
cvsdist d31aa3
	      (and (= (% total 20) 0)
cvsdist d31aa3
		   (if flag
cvsdist d31aa3
		       (message (_"Position %d/%d") position total)
cvsdist d31aa3
		     (message (_"Position %d") total)))
cvsdist d31aa3
	      (setq here (point))
cvsdist d31aa3
	      (goto-char (match-beginning 0))
cvsdist d31aa3
	      (setq total (1+ total))
cvsdist d31aa3
	      (and flag (eq (point) current) (setq position total))
cvsdist d31aa3
	      (cond ((eq (following-char) ?#)
cvsdist d31aa3
		     (setq po-obsolete-counter (1+ po-obsolete-counter)))
cvsdist d31aa3
		    ((looking-at po-untranslated-regexp)
cvsdist d31aa3
		     (setq po-untranslated-counter (1+ po-untranslated-counter)))
cvsdist d31aa3
		    (t (setq po-translated-counter (1+ po-translated-counter))))
cvsdist d31aa3
	      (goto-char here))
cvsdist d31aa3
cvsdist d31aa3
	    ;; Make another pass just for the fuzzy entries, kind of kludgey.
cvsdist d31aa3
	    ;; FIXME: Counts will be wrong if untranslated entries are fuzzy, yet
cvsdist d31aa3
	    ;; this should not normally happen.
cvsdist d31aa3
	    (goto-char (point-min))
cvsdist d31aa3
	    (while (re-search-forward po-fuzzy-regexp nil t)
cvsdist d31aa3
	      (setq po-fuzzy-counter (1+ po-fuzzy-counter)))
cvsdist d31aa3
	    (setq po-translated-counter (- po-translated-counter po-fuzzy-counter)))
cvsdist d31aa3
	'()))
cvsdist c08065
cvsdist c08065
    ;; Push the results out.
cvsdist c08065
    (if flag
cvsdist c08065
	(message (_"\
cvsdist c08065
Position %d/%d; %d translated, %d fuzzy, %d untranslated, %d obsolete")
cvsdist c08065
		 position total po-translated-counter po-fuzzy-counter
cvsdist c08065
		 po-untranslated-counter po-obsolete-counter)
cvsdist c08065
      (message "")))
cvsdist c08065
  (po-update-mode-line-string))
cvsdist c08065
cvsdist c08065
(defun po-redisplay ()
cvsdist c08065
  "Redisplay the current entry."
cvsdist c08065
  ;; FIXME: Should try to fit the whole entry on the window.  If this is not
cvsdist c08065
  ;; possible, should try to fit the comment and the msgid.  Otherwise,
cvsdist c08065
  ;; should try to fit the msgid.  Else, the first line of the msgid should
cvsdist c08065
  ;; be at the top of the window.
cvsdist c08065
  (goto-char po-start-of-msgid))
cvsdist c08065
cvsdist c08065
(defun po-other-window ()
cvsdist c08065
  "Get the cursor into another window, out of PO mode."
cvsdist c08065
  (interactive)
cvsdist c08065
  (if (one-window-p t)
cvsdist c08065
      (progn
cvsdist c08065
	(split-window)
cvsdist c08065
	(switch-to-buffer (other-buffer)))
cvsdist c08065
    (other-window 1)))
cvsdist c08065

cvsdist c08065
;;; Processing the PO file header entry.
cvsdist c08065
cvsdist c08065
(defun po-check-file-header ()
cvsdist c08065
  "Create a missing PO mode file header, or replace an oldish one."
cvsdist c08065
  (save-excursion
cvsdist c08065
    (let ((buffer-read-only po-read-only)
cvsdist c08065
	  insert-flag end-of-header)
cvsdist c08065
      (goto-char (point-min))
cvsdist c08065
      (if (re-search-forward po-any-msgstr-regexp nil t)
cvsdist c08065
	  (progn
cvsdist c08065
	    ;; There is at least one entry.
cvsdist c08065
	    (goto-char (match-beginning 0))
cvsdist c08065
	    (previous-line 1)
cvsdist c08065
	    (setq end-of-header (match-end 0))
cvsdist c08065
	    (if (looking-at "msgid \"\"\n")
cvsdist c08065
		;; There is indeed a PO file header.
cvsdist c08065
		(if (re-search-forward "\n\"PO-Revision-Date: "
cvsdist c08065
				       end-of-header t)
cvsdist c08065
		    nil
cvsdist c08065
		  ;; This is an oldish header.  Replace it all.
cvsdist c08065
		  (goto-char end-of-header)
cvsdist c08065
		  (while (> (point) (point-min))
cvsdist c08065
		    (previous-line 1)
cvsdist c08065
		    (insert "#~ ")
cvsdist c08065
		    (beginning-of-line))
cvsdist c08065
		  (beginning-of-line)
cvsdist c08065
		  (setq insert-flag t))
cvsdist c08065
	      ;; The first entry is not a PO file header, insert one.
cvsdist c08065
	      (setq insert-flag t)))
cvsdist c08065
	;; Not a single entry found.
cvsdist c08065
	(setq insert-flag t))
cvsdist c08065
      (goto-char (point-min))
cvsdist 65c016
      (if insert-flag
cvsdist 65c016
	  (progn
cvsdist 65c016
	    (insert po-default-file-header)
cvsdist 65c016
	    (if (not (eobp))
cvsdist 65c016
		(insert "\n")))))))
cvsdist c08065
cvsdist c08065
(defun po-replace-revision-date ()
cvsdist c08065
  "Replace the revision date by current time in the PO file header."
cvsdist c08065
  (if (fboundp 'format-time-string)
cvsdist c08065
      (if (or (eq po-auto-replace-revision-date t)
cvsdist c08065
	      (and (eq po-auto-replace-revision-date 'ask)
cvsdist c08065
		   (y-or-n-p (_"May I set PO-Revision-Date? "))))
cvsdist c08065
	  (save-excursion
cvsdist c08065
	    (goto-char (point-min))
cvsdist c08065
	    (if (re-search-forward "^\"PO-Revision-Date:.*" nil t)
cvsdist c08065
		(let* ((buffer-read-only po-read-only)
cvsdist c08065
		       (time (current-time))
cvsdist c08065
		       (seconds (or (car (current-time-zone time)) 0))
cvsdist c08065
		       (minutes (/ (abs seconds) 60))
cvsdist c08065
		       (zone (format "%c%02d%02d"
cvsdist c08065
				     (if (< seconds 0) ?- ?+)
cvsdist c08065
				     (/ minutes 60)
cvsdist c08065
				     (% minutes 60))))
cvsdist c08065
		  (replace-match
cvsdist c08065
		       (concat "\"PO-Revision-Date: "
cvsdist c08065
			       (format-time-string "%Y-%m-%d %H:%M" time)
cvsdist c08065
			       zone "\\n\"")
cvsdist c08065
		       t t))))
cvsdist c08065
	(message ""))
cvsdist c08065
    (message (_"PO-Revision-Date should be adjusted..."))))
cvsdist c08065

cvsdist c08065
;;; Handling span of entry, entry type and entry attributes.
cvsdist c08065
cvsdist c08065
(defun po-find-span-of-entry ()
cvsdist 65c016
  "Find the extent of the PO file entry where the cursor is.
cvsdist 65c016
Set variables PO-START-OF-ENTRY, PO-START-OF-MSGID, PO-START-OF-MSGSTR,
cvsdist 65c016
PO-END-OF-ENTRY and PO-ENTRY-TYPE to meaningful values.  Decreasing priority
cvsdist 65c016
of type interpretation is: obsolete, fuzzy, untranslated or translated."
cvsdist c08065
  (let ((here (point)))
cvsdist c08065
    (if (re-search-backward po-any-msgstr-regexp nil t)
cvsdist c08065
	(progn
cvsdist c08065
	  ;; After a backward match, (match-end 0) will not extend
cvsdist c08065
	  ;; beyond point, in case point was *inside* the regexp.  We
cvsdist c08065
	  ;; need a dependable (match-end 0), so we redo the match in
cvsdist c08065
	  ;; the forward direction.
cvsdist c08065
	  (re-search-forward po-any-msgstr-regexp)
cvsdist c08065
	  (if (<= (match-end 0) here)
cvsdist c08065
	      (progn
cvsdist c08065
		;; We most probably found the msgstr of the previous
cvsdist c08065
		;; entry.  The current entry then starts just after
cvsdist c08065
		;; its end, save this information just in case.
cvsdist c08065
		(setq po-start-of-entry (match-end 0))
cvsdist c08065
		;; However, it is also possible that we are located in
cvsdist c08065
		;; the crumb after the last entry in the file.  If
cvsdist c08065
		;; yes, we know the middle and end of last PO entry.
cvsdist c08065
		(setq po-start-of-msgstr (match-beginning 0)
cvsdist c08065
		      po-end-of-entry (match-end 0))
cvsdist c08065
		(if (re-search-forward po-any-msgstr-regexp nil t)
cvsdist c08065
		    (progn
cvsdist c08065
		      ;; We definitely were not in the crumb.
cvsdist c08065
		      (setq po-start-of-msgstr (match-beginning 0)
cvsdist c08065
			    po-end-of-entry (match-end 0)))
cvsdist c08065
		  ;; We were in the crumb.  The start of the last PO
cvsdist c08065
		  ;; file entry is the end of the previous msgstr if
cvsdist c08065
		  ;; any, or else, the beginning of the file.
cvsdist c08065
		  (goto-char po-start-of-msgstr)
cvsdist c08065
		  (setq po-start-of-entry
cvsdist c08065
			(if (re-search-backward po-any-msgstr-regexp nil t)
cvsdist c08065
			    (match-end 0)
cvsdist c08065
			  (point-min)))))
cvsdist c08065
	    ;; The cursor was inside msgstr of the current entry.
cvsdist c08065
	    (setq po-start-of-msgstr (match-beginning 0)
cvsdist c08065
		  po-end-of-entry (match-end 0))
cvsdist c08065
	    ;; The start of this entry is the end of the previous
cvsdist c08065
	    ;; msgstr if any, or else, the beginning of the file.
cvsdist c08065
	    (goto-char po-start-of-msgstr)
cvsdist c08065
	    (setq po-start-of-entry
cvsdist c08065
		  (if (re-search-backward po-any-msgstr-regexp nil t)
cvsdist c08065
		      (match-end 0)
cvsdist c08065
		    (point-min)))))
cvsdist c08065
      ;; The cursor was before msgstr in the first entry in the file.
cvsdist c08065
      (setq po-start-of-entry (point-min))
cvsdist c08065
      (goto-char po-start-of-entry)
cvsdist c08065
      ;; There is at least the PO file header, so this should match.
cvsdist c08065
      (re-search-forward po-any-msgstr-regexp)
cvsdist c08065
      (setq po-start-of-msgstr (match-beginning 0)
cvsdist c08065
	    po-end-of-entry (match-end 0)))
cvsdist c08065
    ;; Find start of msgid.
cvsdist c08065
    (goto-char po-start-of-entry)
cvsdist c08065
    (re-search-forward po-any-msgid-regexp)
cvsdist c08065
    (setq po-start-of-msgid (match-beginning 0))
cvsdist c08065
    ;; Classify the entry.
cvsdist c08065
    (setq po-entry-type
cvsdist c08065
	  (if (eq (following-char) ?#)
cvsdist c08065
	      'obsolete
cvsdist c08065
	    (goto-char po-start-of-entry)
cvsdist c08065
	    (if (re-search-forward po-fuzzy-regexp po-start-of-msgid t)
cvsdist c08065
		'fuzzy
cvsdist c08065
	      (goto-char po-start-of-msgstr)
cvsdist c08065
	      (if (looking-at po-untranslated-regexp)
cvsdist c08065
		  'untranslated
cvsdist c08065
		'translated))))
cvsdist c08065
    ;; Put the cursor back where it was.
cvsdist c08065
    (goto-char here)))
cvsdist c08065
cvsdist c08065
(defun po-add-attribute (name)
cvsdist c08065
  "Add attribute NAME to the current entry, unless it is already there."
cvsdist c08065
  (save-excursion
cvsdist c08065
    (let ((buffer-read-only po-read-only))
cvsdist c08065
      (goto-char po-start-of-entry)
cvsdist c08065
      (if (re-search-forward "\n#[,!] .*" po-start-of-msgid t)
cvsdist c08065
	  (save-restriction
cvsdist c08065
	    (narrow-to-region (match-beginning 0) (match-end 0))
cvsdist c08065
	    (goto-char (point-min))
cvsdist c08065
	    (if (re-search-forward (concat "\\b" name "\\b") nil t)
cvsdist c08065
		nil
cvsdist c08065
	      (goto-char (point-max))
cvsdist c08065
	      (insert ", " name)))
cvsdist c08065
	(skip-chars-forward "\n")
cvsdist c08065
	(while (eq (following-char) ?#)
cvsdist c08065
	  (next-line 1))
cvsdist c08065
	(insert "#, " name "\n")))))
cvsdist c08065
cvsdist c08065
(defun po-delete-attribute (name)
cvsdist c08065
  "Delete attribute NAME from the current entry, if any."
cvsdist c08065
  (save-excursion
cvsdist c08065
    (let ((buffer-read-only po-read-only))
cvsdist c08065
      (goto-char po-start-of-entry)
cvsdist c08065
      (if (re-search-forward "\n#[,!] .*" po-start-of-msgid t)
cvsdist c08065
	  (save-restriction
cvsdist c08065
	    (narrow-to-region (match-beginning 0) (match-end 0))
cvsdist c08065
	    (goto-char (point-min))
cvsdist c08065
	    (if (re-search-forward
cvsdist c08065
		 (concat "\\(\n#[,!] " name "$\\|, " name "$\\| " name ",\\)")
cvsdist c08065
		 nil t)
cvsdist c08065
		(replace-match "" t t)))))))
cvsdist c08065

cvsdist c08065
;;; Entry positionning.
cvsdist c08065
cvsdist c08065
(defun po-say-location-depth ()
cvsdist c08065
  "Tell how many entries in the entry location stack."
cvsdist c08065
  (let ((depth (length po-marker-stack)))
cvsdist c08065
    (cond ((= depth 0) (message (_"Empty location stack")))
cvsdist c08065
	  ((= depth 1) (message (_"One entry in location stack")))
cvsdist c08065
	  (t (message (_"%d entries in location stack") depth)))))
cvsdist c08065
cvsdist c08065
(defun po-push-location ()
cvsdist c08065
  "Stack the location of the current entry, for later return."
cvsdist c08065
  (interactive)
cvsdist c08065
  (po-find-span-of-entry)
cvsdist c08065
  (save-excursion
cvsdist c08065
    (goto-char po-start-of-msgid)
cvsdist c08065
    (setq po-marker-stack (cons (point-marker) po-marker-stack)))
cvsdist c08065
  (po-say-location-depth))
cvsdist c08065
cvsdist c08065
(defun po-pop-location ()
cvsdist c08065
  "Unstack a saved location, and return to the corresponding entry."
cvsdist c08065
  (interactive)
cvsdist c08065
  (if po-marker-stack
cvsdist c08065
      (progn
cvsdist c08065
	(goto-char (car po-marker-stack))
cvsdist c08065
	(setq po-marker-stack (cdr po-marker-stack))
cvsdist c08065
	(po-current-entry)
cvsdist c08065
	(po-say-location-depth))
cvsdist c08065
    (error (_"The entry location stack is empty"))))
cvsdist c08065
cvsdist c08065
(defun po-exchange-location ()
cvsdist c08065
  "Exchange the location of the current entry with the top of stack."
cvsdist c08065
  (interactive)
cvsdist c08065
  (if po-marker-stack
cvsdist c08065
      (progn
cvsdist c08065
	(po-find-span-of-entry)
cvsdist c08065
	(goto-char po-start-of-msgid)
cvsdist c08065
	(let ((location (point-marker)))
cvsdist c08065
	  (goto-char (car po-marker-stack))
cvsdist c08065
	  (setq po-marker-stack (cons location (cdr po-marker-stack))))
cvsdist c08065
	(po-current-entry)
cvsdist c08065
	(po-say-location-depth))
cvsdist c08065
    (error (_"The entry location stack is empty"))))
cvsdist c08065
cvsdist c08065
(defun po-current-entry ()
cvsdist c08065
  "Display the current entry."
cvsdist c08065
  (interactive)
cvsdist c08065
  (po-find-span-of-entry)
cvsdist c08065
  (po-redisplay))
cvsdist c08065
cvsdist c08065
(defun po-first-entry-with-regexp (regexp)
cvsdist c08065
  "Display the first entry in the file which msgstr matches REGEXP."
cvsdist c08065
  (let ((here (point)))
cvsdist c08065
    (goto-char (point-min))
cvsdist c08065
    (if (re-search-forward regexp nil t)
cvsdist c08065
	(progn
cvsdist c08065
	  (goto-char (match-beginning 0))
cvsdist c08065
	  (po-current-entry))
cvsdist c08065
      (goto-char here)
cvsdist c08065
      (error (_"There is no such entry")))))
cvsdist c08065
cvsdist c08065
(defun po-last-entry-with-regexp (regexp)
cvsdist c08065
  "Display the last entry in the file which msgstr matches REGEXP."
cvsdist c08065
  (let ((here (point)))
cvsdist c08065
    (goto-char (point-max))
cvsdist c08065
    (if (re-search-backward regexp nil t)
cvsdist c08065
	(po-current-entry)
cvsdist c08065
      (goto-char here)
cvsdist c08065
      (error (_"There is no such entry")))))
cvsdist c08065
cvsdist c08065
(defun po-next-entry-with-regexp (regexp wrap)
cvsdist c08065
  "Display the entry following the current entry which msgstr matches REGEXP.
cvsdist c08065
If WRAP is not nil, the search may wrap around the buffer."
cvsdist c08065
  (po-find-span-of-entry)
cvsdist c08065
  (let ((here (point)))
cvsdist c08065
    (goto-char po-end-of-entry)
cvsdist c08065
    (if (re-search-forward regexp nil t)
cvsdist c08065
	(progn
cvsdist c08065
	  (goto-char (match-beginning 0))
cvsdist c08065
	  (po-current-entry))
cvsdist c08065
      (if (and wrap
cvsdist c08065
	       (progn
cvsdist c08065
		 (goto-char (point-min))
cvsdist c08065
		 (re-search-forward regexp po-start-of-entry t)))
cvsdist c08065
	  (progn
cvsdist c08065
	    (goto-char (match-beginning 0))
cvsdist c08065
	    (po-current-entry)
cvsdist c08065
	    (message (_"Wrapping around the buffer")))
cvsdist c08065
	(goto-char here)
cvsdist c08065
	(error (_"There is no such entry"))))))
cvsdist c08065
cvsdist c08065
(defun po-previous-entry-with-regexp (regexp wrap)
cvsdist c08065
  "Redisplay the entry preceding the current entry which msgstr matches REGEXP.
cvsdist c08065
If WRAP is not nil, the search may wrap around the buffer."
cvsdist c08065
  (po-find-span-of-entry)
cvsdist c08065
  (let ((here (point)))
cvsdist c08065
    (goto-char po-start-of-entry)
cvsdist c08065
    (if (re-search-backward regexp nil t)
cvsdist c08065
	(po-current-entry)
cvsdist c08065
      (if (and wrap
cvsdist c08065
	       (progn
cvsdist c08065
		 (goto-char (point-max))
cvsdist c08065
		 (re-search-backward regexp po-end-of-entry t)))
cvsdist c08065
	  (progn
cvsdist c08065
	    (po-current-entry)
cvsdist c08065
	    (message (_"Wrapping around the buffer")))
cvsdist c08065
	(goto-char here)
cvsdist c08065
	(error (_"There is no such entry"))))))
cvsdist c08065
cvsdist c08065
;; Any entries.
cvsdist c08065
cvsdist c08065
(defun po-first-entry ()
cvsdist c08065
  "Display the first entry."
cvsdist c08065
  (interactive)
cvsdist c08065
  (po-first-entry-with-regexp po-any-msgstr-regexp))
cvsdist c08065
cvsdist c08065
(defun po-last-entry ()
cvsdist c08065
  "Display the last entry."
cvsdist c08065
  (interactive)
cvsdist c08065
  (po-last-entry-with-regexp po-any-msgstr-regexp))
cvsdist c08065
cvsdist c08065
(defun po-next-entry ()
cvsdist c08065
  "Display the entry following the current entry."
cvsdist c08065
  (interactive)
cvsdist c08065
  (po-next-entry-with-regexp po-any-msgstr-regexp nil))
cvsdist c08065
cvsdist c08065
(defun po-previous-entry ()
cvsdist c08065
  "Display the entry preceding the current entry."
cvsdist c08065
  (interactive)
cvsdist c08065
  (po-previous-entry-with-regexp po-any-msgstr-regexp nil))
cvsdist c08065
cvsdist c08065
;; Untranslated entries.
cvsdist c08065
cvsdist c08065
(defvar po-after-entry-regexp
cvsdist 65c016
  "\\(\\'\\|\\(#[ \t]*\\)?$\\)"
cvsdist c08065
  "Regexp which should be true after a full msgstr string matched.")
cvsdist c08065
cvsdist c08065
(defvar po-untranslated-regexp
cvsdist c08065
  (concat "^msgstr[ \t]*\"\"\n" po-after-entry-regexp)
cvsdist c08065
  "Regexp matching a whole msgstr field, but only if active and empty.")
cvsdist c08065
cvsdist c08065
(defun po-next-untranslated-entry ()
cvsdist c08065
  "Find the next untranslated entry, wrapping around if necessary."
cvsdist c08065
  (interactive)
cvsdist c08065
  (po-next-entry-with-regexp po-untranslated-regexp t))
cvsdist c08065
cvsdist c08065
(defun po-previous-untranslated-entry ()
cvsdist c08065
  "Find the previous untranslated entry, wrapping around if necessary."
cvsdist c08065
  (interactive)
cvsdist c08065
  (po-previous-entry-with-regexp po-untranslated-regexp t))
cvsdist c08065
cvsdist c08065
(defun po-msgid-to-msgstr ()
cvsdist c08065
  "Use another window to edit msgstr reinitialized with msgid."
cvsdist c08065
  (interactive)
cvsdist c08065
  (po-find-span-of-entry)
cvsdist c08065
  (if (or (eq po-entry-type 'untranslated)
cvsdist c08065
	  (eq po-entry-type 'obsolete)
cvsdist c08065
	  (y-or-n-p (_"Really loose previous translation? ")))
cvsdist c08065
      (po-set-msgstr (po-get-msgid nil)))
cvsdist c08065
  (message ""))
cvsdist c08065
cvsdist c08065
;; Obsolete entries.
cvsdist c08065
cvsdist c08065
(defvar po-obsolete-msgstr-regexp
cvsdist 65c016
  "^#~[ \t]*msgstr.*\n\\(#~[ \t]*\".*\n\\)*"
cvsdist c08065
  "Regexp matching a whole msgstr field of an obsolete entry.")
cvsdist c08065
cvsdist c08065
(defun po-next-obsolete-entry ()
cvsdist c08065
  "Find the next obsolete entry, wrapping around if necessary."
cvsdist c08065
  (interactive)
cvsdist c08065
  (po-next-entry-with-regexp po-obsolete-msgstr-regexp t))
cvsdist c08065
cvsdist c08065
(defun po-previous-obsolete-entry ()
cvsdist c08065
  "Find the previous obsolete entry, wrapping around if necessary."
cvsdist c08065
  (interactive)
cvsdist c08065
  (po-previous-entry-with-regexp po-obsolete-msgstr-regexp t))
cvsdist c08065
cvsdist c08065
;; Fuzzy entries.
cvsdist c08065
cvsdist c08065
(defvar po-fuzzy-regexp "^#[,!] .*fuzzy"
cvsdist c08065
  "Regexp matching the string inserted by msgmerge for translations
cvsdist c08065
which does not match exactly.")
cvsdist c08065
cvsdist c08065
(defun po-next-fuzzy-entry ()
cvsdist c08065
  "Find the next fuzzy entry, wrapping around if necessary."
cvsdist c08065
  (interactive)
cvsdist c08065
  (po-next-entry-with-regexp po-fuzzy-regexp t))
cvsdist c08065
cvsdist c08065
(defun po-previous-fuzzy-entry ()
cvsdist c08065
  "Find the next fuzzy entry, wrapping around if necessary."
cvsdist c08065
  (interactive)
cvsdist c08065
  (po-previous-entry-with-regexp po-fuzzy-regexp t))
cvsdist c08065
cvsdist c08065
(defun po-unfuzzy ()
cvsdist c08065
  "Remove the fuzzy attribute for the current entry."
cvsdist c08065
  (interactive)
cvsdist c08065
  (po-find-span-of-entry)
cvsdist c08065
  (cond ((eq po-entry-type 'fuzzy)
cvsdist c08065
	 (po-decrease-type-counter)
cvsdist c08065
	 (po-delete-attribute "fuzzy")
cvsdist c08065
	 (po-current-entry)
cvsdist c08065
	 (po-increase-type-counter)))
cvsdist c08065
  (if po-auto-select-on-unfuzzy
cvsdist c08065
      (po-auto-select-entry))
cvsdist c08065
  (po-update-mode-line-string))
cvsdist c08065
cvsdist c08065
;; Translated entries.
cvsdist c08065
cvsdist c08065
(defun po-next-translated-entry ()
cvsdist 65c016
  "Find the next translated entry, wrapping around if necessary."
cvsdist c08065
  (interactive)
cvsdist c08065
  (if (= po-translated-counter 0)
cvsdist c08065
      (error (_"There is no such entry"))
cvsdist 65c016
    (po-next-entry-with-regexp po-any-msgstr-regexp t)
cvsdist c08065
    (po-find-span-of-entry)
cvsdist c08065
    (while (not (eq po-entry-type 'translated))
cvsdist c08065
      (po-next-entry-with-regexp po-any-msgstr-regexp t)
cvsdist c08065
      (po-find-span-of-entry))))
cvsdist c08065
cvsdist c08065
(defun po-previous-translated-entry ()
cvsdist 65c016
  "Find the previous translated entry, wrapping around if necessary."
cvsdist c08065
  (interactive)
cvsdist c08065
  (if (= po-translated-counter 0)
cvsdist c08065
      (error (_"There is no such entry"))
cvsdist c08065
    (po-previous-entry-with-regexp po-any-msgstr-regexp t)
cvsdist c08065
    (po-find-span-of-entry)
cvsdist c08065
    (while (not (eq po-entry-type 'translated))
cvsdist c08065
      (po-previous-entry-with-regexp po-untranslated-regexp t)
cvsdist 65c016
      (po-find-span-of-entry))))
cvsdist c08065
cvsdist c08065
;; Auto-selection feature.
cvsdist c08065
cvsdist c08065
(defun po-auto-select-entry ()
cvsdist c08065
  "Select the next entry having the same type as the current one.
cvsdist c08065
If none, wrap from the beginning of the buffer with another type,
cvsdist c08065
going from untranslated to fuzzy, and from fuzzy to obsolete.
cvsdist c08065
Plain translated entries are always disregarded unless there are
cvsdist c08065
no entries of the other types."
cvsdist c08065
  (interactive)
cvsdist c08065
  (po-find-span-of-entry)
cvsdist c08065
  (goto-char po-end-of-entry)
cvsdist c08065
  (if (and (= po-untranslated-counter 0)
cvsdist c08065
	   (= po-fuzzy-counter 0)
cvsdist c08065
	   (= po-obsolete-counter 0))
cvsdist c08065
      ;; All entries are plain translated.  Next entry will do, or
cvsdist c08065
      ;; wrap around if there is none.
cvsdist c08065
      (if (re-search-forward po-any-msgstr-regexp nil t)
cvsdist c08065
	  (goto-char (match-beginning 0))
cvsdist c08065
	(goto-char (point-min)))
cvsdist c08065
    ;; If over a translated entry, look for an untranslated one first.
cvsdist c08065
    ;; Else, look for an entry of the same type first.
cvsdist c08065
    (let ((goal (if (eq po-entry-type 'translated)
cvsdist c08065
		    'untranslated
cvsdist c08065
		  po-entry-type)))
cvsdist c08065
      (while goal
cvsdist c08065
	;; Find an untranslated entry, or wrap up for a fuzzy entry.
cvsdist c08065
	(if (eq goal 'untranslated)
cvsdist c08065
	    (if (and (> po-untranslated-counter 0)
cvsdist c08065
		     (re-search-forward po-untranslated-regexp nil t))
cvsdist c08065
		(progn
cvsdist c08065
		  (goto-char (match-beginning 0))
cvsdist c08065
		  (setq goal nil))
cvsdist c08065
	      (goto-char (point-min))
cvsdist c08065
	      (setq goal 'fuzzy)))
cvsdist c08065
	;; Find a fuzzy entry, or wrap up for an obsolete entry.
cvsdist c08065
	(if (eq goal 'fuzzy)
cvsdist c08065
	    (if (and (> po-fuzzy-counter 0)
cvsdist c08065
		     (re-search-forward po-fuzzy-regexp nil t))
cvsdist c08065
		(progn
cvsdist c08065
		  (goto-char (match-beginning 0))
cvsdist c08065
		  (setq goal nil))
cvsdist c08065
	      (goto-char (point-min))
cvsdist c08065
	      (setq goal 'obsolete)))
cvsdist c08065
	;; Find an obsolete entry, or wrap up for an untranslated entry.
cvsdist c08065
	(if (eq goal 'obsolete)
cvsdist c08065
	    (if (and (> po-obsolete-counter 0)
cvsdist c08065
		     (re-search-forward po-obsolete-msgstr-regexp nil t))
cvsdist c08065
		(progn
cvsdist c08065
		  (goto-char (match-beginning 0))
cvsdist c08065
		  (setq goal nil))
cvsdist c08065
	      (goto-char (point-min))
cvsdist c08065
	      (setq goal 'untranslated))))))
cvsdist c08065
  ;; Display this entry nicely.
cvsdist c08065
  (po-current-entry))
cvsdist c08065

cvsdist c08065
;;; Killing and yanking fields.
cvsdist c08065
cvsdist c08065
(defun po-extract-unquoted (buffer start end)
cvsdist c08065
  "Extract and return the unquoted string in BUFFER going from START to END.
cvsdist c08065
Crumb preceding or following the quoted string is ignored."
cvsdist 65c016
  (save-excursion
cvsdist 65c016
    (goto-char start)
cvsdist c08065
    (search-forward "\"")
cvsdist 65c016
    (setq start (point))
cvsdist 65c016
    (goto-char end)
cvsdist c08065
    (search-backward "\"")
cvsdist 65c016
    (setq end (point)))
cvsdist 65c016
  (po-extract-part-unquoted buffer start end))
cvsdist 65c016
cvsdist 65c016
(defun po-extract-part-unquoted (buffer start end)
cvsdist 65c016
  "Extract and return the unquoted string in BUFFER going from START to END.
cvsdist 65c016
Surrounding quotes are already excluded by the position of START and END."
cvsdist 65c016
  (po-with-temp-buffer
cvsdist 65c016
   (insert-buffer-substring buffer start end)
cvsdist 65c016
   ;; Glue concatenated strings.
cvsdist 65c016
   (goto-char (point-min))
cvsdist 65c016
   (while (re-search-forward "\"[ \t]*\\\\?\n\\(#~\\)?[ \t]*\"" nil t)
cvsdist 65c016
     (replace-match "" t t))
cvsdist 65c016
   ;; Remove escaped newlines.
cvsdist 65c016
   (goto-char (point-min))
cvsdist 65c016
   (while (re-search-forward "\\\\[ \t]*\n" nil t)
cvsdist 65c016
     (replace-match "" t t))
cvsdist 65c016
   ;; Unquote individual characters.
cvsdist 65c016
   (goto-char (point-min))
cvsdist 65c016
   (while (re-search-forward "\\\\[\"abfnt\\0-7]" nil t)
cvsdist 65c016
     (cond ((eq (preceding-char) ?\") (replace-match "\"" t t))
cvsdist 65c016
	   ((eq (preceding-char) ?a) (replace-match "\a" t t))
cvsdist 65c016
	   ((eq (preceding-char) ?b) (replace-match "\b" t t))
cvsdist 65c016
	   ((eq (preceding-char) ?f) (replace-match "\f" t t))
cvsdist 65c016
	   ((eq (preceding-char) ?n) (replace-match "\n" t t))
cvsdist 65c016
	   ((eq (preceding-char) ?t) (replace-match "\t" t t))
cvsdist 65c016
	   ((eq (preceding-char) ?\\) (replace-match "\\" t t))
cvsdist 65c016
	   (t (let ((value (- (preceding-char) ?0)))
cvsdist 65c016
		(replace-match "" t t)
cvsdist 65c016
		(while (looking-at "[0-7]")
cvsdist 65c016
		  (setq value (+ (* 8 value) (- (following-char) ?0)))
cvsdist 65c016
		  (replace-match "" t t))
cvsdist 65c016
		(insert value)))))
cvsdist 65c016
   (buffer-string)))
cvsdist c08065
cvsdist c08065
(defun po-eval-requoted (form prefix obsolete)
cvsdist c08065
  "Eval FORM, which inserts a string, and return the string fully requoted.
cvsdist c08065
If PREFIX, precede the result with its contents.  If OBSOLETE, comment all
cvsdist c08065
generated lines in the returned string.  Evaluating FORM should insert the
cvsdist c08065
wanted string in the buffer which is current at the time of evaluation.
cvsdist c08065
If FORM is itself a string, then this string is used for insertion."
cvsdist c08065
  (po-with-temp-buffer
cvsdist c08065
    (if (stringp form)
cvsdist c08065
	(insert form)
cvsdist c08065
      (push-mark)
cvsdist c08065
      (eval form))
cvsdist c08065
    (goto-char (point-min))
cvsdist c08065
    (let ((multi-line (re-search-forward "[^\n]\n+[^\n]" nil t)))
cvsdist c08065
      (goto-char (point-min))
cvsdist 65c016
      (while (re-search-forward "[\"\a\b\f\n\r\t\\]" nil t)
cvsdist c08065
	(cond ((eq (preceding-char) ?\") (replace-match "\\\"" t t))
cvsdist c08065
	      ((eq (preceding-char) ?\a) (replace-match "\\a" t t))
cvsdist c08065
	      ((eq (preceding-char) ?\b) (replace-match "\\b" t t))
cvsdist c08065
	      ((eq (preceding-char) ?\f) (replace-match "\\f" t t))
cvsdist c08065
	      ((eq (preceding-char) ?\n)
cvsdist c08065
	       (replace-match (if (or (not multi-line) (eobp))
cvsdist c08065
				  "\\n"
cvsdist c08065
				"\\n\"\n\"")
cvsdist c08065
			      t t))
cvsdist 65c016
	      ((eq (preceding-char) ?\r) (replace-match "\\r" t t))
cvsdist c08065
	      ((eq (preceding-char) ?\t) (replace-match "\\t" t t))
cvsdist c08065
	      ((eq (preceding-char) ?\\) (replace-match "\\\\" t t))))
cvsdist c08065
      (goto-char (point-min))
cvsdist c08065
      (if prefix (insert prefix " "))
cvsdist c08065
      (insert (if multi-line "\"\"\n\"" "\""))
cvsdist c08065
      (goto-char (point-max))
cvsdist c08065
      (insert "\"")
cvsdist c08065
      (if prefix (insert "\n"))
cvsdist c08065
      (if obsolete
cvsdist c08065
	  (progn
cvsdist c08065
	    (goto-char (point-min))
cvsdist c08065
	    (while (not (eobp))
cvsdist c08065
	      (or (eq (following-char) ?\n) (insert "#~ "))
cvsdist c08065
	      (search-forward "\n"))))
cvsdist c08065
      (buffer-string))))
cvsdist c08065
cvsdist c08065
(defun po-get-msgid (kill)
cvsdist c08065
  "Extract and return the unquoted msgid string.
cvsdist c08065
If KILL, then add the unquoted string to the kill ring."
cvsdist c08065
  (let ((string (po-extract-unquoted (current-buffer)
cvsdist c08065
				     po-start-of-msgid po-start-of-msgstr)))
cvsdist c08065
    (if kill (po-kill-new string))
cvsdist c08065
    string))
cvsdist c08065
cvsdist c08065
(defun po-get-msgstr (kill)
cvsdist c08065
  "Extract and return the unquoted msgstr string.
cvsdist c08065
If KILL, then add the unquoted string to the kill ring."
cvsdist c08065
  (let ((string (po-extract-unquoted (current-buffer)
cvsdist c08065
				     po-start-of-msgstr po-end-of-entry)))
cvsdist c08065
    (if kill (po-kill-new string))
cvsdist c08065
    string))
cvsdist c08065
cvsdist c08065
(defun po-set-msgid (form)
cvsdist c08065
  "Replace the current msgid, using FORM to get a string.
cvsdist c08065
Evaluating FORM should insert the wanted string in the current buffer.  If
cvsdist c08065
FORM is itself a string, then this string is used for insertion.  The string
cvsdist c08065
is properly requoted before the replacement occurs.
cvsdist c08065
cvsdist 65c016
Returns 'nil' if the buffer has not been modified, for if the new msgid
cvsdist c08065
described by FORM is merely identical to the msgid already in place."
cvsdist c08065
  (let ((string (po-eval-requoted form "msgid" (eq po-entry-type 'obsolete))))
cvsdist c08065
    (save-excursion
cvsdist c08065
      (goto-char po-start-of-entry)
cvsdist c08065
      (re-search-forward po-any-msgid-regexp po-start-of-msgstr)
cvsdist 65c016
      (and (not (string-equal (po-match-string 0) string))
cvsdist c08065
	   (let ((buffer-read-only po-read-only))
cvsdist c08065
	     (replace-match string t t)
cvsdist c08065
	     (goto-char po-start-of-msgid)
cvsdist c08065
	     (po-find-span-of-entry)
cvsdist c08065
	     t)))))
cvsdist c08065
cvsdist c08065
(defun po-set-msgstr (form)
cvsdist c08065
  "Replace the current msgstr or msgstr[], using FORM to get a string.
cvsdist c08065
Evaluating FORM should insert the wanted string in the current buffer.  If
cvsdist c08065
FORM is itself a string, then this string is used for insertion.  The string
cvsdist c08065
is properly requoted before the replacement occurs.
cvsdist c08065
cvsdist 65c016
Returns 'nil' if the buffer has not been modified, for if the new msgstr
cvsdist c08065
described by FORM is merely identical to the msgstr already in place."
cvsdist c08065
  (let ((string (po-eval-requoted form "msgstr" (eq po-entry-type 'obsolete)))
cvsdist c08065
        (msgstr-idx nil))
cvsdist c08065
    (save-excursion
cvsdist c08065
      (goto-char po-start-of-entry)
cvsdist c08065
      (save-excursion                   ; check for an indexed msgstr
cvsdist 65c016
        (if (re-search-forward po-msgstr-idx-keyword-regexp
cvsdist 65c016
			       po-end-of-entry t)
cvsdist 65c016
	    (setq msgstr-idx (buffer-substring-no-properties
cvsdist 65c016
			      (match-beginning 0) (match-end 0)))))
cvsdist c08065
      (re-search-forward po-any-msgstr-regexp po-end-of-entry)
cvsdist 65c016
      (and (not (string-equal (po-match-string 0) string))
cvsdist c08065
	   (let ((buffer-read-only po-read-only))
cvsdist c08065
	     (po-decrease-type-counter)
cvsdist c08065
	     (replace-match string t t)
cvsdist c08065
             (goto-char (match-beginning 0))
cvsdist 65c016
             (if (eq msgstr-idx nil) ; hack: replace msgstr with msgstr[d]
cvsdist 65c016
		 nil
cvsdist 65c016
	       (insert msgstr-idx)
cvsdist 65c016
	       (looking-at "\\(#~[ \t]*\\)?msgstr")
cvsdist 65c016
	       (replace-match ""))
cvsdist c08065
	     (goto-char po-start-of-msgid)
cvsdist c08065
	     (po-find-span-of-entry)
cvsdist c08065
	     (po-increase-type-counter)
cvsdist c08065
	     t)))))
cvsdist c08065
cvsdist c08065
(defun po-kill-ring-save-msgstr ()
cvsdist c08065
  "Push the msgstr string from current entry on the kill ring."
cvsdist c08065
  (interactive)
cvsdist c08065
  (po-find-span-of-entry)
cvsdist c08065
  (po-get-msgstr t))
cvsdist c08065
cvsdist c08065
(defun po-kill-msgstr ()
cvsdist c08065
  "Empty the msgstr string from current entry, pushing it on the kill ring."
cvsdist c08065
  (interactive)
cvsdist c08065
  (po-kill-ring-save-msgstr)
cvsdist c08065
  (po-set-msgstr ""))
cvsdist c08065
cvsdist c08065
(defun po-yank-msgstr ()
cvsdist c08065
  "Replace the current msgstr string by the top of the kill ring."
cvsdist c08065
  (interactive)
cvsdist c08065
  (po-find-span-of-entry)
cvsdist c08065
  (po-set-msgstr (if (eq last-command 'yank) '(yank-pop 1) '(yank)))
cvsdist c08065
  (setq this-command 'yank))
cvsdist c08065
cvsdist c08065
(defun po-fade-out-entry ()
cvsdist c08065
  "Mark an active entry as fuzzy; obsolete a fuzzy or untranslated entry;
cvsdist c08065
or completely delete an obsolete entry, saving its msgstr on the kill ring."
cvsdist c08065
  (interactive)
cvsdist c08065
  (po-find-span-of-entry)
cvsdist c08065
cvsdist c08065
  (cond ((eq po-entry-type 'translated)
cvsdist c08065
	 (po-decrease-type-counter)
cvsdist c08065
	 (po-add-attribute "fuzzy")
cvsdist c08065
	 (po-current-entry)
cvsdist c08065
	 (po-increase-type-counter))
cvsdist c08065
cvsdist c08065
	((or (eq po-entry-type 'fuzzy)
cvsdist c08065
	     (eq po-entry-type 'untranslated))
cvsdist 65c016
	 (if (y-or-n-p (_"Should I really obsolete this entry? "))
cvsdist c08065
	     (progn
cvsdist c08065
	       (po-decrease-type-counter)
cvsdist c08065
	       (save-excursion
cvsdist c08065
		 (save-restriction
cvsdist c08065
		   (narrow-to-region po-start-of-entry po-end-of-entry)
cvsdist c08065
		   (let ((buffer-read-only po-read-only))
cvsdist c08065
		     (goto-char (point-min))
cvsdist c08065
		     (skip-chars-forward "\n")
cvsdist c08065
		     (while (not (eobp))
cvsdist c08065
		       (insert "#~ ")
cvsdist c08065
		       (search-forward "\n")))))
cvsdist c08065
	       (po-current-entry)
cvsdist c08065
	       (po-increase-type-counter)))
cvsdist c08065
	 (message ""))
cvsdist c08065
cvsdist c08065
	((and (eq po-entry-type 'obsolete)
cvsdist c08065
	      (po-check-for-pending-edit po-start-of-msgid)
cvsdist c08065
	      (po-check-for-pending-edit po-start-of-msgstr))
cvsdist c08065
	 (po-decrease-type-counter)
cvsdist c08065
	 (po-update-mode-line-string)
cvsdist c08065
	 (po-get-msgstr t)
cvsdist c08065
	 (let ((buffer-read-only po-read-only))
cvsdist c08065
	   (delete-region po-start-of-entry po-end-of-entry))
cvsdist c08065
	 (goto-char po-start-of-entry)
cvsdist c08065
	 (if (re-search-forward po-any-msgstr-regexp nil t)
cvsdist c08065
	     (goto-char (match-beginning 0))
cvsdist c08065
	   (re-search-backward po-any-msgstr-regexp nil t))
cvsdist c08065
	 (po-current-entry)
cvsdist c08065
	 (message ""))))
cvsdist c08065

cvsdist c08065
;;; Killing and yanking comments.
cvsdist c08065
cvsdist c08065
(defvar po-active-comment-regexp
cvsdist c08065
  "^\\(#\n\\|# .*\n\\)+"
cvsdist c08065
  "Regexp matching the whole editable comment part of an active entry.")
cvsdist c08065
cvsdist c08065
(defvar po-obsolete-comment-regexp
cvsdist 65c016
  "^\\(#~ #\n\\|#~ # .*\n\\)+"
cvsdist c08065
  "Regexp matching the whole editable comment part of an obsolete entry.")
cvsdist c08065
cvsdist c08065
(defun po-get-comment (kill-flag)
cvsdist c08065
  "Extract and return the editable comment string, uncommented.
cvsdist c08065
If KILL-FLAG, then add the unquoted comment to the kill ring."
cvsdist c08065
  (let ((buffer (current-buffer))
cvsdist c08065
	(obsolete (eq po-entry-type 'obsolete)))
cvsdist c08065
    (save-excursion
cvsdist c08065
      (goto-char po-start-of-entry)
cvsdist c08065
      (if (re-search-forward (if obsolete po-obsolete-comment-regexp
cvsdist c08065
			         po-active-comment-regexp)
cvsdist c08065
			     po-end-of-entry t)
cvsdist c08065
	  (po-with-temp-buffer
cvsdist c08065
	    (insert-buffer-substring buffer (match-beginning 0) (match-end 0))
cvsdist c08065
	    (goto-char (point-min))
cvsdist c08065
	    (while (not (eobp))
cvsdist 65c016
	      (if (looking-at (if obsolete "#~ # ?" "# ?"))
cvsdist c08065
		  (replace-match "" t t))
cvsdist c08065
	      (forward-line 1))
cvsdist c08065
	    (and kill-flag (copy-region-as-kill (point-min) (point-max)))
cvsdist c08065
	    (buffer-string))
cvsdist c08065
	""))))
cvsdist c08065
cvsdist c08065
(defun po-set-comment (form)
cvsdist c08065
  "Using FORM to get a string, replace the current editable comment.
cvsdist c08065
Evaluating FORM should insert the wanted string in the current buffer.
cvsdist c08065
If FORM is itself a string, then this string is used for insertion.
cvsdist c08065
The string is properly recommented before the replacement occurs."
cvsdist c08065
  (let ((obsolete (eq po-entry-type 'obsolete))
cvsdist c08065
	string)
cvsdist c08065
    (po-with-temp-buffer
cvsdist c08065
      (if (stringp form)
cvsdist c08065
	  (insert form)
cvsdist c08065
	(push-mark)
cvsdist c08065
	(eval form))
cvsdist c08065
      (if (not (or (bobp) (= (preceding-char) ?\n)))
cvsdist c08065
	  (insert "\n"))
cvsdist c08065
      (goto-char (point-min))
cvsdist c08065
      (while (not (eobp))
cvsdist c08065
	(insert (if (= (following-char) ?\n)
cvsdist c08065
		    (if obsolete "#~ #" "#")
cvsdist c08065
		  (if obsolete "#~ # " "# ")))
cvsdist c08065
	(search-forward "\n"))
cvsdist c08065
      (setq string (buffer-string)))
cvsdist c08065
    (goto-char po-start-of-entry)
cvsdist c08065
    (if (re-search-forward
cvsdist c08065
	 (if obsolete po-obsolete-comment-regexp po-active-comment-regexp)
cvsdist c08065
	 po-end-of-entry t)
cvsdist 65c016
	(if (not (string-equal (po-match-string 0) string))
cvsdist c08065
	    (let ((buffer-read-only po-read-only))
cvsdist c08065
	      (replace-match string t t)))
cvsdist c08065
      (skip-chars-forward " \t\n")
cvsdist c08065
      (let ((buffer-read-only po-read-only))
cvsdist c08065
	(insert string))))
cvsdist c08065
  (po-current-entry))
cvsdist c08065
cvsdist c08065
(defun po-kill-ring-save-comment ()
cvsdist c08065
  "Push the msgstr string from current entry on the kill ring."
cvsdist c08065
  (interactive)
cvsdist c08065
  (po-find-span-of-entry)
cvsdist c08065
  (po-get-comment t))
cvsdist c08065
cvsdist c08065
(defun po-kill-comment ()
cvsdist c08065
  "Empty the msgstr string from current entry, pushing it on the kill ring."
cvsdist c08065
  (interactive)
cvsdist c08065
  (po-kill-ring-save-comment)
cvsdist c08065
  (po-set-comment "")
cvsdist c08065
  (po-redisplay))
cvsdist c08065
cvsdist c08065
(defun po-yank-comment ()
cvsdist c08065
  "Replace the current comment string by the top of the kill ring."
cvsdist c08065
  (interactive)
cvsdist c08065
  (po-find-span-of-entry)
cvsdist c08065
  (po-set-comment (if (eq last-command 'yank) '(yank-pop 1) '(yank)))
cvsdist c08065
  (setq this-command 'yank)
cvsdist c08065
  (po-redisplay))
cvsdist c08065

cvsdist c08065
;;; Editing management and submode.
cvsdist c08065
cvsdist c08065
;; In a string edit buffer, BACK-POINTER points to one of the slots of the
cvsdist c08065
;; list EDITED-FIELDS kept in the PO buffer.  See its description elsewhere.
cvsdist c08065
;; Reminder: slots have the form (ENTRY-MARKER EDIT-BUFFER OVERLAY-INFO).
cvsdist c08065
cvsdist c08065
(defvar po-subedit-back-pointer)
cvsdist c08065
cvsdist c08065
(defun po-clean-out-killed-edits ()
cvsdist c08065
  "From EDITED-FIELDS, clean out any edit having a killed edit buffer."
cvsdist c08065
  (let ((cursor po-edited-fields))
cvsdist c08065
    (while cursor
cvsdist c08065
      (let ((slot (car cursor)))
cvsdist c08065
	(setq cursor (cdr cursor))
cvsdist c08065
	(if (buffer-name (nth 1 slot))
cvsdist c08065
	    nil
cvsdist c08065
	  (let ((overlay (nth 2 slot)))
cvsdist c08065
	    (and overlay (po-dehighlight overlay)))
cvsdist c08065
	  (setq po-edited-fields (delete slot po-edited-fields)))))))
cvsdist c08065
cvsdist c08065
(defun po-check-all-pending-edits ()
cvsdist c08065
  "Resume any pending edit.  Return nil if some remains."
cvsdist c08065
  (po-clean-out-killed-edits)
cvsdist c08065
  (or (null po-edited-fields)
cvsdist c08065
      (let ((slot (car po-edited-fields)))
cvsdist c08065
	(goto-char (nth 0 slot))
cvsdist c08065
	(pop-to-buffer (nth 1 slot))
cvsdist c08065
	(let ((overlay (nth 2 slot)))
cvsdist c08065
	  (and overlay (po-rehighlight overlay)))
cvsdist c08065
	(message po-subedit-message)
cvsdist c08065
	nil)))
cvsdist c08065
cvsdist c08065
(defun po-check-for-pending-edit (position)
cvsdist c08065
  "Resume any pending edit at POSITION.  Return nil if such edit exists."
cvsdist c08065
  (po-clean-out-killed-edits)
cvsdist c08065
  (let ((marker (make-marker)))
cvsdist c08065
    (set-marker marker position)
cvsdist c08065
    (let ((slot (assoc marker po-edited-fields)))
cvsdist c08065
      (if slot
cvsdist c08065
	  (progn
cvsdist c08065
	    (goto-char marker)
cvsdist c08065
	    (pop-to-buffer (nth 1 slot))
cvsdist c08065
	    (let ((overlay (nth 2 slot)))
cvsdist c08065
	      (and overlay (po-rehighlight overlay)))
cvsdist c08065
	    (message po-subedit-message)))
cvsdist c08065
      (not slot))))
cvsdist c08065
cvsdist c08065
(defun po-edit-out-full ()
cvsdist c08065
  "Get out of PO mode, leaving PO file buffer in fundamental mode."
cvsdist c08065
  (interactive)
cvsdist c08065
  (if (and (po-check-all-pending-edits)
cvsdist c08065
	   (yes-or-no-p (_"Should I let you edit the whole PO file? ")))
cvsdist c08065
      (progn
cvsdist c08065
	(setq buffer-read-only po-read-only)
cvsdist c08065
	(fundamental-mode)
cvsdist 65c016
	(message (_"Type 'M-x po-mode RET' once done")))))
cvsdist c08065
cvsdist 65c016
(defun po-ediff-quit ()
cvsdist 65c016
  "Quit ediff and exit `recursive-edit'."
cvsdist 65c016
  (interactive)
cvsdist 65c016
  (ediff-quit t)
cvsdist 65c016
  (exit-recursive-edit))
cvsdist 65c016
cvsdist 65c016
(add-hook 'ediff-keymap-setup-hook
cvsdist 65c016
	  '(lambda ()
cvsdist 65c016
	     (define-key ediff-mode-map "Q" 'po-ediff-quit)))
cvsdist 65c016
cvsdist 65c016
(defun po-ediff-buffers-exit-recursive (b1 b2 oldbuf end)
cvsdist 65c016
  "Ediff buffer B1 and B2, pop back to OLDBUF and replace the old variants.
cvsdist 65c016
This function will delete the first two variants in OLDBUF, call
cvsdist 65c016
`ediff-buffers' to compare both strings and replace the two variants in
cvsdist 65c016
OLDBUF with the contents of B2.
cvsdist 65c016
Once done kill B1 and B2.
cvsdist 65c016
cvsdist 65c016
For more info cf. `po-subedit-ediff'."
cvsdist 65c016
  (ediff-buffers b1 b2)
cvsdist 65c016
  (recursive-edit)
cvsdist 65c016
  (pop-to-buffer oldbuf)
cvsdist 65c016
  (delete-region (point-min) end)
cvsdist 65c016
  (insert-buffer b2)
cvsdist 65c016
  (mapc 'kill-buffer `(,b1 ,b2))
cvsdist 65c016
  (display-buffer entry-buffer t))
cvsdist 65c016
cvsdist 65c016
(defun po-subedit-ediff ()
cvsdist 65c016
  "Edit the subedit buffer using `ediff'.
cvsdist 65c016
`po-subedit-ediff' calls `po-ediff-buffers-exit-recursive' to edit translation
cvsdist d31aa3
variants side by side if they are actually different; if variants are equal just
cvsdist d31aa3
delete the first one.
cvsdist d31aa3
cvsdist d31aa3
`msgcat' is able to produce those variants; every variant is marked with:
cvsdist 65c016
cvsdist 65c016
#-#-#-#-#  file name reference  #-#-#-#-#
cvsdist 65c016
cvsdist 65c016
Put changes in second buffer.
cvsdist 65c016
cvsdist 65c016
When done with the `ediff' session press \\[exit-recursive-edit] exit to
cvsdist 65c016
`recursive-edit', or call \\[po-ediff-quit] (`Q') in the ediff control panel."
cvsdist 65c016
  (interactive)
cvsdist 65c016
  (let* ((marker-regex "^#-#-#-#-#  \\(.*\\)  #-#-#-#-#\n")
cvsdist 65c016
	 (buf1 " *po-msgstr-1") ; default if first marker is missing
cvsdist 65c016
	 buf2 start-1 end-1 start-2 end-2
cvsdist 65c016
	 (back-pointer po-subedit-back-pointer)
cvsdist 65c016
	 (entry-marker (nth 0 back-pointer))
cvsdist 65c016
	 (entry-buffer (marker-buffer entry-marker)))
cvsdist 65c016
    (goto-char (point-min))
cvsdist 65c016
    (if (looking-at marker-regex)
cvsdist 65c016
	(and (setq buf1 (match-string-no-properties 1))
cvsdist 65c016
	     (forward-line 1)))
cvsdist 65c016
    (setq start-1 (point))
cvsdist 65c016
    (if (not (re-search-forward marker-regex (point-max) t))
cvsdist 65c016
	(error "Only 1 msgstr found")
cvsdist 65c016
      (setq buf2 (match-string-no-properties 1)
cvsdist 65c016
	    end-1 (match-beginning 0))
cvsdist 65c016
      (let ((oldbuf (current-buffer)))
cvsdist 65c016
	(save-current-buffer
cvsdist 65c016
	  (set-buffer (get-buffer-create
cvsdist 65c016
		       (generate-new-buffer-name buf1)))
cvsdist 65c016
	  (setq buffer-read-only nil)
cvsdist 65c016
	  (erase-buffer)
cvsdist 65c016
	  (insert-buffer-substring oldbuf start-1 end-1)
cvsdist 65c016
	  (setq buffer-read-only t))
cvsdist 65c016
	
cvsdist 65c016
	(setq start-2 (point))
cvsdist 65c016
	(save-excursion
cvsdist 65c016
	  ;; check for a third variant; if found ignore it
cvsdist 65c016
	  (if (re-search-forward marker-regex (point-max) t)
cvsdist 65c016
	      (setq end-2 (match-beginning 0))
cvsdist 65c016
	    (setq end-2 (goto-char (1- (point-max))))))
cvsdist 65c016
	(save-current-buffer
cvsdist 65c016
	  (set-buffer (get-buffer-create
cvsdist 65c016
		       (generate-new-buffer-name buf2)))
cvsdist 65c016
	  (erase-buffer)
cvsdist 65c016
	  (insert-buffer-substring oldbuf start-2 end-2))
cvsdist 65c016
cvsdist d31aa3
	(if (not (string-equal (buffer-substring-no-properties start-1 end-1)
cvsdist d31aa3
			       (buffer-substring-no-properties start-2 end-2)))
cvsdist d31aa3
	    (po-ediff-buffers-exit-recursive buf1 buf2 oldbuf end-2)
cvsdist d31aa3
	  (message "Variants are equal; delete %s" buf1)
cvsdist d31aa3
	  (forward-line -1)
cvsdist d31aa3
	  (delete-region (point-min) (point)))))))
cvsdist c08065
cvsdist c08065
(defun po-subedit-abort ()
cvsdist c08065
  "Exit the subedit buffer, merely discarding its contents."
cvsdist c08065
  (interactive)
cvsdist c08065
  (let* ((edit-buffer (current-buffer))
cvsdist c08065
	 (back-pointer po-subedit-back-pointer)
cvsdist 65c016
	 (entry-marker (nth 0 back-pointer))
cvsdist 65c016
	 (overlay-info (nth 2 back-pointer))
cvsdist 65c016
	 (entry-buffer (marker-buffer entry-marker)))
cvsdist 65c016
    (if (null entry-buffer)
cvsdist c08065
	(error (_"Corresponding PO buffer does not exist anymore"))
cvsdist c08065
      (or (one-window-p) (delete-window))
cvsdist 65c016
      (switch-to-buffer entry-buffer)
cvsdist 65c016
      (goto-char entry-marker)
cvsdist 65c016
      (and overlay-info (po-dehighlight overlay-info))
cvsdist c08065
      (kill-buffer edit-buffer)
cvsdist c08065
      (setq po-edited-fields (delete back-pointer po-edited-fields)))))
cvsdist c08065
cvsdist c08065
(defun po-subedit-exit ()
cvsdist c08065
  "Exit the subedit buffer, replacing the string in the PO buffer."
cvsdist c08065
  (interactive)
cvsdist c08065
  (goto-char (point-max))
cvsdist c08065
  (skip-chars-backward " \t\n")
cvsdist c08065
  (if (eq (preceding-char) ?<)
cvsdist c08065
      (delete-region (1- (point)) (point-max)))
cvsdist c08065
  (run-hooks 'po-subedit-exit-hook)
cvsdist c08065
  (let ((string (buffer-string)))
cvsdist c08065
    (po-subedit-abort)
cvsdist c08065
    (po-find-span-of-entry)
cvsdist c08065
    (cond ((= (point) po-start-of-msgid)
cvsdist c08065
	   (po-set-comment string)
cvsdist c08065
	   (po-redisplay))
cvsdist c08065
	  ((= (point) po-start-of-msgstr)
cvsdist c08065
	   (let ((replaced (po-set-msgstr string)))
cvsdist c08065
	     (if (and replaced
cvsdist c08065
		      po-auto-fuzzy-on-edit
cvsdist c08065
		      (eq po-entry-type 'translated))
cvsdist c08065
		 (progn
cvsdist c08065
		   (po-decrease-type-counter)
cvsdist c08065
		   (po-add-attribute "fuzzy")
cvsdist c08065
		   (po-current-entry)
cvsdist c08065
		   (po-increase-type-counter)))))
cvsdist c08065
	  (t (debug)))))
cvsdist c08065
cvsdist c08065
(defun po-edit-string (string type expand-tabs)
cvsdist c08065
  "Prepare a pop up buffer for editing STRING, which is of a given TYPE.
cvsdist c08065
TYPE may be 'comment or 'msgstr.  If EXPAND-TABS, expand tabs to spaces.
cvsdist c08065
Run functions on po-subedit-mode-hook."
cvsdist c08065
  (let ((marker (make-marker)))
cvsdist c08065
    (set-marker marker (cond ((eq type 'comment) po-start-of-msgid)
cvsdist c08065
			     ((eq type 'msgstr) po-start-of-msgstr)))
cvsdist c08065
    (if (po-check-for-pending-edit marker)
cvsdist c08065
	(let ((edit-buffer (generate-new-buffer
cvsdist c08065
			    (concat "*" (buffer-name) "*")))
cvsdist 65c016
	      (edit-coding buffer-file-coding-system)
cvsdist c08065
	      (buffer (current-buffer))
cvsdist c08065
	      overlay slot)
cvsdist c08065
	  (if (and (eq type 'msgstr) po-highlighting)
cvsdist c08065
	      ;; ;; Try showing all of msgid in the upper window while editing.
cvsdist c08065
	      ;; (goto-char (1- po-start-of-msgstr))
cvsdist c08065
	      ;; (recenter -1)
cvsdist c08065
	      (save-excursion
cvsdist c08065
		(goto-char po-start-of-entry)
cvsdist c08065
		(re-search-forward po-any-msgid-regexp nil t)
cvsdist c08065
		(let ((end (1- (match-end 0))))
cvsdist c08065
		  (goto-char (match-beginning 0))
cvsdist c08065
		  (re-search-forward "msgid +" nil t)
cvsdist c08065
		  (setq overlay (po-create-overlay))
cvsdist c08065
		  (po-highlight overlay (point) end buffer))))
cvsdist c08065
	  (setq slot (list marker edit-buffer overlay)
cvsdist c08065
		po-edited-fields (cons slot po-edited-fields))
cvsdist c08065
	  (pop-to-buffer edit-buffer)
cvsdist 65c016
	  (set (make-local-variable 'po-subedit-back-pointer) slot)
cvsdist 65c016
	  (set (make-local-variable 'indent-line-function)
cvsdist 65c016
	       'indent-relative)
cvsdist 65c016
	  (setq buffer-file-coding-system edit-coding)
cvsdist 65c016
	  (setq local-abbrev-table po-mode-abbrev-table)
cvsdist c08065
	  (erase-buffer)
cvsdist c08065
	  (insert string "<")
cvsdist c08065
	  (goto-char (point-min))
cvsdist c08065
	  (and expand-tabs (setq indent-tabs-mode nil))
cvsdist c08065
	  (use-local-map po-subedit-mode-map)
cvsdist 65c016
	  (if (fboundp 'easy-menu-define)
cvsdist 65c016
	      (progn
cvsdist 65c016
		(easy-menu-define po-subedit-mode-menu po-subedit-mode-map ""
cvsdist 65c016
		  po-subedit-mode-menu-layout)
cvsdist 65c016
		(and po-XEMACS (easy-menu-add po-subedit-mode-menu))))
cvsdist 65c016
	  (set-syntax-table po-subedit-mode-syntax-table)
cvsdist c08065
	  (run-hooks 'po-subedit-mode-hook)
cvsdist c08065
	  (message po-subedit-message)))))
cvsdist c08065
cvsdist c08065
(defun po-edit-comment ()
cvsdist c08065
  "Use another window to edit the current translator comment."
cvsdist c08065
  (interactive)
cvsdist c08065
  (po-find-span-of-entry)
cvsdist c08065
  (po-edit-string (po-get-comment nil) 'comment nil))
cvsdist 65c016
  
cvsdist 65c016
(defun po-edit-comment-and-ediff ()
cvsdist 65c016
  "Use `ediff' to edit the current translator comment.
cvsdist 65c016
This function calls `po-edit-msgstr' and `po-subedit-ediff'; for more info
cvsdist 65c016
read `po-subedit-ediff' documentation."
cvsdist 65c016
  (interactive)
cvsdist 65c016
  (po-edit-comment)
cvsdist 65c016
  (po-subedit-ediff))
cvsdist c08065
cvsdist c08065
(defun po-edit-msgstr ()
cvsdist c08065
  "Use another window to edit the current msgstr."
cvsdist c08065
  (interactive)
cvsdist c08065
  (po-find-span-of-entry)
cvsdist c08065
  (po-edit-string (if (and po-auto-edit-with-msgid
cvsdist c08065
			   (eq po-entry-type 'untranslated))
cvsdist c08065
		      (po-get-msgid nil)
cvsdist c08065
		    (po-get-msgstr nil))
cvsdist c08065
		  'msgstr
cvsdist c08065
		  t))
cvsdist 65c016
cvsdist 65c016
(defun po-edit-msgstr-and-ediff ()
cvsdist 65c016
  "Use `ediff' to edit the current msgstr.
cvsdist 65c016
This function calls `po-edit-msgstr' and `po-subedit-ediff'; for more info
cvsdist 65c016
read `po-subedit-ediff' documentation."
cvsdist 65c016
  (interactive)
cvsdist 65c016
  (po-edit-msgstr)
cvsdist 65c016
  (po-subedit-ediff))
cvsdist c08065

cvsdist c08065
;;; String normalization and searching.
cvsdist c08065
cvsdist c08065
(defun po-normalize-old-style (explain)
cvsdist c08065
  "Normalize old gettext style fields using K&R C multiline string syntax.
cvsdist c08065
To minibuffer messages sent while normalizing, add the EXPLAIN string."
cvsdist c08065
  (let ((here (point-marker))
cvsdist c08065
	(counter 0)
cvsdist c08065
	(buffer-read-only po-read-only))
cvsdist c08065
    (goto-char (point-min))
cvsdist c08065
    (message (_"Normalizing %d, %s") counter explain)
cvsdist c08065
    (while (re-search-forward
cvsdist c08065
	    "\\(^#?[ \t]*msg\\(id\\|str\\)[ \t]*\"\\|[^\" \t][ \t]*\\)\\\\\n"
cvsdist c08065
	    nil t)
cvsdist c08065
      (if (= (% counter 10) 0)
cvsdist c08065
	  (message (_"Normalizing %d, %s") counter explain))
cvsdist c08065
      (replace-match "\\1\"\n\"" t nil)
cvsdist c08065
      (setq counter (1+ counter)))
cvsdist c08065
    (goto-char here)
cvsdist c08065
    (message (_"Normalizing %d...done") counter)))
cvsdist c08065
cvsdist c08065
(defun po-normalize-field (field explain)
cvsdist c08065
  "Normalize FIELD of all entries.  FIELD is either the symbol msgid or msgstr.
cvsdist c08065
To minibuffer messages sent while normalizing, add the EXPLAIN string."
cvsdist c08065
  (let ((here (point-marker))
cvsdist c08065
	(counter 0))
cvsdist c08065
    (goto-char (point-min))
cvsdist c08065
    (while (re-search-forward po-any-msgstr-regexp nil t)
cvsdist c08065
      (if (= (% counter 10) 0)
cvsdist c08065
	  (message (_"Normalizing %d, %s") counter explain))
cvsdist c08065
      (goto-char (match-beginning 0))
cvsdist c08065
      (po-find-span-of-entry)
cvsdist c08065
      (cond ((eq field 'msgid) (po-set-msgid (po-get-msgid nil)))
cvsdist c08065
	    ((eq field 'msgstr) (po-set-msgstr (po-get-msgstr nil))))
cvsdist c08065
      (goto-char po-end-of-entry)
cvsdist c08065
      (setq counter (1+ counter)))
cvsdist c08065
    (goto-char here)
cvsdist c08065
    (message (_"Normalizing %d...done") counter)))
cvsdist c08065
cvsdist c08065
;; Normalize, but the British way! :-)
cvsdist c08065
(defsubst po-normalise () (po-normalize))
cvsdist c08065
cvsdist c08065
(defun po-normalize ()
cvsdist c08065
  "Normalize all entries in the PO file."
cvsdist c08065
  (interactive)
cvsdist c08065
  (po-normalize-old-style (_"pass 1/3"))
cvsdist c08065
  (po-normalize-field t (_"pass 2/3"))
cvsdist c08065
  (po-normalize-field nil (_"pass 3/3"))
cvsdist c08065
  ;; The last PO file entry has just been processed.
cvsdist c08065
  (if (not (= po-end-of-entry (point-max)))
cvsdist c08065
      (let ((buffer-read-only po-read-only))
cvsdist c08065
	(kill-region po-end-of-entry (point-max))))
cvsdist c08065
  ;; A bizarre format might have fooled the counters, so recompute
cvsdist c08065
  ;; them to make sure their value is dependable.
cvsdist c08065
  (po-compute-counters nil))
cvsdist c08065

cvsdist c08065
;;; Multiple PO files.
cvsdist c08065
cvsdist c08065
(defun po-show-auxiliary-list ()
cvsdist c08065
  "Echo the current auxiliary list in the message area."
cvsdist c08065
  (if po-auxiliary-list
cvsdist c08065
      (let ((cursor po-auxiliary-cursor)
cvsdist c08065
	    string)
cvsdist c08065
	(while cursor
cvsdist c08065
	  (setq string (concat string (if string " ") (car (car cursor)))
cvsdist c08065
		cursor (cdr cursor)))
cvsdist c08065
	(setq cursor po-auxiliary-list)
cvsdist c08065
	(while (not (eq cursor po-auxiliary-cursor))
cvsdist c08065
	  (setq string (concat string (if string " ") (car (car cursor)))
cvsdist c08065
		cursor (cdr cursor)))
cvsdist c08065
	(message string))
cvsdist c08065
    (message (_"No auxiliary files."))))
cvsdist c08065
cvsdist c08065
(defun po-consider-as-auxiliary ()
cvsdist c08065
  "Add the current PO file to the list of auxiliary files."
cvsdist c08065
  (interactive)
cvsdist c08065
  (if (member (list buffer-file-name) po-auxiliary-list)
cvsdist c08065
      nil
cvsdist c08065
    (setq po-auxiliary-list
cvsdist c08065
	  (nconc po-auxiliary-list (list (list buffer-file-name))))
cvsdist c08065
    (or po-auxiliary-cursor
cvsdist c08065
	(setq po-auxiliary-cursor po-auxiliary-list)))
cvsdist c08065
  (po-show-auxiliary-list))
cvsdist c08065
cvsdist c08065
(defun po-ignore-as-auxiliary ()
cvsdist c08065
  "Delete the current PO file from the list of auxiliary files."
cvsdist c08065
  (interactive)
cvsdist c08065
  (setq po-auxiliary-list (delete (list buffer-file-name) po-auxiliary-list)
cvsdist c08065
	po-auxiliary-cursor po-auxiliary-list)
cvsdist c08065
  (po-show-auxiliary-list))
cvsdist c08065
cvsdist c08065
(defun po-seek-equivalent-translation (name string)
cvsdist 65c016
  "Search a PO file NAME for a 'msgid' STRING having a non-empty 'msgstr'.
cvsdist 65c016
STRING is the full quoted msgid field, including the 'msgid' keyword.  When
cvsdist 65c016
found, display the file over the current window, with the 'msgstr' field
cvsdist 65c016
possibly highlighted, the cursor at start of msgid, then return 't'.
cvsdist 65c016
Otherwise, move nothing, and just return 'nil'."
cvsdist c08065
  (let ((current (current-buffer))
cvsdist c08065
	(buffer (find-file-noselect name)))
cvsdist c08065
    (set-buffer buffer)
cvsdist c08065
    (let ((start (point))
cvsdist c08065
	  found)
cvsdist c08065
      (goto-char (point-min))
cvsdist c08065
      (while (and (not found) (search-forward string nil t))
cvsdist 65c016
	;; Screen out longer 'msgid's.
cvsdist c08065
	(if (looking-at "^msgstr ")
cvsdist c08065
	    (progn
cvsdist c08065
	      (po-find-span-of-entry)
cvsdist c08065
	      ;; Ignore an untranslated entry.
cvsdist c08065
	      (or (string-equal
cvsdist c08065
		   (buffer-substring po-start-of-msgstr po-end-of-entry)
cvsdist c08065
		   "msgstr \"\"\n")
cvsdist c08065
		  (setq found t)))))
cvsdist c08065
      (if found
cvsdist c08065
	  (progn
cvsdist c08065
	    (switch-to-buffer buffer)
cvsdist c08065
	    (po-find-span-of-entry)
cvsdist c08065
	    (if po-highlighting
cvsdist c08065
		(progn
cvsdist c08065
		  (goto-char po-start-of-entry)
cvsdist c08065
		  (re-search-forward po-any-msgstr-regexp nil t)
cvsdist c08065
		  (let ((end (1- (match-end 0))))
cvsdist c08065
		    (goto-char (match-beginning 0))
cvsdist c08065
		    (re-search-forward "msgstr +" nil t)
cvsdist 65c016
		    ;; Just "borrow" the marking overlay.
cvsdist 65c016
		    (po-highlight po-marking-overlay (point) end))))
cvsdist c08065
	    (goto-char po-start-of-msgid))
cvsdist c08065
	(goto-char start)
cvsdist c08065
	(po-find-span-of-entry)
cvsdist 65c016
	(set-buffer current))
cvsdist c08065
      found)))
cvsdist c08065
cvsdist c08065
(defun po-cycle-auxiliary ()
cvsdist 65c016
  "Select the next auxiliary file having an entry with same 'msgid'."
cvsdist c08065
  (interactive)
cvsdist c08065
  (po-find-span-of-entry)
cvsdist c08065
  (if po-auxiliary-list
cvsdist c08065
      (let ((string (buffer-substring po-start-of-msgid po-start-of-msgstr))
cvsdist c08065
	    (cursor po-auxiliary-cursor)
cvsdist c08065
	    found name)
cvsdist c08065
	(while (and (not found) cursor)
cvsdist c08065
	  (setq name (car (car cursor)))
cvsdist c08065
	  (if (and (not (string-equal buffer-file-name name))
cvsdist c08065
		   (po-seek-equivalent-translation name string))
cvsdist c08065
	      (setq found t
cvsdist c08065
		    po-auxiliary-cursor cursor))
cvsdist c08065
	  (setq cursor (cdr cursor)))
cvsdist c08065
	(setq cursor po-auxiliary-list)
cvsdist c08065
	(while (and (not found) cursor)
cvsdist c08065
	  (setq name (car (car cursor)))
cvsdist c08065
	  (if (and (not (string-equal buffer-file-name name))
cvsdist c08065
		   (po-seek-equivalent-translation name string))
cvsdist c08065
	      (setq found t
cvsdist c08065
		    po-auxiliary-cursor cursor))
cvsdist c08065
	  (setq cursor (cdr cursor)))
cvsdist c08065
	(or found (message (_"No other translation found")))
cvsdist c08065
        found)))
cvsdist c08065
cvsdist c08065
(defun po-subedit-cycle-auxiliary ()
cvsdist c08065
  "Cycle auxiliary file, but from the translation edit buffer."
cvsdist c08065
  (interactive)
cvsdist 65c016
  (let* ((entry-marker (nth 0 po-subedit-back-pointer))
cvsdist 65c016
	 (entry-buffer (marker-buffer entry-marker))
cvsdist 65c016
	 (buffer (current-buffer)))
cvsdist 65c016
    (pop-to-buffer entry-buffer)
cvsdist 65c016
    (po-cycle-auxiliary)
cvsdist 65c016
    (pop-to-buffer buffer)))
cvsdist c08065
cvsdist c08065
(defun po-select-auxiliary ()
cvsdist 65c016
  "Select one of the available auxiliary files and locate an equivalent entry.
cvsdist 65c016
If an entry having the same 'msgid' cannot be found, merely select the file
cvsdist 65c016
without moving its cursor."
cvsdist c08065
  (interactive)
cvsdist c08065
  (po-find-span-of-entry)
cvsdist c08065
  (if po-auxiliary-list
cvsdist c08065
      (let ((string (buffer-substring po-start-of-msgid po-start-of-msgstr))
cvsdist c08065
	    (name (car (assoc (completing-read (_"Which auxiliary file? ")
cvsdist c08065
					       po-auxiliary-list nil t)
cvsdist c08065
			      po-auxiliary-list))))
cvsdist c08065
	(po-consider-as-auxiliary)
cvsdist c08065
	(or (po-seek-equivalent-translation name string)
cvsdist c08065
	    (find-file name)))))
cvsdist c08065

cvsdist c08065
;;; Original program sources as context.
cvsdist c08065
cvsdist c08065
(defun po-show-source-path ()
cvsdist c08065
  "Echo the current source search path in the message area."
cvsdist c08065
  (if po-search-path
cvsdist c08065
      (let ((cursor po-search-path)
cvsdist c08065
	    string)
cvsdist c08065
	(while cursor
cvsdist c08065
	  (setq string (concat string (if string " ") (car (car cursor)))
cvsdist c08065
		cursor (cdr cursor)))
cvsdist c08065
	(message string))
cvsdist c08065
    (message (_"Empty source path."))))
cvsdist c08065
cvsdist c08065
(defun po-consider-source-path (directory)
cvsdist c08065
  "Add a given DIRECTORY, requested interactively, to the source search path."
cvsdist c08065
  (interactive "DDirectory for search path: ")
cvsdist c08065
  (setq po-search-path (cons (list (if (string-match "/$" directory)
cvsdist c08065
					 directory
cvsdist c08065
				       (concat directory "/")))
cvsdist c08065
			     po-search-path))
cvsdist c08065
  (setq po-reference-check 0)
cvsdist c08065
  (po-show-source-path))
cvsdist c08065
cvsdist c08065
(defun po-ignore-source-path ()
cvsdist c08065
  "Delete a directory, selected with completion, from the source search path."
cvsdist c08065
  (interactive)
cvsdist c08065
  (setq po-search-path
cvsdist c08065
	(delete (list (completing-read (_"Directory to remove? ")
cvsdist c08065
				       po-search-path nil t))
cvsdist c08065
		po-search-path))
cvsdist c08065
  (setq po-reference-check 0)
cvsdist c08065
  (po-show-source-path))
cvsdist c08065
cvsdist c08065
(defun po-ensure-source-references ()
cvsdist c08065
  "Extract all references into a list, with paths resolved, if necessary."
cvsdist c08065
  (po-find-span-of-entry)
cvsdist c08065
  (if (= po-start-of-entry po-reference-check)
cvsdist 65c016
      nil
cvsdist c08065
    (setq po-reference-alist nil)
cvsdist c08065
    (save-excursion
cvsdist c08065
      (goto-char po-start-of-entry)
cvsdist c08065
      (if (re-search-forward "^#:" po-start-of-msgid t)
cvsdist 65c016
	  (let (current name line path file)
cvsdist 65c016
	    (while (looking-at "\\(\n#:\\)? *\\([^: ]*\\):\\([0-9]+\\)")
cvsdist 65c016
	      (goto-char (match-end 0))
cvsdist 65c016
	      (setq name (po-match-string 2)
cvsdist 65c016
		    line (po-match-string 3)
cvsdist 65c016
		    path po-search-path)
cvsdist 65c016
	      (if (string-equal name "")
cvsdist 65c016
		  nil
cvsdist 65c016
		(while (and (not (file-exists-p
cvsdist 65c016
				  (setq file (concat (car (car path)) name))))
cvsdist 65c016
			    path)
cvsdist 65c016
		  (setq path (cdr path)))
cvsdist 65c016
		(setq current (and path file)))
cvsdist 65c016
	      (if current
cvsdist c08065
		  (setq po-reference-alist
cvsdist 65c016
			(cons (list (concat current ":" line)
cvsdist 65c016
				    current
cvsdist 65c016
				    (string-to-number line))
cvsdist c08065
			      po-reference-alist)))))))
cvsdist c08065
    (setq po-reference-alist (nreverse po-reference-alist)
cvsdist c08065
	  po-reference-cursor po-reference-alist
cvsdist c08065
	  po-reference-check po-start-of-entry)))
cvsdist c08065
cvsdist c08065
(defun po-show-source-context (triplet)
cvsdist c08065
  "Show the source context given a TRIPLET which is (PROMPT FILE LINE)."
cvsdist c08065
  (find-file-other-window (car (cdr triplet)))
cvsdist c08065
  (goto-line (car (cdr (cdr triplet))))
cvsdist c08065
  (other-window 1)
cvsdist c08065
  (let ((maximum 0)
cvsdist c08065
	position
cvsdist c08065
	(cursor po-reference-alist))
cvsdist c08065
    (while (not (eq triplet (car cursor)))
cvsdist c08065
      (setq maximum (1+ maximum)
cvsdist c08065
	    cursor (cdr cursor)))
cvsdist c08065
    (setq position (1+ maximum)
cvsdist c08065
	  po-reference-cursor cursor)
cvsdist c08065
    (while cursor
cvsdist c08065
      (setq maximum (1+ maximum)
cvsdist c08065
	    cursor (cdr cursor)))
cvsdist c08065
    (message (_"Displaying %d/%d: \"%s\"") position maximum (car triplet))))
cvsdist c08065
cvsdist c08065
(defun po-cycle-source-reference ()
cvsdist c08065
  "Display some source context for the current entry.
cvsdist c08065
If the command is repeated many times in a row, cycle through contexts."
cvsdist c08065
  (interactive)
cvsdist c08065
  (po-ensure-source-references)
cvsdist c08065
  (if po-reference-cursor
cvsdist c08065
      (po-show-source-context
cvsdist c08065
       (car (if (eq last-command 'po-cycle-source-reference)
cvsdist c08065
		(or (cdr po-reference-cursor) po-reference-alist)
cvsdist c08065
	      po-reference-cursor)))
cvsdist c08065
    (error (_"No resolved source references"))))
cvsdist c08065
cvsdist c08065
(defun po-select-source-reference ()
cvsdist c08065
  "Select one of the available source contexts for the current entry."
cvsdist c08065
  (interactive)
cvsdist c08065
  (po-ensure-source-references)
cvsdist c08065
  (if po-reference-alist
cvsdist c08065
      (po-show-source-context
cvsdist c08065
       (assoc
cvsdist c08065
	(completing-read (_"Which source context? ") po-reference-alist nil t)
cvsdist c08065
	po-reference-alist))
cvsdist c08065
    (error (_"No resolved source references"))))
cvsdist c08065

cvsdist 65c016
;;; String marking in program sources, through TAGS table.
cvsdist 65c016
cvsdist 65c016
;; Globally defined within tags.el.
cvsdist 65c016
(defvar tags-loop-operate)
cvsdist 65c016
(defvar tags-loop-scan)
cvsdist 65c016
cvsdist 65c016
;; Locally set in each program source buffer.
cvsdist 65c016
(defvar po-find-string-function)
cvsdist 65c016
(defvar po-mark-string-function)
cvsdist 65c016
cvsdist 65c016
;; Dynamically set within po-tags-search for po-tags-loop-operate.
cvsdist 65c016
(defvar po-current-po-buffer)
cvsdist 65c016
(defvar po-current-po-keywords)
cvsdist 65c016
cvsdist 65c016
(defun po-tags-search (restart)
cvsdist 65c016
  "Find an unmarked translatable string through all files in tags table.
cvsdist 65c016
Disregard some simple strings which are most probably non-translatable.
cvsdist 65c016
With prefix argument, restart search at first file."
cvsdist 65c016
  (interactive "P")
cvsdist 65c016
  (require 'etags)
cvsdist 65c016
  ;; Ensure there is no highlighting, in case the search fails.
cvsdist 65c016
  (if po-highlighting
cvsdist 65c016
      (po-dehighlight po-marking-overlay))
cvsdist 65c016
  (setq po-string-contents nil)
cvsdist 65c016
  ;; Search for a string which might later be marked for translation.
cvsdist 65c016
  (let ((po-current-po-buffer (current-buffer))
cvsdist 65c016
	(po-current-po-keywords po-keywords))
cvsdist 65c016
    (pop-to-buffer po-string-buffer)
cvsdist 65c016
    (if (and (not restart)
cvsdist 65c016
	     (eq (car tags-loop-operate) 'po-tags-loop-operate))
cvsdist 65c016
	;; Continue last po-tags-search.
cvsdist 65c016
	(tags-loop-continue nil)
cvsdist 65c016
      ;; Start or restart po-tags-search all over.
cvsdist 65c016
      (setq tags-loop-scan '(po-tags-loop-scan)
cvsdist 65c016
	    tags-loop-operate '(po-tags-loop-operate))
cvsdist 65c016
      (tags-loop-continue t))
cvsdist 65c016
    (select-window (get-buffer-window po-current-po-buffer)))
cvsdist 65c016
  (if po-string-contents
cvsdist 65c016
      (let ((window (selected-window))
cvsdist 65c016
	    (buffer po-string-buffer)
cvsdist 65c016
	    (start po-string-start)
cvsdist 65c016
	    (end po-string-end))
cvsdist 65c016
	;; Try to fit the string in the displayed part of its window.
cvsdist 65c016
	(select-window (get-buffer-window buffer))
cvsdist 65c016
	(goto-char start)
cvsdist 65c016
	(or (pos-visible-in-window-p start)
cvsdist 65c016
	    (recenter '(nil)))
cvsdist 65c016
	(if (pos-visible-in-window-p end)
cvsdist 65c016
	    (goto-char end)
cvsdist 65c016
	  (goto-char end)
cvsdist 65c016
	  (recenter -1))
cvsdist 65c016
	(select-window window)
cvsdist 65c016
	;; Highlight the string as found.
cvsdist 65c016
	(and po-highlighting
cvsdist 65c016
	     (po-highlight po-marking-overlay start end buffer)))))
cvsdist 65c016
cvsdist 65c016
(defun po-tags-loop-scan ()
cvsdist 65c016
  "Decide if the current buffer is still interesting for PO mode strings."
cvsdist 65c016
  ;; We have little choice, here.  The major mode is needed to dispatch to the
cvsdist 65c016
  ;; proper scanner, so we declare all files as interesting, to force Emacs
cvsdist 65c016
  ;; tags module to revisit files fully.  po-tags-loop-operate sets point at
cvsdist 65c016
  ;; end of buffer when it is done with a file.
cvsdist 65c016
  (not (eobp)))
cvsdist 65c016
cvsdist 65c016
(defun po-tags-loop-operate ()
cvsdist 65c016
  "Find an acceptable tag in the current buffer, according to mode.
cvsdist 65c016
Disregard some simple strings which are most probably non-translatable."
cvsdist 65c016
  (po-preset-string-functions)
cvsdist 65c016
  (let ((continue t)
cvsdist 65c016
	data)
cvsdist 65c016
    (while continue
cvsdist 65c016
      (setq data (apply po-find-string-function po-current-po-keywords nil))
cvsdist 65c016
      (if data
cvsdist 65c016
	  ;; Push the string just found into a work buffer for study.
cvsdist 65c016
	  (po-with-temp-buffer
cvsdist 65c016
	   (insert (nth 0 data))
cvsdist 65c016
	   (goto-char (point-min))
cvsdist 65c016
	   ;; Accept if at least three letters in a row.
cvsdist 65c016
	   (if (re-search-forward "[A-Za-z][A-Za-z][A-Za-z]" nil t)
cvsdist 65c016
	       (setq continue nil)
cvsdist 65c016
	     ;; Disregard if single letters or no letters at all.
cvsdist 65c016
	     (if (re-search-forward "[A-Za-z][A-Za-z]" nil t)
cvsdist 65c016
		 ;; Here, we have two letters in a row, but never more.
cvsdist 65c016
		 ;; Accept only if more letters than punctuations.
cvsdist 65c016
		 (let ((total (buffer-size)))
cvsdist 65c016
		   (goto-char (point-min))
cvsdist 65c016
		   (while (re-search-forward "[A-Za-z]+" nil t)
cvsdist 65c016
		     (replace-match "" t t))
cvsdist 65c016
		   (if (< (* 2 (buffer-size)) total)
cvsdist 65c016
		       (setq continue nil))))))
cvsdist 65c016
	;; No string left in this buffer.
cvsdist 65c016
	(setq continue nil)))
cvsdist 65c016
    (if data
cvsdist 65c016
	;; Save information for marking functions.
cvsdist 65c016
	(let ((buffer (current-buffer)))
cvsdist 65c016
	  (save-excursion
cvsdist 65c016
	    (set-buffer po-current-po-buffer)
cvsdist 65c016
	    (setq po-string-contents (nth 0 data)
cvsdist 65c016
		  po-string-buffer buffer
cvsdist 65c016
		  po-string-start (nth 1 data)
cvsdist 65c016
		  po-string-end (nth 2 data))))
cvsdist 65c016
      (goto-char (point-max)))
cvsdist 65c016
    ;; If nothing was found, trigger scanning of next file.
cvsdist 65c016
    (not data)))
cvsdist 65c016
cvsdist 65c016
(defun po-mark-found-string (keyword)
cvsdist 65c016
  "Mark last found string in program sources as translatable, using KEYWORD."
cvsdist 65c016
  (if (not po-string-contents)
cvsdist 65c016
    (error (_"No such string")))
cvsdist 65c016
  (and po-highlighting (po-dehighlight po-marking-overlay))
cvsdist 65c016
  (let ((contents po-string-contents)
cvsdist 65c016
	(buffer po-string-buffer)
cvsdist 65c016
	(start po-string-start)
cvsdist 65c016
	(end po-string-end)
cvsdist 65c016
	line string)
cvsdist 65c016
    ;; Mark string in program sources.
cvsdist 65c016
    (save-excursion
cvsdist 65c016
      (set-buffer buffer)
cvsdist 65c016
      (setq line (count-lines (point-min) start))
cvsdist 65c016
      (apply po-mark-string-function start end keyword nil))
cvsdist 65c016
    ;; Add PO file entry.
cvsdist 65c016
    (let ((buffer-read-only po-read-only))
cvsdist 65c016
      (goto-char (point-max))
cvsdist 65c016
      (insert "\n" (format "#: %s:%d\n"
cvsdist 65c016
			   (buffer-file-name po-string-buffer)
cvsdist 65c016
			   line))
cvsdist 65c016
      (save-excursion
cvsdist 65c016
	(insert (po-eval-requoted contents "msgid" nil) "msgstr \"\"\n"))
cvsdist 65c016
      (setq po-untranslated-counter (1+ po-untranslated-counter))
cvsdist 65c016
      (po-update-mode-line-string))
cvsdist 65c016
    (setq po-string-contents nil)))
cvsdist 65c016
cvsdist 65c016
(defun po-mark-translatable ()
cvsdist 65c016
  "Mark last found string in program sources as translatable, using '_'."
cvsdist 65c016
  (interactive)
cvsdist 65c016
  (po-mark-found-string "_"))
cvsdist 65c016
cvsdist 65c016
(defun po-select-mark-and-mark (arg)
cvsdist 65c016
  "Mark last found string in program sources as translatable, ask for keywoard,
cvsdist 65c016
using completion.  With prefix argument, just ask the name of a preferred
cvsdist 65c016
keyword for subsequent commands, also added to possible completions."
cvsdist 65c016
  (interactive "P")
cvsdist 65c016
  (if arg
cvsdist 65c016
      (let ((keyword (list (read-from-minibuffer (_"Keyword: ")))))
cvsdist 65c016
	(setq po-keywords (cons keyword (delete keyword po-keywords))))
cvsdist 65c016
    (or po-string-contents (error (_"No such string")))
cvsdist 65c016
    (let* ((default (car (car po-keywords)))
cvsdist 65c016
	   (keyword (completing-read (format (_"Mark with keywoard? [%s] ")
cvsdist 65c016
					     default)
cvsdist 65c016
				     po-keywords nil t )))
cvsdist 65c016
      (if (string-equal keyword "") (setq keyword default))
cvsdist 65c016
      (po-mark-found-string keyword))))
cvsdist 65c016

cvsdist 65c016
;;; Unknown mode specifics.
cvsdist 65c016
cvsdist 65c016
(defun po-preset-string-functions ()
cvsdist 65c016
  "Preset FIND-STRING-FUNCTION and MARK-STRING-FUNCTION according to mode.
cvsdist 65c016
These variables are locally set in source buffer only when not already bound."
cvsdist 65c016
  (let ((pair (cond ((string-equal mode-name "AWK")
cvsdist 65c016
		     '(po-find-awk-string . po-mark-awk-string))
cvsdist 65c016
		    ((member mode-name '("C" "C++"))
cvsdist 65c016
		     '(po-find-c-string . po-mark-c-string))
cvsdist 65c016
		    ((string-equal mode-name "Emacs-Lisp")
cvsdist 65c016
		     '(po-find-emacs-lisp-string . po-mark-emacs-lisp-string))
cvsdist 65c016
		    ((string-equal mode-name "Python")
cvsdist 65c016
		     '(po-find-python-string . po-mark-python-string))
cvsdist 65c016
		    ((and (string-equal mode-name "Shell-script")
cvsdist 65c016
			  (string-equal mode-line-process "[bash]"))
cvsdist 65c016
		     '(po-find-bash-string . po-mark-bash-string))
cvsdist 65c016
		    (t '(po-find-unknown-string . po-mark-unknown-string)))))
cvsdist 65c016
    (or (boundp 'po-find-string-function)
cvsdist 65c016
	(set (make-local-variable 'po-find-string-function) (car pair)))
cvsdist 65c016
    (or (boundp 'po-mark-string-function)
cvsdist 65c016
	(set (make-local-variable 'po-mark-string-function) (cdr pair)))))
cvsdist 65c016
cvsdist 65c016
(defun po-find-unknown-string (keywords)
cvsdist 65c016
  "Dummy function to skip over a file, finding no string in it."
cvsdist 65c016
  nil)
cvsdist 65c016
cvsdist 65c016
(defun po-mark-unknown-string (start end keyword)
cvsdist 65c016
  "Dummy function to mark a given string.  May not be called."
cvsdist 65c016
  (error (_"Dummy function called")))
cvsdist 65c016

cvsdist 65c016
;;; Awk mode specifics.
cvsdist 65c016
cvsdist 65c016
(defun po-find-awk-string (keywords)
cvsdist 65c016
  "Find the next Awk string, excluding those marked by any of KEYWORDS.
cvsdist 65c016
Return (CONTENTS START END) for the found string, or nil if none found."
cvsdist 65c016
  (let (start end)
cvsdist 65c016
    (while (and (not start)
cvsdist 65c016
		(re-search-forward "[#/\"]" nil t))
cvsdist 65c016
      (cond ((= (preceding-char) ?#)
cvsdist 65c016
	     ;; Disregard comments.
cvsdist 65c016
	     (or (search-forward "\n" nil t)
cvsdist 65c016
		 (goto-char (point-max))))
cvsdist 65c016
	    ((= (preceding-char) ?/)
cvsdist 65c016
	     ;; Skip regular expressions.
cvsdist 65c016
	     (while (not (= (following-char) ?/))
cvsdist 65c016
	       (skip-chars-forward "^/\\\\")
cvsdist 65c016
	       (if (= (following-char) ?\\) (forward-char 2)))
cvsdist 65c016
	     (forward-char 1))
cvsdist 65c016
	    ;; Else find the end of the string.
cvsdist 65c016
	    (t (setq start (1- (point)))
cvsdist 65c016
	       (while (not (= (following-char) ?\"))
cvsdist 65c016
		 (skip-chars-forward "^\"\\\\")
cvsdist 65c016
		 (if (= (following-char) ?\\) (forward-char 2)))
cvsdist 65c016
	       (forward-char 1)
cvsdist 65c016
	       (setq end (point))
cvsdist 65c016
	       ;; Check before string either for underline, or for keyword
cvsdist 65c016
	       ;; and opening parenthesis.
cvsdist 65c016
	       (save-excursion
cvsdist 65c016
		 (goto-char start)
cvsdist 65c016
		 (cond ((= (preceding-char) ?_)
cvsdist 65c016
			;; Disregard already marked strings.
cvsdist 65c016
			(setq start nil
cvsdist 65c016
			      end nil))
cvsdist 65c016
		       ((= (preceding-char) ?\()
cvsdist 65c016
			(backward-char 1)
cvsdist 65c016
			(let ((end-keyword (point)))
cvsdist 65c016
			  (skip-chars-backward "_A-Za-z0-9")
cvsdist 65c016
			  (if (member (list (po-buffer-substring
cvsdist 65c016
					     (point) end-keyword))
cvsdist 65c016
				      keywords)
cvsdist 65c016
			      ;; Disregard already marked strings.
cvsdist 65c016
			      (setq start nil
cvsdist 65c016
				    end nil)))))))))
cvsdist 65c016
    (and start end
cvsdist 65c016
	 (list (po-extract-unquoted (current-buffer) start end) start end))))
cvsdist 65c016
cvsdist 65c016
(defun po-mark-awk-string (start end keyword)
cvsdist 65c016
  "Mark the Awk string, from START to END, with KEYWORD.
cvsdist 65c016
Leave point after marked string."
cvsdist 65c016
  (if (string-equal keyword "_")
cvsdist 65c016
      (progn
cvsdist 65c016
	(goto-char start)
cvsdist 65c016
	(insert "_")
cvsdist 65c016
	(goto-char (1+ end)))
cvsdist 65c016
    (goto-char end)
cvsdist 65c016
    (insert ")")
cvsdist 65c016
    (save-excursion
cvsdist 65c016
      (goto-char start)
cvsdist 65c016
      (insert keyword "("))))
cvsdist 65c016

cvsdist 65c016
;;; Bash mode specifics.
cvsdist c08065
cvsdist 65c016
(defun po-find-bash-string (keywords)
cvsdist 65c016
  "Find the next unmarked Bash string.  KEYWORDS are merely ignored.
cvsdist 65c016
Return (CONTENTS START END) for the found string, or nil if none found."
cvsdist 65c016
  (let (start end)
cvsdist 65c016
    (while (and (not start)
cvsdist 65c016
		(re-search-forward "[#'\"]" nil t))
cvsdist 65c016
      (cond ((= (preceding-char) ?#)
cvsdist 65c016
	     ;; Disregard comments.
cvsdist 65c016
	     (or (search-forward "\n" nil t)
cvsdist 65c016
		 (goto-char (point-max))))
cvsdist 65c016
	    ((= (preceding-char) ?')
cvsdist 65c016
	     ;; Skip single quoted strings.
cvsdist 65c016
	     (while (not (= (following-char) ?'))
cvsdist 65c016
	       (skip-chars-forward "^'\\\\")
cvsdist 65c016
	       (if (= (following-char) ?\\) (forward-char 2)))
cvsdist 65c016
	     (forward-char 1))
cvsdist 65c016
	    ;; Else find the end of the double quoted string.
cvsdist 65c016
	    (t (setq start (1- (point)))
cvsdist 65c016
	       (while (not (= (following-char) ?\"))
cvsdist 65c016
		 (skip-chars-forward "^\"\\\\")
cvsdist 65c016
		 (if (= (following-char) ?\\) (forward-char 2)))
cvsdist 65c016
	       (forward-char 1)
cvsdist 65c016
	       (setq end (point))
cvsdist 65c016
	       ;; Check before string for dollar sign.
cvsdist 65c016
	       (save-excursion
cvsdist 65c016
		 (goto-char start)
cvsdist 65c016
		 (if (= (preceding-char) ?$)
cvsdist 65c016
		     ;; Disregard already marked strings.
cvsdist 65c016
		     (setq start nil
cvsdist 65c016
			   end nil))))))
cvsdist 65c016
    (and start end
cvsdist 65c016
	 (list (po-extract-unquoted (current-buffer) start end) start end))))
cvsdist 65c016
cvsdist 65c016
(defun po-mark-bash-string (start end keyword)
cvsdist 65c016
  "Mark the Bash string, from START to END, with '$'.  KEYWORD is ignored.
cvsdist 65c016
Leave point after marked string."
cvsdist 65c016
  (goto-char start)
cvsdist 65c016
  (insert "$")
cvsdist 65c016
  (goto-char (1+ end)))
cvsdist 65c016

cvsdist 65c016
;;; C or C++ mode specifics.
cvsdist c08065
cvsdist c08065
;;; A few long string cases (submitted by Ben Pfaff).
cvsdist c08065
cvsdist c08065
;; #define string "This is a long string " \
cvsdist c08065
;; "that is continued across several lines " \
cvsdist c08065
;; "in a macro in order to test \\ quoting\\" \
cvsdist c08065
;; "\\ with goofy strings.\\"
cvsdist c08065
cvsdist c08065
;; char *x = "This is just an ordinary string "
cvsdist c08065
;; "continued across several lines without needing "
cvsdist c08065
;; "to use \\ characters at end-of-line.";
cvsdist c08065
cvsdist c08065
;; char *y = "Here is a string continued across \
cvsdist c08065
;; several lines in the manner that was sanctioned \
cvsdist c08065
;; in K&R C compilers and still works today, \
cvsdist c08065
;; even though the method used above is more esthetic.";
cvsdist c08065
cvsdist c08065
;;; End of long string cases.
cvsdist c08065
cvsdist c08065
(defun po-find-c-string (keywords)
cvsdist c08065
  "Find the next C string, excluding those marked by any of KEYWORDS.
cvsdist 65c016
Returns (CONTENTS START END) for the found string, or nil if none found."
cvsdist c08065
  (let (start end)
cvsdist c08065
    (while (and (not start)
cvsdist c08065
		(re-search-forward "\\([\"']\\|/\\*\\|//\\)" nil t))
cvsdist c08065
      (cond ((= (preceding-char) ?*)
cvsdist c08065
	     ;; Disregard comments.
cvsdist c08065
	     (search-forward "*/"))
cvsdist c08065
	    ((= (preceding-char) ?/)
cvsdist c08065
	     ;; Disregard C++ comments.
cvsdist c08065
	     (end-of-line)
cvsdist c08065
	     (forward-char 1))
cvsdist c08065
	    ((= (preceding-char) ?\')
cvsdist c08065
	     ;; Disregard character constants.
cvsdist c08065
	     (forward-char (if (= (following-char) ?\\) 3 2)))
cvsdist c08065
	    ((save-excursion
cvsdist c08065
	       (beginning-of-line)
cvsdist c08065
	       (looking-at "^# *\\(include\\|line\\)"))
cvsdist c08065
	     ;; Disregard lines being #include or #line directives.
cvsdist c08065
	     (end-of-line))
cvsdist c08065
	    ;; Else, find the end of the (possibly concatenated) string.
cvsdist c08065
	    (t (setq start (1- (point))
cvsdist c08065
		     end nil)
cvsdist c08065
	       (while (not end)
cvsdist c08065
		 (cond ((= (following-char) ?\")
cvsdist c08065
			(if (looking-at "\"[ \t\n\\\\]*\"")
cvsdist c08065
			    (goto-char (match-end 0))
cvsdist c08065
			  (forward-char 1)
cvsdist c08065
			  (setq end (point))))
cvsdist c08065
		       ((= (following-char) ?\\) (forward-char 2))
cvsdist c08065
		       (t (skip-chars-forward "^\"\\\\"))))
cvsdist c08065
	       ;; Check before string for keyword and opening parenthesis.
cvsdist c08065
	       (goto-char start)
cvsdist c08065
	       (skip-chars-backward " \n\t")
cvsdist c08065
	       (if (= (preceding-char) ?\()
cvsdist c08065
		   (progn
cvsdist c08065
		     (backward-char 1)
cvsdist c08065
		     (skip-chars-backward " \n\t")
cvsdist c08065
		     (let ((end-keyword (point)))
cvsdist c08065
		       (skip-chars-backward "_A-Za-z0-9")
cvsdist c08065
		       (if (member (list (po-buffer-substring (point)
cvsdist c08065
							      end-keyword))
cvsdist c08065
				   keywords)
cvsdist c08065
			   ;; Disregard already marked strings.
cvsdist c08065
			   (progn
cvsdist c08065
			     (goto-char end)
cvsdist c08065
			     (setq start nil
cvsdist 65c016
				   end nil))
cvsdist 65c016
			 ;; String found.  Prepare to resume search.
cvsdist 65c016
			 (goto-char end))))
cvsdist 65c016
		 ;; String found.  Prepare to resume search.
cvsdist 65c016
		 (goto-char end)))))
cvsdist c08065
    ;; Return the found string, if any.
cvsdist 65c016
    (and start end
cvsdist 65c016
	 (list (po-extract-unquoted (current-buffer) start end) start end))))
cvsdist c08065
cvsdist c08065
(defun po-mark-c-string (start end keyword)
cvsdist c08065
  "Mark the C string, from START to END, with KEYWORD.
cvsdist 65c016
Leave point after marked string."
cvsdist c08065
  (goto-char end)
cvsdist c08065
  (insert ")")
cvsdist 65c016
  (save-excursion
cvsdist 65c016
    (goto-char start)
cvsdist 65c016
    (insert keyword)
cvsdist 65c016
    (or (string-equal keyword "_") (insert " "))
cvsdist 65c016
    (insert "(")))
cvsdist 65c016

cvsdist 65c016
;;; Emacs LISP mode specifics.
cvsdist c08065
cvsdist c08065
(defun po-find-emacs-lisp-string (keywords)
cvsdist c08065
  "Find the next Emacs LISP string, excluding those marked by any of KEYWORDS.
cvsdist 65c016
Returns (CONTENTS START END) for the found string, or nil if none found."
cvsdist c08065
  (let (start end)
cvsdist c08065
    (while (and (not start)
cvsdist c08065
		(re-search-forward "[;\"?]" nil t))
cvsdist c08065
      (cond ((= (preceding-char) ?\;)
cvsdist c08065
	     ;; Disregard comments.
cvsdist c08065
	     (search-forward "\n"))
cvsdist c08065
	    ((= (preceding-char) ?\?)
cvsdist c08065
	     ;; Disregard character constants.
cvsdist c08065
	     (forward-char (if (= (following-char) ?\\) 2 1)))
cvsdist c08065
	    ;; Else, find the end of the string.
cvsdist c08065
	    (t (setq start (1- (point)))
cvsdist c08065
	       (while (not (= (following-char) ?\"))
cvsdist c08065
		 (skip-chars-forward "^\"\\\\")
cvsdist c08065
		 (if (= (following-char) ?\\) (forward-char 2)))
cvsdist c08065
	       (forward-char 1)
cvsdist c08065
	       (setq end (point))
cvsdist c08065
	       ;; Check before string for keyword and opening parenthesis.
cvsdist c08065
	       (goto-char start)
cvsdist c08065
	       (skip-chars-backward " \n\t")
cvsdist c08065
	       (let ((end-keyword (point)))
cvsdist c08065
		 (skip-chars-backward "-_A-Za-z0-9")
cvsdist c08065
		 (if (and (= (preceding-char) ?\()
cvsdist c08065
			  (member (list (po-buffer-substring (point)
cvsdist c08065
							     end-keyword))
cvsdist c08065
				  keywords))
cvsdist c08065
		     ;; Disregard already marked strings.
cvsdist c08065
		     (progn
cvsdist c08065
		       (goto-char end)
cvsdist c08065
		       (setq start nil
cvsdist c08065
			     end nil)))))))
cvsdist c08065
    ;; Return the found string, if any.
cvsdist 65c016
    (and start end
cvsdist 65c016
	 (list (po-extract-unquoted (current-buffer) start end) start end))))
cvsdist c08065
cvsdist c08065
(defun po-mark-emacs-lisp-string (start end keyword)
cvsdist c08065
  "Mark the Emacs LISP string, from START to END, with KEYWORD.
cvsdist 65c016
Leave point after marked string."
cvsdist c08065
  (goto-char end)
cvsdist c08065
  (insert ")")
cvsdist 65c016
  (save-excursion
cvsdist c08065
    (goto-char start)
cvsdist 65c016
    (insert "(" keyword)
cvsdist 65c016
    (or (string-equal keyword "_") (insert " "))))
cvsdist 65c016

cvsdist 65c016
;;; Python mode specifics.
cvsdist 65c016
cvsdist 65c016
(defun po-find-python-string (keywords)
cvsdist 65c016
  "Find the next Python string, excluding those marked by any of KEYWORDS.
cvsdist 65c016
Also disregard strings when preceded by an empty string of the other type.
cvsdist 65c016
Returns (CONTENTS START END) for the found string, or nil if none found."
cvsdist 65c016
  (let (contents start end)
cvsdist 65c016
    (while (and (not contents)
cvsdist 65c016
		(re-search-forward "[#\"']" nil t))
cvsdist 65c016
      (forward-char -1)
cvsdist 65c016
      (cond ((= (following-char) ?\#)
cvsdist 65c016
	     ;; Disregard comments.
cvsdist 65c016
	     (search-forward "\n"))
cvsdist 65c016
	    ((looking-at "\"\"'")
cvsdist 65c016
	     ;; Quintuple-quoted string
cvsdist 65c016
	     (po-skip-over-python-string))
cvsdist 65c016
	    ((looking-at "''\"")
cvsdist 65c016
	     ;; Quadruple-quoted string
cvsdist 65c016
	     (po-skip-over-python-string))
cvsdist 65c016
	    (t
cvsdist 65c016
	     ;; Simple-, double-, triple- or sextuple-quoted string.
cvsdist 65c016
	     (if (memq (preceding-char) '(?r ?R))
cvsdist 65c016
		 (forward-char -1))
cvsdist 65c016
	     (setq start (point)
cvsdist 65c016
		   contents (po-skip-over-python-string)
cvsdist 65c016
		   end (point))
cvsdist 65c016
	     (goto-char start)
cvsdist 65c016
	     (skip-chars-backward " \n\t")
cvsdist 65c016
	     (cond ((= (preceding-char) ?\[)
cvsdist 65c016
		    ;; Disregard a string used as a dictionary index.
cvsdist 65c016
		    (setq contents nil))
cvsdist 65c016
		   ((= (preceding-char) ?\()
cvsdist 65c016
		    ;; Isolate the keyword which precedes string.
cvsdist 65c016
		    (backward-char 1)
cvsdist 65c016
		    (skip-chars-backward " \n\t")
cvsdist 65c016
		    (let ((end-keyword (point)))
cvsdist 65c016
		      (skip-chars-backward "_A-Za-z0-9")
cvsdist 65c016
		      (if (member (list (po-buffer-substring (point)
cvsdist 65c016
							     end-keyword))
cvsdist 65c016
				  keywords)
cvsdist 65c016
			  ;; Disregard already marked strings.
cvsdist 65c016
			  (setq contents nil)))))
cvsdist 65c016
	     (goto-char end))))
cvsdist 65c016
    ;; Return the found string, if any.
cvsdist 65c016
    (and contents (list contents start end))))
cvsdist 65c016
cvsdist 65c016
(defun po-skip-over-python-string ()
cvsdist 65c016
  "Skip over a Python string, possibly made up of many concatenated parts.
cvsdist 65c016
Leave point after string.  Return unquoted overall string contents."
cvsdist 65c016
  (let ((continue t)
cvsdist 65c016
	(contents "")
cvsdist 65c016
	raw start end resume)
cvsdist 65c016
    (while continue
cvsdist 65c016
      (skip-chars-forward " \t\n")	; whitespace
cvsdist 65c016
      (cond ((= (following-char) ?#)	; comment
cvsdist 65c016
	     (setq start nil)
cvsdist 65c016
	     (search-forward "\n"))
cvsdist 65c016
	    ((looking-at "\\\n")	; escaped newline
cvsdist 65c016
	     (setq start nil)
cvsdist 65c016
	     (forward-char 2))
cvsdist 65c016
	    ((looking-at "[rR]?\"\"\"")	; sextuple-quoted string
cvsdist 65c016
	     (setq raw (memq (following-char) '(?r ?R))
cvsdist 65c016
		   start (match-end 0))
cvsdist 65c016
	     (goto-char start)
cvsdist 65c016
	     (search-forward "\"\"\"")
cvsdist 65c016
	     (setq resume (point)
cvsdist 65c016
		   end (- resume 3)))
cvsdist 65c016
	    ((looking-at "[rr]?'''")	; triple-quoted string
cvsdist 65c016
	     (setq raw (memq (following-char) '(?r ?R))
cvsdist 65c016
		   start (match-end 0))
cvsdist 65c016
	     (goto-char start)
cvsdist 65c016
	     (search-forward "'''")
cvsdist 65c016
	     (setq resume (point)
cvsdist 65c016
		   end (- resume 3)))
cvsdist 65c016
	    ((looking-at "[rR]?\"")	; double-quoted string
cvsdist 65c016
	     (setq raw (memq (following-char) '(?r ?R))
cvsdist 65c016
		   start (match-end 0))
cvsdist 65c016
	     (goto-char start)
cvsdist 65c016
	     (while (not (memq (following-char) '(0 ?\")))
cvsdist 65c016
	       (skip-chars-forward "^\"\\\\")
cvsdist 65c016
	       (if (= (following-char) ?\\) (forward-char 2)))
cvsdist 65c016
	     (if (eobp)
cvsdist 65c016
		 (setq contents nil
cvsdist 65c016
		       start nil)
cvsdist 65c016
	       (setq end (point))
cvsdist 65c016
	       (forward-char 1))
cvsdist 65c016
	     (setq resume (point)))
cvsdist 65c016
	    ((looking-at "[rR]?'")	; single-quoted string
cvsdist 65c016
	     (setq raw (memq (following-char) '(?r ?R))
cvsdist 65c016
		   start (match-end 0))
cvsdist 65c016
	     (goto-char start)
cvsdist 65c016
	     (while (not (memq (following-char) '(0 ?\')))
cvsdist 65c016
	       (skip-chars-forward "^'\\\\")
cvsdist 65c016
	       (if (= (following-char) ?\\) (forward-char 2)))
cvsdist 65c016
	     (if (eobp)
cvsdist 65c016
		 (setq contents nil
cvsdist 65c016
		       start nil)
cvsdist 65c016
	       (setq end (point))
cvsdist 65c016
	       (forward-char 1))
cvsdist 65c016
	     (setq resume (point)))
cvsdist 65c016
	    (t				; no string anymore
cvsdist 65c016
	     (setq start nil
cvsdist 65c016
		   continue nil)))
cvsdist 65c016
      (if start
cvsdist 65c016
	  (setq contents (concat contents
cvsdist 65c016
				 (if raw
cvsdist 65c016
				     (buffer-substring start end)
cvsdist 65c016
				   (po-extract-part-unquoted (current-buffer)
cvsdist 65c016
							     start end))))))
cvsdist 65c016
    (goto-char resume)
cvsdist 65c016
    contents))
cvsdist 65c016
cvsdist 65c016
(defun po-mark-python-string (start end keyword)
cvsdist 65c016
  "Mark the Python string, from START to END, with KEYWORD.
cvsdist 65c016
If KEYWORD is '.', prefix the string with an empty string of the other type.
cvsdist 65c016
Leave point after marked string."
cvsdist 65c016
  (cond ((string-equal keyword ".")
cvsdist 65c016
	 (goto-char end)
cvsdist 65c016
	 (save-excursion
cvsdist 65c016
	   (goto-char start)
cvsdist 65c016
	   (insert (cond ((= (following-char) ?\') "\"\"")
cvsdist 65c016
			 ((= (following-char) ?\") "''")
cvsdist 65c016
			 (t "??")))))
cvsdist 65c016
	(t (goto-char end)
cvsdist 65c016
	   (insert ")")
cvsdist 65c016
	   (save-excursion
cvsdist 65c016
	     (goto-char start)
cvsdist 65c016
	     (insert keyword "(")))))
cvsdist c08065

cvsdist c08065
;;; Miscellaneous features.
cvsdist c08065
cvsdist c08065
(defun po-help ()
cvsdist c08065
  "Provide an help window for PO mode."
cvsdist c08065
  (interactive)
cvsdist c08065
  (po-with-temp-buffer
cvsdist c08065
   (insert po-help-display-string)
cvsdist c08065
   (goto-char (point-min))
cvsdist c08065
   (save-window-excursion
cvsdist c08065
     (switch-to-buffer (current-buffer))
cvsdist c08065
     (delete-other-windows)
cvsdist c08065
     (message (_"Type any character to continue"))
cvsdist c08065
     (po-read-event))))
cvsdist c08065
cvsdist c08065
(defun po-undo ()
cvsdist c08065
  "Undo the last change to the PO file."
cvsdist c08065
  (interactive)
cvsdist c08065
  (let ((buffer-read-only po-read-only))
cvsdist c08065
    (undo))
cvsdist c08065
  (po-compute-counters nil))
cvsdist c08065
cvsdist c08065
(defun po-statistics ()
cvsdist c08065
  "Say how many entries in each category, and the current position."
cvsdist c08065
  (interactive)
cvsdist c08065
  (po-compute-counters t))
cvsdist c08065
cvsdist c08065
(defun po-validate ()
cvsdist 65c016
  "Use 'msgfmt' for validating the current PO file contents."
cvsdist c08065
  (interactive)
cvsdist 65c016
  (let* ((dev-null
cvsdist 65c016
	  (cond ((boundp 'null-device) null-device) ; since Emacs 20.3
cvsdist 65c016
		((memq system-type '(windows-nt windows-95)) "NUL")
cvsdist 65c016
		(t "/dev/null")))
cvsdist 65c016
	 (compilation-buffer-name-function
cvsdist 65c016
	  (function (lambda (mode-name)
cvsdist 65c016
		      (concat "*" mode-name " validation*"))))
cvsdist 65c016
	 (compile-command (concat po-msgfmt-program
cvsdist 65c016
                                 " --statistics -c -v -o " dev-null " "
cvsdist 65c016
                                 buffer-file-name)))
cvsdist 65c016
    (po-msgfmt-version-check)
cvsdist c08065
    (compile compile-command)))
cvsdist c08065
cvsdist 65c016
(defvar po-msgfmt-version-checked nil)
cvsdist 65c016
(defun po-msgfmt-version-check ()
cvsdist 65c016
  "'msgfmt' from GNU gettext 0.10.36 or greater is required."
cvsdist 65c016
  (po-with-temp-buffer
cvsdist 65c016
    (or
cvsdist 65c016
     ;; Don't bother checking again.
cvsdist 65c016
     po-msgfmt-version-checked
cvsdist 65c016
cvsdist 65c016
     (and
cvsdist 65c016
      ;; Make sure 'msgfmt' is available.
cvsdist 65c016
      (condition-case nil
cvsdist 65c016
          (call-process po-msgfmt-program
cvsdist 65c016
			nil t nil "--verbose" "--version")
cvsdist 65c016
	(file-error nil))
cvsdist 65c016
cvsdist 65c016
      ;; Make sure there's a version number in the output:
cvsdist 65c016
      ;; 0.11 or 0.10.36 or 0.11-pre1
cvsdist 65c016
      (progn (goto-char (point-min))
cvsdist 65c016
             (or (looking-at ".* \\([0-9]+\\)\\.\\([0-9]+\\)$")
cvsdist 65c016
                 (looking-at ".* \\([0-9]+\\)\\.\\([0-9]+\\)\\.\\([0-9]+\\)$")
cvsdist 65c016
		 (looking-at ".* \\([0-9]+\\)\\.\\([0-9]+\\)[-_A-Za-z0-9]+$")))
cvsdist 65c016
cvsdist 65c016
      ;; Make sure the version is recent enough.
cvsdist 65c016
      (>= (string-to-number
cvsdist 65c016
	   (format "%d%03d%03d"
cvsdist 65c016
		   (string-to-number (match-string 1))
cvsdist 65c016
		   (string-to-number (match-string 2))
cvsdist 65c016
		   (string-to-number (or (match-string 3) "0"))))
cvsdist 65c016
	  010036)
cvsdist 65c016
cvsdist 65c016
      ;; Remember the outcome.
cvsdist 65c016
      (setq po-msgfmt-version-checked t))
cvsdist 65c016
cvsdist 65c016
     (error (_"'msgfmt' from GNU gettext 0.10.36 or greater is required")))))
cvsdist 65c016
cvsdist c08065
(defun po-guess-archive-name ()
cvsdist c08065
  "Return the ideal file name for this PO file in the central archives."
cvsdist 65c016
  (let ((filename (file-name-nondirectory buffer-file-name))
cvsdist 65c016
	start-of-header end-of-header package version team)
cvsdist c08065
    (save-excursion
cvsdist c08065
      ;; Find the PO file header entry.
cvsdist c08065
      (goto-char (point-min))
cvsdist c08065
      (re-search-forward po-any-msgstr-regexp)
cvsdist c08065
      (setq start-of-header (match-beginning 0)
cvsdist c08065
	    end-of-header (match-end 0))
cvsdist c08065
      ;; Get the package and version.
cvsdist c08065
      (goto-char start-of-header)
cvsdist 65c016
      (if (re-search-forward "\n\
cvsdist 65c016
\"Project-Id-Version: \\(GNU \\|Free \\)?\\([^\n ]+\\) \\([^\n ]+\\)\\\\n\"$"
cvsdist c08065
	   end-of-header t)
cvsdist 65c016
	  (setq package (po-match-string 2)
cvsdist 65c016
		version (po-match-string 3)))
cvsdist c08065
      (if (or (not package) (string-equal package "PACKAGE")
cvsdist c08065
	      (not version) (string-equal version "VERSION"))
cvsdist c08065
	  (error (_"Project-Id-Version field does not have a proper value")))
cvsdist 65c016
      ;; File name version and Project-Id-Version must match
cvsdist 65c016
      (cond (;; A `filename' w/o package and version info at all
cvsdist 65c016
	     (string-match "^[^\\.]*\\.po\\'" filename))
cvsdist 65c016
	    (;; TP Robot compatible `filename': PACKAGE-VERSION.LL.po
cvsdist 65c016
	     (string-match (concat (regexp-quote package)
cvsdist 65c016
				   "-\\(.*\\)\\.[^\\.]*\\.po\\'") filename)
cvsdist 65c016
	     (if (not (equal version (po-match-string 1 filename)))
cvsdist 65c016
		 (error (_"\
cvsdist 65c016
Version mismatch: file name: %s; header: %s.\n\
cvsdist 65c016
Adjust Project-Id-Version field to match file name and try again")
cvsdist 65c016
			(po-match-string 1 filename) version))))
cvsdist c08065
      ;; Get the team.
cvsdist 65c016
      (if (stringp po-team-name-to-code)
cvsdist 65c016
	  (setq team po-team-name-to-code)
cvsdist 65c016
	(goto-char start-of-header)
cvsdist 65c016
	(if (re-search-forward "\n\
cvsdist 65c016
\"Language-Team: \\([^ ].*[^ ]\\) <.+@.+>\\\\n\"$"
cvsdist 65c016
			       end-of-header t)
cvsdist 65c016
	    (let ((name (po-match-string 1)))
cvsdist 65c016
	      (if name
cvsdist 65c016
		  (let ((pair (assoc name po-team-name-to-code)))
cvsdist 65c016
		    (if pair
cvsdist 65c016
			(setq team (cdr pair))
cvsdist 65c016
		      (setq team (read-string (format "\
cvsdist 65c016
Team name '%s' unknown.  What is the team code? "
cvsdist 65c016
						      name)))))))))
cvsdist c08065
      (if (or (not team) (string-equal team "LL"))
cvsdist c08065
	  (error (_"Language-Team field does not have a proper value")))
cvsdist c08065
      ;; Compose the name.
cvsdist c08065
      (concat package "-" version "." team ".po"))))
cvsdist c08065
cvsdist c08065
(defun po-guess-team-address ()
cvsdist c08065
  "Return the team address related to this PO file."
cvsdist c08065
  (let (team)
cvsdist c08065
    (save-excursion
cvsdist c08065
      (goto-char (point-min))
cvsdist c08065
      (re-search-forward po-any-msgstr-regexp)
cvsdist c08065
      (goto-char (match-beginning 0))
cvsdist c08065
      (if (re-search-forward
cvsdist 65c016
	   "\n\"Language-Team: +\\(.*<\\(.*\\)@.*>\\)\\\\n\"$"
cvsdist c08065
	   (match-end 0) t)
cvsdist 65c016
	  (setq team (po-match-string 2)))
cvsdist c08065
      (if (or (not team) (string-equal team "LL"))
cvsdist c08065
	  (error (_"Language-Team field does not have a proper value")))
cvsdist 65c016
      (po-match-string 1))))
cvsdist c08065
cvsdist c08065
(defun po-send-mail ()
cvsdist c08065
  "Start composing a letter, possibly including the current PO file."
cvsdist c08065
  (interactive)
cvsdist c08065
  (let* ((team-flag (y-or-n-p
cvsdist c08065
		     (_"\
cvsdist 65c016
Write to your team?  ('n' if writing to the Translation Project robot) ")))
cvsdist c08065
	 (address (if team-flag
cvsdist c08065
		      (po-guess-team-address)
cvsdist c08065
		    po-translation-project-address)))
cvsdist 65c016
    (if (not (y-or-n-p (_"Include current PO file in mail? ")))
cvsdist c08065
	(apply po-compose-mail-function address
cvsdist c08065
	       (read-string (_"Subject? ")) nil)
cvsdist c08065
      (if (buffer-modified-p)
cvsdist c08065
	  (error (_"The file is not even saved, you did not validate it.")))
cvsdist 65c016
      (if (and (y-or-n-p (_"You validated ('V') this file, didn't you? "))
cvsdist c08065
	       (or (zerop po-untranslated-counter)
cvsdist c08065
		   (y-or-n-p
cvsdist c08065
		    (format (_"%d entries are untranslated, include anyway? ")
cvsdist c08065
			    po-untranslated-counter)))
cvsdist c08065
	       (or (zerop po-fuzzy-counter)
cvsdist c08065
		   (y-or-n-p
cvsdist c08065
		    (format (_"%d entries are still fuzzy, include anyway? ")
cvsdist c08065
			    po-fuzzy-counter)))
cvsdist c08065
	       (or (zerop po-obsolete-counter)
cvsdist c08065
		   (y-or-n-p
cvsdist c08065
		    (format (_"%d entries are obsolete, include anyway? ")
cvsdist c08065
			    po-obsolete-counter))))
cvsdist c08065
	  (let ((buffer (current-buffer))
cvsdist c08065
		(name (po-guess-archive-name))
cvsdist d31aa3
		(transient-mark-mode nil)
cvsdist d31aa3
		(coding-system-for-read buffer-file-coding-system)
cvsdist d31aa3
		(coding-system-for-write buffer-file-coding-system))
cvsdist c08065
	    (apply po-compose-mail-function address
cvsdist c08065
		   (if team-flag
cvsdist c08065
		       (read-string (_"Subject? "))
cvsdist d31aa3
		     (format "%s %s" po-translation-project-mail-label name))
cvsdist c08065
		   nil)
cvsdist c08065
	    (goto-char (point-min))
cvsdist c08065
	    (re-search-forward
cvsdist c08065
	     (concat "^" (regexp-quote mail-header-separator) "\n"))
cvsdist c08065
	    (save-excursion
cvsdist c08065
	      (insert-buffer buffer)
cvsdist c08065
	      (shell-command-on-region
cvsdist c08065
	       (region-beginning) (region-end)
cvsdist c08065
	       (concat po-gzip-uuencode-command " " name ".gz") t))))))
cvsdist c08065
  (message ""))
cvsdist c08065
cvsdist c08065
(defun po-confirm-and-quit ()
cvsdist c08065
  "Confirm if quit should be attempted and then, do it.
cvsdist c08065
This is a failsafe.  Confirmation is asked if only the real quit would not."
cvsdist c08065
  (interactive)
cvsdist c08065
  (if (po-check-all-pending-edits)
cvsdist c08065
      (progn
cvsdist c08065
	(if (or (buffer-modified-p)
cvsdist c08065
		(> po-untranslated-counter 0)
cvsdist c08065
		(> po-fuzzy-counter 0)
cvsdist c08065
		(> po-obsolete-counter 0)
cvsdist c08065
		(y-or-n-p (_"Really quit editing this PO file? ")))
cvsdist c08065
	    (po-quit))
cvsdist c08065
	(message ""))))
cvsdist c08065
cvsdist c08065
(defun po-quit ()
cvsdist 65c016
  "Save the PO file and kill buffer.
cvsdist 65c016
However, offer validation if appropriate and ask confirmation if untranslated
cvsdist 65c016
strings remain."
cvsdist c08065
  (interactive)
cvsdist c08065
  (if (po-check-all-pending-edits)
cvsdist c08065
      (let ((quit t))
cvsdist c08065
	;; Offer validation of newly modified entries.
cvsdist c08065
	(if (and (buffer-modified-p)
cvsdist c08065
		 (not (y-or-n-p
cvsdist c08065
		       (_"File was modified; skip validation step? "))))
cvsdist c08065
	    (progn
cvsdist c08065
	      (message "")
cvsdist c08065
	      (po-validate)
cvsdist c08065
	      ;; If we knew that the validation was all successful, we should
cvsdist c08065
	      ;; just quit.  But since we do not know yet, as the validation
cvsdist c08065
	      ;; might be asynchronous with PO mode commands, the safest is to
cvsdist c08065
	      ;; stay within PO mode, even if this implies that another
cvsdist 65c016
	      ;; 'po-quit' command will be later required to exit for true.
cvsdist c08065
	      (setq quit nil)))
cvsdist c08065
	;; Offer to work on untranslated entries.
cvsdist c08065
	(if (and quit
cvsdist c08065
		 (or (> po-untranslated-counter 0)
cvsdist c08065
		     (> po-fuzzy-counter 0)
cvsdist c08065
		     (> po-obsolete-counter 0))
cvsdist c08065
		 (not (y-or-n-p
cvsdist c08065
		       (_"Unprocessed entries remain; quit anyway? "))))
cvsdist c08065
	    (progn
cvsdist c08065
	      (setq quit nil)
cvsdist c08065
	      (po-auto-select-entry)))
cvsdist c08065
	;; Clear message area.
cvsdist c08065
	(message "")
cvsdist c08065
	;; Or else, kill buffers and quit for true.
cvsdist c08065
	(if quit
cvsdist c08065
	    (progn
cvsdist c08065
	      (save-buffer)
cvsdist c08065
	      (kill-buffer (current-buffer)))))))
cvsdist c08065
cvsdist d31aa3
(provide 'po-mode)
cvsdist d31aa3
cvsdist c08065
;;; po-mode.el ends here