Imported Upstream version 0.19.7
[platform/upstream/gettext.git] / gettext-tools / misc / po-mode.el
1 ;;; po-mode.el -- major mode for GNU gettext PO files
2
3 ;; Copyright (C) 1995-2002, 2005-2008, 2010, 2015 Free Software
4 ;; Foundation, Inc.
5
6 ;; Authors: François Pinard <pinard@iro.umontreal.ca>
7 ;;          Greg McGary <gkm@magilla.cichlid.com>
8 ;; Keywords: i18n gettext
9 ;; Created: 1995
10
11 ;; This file is part of GNU gettext.
12
13 ;; This program is free software: you can redistribute it and/or modify
14 ;; it under the terms of the GNU General Public License as published by
15 ;; the Free Software Foundation; either version 3 of the License, or
16 ;; (at your option) any later version.
17
18 ;; This program is distributed in the hope that it will be useful,
19 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
20 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21 ;; GNU General Public License for more details.
22
23 ;; You should have received a copy of the GNU General Public License
24 ;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
25
26 ;;; Commentary:
27
28 ;; This package provides the tools meant to help editing PO files,
29 ;; as documented in the GNU gettext user's manual.  See this manual
30 ;; for user documentation, which is not repeated here.
31
32 ;; To install, merely put this file somewhere GNU Emacs will find it,
33 ;; then add the following lines to your .emacs file:
34 ;;
35 ;;   (autoload 'po-mode "po-mode"
36 ;;             "Major mode for translators to edit PO files" t)
37 ;;   (setq auto-mode-alist (cons '("\\.po\\'\\|\\.po\\." . po-mode)
38 ;;                               auto-mode-alist))
39 ;;
40 ;; To use the right coding system automatically under Emacs 20 or newer,
41 ;; also add:
42 ;;
43 ;;   (autoload 'po-find-file-coding-system "po-compat")
44 ;;   (modify-coding-system-alist 'file "\\.po\\'\\|\\.po\\."
45 ;;                               'po-find-file-coding-system)
46 ;;
47 ;; You may also adjust some variables, below, by defining them in your
48 ;; '.emacs' file, either directly or through command 'M-x customize'.
49
50 ;; TODO:
51 ;; Plural form editing:
52 ;;  - When in edit mode, currently it highlights (in green) the msgid;
53 ;;    it should also highlight the msgid_plural string, I would say, since
54 ;;    the translator has to look at both.
55 ;;  - After the translator finished the translation of msgstr[0], it would
56 ;;    be nice if the cursor would automatically move to the beginning of the
57 ;;    msgstr[1] line, so that the translator just needs to press RET to edit
58 ;;    that.
59 ;;  - If msgstr[1] is empty but msgstr[0] is not, it would be ergonomic if the
60 ;;    contents of msgstr[0] would be copied. (Not sure if this should happen
61 ;;    at the end of the editing msgstr[0] or at the beginning of the editing
62 ;;    of msgstr[1].) Reason: These two strings are usually very similar.
63
64 ;;; Code:
65 \f
66 (defconst po-mode-version-string "2.24" "\
67 Version number of this version of po-mode.el.")
68
69 ;;; Emacs portability matters - part I.
70 ;;; Here is the minimum for customization to work.  See part II.
71
72 ;; Identify which Emacs variety is being used.
73 ;; This file supports:
74 ;;   - XEmacs (version 19 and above) -> po-XEMACS = t,
75 ;;   - GNU Emacs (version 20 and above) -> po-EMACS20 = t,
76 ;;   - GNU Emacs (version 19) -> no flag.
77 (eval-and-compile
78   (cond ((string-match "XEmacs\\|Lucid" emacs-version)
79          (setq po-EMACS20 nil po-XEMACS t))
80         ((and (string-lessp "19" emacs-version) (featurep 'faces))
81          (setq po-EMACS20 t po-XEMACS nil))
82         (t (setq po-EMACS20 nil po-XEMACS nil))))
83
84 ;; Experiment with Emacs LISP message internationalisation.
85 (eval-and-compile
86   (or (fboundp 'set-translation-domain)
87       (defsubst set-translation-domain (string) nil))
88   (or (fboundp 'translate-string)
89       (defsubst translate-string (string) string)))
90 (defsubst _ (string) (translate-string string))
91 (defsubst N_ (string) string)
92
93 ;; Handle missing 'customs' package.
94 (eval-and-compile
95   (condition-case ()
96       (require 'custom)
97     (error nil))
98   (if (and (featurep 'custom) (fboundp 'custom-declare-variable))
99       nil
100     (defmacro defgroup (&rest args)
101       nil)
102     (defmacro defcustom (var value doc &rest args)
103       `(defvar ,var ,value ,doc))))
104 \f
105 ;;; Customisation.
106
107 (defgroup po nil
108   "Major mode for editing PO files"
109   :group 'i18n)
110
111 (defcustom po-auto-edit-with-msgid nil
112   "*Automatically use msgid when editing untranslated entries."
113   :type 'boolean
114   :group 'po)
115
116 (defcustom po-auto-fuzzy-on-edit nil
117   "*Automatically mark entries fuzzy when being edited."
118   :type 'boolean
119   :group 'po)
120
121 (defcustom po-auto-delete-previous-msgid t
122   "*Automatically delete previous msgid (marked #|) when editing entry.
123 Value is nil, t, or ask."
124   :type '(choice (const nil)
125                  (const t)
126                  (const ask))
127   :group 'po)
128
129 (defcustom po-auto-select-on-unfuzzy nil
130   "*Automatically select some new entry while making an entry not fuzzy."
131   :type 'boolean
132   :group 'po)
133
134 (defcustom po-keep-mo-file nil
135   "*Set whether MO file should be kept or discarded after validation."
136   :type 'boolean
137   :group 'po)
138
139 (defcustom po-auto-update-file-header t
140   "*Automatically revise headers.  Value is nil, t, or ask."
141   :type '(choice (const nil)
142                  (const t)
143                  (const ask))
144   :group 'po)
145
146 (defcustom po-auto-replace-revision-date t
147   "*Automatically revise date in headers.  Value is nil, t, or ask."
148   :type '(choice (const nil)
149                  (const t)
150                  (const ask))
151   :group 'po)
152
153 (defcustom po-default-file-header "\
154 # SOME DESCRIPTIVE TITLE.
155 # Copyright (C) YEAR Free Software Foundation, Inc.
156 # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
157 #
158 #, fuzzy
159 msgid \"\"
160 msgstr \"\"
161 \"Project-Id-Version: PACKAGE VERSION\\n\"
162 \"PO-Revision-Date: YEAR-MO-DA HO:MI +ZONE\\n\"
163 \"Last-Translator: FULL NAME <EMAIL@ADDRESS>\\n\"
164 \"Language-Team: LANGUAGE <LL@li.org>\\n\"
165 \"MIME-Version: 1.0\\n\"
166 \"Content-Type: text/plain; charset=CHARSET\\n\"
167 \"Content-Transfer-Encoding: 8bit\\n\"
168 "
169   "*Default PO file header."
170   :type 'string
171   :group 'po)
172
173 (defcustom po-translation-project-address
174   "robot@translationproject.org"
175   "*Electronic mail address of the Translation Project.
176 Typing \\[po-send-mail] (normally bound to `M') the user will send the PO file
177 to this email address."
178   :type 'string
179   :group 'po)
180
181 (defcustom po-translation-project-mail-label "TP-Robot"
182   "*Subject label when sending the PO file to `po-translation-project-address'."
183   :type 'string
184   :group 'po)
185
186 (defcustom po-highlighting (or po-EMACS20 po-XEMACS)
187   "*Highlight text whenever appropriate, when non-nil.
188 However, on older Emacses, a yet unexplained highlighting bug causes files
189 to get mangled."
190   :type 'boolean
191   :group 'po)
192
193 (defcustom po-highlight-face 'highlight
194   "*The face used for PO mode highlighting.  For Emacses with overlays.
195 Possible values are 'highlight', 'modeline', 'secondary-selection',
196 'region', and 'underline'.
197 This variable can be set by the user to whatever face they desire.
198 It's most convenient if the cursor color and highlight color are
199 slightly different."
200   :type 'face
201   :group 'po)
202
203 (defcustom po-team-name-to-code
204   ;; All possible languages, a complete ISO 639 list, the inverse of
205   ;; gettext-tools/src/lang-table.c, and a little more.
206   '(("LANGUAGE" . "LL")
207     ("(Afan) Oromo" . "om")
208     ("Abkhazian" . "ab")
209     ("Achinese" . "ace")
210     ("Afar" . "aa")
211     ("Afrikaans" . "af")
212     ("Akan" . "ak")
213     ("Albanian" . "sq")
214     ("Amharic" . "am")
215     ("Arabic" . "ar")
216     ("Aragonese" . "an")
217     ("Argentinian" . "es_AR")
218     ("Armenian" . "hy")
219     ("Assamese" . "as")
220     ("Austrian" . "de_AT")
221     ("Avaric" . "av")
222     ("Avestan" . "ae")
223     ("Awadhi" . "awa")
224     ("Aymara" . "ay")
225     ("Azerbaijani" . "az")
226     ("Balinese" . "ban")
227     ("Baluchi" . "bal")
228     ("Bambara" . "bm")
229     ("Bashkir" . "ba")
230     ("Basque" . "eu")
231     ("Beja" . "bej")
232     ("Belarusian" . "be")
233     ("Bemba" . "bem")
234     ("Bengali" . "bn")
235     ("Bhojpuri" . "bho")
236     ("Bihari" . "bh")
237     ("Bikol" . "bik")
238     ("Bini" . "bin")
239     ("Bislama" . "bi")
240     ("Bosnian" . "bs")
241     ("Brazilian Portuguese" . "pt_BR")
242     ("Breton" . "br")
243     ("Buginese" . "bug")
244     ("Bulgarian" . "bg")
245     ("Burmese" . "my")
246     ("Catalan" . "ca")
247     ("Cebuano" . "ceb")
248     ("Central Khmer" . "km")
249     ("Chamorro" . "ch")
250     ("Chechen" . "ce")
251     ("Chinese" . "zh")
252     ("Chinese (Hong Kong)" . "zh_HK")
253     ("Chinese (simplified)" . "zh_CN")
254     ("Chinese (traditional)" . "zh_TW")
255     ("Church Slavic" . "cu")
256     ("Chuvash" . "cv")
257     ("Cornish" . "kw")
258     ("Corsican" . "co")
259     ("Cree" . "cr")
260     ("Croatian" . "hr")
261     ("Czech" . "cs")
262     ("Danish" . "da")
263     ("Dinka" . "din")
264     ("Divehi" . "dv")
265     ("Dogri" . "doi")
266     ("Dutch" . "nl")
267     ("Dzongkha" . "dz")
268     ("English" . "en")
269     ("English (British)" . "en_GB")
270     ("Esperanto" . "eo")
271     ("Estonian" . "et")
272     ("Ewe" . "ee")
273     ("Faroese" . "fo")
274     ("Fijian" . "fj")
275     ("Filipino" . "fil")
276     ("Finnish" . "fi")
277     ("Fon" . "fon")
278     ("French" . "fr")
279     ("Frisian" . "fy")
280     ("Fulah" . "ff")
281     ("Galician" . "gl")
282     ("Ganda" . "lg")
283     ("Georgian" . "ka")
284     ("German" . "de")
285     ("Gondi" . "gon")
286     ("Greek" . "el")
287     ("Guarani" . "gn")
288     ("Gujarati" . "gu")
289     ("Haitian" . "ht")
290     ("Hausa" . "ha")
291     ("Hebrew" . "he")
292     ("Herero" . "hz")
293     ("Hiligaynon" . "hil")
294     ("Hindi" . "hi")
295     ("Hiri Motu" . "ho")
296     ("Hmong" . "hmn")
297     ("Hungarian" . "hu")
298     ("Hyam" . "jab")
299     ("Icelandic" . "is")
300     ("Ido" . "io")
301     ("Igbo" . "ig")
302     ("Iloko" . "ilo")
303     ("Indonesian" . "id")
304     ("Interlingua" . "ia")
305     ("Interlingue" . "ie")
306     ("Inuktitut" . "iu")
307     ("Inupiak" . "ik")
308     ("Irish" . "ga")
309     ("Italian" . "it")
310     ("Japanese" . "ja")
311     ("Javanese" . "jv")
312     ("Jju" . "kaj")
313     ("Kabardian" . "kbd")
314     ("Kabyle" . "kab")
315     ("Kagoma" . "kdm")
316     ("Kalaallisut" . "kl")
317     ("Kamba" . "kam")
318     ("Kannada" . "kn")
319     ("Kanuri" . "kr")
320     ("Kashmiri" . "ks")
321     ("Kashubian" . "csb")
322     ("Kazakh" . "kk")
323     ("Khmer" . "km") ; old name
324     ("Kikuyu" . "ki")
325     ("Kimbundu" . "kmb")
326     ("Kinyarwanda" . "rw")
327     ("Kirghiz" . "ky")
328     ("Kirundi" . "rn")
329     ("Komi" . "kv")
330     ("Kongo" . "kg")
331     ("Konkani" . "kok")
332     ("Korean" . "ko")
333     ("Kuanyama" . "kj")
334     ("Kurdish" . "ku")
335     ("Kurukh" . "kru")
336     ("Laotian" . "lo")
337     ("Latin" . "la")
338     ("Latvian" . "lv")
339     ("Letzeburgesch" . "lb")
340     ("Limburgish" . "li")
341     ("Lingala" . "ln")
342     ("Lithuanian" . "lt")
343     ("Low Saxon" . "nds")
344     ("Luba-Katanga" . "lu")
345     ("Luba-Lulua" . "lua")
346     ("Luo" . "luo")
347     ("Macedonian" . "mk")
348     ("Madurese" . "mad")
349     ("Magahi" . "mag")
350     ("Maithili" . "mai")
351     ("Makasar" . "mak")
352     ("Malagasy" . "mg")
353     ("Malay" . "ms")
354     ("Malayalam" . "ml")
355     ("Maltese" . "mt")
356     ("Mandingo" . "man")
357     ("Manipuri" . "mni")
358     ("Manx" . "gv")
359     ("Maori" . "mi")
360     ("Marathi" . "mr")
361     ("Marshall" . "mh")
362     ("Marshallese" . "mh")
363     ("Marwari" . "mwr")
364     ("Mayan" . "myn")
365     ("Mende" . "men")
366     ("Minangkabau" . "min")
367     ("Moldavian" . "mo")
368     ("Mongolian" . "mn")
369     ("Mossi" . "mos")
370     ("Nahuatl" . "nah")
371     ("Nauru" . "na")
372     ("Navajo" . "nv")
373     ("Ndonga" . "ng")
374     ("Neapolitan" . "nap")
375     ("Nepali" . "ne")
376     ("North Ndebele" . "nd")
377     ("Northern Sami" . "se")
378     ("Northern Sotho" . "nso")
379     ("Norwegian Bokmal" . "nb")
380     ("Norwegian Nynorsk" . "nn")
381     ("Norwegian" . "no")
382     ("Nyamwezi" . "nym")
383     ("Nyanja" . "ny")
384     ("Nyankole" . "nyn")
385     ("Occitan" . "oc")
386     ("Ojibwa" . "oj")
387     ("Old English" . "ang")
388     ("Oriya" . "or")
389     ("Ossetian" . "os")
390     ("Páez" . "pbb")
391     ("Pali" . "pi")
392     ("Pampanga" . "pam")
393     ("Pangasinan" . "pag")
394     ("Pashto" . "ps")
395     ("Persian" . "fa")
396     ("Polish" . "pl")
397     ("Portuguese" . "pt")
398     ("Punjabi" . "pa")
399     ("Quechua" . "qu")
400     ("Rajasthani" . "raj")
401     ("Rhaeto-Roman" . "rm") ; old name
402     ("Romanian" . "ro")
403     ("Romansh" . "rm")
404     ("Russian" . "ru")
405     ("Samoan" . "sm")
406     ("Sango" . "sg")
407     ("Sanskrit" . "sa")
408     ("Santali" . "sat")
409     ("Sardinian" . "sc")
410     ("Sasak" . "sas")
411     ("Scots" . "gd") ; old name
412     ("Scottish Gaelic" . "gd")
413     ("Serbian" . "sr")
414     ("Serer" . "srr")
415     ("Sesotho" . "st")
416     ("Setswana" . "tn")
417     ("Shan" . "shn")
418     ("Shona" . "sn")
419     ("Sichuan Yi" . "ii")
420     ("Sicilian" . "scn")
421     ("Sidamo" . "sid")
422     ("Sindhi" . "sd")
423     ("Sinhala" . "si")
424     ("Sinhalese" . "si")
425     ("Siswati" . "ss")
426     ("Slovak" . "sk")
427     ("Slovenian" . "sl")
428     ("Somali" . "so")
429     ("Sorbian" . "wen")
430     ("South Ndebele" . "nr")
431     ("Spanish" . "es")
432     ("Spanish (Canary Islands)" . "es_IC")
433     ("Sukuma" . "suk")
434     ("Sundanese" . "su")
435     ("Susu" . "sus")
436     ("Swahili" . "sw")
437     ("Swedish" . "sv")
438     ("Swiss German" . "gsw")
439     ("Tagalog" . "tl")
440     ("Tahitian" . "ty")
441     ("Tajik" . "tg")
442     ("Tamil" . "ta")
443     ("Tatar" . "tt")
444     ("Telugu" . "te")
445     ("Tetum" . "tet")
446     ("Thai" . "th")
447     ("Tibetan" . "bo")
448     ("Tigrinya" . "ti")
449     ("Timne" . "tem")
450     ("Tiv" . "tiv")
451     ("Tonga" . "to")
452     ("Tsonga" . "ts")
453     ("Tumbuka" . "tum")
454     ("Turkish" . "tr")
455     ("Turkmen" . "tk")
456     ("Twi" . "tw")
457     ("Tyap" . "kcg")
458     ("Uighur" . "ug")
459     ("Ukrainian" . "uk")
460     ("Umbundu" . "umb")
461     ("Urdu" . "ur")
462     ("Uzbek" . "uz")
463     ("Venda" . "ve")
464     ("Vietnamese" . "vi")
465     ("Volapuk" . "vo")
466     ("Walloon" . "wa")
467     ("Walamo" . "wal")
468     ("Waray" . "war")
469     ("Welsh" . "cy")
470     ("Western Frisian" . "fy")
471     ("Wolof" . "wo")
472     ("Xhosa" . "xh")
473     ("Yao" . "yao")
474     ("Yiddish" . "yi")
475     ("Yoruba" . "yo")
476     ("Zapotec" . "zap")
477     ("Zhuang" . "za")
478     ("Zulu" . "zu")
479     )
480   "*Association list giving team codes from team names.
481 This is used for generating a submission file name for the 'M' command.
482 If a string instead of an alist, it is a team code to use unconditionnally."
483   :type 'sexp
484   :group 'po)
485
486 (defcustom po-gzip-uuencode-command "gzip -9 | uuencode -m"
487   "*The filter to use for preparing a mail invoice of the PO file.
488 Normally \"gzip -9 | uuencode -m\", remove the -9 for lesser compression,
489 or remove the -m if you are not using the GNU version of 'uuencode'."
490   :type 'string
491   :group 'po)
492
493 (defvar po-subedit-mode-syntax-table
494   (copy-syntax-table text-mode-syntax-table)
495   "Syntax table used while in PO mode.")
496 \f
497 ;;; Emacs portability matters - part II.
498
499 ;;; Many portability matters are addressed in this page.  The few remaining
500 ;;; cases, elsewhere, all involve  'eval-and-compile', 'boundp' or 'fboundp'.
501
502 ;; Protect string comparisons from text properties if possible.
503 (eval-and-compile
504   (fset 'po-buffer-substring
505         (symbol-function (if (fboundp 'buffer-substring-no-properties)
506                              'buffer-substring-no-properties
507                            'buffer-substring)))
508
509   (if (fboundp 'match-string-no-properties)
510       (fset 'po-match-string (symbol-function 'match-string-no-properties))
511     (defun po-match-string (number)
512       "Return string of text matched by last search."
513       (po-buffer-substring (match-beginning number) (match-end number)))))
514
515 ;; Handle missing 'with-temp-buffer' function.
516 (eval-and-compile
517   (if (fboundp 'with-temp-buffer)
518       (fset 'po-with-temp-buffer (symbol-function 'with-temp-buffer))
519
520     (defmacro po-with-temp-buffer (&rest forms)
521       "Create a temporary buffer, and evaluate FORMS there like 'progn'."
522       (let ((curr-buffer (make-symbol "curr-buffer"))
523             (temp-buffer (make-symbol "temp-buffer")))
524         `(let ((,curr-buffer (current-buffer))
525                (,temp-buffer (get-buffer-create
526                               (generate-new-buffer-name " *po-temp*"))))
527            (unwind-protect
528                (progn
529                  (set-buffer ,temp-buffer)
530                  ,@forms)
531              (set-buffer ,curr-buffer)
532              (and (buffer-name ,temp-buffer)
533                   (kill-buffer ,temp-buffer))))))))
534
535 ;; Handle missing 'kill-new' function.
536 (eval-and-compile
537   (if (fboundp 'kill-new)
538       (fset 'po-kill-new (symbol-function 'kill-new))
539
540     (defun po-kill-new (string)
541       "Push STRING onto the kill ring, for Emacs 18 where kill-new is missing."
542       (po-with-temp-buffer
543         (insert string)
544         (kill-region (point-min) (point-max))))))
545
546 ;; Handle missing 'read-event' function.
547 (eval-and-compile
548   (fset 'po-read-event
549         (cond ((fboundp 'read-event)
550                ;; GNU Emacs.
551                'read-event)
552               ((fboundp 'next-command-event)
553                ;; XEmacs.
554                'next-command-event)
555               (t
556                ;; Older Emacses.
557                'read-char))))
558
559 ;; Handle missing 'force-mode-line-update' function.
560 (eval-and-compile
561   (if (fboundp 'force-mode-line-update)
562       (fset 'po-force-mode-line-update
563             (symbol-function 'force-mode-line-update))
564
565     (defun po-force-mode-line-update ()
566       "Force the mode-line of the current buffer to be redisplayed."
567       (set-buffer-modified-p (buffer-modified-p)))))
568
569 ;; Handle portable highlighting.  Code has been adapted (OK... stolen! :-)
570 ;; from 'ispell.el'.
571 (eval-and-compile
572   (cond
573    (po-EMACS20
574
575     (defun po-create-overlay ()
576       "Create and return a deleted overlay structure.
577 The variable 'po-highlight-face' selects the face to use for highlighting."
578       (let ((overlay (make-overlay (point) (point))))
579         (overlay-put overlay 'face po-highlight-face)
580         ;; The fun thing is that a deleted overlay retains its face, and is
581         ;; movable.
582         (delete-overlay overlay)
583         overlay))
584
585     (defun po-highlight (overlay start end &optional buffer)
586       "Use OVERLAY to highlight the string from START to END.
587 If limits are not relative to the current buffer, use optional BUFFER."
588       (move-overlay overlay start end (or buffer (current-buffer))))
589
590     (defun po-rehighlight (overlay)
591       "Ensure OVERLAY is highlighted."
592       ;; There is nothing to do, as GNU Emacs allows multiple highlights.
593       nil)
594
595     (defun po-dehighlight (overlay)
596       "Display normally the last string which OVERLAY highlighted.
597 The current buffer should be in PO mode, when this function is called."
598       (delete-overlay overlay)))
599
600    (po-XEMACS
601
602     (defun po-create-overlay ()
603       "Create and return a deleted overlay structure."
604       ;; The same as for GNU Emacs above, except the created extent is
605       ;; already detached, so there's no need to "delete" it
606       ;; explicitly.
607       (let ((extent (make-extent nil nil)))
608         (set-extent-face extent po-highlight-face)
609         extent))
610
611     (defun po-highlight (extent start end &optional buffer)
612       "Use EXTENT to highlight the string from START to END.
613 If limits are not relative to the current buffer, use optional BUFFER."
614       (set-extent-endpoints extent start end (or buffer (current-buffer))))
615
616     (defun po-rehighlight (extent)
617       "Ensure EXTENT is highlighted."
618       ;; Nothing to do here.
619       nil)
620
621     (defun po-dehighlight (extent)
622       "Display normally the last string which EXTENT highlighted."
623       (detach-extent extent)))
624
625    (t
626
627     (defun po-create-overlay ()
628       "Create and return a deleted overlay structure."
629       (cons (make-marker) (make-marker)))
630
631     (defun po-highlight (overlay start end &optional buffer)
632       "Use OVERLAY to highlight the string from START to END.
633 If limits are not relative to the current buffer, use optional BUFFER.
634 No doubt that highlighting, when Emacs does not allow it, is a kludge."
635       (save-excursion
636         (and buffer (set-buffer buffer))
637         (let ((modified (buffer-modified-p))
638               (buffer-read-only nil)
639               (inhibit-quit t)
640               (buffer-undo-list t)
641               (text (buffer-substring start end)))
642           (goto-char start)
643           (delete-region start end)
644           (insert-char ?  (- end start))
645           (sit-for 0)
646           (setq inverse-video (not inverse-video))
647           (delete-region start end)
648           (insert text)
649           (sit-for 0)
650           (setq inverse-video (not inverse-video))
651           (set-buffer-modified-p modified)))
652       (set-marker (car overlay) start (or buffer (current-buffer)))
653       (set-marker (cdr overlay) end (or buffer (current-buffer))))
654
655     (defun po-rehighlight (overlay)
656       "Ensure OVERLAY is highlighted."
657       (let ((buffer (marker-buffer (car overlay)))
658             (start (marker-position (car overlay)))
659             (end (marker-position (cdr overlay))))
660         (and buffer
661              (buffer-name buffer)
662              (po-highlight overlay start end buffer))))
663
664     (defun po-dehighlight (overlay)
665       "Display normally the last string which OVERLAY highlighted."
666       (let ((buffer (marker-buffer (car overlay)))
667             (start (marker-position (car overlay)))
668             (end (marker-position (cdr overlay))))
669         (if buffer
670             (save-excursion
671               (set-buffer buffer)
672               (let ((modified (buffer-modified-p))
673                     (buffer-read-only nil)
674                     (inhibit-quit t)
675                     (buffer-undo-list t))
676                 (let ((text (buffer-substring start end)))
677                   (goto-char start)
678                   (delete-region start end)
679                   (insert-char ?  (- end start))
680                   (sit-for 0)
681                   (delete-region start end)
682                   (insert text)
683                   (sit-for 0)
684                   (set-buffer-modified-p modified)))))
685         (setcar overlay (make-marker))
686         (setcdr overlay (make-marker))))
687
688     )))
689 \f
690 ;;; Buffer local variables.
691
692 ;; The following block of declarations has the main purpose of avoiding
693 ;; byte compiler warnings.  It also introduces some documentation for
694 ;; each of these variables, all meant to be local to PO mode buffers.
695
696 ;; Flag telling that MODE-LINE-STRING should be displayed.  See 'Window'
697 ;; page below.  Exceptionally, this variable is local to *all* buffers.
698 (defvar po-mode-flag)
699
700 ;; PO buffers are kept read-only to prevent random modifications.  READ-ONLY
701 ;; holds the value of the read-only flag before PO mode was entered.
702 (defvar po-read-only)
703
704 ;; The current entry extends from START-OF-ENTRY to END-OF-ENTRY, it
705 ;; includes preceding whitespace and excludes following whitespace.  The
706 ;; start of keyword lines are START-OF-MSGID and START-OF-MSGSTR.
707 ;; ENTRY-TYPE classifies the entry.
708 (defvar po-start-of-entry)
709 (defvar po-start-of-msgctxt) ; = po-start-of-msgid if there is no msgctxt
710 (defvar po-start-of-msgid)
711 (defvar po-start-of-msgid_plural) ; = nil if there is no msgid_plural
712 (defvar po-start-of-msgstr-block)
713 (defvar po-start-of-msgstr-form)
714 (defvar po-end-of-msgstr-form)
715 (defvar po-end-of-entry)
716 (defvar po-entry-type)
717
718 ;; A few counters are usefully shown in the Emacs mode line.
719 (defvar po-translated-counter)
720 (defvar po-fuzzy-counter)
721 (defvar po-untranslated-counter)
722 (defvar po-obsolete-counter)
723 (defvar po-mode-line-string)
724
725 ;; PO mode keeps track of fields being edited, for one given field should
726 ;; have one editing buffer at most, and for exiting a PO buffer properly
727 ;; should offer to close all pending edits.  Variable EDITED-FIELDS holds an
728 ;; an list of "slots" of the form: (ENTRY-MARKER EDIT-BUFFER OVERLAY-INFO).
729 ;; To allow simultaneous edition of the comment and the msgstr of an entry,
730 ;; ENTRY-MARKER points to the msgid line if a comment is being edited, or to
731 ;; the msgstr line if the msgstr is being edited.  EDIT-BUFFER is the
732 ;; temporary Emacs buffer used to edit the string.  OVERLAY-INFO, when not
733 ;; nil, holds an overlay (or if overlays are not supported, a cons of two
734 ;; markers) for this msgid string which became highlighted for the edit.
735 (defvar po-edited-fields)
736
737 ;; We maintain a set of movable pointers for returning to entries.
738 (defvar po-marker-stack)
739
740 ;; SEARCH path contains a list of directories where files may be found,
741 ;; in a format suitable for read completion.  Each directory includes
742 ;; its trailing slash.  PO mode starts with "./" and "../".
743 (defvar po-search-path)
744
745 ;; The following variables are meaningful only when REFERENCE-CHECK
746 ;; is identical to START-OF-ENTRY, else they should be recomputed.
747 ;; REFERENCE-ALIST contains all known references for the current
748 ;; entry, each list element is (PROMPT FILE LINE), where PROMPT may
749 ;; be used for completing read, FILE is a string and LINE is a number.
750 ;; REFERENCE-CURSOR is a cycling cursor into REFERENCE-ALIST.
751 (defvar po-reference-alist)
752 (defvar po-reference-cursor)
753 (defvar po-reference-check)
754
755 ;; The following variables are for marking translatable strings in program
756 ;; sources.  KEYWORDS is the list of keywords for marking translatable
757 ;; strings, kept in a format suitable for reading with completion.
758 ;; STRING-CONTENTS holds the value of the most recent string found in sources,
759 ;; and when it is not nil, then STRING-BUFFER, STRING-START and STRING-END
760 ;; describe where it is.  MARKING-OVERLAY, if not 'nil', holds the overlay
761 ;; which highlight the last found string; for older Emacses, it holds the cons
762 ;; of two markers around the highlighted region.
763 (defvar po-keywords)
764 (defvar po-string-contents)
765 (defvar po-string-buffer)
766 (defvar po-string-start)
767 (defvar po-string-end)
768 (defvar po-marking-overlay)
769 \f
770 ;;; PO mode variables and constants (usually not to customize).
771
772 ;; The textdomain should really be "gettext", only trying it for now.
773 ;; All this requires more thinking, we cannot just do this like that.
774 (set-translation-domain "po-mode")
775
776 (defun po-mode-version ()
777   "Show Emacs PO mode version."
778   (interactive)
779   (message (_"Emacs PO mode, version %s") po-mode-version-string))
780
781 (defconst po-help-display-string
782   (_"\
783 PO Mode Summary           Next Previous            Miscellaneous
784 *: Later, /: Docum        n    p    Any type       .     Redisplay
785                           t    T    Translated     /v    Version info
786 Moving around             f    F    Fuzzy          ?, h  This help
787 <    First if any         o    O    Obsolete       =     Current index
788 >    Last if any          u    U    Untranslated   0     Other window
789 /SPC Auto select                                   V     Validate
790                         Msgstr Comments            M     Mail officially
791 Modifying entries         RET  #    Call editor    _     Undo
792 TAB   Remove fuzzy mark   k    K    Kill to        E     Edit out full
793 DEL   Fuzzy or fade out   w    W    Copy to        Q     Forceful quit
794 LFD   Init with msgid     y    Y    Yank from      q     Confirm and quit
795
796 gettext Keyword Marking                            Position Stack
797 ,    Find next string     Compendiums              m  Mark and push current
798 M-,  Mark translatable    *c    To compendium      r  Pop and return
799 M-.  Change mark, mark    *M-C  Select, save       x  Exchange current/top
800
801 Program Sources           Auxiliary Files          Lexicography
802 s    Cycle reference      a    Cycle file          *l    Lookup translation
803 M-s  Select reference     C-c C-a  Select file     *M-l  Add/edit translation
804 S    Consider path        A    Consider PO file    *L    Consider lexicon
805 M-S  Ignore path          M-A  Ignore PO file      *M-L  Ignore lexicon
806 ")
807   "Help page for PO mode.")
808
809 (defconst po-mode-menu-layout
810   `("PO"
811     ("Moving around"
812      ["Auto select" po-auto-select-entry
813       ,@(if (featurep 'xemacs) '(t)
814           '(:help "Jump to next interesting entry"))]
815      "---"
816      ;; Forward
817      ["Any next" po-next-entry
818       ,@(if (featurep 'xemacs) '(t)
819           '(:help "Jump to next entry"))]
820      ["Next translated" po-next-translated-entry
821       ,@(if (featurep 'xemacs) '(t)
822           '(:help "Jump to next translated entry"))]
823      ["Next fuzzy" po-next-fuzzy-entry
824       ,@(if (featurep 'xemacs) '(t)
825           '(:help "Jump to next fuzzy entry"))]
826      ["Next obsolete" po-next-obsolete-entry
827       ,@(if (featurep 'xemacs) '(t)
828           '(:help "Jump to next obsolete entry"))]
829      ["Next untranslated" po-next-untranslated-entry
830       ,@(if (featurep 'xemacs) '(t)
831           '(:help "Jump to next untranslated entry"))]
832      ["Last file entry" po-last-entry
833       ,@(if (featurep 'xemacs) '(t)
834           '(:help "Jump to last entry"))]
835      "---"
836      ;; Backward
837      ["Any previous" po-previous-entry
838       ,@(if (featurep 'xemacs) '(t)
839           '(:help "Jump to previous entry"))]
840      ["Previous translated" po-previous-translated-entry
841       ,@(if (featurep 'xemacs) '(t)
842           '(:help "Jump to previous translated entry"))]
843      ["Previous fuzzy" po-previous-fuzzy-entry
844       ,@(if (featurep 'xemacs) '(t)
845           '(:help "Jump to previous fuzzy entry"))]
846      ["Previous obsolete" po-previous-obsolete-entry
847       ,@(if (featurep 'xemacs) '(t)
848           '(:help "Jump to previous obsolete entry"))]
849      ["Previous untranslated" po-previous-untranslated-entry
850       ,@(if (featurep 'xemacs) '(t)
851           '(:help "Jump to previous untranslated entry"))]
852      ["First file entry" po-first-entry
853       ,@(if (featurep 'xemacs) '(t)
854           '(:help "Jump to first entry"))]
855      "---"
856      ;; "Position stack"
857      ["Mark and push current" po-push-location
858       ,@(if (featurep 'xemacs) '(t)
859           '(:help "Remember current location"))]
860      ["Pop and return" po-pop-location
861       ,@(if (featurep 'xemacs) '(t)
862           '(:help "Jump to last remembered location and forget about it"))]
863      ["Exchange current/top" po-exchange-location
864       ,@(if (featurep 'xemacs) '(t)
865           '(:help "Jump to last remembered location and remember current location"))]
866      "---"
867      ["Redisplay" po-current-entry
868       ,@(if (featurep 'xemacs) '(t)
869           '(:help "Make current entry properly visible"))]
870      ["Current index" po-statistics
871       ,@(if (featurep 'xemacs) '(t)
872           '(:help "Statistical info on current translation file"))])
873     ("Modifying entries"
874      ["Undo" po-undo
875       ,@(if (featurep 'xemacs) '(t)
876           '(:help "Revoke last changed entry"))]
877      "---"
878      ;; "Msgstr"
879      ["Edit msgstr" po-edit-msgstr
880       ,@(if (featurep 'xemacs) '(t)
881           '(:help "Edit current translation"))]
882      ["Ediff and merge msgstr" po-edit-msgstr-and-ediff
883       ,@(if (featurep 'xemacs) '(t)
884           '(:help "Call `ediff' on current translation for merging"))]
885      ["Cut msgstr" po-kill-msgstr
886       ,@(if (featurep 'xemacs) '(t)
887           '(:help "Cut (kill) current translation"))]
888      ["Copy msgstr" po-kill-ring-save-msgstr
889       ,@(if (featurep 'xemacs) '(t)
890           '(:help "Copy current translation"))]
891      ["Paste msgstr" po-yank-msgstr
892       ,@(if (featurep 'xemacs) '(t)
893           '(:help "Paste (yank) text most recently cut/copied translation"))]
894      "---"
895      ;; "Comments"
896      ["Edit comment" po-edit-comment
897       ,@(if (featurep 'xemacs) '(t)
898           '(:help "Edit current comment"))]
899      ["Ediff and merge comment" po-edit-comment-and-ediff
900       ,@(if (featurep 'xemacs) '(t)
901           '(:help "Call `ediff' on current comment for merging"))]
902      ["Cut comment" po-kill-comment
903       ,@(if (featurep 'xemacs) '(t)
904           '(:help "Cut (kill) current comment"))]
905      ["Copy comment" po-kill-ring-save-comment
906       ,@(if (featurep 'xemacs) '(t)
907           '(:help "Copy current translation"))]
908      ["Paste comment" po-yank-comment
909       ,@(if (featurep 'xemacs) '(t)
910           '(:help "Paste (yank) text most recently cut/copied"))]
911      "---"
912      ["Remove fuzzy mark" po-unfuzzy
913       ,@(if (featurep 'xemacs) '(t)
914           '(:help "Remove \"#, fuzzy\""))]
915      ["Fuzzy or fade out" po-fade-out-entry
916       ,@(if (featurep 'xemacs) '(t)
917           '(:help "Set current entry fuzzy, or if already fuzzy delete it"))]
918      ["Init with msgid" po-msgid-to-msgstr
919       ,@(if (featurep 'xemacs) '(t)
920           '(:help "\
921 Initialize or replace current translation with the original message"))])
922     ("Other files"
923      ["Other window" po-other-window
924       ,@(if (featurep 'xemacs) '(t)
925           '(:help "Select other window; if necessay split current frame"))]
926      "---"
927      ;; "Program sources"
928      ["Cycle reference in source file" po-cycle-source-reference t]
929      ["Select reference" po-select-source-reference t]
930      ["Consider path" po-consider-source-path t]
931      ["Ignore path" po-ignore-source-path t]
932      ;; "---"
933      ;; ;; "Compendiums"
934      ;; ["To add entry to compendium" po-save-entry nil]
935      ;; ["Select from compendium, save" po-select-and-save-entry nil]
936      "---"
937      ;; "Auxiliary files"
938      ["Cycle through auxilicary file" po-cycle-auxiliary t]
939      ["Select auxilicary file" po-select-auxiliary t]
940      ["Consider as auxilicary file" po-consider-as-auxiliary t]
941      ["Ignore as auxilicary file" po-ignore-as-auxiliary t]
942      ;; "---"
943      ;; ;; "Lexicography"
944      ;; ["Lookup translation" po-lookup-lexicons nil]
945      ;; ["Add/edit translation" po-edit-lexicon-entry nil]
946      ;; ["Consider lexicon" po-consider-lexicon-file nil]
947      ;; ["Ignore lexicon" po-ignore-lexicon-file nil])
948      "---"
949      "Source marking"
950      ["Find first string" (po-tags-search '(nil)) t]
951      ["Prefer keyword" (po-select-mark-and-mark '(nil)) t]
952      ["Find next string" po-tags-search t]
953      ["Mark preferred" po-mark-translatable t]
954      ["Mark with keyword" po-select-mark-and-mark t])
955      "---"
956      ["Version info" po-mode-version
957       ,@(if (featurep 'xemacs) '(t)
958           '(:help "Display version number of PO mode"))]
959      ["Help page" po-help
960       ,@(if (featurep 'xemacs) '(t)
961           '(:help "Show the PO mode help screen"))]
962      ["Validate" po-validate
963       ,@(if (featurep 'xemacs) '(t)
964           '(:help "Check validity of current translation file using `msgfmt'"))]
965      ["Mail officially" po-send-mail
966       ,@(if (featurep 'xemacs) '(t)
967           '(:help "Send current translation file to the Translation Robot by mail"))]
968      ["Edit out full" po-edit-out-full
969       ,@(if (featurep 'xemacs) '(t)
970           '(:help "Leave PO mode to edit translation file using fundamental mode"))]
971      "---"
972      ["Forceful quit" po-quit
973       ,@(if (featurep 'xemacs) '(t)
974           '(:help "Close (kill) current translation file without saving"))]
975      ["Soft quit" po-confirm-and-quit
976       ,@(if (featurep 'xemacs) '(t)
977           '(:help "Save current translation file, than close (kill) it"))]))
978
979
980 (defconst po-subedit-mode-menu-layout
981   `("PO-Edit"
982     ["Ediff and merge translation variants" po-subedit-ediff
983       ,@(if (featurep 'xemacs) '(t)
984           '(:help "Call `ediff' for merging variants"))]
985     ["Cycle through auxiliary files" po-subedit-cycle-auxiliary t]
986     "---"
987     ["Abort edit" po-subedit-abort
988      ,@(if (featurep 'xemacs) '(t)
989           '(:help "Don't change the translation"))]
990     ["Exit edit" po-subedit-exit
991      ,@(if (featurep 'xemacs) '(t)
992          '(:help "Use this text as the translation and close current edit buffer"))]))
993
994 (defconst po-subedit-message
995   (_"Type 'C-c C-c' once done, or 'C-c C-k' to abort edit")
996   "Message to post in the minibuffer when an edit buffer is displayed.")
997
998 (defvar po-auxiliary-list nil
999   "List of auxiliary PO files, in completing read format.")
1000
1001 (defvar po-auxiliary-cursor nil
1002   "Cursor into the 'po-auxiliary-list'.")
1003
1004 (defvar po-compose-mail-function
1005   (let ((functions '(compose-mail-other-window
1006                      message-mail-other-window
1007                      compose-mail
1008                      message-mail))
1009         result)
1010     (while (and (not result) functions)
1011       (if (fboundp (car functions))
1012           (setq result (car functions))
1013         (setq functions (cdr functions))))
1014     (cond (result)
1015           ((fboundp 'mail-other-window)
1016            (function (lambda (to subject)
1017                        (mail-other-window nil to subject))))
1018           ((fboundp 'mail)
1019            (function (lambda (to subject)
1020                        (mail nil to subject))))
1021           (t (function (lambda (to subject)
1022                          (error (_"I do not know how to mail to '%s'") to))))))
1023   "Function to start composing an electronic message.")
1024
1025 (defvar po-any-previous-msgctxt-regexp
1026   "^#\\(~\\)?|[ \t]*msgctxt.*\n\\(#\\(~\\)?|[ \t]*\".*\n\\)*"
1027   "Regexp matching a whole #| msgctxt field, whether obsolete or not.")
1028
1029 (defvar po-any-previous-msgid-regexp
1030   "^#\\(~\\)?|[ \t]*msgid.*\n\\(#\\(~\\)?|[ \t]*\".*\n\\)*"
1031   "Regexp matching a whole #| msgid field, whether obsolete or not.")
1032
1033 (defvar po-any-previous-msgid_plural-regexp
1034   "^#\\(~\\)?|[ \t]*msgid_plural.*\n\\(#\\(~\\)?|[ \t]*\".*\n\\)*"
1035   "Regexp matching a whole #| msgid_plural field, whether obsolete or not.")
1036
1037 (defvar po-any-msgctxt-msgid-regexp
1038   "^\\(#~[ \t]*\\)?msg\\(ctxt\\|id\\).*\n\\(\\(#~[ \t]*\\)?\".*\n\\)*"
1039   "Regexp matching a whole msgctxt or msgid field, whether obsolete or not.")
1040
1041 (defvar po-any-msgid-regexp
1042   "^\\(#~[ \t]*\\)?msgid.*\n\\(\\(#~[ \t]*\\)?\".*\n\\)*"
1043   "Regexp matching a whole msgid field, whether obsolete or not.")
1044
1045 (defvar po-any-msgid_plural-regexp
1046   "^\\(#~[ \t]*\\)?msgid_plural.*\n\\(\\(#~[ \t]*\\)?\".*\n\\)*"
1047   "Regexp matching a whole msgid_plural field, whether obsolete or not.")
1048
1049 (defvar po-any-msgstr-block-regexp
1050   "^\\(#~[ \t]*\\)?msgstr\\([ \t]\\|\\[0\\]\\).*\n\\(\\(#~[ \t]*\\)?\".*\n\\)*\\(\\(#~[ \t]*\\)?msgstr\\[[0-9]\\].*\n\\(\\(#~[ \t]*\\)?\".*\n\\)*\\)*"
1051   "Regexp matching a whole msgstr or msgstr[] field, whether obsolete or not.")
1052
1053 (defvar po-any-msgstr-form-regexp
1054   ;; "^\\(#~[ \t]*\\)?msgstr.*\n\\(\\(#~[ \t]*\\)?\".*\n\\)*"
1055   "^\\(#~[ \t]*\\)?msgstr\\(\\[[0-9]\\]\\)?.*\n\\(\\(#~[ \t]*\\)?\".*\n\\)*"
1056   "Regexp matching just one msgstr or msgstr[] field, whether obsolete or not.")
1057
1058 (defvar po-msgstr-idx-keyword-regexp
1059   "^\\(#~[ \t]*\\)?msgstr\\[[0-9]\\]"
1060   "Regexp matching an indexed msgstr keyword, whether obsolete or not.")
1061
1062 (defvar po-msgfmt-program "msgfmt"
1063   "Path to msgfmt program from GNU gettext package.")
1064
1065 ;; Font lock based highlighting code.
1066 (defconst po-font-lock-keywords
1067   '(
1068     ;; ("^\\(msgctxt \\|msgid \\|msgstr \\)?\"\\|\"$" . font-lock-keyword-face)
1069     ;; (regexp-opt
1070     ;;  '("msgctxt " "msgid " "msgid_plural " "msgstr " "msgstr[0] " "msgstr[1] "))
1071     ("^\\(\\(msg\\(ctxt\\|id\\(_plural\\)?\\|str\\(\\[[0-9]\\]\\)?\\)\\) \\)?\"\\|\"$"
1072      . font-lock-keyword-face)
1073     ("\\\\.\\|%[*$-.0-9hjltuzL]*[a-zA-Z]" . font-lock-variable-name-face)
1074     ("^# .*\\|^#[:,]?" . font-lock-comment-face)
1075     ("^#:\\(.*\\)" 1 font-lock-reference-face)
1076     ;; The following line does not work, and I wonder why.
1077     ;;("^#,\\(.*\\)" 1 font-function-name-reference-face)
1078     )
1079   "Additional expressions to highlight in PO mode.")
1080
1081 ;; Old activator for 'font lock'.  Is it still useful?  I don't think so.
1082 ;;(if (boundp 'font-lock-keywords)
1083 ;;    (put 'po-mode 'font-lock-keywords 'po-font-lock-keywords))
1084
1085 ;; 'hilit19' based highlighting code has been disabled, as most probably
1086 ;; nobody really needs it (it also generates ugly byte-compiler warnings).
1087 ;;
1088 ;;(if (fboundp 'hilit-set-mode-patterns)
1089 ;;    (hilit-set-mode-patterns 'po-mode
1090 ;;                             '(("^# .*\\|^#$" nil comment)
1091 ;;                               ("^#[.,:].*" nil include)
1092 ;;                               ("^\\(msgid\\|msgstr\\) *\"" nil keyword)
1093 ;;                               ("^\"\\|\"$" nil keyword))))
1094 \f
1095 ;;; Mode activation.
1096
1097 ;; Emacs 21.2 comes with po-find-file-coding-system. We give preference
1098 ;; to the version shipped with Emacs.
1099 (if (not (fboundp 'po-find-file-coding-system))
1100   (require 'po-compat))
1101
1102 (defvar po-mode-abbrev-table nil
1103   "Abbrev table used while in PO mode.")
1104 (define-abbrev-table 'po-mode-abbrev-table ())
1105
1106 (defvar po-mode-map
1107   ;; Use (make-keymap) because (make-sparse-keymap) does not work on Demacs.
1108   (let ((po-mode-map (make-keymap)))
1109     (suppress-keymap po-mode-map)
1110     (define-key po-mode-map "\C-i" 'po-unfuzzy)
1111     (define-key po-mode-map "\C-j" 'po-msgid-to-msgstr)
1112     (define-key po-mode-map "\C-m" 'po-edit-msgstr)
1113     (define-key po-mode-map " " 'po-auto-select-entry)
1114     (define-key po-mode-map "?" 'po-help)
1115     (define-key po-mode-map "#" 'po-edit-comment)
1116     (define-key po-mode-map "," 'po-tags-search)
1117     (define-key po-mode-map "." 'po-current-entry)
1118     (define-key po-mode-map "<" 'po-first-entry)
1119     (define-key po-mode-map "=" 'po-statistics)
1120     (define-key po-mode-map ">" 'po-last-entry)
1121     (define-key po-mode-map "a" 'po-cycle-auxiliary)
1122 ;;;;  (define-key po-mode-map "c" 'po-save-entry)
1123     (define-key po-mode-map "f" 'po-next-fuzzy-entry)
1124     (define-key po-mode-map "h" 'po-help)
1125     (define-key po-mode-map "k" 'po-kill-msgstr)
1126 ;;;;  (define-key po-mode-map "l" 'po-lookup-lexicons)
1127     (define-key po-mode-map "m" 'po-push-location)
1128     (define-key po-mode-map "n" 'po-next-entry)
1129     (define-key po-mode-map "o" 'po-next-obsolete-entry)
1130     (define-key po-mode-map "p" 'po-previous-entry)
1131     (define-key po-mode-map "q" 'po-confirm-and-quit)
1132     (define-key po-mode-map "r" 'po-pop-location)
1133     (define-key po-mode-map "s" 'po-cycle-source-reference)
1134     (define-key po-mode-map "t" 'po-next-translated-entry)
1135     (define-key po-mode-map "u" 'po-next-untranslated-entry)
1136     (define-key po-mode-map "v" 'po-mode-version)
1137     (define-key po-mode-map "w" 'po-kill-ring-save-msgstr)
1138     (define-key po-mode-map "x" 'po-exchange-location)
1139     (define-key po-mode-map "y" 'po-yank-msgstr)
1140     (define-key po-mode-map "A" 'po-consider-as-auxiliary)
1141     (define-key po-mode-map "E" 'po-edit-out-full)
1142     (define-key po-mode-map "F" 'po-previous-fuzzy-entry)
1143     (define-key po-mode-map "K" 'po-kill-comment)
1144 ;;;;  (define-key po-mode-map "L" 'po-consider-lexicon-file)
1145     (define-key po-mode-map "M" 'po-send-mail)
1146     (define-key po-mode-map "O" 'po-previous-obsolete-entry)
1147     (define-key po-mode-map "T" 'po-previous-translated-entry)
1148     (define-key po-mode-map "U" 'po-previous-untranslated-entry)
1149     (define-key po-mode-map "Q" 'po-quit)
1150     (define-key po-mode-map "S" 'po-consider-source-path)
1151     (define-key po-mode-map "V" 'po-validate)
1152     (define-key po-mode-map "W" 'po-kill-ring-save-comment)
1153     (define-key po-mode-map "Y" 'po-yank-comment)
1154     (define-key po-mode-map "_" 'po-undo)
1155     (define-key po-mode-map "\C-_" 'po-undo)
1156     (define-key po-mode-map "\C-xu" 'po-undo)
1157     (define-key po-mode-map "0" 'po-other-window)
1158     (define-key po-mode-map "\177" 'po-fade-out-entry)
1159     (define-key po-mode-map "\C-c\C-a" 'po-select-auxiliary)
1160     (define-key po-mode-map "\C-c\C-e" 'po-edit-msgstr-and-ediff)
1161     (define-key po-mode-map [?\C-c?\C-#] 'po-edit-comment-and-ediff)
1162     (define-key po-mode-map "\C-c\C-C" 'po-edit-comment-and-ediff)
1163     (define-key po-mode-map "\M-," 'po-mark-translatable)
1164     (define-key po-mode-map "\M-." 'po-select-mark-and-mark)
1165 ;;;;  (define-key po-mode-map "\M-c" 'po-select-and-save-entry)
1166 ;;;;  (define-key po-mode-map "\M-l" 'po-edit-lexicon-entry)
1167     (define-key po-mode-map "\M-s" 'po-select-source-reference)
1168     (define-key po-mode-map "\M-A" 'po-ignore-as-auxiliary)
1169 ;;;;  (define-key po-mode-map "\M-L" 'po-ignore-lexicon-file)
1170     (define-key po-mode-map "\M-S" 'po-ignore-source-path)
1171     po-mode-map)
1172   "Keymap for PO mode.")
1173
1174 (defun po-mode ()
1175   "Major mode for translators when they edit PO files.
1176
1177 Special commands:
1178 \\{po-mode-map}
1179 Turning on PO mode calls the value of the variable 'po-mode-hook',
1180 if that value is non-nil.  Behaviour may be adjusted through some variables,
1181 all reachable through 'M-x customize', in group 'Emacs.Editing.I18n.Po'."
1182   (interactive)
1183   (kill-all-local-variables)
1184   (setq major-mode 'po-mode
1185         mode-name "PO")
1186   (use-local-map po-mode-map)
1187   (if (fboundp 'easy-menu-define)
1188       (progn
1189         (easy-menu-define po-mode-menu po-mode-map "" po-mode-menu-layout)
1190         (and po-XEMACS (easy-menu-add po-mode-menu))))
1191   (set (make-local-variable 'font-lock-defaults) '(po-font-lock-keywords t))
1192
1193   (set (make-local-variable 'po-read-only) buffer-read-only)
1194   (setq buffer-read-only t)
1195
1196   (make-local-variable 'po-start-of-entry)
1197   (make-local-variable 'po-start-of-msgctxt)
1198   (make-local-variable 'po-start-of-msgid)
1199   (make-local-variable 'po-start-of-msgid_plural)
1200   (make-local-variable 'po-start-of-msgstr-block)
1201   (make-local-variable 'po-end-of-entry)
1202   (make-local-variable 'po-entry-type)
1203
1204   (make-local-variable 'po-translated-counter)
1205   (make-local-variable 'po-fuzzy-counter)
1206   (make-local-variable 'po-untranslated-counter)
1207   (make-local-variable 'po-obsolete-counter)
1208   (make-local-variable 'po-mode-line-string)
1209
1210   (setq po-mode-flag t)
1211
1212   (po-check-file-header)
1213   (po-compute-counters nil)
1214
1215   (set (make-local-variable 'po-edited-fields) nil)
1216   (set (make-local-variable 'po-marker-stack) nil)
1217   (set (make-local-variable 'po-search-path) '(("./") ("../")))
1218
1219   (set (make-local-variable 'po-reference-alist) nil)
1220   (set (make-local-variable 'po-reference-cursor) nil)
1221   (set (make-local-variable 'po-reference-check) 0)
1222
1223   (set (make-local-variable 'po-keywords)
1224        '(("gettext") ("gettext_noop") ("_") ("N_")))
1225   (set (make-local-variable 'po-string-contents) nil)
1226   (set (make-local-variable 'po-string-buffer) nil)
1227   (set (make-local-variable 'po-string-start) nil)
1228   (set (make-local-variable 'po-string-end) nil)
1229   (set (make-local-variable 'po-marking-overlay) (po-create-overlay))
1230
1231   (add-hook 'write-contents-hooks 'po-replace-revision-date)
1232
1233   (run-hooks 'po-mode-hook)
1234   (message (_"You may type 'h' or '?' for a short PO mode reminder.")))
1235
1236 (defvar po-subedit-mode-map
1237   ;; Use (make-keymap) because (make-sparse-keymap) does not work on Demacs.
1238   (let ((po-subedit-mode-map (make-keymap)))
1239     (define-key po-subedit-mode-map "\C-c\C-a" 'po-subedit-cycle-auxiliary)
1240     (define-key po-subedit-mode-map "\C-c\C-c" 'po-subedit-exit)
1241     (define-key po-subedit-mode-map "\C-c\C-e" 'po-subedit-ediff)
1242     (define-key po-subedit-mode-map "\C-c\C-k" 'po-subedit-abort)
1243     po-subedit-mode-map)
1244   "Keymap while editing a PO mode entry (or the full PO file).")
1245 \f
1246 ;;; Window management.
1247
1248 (make-variable-buffer-local 'po-mode-flag)
1249
1250 (defvar po-mode-line-entry '(po-mode-flag ("  " po-mode-line-string))
1251   "Mode line format entry displaying MODE-LINE-STRING.")
1252
1253 ;; Insert MODE-LINE-ENTRY in mode line, but on first load only.
1254 (or (member po-mode-line-entry mode-line-format)
1255     ;; mode-line-format usually contains global-mode-string, but some
1256     ;; people customize this variable. As a last resort, append at the end.
1257     (let ((prev-entry (or (member 'global-mode-string mode-line-format)
1258                           (member "   " mode-line-format)
1259                           (last mode-line-format))))
1260       (setcdr prev-entry (cons po-mode-line-entry (cdr prev-entry)))))
1261
1262 (defun po-update-mode-line-string ()
1263   "Compute a new statistics string to display in mode line."
1264   (setq po-mode-line-string
1265         (concat (format "%dt" po-translated-counter)
1266                 (if (> po-fuzzy-counter 0)
1267                     (format "+%df" po-fuzzy-counter))
1268                 (if (> po-untranslated-counter 0)
1269                     (format "+%du" po-untranslated-counter))
1270                 (if (> po-obsolete-counter 0)
1271                     (format "+%do" po-obsolete-counter))))
1272   (po-force-mode-line-update))
1273
1274 (defun po-type-counter ()
1275   "Return the symbol name of the counter appropriate for the current entry."
1276   (cond ((eq po-entry-type 'obsolete) 'po-obsolete-counter)
1277         ((eq po-entry-type 'fuzzy) 'po-fuzzy-counter)
1278         ((eq po-entry-type 'translated) 'po-translated-counter)
1279         ((eq po-entry-type 'untranslated) 'po-untranslated-counter)
1280         (t (error (_"Unknown entry type")))))
1281
1282 (defun po-decrease-type-counter ()
1283   "Decrease the counter corresponding to the nature of the current entry."
1284   (let ((counter (po-type-counter)))
1285     (set counter (1- (eval counter)))))
1286
1287 (defun po-increase-type-counter ()
1288   "Increase the counter corresponding to the nature of the current entry.
1289 Then, update the mode line counters."
1290   (let ((counter (po-type-counter)))
1291     (set counter (1+ (eval counter))))
1292   (po-update-mode-line-string))
1293
1294 ;; Avoid byte compiler warnings.
1295 (defvar po-fuzzy-regexp)
1296 (defvar po-untranslated-regexp)
1297
1298 (defun po-compute-counters (flag)
1299   "Prepare counters for mode line display.  If FLAG, also echo entry position."
1300   (and flag (po-find-span-of-entry))
1301   (setq po-translated-counter 0
1302         po-fuzzy-counter 0
1303         po-untranslated-counter 0
1304         po-obsolete-counter 0)
1305   (let ((position 0) (total 0) current here)
1306     ;; FIXME 'here' looks obsolete / 2001-08-23 03:54:26 CEST -ke-
1307     (save-excursion
1308       (po-find-span-of-entry)
1309       (setq current po-start-of-msgstr-block)
1310       (goto-char (point-min))
1311       ;; While counting, skip the header entry, for consistency with msgfmt.
1312       (po-find-span-of-entry)
1313       (if (string-equal (po-get-msgid) "")
1314           (goto-char po-end-of-entry))
1315       (if (re-search-forward "^msgid" (point-max) t)
1316           (progn
1317             ;; Start counting
1318             (while (re-search-forward po-any-msgstr-block-regexp nil t)
1319               (and (= (% total 20) 0)
1320                    (if flag
1321                        (message (_"Position %d/%d") position total)
1322                      (message (_"Position %d") total)))
1323               (setq here (point))
1324               (goto-char (match-beginning 0))
1325               (setq total (1+ total))
1326               (and flag (eq (point) current) (setq position total))
1327               (cond ((eq (following-char) ?#)
1328                      (setq po-obsolete-counter (1+ po-obsolete-counter)))
1329                     ((looking-at po-untranslated-regexp)
1330                      (setq po-untranslated-counter (1+ po-untranslated-counter)))
1331                     (t (setq po-translated-counter (1+ po-translated-counter))))
1332               (goto-char here))
1333
1334             ;; Make another pass just for the fuzzy entries, kind of kludgey.
1335             ;; FIXME: Counts will be wrong if untranslated entries are fuzzy, yet
1336             ;; this should not normally happen.
1337             (goto-char (point-min))
1338             (while (re-search-forward po-fuzzy-regexp nil t)
1339               (setq po-fuzzy-counter (1+ po-fuzzy-counter)))
1340             (setq po-translated-counter (- po-translated-counter po-fuzzy-counter)))
1341         '()))
1342
1343     ;; Push the results out.
1344     (if flag
1345         (message (_"\
1346 Position %d/%d; %d translated, %d fuzzy, %d untranslated, %d obsolete")
1347                  position total po-translated-counter po-fuzzy-counter
1348                  po-untranslated-counter po-obsolete-counter)
1349       (message "")))
1350   (po-update-mode-line-string))
1351
1352 (defun po-redisplay ()
1353   "Redisplay the current entry."
1354   ;; FIXME: Should try to fit the whole entry on the window.  If this is not
1355   ;; possible, should try to fit the comment and the msgid.  Otherwise,
1356   ;; should try to fit the msgid.  Else, the first line of the msgid should
1357   ;; be at the top of the window.
1358   (goto-char po-start-of-msgid))
1359
1360 (defun po-other-window ()
1361   "Get the cursor into another window, out of PO mode."
1362   (interactive)
1363   (if (one-window-p t)
1364       (progn
1365         (split-window)
1366         (switch-to-buffer (other-buffer)))
1367     (other-window 1)))
1368 \f
1369 ;;; Processing the PO file header entry.
1370
1371 (defun po-check-file-header ()
1372   "Create a missing PO mode file header, or replace an oldish one.
1373 Can be customized with the `po-auto-update-file-header' variable."
1374   (if (or (eq po-auto-update-file-header t)
1375           (and (eq po-auto-update-file-header 'ask)
1376                (y-or-n-p (_"May I update the PO Header Entry? "))))
1377       (save-excursion
1378         (save-restriction
1379           (widen) ; in case of a narrowed view to the buffer
1380           (let ((buffer-read-only po-read-only)
1381                 insert-flag end-of-header)
1382             (goto-char (point-min))
1383             (if (re-search-forward po-any-msgstr-block-regexp nil t)
1384                 (progn
1385                   ;; There is at least one entry.
1386                   (goto-char (match-beginning 0))
1387                   (forward-line -1)
1388                   (setq end-of-header (match-end 0))
1389                   (if (looking-at "msgid \"\"\n")
1390                       ;; There is indeed a PO file header.
1391                       (if (re-search-forward "\n\"PO-Revision-Date: "
1392                                              end-of-header t)
1393                           nil
1394                         ;; This is an oldish header.  Replace it all.
1395                         (goto-char end-of-header)
1396                         (while (> (point) (point-min))
1397                           (forward-line -1)
1398                           (insert "#~ ")
1399                           (beginning-of-line))
1400                         (beginning-of-line)
1401                         (setq insert-flag t))
1402                     ;; The first entry is not a PO file header, insert one.
1403                     (setq insert-flag t)))
1404               ;; Not a single entry found.
1405               (setq insert-flag t))
1406             (goto-char (point-min))
1407             (if insert-flag
1408                 (progn
1409                   (insert po-default-file-header)
1410                   (if (not (eobp))
1411                       (insert "\n")))))))
1412     (message (_"PO Header Entry was not updated..."))))
1413
1414 (defun po-replace-revision-date ()
1415   "Replace the revision date by current time in the PO file header."
1416   (if (fboundp 'format-time-string)
1417       (if (or (eq po-auto-replace-revision-date t)
1418               (and (eq po-auto-replace-revision-date 'ask)
1419                    (y-or-n-p (_"May I set PO-Revision-Date? "))))
1420           (save-excursion
1421             (goto-char (point-min))
1422             (if (re-search-forward "^\"PO-Revision-Date:.*" nil t)
1423                 (let* ((buffer-read-only po-read-only)
1424                        (time (current-time))
1425                        (seconds (or (car (current-time-zone time)) 0))
1426                        (minutes (/ (abs seconds) 60))
1427                        (zone (format "%c%02d%02d"
1428                                      (if (< seconds 0) ?- ?+)
1429                                      (/ minutes 60)
1430                                      (% minutes 60))))
1431                   (replace-match
1432                        (concat "\"PO-Revision-Date: "
1433                                (format-time-string "%Y-%m-%d %H:%M" time)
1434                                zone "\\n\"")
1435                        t t))))
1436         (message ""))
1437     (message (_"PO-Revision-Date should be adjusted...")))
1438   ;; Return nil to indicate that the buffer has not yet been saved.
1439   nil)
1440 \f
1441 ;;; Handling span of entry, entry type and entry attributes.
1442
1443 (defun po-find-span-of-entry ()
1444   "Find the extent of the PO file entry where the cursor is.
1445 Set variables po-start-of-entry, po-start-of-msgctxt, po-start-of-msgid,
1446 po-start-of-msgid_plural, po-start-of-msgstr-block, po-end-of-entry, and
1447 po-entry-type to meaningful values. po-entry-type may be set to: obsolete,
1448 fuzzy, untranslated, or translated."
1449   (let ((here (point)))
1450     (if (re-search-backward po-any-msgstr-block-regexp nil t)
1451         (progn
1452           ;; After a backward match, (match-end 0) will not extend
1453           ;; beyond point, in case point was *inside* the regexp.  We
1454           ;; need a dependable (match-end 0), so we redo the match in
1455           ;; the forward direction.
1456           (re-search-forward po-any-msgstr-block-regexp)
1457           (if (<= (match-end 0) here)
1458               (progn
1459                 ;; We most probably found the msgstr of the previous
1460                 ;; entry.  The current entry then starts just after
1461                 ;; its end, save this information just in case.
1462                 (setq po-start-of-entry (match-end 0))
1463                 ;; However, it is also possible that we are located in
1464                 ;; the crumb after the last entry in the file.  If
1465                 ;; yes, we know the middle and end of last PO entry.
1466                 (setq po-start-of-msgstr-block (match-beginning 0)
1467                       po-end-of-entry (match-end 0))
1468                 (if (re-search-forward po-any-msgstr-block-regexp nil t)
1469                     (progn
1470                       ;; We definitely were not in the crumb.
1471                       (setq po-start-of-msgstr-block (match-beginning 0)
1472                             po-end-of-entry (match-end 0)))
1473                   ;; We were in the crumb.  The start of the last PO
1474                   ;; file entry is the end of the previous msgstr if
1475                   ;; any, or else, the beginning of the file.
1476                   (goto-char po-start-of-msgstr-block)
1477                   (setq po-start-of-entry
1478                         (if (re-search-backward po-any-msgstr-block-regexp nil t)
1479                             (match-end 0)
1480                           (point-min)))))
1481             ;; The cursor was inside msgstr of the current entry.
1482             (setq po-start-of-msgstr-block (match-beginning 0)
1483                   po-end-of-entry (match-end 0))
1484             ;; The start of this entry is the end of the previous
1485             ;; msgstr if any, or else, the beginning of the file.
1486             (goto-char po-start-of-msgstr-block)
1487             (setq po-start-of-entry
1488                   (if (re-search-backward po-any-msgstr-block-regexp nil t)
1489                       (match-end 0)
1490                     (point-min)))))
1491       ;; The cursor was before msgstr in the first entry in the file.
1492       (setq po-start-of-entry (point-min))
1493       (goto-char po-start-of-entry)
1494       ;; There is at least the PO file header, so this should match.
1495       (re-search-forward po-any-msgstr-block-regexp)
1496       (setq po-start-of-msgstr-block (match-beginning 0)
1497             po-end-of-entry (match-end 0)))
1498     ;; Find start of msgid.
1499     (goto-char po-start-of-entry)
1500     (re-search-forward po-any-msgctxt-msgid-regexp)
1501     (setq po-start-of-msgctxt (match-beginning 0))
1502     (goto-char po-start-of-entry)
1503     (re-search-forward po-any-msgid-regexp)
1504     (setq po-start-of-msgid (match-beginning 0))
1505     (save-excursion
1506       (goto-char po-start-of-msgid)
1507       (setq po-start-of-msgid_plural
1508             (if (re-search-forward po-any-msgid_plural-regexp
1509                                    po-start-of-msgstr-block t)
1510                 (match-beginning 0)
1511               nil)))
1512     (save-excursion
1513       (when (>= here po-start-of-msgstr-block)
1514         ;; point was somewhere inside of msgstr*
1515         (goto-char here)
1516         (end-of-line)
1517         (re-search-backward "^\\(#~[ \t]*\\)?msgstr"))
1518       ;; Detect the boundaries of the msgstr we are interested in.
1519       (re-search-forward po-any-msgstr-form-regexp)
1520       (setq po-start-of-msgstr-form (match-beginning 0)
1521             po-end-of-msgstr-form (match-end 0)))
1522     ;; Classify the entry.
1523     (setq po-entry-type
1524           (if (eq (following-char) ?#)
1525               'obsolete
1526             (goto-char po-start-of-entry)
1527             (if (re-search-forward po-fuzzy-regexp po-start-of-msgctxt t)
1528                 'fuzzy
1529               (goto-char po-start-of-msgstr-block)
1530               (if (looking-at po-untranslated-regexp)
1531                   'untranslated
1532                 'translated))))
1533     ;; Put the cursor back where it was.
1534     (goto-char here)))
1535
1536 (defun po-add-attribute (name)
1537   "Add attribute NAME to the current entry, unless it is already there."
1538   (save-excursion
1539     (let ((buffer-read-only po-read-only))
1540       (goto-char po-start-of-entry)
1541       (if (re-search-forward "\n#, .*" po-start-of-msgctxt t)
1542           (save-restriction
1543             (narrow-to-region (match-beginning 0) (match-end 0))
1544             (goto-char (point-min))
1545             (if (re-search-forward (concat "\\b" name "\\b") nil t)
1546                 nil
1547               (goto-char (point-max))
1548               (insert ", " name)))
1549         (skip-chars-forward "\n")
1550         (while (eq (following-char) ?#)
1551           (forward-line 1))
1552         (insert "#, " name "\n")))))
1553
1554 (defun po-delete-attribute (name)
1555   "Delete attribute NAME from the current entry, if any."
1556   (save-excursion
1557     (let ((buffer-read-only po-read-only))
1558       (goto-char po-start-of-entry)
1559       (if (re-search-forward "\n#, .*" po-start-of-msgctxt t)
1560           (save-restriction
1561             (narrow-to-region (match-beginning 0) (match-end 0))
1562             (goto-char (point-min))
1563             (if (re-search-forward
1564                  (concat "\\(\n#, " name "$\\|, " name "$\\| " name ",\\)")
1565                  nil t)
1566                 (replace-match "" t t)))))))
1567 \f
1568 ;;; Entry positionning.
1569
1570 (defun po-say-location-depth ()
1571   "Tell how many entries in the entry location stack."
1572   (let ((depth (length po-marker-stack)))
1573     (cond ((= depth 0) (message (_"Empty location stack")))
1574           ((= depth 1) (message (_"One entry in location stack")))
1575           (t (message (_"%d entries in location stack") depth)))))
1576
1577 (defun po-push-location ()
1578   "Stack the location of the current entry, for later return."
1579   (interactive)
1580   (po-find-span-of-entry)
1581   (save-excursion
1582     (goto-char po-start-of-msgid)
1583     (setq po-marker-stack (cons (point-marker) po-marker-stack)))
1584   (po-say-location-depth))
1585
1586 (defun po-pop-location ()
1587   "Unstack a saved location, and return to the corresponding entry."
1588   (interactive)
1589   (if po-marker-stack
1590       (progn
1591         (goto-char (car po-marker-stack))
1592         (setq po-marker-stack (cdr po-marker-stack))
1593         (po-current-entry)
1594         (po-say-location-depth))
1595     (error (_"The entry location stack is empty"))))
1596
1597 (defun po-exchange-location ()
1598   "Exchange the location of the current entry with the top of stack."
1599   (interactive)
1600   (if po-marker-stack
1601       (progn
1602         (po-find-span-of-entry)
1603         (goto-char po-start-of-msgid)
1604         (let ((location (point-marker)))
1605           (goto-char (car po-marker-stack))
1606           (setq po-marker-stack (cons location (cdr po-marker-stack))))
1607         (po-current-entry)
1608         (po-say-location-depth))
1609     (error (_"The entry location stack is empty"))))
1610
1611 (defun po-current-entry ()
1612   "Display the current entry."
1613   (interactive)
1614   (po-find-span-of-entry)
1615   (po-redisplay))
1616
1617 (defun po-first-entry-with-regexp (regexp)
1618   "Display the first entry in the file which msgstr matches REGEXP."
1619   (let ((here (point)))
1620     (goto-char (point-min))
1621     (if (re-search-forward regexp nil t)
1622         (progn
1623           (goto-char (match-beginning 0))
1624           (po-current-entry))
1625       (goto-char here)
1626       (error (_"There is no such entry")))))
1627
1628 (defun po-last-entry-with-regexp (regexp)
1629   "Display the last entry in the file which msgstr matches REGEXP."
1630   (let ((here (point)))
1631     (goto-char (point-max))
1632     (if (re-search-backward regexp nil t)
1633         (po-current-entry)
1634       (goto-char here)
1635       (error (_"There is no such entry")))))
1636
1637 (defun po-next-entry-with-regexp (regexp wrap)
1638   "Display the entry following the current entry which msgstr matches REGEXP.
1639 If WRAP is not nil, the search may wrap around the buffer."
1640   (po-find-span-of-entry)
1641   (let ((here (point)))
1642     (goto-char po-end-of-entry)
1643     (if (re-search-forward regexp nil t)
1644         (progn
1645           (goto-char (match-beginning 0))
1646           (po-current-entry))
1647       (if (and wrap
1648                (progn
1649                  (goto-char (point-min))
1650                  (re-search-forward regexp po-start-of-entry t)))
1651           (progn
1652             (goto-char (match-beginning 0))
1653             (po-current-entry)
1654             (message (_"Wrapping around the buffer")))
1655         (goto-char here)
1656         (error (_"There is no such entry"))))))
1657
1658 (defun po-previous-entry-with-regexp (regexp wrap)
1659   "Redisplay the entry preceding the current entry which msgstr matches REGEXP.
1660 If WRAP is not nil, the search may wrap around the buffer."
1661   (po-find-span-of-entry)
1662   (let ((here (point)))
1663     (goto-char po-start-of-entry)
1664     (if (re-search-backward regexp nil t)
1665         (po-current-entry)
1666       (if (and wrap
1667                (progn
1668                  (goto-char (point-max))
1669                  (re-search-backward regexp po-end-of-entry t)))
1670           (progn
1671             (po-current-entry)
1672             (message (_"Wrapping around the buffer")))
1673         (goto-char here)
1674         (error (_"There is no such entry"))))))
1675
1676 ;; Any entries.
1677
1678 (defun po-first-entry ()
1679   "Display the first entry."
1680   (interactive)
1681   (po-first-entry-with-regexp po-any-msgstr-block-regexp))
1682
1683 (defun po-last-entry ()
1684   "Display the last entry."
1685   (interactive)
1686   (po-last-entry-with-regexp po-any-msgstr-block-regexp))
1687
1688 (defun po-next-entry ()
1689   "Display the entry following the current entry."
1690   (interactive)
1691   (po-next-entry-with-regexp po-any-msgstr-block-regexp nil))
1692
1693 (defun po-previous-entry ()
1694   "Display the entry preceding the current entry."
1695   (interactive)
1696   (po-previous-entry-with-regexp po-any-msgstr-block-regexp nil))
1697
1698 ;; Untranslated entries.
1699
1700 (defvar po-after-entry-regexp
1701   "\\(\\'\\|\\(#[ \t]*\\)?$\\)"
1702   "Regexp which should be true after a full msgstr string matched.")
1703
1704 (defvar po-untranslated-regexp
1705   (concat "^msgstr\\(\\[[0-9]\\]\\)?[ \t]*\"\"\n" po-after-entry-regexp)
1706   "Regexp matching a whole msgstr field, but only if active and empty.")
1707
1708 (defun po-next-untranslated-entry ()
1709   "Find the next untranslated entry, wrapping around if necessary."
1710   (interactive)
1711   (po-next-entry-with-regexp po-untranslated-regexp t))
1712
1713 (defun po-previous-untranslated-entry ()
1714   "Find the previous untranslated entry, wrapping around if necessary."
1715   (interactive)
1716   (po-previous-entry-with-regexp po-untranslated-regexp t))
1717
1718 (defun po-msgid-to-msgstr ()
1719   "Use another window to edit msgstr reinitialized with msgid."
1720   (interactive)
1721   (po-find-span-of-entry)
1722   (if (or (eq po-entry-type 'untranslated)
1723           (eq po-entry-type 'obsolete)
1724           (prog1 (y-or-n-p (_"Really lose previous translation? "))
1725                  (message "")))
1726       ;; In an entry with plural forms, use the msgid_plural string,
1727       ;; as it is more general than the msgid string.
1728       (if (po-set-msgstr-form (or (po-get-msgid_plural) (po-get-msgid)))
1729           (po-maybe-delete-previous-untranslated))))
1730
1731 ;; Obsolete entries.
1732
1733 (defvar po-obsolete-msgstr-regexp
1734   "^#~[ \t]*msgstr.*\n\\(#~[ \t]*\".*\n\\)*"
1735   "Regexp matching a whole msgstr field of an obsolete entry.")
1736
1737 (defun po-next-obsolete-entry ()
1738   "Find the next obsolete entry, wrapping around if necessary."
1739   (interactive)
1740   (po-next-entry-with-regexp po-obsolete-msgstr-regexp t))
1741
1742 (defun po-previous-obsolete-entry ()
1743   "Find the previous obsolete entry, wrapping around if necessary."
1744   (interactive)
1745   (po-previous-entry-with-regexp po-obsolete-msgstr-regexp t))
1746
1747 ;; Fuzzy entries.
1748
1749 (defvar po-fuzzy-regexp "^#, .*fuzzy"
1750   "Regexp matching the string inserted by msgmerge for translations
1751 which does not match exactly.")
1752
1753 (defun po-next-fuzzy-entry ()
1754   "Find the next fuzzy entry, wrapping around if necessary."
1755   (interactive)
1756   (po-next-entry-with-regexp po-fuzzy-regexp t))
1757
1758 (defun po-previous-fuzzy-entry ()
1759   "Find the next fuzzy entry, wrapping around if necessary."
1760   (interactive)
1761   (po-previous-entry-with-regexp po-fuzzy-regexp t))
1762
1763 (defun po-unfuzzy ()
1764   "Remove the fuzzy attribute for the current entry."
1765   (interactive)
1766   (po-find-span-of-entry)
1767   (cond ((eq po-entry-type 'fuzzy)
1768          (po-decrease-type-counter)
1769          (po-delete-attribute "fuzzy")
1770          (po-maybe-delete-previous-untranslated)
1771          (po-current-entry)
1772          (po-increase-type-counter)))
1773   (if po-auto-select-on-unfuzzy
1774       (po-auto-select-entry))
1775   (po-update-mode-line-string))
1776
1777 ;; Translated entries.
1778
1779 (defun po-next-translated-entry ()
1780   "Find the next translated entry, wrapping around if necessary."
1781   (interactive)
1782   (if (= po-translated-counter 0)
1783       (error (_"There is no such entry"))
1784     (po-next-entry-with-regexp po-any-msgstr-block-regexp t)
1785     (po-find-span-of-entry)
1786     (while (not (eq po-entry-type 'translated))
1787       (po-next-entry-with-regexp po-any-msgstr-block-regexp t)
1788       (po-find-span-of-entry))))
1789
1790 (defun po-previous-translated-entry ()
1791   "Find the previous translated entry, wrapping around if necessary."
1792   (interactive)
1793   (if (= po-translated-counter 0)
1794       (error (_"There is no such entry"))
1795     (po-previous-entry-with-regexp po-any-msgstr-block-regexp t)
1796     (po-find-span-of-entry)
1797     (while (not (eq po-entry-type 'translated))
1798       (po-previous-entry-with-regexp po-any-msgstr-block-regexp t)
1799       (po-find-span-of-entry))))
1800
1801 ;; Auto-selection feature.
1802
1803 (defun po-auto-select-entry ()
1804   "Select the next entry having the same type as the current one.
1805 If none, wrap from the beginning of the buffer with another type,
1806 going from untranslated to fuzzy, and from fuzzy to obsolete.
1807 Plain translated entries are always disregarded unless there are
1808 no entries of the other types."
1809   (interactive)
1810   (po-find-span-of-entry)
1811   (goto-char po-end-of-entry)
1812   (if (and (= po-untranslated-counter 0)
1813            (= po-fuzzy-counter 0)
1814            (= po-obsolete-counter 0))
1815       ;; All entries are plain translated.  Next entry will do, or
1816       ;; wrap around if there is none.
1817       (if (re-search-forward po-any-msgstr-block-regexp nil t)
1818           (goto-char (match-beginning 0))
1819         (goto-char (point-min)))
1820     ;; If over a translated entry, look for an untranslated one first.
1821     ;; Else, look for an entry of the same type first.
1822     (let ((goal (if (eq po-entry-type 'translated)
1823                     'untranslated
1824                   po-entry-type)))
1825       (while goal
1826         ;; Find an untranslated entry, or wrap up for a fuzzy entry.
1827         (if (eq goal 'untranslated)
1828             (if (and (> po-untranslated-counter 0)
1829                      (re-search-forward po-untranslated-regexp nil t))
1830                 (progn
1831                   (goto-char (match-beginning 0))
1832                   (setq goal nil))
1833               (goto-char (point-min))
1834               (setq goal 'fuzzy)))
1835         ;; Find a fuzzy entry, or wrap up for an obsolete entry.
1836         (if (eq goal 'fuzzy)
1837             (if (and (> po-fuzzy-counter 0)
1838                      (re-search-forward po-fuzzy-regexp nil t))
1839                 (progn
1840                   (goto-char (match-beginning 0))
1841                   (setq goal nil))
1842               (goto-char (point-min))
1843               (setq goal 'obsolete)))
1844         ;; Find an obsolete entry, or wrap up for an untranslated entry.
1845         (if (eq goal 'obsolete)
1846             (if (and (> po-obsolete-counter 0)
1847                      (re-search-forward po-obsolete-msgstr-regexp nil t))
1848                 (progn
1849                   (goto-char (match-beginning 0))
1850                   (setq goal nil))
1851               (goto-char (point-min))
1852               (setq goal 'untranslated))))))
1853   ;; Display this entry nicely.
1854   (po-current-entry))
1855 \f
1856 ;;; Killing and yanking fields.
1857
1858 (defun po-extract-unquoted (buffer start end)
1859   "Extract and return the unquoted string in BUFFER going from START to END.
1860 Crumb preceding or following the quoted string is ignored."
1861   (save-excursion
1862     (goto-char start)
1863     (search-forward "\"")
1864     (setq start (point))
1865     (goto-char end)
1866     (search-backward "\"")
1867     (setq end (point)))
1868   (po-extract-part-unquoted buffer start end))
1869
1870 (defun po-extract-part-unquoted (buffer start end)
1871   "Extract and return the unquoted string in BUFFER going from START to END.
1872 Surrounding quotes are already excluded by the position of START and END."
1873   (po-with-temp-buffer
1874    (insert-buffer-substring buffer start end)
1875    ;; Glue concatenated strings.
1876    (goto-char (point-min))
1877    (while (re-search-forward "\"[ \t]*\\\\?\n\\(#~\\)?[ \t]*\"" nil t)
1878      (replace-match "" t t))
1879    ;; Remove escaped newlines.
1880    (goto-char (point-min))
1881    (while (re-search-forward "\\\\[ \t]*\n" nil t)
1882      (replace-match "" t t))
1883    ;; Unquote individual characters.
1884    (goto-char (point-min))
1885    (while (re-search-forward "\\\\[\"abfnt\\0-7]" nil t)
1886      (cond ((eq (preceding-char) ?\") (replace-match "\"" t t))
1887            ((eq (preceding-char) ?a) (replace-match "\a" t t))
1888            ((eq (preceding-char) ?b) (replace-match "\b" t t))
1889            ((eq (preceding-char) ?f) (replace-match "\f" t t))
1890            ((eq (preceding-char) ?n) (replace-match "\n" t t))
1891            ((eq (preceding-char) ?t) (replace-match "\t" t t))
1892            ((eq (preceding-char) ?\\) (replace-match "\\" t t))
1893            (t (let ((value (- (preceding-char) ?0)))
1894                 (replace-match "" t t)
1895                 (while (looking-at "[0-7]")
1896                   (setq value (+ (* 8 value) (- (following-char) ?0)))
1897                   (replace-match "" t t))
1898                 (insert value)))))
1899    (buffer-string)))
1900
1901 (defun po-eval-requoted (form prefix obsolete)
1902   "Eval FORM, which inserts a string, and return the string fully requoted.
1903 If PREFIX, precede the result with its contents.  If OBSOLETE, comment all
1904 generated lines in the returned string.  Evaluating FORM should insert the
1905 wanted string in the buffer which is current at the time of evaluation.
1906 If FORM is itself a string, then this string is used for insertion."
1907   (po-with-temp-buffer
1908     (if (stringp form)
1909         (insert form)
1910       (push-mark)
1911       (eval form))
1912     (goto-char (point-min))
1913     (let ((multi-line (re-search-forward "[^\n]\n+[^\n]" nil t)))
1914       (goto-char (point-min))
1915       (while (re-search-forward "[\"\a\b\f\n\r\t\\]" nil t)
1916         (cond ((eq (preceding-char) ?\") (replace-match "\\\"" t t))
1917               ((eq (preceding-char) ?\a) (replace-match "\\a" t t))
1918               ((eq (preceding-char) ?\b) (replace-match "\\b" t t))
1919               ((eq (preceding-char) ?\f) (replace-match "\\f" t t))
1920               ((eq (preceding-char) ?\n)
1921                (replace-match (if (or (not multi-line) (eobp))
1922                                   "\\n"
1923                                 "\\n\"\n\"")
1924                               t t))
1925               ((eq (preceding-char) ?\r) (replace-match "\\r" t t))
1926               ((eq (preceding-char) ?\t) (replace-match "\\t" t t))
1927               ((eq (preceding-char) ?\\) (replace-match "\\\\" t t))))
1928       (goto-char (point-min))
1929       (if prefix (insert prefix " "))
1930       (insert (if multi-line "\"\"\n\"" "\""))
1931       (goto-char (point-max))
1932       (insert "\"")
1933       (if prefix (insert "\n"))
1934       (if obsolete
1935           (progn
1936             (goto-char (point-min))
1937             (while (not (eobp))
1938               (or (eq (following-char) ?\n) (insert "#~ "))
1939               (search-forward "\n"))))
1940       (buffer-string))))
1941
1942 (defun po-get-msgid ()
1943   "Extract and return the unquoted msgid string."
1944   (let ((string (po-extract-unquoted (current-buffer)
1945                                      po-start-of-msgid
1946                                      (or po-start-of-msgid_plural
1947                                          po-start-of-msgstr-block))))
1948     string))
1949
1950 (defun po-get-msgid_plural ()
1951   "Extract and return the unquoted msgid_plural string.
1952 Return nil if it is not present."
1953   (if po-start-of-msgid_plural
1954       (let ((string (po-extract-unquoted (current-buffer)
1955                                          po-start-of-msgid_plural
1956                                          po-start-of-msgstr-block)))
1957         string)
1958     nil))
1959
1960 (defun po-get-msgstr-flavor ()
1961   "Helper function to detect msgstr and msgstr[] variants.
1962 Returns one of \"msgstr\" or \"msgstr[i]\" for some i."
1963   (save-excursion
1964     (goto-char po-start-of-msgstr-form)
1965     (re-search-forward "^\\(#~[ \t]*\\)?\\(msgstr\\(\\[[0-9]\\]\\)?\\)")
1966     (match-string 2)))
1967
1968 (defun po-get-msgstr-form ()
1969   "Extract and return the unquoted msgstr string."
1970   (let ((string (po-extract-unquoted (current-buffer)
1971                                      po-start-of-msgstr-form
1972                                      po-end-of-msgstr-form)))
1973     string))
1974
1975 (defun po-set-msgid (form)
1976   "Replace the current msgid, using FORM to get a string.
1977 Evaluating FORM should insert the wanted string in the current buffer.  If
1978 FORM is itself a string, then this string is used for insertion.  The string
1979 is properly requoted before the replacement occurs.
1980
1981 Returns 'nil' if the buffer has not been modified, for if the new msgid
1982 described by FORM is merely identical to the msgid already in place."
1983   (let ((string (po-eval-requoted form "msgid" (eq po-entry-type 'obsolete))))
1984     (save-excursion
1985       (goto-char po-start-of-entry)
1986       (re-search-forward po-any-msgid-regexp po-start-of-msgstr-block)
1987       (and (not (string-equal (po-match-string 0) string))
1988            (let ((buffer-read-only po-read-only))
1989              (replace-match string t t)
1990              (goto-char po-start-of-msgid)
1991              (po-find-span-of-entry)
1992              t)))))
1993
1994 (defun po-set-msgstr-form (form)
1995   "Replace the current msgstr or msgstr[], using FORM to get a string.
1996 Evaluating FORM should insert the wanted string in the current buffer.  If
1997 FORM is itself a string, then this string is used for insertion.  The string
1998 is properly requoted before the replacement occurs.
1999
2000 Returns 'nil' if the buffer has not been modified, for if the new msgstr
2001 described by FORM is merely identical to the msgstr already in place."
2002   (let ((string (po-eval-requoted form
2003                                   (po-get-msgstr-flavor)
2004                                   (eq po-entry-type 'obsolete))))
2005     (save-excursion
2006       (goto-char po-start-of-msgstr-form)
2007       (re-search-forward po-any-msgstr-form-regexp po-end-of-msgstr-form)
2008       (and (not (string-equal (po-match-string 0) string))
2009            (let ((buffer-read-only po-read-only))
2010              (po-decrease-type-counter)
2011              (replace-match string t t)
2012              (goto-char po-start-of-msgid)
2013              (po-find-span-of-entry)
2014              (po-increase-type-counter)
2015              t)))))
2016
2017 (defun po-kill-ring-save-msgstr ()
2018   "Push the msgstr string from current entry on the kill ring."
2019   (interactive)
2020   (po-find-span-of-entry)
2021   (let ((string (po-get-msgstr-form)))
2022     (po-kill-new string)
2023     string))
2024
2025 (defun po-kill-msgstr ()
2026   "Empty the msgstr string from current entry, pushing it on the kill ring."
2027   (interactive)
2028   (po-kill-ring-save-msgstr)
2029   (if (po-set-msgstr-form "")
2030       (po-maybe-delete-previous-untranslated)))
2031
2032 (defun po-yank-msgstr ()
2033   "Replace the current msgstr string by the top of the kill ring."
2034   (interactive)
2035   (po-find-span-of-entry)
2036   (if (po-set-msgstr-form (if (eq last-command 'yank) '(yank-pop 1) '(yank)))
2037       (po-maybe-delete-previous-untranslated))
2038   (setq this-command 'yank))
2039
2040 (defun po-fade-out-entry ()
2041   "Mark an active entry as fuzzy; obsolete a fuzzy or untranslated entry;
2042 or completely delete an obsolete entry, saving its msgstr on the kill ring."
2043   (interactive)
2044   (po-find-span-of-entry)
2045
2046   (cond ((eq po-entry-type 'translated)
2047          (po-decrease-type-counter)
2048          (po-add-attribute "fuzzy")
2049          (po-current-entry)
2050          (po-increase-type-counter))
2051
2052         ((or (eq po-entry-type 'fuzzy)
2053              (eq po-entry-type 'untranslated))
2054          (if (y-or-n-p (_"Should I really obsolete this entry? "))
2055              (progn
2056                (po-decrease-type-counter)
2057                (save-excursion
2058                  (save-restriction
2059                    (narrow-to-region po-start-of-entry po-end-of-entry)
2060                    (let ((buffer-read-only po-read-only))
2061                      (goto-char (point-min))
2062                      (skip-chars-forward "\n")
2063                      (while (not (eobp))
2064                        (insert "#~ ")
2065                        (search-forward "\n")))))
2066                (po-current-entry)
2067                (po-increase-type-counter)))
2068          (message ""))
2069
2070         ((and (eq po-entry-type 'obsolete)
2071               (po-check-for-pending-edit po-start-of-msgid)
2072               (po-check-for-pending-edit po-start-of-msgstr-block))
2073          (po-decrease-type-counter)
2074          (po-update-mode-line-string)
2075          ;; TODO: Should save all msgstr forms here, not just one.
2076          (po-kill-new (po-get-msgstr-form))
2077          (let ((buffer-read-only po-read-only))
2078            (delete-region po-start-of-entry po-end-of-entry))
2079          (goto-char po-start-of-entry)
2080          (if (re-search-forward po-any-msgstr-block-regexp nil t)
2081              (goto-char (match-beginning 0))
2082            (re-search-backward po-any-msgstr-block-regexp nil t))
2083          (po-current-entry)
2084          (message ""))))
2085 \f
2086 ;;; Killing and yanking comments.
2087
2088 (defvar po-comment-regexp
2089   "^\\(#\n\\|# .*\n\\)+"
2090   "Regexp matching the whole editable comment part of an entry.")
2091
2092 (defun po-get-comment (kill-flag)
2093   "Extract and return the editable comment string, uncommented.
2094 If KILL-FLAG, then add the unquoted comment to the kill ring."
2095   (let ((buffer (current-buffer))
2096         (obsolete (eq po-entry-type 'obsolete)))
2097     (save-excursion
2098       (goto-char po-start-of-entry)
2099       (if (re-search-forward po-comment-regexp po-end-of-entry t)
2100           (po-with-temp-buffer
2101             (insert-buffer-substring buffer (match-beginning 0) (match-end 0))
2102             (goto-char (point-min))
2103             (while (not (eobp))
2104               (if (looking-at (if obsolete "#\\(\n\\| \\)" "# ?"))
2105                   (replace-match "" t t))
2106               (forward-line 1))
2107             (and kill-flag (copy-region-as-kill (point-min) (point-max)))
2108             (buffer-string))
2109         ""))))
2110
2111 (defun po-set-comment (form)
2112   "Using FORM to get a string, replace the current editable comment.
2113 Evaluating FORM should insert the wanted string in the current buffer.
2114 If FORM is itself a string, then this string is used for insertion.
2115 The string is properly recommented before the replacement occurs."
2116   (let ((obsolete (eq po-entry-type 'obsolete))
2117         string)
2118     (po-with-temp-buffer
2119       (if (stringp form)
2120           (insert form)
2121         (push-mark)
2122         (eval form))
2123       (if (not (or (bobp) (= (preceding-char) ?\n)))
2124           (insert "\n"))
2125       (goto-char (point-min))
2126       (while (not (eobp))
2127         (insert (if (= (following-char) ?\n) "#" "# "))
2128         (search-forward "\n"))
2129       (setq string (buffer-string)))
2130     (goto-char po-start-of-entry)
2131     (if (re-search-forward po-comment-regexp po-end-of-entry t)
2132         (if (not (string-equal (po-match-string 0) string))
2133             (let ((buffer-read-only po-read-only))
2134               (replace-match string t t)))
2135       (skip-chars-forward " \t\n")
2136       (let ((buffer-read-only po-read-only))
2137         (insert string))))
2138   (po-current-entry))
2139
2140 (defun po-kill-ring-save-comment ()
2141   "Push the msgstr string from current entry on the kill ring."
2142   (interactive)
2143   (po-find-span-of-entry)
2144   (po-get-comment t))
2145
2146 (defun po-kill-comment ()
2147   "Empty the msgstr string from current entry, pushing it on the kill ring."
2148   (interactive)
2149   (po-kill-ring-save-comment)
2150   (po-set-comment "")
2151   (po-redisplay))
2152
2153 (defun po-yank-comment ()
2154   "Replace the current comment string by the top of the kill ring."
2155   (interactive)
2156   (po-find-span-of-entry)
2157   (po-set-comment (if (eq last-command 'yank) '(yank-pop 1) '(yank)))
2158   (setq this-command 'yank)
2159   (po-redisplay))
2160
2161 ;;; Deleting the "previous untranslated" comment.
2162
2163 (defun po-previous-untranslated-region-for (rx)
2164   "Return the list of previous untranslated regions (at most one) for the
2165 given regular expression RX."
2166   (save-excursion
2167     (goto-char po-start-of-entry)
2168     (if (re-search-forward rx po-start-of-msgctxt t)
2169         (list (cons (copy-marker (match-beginning 0))
2170                     (copy-marker (match-end 0))))
2171       nil)))
2172
2173 (defun po-previous-untranslated-regions ()
2174   "Return the list of previous untranslated regions in the current entry."
2175   (append (po-previous-untranslated-region-for po-any-previous-msgctxt-regexp)
2176           (po-previous-untranslated-region-for po-any-previous-msgid-regexp)
2177           (po-previous-untranslated-region-for po-any-previous-msgid_plural-regexp)))
2178
2179 (defun po-delete-previous-untranslated ()
2180   "Delete the previous msgctxt, msgid, msgid_plural fields (marked as #|
2181 comments) from the current entry."
2182   (interactive)
2183   (po-find-span-of-entry)
2184   (let ((buffer-read-only po-read-only))
2185     (dolist (region (po-previous-untranslated-regions))
2186       (delete-region (car region) (cdr region))))
2187   (po-redisplay))
2188
2189 (defun po-maybe-delete-previous-untranslated ()
2190   "Delete the previous msgctxt, msgid, msgid_plural fields (marked as #|
2191 comments) from the current entry, if the user gives the permission."
2192   (po-find-span-of-entry)
2193   (let ((previous-regions (po-previous-untranslated-regions)))
2194     (if previous-regions
2195         (if (or (eq po-auto-delete-previous-msgid t)
2196                 (and (eq po-auto-delete-previous-msgid 'ask)
2197                      (let ((overlays nil))
2198                        (unwind-protect
2199                            (progn
2200                              (setq overlays
2201                                    (mapcar (function
2202                                              (lambda (region)
2203                                                (let ((overlay (po-create-overlay)))
2204                                                  (po-highlight overlay (car region) (cdr region))
2205                                                  overlay)))
2206                                            previous-regions))
2207                              ;; Scroll, to show the previous-regions.
2208                              (goto-char (car (car previous-regions)))
2209                              (prog1 (y-or-n-p (_"Delete previous msgid comments? "))
2210                                     (message "")))
2211                          (mapc 'po-dehighlight overlays)))))
2212             (let ((buffer-read-only po-read-only))
2213               (dolist (region previous-regions)
2214                 (delete-region (car region) (cdr region))))))))
2215
2216 ;;; Editing management and submode.
2217
2218 ;; In a string edit buffer, BACK-POINTER points to one of the slots of the
2219 ;; list EDITED-FIELDS kept in the PO buffer.  See its description elsewhere.
2220 ;; Reminder: slots have the form (ENTRY-MARKER EDIT-BUFFER OVERLAY-INFO).
2221
2222 (defvar po-subedit-back-pointer)
2223
2224 (defun po-clean-out-killed-edits ()
2225   "From EDITED-FIELDS, clean out any edit having a killed edit buffer."
2226   (let ((cursor po-edited-fields))
2227     (while cursor
2228       (let ((slot (car cursor)))
2229         (setq cursor (cdr cursor))
2230         (if (buffer-name (nth 1 slot))
2231             nil
2232           (let ((overlay (nth 2 slot)))
2233             (and overlay (po-dehighlight overlay)))
2234           (setq po-edited-fields (delete slot po-edited-fields)))))))
2235
2236 (defun po-check-all-pending-edits ()
2237   "Resume any pending edit.  Return nil if some remains."
2238   (po-clean-out-killed-edits)
2239   (or (null po-edited-fields)
2240       (let ((slot (car po-edited-fields)))
2241         (goto-char (nth 0 slot))
2242         (pop-to-buffer (nth 1 slot))
2243         (let ((overlay (nth 2 slot)))
2244           (and overlay (po-rehighlight overlay)))
2245         (message po-subedit-message)
2246         nil)))
2247
2248 (defun po-check-for-pending-edit (position)
2249   "Resume any pending edit at POSITION.  Return nil if such edit exists."
2250   (po-clean-out-killed-edits)
2251   (let ((marker (make-marker)))
2252     (set-marker marker position)
2253     (let ((slot (assoc marker po-edited-fields)))
2254       (if slot
2255           (progn
2256             (goto-char marker)
2257             (pop-to-buffer (nth 1 slot))
2258             (let ((overlay (nth 2 slot)))
2259               (and overlay (po-rehighlight overlay)))
2260             (message po-subedit-message)))
2261       (not slot))))
2262
2263 (defun po-edit-out-full ()
2264   "Get out of PO mode, leaving PO file buffer in fundamental mode."
2265   (interactive)
2266   (if (po-check-all-pending-edits)
2267       ;; Don't ask the user for confirmation, since he has explicitly asked
2268       ;; for it.
2269       (progn
2270         (setq buffer-read-only po-read-only)
2271         (fundamental-mode)
2272         (message (_"Type 'M-x po-mode RET' once done")))))
2273
2274 (defun po-ediff-quit ()
2275   "Quit ediff and exit `recursive-edit'."
2276   (interactive)
2277   (ediff-quit t)
2278   (exit-recursive-edit))
2279
2280 (add-hook 'ediff-keymap-setup-hook
2281           '(lambda ()
2282              (define-key ediff-mode-map "Q" 'po-ediff-quit)))
2283
2284 (defun po-ediff-buffers-exit-recursive (b1 b2 oldbuf end)
2285   "Ediff buffer B1 and B2, pop back to OLDBUF and replace the old variants.
2286 This function will delete the first two variants in OLDBUF, call
2287 `ediff-buffers' to compare both strings and replace the two variants in
2288 OLDBUF with the contents of B2.
2289 Once done kill B1 and B2.
2290
2291 For more info cf. `po-subedit-ediff'."
2292   (ediff-buffers b1 b2)
2293   (recursive-edit)
2294   (pop-to-buffer oldbuf)
2295   (delete-region (point-min) end)
2296   (insert-buffer-substring b2)
2297   (mapc 'kill-buffer `(,b1 ,b2))
2298   (display-buffer entry-buffer t))
2299
2300 (defun po-subedit-ediff ()
2301   "Edit the subedit buffer using `ediff'.
2302 `po-subedit-ediff' calls `po-ediff-buffers-exit-recursive' to edit translation
2303 variants side by side if they are actually different; if variants are equal just
2304 delete the first one.
2305
2306 `msgcat' is able to produce those variants; every variant is marked with:
2307
2308 #-#-#-#-#  file name reference  #-#-#-#-#
2309
2310 Put changes in second buffer.
2311
2312 When done with the `ediff' session press \\[exit-recursive-edit] exit to
2313 `recursive-edit', or call \\[po-ediff-quit] (`Q') in the ediff control panel."
2314   (interactive)
2315   (let* ((marker-regex "^#-#-#-#-#  \\(.*\\)  #-#-#-#-#\n")
2316          (buf1 " *po-msgstr-1") ; default if first marker is missing
2317          buf2 start-1 end-1 start-2 end-2
2318          (back-pointer po-subedit-back-pointer)
2319          (entry-marker (nth 0 back-pointer))
2320          (entry-buffer (marker-buffer entry-marker)))
2321     (goto-char (point-min))
2322     (if (looking-at marker-regex)
2323         (and (setq buf1 (match-string-no-properties 1))
2324              (forward-line 1)))
2325     (setq start-1 (point))
2326     (if (not (re-search-forward marker-regex (point-max) t))
2327         (error "Only 1 msgstr found")
2328       (setq buf2 (match-string-no-properties 1)
2329             end-1 (match-beginning 0))
2330       (let ((oldbuf (current-buffer)))
2331         (save-current-buffer
2332           (set-buffer (get-buffer-create
2333                        (generate-new-buffer-name buf1)))
2334           (setq buffer-read-only nil)
2335           (erase-buffer)
2336           (insert-buffer-substring oldbuf start-1 end-1)
2337           (setq buffer-read-only t))
2338
2339         (setq start-2 (point))
2340         (save-excursion
2341           ;; check for a third variant; if found ignore it
2342           (if (re-search-forward marker-regex (point-max) t)
2343               (setq end-2 (match-beginning 0))
2344             (setq end-2 (goto-char (1- (point-max))))))
2345         (save-current-buffer
2346           (set-buffer (get-buffer-create
2347                        (generate-new-buffer-name buf2)))
2348           (erase-buffer)
2349           (insert-buffer-substring oldbuf start-2 end-2))
2350
2351         (if (not (string-equal (buffer-substring-no-properties start-1 end-1)
2352                                (buffer-substring-no-properties start-2 end-2)))
2353             (po-ediff-buffers-exit-recursive buf1 buf2 oldbuf end-2)
2354           (message "Variants are equal; delete %s" buf1)
2355           (forward-line -1)
2356           (delete-region (point-min) (point)))))))
2357
2358 (defun po-subedit-abort ()
2359   "Exit the subedit buffer, merely discarding its contents."
2360   (interactive)
2361   (let* ((edit-buffer (current-buffer))
2362          (back-pointer po-subedit-back-pointer)
2363          (entry-marker (nth 0 back-pointer))
2364          (overlay-info (nth 2 back-pointer))
2365          (entry-buffer (marker-buffer entry-marker)))
2366     (if (null entry-buffer)
2367         (error (_"Corresponding PO buffer does not exist anymore"))
2368       (or (one-window-p) (delete-window))
2369       (switch-to-buffer entry-buffer)
2370       (goto-char entry-marker)
2371       (and overlay-info (po-dehighlight overlay-info))
2372       (kill-buffer edit-buffer)
2373       (setq po-edited-fields (delete back-pointer po-edited-fields)))))
2374
2375 (defun po-subedit-exit ()
2376   "Exit the subedit buffer, replacing the string in the PO buffer."
2377   (interactive)
2378   (goto-char (point-max))
2379   (skip-chars-backward " \t\n")
2380   (if (eq (preceding-char) ?<)
2381       (delete-region (1- (point)) (point-max)))
2382   (run-hooks 'po-subedit-exit-hook)
2383   (let ((string (buffer-string)))
2384     (po-subedit-abort)
2385     (po-find-span-of-entry)
2386     (cond ((= (point) po-start-of-msgid)
2387            (po-set-comment string)
2388            (po-redisplay))
2389           ((= (point) po-start-of-msgstr-form)
2390            (if (po-set-msgstr-form string)
2391                (progn
2392                  (po-maybe-delete-previous-untranslated)
2393                  (if (and po-auto-fuzzy-on-edit
2394                           (eq po-entry-type 'translated))
2395                      (progn
2396                        (po-decrease-type-counter)
2397                        (po-add-attribute "fuzzy")
2398                        (po-current-entry)
2399                        (po-increase-type-counter))))))
2400           (t (debug)))))
2401
2402 (defun po-edit-string (string type expand-tabs)
2403   "Prepare a pop up buffer for editing STRING, which is of a given TYPE.
2404 TYPE may be 'comment or 'msgstr.  If EXPAND-TABS, expand tabs to spaces.
2405 Run functions on po-subedit-mode-hook."
2406   (let ((marker (make-marker)))
2407     (set-marker marker (cond ((eq type 'comment) po-start-of-msgid)
2408                              ((eq type 'msgstr) po-start-of-msgstr-form)))
2409     (if (po-check-for-pending-edit marker)
2410         (let ((edit-buffer (generate-new-buffer
2411                             (concat "*" (buffer-name) "*")))
2412               (edit-coding buffer-file-coding-system)
2413               (buffer (current-buffer))
2414               overlay slot)
2415           (if (and (eq type 'msgstr) po-highlighting)
2416               ;; ;; Try showing all of msgid in the upper window while editing.
2417               ;; (goto-char (1- po-start-of-msgstr-block))
2418               ;; (recenter -1)
2419               (save-excursion
2420                 (goto-char po-start-of-entry)
2421                 (re-search-forward po-any-msgid-regexp nil t)
2422                 (let ((end (1- (match-end 0))))
2423                   (goto-char (match-beginning 0))
2424                   (re-search-forward "msgid +" nil t)
2425                   (setq overlay (po-create-overlay))
2426                   (po-highlight overlay (point) end buffer))))
2427           (setq slot (list marker edit-buffer overlay)
2428                 po-edited-fields (cons slot po-edited-fields))
2429           (pop-to-buffer edit-buffer)
2430           (text-mode)
2431           (set (make-local-variable 'po-subedit-back-pointer) slot)
2432           (set (make-local-variable 'indent-line-function)
2433                'indent-relative)
2434           (setq buffer-file-coding-system edit-coding)
2435           (setq local-abbrev-table po-mode-abbrev-table)
2436           (erase-buffer)
2437           (insert string "<")
2438           (goto-char (point-min))
2439           (and expand-tabs (setq indent-tabs-mode nil))
2440           (use-local-map po-subedit-mode-map)
2441           (if (fboundp 'easy-menu-define)
2442               (progn
2443                 (easy-menu-define po-subedit-mode-menu po-subedit-mode-map ""
2444                   po-subedit-mode-menu-layout)
2445                 (and po-XEMACS (easy-menu-add po-subedit-mode-menu))))
2446           (set-syntax-table po-subedit-mode-syntax-table)
2447           (run-hooks 'po-subedit-mode-hook)
2448           (message po-subedit-message)))))
2449
2450 (defun po-edit-comment ()
2451   "Use another window to edit the current translator comment."
2452   (interactive)
2453   (po-find-span-of-entry)
2454   (po-edit-string (po-get-comment nil) 'comment nil))
2455
2456 (defun po-edit-comment-and-ediff ()
2457   "Use `ediff' to edit the current translator comment.
2458 This function calls `po-edit-msgstr' and `po-subedit-ediff'; for more info
2459 read `po-subedit-ediff' documentation."
2460   (interactive)
2461   (po-edit-comment)
2462   (po-subedit-ediff))
2463
2464 (defun po-edit-msgstr ()
2465   "Use another window to edit the current msgstr."
2466   (interactive)
2467   (po-find-span-of-entry)
2468   (po-edit-string (if (and po-auto-edit-with-msgid
2469                            (eq po-entry-type 'untranslated))
2470                       (po-get-msgid)
2471                     (po-get-msgstr-form))
2472                   'msgstr
2473                   t))
2474
2475 (defun po-edit-msgstr-and-ediff ()
2476   "Use `ediff' to edit the current msgstr.
2477 This function calls `po-edit-msgstr' and `po-subedit-ediff'; for more info
2478 read `po-subedit-ediff' documentation."
2479   (interactive)
2480   (po-edit-msgstr)
2481   (po-subedit-ediff))
2482 \f
2483 ;;; String normalization and searching.
2484
2485 (defun po-normalize-old-style (explain)
2486   "Normalize old gettext style fields using K&R C multiline string syntax.
2487 To minibuffer messages sent while normalizing, add the EXPLAIN string."
2488   (let ((here (point-marker))
2489         (counter 0)
2490         (buffer-read-only po-read-only))
2491     (goto-char (point-min))
2492     (message (_"Normalizing %d, %s") counter explain)
2493     (while (re-search-forward
2494             "\\(^#?[ \t]*msg\\(id\\|str\\)[ \t]*\"\\|[^\" \t][ \t]*\\)\\\\\n"
2495             nil t)
2496       (if (= (% counter 10) 0)
2497           (message (_"Normalizing %d, %s") counter explain))
2498       (replace-match "\\1\"\n\"" t nil)
2499       (setq counter (1+ counter)))
2500     (goto-char here)
2501     (message (_"Normalizing %d...done") counter)))
2502
2503 (defun po-normalize-field (field explain)
2504   "Normalize FIELD of all entries.  FIELD is either the symbol msgid or msgstr.
2505 To minibuffer messages sent while normalizing, add the EXPLAIN string."
2506   (let ((here (point-marker))
2507         (counter 0))
2508     (goto-char (point-min))
2509     (while (re-search-forward po-any-msgstr-block-regexp nil t)
2510       (if (= (% counter 10) 0)
2511           (message (_"Normalizing %d, %s") counter explain))
2512       (goto-char (match-beginning 0))
2513       (po-find-span-of-entry)
2514       (cond ((eq field 'msgid) (po-set-msgid (po-get-msgid)))
2515             ((eq field 'msgstr) (po-set-msgstr-form (po-get-msgstr-form))))
2516       (goto-char po-end-of-entry)
2517       (setq counter (1+ counter)))
2518     (goto-char here)
2519     (message (_"Normalizing %d...done") counter)))
2520
2521 ;; Normalize, but the British way! :-)
2522 (defsubst po-normalise () (po-normalize))
2523
2524 (defun po-normalize ()
2525   "Normalize all entries in the PO file."
2526   (interactive)
2527   (po-normalize-old-style (_"pass 1/3"))
2528   ;; FIXME: This cannot work: t and nil are not msgid and msgstr.
2529   (po-normalize-field t (_"pass 2/3"))
2530   (po-normalize-field nil (_"pass 3/3"))
2531   ;; The last PO file entry has just been processed.
2532   (if (not (= po-end-of-entry (point-max)))
2533       (let ((buffer-read-only po-read-only))
2534         (kill-region po-end-of-entry (point-max))))
2535   ;; A bizarre format might have fooled the counters, so recompute
2536   ;; them to make sure their value is dependable.
2537   (po-compute-counters nil))
2538 \f
2539 ;;; Multiple PO files.
2540
2541 (defun po-show-auxiliary-list ()
2542   "Echo the current auxiliary list in the message area."
2543   (if po-auxiliary-list
2544       (let ((cursor po-auxiliary-cursor)
2545             string)
2546         (while cursor
2547           (setq string (concat string (if string " ") (car (car cursor)))
2548                 cursor (cdr cursor)))
2549         (setq cursor po-auxiliary-list)
2550         (while (not (eq cursor po-auxiliary-cursor))
2551           (setq string (concat string (if string " ") (car (car cursor)))
2552                 cursor (cdr cursor)))
2553         (message string))
2554     (message (_"No auxiliary files."))))
2555
2556 (defun po-consider-as-auxiliary ()
2557   "Add the current PO file to the list of auxiliary files."
2558   (interactive)
2559   (if (member (list buffer-file-name) po-auxiliary-list)
2560       nil
2561     (setq po-auxiliary-list
2562           (nconc po-auxiliary-list (list (list buffer-file-name))))
2563     (or po-auxiliary-cursor
2564         (setq po-auxiliary-cursor po-auxiliary-list)))
2565   (po-show-auxiliary-list))
2566
2567 (defun po-ignore-as-auxiliary ()
2568   "Delete the current PO file from the list of auxiliary files."
2569   (interactive)
2570   (setq po-auxiliary-list (delete (list buffer-file-name) po-auxiliary-list)
2571         po-auxiliary-cursor po-auxiliary-list)
2572   (po-show-auxiliary-list))
2573
2574 (defun po-seek-equivalent-translation (name string)
2575   "Search a PO file NAME for a 'msgid' STRING having a non-empty 'msgstr'.
2576 STRING is the full quoted msgid field, including the 'msgid' keyword.  When
2577 found, display the file over the current window, with the 'msgstr' field
2578 possibly highlighted, the cursor at start of msgid, then return 't'.
2579 Otherwise, move nothing, and just return 'nil'."
2580   (let ((current (current-buffer))
2581         (buffer (find-file-noselect name)))
2582     (set-buffer buffer)
2583     (let ((start (point))
2584           found)
2585       (goto-char (point-min))
2586       (while (and (not found) (search-forward string nil t))
2587         ;; Screen out longer 'msgid's.
2588         (if (looking-at "^msgstr ")
2589             (progn
2590               (po-find-span-of-entry)
2591               ;; Ignore an untranslated entry.
2592               (or (string-equal
2593                    (buffer-substring po-start-of-msgstr-block po-end-of-entry)
2594                    "msgstr \"\"\n")
2595                   (setq found t)))))
2596       (if found
2597           (progn
2598             (switch-to-buffer buffer)
2599             (po-find-span-of-entry)
2600             (if po-highlighting
2601                 (progn
2602                   (goto-char po-start-of-entry)
2603                   (re-search-forward po-any-msgstr-block-regexp nil t)
2604                   (let ((end (1- (match-end 0))))
2605                     (goto-char (match-beginning 0))
2606                     (re-search-forward "msgstr +" nil t)
2607                     ;; Just "borrow" the marking overlay.
2608                     (po-highlight po-marking-overlay (point) end))))
2609             (goto-char po-start-of-msgid))
2610         (goto-char start)
2611         (po-find-span-of-entry)
2612         (set-buffer current))
2613       found)))
2614
2615 (defun po-cycle-auxiliary ()
2616   "Select the next auxiliary file having an entry with same 'msgid'."
2617   (interactive)
2618   (po-find-span-of-entry)
2619   (if po-auxiliary-list
2620       (let ((string (buffer-substring po-start-of-msgid
2621                                       po-start-of-msgstr-block))
2622             (cursor po-auxiliary-cursor)
2623             found name)
2624         (while (and (not found) cursor)
2625           (setq name (car (car cursor)))
2626           (if (and (not (string-equal buffer-file-name name))
2627                    (po-seek-equivalent-translation name string))
2628               (setq found t
2629                     po-auxiliary-cursor cursor))
2630           (setq cursor (cdr cursor)))
2631         (setq cursor po-auxiliary-list)
2632         (while (and (not found) cursor)
2633           (setq name (car (car cursor)))
2634           (if (and (not (string-equal buffer-file-name name))
2635                    (po-seek-equivalent-translation name string))
2636               (setq found t
2637                     po-auxiliary-cursor cursor))
2638           (setq cursor (cdr cursor)))
2639         (or found (message (_"No other translation found")))
2640         found)))
2641
2642 (defun po-subedit-cycle-auxiliary ()
2643   "Cycle auxiliary file, but from the translation edit buffer."
2644   (interactive)
2645   (let* ((entry-marker (nth 0 po-subedit-back-pointer))
2646          (entry-buffer (marker-buffer entry-marker))
2647          (buffer (current-buffer)))
2648     (pop-to-buffer entry-buffer)
2649     (po-cycle-auxiliary)
2650     (pop-to-buffer buffer)))
2651
2652 (defun po-select-auxiliary ()
2653   "Select one of the available auxiliary files and locate an equivalent entry.
2654 If an entry having the same 'msgid' cannot be found, merely select the file
2655 without moving its cursor."
2656   (interactive)
2657   (po-find-span-of-entry)
2658   (if po-auxiliary-list
2659       (let ((string
2660               (buffer-substring po-start-of-msgid po-start-of-msgstr-block))
2661             (name (car (assoc (completing-read (_"Which auxiliary file? ")
2662                                                po-auxiliary-list nil t)
2663                               po-auxiliary-list))))
2664         (po-consider-as-auxiliary)
2665         (or (po-seek-equivalent-translation name string)
2666             (find-file name)))))
2667 \f
2668 ;;; Original program sources as context.
2669
2670 (defun po-show-source-path ()
2671   "Echo the current source search path in the message area."
2672   (if po-search-path
2673       (let ((cursor po-search-path)
2674             string)
2675         (while cursor
2676           (setq string (concat string (if string " ") (car (car cursor)))
2677                 cursor (cdr cursor)))
2678         (message string))
2679     (message (_"Empty source path."))))
2680
2681 (defun po-consider-source-path (directory)
2682   "Add a given DIRECTORY, requested interactively, to the source search path."
2683   (interactive "DDirectory for search path: ")
2684   (setq po-search-path (cons (list (if (string-match "/$" directory)
2685                                          directory
2686                                        (concat directory "/")))
2687                              po-search-path))
2688   (setq po-reference-check 0)
2689   (po-show-source-path))
2690
2691 (defun po-ignore-source-path ()
2692   "Delete a directory, selected with completion, from the source search path."
2693   (interactive)
2694   (setq po-search-path
2695         (delete (list (completing-read (_"Directory to remove? ")
2696                                        po-search-path nil t))
2697                 po-search-path))
2698   (setq po-reference-check 0)
2699   (po-show-source-path))
2700
2701 (defun po-ensure-source-references ()
2702   "Extract all references into a list, with paths resolved, if necessary."
2703   (po-find-span-of-entry)
2704   (if (= po-start-of-entry po-reference-check)
2705       nil
2706     (setq po-reference-alist nil)
2707     (save-excursion
2708       (goto-char po-start-of-entry)
2709       (if (re-search-forward "^#:" po-start-of-msgid t)
2710           (let (current name line path file)
2711             (while (looking-at "\\(\n#:\\)? *\\([^: ]*\\):\\([0-9]+\\)")
2712               (goto-char (match-end 0))
2713               (setq name (po-match-string 2)
2714                     line (po-match-string 3)
2715                     path po-search-path)
2716               (if (string-equal name "")
2717                   nil
2718                 (while (and (not (file-exists-p
2719                                   (setq file (concat (car (car path)) name))))
2720                             path)
2721                   (setq path (cdr path)))
2722                 (setq current (and path file)))
2723               (if current
2724                   (setq po-reference-alist
2725                         (cons (list (concat current ":" line)
2726                                     current
2727                                     (string-to-number line))
2728                               po-reference-alist)))))))
2729     (setq po-reference-alist (nreverse po-reference-alist)
2730           po-reference-cursor po-reference-alist
2731           po-reference-check po-start-of-entry)))
2732
2733 (defun po-show-source-context (triplet)
2734   "Show the source context given a TRIPLET which is (PROMPT FILE LINE)."
2735   (find-file-other-window (car (cdr triplet)))
2736   (goto-line (car (cdr (cdr triplet))))
2737   (other-window 1)
2738   (let ((maximum 0)
2739         position
2740         (cursor po-reference-alist))
2741     (while (not (eq triplet (car cursor)))
2742       (setq maximum (1+ maximum)
2743             cursor (cdr cursor)))
2744     (setq position (1+ maximum)
2745           po-reference-cursor cursor)
2746     (while cursor
2747       (setq maximum (1+ maximum)
2748             cursor (cdr cursor)))
2749     (message (_"Displaying %d/%d: \"%s\"") position maximum (car triplet))))
2750
2751 (defun po-cycle-source-reference ()
2752   "Display some source context for the current entry.
2753 If the command is repeated many times in a row, cycle through contexts."
2754   (interactive)
2755   (po-ensure-source-references)
2756   (if po-reference-cursor
2757       (po-show-source-context
2758        (car (if (eq last-command 'po-cycle-source-reference)
2759                 (or (cdr po-reference-cursor) po-reference-alist)
2760               po-reference-cursor)))
2761     (error (_"No resolved source references"))))
2762
2763 (defun po-select-source-reference ()
2764   "Select one of the available source contexts for the current entry."
2765   (interactive)
2766   (po-ensure-source-references)
2767   (if po-reference-alist
2768       (po-show-source-context
2769        (assoc
2770         (completing-read (_"Which source context? ") po-reference-alist nil t)
2771         po-reference-alist))
2772     (error (_"No resolved source references"))))
2773 \f
2774 ;;; String marking in program sources, through TAGS table.
2775
2776 ;; Globally defined within tags.el.
2777 (defvar tags-loop-operate)
2778 (defvar tags-loop-scan)
2779
2780 ;; Locally set in each program source buffer.
2781 (defvar po-find-string-function)
2782 (defvar po-mark-string-function)
2783
2784 ;; Dynamically set within po-tags-search for po-tags-loop-operate.
2785 (defvar po-current-po-buffer)
2786 (defvar po-current-po-keywords)
2787
2788 (defun po-tags-search (restart)
2789   "Find an unmarked translatable string through all files in tags table.
2790 Disregard some simple strings which are most probably non-translatable.
2791 With prefix argument, restart search at first file."
2792   (interactive "P")
2793   (require 'etags)
2794   ;; Ensure there is no highlighting, in case the search fails.
2795   (if po-highlighting
2796       (po-dehighlight po-marking-overlay))
2797   (setq po-string-contents nil)
2798   ;; Search for a string which might later be marked for translation.
2799   (let ((po-current-po-buffer (current-buffer))
2800         (po-current-po-keywords po-keywords))
2801     (pop-to-buffer po-string-buffer)
2802     (if (and (not restart)
2803              (eq (car tags-loop-operate) 'po-tags-loop-operate))
2804         ;; Continue last po-tags-search.
2805         (tags-loop-continue nil)
2806       ;; Start or restart po-tags-search all over.
2807       (setq tags-loop-scan '(po-tags-loop-scan)
2808             tags-loop-operate '(po-tags-loop-operate))
2809       (tags-loop-continue t))
2810     (select-window (get-buffer-window po-current-po-buffer)))
2811   (if po-string-contents
2812       (let ((window (selected-window))
2813             (buffer po-string-buffer)
2814             (start po-string-start)
2815             (end po-string-end))
2816         ;; Try to fit the string in the displayed part of its window.
2817         (select-window (get-buffer-window buffer))
2818         (goto-char start)
2819         (or (pos-visible-in-window-p start)
2820             (recenter '(nil)))
2821         (if (pos-visible-in-window-p end)
2822             (goto-char end)
2823           (goto-char end)
2824           (recenter -1))
2825         (select-window window)
2826         ;; Highlight the string as found.
2827         (and po-highlighting
2828              (po-highlight po-marking-overlay start end buffer)))))
2829
2830 (defun po-tags-loop-scan ()
2831   "Decide if the current buffer is still interesting for PO mode strings."
2832   ;; We have little choice, here.  The major mode is needed to dispatch to the
2833   ;; proper scanner, so we declare all files as interesting, to force Emacs
2834   ;; tags module to revisit files fully.  po-tags-loop-operate sets point at
2835   ;; end of buffer when it is done with a file.
2836   (not (eobp)))
2837
2838 (defun po-tags-loop-operate ()
2839   "Find an acceptable tag in the current buffer, according to mode.
2840 Disregard some simple strings which are most probably non-translatable."
2841   (po-preset-string-functions)
2842   (let ((continue t)
2843         data)
2844     (while continue
2845       (setq data (apply po-find-string-function po-current-po-keywords nil))
2846       (if data
2847           ;; Push the string just found into a work buffer for study.
2848           (po-with-temp-buffer
2849            (insert (nth 0 data))
2850            (goto-char (point-min))
2851            ;; Accept if at least three letters in a row.
2852            (if (re-search-forward "[A-Za-z][A-Za-z][A-Za-z]" nil t)
2853                (setq continue nil)
2854              ;; Disregard if single letters or no letters at all.
2855              (if (re-search-forward "[A-Za-z][A-Za-z]" nil t)
2856                  ;; Here, we have two letters in a row, but never more.
2857                  ;; Accept only if more letters than punctuations.
2858                  (let ((total (buffer-size)))
2859                    (goto-char (point-min))
2860                    (while (re-search-forward "[A-Za-z]+" nil t)
2861                      (replace-match "" t t))
2862                    (if (< (* 2 (buffer-size)) total)
2863                        (setq continue nil))))))
2864         ;; No string left in this buffer.
2865         (setq continue nil)))
2866     (if data
2867         ;; Save information for marking functions.
2868         (let ((buffer (current-buffer)))
2869           (save-excursion
2870             (set-buffer po-current-po-buffer)
2871             (setq po-string-contents (nth 0 data)
2872                   po-string-buffer buffer
2873                   po-string-start (nth 1 data)
2874                   po-string-end (nth 2 data))))
2875       (goto-char (point-max)))
2876     ;; If nothing was found, trigger scanning of next file.
2877     (not data)))
2878
2879 (defun po-mark-found-string (keyword)
2880   "Mark last found string in program sources as translatable, using KEYWORD."
2881   (if (not po-string-contents)
2882     (error (_"No such string")))
2883   (and po-highlighting (po-dehighlight po-marking-overlay))
2884   (let ((contents po-string-contents)
2885         (buffer po-string-buffer)
2886         (start po-string-start)
2887         (end po-string-end)
2888         line string)
2889     ;; Mark string in program sources.
2890     (save-excursion
2891       (set-buffer buffer)
2892       (setq line (count-lines (point-min) start))
2893       (apply po-mark-string-function start end keyword nil))
2894     ;; Add PO file entry.
2895     (let ((buffer-read-only po-read-only))
2896       (goto-char (point-max))
2897       (insert "\n" (format "#: %s:%d\n"
2898                            (buffer-file-name po-string-buffer)
2899                            line))
2900       (save-excursion
2901         (insert (po-eval-requoted contents "msgid" nil) "msgstr \"\"\n"))
2902       (setq po-untranslated-counter (1+ po-untranslated-counter))
2903       (po-update-mode-line-string))
2904     (setq po-string-contents nil)))
2905
2906 (defun po-mark-translatable ()
2907   "Mark last found string in program sources as translatable, using '_'."
2908   (interactive)
2909   (po-mark-found-string "_"))
2910
2911 (defun po-select-mark-and-mark (arg)
2912   "Mark last found string in program sources as translatable, ask for keywoard,
2913 using completion.  With prefix argument, just ask the name of a preferred
2914 keyword for subsequent commands, also added to possible completions."
2915   (interactive "P")
2916   (if arg
2917       (let ((keyword (list (read-from-minibuffer (_"Keyword: ")))))
2918         (setq po-keywords (cons keyword (delete keyword po-keywords))))
2919     (or po-string-contents (error (_"No such string")))
2920     (let* ((default (car (car po-keywords)))
2921            (keyword (completing-read (format (_"Mark with keywoard? [%s] ")
2922                                              default)
2923                                      po-keywords nil t )))
2924       (if (string-equal keyword "") (setq keyword default))
2925       (po-mark-found-string keyword))))
2926 \f
2927 ;;; Unknown mode specifics.
2928
2929 (defun po-preset-string-functions ()
2930   "Preset FIND-STRING-FUNCTION and MARK-STRING-FUNCTION according to mode.
2931 These variables are locally set in source buffer only when not already bound."
2932   (let ((pair (cond ((string-equal mode-name "AWK")
2933                      '(po-find-awk-string . po-mark-awk-string))
2934                     ((member mode-name '("C" "C++"))
2935                      '(po-find-c-string . po-mark-c-string))
2936                     ((string-equal mode-name "Emacs-Lisp")
2937                      '(po-find-emacs-lisp-string . po-mark-emacs-lisp-string))
2938                     ((string-equal mode-name "Python")
2939                      '(po-find-python-string . po-mark-python-string))
2940                     ((and (string-equal mode-name "Shell-script")
2941                           (string-equal mode-line-process "[bash]"))
2942                      '(po-find-bash-string . po-mark-bash-string))
2943                     (t '(po-find-unknown-string . po-mark-unknown-string)))))
2944     (or (boundp 'po-find-string-function)
2945         (set (make-local-variable 'po-find-string-function) (car pair)))
2946     (or (boundp 'po-mark-string-function)
2947         (set (make-local-variable 'po-mark-string-function) (cdr pair)))))
2948
2949 (defun po-find-unknown-string (keywords)
2950   "Dummy function to skip over a file, finding no string in it."
2951   nil)
2952
2953 (defun po-mark-unknown-string (start end keyword)
2954   "Dummy function to mark a given string.  May not be called."
2955   (error (_"Dummy function called")))
2956 \f
2957 ;;; Awk mode specifics.
2958
2959 (defun po-find-awk-string (keywords)
2960   "Find the next Awk string, excluding those marked by any of KEYWORDS.
2961 Return (CONTENTS START END) for the found string, or nil if none found."
2962   (let (start end)
2963     (while (and (not start)
2964                 (re-search-forward "[#/\"]" nil t))
2965       (cond ((= (preceding-char) ?#)
2966              ;; Disregard comments.
2967              (or (search-forward "\n" nil t)
2968                  (goto-char (point-max))))
2969             ((= (preceding-char) ?/)
2970              ;; Skip regular expressions.
2971              (while (not (= (following-char) ?/))
2972                (skip-chars-forward "^/\\\\")
2973                (if (= (following-char) ?\\) (forward-char 2)))
2974              (forward-char 1))
2975             ;; Else find the end of the string.
2976             (t (setq start (1- (point)))
2977                (while (not (= (following-char) ?\"))
2978                  (skip-chars-forward "^\"\\\\")
2979                  (if (= (following-char) ?\\) (forward-char 2)))
2980                (forward-char 1)
2981                (setq end (point))
2982                ;; Check before string either for underline, or for keyword
2983                ;; and opening parenthesis.
2984                (save-excursion
2985                  (goto-char start)
2986                  (cond ((= (preceding-char) ?_)
2987                         ;; Disregard already marked strings.
2988                         (setq start nil
2989                               end nil))
2990                        ((= (preceding-char) ?\()
2991                         (backward-char 1)
2992                         (let ((end-keyword (point)))
2993                           (skip-chars-backward "_A-Za-z0-9")
2994                           (if (member (list (po-buffer-substring
2995                                              (point) end-keyword))
2996                                       keywords)
2997                               ;; Disregard already marked strings.
2998                               (setq start nil
2999                                     end nil)))))))))
3000     (and start end
3001          (list (po-extract-unquoted (current-buffer) start end) start end))))
3002
3003 (defun po-mark-awk-string (start end keyword)
3004   "Mark the Awk string, from START to END, with KEYWORD.
3005 Leave point after marked string."
3006   (if (string-equal keyword "_")
3007       (progn
3008         (goto-char start)
3009         (insert "_")
3010         (goto-char (1+ end)))
3011     (goto-char end)
3012     (insert ")")
3013     (save-excursion
3014       (goto-char start)
3015       (insert keyword "("))))
3016 \f
3017 ;;; Bash mode specifics.
3018
3019 (defun po-find-bash-string (keywords)
3020   "Find the next unmarked Bash string.  KEYWORDS are merely ignored.
3021 Return (CONTENTS START END) for the found string, or nil if none found."
3022   (let (start end)
3023     (while (and (not start)
3024                 (re-search-forward "[#'\"]" nil t))
3025       (cond ((= (preceding-char) ?#)
3026              ;; Disregard comments.
3027              (or (search-forward "\n" nil t)
3028                  (goto-char (point-max))))
3029             ((= (preceding-char) ?')
3030              ;; Skip single quoted strings.
3031              (while (not (= (following-char) ?'))
3032                (skip-chars-forward "^'\\\\")
3033                (if (= (following-char) ?\\) (forward-char 2)))
3034              (forward-char 1))
3035             ;; Else find the end of the double quoted string.
3036             (t (setq start (1- (point)))
3037                (while (not (= (following-char) ?\"))
3038                  (skip-chars-forward "^\"\\\\")
3039                  (if (= (following-char) ?\\) (forward-char 2)))
3040                (forward-char 1)
3041                (setq end (point))
3042                ;; Check before string for dollar sign.
3043                (save-excursion
3044                  (goto-char start)
3045                  (if (= (preceding-char) ?$)
3046                      ;; Disregard already marked strings.
3047                      (setq start nil
3048                            end nil))))))
3049     (and start end
3050          (list (po-extract-unquoted (current-buffer) start end) start end))))
3051
3052 (defun po-mark-bash-string (start end keyword)
3053   "Mark the Bash string, from START to END, with '$'.  KEYWORD is ignored.
3054 Leave point after marked string."
3055   (goto-char start)
3056   (insert "$")
3057   (goto-char (1+ end)))
3058 \f
3059 ;;; C or C++ mode specifics.
3060
3061 ;;; A few long string cases (submitted by Ben Pfaff).
3062
3063 ;; #define string "This is a long string " \
3064 ;; "that is continued across several lines " \
3065 ;; "in a macro in order to test \\ quoting\\" \
3066 ;; "\\ with goofy strings.\\"
3067
3068 ;; char *x = "This is just an ordinary string "
3069 ;; "continued across several lines without needing "
3070 ;; "to use \\ characters at end-of-line.";
3071
3072 ;; char *y = "Here is a string continued across \
3073 ;; several lines in the manner that was sanctioned \
3074 ;; in K&R C compilers and still works today, \
3075 ;; even though the method used above is more esthetic.";
3076
3077 ;;; End of long string cases.
3078
3079 (defun po-find-c-string (keywords)
3080   "Find the next C string, excluding those marked by any of KEYWORDS.
3081 Returns (CONTENTS START END) for the found string, or nil if none found."
3082   (let (start end)
3083     (while (and (not start)
3084                 (re-search-forward "\\([\"']\\|/\\*\\|//\\)" nil t))
3085       (cond ((= (preceding-char) ?*)
3086              ;; Disregard comments.
3087              (search-forward "*/"))
3088             ((= (preceding-char) ?/)
3089              ;; Disregard C++ comments.
3090              (end-of-line)
3091              (forward-char 1))
3092             ((= (preceding-char) ?\')
3093              ;; Disregard character constants.
3094              (forward-char (if (= (following-char) ?\\) 3 2)))
3095             ((save-excursion
3096                (beginning-of-line)
3097                (looking-at "^# *\\(include\\|line\\)"))
3098              ;; Disregard lines being #include or #line directives.
3099              (end-of-line))
3100             ;; Else, find the end of the (possibly concatenated) string.
3101             (t (setq start (1- (point))
3102                      end nil)
3103                (while (not end)
3104                  (cond ((= (following-char) ?\")
3105                         (if (looking-at "\"[ \t\n\\\\]*\"")
3106                             (goto-char (match-end 0))
3107                           (forward-char 1)
3108                           (setq end (point))))
3109                        ((= (following-char) ?\\) (forward-char 2))
3110                        (t (skip-chars-forward "^\"\\\\"))))
3111                ;; Check before string for keyword and opening parenthesis.
3112                (goto-char start)
3113                (skip-chars-backward " \n\t")
3114                (if (= (preceding-char) ?\()
3115                    (progn
3116                      (backward-char 1)
3117                      (skip-chars-backward " \n\t")
3118                      (let ((end-keyword (point)))
3119                        (skip-chars-backward "_A-Za-z0-9")
3120                        (if (member (list (po-buffer-substring (point)
3121                                                               end-keyword))
3122                                    keywords)
3123                            ;; Disregard already marked strings.
3124                            (progn
3125                              (goto-char end)
3126                              (setq start nil
3127                                    end nil))
3128                          ;; String found.  Prepare to resume search.
3129                          (goto-char end))))
3130                  ;; String found.  Prepare to resume search.
3131                  (goto-char end)))))
3132     ;; Return the found string, if any.
3133     (and start end
3134          (list (po-extract-unquoted (current-buffer) start end) start end))))
3135
3136 (defun po-mark-c-string (start end keyword)
3137   "Mark the C string, from START to END, with KEYWORD.
3138 Leave point after marked string."
3139   (goto-char end)
3140   (insert ")")
3141   (save-excursion
3142     (goto-char start)
3143     (insert keyword)
3144     (or (string-equal keyword "_") (insert " "))
3145     (insert "(")))
3146 \f
3147 ;;; Emacs LISP mode specifics.
3148
3149 (defun po-find-emacs-lisp-string (keywords)
3150   "Find the next Emacs LISP string, excluding those marked by any of KEYWORDS.
3151 Returns (CONTENTS START END) for the found string, or nil if none found."
3152   (let (start end)
3153     (while (and (not start)
3154                 (re-search-forward "[;\"?]" nil t))
3155       (cond ((= (preceding-char) ?\;)
3156              ;; Disregard comments.
3157              (search-forward "\n"))
3158             ((= (preceding-char) ?\?)
3159              ;; Disregard character constants.
3160              (forward-char (if (= (following-char) ?\\) 2 1)))
3161             ;; Else, find the end of the string.
3162             (t (setq start (1- (point)))
3163                (while (not (= (following-char) ?\"))
3164                  (skip-chars-forward "^\"\\\\")
3165                  (if (= (following-char) ?\\) (forward-char 2)))
3166                (forward-char 1)
3167                (setq end (point))
3168                ;; Check before string for keyword and opening parenthesis.
3169                (goto-char start)
3170                (skip-chars-backward " \n\t")
3171                (let ((end-keyword (point)))
3172                  (skip-chars-backward "-_A-Za-z0-9")
3173                  (if (and (= (preceding-char) ?\()
3174                           (member (list (po-buffer-substring (point)
3175                                                              end-keyword))
3176                                   keywords))
3177                      ;; Disregard already marked strings.
3178                      (progn
3179                        (goto-char end)
3180                        (setq start nil
3181                              end nil)))))))
3182     ;; Return the found string, if any.
3183     (and start end
3184          (list (po-extract-unquoted (current-buffer) start end) start end))))
3185
3186 (defun po-mark-emacs-lisp-string (start end keyword)
3187   "Mark the Emacs LISP string, from START to END, with KEYWORD.
3188 Leave point after marked string."
3189   (goto-char end)
3190   (insert ")")
3191   (save-excursion
3192     (goto-char start)
3193     (insert "(" keyword)
3194     (or (string-equal keyword "_") (insert " "))))
3195 \f
3196 ;;; Python mode specifics.
3197
3198 (defun po-find-python-string (keywords)
3199   "Find the next Python string, excluding those marked by any of KEYWORDS.
3200 Also disregard strings when preceded by an empty string of the other type.
3201 Returns (CONTENTS START END) for the found string, or nil if none found."
3202   (let (contents start end)
3203     (while (and (not contents)
3204                 (re-search-forward "[#\"']" nil t))
3205       (forward-char -1)
3206       (cond ((= (following-char) ?\#)
3207              ;; Disregard comments.
3208              (search-forward "\n"))
3209             ((looking-at "\"\"'")
3210              ;; Quintuple-quoted string
3211              (po-skip-over-python-string))
3212             ((looking-at "''\"")
3213              ;; Quadruple-quoted string
3214              (po-skip-over-python-string))
3215             (t
3216              ;; Simple-, double-, triple- or sextuple-quoted string.
3217              (if (memq (preceding-char) '(?r ?R))
3218                  (forward-char -1))
3219              (setq start (point)
3220                    contents (po-skip-over-python-string)
3221                    end (point))
3222              (goto-char start)
3223              (skip-chars-backward " \n\t")
3224              (cond ((= (preceding-char) ?\[)
3225                     ;; Disregard a string used as a dictionary index.
3226                     (setq contents nil))
3227                    ((= (preceding-char) ?\()
3228                     ;; Isolate the keyword which precedes string.
3229                     (backward-char 1)
3230                     (skip-chars-backward " \n\t")
3231                     (let ((end-keyword (point)))
3232                       (skip-chars-backward "_A-Za-z0-9")
3233                       (if (member (list (po-buffer-substring (point)
3234                                                              end-keyword))
3235                                   keywords)
3236                           ;; Disregard already marked strings.
3237                           (setq contents nil)))))
3238              (goto-char end))))
3239     ;; Return the found string, if any.
3240     (and contents (list contents start end))))
3241
3242 (defun po-skip-over-python-string ()
3243   "Skip over a Python string, possibly made up of many concatenated parts.
3244 Leave point after string.  Return unquoted overall string contents."
3245   (let ((continue t)
3246         (contents "")
3247         raw start end resume)
3248     (while continue
3249       (skip-chars-forward " \t\n")      ; whitespace
3250       (cond ((= (following-char) ?#)    ; comment
3251              (setq start nil)
3252              (search-forward "\n"))
3253             ((looking-at "\\\n")        ; escaped newline
3254              (setq start nil)
3255              (forward-char 2))
3256             ((looking-at "[rR]?\"\"\"") ; sextuple-quoted string
3257              (setq raw (memq (following-char) '(?r ?R))
3258                    start (match-end 0))
3259              (goto-char start)
3260              (search-forward "\"\"\"")
3261              (setq resume (point)
3262                    end (- resume 3)))
3263             ((looking-at "[rr]?'''")    ; triple-quoted string
3264              (setq raw (memq (following-char) '(?r ?R))
3265                    start (match-end 0))
3266              (goto-char start)
3267              (search-forward "'''")
3268              (setq resume (point)
3269                    end (- resume 3)))
3270             ((looking-at "[rR]?\"")     ; double-quoted string
3271              (setq raw (memq (following-char) '(?r ?R))
3272                    start (match-end 0))
3273              (goto-char start)
3274              (while (not (memq (following-char) '(0 ?\")))
3275                (skip-chars-forward "^\"\\\\")
3276                (if (= (following-char) ?\\) (forward-char 2)))
3277              (if (eobp)
3278                  (setq contents nil
3279                        start nil)
3280                (setq end (point))
3281                (forward-char 1))
3282              (setq resume (point)))
3283             ((looking-at "[rR]?'")      ; single-quoted string
3284              (setq raw (memq (following-char) '(?r ?R))
3285                    start (match-end 0))
3286              (goto-char start)
3287              (while (not (memq (following-char) '(0 ?\')))
3288                (skip-chars-forward "^'\\\\")
3289                (if (= (following-char) ?\\) (forward-char 2)))
3290              (if (eobp)
3291                  (setq contents nil
3292                        start nil)
3293                (setq end (point))
3294                (forward-char 1))
3295              (setq resume (point)))
3296             (t                          ; no string anymore
3297              (setq start nil
3298                    continue nil)))
3299       (if start
3300           (setq contents (concat contents
3301                                  (if raw
3302                                      (buffer-substring start end)
3303                                    (po-extract-part-unquoted (current-buffer)
3304                                                              start end))))))
3305     (goto-char resume)
3306     contents))
3307
3308 (defun po-mark-python-string (start end keyword)
3309   "Mark the Python string, from START to END, with KEYWORD.
3310 If KEYWORD is '.', prefix the string with an empty string of the other type.
3311 Leave point after marked string."
3312   (cond ((string-equal keyword ".")
3313          (goto-char end)
3314          (save-excursion
3315            (goto-char start)
3316            (insert (cond ((= (following-char) ?\') "\"\"")
3317                          ((= (following-char) ?\") "''")
3318                          (t "??")))))
3319         (t (goto-char end)
3320            (insert ")")
3321            (save-excursion
3322              (goto-char start)
3323              (insert keyword "(")))))
3324 \f
3325 ;;; Miscellaneous features.
3326
3327 (defun po-help ()
3328   "Provide an help window for PO mode."
3329   (interactive)
3330   (po-with-temp-buffer
3331    (insert po-help-display-string)
3332    (goto-char (point-min))
3333    (save-window-excursion
3334      (switch-to-buffer (current-buffer))
3335      (delete-other-windows)
3336      (message (_"Type any character to continue"))
3337      (po-read-event))))
3338
3339 (defun po-undo ()
3340   "Undo the last change to the PO file."
3341   (interactive)
3342   (let ((buffer-read-only po-read-only))
3343     (undo))
3344   (po-compute-counters nil))
3345
3346 (defun po-statistics ()
3347   "Say how many entries in each category, and the current position."
3348   (interactive)
3349   (po-compute-counters t))
3350
3351 (defun po-validate ()
3352   "Use 'msgfmt' for validating the current PO file contents."
3353   (interactive)
3354   ;; The 'compile' subsystem is autoloaded through a call to (compile ...).
3355   ;; We need to initialize it outside of any binding. Without this statement,
3356   ;; all defcustoms and defvars of compile.el would be undone when the let*
3357   ;; terminates.
3358   (require 'compile)
3359   (let* ((dev-null
3360           (cond ((boundp 'null-device) null-device) ; since Emacs 20.3
3361                 ((memq system-type '(windows-nt windows-95)) "NUL")
3362                 (t "/dev/null")))
3363          (output
3364           (if po-keep-mo-file
3365               (concat (file-name-sans-extension buffer-file-name) ".mo")
3366             dev-null))
3367          (compilation-buffer-name-function
3368           (function (lambda (mode-name)
3369                       (concat "*" mode-name " validation*"))))
3370          (compile-command (concat po-msgfmt-program
3371                                   " --statistics -c -v -o "
3372                                   (shell-quote-argument output) " "
3373                                   (shell-quote-argument buffer-file-name))))
3374     (po-msgfmt-version-check)
3375     (compile compile-command)))
3376
3377 (defvar po-msgfmt-version-checked nil)
3378 (defun po-msgfmt-version-check ()
3379   "'msgfmt' from GNU gettext 0.10.36 or greater is required."
3380   (po-with-temp-buffer
3381     (or
3382      ;; Don't bother checking again.
3383      po-msgfmt-version-checked
3384
3385      (and
3386       ;; Make sure 'msgfmt' is available.
3387       (condition-case nil
3388           (call-process po-msgfmt-program
3389                         nil t nil "--verbose" "--version")
3390         (file-error nil))
3391
3392       ;; Make sure there's a version number in the output:
3393       ;; 0.11 or 0.10.36 or 0.19.5.1 or 0.11-pre1 or 0.16.2-pre1
3394       (progn (goto-char (point-min))
3395              (or (looking-at ".* \\([0-9]+\\)\\.\\([0-9]+\\)$")
3396                  (looking-at ".* \\([0-9]+\\)\\.\\([0-9]+\\)\\.\\([0-9]+\\)$")
3397                  (looking-at ".* \\([0-9]+\\)\\.\\([0-9]+\\)\\.\\([0-9]+\\)\\.\\([0-9]+\\)$")
3398                  (looking-at ".* \\([0-9]+\\)\\.\\([0-9]+\\)[-_A-Za-z0-9]+$")
3399                  (looking-at ".* \\([0-9]+\\)\\.\\([0-9]+\\)\\.\\([0-9]+\\)[-_A-Za-z0-9]+$")))
3400
3401       ;; Make sure the version is recent enough.
3402       (>= (string-to-number
3403            (format "%d%03d%03d"
3404                    (string-to-number (match-string 1))
3405                    (string-to-number (match-string 2))
3406                    (string-to-number (or (match-string 3) "0"))))
3407           010036)
3408
3409       ;; Remember the outcome.
3410       (setq po-msgfmt-version-checked t))
3411
3412      (error (_"'msgfmt' from GNU gettext 0.10.36 or greater is required")))))
3413
3414 (defun po-guess-archive-name ()
3415   "Return the ideal file name for this PO file in the central archives."
3416   (let ((filename (file-name-nondirectory buffer-file-name))
3417         start-of-header end-of-header package version team)
3418     (save-excursion
3419       ;; Find the PO file header entry.
3420       (goto-char (point-min))
3421       (re-search-forward po-any-msgstr-block-regexp)
3422       (setq start-of-header (match-beginning 0)
3423             end-of-header (match-end 0))
3424       ;; Get the package and version.
3425       (goto-char start-of-header)
3426       (if (re-search-forward "\n\
3427 \"Project-Id-Version: \\(GNU \\|Free \\)?\\([^\n ]+\\) \\([^\n ]+\\)\\\\n\"$"
3428            end-of-header t)
3429           (setq package (po-match-string 2)
3430                 version (po-match-string 3)))
3431       (if (or (not package) (string-equal package "PACKAGE")
3432               (not version) (string-equal version "VERSION"))
3433           (error (_"Project-Id-Version field does not have a proper value")))
3434       ;; File name version and Project-Id-Version must match
3435       (cond (;; A `filename' w/o package and version info at all
3436              (string-match "^[^\\.]*\\.po\\'" filename))
3437             (;; TP Robot compatible `filename': PACKAGE-VERSION.LL.po
3438              (string-match (concat (regexp-quote package)
3439                                    "-\\(.*\\)\\.[^\\.]*\\.po\\'") filename)
3440              (if (not (equal version (po-match-string 1 filename)))
3441                  (error (_"\
3442 Version mismatch: file name: %s; header: %s.\n\
3443 Adjust Project-Id-Version field to match file name and try again")
3444                         (po-match-string 1 filename) version))))
3445       ;; Get the team.
3446       (if (stringp po-team-name-to-code)
3447           (setq team po-team-name-to-code)
3448         (goto-char start-of-header)
3449         (if (re-search-forward "\n\
3450 \"Language-Team: \\([^ ].*[^ ]\\) <.+@.+>\\\\n\"$"
3451                                end-of-header t)
3452             (let ((name (po-match-string 1)))
3453               (if name
3454                   (let ((pair (assoc name po-team-name-to-code)))
3455                     (if pair
3456                         (setq team (cdr pair))
3457                       (setq team (read-string (format "\
3458 Team name '%s' unknown.  What is the team code? "
3459                                                       name)))))))))
3460       (if (or (not team) (string-equal team "LL"))
3461           (error (_"Language-Team field does not have a proper value")))
3462       ;; Compose the name.
3463       (concat package "-" version "." team ".po"))))
3464
3465 (defun po-guess-team-address ()
3466   "Return the team address related to this PO file."
3467   (let (team)
3468     (save-excursion
3469       (goto-char (point-min))
3470       (re-search-forward po-any-msgstr-block-regexp)
3471       (goto-char (match-beginning 0))
3472       (if (re-search-forward
3473            "\n\"Language-Team: +\\(.*<\\(.*\\)@.*>\\)\\\\n\"$"
3474            (match-end 0) t)
3475           (setq team (po-match-string 2)))
3476       (if (or (not team) (string-equal team "LL"))
3477           (error (_"Language-Team field does not have a proper value")))
3478       (po-match-string 1))))
3479
3480 (defun po-send-mail ()
3481   "Start composing a letter, possibly including the current PO file."
3482   (interactive)
3483   (let* ((team-flag (y-or-n-p
3484                      (_"\
3485 Write to your team?  ('n' if writing to the Translation Project robot) ")))
3486          (address (if team-flag
3487                       (po-guess-team-address)
3488                     po-translation-project-address)))
3489     (if (not (y-or-n-p (_"Include current PO file in mail? ")))
3490         (apply po-compose-mail-function address
3491                (read-string (_"Subject? ")) nil)
3492       (if (buffer-modified-p)
3493           (error (_"The file is not even saved, you did not validate it.")))
3494       (if (and (y-or-n-p (_"You validated ('V') this file, didn't you? "))
3495                (or (zerop po-untranslated-counter)
3496                    (y-or-n-p
3497                     (format (_"%d entries are untranslated, include anyway? ")
3498                             po-untranslated-counter)))
3499                (or (zerop po-fuzzy-counter)
3500                    (y-or-n-p
3501                     (format (_"%d entries are still fuzzy, include anyway? ")
3502                             po-fuzzy-counter)))
3503                (or (zerop po-obsolete-counter)
3504                    (y-or-n-p
3505                     (format (_"%d entries are obsolete, include anyway? ")
3506                             po-obsolete-counter))))
3507           (let ((buffer (current-buffer))
3508                 (name (po-guess-archive-name))
3509                 (transient-mark-mode nil)
3510                 (coding-system-for-read buffer-file-coding-system)
3511                 (coding-system-for-write buffer-file-coding-system))
3512             (apply po-compose-mail-function address
3513                    (if team-flag
3514                        (read-string (_"Subject? "))
3515                      (format "%s %s" po-translation-project-mail-label name))
3516                    nil)
3517             (goto-char (point-min))
3518             (re-search-forward
3519              (concat "^" (regexp-quote mail-header-separator) "\n"))
3520             (save-excursion
3521               (insert-buffer-substring buffer)
3522               (shell-command-on-region
3523                (region-beginning) (region-end)
3524                (concat po-gzip-uuencode-command " " name ".gz") t t))))))
3525   (message ""))
3526
3527 (defun po-confirm-and-quit ()
3528   "Confirm if quit should be attempted and then, do it.
3529 This is a failsafe.  Confirmation is asked if only the real quit would not."
3530   (interactive)
3531   (if (po-check-all-pending-edits)
3532       (progn
3533         (if (or (buffer-modified-p)
3534                 (> po-untranslated-counter 0)
3535                 (> po-fuzzy-counter 0)
3536                 (> po-obsolete-counter 0)
3537                 (y-or-n-p (_"Really quit editing this PO file? ")))
3538             (po-quit))
3539         (message ""))))
3540
3541 (defun po-quit ()
3542   "Save the PO file and kill buffer.
3543 However, offer validation if appropriate and ask confirmation if untranslated
3544 strings remain."
3545   (interactive)
3546   (if (po-check-all-pending-edits)
3547       (let ((quit t))
3548         ;; Offer validation of newly modified entries.
3549         (if (and (buffer-modified-p)
3550                  (not (y-or-n-p
3551                        (_"File was modified; skip validation step? "))))
3552             (progn
3553               (message "")
3554               (po-validate)
3555               ;; If we knew that the validation was all successful, we should
3556               ;; just quit.  But since we do not know yet, as the validation
3557               ;; might be asynchronous with PO mode commands, the safest is to
3558               ;; stay within PO mode, even if this implies that another
3559               ;; 'po-quit' command will be later required to exit for true.
3560               (setq quit nil)))
3561         ;; Offer to work on untranslated entries.
3562         (if (and quit
3563                  (or (> po-untranslated-counter 0)
3564                      (> po-fuzzy-counter 0)
3565                      (> po-obsolete-counter 0))
3566                  (not (y-or-n-p
3567                        (_"Unprocessed entries remain; quit anyway? "))))
3568             (progn
3569               (setq quit nil)
3570               (po-auto-select-entry)))
3571         ;; Clear message area.
3572         (message "")
3573         ;; Or else, kill buffers and quit for true.
3574         (if quit
3575             (progn
3576               (save-buffer)
3577               (kill-buffer (current-buffer)))))))
3578
3579 (provide 'po-mode)
3580
3581 ;; Hey Emacs!
3582 ;; Local Variables:
3583 ;; indent-tabs-mode: nil
3584 ;; coding: utf-8
3585 ;; End:
3586
3587 ;;; po-mode.el ends here