~ubuntu-branches/ubuntu/trusty/gettext/trusty

« back to all changes in this revision

Viewing changes to misc/po-mode.el

  • Committer: Bazaar Package Importer
  • Author(s): Santiago Vila
  • Date: 2002-04-10 13:17:42 UTC
  • Revision ID: james.westby@ubuntu.com-20020410131742-npf89tsaygdolprj
Tags: upstream-0.10.40
ImportĀ upstreamĀ versionĀ 0.10.40

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
;;; po-mode.el -- for helping GNU gettext lovers to edit PO files.
 
2
;;; Copyright (C) 1995-1998, 2000, 2001 Free Software Foundation, Inc.
 
3
;;; Franļæ½ois Pinard <pinard@iro.umontreal.ca>, 1995.
 
4
;;; Helped by Greg McGary <gkm@magilla.cichlid.com>.
 
5
 
 
6
;; This file is part of GNU gettext.
 
7
 
 
8
;; GNU gettext is free software; you can redistribute it and/or modify
 
9
;; it under the terms of the GNU General Public License as published by
 
10
;; the Free Software Foundation; either version 2, or (at your option)
 
11
;; any later version.
 
12
 
 
13
;; GNU gettext is distributed in the hope that it will be useful,
 
14
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
 
15
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
16
;; GNU General Public License for more details.
 
17
 
 
18
;; You should have received a copy of the GNU General Public License
 
19
;; along with GNU Emacs; see the file COPYING.  If not, write to the
 
20
;; Free Software Foundation, 59 Temple Place - Suite 330, Boston,
 
21
;; MA 02111-1307, USA.
 
22
 
 
23
;;; This package provides the tools meant to help editing PO files,
 
24
;;; as documented in the GNU gettext user's manual.  See this manual
 
25
;;; for user documentation, which is not repeated here.
 
26
 
 
27
;;; To install, merely put this file somewhere GNU Emacs will find it,
 
28
;;; then add the following lines to your .emacs file:
 
29
;;;
 
30
;;;   (autoload 'po-mode "po-mode"
 
31
;;;             "Major mode for translators to edit PO files" t)
 
32
;;;   (setq auto-mode-alist (cons '("\\.po[tx]?\\'\\|\\.po\\." . po-mode)
 
33
;;;                               auto-mode-alist))
 
34
;;;
 
35
;;; To automatically use the right coding system under Emacs 20, also add:
 
36
;;;
 
37
;;;   (autoload 'po-find-file-coding-system "po-mode")
 
38
;;;   (modify-coding-system-alist 'file "\\.po[tx]?\\'\\|\\.po\\."
 
39
;;;                               'po-find-file-coding-system)
 
40
;;;
 
41
;;; You may also adjust some variables, below, by defining them in your
 
42
;;; `.emacs' file, either directly or through command `M-x customize'.
 
43
 
 
44
;;; Emacs portability matters.
 
45
 
 
46
;;; Most portability matters are addressed in this page.  All other cases
 
47
;;; involve one of `eval-and-compile' or `fboundp', just search for these.
 
48
 
 
49
;; Identify which Emacs variety is being used.
 
50
(eval-and-compile
 
51
  (cond ((string-match "Lucid\\|XEmacs" emacs-version)
 
52
         (setq po-EMACS20 nil po-XEMACS t))
 
53
        ((and (string-lessp "19" emacs-version) (featurep 'faces))
 
54
         (setq po-EMACS20 t po-XEMACS nil))
 
55
        (t (setq po-EMACS20 nil po-XEMACS nil))))
 
56
 
 
57
;; Experiment with Emacs LISP message internationalisation.
 
58
(eval-and-compile
 
59
  (or (fboundp 'set-translation-domain)
 
60
      (defsubst set-translation-domain (string) nil))
 
61
  (or (fboundp 'translate-string)
 
62
      (defsubst translate-string (string) string)))
 
63
(defsubst _ (string) (translate-string string))
 
64
(defsubst N_ (string) string)
 
65
 
 
66
;; Handle missing `customs' package.
 
67
(eval-and-compile
 
68
  (condition-case ()
 
69
      (require 'custom)
 
70
    (error nil))
 
71
  (if (and (featurep 'custom) (fboundp 'custom-declare-variable))
 
72
      nil
 
73
    (defmacro defgroup (&rest args)
 
74
      nil)
 
75
    (defmacro defcustom (var value doc &rest args)
 
76
      (` (defvar (, var) (, value) (, doc))))))
 
77
 
 
78
;; Protect string comparisons from text properties.
 
79
(eval-and-compile
 
80
  (fset 'po-buffer-substring
 
81
        (symbol-function (if (fboundp 'buffer-substring-no-properties)
 
82
                             'buffer-substring-no-properties
 
83
                           'buffer-substring))))
 
84
 
 
85
;; Handle missing `with-temp-buffer' function.
 
86
(eval-and-compile
 
87
  (if nil ; FIXME: just testing...  (fboundp 'with-temp-buffer)
 
88
 
 
89
      (fset 'po-with-temp-buffer (symbol-function 'with-temp-buffer))
 
90
 
 
91
    (defmacro po-with-temp-buffer (&rest forms)
 
92
      "Create a temporary buffer, and evaluate FORMS there like `progn'."
 
93
      (let ((curr-buffer (make-symbol "curr-buffer"))
 
94
            (temp-buffer (make-symbol "temp-buffer")))
 
95
        `(let ((,curr-buffer (current-buffer))
 
96
               (,temp-buffer (get-buffer-create
 
97
                              (generate-new-buffer-name " *po-temp*"))))
 
98
           (unwind-protect
 
99
               (progn
 
100
                 (set-buffer ,temp-buffer)
 
101
                 ,@forms)
 
102
             (set-buffer ,curr-buffer)
 
103
             (and (buffer-name ,temp-buffer)
 
104
                  (kill-buffer ,temp-buffer))))))))
 
105
 
 
106
;; Handle missing `kill-new' function.
 
107
(eval-and-compile
 
108
  (if (fboundp 'kill-new)
 
109
 
 
110
      (fset 'po-kill-new (symbol-function 'kill-new))
 
111
 
 
112
    (defun po-kill-new (string)
 
113
      "Push STRING onto the kill ring, for Emacs 18 where kill-new is missing."
 
114
      (po-with-temp-buffer
 
115
        (insert string)
 
116
        (kill-region (point-min) (point-max))))))
 
117
 
 
118
;; Handle missing `read-event' function.
 
119
(eval-and-compile
 
120
  (fset 'po-read-event
 
121
        (cond ((fboundp 'read-event)
 
122
               ;; GNU Emacs.
 
123
               'read-event)
 
124
              ((fboundp 'next-command-event)
 
125
               ;; XEmacs.
 
126
               'next-command-event)
 
127
              (t
 
128
               ;; Older Emacses.
 
129
               'read-char))))
 
130
 
 
131
;; Handle missing `force-mode-line-update' function.
 
132
(eval-and-compile
 
133
  (if (fboundp 'force-mode-line-update)
 
134
 
 
135
      (fset 'po-force-mode-line-update
 
136
            (symbol-function 'force-mode-line-update))
 
137
 
 
138
    (defun po-force-mode-line-update ()
 
139
      "Force the mode-line of the current buffer to be redisplayed."
 
140
      (set-buffer-modified-p (buffer-modified-p)))))
 
141
 
 
142
;; Handle portable highlighting.  Code has been adapted (OK... stolen! :-)
 
143
;; from `ispell.el'.
 
144
(eval-and-compile
 
145
  (cond
 
146
   (po-EMACS20
 
147
 
 
148
    (defun po-create-overlay ()
 
149
      "Create and return a deleted overlay structure.
 
150
The variable `po-highlight-face' selects the face to use for highlighting."
 
151
      (let ((overlay (make-overlay (point) (point))))
 
152
        (overlay-put overlay 'face po-highlight-face)
 
153
        ;; The fun thing is that a deleted overlay retains its face, and is
 
154
        ;; movable.
 
155
        (delete-overlay overlay)
 
156
        overlay))
 
157
 
 
158
    (defun po-highlight (overlay start end &optional buffer)
 
159
      "Use OVERLAY to highlight the string from START to END.
 
160
If limits are not relative to the current buffer, use optional BUFFER."
 
161
      (move-overlay overlay start end (or buffer (current-buffer))))
 
162
 
 
163
    (defun po-rehighlight (overlay)
 
164
      "Ensure OVERLAY is highlighted."
 
165
      ;; There is nothing to do, as GNU Emacs allows multiple highlights.
 
166
      nil)
 
167
 
 
168
    (defun po-dehighlight (overlay)
 
169
      "Display normally the last string which OVERLAY highlighted.
 
170
The current buffer should be in PO mode, when this function is called."
 
171
      (delete-overlay overlay)))
 
172
 
 
173
   (po-XEMACS
 
174
 
 
175
    (defun po-create-overlay ()
 
176
      "Create and return a deleted overlay structure."
 
177
      (cons (make-marker) (make-marker)))
 
178
 
 
179
    (defun po-highlight (overlay start end &optional buffer)
 
180
      "Use OVERLAY to highlight the string from START to END.
 
181
If limits are not relative to the current buffer, use optional BUFFER."
 
182
      (if buffer
 
183
          (save-excursion
 
184
            (set-buffer buffer)
 
185
            (isearch-highlight start end))
 
186
        (isearch-highlight start end))
 
187
      (set-marker (car overlay) start (or buffer (current-buffer)))
 
188
      (set-marker (cdr overlay) end (or buffer (current-buffer))))
 
189
 
 
190
    (defun po-rehighlight (overlay)
 
191
      "Ensure OVERLAY is highlighted."
 
192
      (let ((buffer (marker-buffer (car overlay)))
 
193
            (start (marker-position (car overlay)))
 
194
            (end (marker-position (cdr overlay))))
 
195
        (and buffer
 
196
             (name-buffer buffer)
 
197
             (po-highlight overlay start end buffer))))
 
198
 
 
199
    (defun po-dehighlight (overlay)
 
200
      "Display normally the last string which OVERLAY highlighted."
 
201
      (isearch-dehighlight t)
 
202
      (setcar overlay (make-marker))
 
203
      (setcdr overlay (make-marker))))
 
204
 
 
205
   (t
 
206
 
 
207
    (defun po-create-overlay ()
 
208
      "Create and return a deleted overlay structure."
 
209
      (cons (make-marker) (make-marker)))
 
210
 
 
211
    (defun po-highlight (overlay start end &optional buffer)
 
212
      "Use OVERLAY to highlight the string from START to END.
 
213
If limits are not relative to the current buffer, use optional BUFFER.
 
214
No doubt that highlighting, when Emacs does not allow it, is a kludge."
 
215
      (save-excursion
 
216
        (and buffer (set-buffer buffer))
 
217
        (let ((modified (buffer-modified-p))
 
218
              (buffer-read-only nil)
 
219
              (inhibit-quit t)
 
220
              (buffer-undo-list t)
 
221
              (text (buffer-substring start end)))
 
222
          (goto-char start)
 
223
          (delete-region start end)
 
224
          (insert-char ?  (- end start))
 
225
          (sit-for 0)
 
226
          (setq inverse-video (not inverse-video))
 
227
          (delete-region start end)
 
228
          (insert text)
 
229
          (sit-for 0)
 
230
          (setq inverse-video (not inverse-video))
 
231
          (set-buffer-modified-p modified)))
 
232
      (set-marker (car overlay) start (or buffer (current-buffer)))
 
233
      (set-marker (cdr overlay) end (or buffer (current-buffer))))
 
234
 
 
235
    (defun po-rehighlight (overlay)
 
236
      "Ensure OVERLAY is highlighted."
 
237
      (let ((buffer (marker-buffer (car overlay)))
 
238
            (start (marker-position (car overlay)))
 
239
            (end (marker-position (cdr overlay))))
 
240
        (and buffer
 
241
             (name-buffer buffer)
 
242
             (po-highlight overlay start end buffer))))
 
243
 
 
244
    (defun po-dehighlight (overlay)
 
245
      "Display normally the last string which OVERLAY highlighted."
 
246
      (let ((buffer (marker-buffer (car overlay)))
 
247
            (start (marker-position (car overlay)))
 
248
            (end (marker-position (cdr overlay))))
 
249
        (if buffer
 
250
            (save-excursion
 
251
              (set-buffer buffer)
 
252
              (let ((modified (buffer-modified-p))
 
253
                    (buffer-read-only nil)
 
254
                    (inhibit-quit t)
 
255
                    (buffer-undo-list t))
 
256
                (let ((text (buffer-substring start end)))
 
257
                  (goto-char start)
 
258
                  (delete-region start end)
 
259
                  (insert-char ?  (- end start))
 
260
                  (sit-for 0)
 
261
                  (delete-region start end)
 
262
                  (insert text)
 
263
                  (sit-for 0)
 
264
                  (set-buffer-modified-p modified)))))
 
265
        (setcar overlay (make-marker))
 
266
        (setcdr overlay (make-marker))))
 
267
 
 
268
    )))
 
269
 
 
270
;;; Customisation.
 
271
 
 
272
(defgroup po nil
 
273
  "Major mode for editing PO files"
 
274
  :group 'i18n)
 
275
 
 
276
(defcustom po-auto-edit-with-msgid nil
 
277
  "*Automatically use msgid when editing untranslated entries."
 
278
  :type 'boolean
 
279
  :group 'po)
 
280
 
 
281
(defcustom po-auto-fuzzy-on-edit nil
 
282
  "*Automatically mark entries fuzzy when being edited."
 
283
  :type 'boolean
 
284
  :group 'po)
 
285
 
 
286
(defcustom po-auto-select-on-unfuzzy nil
 
287
  "*Automatically select some new entry while making an entry not fuzzy."
 
288
  :type 'boolean
 
289
  :group 'po)
 
290
 
 
291
(defcustom po-auto-replace-revision-date 'ask
 
292
  "*Automatically revise date in headers.  Value is nil, t, or ask."
 
293
  :type '(choice (const nil)
 
294
                 (const t)
 
295
                 (const ask))
 
296
  :group 'po)
 
297
 
 
298
(defcustom po-default-file-header "\
 
299
# SOME DESCRIPTIVE TITLE.
 
300
# Copyright (C) YEAR Free Software Foundation, Inc.
 
301
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
 
302
#
 
303
#, fuzzy
 
304
msgid \"\"
 
305
msgstr \"\"
 
306
\"Project-Id-Version: PACKAGE VERSION\\n\"
 
307
\"PO-Revision-Date: YEAR-MO-DA HO:MI +ZONE\\n\"
 
308
\"Last-Translator: FULL NAME <EMAIL@ADDRESS>\\n\"
 
309
\"Language-Team: LANGUAGE <LL@li.org>\\n\"
 
310
\"MIME-Version: 1.0\\n\"
 
311
\"Content-Type: text/plain; charset=CHARSET\\n\"
 
312
\"Content-Transfer-Encoding: 8bit\\n\"
 
313
"
 
314
  "*Default PO file header."
 
315
  :type 'string
 
316
  :group 'po)
 
317
 
 
318
(defcustom po-highlighting (or po-EMACS20 po-XEMACS)
 
319
  "*Highlight text whenever appropriate, when non-nil.  However, on older
 
320
Emacses, a yet unexplained highlighting bug causes files to get mangled."
 
321
  :type 'boolean
 
322
  :group 'po)
 
323
 
 
324
(defcustom po-highlight-face 'highlight
 
325
  "*The face used for PO mode highlighting.  For Emacses with overlays.
 
326
Possible values are `highlight', `modeline', `secondary-selection',
 
327
`region', and `underline'.
 
328
This variable can be set by the user to whatever face they desire.
 
329
It's most convenient if the cursor color and highlight color are
 
330
slightly different."
 
331
  :type 'face
 
332
  :group 'po)
 
333
 
 
334
(defcustom po-gzip-uuencode-command "gzip -9 | uuencode -m"
 
335
  "*The filter to use for preparing a mail invoice of the PO file.
 
336
Normally \"gzip -9 | uuencode -m\", remove the -9 for lesser compression,
 
337
or remove the -m if you are not using the GNU version of `uuencode'."
 
338
  :type 'string
 
339
  :group 'po)
 
340
 
 
341
;;; The following block of declarations has the main purpose of avoiding
 
342
;;; byte compiler warnings.  It also introduces some documentation for
 
343
;;; each of these variables, all meant to be local to PO mode buffers.
 
344
 
 
345
;; Flag telling that MODE-LINE-STRING should be displayed.  See `Window'
 
346
;; page below.  Exceptionally, this variable is local to *all* buffers.
 
347
 
 
348
(defvar po-mode-flag)
 
349
 
 
350
;; PO buffers are kept read-only to prevent random modifications.  READ-ONLY
 
351
;; holds the value of the read-only flag before PO mode was entered.
 
352
 
 
353
(defvar po-read-only)
 
354
 
 
355
;; The current entry extends from START-OF-ENTRY to END-OF-ENTRY, it
 
356
;; includes preceding whitespace and excludes following whitespace.  The
 
357
;; start of keyword lines are START-OF-MSGID and START-OF-MSGSTR.
 
358
;; ENTRY-TYPE classifies the entry.
 
359
 
 
360
(defvar po-start-of-entry)
 
361
(defvar po-start-of-msgid)
 
362
(defvar po-start-of-msgstr)
 
363
(defvar po-end-of-entry)
 
364
(defvar po-entry-type)
 
365
 
 
366
;; A few counters are usefully shown in the Emacs mode line.
 
367
 
 
368
(defvar po-translated-counter)
 
369
(defvar po-fuzzy-counter)
 
370
(defvar po-untranslated-counter)
 
371
(defvar po-obsolete-counter)
 
372
(defvar po-mode-line-string)
 
373
 
 
374
;; PO mode keeps track of fields being edited, for one given field should
 
375
;; have one editing buffer at most, and for exiting a PO buffer properly
 
376
;; should offer to close all pending edits.  Variable EDITED-FIELDS holds an
 
377
;; an list of "slots" of the form: (ENTRY-MARKER EDIT-BUFFER OVERLAY-INFO).
 
378
;; To allow simultaneous edition of the comment and the msgstr of an entry,
 
379
;; ENTRY-MARKER points to the msgid line if a comment is being edited, or to
 
380
;; the msgstr line if the msgstr is being edited.  EDIT-BUFFER is the
 
381
;; temporary Emacs buffer used to edit the string.  OVERLAY-INFO, when not
 
382
;; nil, holds an overlay (or if overlays are not supported, a cons of two
 
383
;; markers) for this msgid string which became highlighted for the edit.
 
384
 
 
385
(defvar po-edited-fields)
 
386
 
 
387
;; We maintain a set of movable pointers for returning to entries.
 
388
 
 
389
(defvar po-marker-stack)
 
390
 
 
391
;; SEARCH path contains a list of directories where files may be found,
 
392
;; in a format suitable for read completion.  Each directory includes
 
393
;; its trailing slash.  PO mode starts with "./" and "../".
 
394
 
 
395
(defvar po-search-path)
 
396
 
 
397
;; The following variables are meaningful only when REFERENCE-CHECK
 
398
;; is identical to START-OF-ENTRY, else they should be recomputed.
 
399
;; REFERENCE-ALIST contains all known references for the current
 
400
;; entry, each list element is (PROMPT FILE LINE), where PROMPT may
 
401
;; be used for completing read, FILE is a string and LINE is a number.
 
402
;; REFERENCE-CURSOR is a cycling cursor into REFERENCE-ALIST.
 
403
 
 
404
(defvar po-reference-alist)
 
405
(defvar po-reference-cursor)
 
406
(defvar po-reference-check)
 
407
 
 
408
;; The following variables are for marking translatable strings in program
 
409
;; sources.  KEYWORDS is the list of keywords for marking translatable
 
410
;; strings, kept in a format suitable for reading with completion.
 
411
;; NEXT-FILE-LIST is the list of source files to visit, gotten from the tags
 
412
;; table.  STRING-START is the position for the beginning of the last found
 
413
;; string, or nil if the string is invalidated.  STRING-END is the position
 
414
;; for the end of the string and indicates where the search should be
 
415
;; resumed, or nil for the beginning of the current file.  MARKING-OVERLAY,
 
416
;; if not `nil', holds the overlay which highlight the last found string;
 
417
;; for older Emacses, it holds the cons of two markers around the
 
418
;; highlighted region.
 
419
 
 
420
(defvar po-keywords)
 
421
(defvar po-next-file-list)
 
422
(defvar po-string-start)
 
423
(defvar po-string-end)
 
424
(defvar po-marking-overlay)
 
425
 
 
426
;;; PO mode variables and constants (usually not to customize).
 
427
 
 
428
;; The textdomain should really be "gettext", only trying it for now.
 
429
;; All this requires more thinking, we cannot just do this like that.
 
430
(set-translation-domain "po-mode")
 
431
 
 
432
(defun po-mode-version ()
 
433
  "Show Emacs PO mode version."
 
434
  (interactive)
 
435
  (message (_"Emacs PO mode, version %s") (substring "$Revision: 1.1.1.1 $" 11 -2)))
 
436
 
 
437
(defconst po-help-display-string
 
438
  (_"\
 
439
PO Mode Summary           Next Previous            Miscellaneous
 
440
*: Later, /: Docum        n    p    Any type       .     Redisplay
 
441
                          /t   /M-t Translated     /v    Version info
 
442
Moving around             f    M-f  Fuzzy          ?, h  This help
 
443
<    First if any         o    M-o  Obsolete       =     Current index
 
444
>    Last if any          u    M-u  Untranslated   O     Other window
 
445
/SPC Auto select                                   V     Validate
 
446
                        Msgstr Comments            M     Mail officially
 
447
Modifying entries         RET  #    Call editor    U     Undo
 
448
TAB   Remove fuzzy mark   k    K    Kill to        E     Edit out full
 
449
/DEL  Fuzzy or fade out   w    W    Copy to        Q     Forceful quit
 
450
LFD   Init with msgid     y    Y    Yank from      q     Confirm and quit
 
451
 
 
452
gettext Keyword Marking                            Position Stack
 
453
,    Find next string     Compendiums              m  Mark and push current
 
454
M-,  Mark translatable    *c    To compendium      r  Pop and return
 
455
M-.  Change mark, mark    *M-C  Select, save       x  Exchange current/top
 
456
 
 
457
Program Sources           Auxiliary Files          Lexicography
 
458
s    Cycle reference      a    Cycle file          *l    Lookup translation
 
459
M-s  Select reference     M-a  Select file         *M-l  Add/edit translation
 
460
S    Consider path        A    Consider PO file    *L    Consider lexicon
 
461
M-S  Ignore path          M-A  Ignore PO file      *M-L  Ignore lexicon
 
462
")
 
463
  "Help page for PO mode.")
 
464
 
 
465
(defconst po-mode-menu-layout
 
466
  '("PO"
 
467
    ("Moving around"
 
468
     ["Auto select" po-auto-select-entry t]
 
469
     "---"
 
470
     "Forward"
 
471
     ["Any next" po-next-entry t]
 
472
     ["Next translated" po-next-translated-entry t]
 
473
     ["Next fuzzy" po-next-fuzzy-entry t]
 
474
     ["Next obsolete" po-next-obsolete-entry t]
 
475
     ["Next untranslated" po-next-untranslated-entry t]
 
476
     ["Last file entry" po-last-entry t]
 
477
     "---"
 
478
     "Backward"
 
479
     ["Any previous" po-previous-entry t]
 
480
     ["Previous translated" po-previous-translated-entry t]
 
481
     ["Previous fuzzy" po-previous-fuzzy-entry t]
 
482
     ["Previous obsolete" po-previous-obsolete-entry t]
 
483
     ["Previous untranslated" po-previous-untranslated-entry t]
 
484
     ["First file entry" po-first-entry t]
 
485
     "---"
 
486
     "Position stack"
 
487
     ["Mark and push current" po-push-location t]
 
488
     ["Pop and return" po-pop-location t]
 
489
     ["Exchange current/top" po-exchange-location t]
 
490
     "---"
 
491
     ["Redisplay" po-current-entry t]
 
492
     ["Current index" po-statistics t])
 
493
    ("Modifying entries"
 
494
     ["Undo" po-undo t]
 
495
     "---"
 
496
     "Msgstr"
 
497
     ["Edit msgstr" po-edit-msgstr t]
 
498
     ["Kill msgstr" po-kill-msgstr t]
 
499
     ["Save msgstr" po-kill-ring-save-msgstr t]
 
500
     ["Yank msgstr" po-yank-msgstr t]
 
501
     "---"
 
502
     "Comments"
 
503
     ["Edit comment" po-edit-comment t]
 
504
     ["Kill comment" po-kill-comment t]
 
505
     ["Save comment" po-kill-ring-save-comment t]
 
506
     ["Yank comment" po-yank-comment t]
 
507
     "---"
 
508
     ["Remove fuzzy mark" po-unfuzzy t]
 
509
     ["Fuzzy or fade out" po-fade-out-entry t]
 
510
     ["Init with msgid" po-msgid-to-msgstr t])
 
511
    ("Other files"
 
512
     ["Other window" po-other-window t]
 
513
     "---"
 
514
     "Program sources"
 
515
     ["Cycle reference" po-cycle-source-reference t]
 
516
     ["Select reference" po-select-source-reference t]
 
517
     ["Consider path" po-consider-source-path t]
 
518
     ["Ignore path" po-ignore-source-path t]
 
519
     "---"
 
520
     "Compendiums"
 
521
     ["To compendium" po-save-entry nil]
 
522
     ["Select, save" po-select-and-save-entry nil]
 
523
     "---"
 
524
     "Auxiliary files"
 
525
     ["Cycle file" po-cycle-auxiliary t]
 
526
     ["Select file" po-select-auxiliary t]
 
527
     ["Consider file" po-consider-as-auxiliary t]
 
528
     ["Ignore file" po-ignore-as-auxiliary t]
 
529
     "---"
 
530
     "Lexicography"
 
531
     ["Lookup translation" po-lookup-lexicons nil]
 
532
     ["Add/edit translation" po-edit-lexicon-entry nil]
 
533
     ["Consider lexicon" po-consider-lexicon-file nil]
 
534
     ["Ignore lexicon" po-ignore-lexicon-file nil])
 
535
    "---"
 
536
    "Source marking"
 
537
    ["Find first string" (po-tags-search '(nil)) t]
 
538
    ["Prefer keyword" (po-select-mark-and-mark '(nil)) t]
 
539
    ["Find next string" po-tags-search t]
 
540
    ["Mark preferred" po-mark-translatable t]
 
541
    ["Mark with keyword" po-select-mark-and-mark t]
 
542
    "---"
 
543
    ["Version info" po-mode-version t]
 
544
    ["Help page" po-help t]
 
545
    ["Validate" po-validate t]
 
546
    ["Mail officially" po-send-mail t]
 
547
    ["Edit out full" po-edit-out-full t]
 
548
    "---"
 
549
    ["Forceful quit" po-quit t]
 
550
    ["Soft quit" po-confirm-and-quit t])
 
551
  "Menu layout for PO mode.")
 
552
 
 
553
;; FIXME: subedit mode should also have its own layout.
 
554
 
 
555
(defconst po-subedit-message
 
556
  (_"Type `C-c C-c' once done, or `C-c C-k' to abort edit")
 
557
  "Message to post in the minibuffer when an edit buffer is displayed.")
 
558
 
 
559
(defconst po-content-type-charset-alist
 
560
  '(; Note: Emacs 20 doesn't support all encodings, thus the missing entries.
 
561
    (ASCII . undecided)
 
562
    (ANSI_X3.4-1968 . undecided)
 
563
    (US-ASCII . undecided)
 
564
    (ISO-8859-1 . iso-8859-1)
 
565
    (ISO_8859-1 . iso-8859-1)
 
566
    (ISO-8859-2 . iso-8859-2)
 
567
    (ISO_8859-2 . iso-8859-2)
 
568
    (ISO-8859-3 . iso-8859-3)
 
569
    (ISO_8859-3 . iso-8859-3)
 
570
    (ISO-8859-4 . iso-8859-4)
 
571
    (ISO_8859-4 . iso-8859-4)
 
572
    (ISO-8859-5 . iso-8859-5)
 
573
    (ISO_8859-5 . iso-8859-5)
 
574
    ;(ISO-8859-6 . ??)
 
575
    ;(ISO_8859-6 . ??)
 
576
    (ISO-8859-7 . iso-8859-7)
 
577
    (ISO_8859-7 . iso-8859-7)
 
578
    (ISO-8859-8 . iso-8859-8)
 
579
    (ISO_8859-8 . iso-8859-8)
 
580
    (ISO-8859-9 . iso-8859-9)
 
581
    (ISO_8859-9 . iso-8859-9)
 
582
    ;(ISO-8859-13 . ??)
 
583
    ;(ISO_8859-13 . ??)
 
584
    (ISO-8859-15 . iso-8859-15) ; requires Emacs 21
 
585
    (ISO_8859-15 . iso-8859-15) ; requires Emacs 21
 
586
    (KOI8-R . koi8-r)
 
587
    ;(KOI8-U . ??)
 
588
    ;(CP850 . ??)
 
589
    ;(CP866 . ??)
 
590
    ;(CP874 . ??)
 
591
    ;(CP932 . ??)
 
592
    ;(CP949 . ??)
 
593
    ;(CP950 . ??)
 
594
    ;(CP1250 . ??)
 
595
    ;(CP1251 . ??)
 
596
    ;(CP1252 . ??)
 
597
    ;(CP1253 . ??)
 
598
    ;(CP1254 . ??)
 
599
    ;(CP1255 . ??)
 
600
    ;(CP1256 . ??)
 
601
    ;(CP1257 . ??)
 
602
    (GB2312 . cn-gb-2312)  ; also named 'gb2312' in XEmacs 21 or Emacs 21
 
603
                           ; also named 'euc-cn' in Emacs 20 or Emacs 21
 
604
    (EUC-JP . euc-jp)
 
605
    (EUC-KR . euc-kr)
 
606
    ;(EUC-TW . ??)
 
607
    (BIG5 . big5)
 
608
    ;(BIG5-HKSCS . ??)
 
609
    ;(GBK . ??)
 
610
    ;(GB18030 . ??)
 
611
    (SHIFT_JIS . shift_jis)
 
612
    ;(JOHAB . ??)
 
613
    (TIS-620 . tis-620)    ; requires Emacs 20 or Emacs 21
 
614
    (VISCII . viscii)      ; requires Emacs 20 or Emacs 21
 
615
    (UTF-8 . utf-8)        ; requires Mule-UCS in Emacs 20, or Emacs 21
 
616
    )
 
617
  "How to convert a GNU libc/libiconv canonical charset name as seen in
 
618
Content-Type into a Mule coding system.")
 
619
 
 
620
(defvar po-auxiliary-list nil
 
621
  "List of auxiliary PO files, in completing read format.")
 
622
 
 
623
(defvar po-auxiliary-cursor nil
 
624
  "Cursor into the `po-auxiliary-list'.")
 
625
 
 
626
(defvar po-translation-project-address
 
627
  "translation@iro.umontreal.ca"
 
628
  "Electronic mail address of the Translation Project.")
 
629
 
 
630
(defvar po-compose-mail-function
 
631
  (let ((functions '(compose-mail-other-window
 
632
                     message-mail-other-window
 
633
                     compose-mail
 
634
                     message-mail))
 
635
        result)
 
636
    (while (and (not result) functions)
 
637
      (if (fboundp (car functions))
 
638
          (setq result (car functions))
 
639
        (setq functions (cdr functions))))
 
640
    (cond (result)
 
641
          ((fboundp 'mail-other-window)
 
642
           (function (lambda (to subject)
 
643
                       (mail-other-window nil to subject))))
 
644
          ((fboundp 'mail)
 
645
           (function (lambda (to subject)
 
646
                       (mail nil to subject))))
 
647
          (t (function (lambda (to subject)
 
648
                         (error (_"I do not know how to mail to `%s'") to))))))
 
649
  "Function to start composing an electronic message.")
 
650
 
 
651
(defvar po-any-msgid-regexp
 
652
  "^\\(#~?[ \t]*\\)?msgid.*\n\\(\\(#~?[ \t]*\\)?\".*\n\\)*"
 
653
  "Regexp matching a whole msgid field, whether obsolete or not.")
 
654
 
 
655
(defvar po-any-msgstr-regexp
 
656
  ;; "^\\(#~?[ \t]*\\)?msgstr.*\n\\(\\(#~?[ \t]*\\)?\".*\n\\)*"
 
657
  "^\\(#~?[ \t]*\\)?msgstr\\(\\[[0-9]\\]\\)?.*\n\\(\\(#~?[ \t]*\\)?\".*\n\\)*"
 
658
  "Regexp matching a whole msgstr or msgstr[] field, whether obsolete or not.")
 
659
 
 
660
(defvar po-msgstr-idx-keyword-regexp
 
661
  "^\\(#~?[ \t]*\\)?msgstr\\[[0-9]\\]"
 
662
  "Regexp matching an indexed msgstr keyword, whether obsolete or not.")
 
663
 
 
664
(defvar po-msgfmt-program "msgfmt"
 
665
  "Path to msgfmt program from GNU gettext package.")
 
666
 
 
667
;; Font lock based highlighting code.
 
668
(defconst po-font-lock-keywords
 
669
  '(
 
670
    ;; ("^\\(msgid \\|msgstr \\)?\"\\|\"$" . font-lock-keyword-face)
 
671
    ;; (regexp-opt
 
672
    ;;  '("msgid " "msgid_plural " "msgstr " "msgstr[0] " "msgstr[1] "))
 
673
    ("^\\(\\(msg\\(id\\(_plural\\)?\\|str\\(\\[[0-9]\\]\\)?\\)?\\) \\)?\"\\|\"$"
 
674
     . font-lock-keyword-face)
 
675
    ("\\\\.\\|%\\*?[-.0-9ul]*[a-zA-Z]" . font-lock-variable-name-face)
 
676
    ("^# .*\\|^#[:,]?" . font-lock-comment-face)
 
677
    ("^#:\\(.*\\)" 1 font-lock-reference-face)
 
678
    ;; The following line does not work, and I wonder why.
 
679
    ;;("^#,\\(.*\\)" 1 font-function-name-reference-face)
 
680
    )
 
681
  "Additional expressions to highlight in PO mode.")
 
682
 
 
683
;; Old activator for `font lock'.  Is it still useful?  I don't think so.
 
684
;;
 
685
;;(if (boundp 'font-lock-keywords)
 
686
;;    (put 'po-mode 'font-lock-keywords 'po-font-lock-keywords))
 
687
 
 
688
;; `hilit19' based highlighting code has been disabled, as most probably
 
689
;; nobody really needs it (it also generates ugly byte-compiler warnings).
 
690
;;
 
691
;;(if (fboundp 'hilit-set-mode-patterns)
 
692
;;    (hilit-set-mode-patterns 'po-mode
 
693
;;                           '(("^# .*\\|^#$" nil comment)
 
694
;;                             ("^#[.,:].*" nil include)
 
695
;;                             ("^\\(msgid\\|msgstr\\) *\"" nil keyword)
 
696
;;                             ("^\"\\|\"$" nil keyword))))
 
697
 
 
698
;;; Mode activation.
 
699
 
 
700
(eval-and-compile
 
701
  (if (or po-EMACS20 po-XEMACS)
 
702
      (defun po-find-file-coding-system-guts (operation filename)
 
703
        "Return a Mule (DECODING . ENCODING) pair, according to PO file charset.
 
704
Called through file-coding-system-alist, before the file is visited for real."
 
705
        (and (eq operation 'insert-file-contents)
 
706
             (with-temp-buffer
 
707
               (let ((coding-system-for-read 'no-conversion))
 
708
                 ;; Is 4096 enough?  FIXME: Retry as needed!
 
709
                 (insert-file-contents filename nil 0 4096)
 
710
                 (if (re-search-forward
 
711
                      "^\"Content-Type: text/plain;[ \t]*charset=\\([^\\]+\\)"
 
712
                      nil t)
 
713
                     (let* ((charset (buffer-substring
 
714
                                       (match-beginning 1) (match-end 1)))
 
715
                            (charset-upper (intern (upcase charset)))
 
716
                            (charset-lower (intern (downcase charset))))
 
717
                       (list (or (cdr (assq charset-upper
 
718
                                            po-content-type-charset-alist))
 
719
                                 (if (memq charset-lower (coding-system-list))
 
720
                                     charset-lower
 
721
                                   'no-conversion))))
 
722
                   '(no-conversion)))))))
 
723
 
 
724
  (if po-EMACS20
 
725
      (defun po-find-file-coding-system (arg-list)
 
726
        "Return a Mule (DECODING . ENCODING) pair, according to PO file charset.
 
727
Called through file-coding-system-alist, before the file is visited for real."
 
728
        (po-find-file-coding-system-guts (car arg-list) (car (cdr arg-list)))))
 
729
 
 
730
  (if po-XEMACS
 
731
      (defun po-find-file-coding-system (operation filename)
 
732
        "Return a Mule (DECODING . ENCODING) pair, according to PO file charset.
 
733
Called through file-coding-system-alist, before the file is visited for real."
 
734
        (po-find-file-coding-system-guts operation filename)))
 
735
 
 
736
 )
 
737
 
 
738
(defvar po-mode-map nil
 
739
  "Keymap for PO mode.")
 
740
(if po-mode-map
 
741
    ()
 
742
  ;; The following line because (make-sparse-keymap) does not work on Demacs.
 
743
  (setq po-mode-map (make-keymap))
 
744
  (suppress-keymap po-mode-map)
 
745
  (define-key po-mode-map "\C-i" 'po-unfuzzy)
 
746
  (define-key po-mode-map "\C-j" 'po-msgid-to-msgstr)
 
747
  (define-key po-mode-map "\C-m" 'po-edit-msgstr)
 
748
  (define-key po-mode-map " " 'po-auto-select-entry)
 
749
  (define-key po-mode-map "?" 'po-help)
 
750
  (define-key po-mode-map "#" 'po-edit-comment)
 
751
  (define-key po-mode-map "," 'po-tags-search)
 
752
  (define-key po-mode-map "." 'po-current-entry)
 
753
  (define-key po-mode-map "<" 'po-first-entry)
 
754
  (define-key po-mode-map "=" 'po-statistics)
 
755
  (define-key po-mode-map ">" 'po-last-entry)
 
756
  (define-key po-mode-map "a" 'po-cycle-auxiliary)
 
757
;;;;  (define-key po-mode-map "c" 'po-save-entry)
 
758
  (define-key po-mode-map "f" 'po-next-fuzzy-entry)
 
759
  (define-key po-mode-map "h" 'po-help)
 
760
  (define-key po-mode-map "k" 'po-kill-msgstr)
 
761
;;;;  (define-key po-mode-map "l" 'po-lookup-lexicons)
 
762
  (define-key po-mode-map "m" 'po-push-location)
 
763
  (define-key po-mode-map "n" 'po-next-entry)
 
764
  (define-key po-mode-map "o" 'po-next-obsolete-entry)
 
765
  (define-key po-mode-map "p" 'po-previous-entry)
 
766
  (define-key po-mode-map "q" 'po-confirm-and-quit)
 
767
  (define-key po-mode-map "r" 'po-pop-location)
 
768
  (define-key po-mode-map "s" 'po-cycle-source-reference)
 
769
  (define-key po-mode-map "t" 'po-next-translated-entry)
 
770
  (define-key po-mode-map "u" 'po-next-untranslated-entry)
 
771
  (define-key po-mode-map "v" 'po-mode-version)
 
772
  (define-key po-mode-map "w" 'po-kill-ring-save-msgstr)
 
773
  (define-key po-mode-map "x" 'po-exchange-location)
 
774
  (define-key po-mode-map "y" 'po-yank-msgstr)
 
775
  (define-key po-mode-map "A" 'po-consider-as-auxiliary)
 
776
  (define-key po-mode-map "E" 'po-edit-out-full)
 
777
  (define-key po-mode-map "K" 'po-kill-comment)
 
778
;;;;  (define-key po-mode-map "L" 'po-consider-lexicon-file)
 
779
  (define-key po-mode-map "M" 'po-send-mail)
 
780
  (define-key po-mode-map "O" 'po-other-window)
 
781
  (define-key po-mode-map "Q" 'po-quit)
 
782
  (define-key po-mode-map "S" 'po-consider-source-path)
 
783
  (define-key po-mode-map "U" 'po-undo)
 
784
  (define-key po-mode-map "V" 'po-validate)
 
785
  (define-key po-mode-map "W" 'po-kill-ring-save-comment)
 
786
  (define-key po-mode-map "Y" 'po-yank-comment)
 
787
  (define-key po-mode-map "\177" 'po-fade-out-entry)
 
788
  (define-key po-mode-map "\M-," 'po-mark-translatable)
 
789
  (define-key po-mode-map "\M-." 'po-select-mark-and-mark)
 
790
  (define-key po-mode-map "\M-a" 'po-select-auxiliary)
 
791
;;;;  (define-key po-mode-map "\M-c" 'po-select-and-save-entry)
 
792
  (define-key po-mode-map "\M-f" 'po-previous-fuzzy-entry)
 
793
;;;;  (define-key po-mode-map "\M-l" 'po-edit-lexicon-entry)
 
794
  (define-key po-mode-map "\M-o" 'po-previous-obsolete-entry)
 
795
  (define-key po-mode-map "\M-t" 'po-previous-translated-entry)
 
796
  (define-key po-mode-map "\M-u" 'po-previous-untranslated-entry)
 
797
  (define-key po-mode-map "\M-s" 'po-select-source-reference)
 
798
  (define-key po-mode-map "\M-A" 'po-ignore-as-auxiliary)
 
799
;;;;  (define-key po-mode-map "\M-L" 'po-ignore-lexicon-file)
 
800
  (define-key po-mode-map "\M-S" 'po-ignore-source-path)
 
801
  )
 
802
 
 
803
(defun po-mode ()
 
804
  "Major mode for translators when they edit PO files.
 
805
Special commands:\\{po-mode-map}
 
806
Turning on PO mode calls the value of the variable `po-mode-hook',
 
807
if that value is non-nil.  Behaviour may be adjusted through some variables,
 
808
all reachable through `M-x customize', in group `Emacs.Editing.I18n.Po'."
 
809
 
 
810
  (interactive)
 
811
  (kill-all-local-variables)
 
812
  (setq major-mode 'po-mode
 
813
        mode-name "PO")
 
814
  (use-local-map po-mode-map)
 
815
  (if (fboundp 'easy-menu-define)
 
816
      (progn
 
817
       (easy-menu-define po-mode-menu po-mode-map "" po-mode-menu-layout)
 
818
       (and po-XEMACS (easy-menu-add po-mode-menu))))
 
819
  (make-local-variable 'font-lock-defaults)
 
820
  (setq font-lock-defaults '(po-font-lock-keywords t))
 
821
 
 
822
  (make-local-variable 'po-read-only)
 
823
  (setq po-read-only buffer-read-only
 
824
        buffer-read-only t)
 
825
 
 
826
  (make-local-variable 'po-start-of-entry)
 
827
  (make-local-variable 'po-start-of-msgid)
 
828
  (make-local-variable 'po-start-of-msgstr)
 
829
  (make-local-variable 'po-end-of-entry)
 
830
  (make-local-variable 'po-entry-type)
 
831
 
 
832
  (make-local-variable 'po-translated-counter)
 
833
  (make-local-variable 'po-fuzzy-counter)
 
834
  (make-local-variable 'po-untranslated-counter)
 
835
  (make-local-variable 'po-obsolete-counter)
 
836
  (make-local-variable 'po-mode-line-string)
 
837
 
 
838
  (setq po-mode-flag t)
 
839
 
 
840
  (po-check-file-header)
 
841
  (po-compute-counters nil)
 
842
 
 
843
  (make-local-variable 'po-edited-fields)
 
844
  (setq po-edited-fields nil)
 
845
 
 
846
  (make-local-variable 'po-marker-stack)
 
847
  (setq po-marker-stack nil)
 
848
 
 
849
  (make-local-variable 'po-search-path)
 
850
  (setq po-search-path '(("./") ("../")))
 
851
 
 
852
  (make-local-variable 'po-reference-alist)
 
853
  (make-local-variable 'po-reference-cursor)
 
854
  (make-local-variable 'po-reference-check)
 
855
  (setq po-reference-alist nil
 
856
        po-reference-cursor nil
 
857
        po-reference-check 0)
 
858
 
 
859
  (make-local-variable 'po-keywords)
 
860
  (make-local-variable 'po-next-file-list)
 
861
  (make-local-variable 'po-string-start)
 
862
  (make-local-variable 'po-string-end)
 
863
  (make-local-variable 'po-marking-overlay)
 
864
  (setq po-keywords '(("gettext") ("gettext_noop") ("_") ("N_"))
 
865
        po-next-file-list nil
 
866
        po-string-start nil
 
867
        po-string-end nil
 
868
        po-marking-overlay (po-create-overlay))
 
869
 
 
870
  (message (_"You may type `h' or `?' for a short PO mode reminder."))
 
871
  (run-hooks 'po-mode-hook))
 
872
 
 
873
;;; Window management.
 
874
 
 
875
(make-variable-buffer-local 'po-mode-flag)
 
876
 
 
877
(defvar po-mode-line-entry '(po-mode-flag ("  " po-mode-line-string))
 
878
  "Mode line format entry displaying MODE-LINE-STRING.")
 
879
 
 
880
;; Insert MODE-LINE-ENTRY in mode line, but on first load only.
 
881
(or (member po-mode-line-entry mode-line-format)
 
882
    (let ((entry (member 'global-mode-string mode-line-format)))
 
883
      (setcdr entry (cons po-mode-line-entry (cdr entry)))))
 
884
 
 
885
(defun po-update-mode-line-string ()
 
886
  "Compute a new statistics string to display in mode line."
 
887
  (setq po-mode-line-string
 
888
        (concat (format "%dt" po-translated-counter)
 
889
                (if (> po-fuzzy-counter 0)
 
890
                    (format "+%df" po-fuzzy-counter))
 
891
                (if (> po-untranslated-counter 0)
 
892
                    (format "+%du" po-untranslated-counter))
 
893
                (if (> po-obsolete-counter 0)
 
894
                    (format "+%do" po-obsolete-counter))))
 
895
  (po-force-mode-line-update))
 
896
 
 
897
(defun po-type-counter ()
 
898
  "Return the symbol name of the counter appropriate for the current entry."
 
899
  (cond ((eq po-entry-type 'obsolete) 'po-obsolete-counter)
 
900
        ((eq po-entry-type 'fuzzy) 'po-fuzzy-counter)
 
901
        ((eq po-entry-type 'translated) 'po-translated-counter)
 
902
        ((eq po-entry-type 'untranslated) 'po-untranslated-counter)
 
903
        (t (error (_"Unknown entry type")))))
 
904
 
 
905
(defun po-decrease-type-counter ()
 
906
  "Decrease the counter corresponding to the nature of the current entry."
 
907
  (let ((counter (po-type-counter)))
 
908
    (set counter (1- (eval counter)))))
 
909
 
 
910
(defun po-increase-type-counter ()
 
911
  "Increase the counter corresponding to the nature of the current entry.
 
912
Then, update the mode line counters."
 
913
  (let ((counter (po-type-counter)))
 
914
    (set counter (1+ (eval counter))))
 
915
  (po-update-mode-line-string))
 
916
 
 
917
;; Avoid byte compiler warnings.
 
918
(defvar po-fuzzy-regexp)
 
919
(defvar po-untranslated-regexp)
 
920
 
 
921
(defun po-compute-counters (flag)
 
922
  "Prepare counters for mode line display.  If FLAG, also echo entry position."
 
923
  (and flag (po-find-span-of-entry))
 
924
  (setq po-translated-counter 0
 
925
        po-fuzzy-counter 0
 
926
        po-untranslated-counter 0
 
927
        po-obsolete-counter 0)
 
928
  (let ((position 0) (total 0) here)
 
929
    (save-excursion
 
930
      (goto-char (point-min))
 
931
      (while (re-search-forward po-any-msgstr-regexp nil t)
 
932
        (and (= (% total 20) 0)
 
933
             (if flag
 
934
                 (message (_"Position %d/%d") position total)
 
935
               (message (_"Position %d") total)))
 
936
        (setq here (point))
 
937
        (goto-char (match-beginning 0))
 
938
        (setq total (1+ total))
 
939
        (and flag (eq (point) po-start-of-msgstr) (setq position total))
 
940
        (cond ((eq (following-char) ?#)
 
941
               (setq po-obsolete-counter (1+ po-obsolete-counter)))
 
942
              ((looking-at po-untranslated-regexp)
 
943
               (setq po-untranslated-counter (1+ po-untranslated-counter)))
 
944
              (t (setq po-translated-counter (1+ po-translated-counter))))
 
945
        (goto-char here))
 
946
 
 
947
      ;; Make another pass just for the fuzzy entries, kind of kludgey.
 
948
      ;; FIXME: Counts will be wrong if untranslated entries are fuzzy, yet
 
949
      ;; this should not normally happen.
 
950
      (goto-char (point-min))
 
951
      (while (re-search-forward po-fuzzy-regexp nil t)
 
952
        (setq po-fuzzy-counter (1+ po-fuzzy-counter)))
 
953
      (setq po-translated-counter (- po-translated-counter po-fuzzy-counter)))
 
954
 
 
955
    ;; Push the results out.
 
956
    (if flag
 
957
        (message (_"\
 
958
Position %d/%d; %d translated, %d fuzzy, %d untranslated, %d obsolete")
 
959
                 position total po-translated-counter po-fuzzy-counter
 
960
                 po-untranslated-counter po-obsolete-counter)
 
961
      (message "")))
 
962
  (po-update-mode-line-string))
 
963
 
 
964
(defun po-redisplay ()
 
965
  "Redisplay the current entry."
 
966
  ;; FIXME: Should try to fit the whole entry on the window.  If this is not
 
967
  ;; possible, should try to fit the comment and the msgid.  Otherwise,
 
968
  ;; should try to fit the msgid.  Else, the first line of the msgid should
 
969
  ;; be at the top of the window.
 
970
  (goto-char po-start-of-msgid))
 
971
 
 
972
(defun po-other-window ()
 
973
  "Get the cursor into another window, out of PO mode."
 
974
  (interactive)
 
975
  (if (one-window-p t)
 
976
      (progn
 
977
        (split-window)
 
978
        (switch-to-buffer (other-buffer)))
 
979
    (other-window 1)))
 
980
 
 
981
;;; Processing the PO file header entry.
 
982
 
 
983
(defun po-check-file-header ()
 
984
  "Create a missing PO mode file header, or replace an oldish one."
 
985
  (save-excursion
 
986
    (let ((buffer-read-only po-read-only)
 
987
          insert-flag end-of-header)
 
988
      (goto-char (point-min))
 
989
      (if (re-search-forward po-any-msgstr-regexp nil t)
 
990
          (progn
 
991
 
 
992
            ;; There is at least one entry.
 
993
            (goto-char (match-beginning 0))
 
994
            (previous-line 1)
 
995
            (setq end-of-header (match-end 0))
 
996
            (if (looking-at "msgid \"\"\n")
 
997
 
 
998
                ;; There is indeed a PO file header.
 
999
                (if (re-search-forward "\n\"PO-Revision-Date: "
 
1000
                                       end-of-header t)
 
1001
                    nil
 
1002
 
 
1003
                  ;; This is an oldish header.  Replace it all.
 
1004
                  (goto-char end-of-header)
 
1005
                  (while (> (point) (point-min))
 
1006
                    (previous-line 1)
 
1007
                    (insert "#~ ")
 
1008
                    (beginning-of-line))
 
1009
                  (beginning-of-line)
 
1010
                  (setq insert-flag t))
 
1011
 
 
1012
              ;; The first entry is not a PO file header, insert one.
 
1013
              (setq insert-flag t)))
 
1014
 
 
1015
        ;; Not a single entry found.
 
1016
        (setq insert-flag t))
 
1017
 
 
1018
      (goto-char (point-min))
 
1019
      (and insert-flag (insert po-default-file-header "\n")))))
 
1020
 
 
1021
(defun po-replace-revision-date ()
 
1022
  "Replace the revision date by current time in the PO file header."
 
1023
  (if (fboundp 'format-time-string)
 
1024
      (if (or (eq po-auto-replace-revision-date t)
 
1025
              (and (eq po-auto-replace-revision-date 'ask)
 
1026
                   (y-or-n-p (_"May I set PO-Revision-Date? "))))
 
1027
          (save-excursion
 
1028
            (goto-char (point-min))
 
1029
            (if (re-search-forward "^\"PO-Revision-Date:.*" nil t)
 
1030
                (let* ((buffer-read-only po-read-only)
 
1031
                       (time (current-time))
 
1032
                       (seconds (or (car (current-time-zone time)) 0))
 
1033
                       (minutes (/ (abs seconds) 60))
 
1034
                       (zone (format "%c%02d%02d"
 
1035
                                     (if (< seconds 0) ?- ?+)
 
1036
                                     (/ minutes 60)
 
1037
                                     (% minutes 60))))
 
1038
                  (replace-match
 
1039
                       (concat "\"PO-Revision-Date: "
 
1040
                               (format-time-string "%Y-%m-%d %H:%M" time)
 
1041
                               zone "\\n\"")
 
1042
                       t t))))
 
1043
        (message ""))
 
1044
    (message (_"PO-Revision-Date should be adjusted..."))))
 
1045
 
 
1046
;;; Handling span of entry, entry type and entry attributes.
 
1047
 
 
1048
(defun po-find-span-of-entry ()
 
1049
  "Find the extent of the PO file entry where the cursor is.  Set variables
 
1050
PO-START-OF-ENTRY, PO-START-OF-MSGID, PO-START-OF-MSGSTR, PO-END-OF-ENTRY
 
1051
and PO-ENTRY-TYPE to meaningful values.  Decreasing priority of type
 
1052
interpretation is: obsolete, fuzzy, untranslated or translated."
 
1053
  (let ((here (point)))
 
1054
    (if (re-search-backward po-any-msgstr-regexp nil t)
 
1055
        (progn
 
1056
 
 
1057
          ;; After a backward match, (match-end 0) will not extend
 
1058
          ;; beyond point, in case point was *inside* the regexp.  We
 
1059
          ;; need a dependable (match-end 0), so we redo the match in
 
1060
          ;; the forward direction.
 
1061
          (re-search-forward po-any-msgstr-regexp)
 
1062
          (if (<= (match-end 0) here)
 
1063
              (progn
 
1064
 
 
1065
                ;; We most probably found the msgstr of the previous
 
1066
                ;; entry.  The current entry then starts just after
 
1067
                ;; its end, save this information just in case.
 
1068
                (setq po-start-of-entry (match-end 0))
 
1069
 
 
1070
                ;; However, it is also possible that we are located in
 
1071
                ;; the crumb after the last entry in the file.  If
 
1072
                ;; yes, we know the middle and end of last PO entry.
 
1073
                (setq po-start-of-msgstr (match-beginning 0)
 
1074
                      po-end-of-entry (match-end 0))
 
1075
 
 
1076
                (if (re-search-forward po-any-msgstr-regexp nil t)
 
1077
                    (progn
 
1078
 
 
1079
                      ;; We definitely were not in the crumb.
 
1080
                      (setq po-start-of-msgstr (match-beginning 0)
 
1081
                            po-end-of-entry (match-end 0)))
 
1082
 
 
1083
                  ;; We were in the crumb.  The start of the last PO
 
1084
                  ;; file entry is the end of the previous msgstr if
 
1085
                  ;; any, or else, the beginning of the file.
 
1086
                  (goto-char po-start-of-msgstr)
 
1087
                  (setq po-start-of-entry
 
1088
                        (if (re-search-backward po-any-msgstr-regexp nil t)
 
1089
                            (match-end 0)
 
1090
                          (point-min)))))
 
1091
 
 
1092
            ;; The cursor was inside msgstr of the current entry.
 
1093
            (setq po-start-of-msgstr (match-beginning 0)
 
1094
                  po-end-of-entry (match-end 0))
 
1095
            ;; The start of this entry is the end of the previous
 
1096
            ;; msgstr if any, or else, the beginning of the file.
 
1097
            (goto-char po-start-of-msgstr)
 
1098
            (setq po-start-of-entry
 
1099
                  (if (re-search-backward po-any-msgstr-regexp nil t)
 
1100
                      (match-end 0)
 
1101
                    (point-min)))))
 
1102
 
 
1103
      ;; The cursor was before msgstr in the first entry in the file.
 
1104
      (setq po-start-of-entry (point-min))
 
1105
      (goto-char po-start-of-entry)
 
1106
      ;; There is at least the PO file header, so this should match.
 
1107
      (re-search-forward po-any-msgstr-regexp)
 
1108
      (setq po-start-of-msgstr (match-beginning 0)
 
1109
            po-end-of-entry (match-end 0)))
 
1110
 
 
1111
    ;; Find start of msgid.
 
1112
    (goto-char po-start-of-entry)
 
1113
    (re-search-forward po-any-msgid-regexp)
 
1114
    (setq po-start-of-msgid (match-beginning 0))
 
1115
 
 
1116
    ;; Classify the entry.
 
1117
    (setq po-entry-type
 
1118
          (if (eq (following-char) ?#)
 
1119
              'obsolete
 
1120
            (goto-char po-start-of-entry)
 
1121
            (if (re-search-forward po-fuzzy-regexp po-start-of-msgid t)
 
1122
                'fuzzy
 
1123
              (goto-char po-start-of-msgstr)
 
1124
              (if (looking-at po-untranslated-regexp)
 
1125
                  'untranslated
 
1126
                'translated))))
 
1127
 
 
1128
    ;; Put the cursor back where it was.
 
1129
    (goto-char here)))
 
1130
 
 
1131
(defun po-add-attribute (name)
 
1132
  "Add attribute NAME to the current entry, unless it is already there."
 
1133
  (save-excursion
 
1134
    (let ((buffer-read-only po-read-only))
 
1135
      (goto-char po-start-of-entry)
 
1136
      (if (re-search-forward "\n#[,!] .*" po-start-of-msgid t)
 
1137
          (save-restriction
 
1138
            (narrow-to-region (match-beginning 0) (match-end 0))
 
1139
            (goto-char (point-min))
 
1140
            (if (re-search-forward (concat "\\b" name "\\b") nil t)
 
1141
                nil
 
1142
              (goto-char (point-max))
 
1143
              (insert ", " name)))
 
1144
        (skip-chars-forward "\n")
 
1145
        (while (eq (following-char) ?#)
 
1146
          (next-line 1))
 
1147
        (insert "#, " name "\n")))))
 
1148
 
 
1149
(defun po-delete-attribute (name)
 
1150
  "Delete attribute NAME from the current entry, if any."
 
1151
  (save-excursion
 
1152
    (let ((buffer-read-only po-read-only))
 
1153
      (goto-char po-start-of-entry)
 
1154
      (if (re-search-forward "\n#[,!] .*" po-start-of-msgid t)
 
1155
          (save-restriction
 
1156
            (narrow-to-region (match-beginning 0) (match-end 0))
 
1157
            (goto-char (point-min))
 
1158
            (if (re-search-forward
 
1159
                 (concat "\\(\n#[,!] " name "$\\|, " name "$\\| " name ",\\)")
 
1160
                 nil t)
 
1161
                (replace-match "" t t)))))))
 
1162
 
 
1163
;;; Entry positionning.
 
1164
 
 
1165
(defun po-say-location-depth ()
 
1166
  "Tell how many entries in the entry location stack."
 
1167
  (let ((depth (length po-marker-stack)))
 
1168
    (cond ((= depth 0) (message (_"Empty location stack")))
 
1169
          ((= depth 1) (message (_"One entry in location stack")))
 
1170
          (t (message (_"%d entries in location stack") depth)))))
 
1171
 
 
1172
(defun po-push-location ()
 
1173
  "Stack the location of the current entry, for later return."
 
1174
  (interactive)
 
1175
  (po-find-span-of-entry)
 
1176
  (save-excursion
 
1177
    (goto-char po-start-of-msgid)
 
1178
    (setq po-marker-stack (cons (point-marker) po-marker-stack)))
 
1179
  (po-say-location-depth))
 
1180
 
 
1181
(defun po-pop-location ()
 
1182
  "Unstack a saved location, and return to the corresponding entry."
 
1183
  (interactive)
 
1184
  (if po-marker-stack
 
1185
      (progn
 
1186
        (goto-char (car po-marker-stack))
 
1187
        (setq po-marker-stack (cdr po-marker-stack))
 
1188
        (po-current-entry)
 
1189
        (po-say-location-depth))
 
1190
    (error (_"The entry location stack is empty"))))
 
1191
 
 
1192
(defun po-exchange-location ()
 
1193
  "Exchange the location of the current entry with the top of stack."
 
1194
  (interactive)
 
1195
  (if po-marker-stack
 
1196
      (progn
 
1197
        (po-find-span-of-entry)
 
1198
        (goto-char po-start-of-msgid)
 
1199
        (let ((location (point-marker)))
 
1200
          (goto-char (car po-marker-stack))
 
1201
          (setq po-marker-stack (cons location (cdr po-marker-stack))))
 
1202
        (po-current-entry)
 
1203
        (po-say-location-depth))
 
1204
    (error (_"The entry location stack is empty"))))
 
1205
 
 
1206
(defun po-current-entry ()
 
1207
  "Display the current entry."
 
1208
  (interactive)
 
1209
  (po-find-span-of-entry)
 
1210
  (po-redisplay))
 
1211
 
 
1212
(defun po-first-entry-with-regexp (regexp)
 
1213
  "Display the first entry in the file which msgstr matches REGEXP."
 
1214
  (let ((here (point)))
 
1215
    (goto-char (point-min))
 
1216
    (if (re-search-forward regexp nil t)
 
1217
        (progn
 
1218
          (goto-char (match-beginning 0))
 
1219
          (po-current-entry))
 
1220
      (goto-char here)
 
1221
      (error (_"There is no such entry")))))
 
1222
 
 
1223
(defun po-last-entry-with-regexp (regexp)
 
1224
  "Display the last entry in the file which msgstr matches REGEXP."
 
1225
  (let ((here (point)))
 
1226
    (goto-char (point-max))
 
1227
    (if (re-search-backward regexp nil t)
 
1228
        (po-current-entry)
 
1229
      (goto-char here)
 
1230
      (error (_"There is no such entry")))))
 
1231
 
 
1232
(defun po-next-entry-with-regexp (regexp wrap)
 
1233
  "Display the entry following the current entry which msgstr matches REGEXP.
 
1234
If WRAP is not nil, the search may wrap around the buffer."
 
1235
  (po-find-span-of-entry)
 
1236
  (let ((here (point)))
 
1237
    (goto-char po-end-of-entry)
 
1238
    (if (re-search-forward regexp nil t)
 
1239
        (progn
 
1240
          (goto-char (match-beginning 0))
 
1241
          (po-current-entry))
 
1242
      (if (and wrap
 
1243
               (progn
 
1244
                 (goto-char (point-min))
 
1245
                 (re-search-forward regexp po-start-of-entry t)))
 
1246
          (progn
 
1247
            (goto-char (match-beginning 0))
 
1248
            (po-current-entry)
 
1249
            (message (_"Wrapping around the buffer")))
 
1250
        (goto-char here)
 
1251
        (error (_"There is no such entry"))))))
 
1252
 
 
1253
(defun po-previous-entry-with-regexp (regexp wrap)
 
1254
  "Redisplay the entry preceding the current entry which msgstr matches REGEXP.
 
1255
If WRAP is not nil, the search may wrap around the buffer."
 
1256
  (po-find-span-of-entry)
 
1257
  (let ((here (point)))
 
1258
    (goto-char po-start-of-entry)
 
1259
    (if (re-search-backward regexp nil t)
 
1260
        (po-current-entry)
 
1261
      (if (and wrap
 
1262
               (progn
 
1263
                 (goto-char (point-max))
 
1264
                 (re-search-backward regexp po-end-of-entry t)))
 
1265
          (progn
 
1266
            (po-current-entry)
 
1267
            (message (_"Wrapping around the buffer")))
 
1268
        (goto-char here)
 
1269
        (error (_"There is no such entry"))))))
 
1270
 
 
1271
;; Any entries.
 
1272
 
 
1273
(defun po-first-entry ()
 
1274
  "Display the first entry."
 
1275
  (interactive)
 
1276
  (po-first-entry-with-regexp po-any-msgstr-regexp))
 
1277
 
 
1278
(defun po-last-entry ()
 
1279
  "Display the last entry."
 
1280
  (interactive)
 
1281
  (po-last-entry-with-regexp po-any-msgstr-regexp))
 
1282
 
 
1283
(defun po-next-entry ()
 
1284
  "Display the entry following the current entry."
 
1285
  (interactive)
 
1286
  (po-next-entry-with-regexp po-any-msgstr-regexp nil))
 
1287
 
 
1288
(defun po-previous-entry ()
 
1289
  "Display the entry preceding the current entry."
 
1290
  (interactive)
 
1291
  (po-previous-entry-with-regexp po-any-msgstr-regexp nil))
 
1292
 
 
1293
;; Untranslated entries.
 
1294
 
 
1295
(defvar po-after-entry-regexp
 
1296
  "\\(\\'\\|\\(#[ \t]*\\)?[^\"]\\)"
 
1297
  "Regexp which should be true after a full msgstr string matched.")
 
1298
 
 
1299
(defvar po-untranslated-regexp
 
1300
  (concat "^msgstr[ \t]*\"\"\n" po-after-entry-regexp)
 
1301
  "Regexp matching a whole msgstr field, but only if active and empty.")
 
1302
 
 
1303
(defun po-next-untranslated-entry ()
 
1304
  "Find the next untranslated entry, wrapping around if necessary."
 
1305
  (interactive)
 
1306
  (po-next-entry-with-regexp po-untranslated-regexp t))
 
1307
 
 
1308
(defun po-previous-untranslated-entry ()
 
1309
  "Find the previous untranslated entry, wrapping around if necessary."
 
1310
  (interactive)
 
1311
  (po-previous-entry-with-regexp po-untranslated-regexp t))
 
1312
 
 
1313
(defun po-msgid-to-msgstr ()
 
1314
  "Use another window to edit msgstr reinitialized with msgid."
 
1315
  (interactive)
 
1316
  (po-find-span-of-entry)
 
1317
  (if (or (eq po-entry-type 'untranslated)
 
1318
          (eq po-entry-type 'obsolete)
 
1319
          (y-or-n-p (_"Really loose previous translation? ")))
 
1320
      (po-set-msgstr (po-get-msgid nil)))
 
1321
  (message ""))
 
1322
 
 
1323
;; Obsolete entries.
 
1324
 
 
1325
(defvar po-obsolete-msgstr-regexp
 
1326
  "^#~?[ \t]*msgstr.*\n\\(#~?[ \t]*\".*\n\\)*"
 
1327
  "Regexp matching a whole msgstr field of an obsolete entry.")
 
1328
 
 
1329
(defun po-next-obsolete-entry ()
 
1330
  "Find the next obsolete entry, wrapping around if necessary."
 
1331
  (interactive)
 
1332
  (po-next-entry-with-regexp po-obsolete-msgstr-regexp t))
 
1333
 
 
1334
(defun po-previous-obsolete-entry ()
 
1335
  "Find the previous obsolete entry, wrapping around if necessary."
 
1336
  (interactive)
 
1337
  (po-previous-entry-with-regexp po-obsolete-msgstr-regexp t))
 
1338
 
 
1339
;; Fuzzy entries.
 
1340
 
 
1341
(defvar po-fuzzy-regexp "^#[,!] .*fuzzy"
 
1342
  "Regexp matching the string inserted by msgmerge for translations
 
1343
which does not match exactly.")
 
1344
 
 
1345
(defun po-next-fuzzy-entry ()
 
1346
  "Find the next fuzzy entry, wrapping around if necessary."
 
1347
  (interactive)
 
1348
  (po-next-entry-with-regexp po-fuzzy-regexp t))
 
1349
 
 
1350
(defun po-previous-fuzzy-entry ()
 
1351
  "Find the next fuzzy entry, wrapping around if necessary."
 
1352
  (interactive)
 
1353
  (po-previous-entry-with-regexp po-fuzzy-regexp t))
 
1354
 
 
1355
(defun po-unfuzzy ()
 
1356
  "Remove the fuzzy attribute for the current entry."
 
1357
  (interactive)
 
1358
  (po-find-span-of-entry)
 
1359
  (cond ((eq po-entry-type 'fuzzy)
 
1360
         (po-decrease-type-counter)
 
1361
         (po-delete-attribute "fuzzy")
 
1362
         (po-current-entry)
 
1363
         (po-increase-type-counter)))
 
1364
  (if po-auto-select-on-unfuzzy
 
1365
      (po-auto-select-entry))
 
1366
  (po-update-mode-line-string))
 
1367
 
 
1368
;; Translated entries.
 
1369
 
 
1370
(defun po-next-translated-entry ()
 
1371
  "Find the next untranslated entry, wrapping around if necessary."
 
1372
  (interactive)
 
1373
  (if (= po-translated-counter 0)
 
1374
      (error (_"There is no such entry"))
 
1375
    (po-next-entry-with-regexp po-untranslated-regexp t)
 
1376
    (po-find-span-of-entry)
 
1377
    (while (not (eq po-entry-type 'translated))
 
1378
      (po-next-entry-with-regexp po-any-msgstr-regexp t)
 
1379
      (po-find-span-of-entry))))
 
1380
 
 
1381
(defun po-previous-translated-entry ()
 
1382
  "Find the previous untranslated entry, wrapping around if necessary."
 
1383
  (interactive)
 
1384
  (if (= po-translated-counter 0)
 
1385
      (error (_"There is no such entry"))
 
1386
    (po-previous-entry-with-regexp po-any-msgstr-regexp t)
 
1387
    (po-find-span-of-entry)
 
1388
    (while (not (eq po-entry-type 'translated))
 
1389
      (po-previous-entry-with-regexp po-untranslated-regexp t)
 
1390
    (po-find-span-of-entry))))
 
1391
 
 
1392
;; Auto-selection feature.
 
1393
 
 
1394
(defun po-auto-select-entry ()
 
1395
  "Select the next entry having the same type as the current one.
 
1396
If none, wrap from the beginning of the buffer with another type,
 
1397
going from untranslated to fuzzy, and from fuzzy to obsolete.
 
1398
Plain translated entries are always disregarded unless there are
 
1399
no entries of the other types."
 
1400
  (interactive)
 
1401
  (po-find-span-of-entry)
 
1402
  (goto-char po-end-of-entry)
 
1403
  (if (and (= po-untranslated-counter 0)
 
1404
           (= po-fuzzy-counter 0)
 
1405
           (= po-obsolete-counter 0))
 
1406
 
 
1407
      ;; All entries are plain translated.  Next entry will do, or
 
1408
      ;; wrap around if there is none.
 
1409
      (if (re-search-forward po-any-msgstr-regexp nil t)
 
1410
          (goto-char (match-beginning 0))
 
1411
        (goto-char (point-min)))
 
1412
 
 
1413
    ;; If over a translated entry, look for an untranslated one first.
 
1414
    ;; Else, look for an entry of the same type first.
 
1415
    (let ((goal (if (eq po-entry-type 'translated)
 
1416
                    'untranslated
 
1417
                  po-entry-type)))
 
1418
      (while goal
 
1419
 
 
1420
        ;; Find an untranslated entry, or wrap up for a fuzzy entry.
 
1421
        (if (eq goal 'untranslated)
 
1422
            (if (and (> po-untranslated-counter 0)
 
1423
                     (re-search-forward po-untranslated-regexp nil t))
 
1424
                (progn
 
1425
                  (goto-char (match-beginning 0))
 
1426
                  (setq goal nil))
 
1427
              (goto-char (point-min))
 
1428
              (setq goal 'fuzzy)))
 
1429
 
 
1430
        ;; Find a fuzzy entry, or wrap up for an obsolete entry.
 
1431
        (if (eq goal 'fuzzy)
 
1432
            (if (and (> po-fuzzy-counter 0)
 
1433
                     (re-search-forward po-fuzzy-regexp nil t))
 
1434
                (progn
 
1435
                  (goto-char (match-beginning 0))
 
1436
                  (setq goal nil))
 
1437
              (goto-char (point-min))
 
1438
              (setq goal 'obsolete)))
 
1439
 
 
1440
        ;; Find an obsolete entry, or wrap up for an untranslated entry.
 
1441
        (if (eq goal 'obsolete)
 
1442
            (if (and (> po-obsolete-counter 0)
 
1443
                     (re-search-forward po-obsolete-msgstr-regexp nil t))
 
1444
                (progn
 
1445
                  (goto-char (match-beginning 0))
 
1446
                  (setq goal nil))
 
1447
              (goto-char (point-min))
 
1448
              (setq goal 'untranslated))))))
 
1449
 
 
1450
  ;; Display this entry nicely.
 
1451
  (po-current-entry))
 
1452
 
 
1453
;;; Killing and yanking fields.
 
1454
 
 
1455
(defun po-extract-unquoted (buffer start end)
 
1456
  "Extract and return the unquoted string in BUFFER going from START to END.
 
1457
Crumb preceding or following the quoted string is ignored."
 
1458
  (po-with-temp-buffer
 
1459
    (insert-buffer-substring buffer start end)
 
1460
    ;; Remove preceding crumb.
 
1461
    (goto-char (point-min))
 
1462
    (search-forward "\"")
 
1463
    (delete-region (point-min) (point))
 
1464
    ;; Remove following crumb.
 
1465
    (goto-char (point-max))
 
1466
    (search-backward "\"")
 
1467
    (delete-region (point) (point-max))
 
1468
    ;; Glue concatenated strings.
 
1469
    (goto-char (point-min))
 
1470
    (while (re-search-forward "\"[ \t]*\\\\?\n\\(#~?\\)?[ \t]*\"" nil t)
 
1471
      (replace-match "" t t))
 
1472
    ;; Remove escaped newlines.
 
1473
    (goto-char (point-min))
 
1474
    (while (re-search-forward "\\\\[ \t]*\n" nil t)
 
1475
      (replace-match "" t t))
 
1476
    ;; Unquote individual characters.
 
1477
    (goto-char (point-min))
 
1478
    (while (re-search-forward "\\\\[\"abfnt\\0-7]" nil t)
 
1479
      (cond ((eq (preceding-char) ?\") (replace-match "\"" t t))
 
1480
            ((eq (preceding-char) ?a) (replace-match "\a" t t))
 
1481
            ((eq (preceding-char) ?b) (replace-match "\b" t t))
 
1482
            ((eq (preceding-char) ?f) (replace-match "\f" t t))
 
1483
            ((eq (preceding-char) ?n) (replace-match "\n" t t))
 
1484
            ((eq (preceding-char) ?t) (replace-match "\t" t t))
 
1485
            ((eq (preceding-char) ?\\) (replace-match "\\" t t))
 
1486
            (t (let ((value (- (preceding-char) ?0)))
 
1487
                 (replace-match "" t t)
 
1488
                 (while (looking-at "[0-7]")
 
1489
                   (setq value (+ (* 8 value) (- (following-char) ?0)))
 
1490
                   (replace-match "" t t))
 
1491
                 (insert value)))))
 
1492
    (buffer-string)))
 
1493
 
 
1494
(defun po-eval-requoted (form prefix obsolete)
 
1495
  "Eval FORM, which inserts a string, and return the string fully requoted.
 
1496
If PREFIX, precede the result with its contents.  If OBSOLETE, comment all
 
1497
generated lines in the returned string.  Evaluating FORM should insert the
 
1498
wanted string in the buffer which is current at the time of evaluation.
 
1499
If FORM is itself a string, then this string is used for insertion."
 
1500
  (po-with-temp-buffer
 
1501
    (if (stringp form)
 
1502
        (insert form)
 
1503
      (push-mark)
 
1504
      (eval form))
 
1505
    (goto-char (point-min))
 
1506
    (let ((multi-line (re-search-forward "[^\n]\n+[^\n]" nil t)))
 
1507
      (goto-char (point-min))
 
1508
      (while (re-search-forward "[\"\a\b\f\n\t\\]" nil t)
 
1509
        (cond ((eq (preceding-char) ?\") (replace-match "\\\"" t t))
 
1510
              ((eq (preceding-char) ?\a) (replace-match "\\a" t t))
 
1511
              ((eq (preceding-char) ?\b) (replace-match "\\b" t t))
 
1512
              ((eq (preceding-char) ?\f) (replace-match "\\f" t t))
 
1513
              ((eq (preceding-char) ?\n)
 
1514
               (replace-match (if (or (not multi-line) (eobp))
 
1515
                                  "\\n"
 
1516
                                "\\n\"\n\"")
 
1517
                              t t))
 
1518
              ((eq (preceding-char) ?\t) (replace-match "\\t" t t))
 
1519
              ((eq (preceding-char) ?\\) (replace-match "\\\\" t t))))
 
1520
      (goto-char (point-min))
 
1521
      (if prefix (insert prefix " "))
 
1522
      (insert (if multi-line "\"\"\n\"" "\""))
 
1523
      (goto-char (point-max))
 
1524
      (insert "\"")
 
1525
      (if prefix (insert "\n"))
 
1526
      (if obsolete
 
1527
          (progn
 
1528
            (goto-char (point-min))
 
1529
            (while (not (eobp))
 
1530
              (or (eq (following-char) ?\n) (insert "#~ "))
 
1531
              (search-forward "\n"))))
 
1532
      (buffer-string))))
 
1533
 
 
1534
(defun po-get-msgid (kill)
 
1535
  "Extract and return the unquoted msgid string.
 
1536
If KILL, then add the unquoted string to the kill ring."
 
1537
  (let ((string (po-extract-unquoted (current-buffer)
 
1538
                                     po-start-of-msgid po-start-of-msgstr)))
 
1539
    (if kill (po-kill-new string))
 
1540
    string))
 
1541
 
 
1542
(defun po-get-msgstr (kill)
 
1543
  "Extract and return the unquoted msgstr string.
 
1544
If KILL, then add the unquoted string to the kill ring."
 
1545
  (let ((string (po-extract-unquoted (current-buffer)
 
1546
                                     po-start-of-msgstr po-end-of-entry)))
 
1547
    (if kill (po-kill-new string))
 
1548
    string))
 
1549
 
 
1550
(defun po-set-msgid (form)
 
1551
  "Replace the current msgid, using FORM to get a string.
 
1552
Evaluating FORM should insert the wanted string in the current buffer.  If
 
1553
FORM is itself a string, then this string is used for insertion.  The string
 
1554
is properly requoted before the replacement occurs.
 
1555
 
 
1556
Returns `nil' if the buffer has not been modified, for if the new msgid
 
1557
described by FORM is merely identical to the msgid already in place."
 
1558
  (let ((string (po-eval-requoted form "msgid" (eq po-entry-type 'obsolete))))
 
1559
    (save-excursion
 
1560
      (goto-char po-start-of-entry)
 
1561
      (re-search-forward po-any-msgid-regexp po-start-of-msgstr)
 
1562
      (and (not (string-equal (po-buffer-substring (match-beginning 0)
 
1563
                                                   (match-end 0))
 
1564
                              string))
 
1565
           (let ((buffer-read-only po-read-only))
 
1566
             (replace-match string t t)
 
1567
             (goto-char po-start-of-msgid)
 
1568
             (po-find-span-of-entry)
 
1569
             t)))))
 
1570
 
 
1571
(defun po-set-msgstr (form)
 
1572
  "Replace the current msgstr or msgstr[], using FORM to get a string.
 
1573
Evaluating FORM should insert the wanted string in the current buffer.  If
 
1574
FORM is itself a string, then this string is used for insertion.  The string
 
1575
is properly requoted before the replacement occurs.
 
1576
 
 
1577
Returns `nil' if the buffer has not been modified, for if the new msgstr
 
1578
described by FORM is merely identical to the msgstr already in place."
 
1579
  (let ((string (po-eval-requoted form "msgstr" (eq po-entry-type 'obsolete)))
 
1580
        (msgstr-idx nil))
 
1581
    (save-excursion
 
1582
      (goto-char po-start-of-entry)
 
1583
      (save-excursion                   ; check for an indexed msgstr
 
1584
        (when (re-search-forward po-msgstr-idx-keyword-regexp po-end-of-entry t)
 
1585
          (setq msgstr-idx (buffer-substring-no-properties
 
1586
                     (match-beginning 0) (match-end 0)))))
 
1587
      (re-search-forward po-any-msgstr-regexp po-end-of-entry)
 
1588
      (and (not (string-equal (po-buffer-substring (match-beginning 0)
 
1589
                                                   (match-end 0))
 
1590
                              string))
 
1591
           (let ((buffer-read-only po-read-only))
 
1592
             (po-decrease-type-counter)
 
1593
             (replace-match string t t)
 
1594
             (goto-char (match-beginning 0))
 
1595
             (unless (eq msgstr-idx nil) ; hack: replace msgstr with msgstr[d]
 
1596
               (progn
 
1597
                 (insert msgstr-idx)
 
1598
                 (looking-at "\\(#~?[ \t]*\\)?msgstr")
 
1599
                 (replace-match "")))
 
1600
             (goto-char po-start-of-msgid)
 
1601
             (po-find-span-of-entry)
 
1602
             (po-increase-type-counter)
 
1603
             t)))))
 
1604
 
 
1605
(defun po-kill-ring-save-msgstr ()
 
1606
  "Push the msgstr string from current entry on the kill ring."
 
1607
  (interactive)
 
1608
  (po-find-span-of-entry)
 
1609
  (po-get-msgstr t))
 
1610
 
 
1611
(defun po-kill-msgstr ()
 
1612
  "Empty the msgstr string from current entry, pushing it on the kill ring."
 
1613
  (interactive)
 
1614
  (po-kill-ring-save-msgstr)
 
1615
  (po-set-msgstr ""))
 
1616
 
 
1617
(defun po-yank-msgstr ()
 
1618
  "Replace the current msgstr string by the top of the kill ring."
 
1619
  (interactive)
 
1620
  (po-find-span-of-entry)
 
1621
  (po-set-msgstr (if (eq last-command 'yank) '(yank-pop 1) '(yank)))
 
1622
  (setq this-command 'yank))
 
1623
 
 
1624
(defun po-fade-out-entry ()
 
1625
  "Mark an active entry as fuzzy; obsolete a fuzzy or untranslated entry;
 
1626
or completely delete an obsolete entry, saving its msgstr on the kill ring."
 
1627
  (interactive)
 
1628
  (po-find-span-of-entry)
 
1629
 
 
1630
  (cond ((eq po-entry-type 'translated)
 
1631
         (po-decrease-type-counter)
 
1632
         (po-add-attribute "fuzzy")
 
1633
         (po-current-entry)
 
1634
         (po-increase-type-counter))
 
1635
 
 
1636
        ((or (eq po-entry-type 'fuzzy)
 
1637
             (eq po-entry-type 'untranslated))
 
1638
         (if (yes-or-no-p (_"Should I really obsolete this entry? "))
 
1639
             (progn
 
1640
               (po-decrease-type-counter)
 
1641
               (save-excursion
 
1642
                 (save-restriction
 
1643
                   (narrow-to-region po-start-of-entry po-end-of-entry)
 
1644
                   (let ((buffer-read-only po-read-only))
 
1645
                     (goto-char (point-min))
 
1646
                     (skip-chars-forward "\n")
 
1647
                     (while (not (eobp))
 
1648
                       (insert "#~ ")
 
1649
                       (search-forward "\n")))))
 
1650
               (po-current-entry)
 
1651
               (po-increase-type-counter)))
 
1652
         (message ""))
 
1653
 
 
1654
        ((and (eq po-entry-type 'obsolete)
 
1655
              (po-check-for-pending-edit po-start-of-msgid)
 
1656
              (po-check-for-pending-edit po-start-of-msgstr))
 
1657
         (po-decrease-type-counter)
 
1658
         (po-update-mode-line-string)
 
1659
         (po-get-msgstr t)
 
1660
         (let ((buffer-read-only po-read-only))
 
1661
           (delete-region po-start-of-entry po-end-of-entry))
 
1662
         (goto-char po-start-of-entry)
 
1663
         (if (re-search-forward po-any-msgstr-regexp nil t)
 
1664
             (goto-char (match-beginning 0))
 
1665
           (re-search-backward po-any-msgstr-regexp nil t))
 
1666
         (po-current-entry)
 
1667
         (message ""))))
 
1668
 
 
1669
;;; Killing and yanking comments.
 
1670
 
 
1671
(defvar po-active-comment-regexp
 
1672
  "^\\(#\n\\|# .*\n\\)+"
 
1673
  "Regexp matching the whole editable comment part of an active entry.")
 
1674
 
 
1675
(defvar po-obsolete-comment-regexp
 
1676
  "^\\(#~? #\n\\|#~? # .*\n\\)+"
 
1677
  "Regexp matching the whole editable comment part of an obsolete entry.")
 
1678
 
 
1679
(defun po-get-comment (kill-flag)
 
1680
  "Extract and return the editable comment string, uncommented.
 
1681
If KILL-FLAG, then add the unquoted comment to the kill ring."
 
1682
  (let ((buffer (current-buffer))
 
1683
        (obsolete (eq po-entry-type 'obsolete)))
 
1684
    (save-excursion
 
1685
      (goto-char po-start-of-entry)
 
1686
      (if (re-search-forward (if obsolete po-obsolete-comment-regexp
 
1687
                                 po-active-comment-regexp)
 
1688
                             po-end-of-entry t)
 
1689
          (po-with-temp-buffer
 
1690
            (insert-buffer-substring buffer (match-beginning 0) (match-end 0))
 
1691
            (goto-char (point-min))
 
1692
            (while (not (eobp))
 
1693
              (if (looking-at (if obsolete "#~? # ?" "#~? ?"))
 
1694
                  (replace-match "" t t))
 
1695
              (forward-line 1))
 
1696
            (and kill-flag (copy-region-as-kill (point-min) (point-max)))
 
1697
            (buffer-string))
 
1698
        ""))))
 
1699
 
 
1700
(defun po-set-comment (form)
 
1701
  "Using FORM to get a string, replace the current editable comment.
 
1702
Evaluating FORM should insert the wanted string in the current buffer.
 
1703
If FORM is itself a string, then this string is used for insertion.
 
1704
The string is properly recommented before the replacement occurs."
 
1705
  (let ((obsolete (eq po-entry-type 'obsolete))
 
1706
        string)
 
1707
    (po-with-temp-buffer
 
1708
      (if (stringp form)
 
1709
          (insert form)
 
1710
        (push-mark)
 
1711
        (eval form))
 
1712
      (if (not (or (bobp) (= (preceding-char) ?\n)))
 
1713
          (insert "\n"))
 
1714
      (goto-char (point-min))
 
1715
      (while (not (eobp))
 
1716
        (insert (if (= (following-char) ?\n)
 
1717
                    (if obsolete "#~ #" "#")
 
1718
                  (if obsolete "#~ # " "# ")))
 
1719
        (search-forward "\n"))
 
1720
      (setq string (buffer-string)))
 
1721
    (goto-char po-start-of-entry)
 
1722
    (if (re-search-forward
 
1723
         (if obsolete po-obsolete-comment-regexp po-active-comment-regexp)
 
1724
         po-end-of-entry t)
 
1725
        (if (not (string-equal (po-buffer-substring (match-beginning 0)
 
1726
                                                    (match-end 0))
 
1727
                               string))
 
1728
            (let ((buffer-read-only po-read-only))
 
1729
              (replace-match string t t)))
 
1730
      (skip-chars-forward " \t\n")
 
1731
      (let ((buffer-read-only po-read-only))
 
1732
        (insert string))))
 
1733
  (po-current-entry))
 
1734
 
 
1735
(defun po-kill-ring-save-comment ()
 
1736
  "Push the msgstr string from current entry on the kill ring."
 
1737
  (interactive)
 
1738
  (po-find-span-of-entry)
 
1739
  (po-get-comment t))
 
1740
 
 
1741
(defun po-kill-comment ()
 
1742
  "Empty the msgstr string from current entry, pushing it on the kill ring."
 
1743
  (interactive)
 
1744
  (po-kill-ring-save-comment)
 
1745
  (po-set-comment "")
 
1746
  (po-redisplay))
 
1747
 
 
1748
(defun po-yank-comment ()
 
1749
  "Replace the current comment string by the top of the kill ring."
 
1750
  (interactive)
 
1751
  (po-find-span-of-entry)
 
1752
  (po-set-comment (if (eq last-command 'yank) '(yank-pop 1) '(yank)))
 
1753
  (setq this-command 'yank)
 
1754
  (po-redisplay))
 
1755
 
 
1756
;;; Editing management and submode.
 
1757
 
 
1758
;; In a string edit buffer, BACK-POINTER points to one of the slots of the
 
1759
;; list EDITED-FIELDS kept in the PO buffer.  See its description elsewhere.
 
1760
;; Reminder: slots have the form (ENTRY-MARKER EDIT-BUFFER OVERLAY-INFO).
 
1761
 
 
1762
(defvar po-subedit-back-pointer)
 
1763
 
 
1764
(defun po-clean-out-killed-edits ()
 
1765
  "From EDITED-FIELDS, clean out any edit having a killed edit buffer."
 
1766
  (let ((cursor po-edited-fields))
 
1767
    (while cursor
 
1768
      (let ((slot (car cursor)))
 
1769
        (setq cursor (cdr cursor))
 
1770
        (if (buffer-name (nth 1 slot))
 
1771
            nil
 
1772
          (let ((overlay (nth 2 slot)))
 
1773
            (and overlay (po-dehighlight overlay)))
 
1774
          (setq po-edited-fields (delete slot po-edited-fields)))))))
 
1775
 
 
1776
(defun po-check-all-pending-edits ()
 
1777
  "Resume any pending edit.  Return nil if some remains."
 
1778
  (po-clean-out-killed-edits)
 
1779
  (or (null po-edited-fields)
 
1780
      (let ((slot (car po-edited-fields)))
 
1781
        (goto-char (nth 0 slot))
 
1782
        (pop-to-buffer (nth 1 slot))
 
1783
        (let ((overlay (nth 2 slot)))
 
1784
          (and overlay (po-rehighlight overlay)))
 
1785
        (message po-subedit-message)
 
1786
        nil)))
 
1787
 
 
1788
(defun po-check-for-pending-edit (position)
 
1789
  "Resume any pending edit at POSITION.  Return nil if such edit exists."
 
1790
  (po-clean-out-killed-edits)
 
1791
  (let ((marker (make-marker)))
 
1792
    (set-marker marker position)
 
1793
    (let ((slot (assoc marker po-edited-fields)))
 
1794
      (if slot
 
1795
          (progn
 
1796
            (goto-char marker)
 
1797
            (pop-to-buffer (nth 1 slot))
 
1798
            (let ((overlay (nth 2 slot)))
 
1799
              (and overlay (po-rehighlight overlay)))
 
1800
            (message po-subedit-message)))
 
1801
      (not slot))))
 
1802
 
 
1803
(defun po-edit-out-full ()
 
1804
  "Get out of PO mode, leaving PO file buffer in fundamental mode."
 
1805
  (interactive)
 
1806
  (if (and (po-check-all-pending-edits)
 
1807
           (yes-or-no-p (_"Should I let you edit the whole PO file? ")))
 
1808
      (progn
 
1809
        (setq buffer-read-only po-read-only)
 
1810
        (fundamental-mode)
 
1811
        (message (_"Type `M-x po-mode RET' once done")))))
 
1812
 
 
1813
(defvar po-subedit-mode-map nil
 
1814
  "Keymap while editing a PO mode entry (or the full PO file).")
 
1815
(if po-subedit-mode-map
 
1816
    ()
 
1817
  (setq po-subedit-mode-map (make-sparse-keymap))
 
1818
  (define-key po-subedit-mode-map "\C-c\C-a" 'po-subedit-cycle-auxiliary)
 
1819
  (define-key po-subedit-mode-map "\C-c\C-c" 'po-subedit-exit)
 
1820
  (define-key po-subedit-mode-map "\C-c\C-k" 'po-subedit-abort))
 
1821
 
 
1822
(defun po-subedit-abort ()
 
1823
  "Exit the subedit buffer, merely discarding its contents."
 
1824
  (interactive)
 
1825
  (let* ((edit-buffer (current-buffer))
 
1826
         (back-pointer po-subedit-back-pointer)
 
1827
         (marker (nth 0 back-pointer))
 
1828
         (overlay (nth 2 back-pointer))
 
1829
         (buffer (marker-buffer marker)))
 
1830
    (if (null buffer)
 
1831
        (error (_"Corresponding PO buffer does not exist anymore"))
 
1832
      (or (one-window-p) (delete-window))
 
1833
      (switch-to-buffer buffer)
 
1834
      (goto-char marker)
 
1835
      (and overlay (po-dehighlight overlay))
 
1836
      (kill-buffer edit-buffer)
 
1837
      (setq po-edited-fields (delete back-pointer po-edited-fields)))))
 
1838
 
 
1839
(defun po-subedit-exit ()
 
1840
  "Exit the subedit buffer, replacing the string in the PO buffer."
 
1841
  (interactive)
 
1842
  (goto-char (point-max))
 
1843
  (skip-chars-backward " \t\n")
 
1844
  (if (eq (preceding-char) ?<)
 
1845
      (delete-region (1- (point)) (point-max)))
 
1846
  (run-hooks 'po-subedit-exit-hook)
 
1847
  (let ((string (buffer-string)))
 
1848
    (po-subedit-abort)
 
1849
    (po-find-span-of-entry)
 
1850
    (cond ((= (point) po-start-of-msgid)
 
1851
           (po-set-comment string)
 
1852
           (po-redisplay))
 
1853
          ((= (point) po-start-of-msgstr)
 
1854
           (let ((replaced (po-set-msgstr string)))
 
1855
             (if (and replaced
 
1856
                      po-auto-fuzzy-on-edit
 
1857
                      (eq po-entry-type 'translated))
 
1858
                 (progn
 
1859
                   (po-decrease-type-counter)
 
1860
                   (po-add-attribute "fuzzy")
 
1861
                   (po-current-entry)
 
1862
                   (po-increase-type-counter)))))
 
1863
          (t (debug)))))
 
1864
 
 
1865
(defun po-edit-string (string type expand-tabs)
 
1866
  "Prepare a pop up buffer for editing STRING, which is of a given TYPE.
 
1867
TYPE may be 'comment or 'msgstr.  If EXPAND-TABS, expand tabs to spaces.
 
1868
Run functions on po-subedit-mode-hook."
 
1869
  (let ((marker (make-marker)))
 
1870
    (set-marker marker (cond ((eq type 'comment) po-start-of-msgid)
 
1871
                             ((eq type 'msgstr) po-start-of-msgstr)))
 
1872
    (if (po-check-for-pending-edit marker)
 
1873
        (let ((edit-buffer (generate-new-buffer
 
1874
                            (concat "*" (buffer-name) "*")))
 
1875
              (buffer (current-buffer))
 
1876
              overlay slot)
 
1877
          (if (and (eq type 'msgstr) po-highlighting)
 
1878
              ;; ;; Try showing all of msgid in the upper window while editing.
 
1879
              ;; (goto-char (1- po-start-of-msgstr))
 
1880
              ;; (recenter -1)
 
1881
              (save-excursion
 
1882
                (goto-char po-start-of-entry)
 
1883
                (re-search-forward po-any-msgid-regexp nil t)
 
1884
                (let ((end (1- (match-end 0))))
 
1885
                  (goto-char (match-beginning 0))
 
1886
                  (re-search-forward "msgid +" nil t)
 
1887
                  (setq overlay (po-create-overlay))
 
1888
                  (po-highlight overlay (point) end buffer))))
 
1889
          (setq slot (list marker edit-buffer overlay)
 
1890
                po-edited-fields (cons slot po-edited-fields))
 
1891
          (pop-to-buffer edit-buffer)
 
1892
          (make-local-variable 'po-subedit-back-pointer)
 
1893
          (setq po-subedit-back-pointer slot)
 
1894
          (erase-buffer)
 
1895
          (insert string "<")
 
1896
          (goto-char (point-min))
 
1897
          (and expand-tabs (setq indent-tabs-mode nil))
 
1898
          (use-local-map po-subedit-mode-map)
 
1899
          (run-hooks 'po-subedit-mode-hook)
 
1900
          (message po-subedit-message)))))
 
1901
 
 
1902
(defun po-edit-comment ()
 
1903
  "Use another window to edit the current translator comment."
 
1904
  (interactive)
 
1905
  (po-find-span-of-entry)
 
1906
  (po-edit-string (po-get-comment nil) 'comment nil))
 
1907
 
 
1908
(defun po-edit-msgstr ()
 
1909
  "Use another window to edit the current msgstr."
 
1910
  (interactive)
 
1911
  (po-find-span-of-entry)
 
1912
  (po-edit-string (if (and po-auto-edit-with-msgid
 
1913
                           (eq po-entry-type 'untranslated))
 
1914
                      (po-get-msgid nil)
 
1915
                    (po-get-msgstr nil))
 
1916
                  'msgstr
 
1917
                  t))
 
1918
 
 
1919
;;; String normalization and searching.
 
1920
 
 
1921
(defun po-normalize-old-style (explain)
 
1922
  "Normalize old gettext style fields using K&R C multiline string syntax.
 
1923
To minibuffer messages sent while normalizing, add the EXPLAIN string."
 
1924
  (let ((here (point-marker))
 
1925
        (counter 0)
 
1926
        (buffer-read-only po-read-only))
 
1927
    (goto-char (point-min))
 
1928
    (message (_"Normalizing %d, %s") counter explain)
 
1929
    (while (re-search-forward
 
1930
            "\\(^#?[ \t]*msg\\(id\\|str\\)[ \t]*\"\\|[^\" \t][ \t]*\\)\\\\\n"
 
1931
            nil t)
 
1932
      (if (= (% counter 10) 0)
 
1933
          (message (_"Normalizing %d, %s") counter explain))
 
1934
      (replace-match "\\1\"\n\"" t nil)
 
1935
      (setq counter (1+ counter)))
 
1936
    (goto-char here)
 
1937
    (message (_"Normalizing %d...done") counter)))
 
1938
 
 
1939
(defun po-normalize-field (field explain)
 
1940
  "Normalize FIELD of all entries.  FIELD is either the symbol msgid or msgstr.
 
1941
To minibuffer messages sent while normalizing, add the EXPLAIN string."
 
1942
  (let ((here (point-marker))
 
1943
        (counter 0))
 
1944
    (goto-char (point-min))
 
1945
    (while (re-search-forward po-any-msgstr-regexp nil t)
 
1946
      (if (= (% counter 10) 0)
 
1947
          (message (_"Normalizing %d, %s") counter explain))
 
1948
      (goto-char (match-beginning 0))
 
1949
      (po-find-span-of-entry)
 
1950
      (cond ((eq field 'msgid) (po-set-msgid (po-get-msgid nil)))
 
1951
            ((eq field 'msgstr) (po-set-msgstr (po-get-msgstr nil))))
 
1952
      (goto-char po-end-of-entry)
 
1953
      (setq counter (1+ counter)))
 
1954
    (goto-char here)
 
1955
    (message (_"Normalizing %d...done") counter)))
 
1956
 
 
1957
;; Normalize, but the British way! :-)
 
1958
(defsubst po-normalise () (po-normalize))
 
1959
 
 
1960
(defun po-normalize ()
 
1961
  "Normalize all entries in the PO file."
 
1962
  (interactive)
 
1963
  (po-normalize-old-style (_"pass 1/3"))
 
1964
  (po-normalize-field t (_"pass 2/3"))
 
1965
  (po-normalize-field nil (_"pass 3/3"))
 
1966
  ;; The last PO file entry has just been processed.
 
1967
  (if (not (= po-end-of-entry (point-max)))
 
1968
      (let ((buffer-read-only po-read-only))
 
1969
        (kill-region po-end-of-entry (point-max))))
 
1970
  ;; A bizarre format might have fooled the counters, so recompute
 
1971
  ;; them to make sure their value is dependable.
 
1972
  (po-compute-counters nil))
 
1973
 
 
1974
;;; Multiple PO files.
 
1975
 
 
1976
(defun po-show-auxiliary-list ()
 
1977
  "Echo the current auxiliary list in the message area."
 
1978
  (if po-auxiliary-list
 
1979
      (let ((cursor po-auxiliary-cursor)
 
1980
            string)
 
1981
        (while cursor
 
1982
          (setq string (concat string (if string " ") (car (car cursor)))
 
1983
                cursor (cdr cursor)))
 
1984
        (setq cursor po-auxiliary-list)
 
1985
        (while (not (eq cursor po-auxiliary-cursor))
 
1986
          (setq string (concat string (if string " ") (car (car cursor)))
 
1987
                cursor (cdr cursor)))
 
1988
        (message string))
 
1989
    (message (_"No auxiliary files."))))
 
1990
 
 
1991
(defun po-consider-as-auxiliary ()
 
1992
  "Add the current PO file to the list of auxiliary files."
 
1993
  (interactive)
 
1994
  (if (member (list buffer-file-name) po-auxiliary-list)
 
1995
      nil
 
1996
    (setq po-auxiliary-list
 
1997
          (nconc po-auxiliary-list (list (list buffer-file-name))))
 
1998
    (or po-auxiliary-cursor
 
1999
        (setq po-auxiliary-cursor po-auxiliary-list)))
 
2000
  (po-show-auxiliary-list))
 
2001
 
 
2002
(defun po-ignore-as-auxiliary ()
 
2003
  "Delete the current PO file from the list of auxiliary files."
 
2004
  (interactive)
 
2005
  (setq po-auxiliary-list (delete (list buffer-file-name) po-auxiliary-list)
 
2006
        po-auxiliary-cursor po-auxiliary-list)
 
2007
  (po-show-auxiliary-list))
 
2008
 
 
2009
(defun po-seek-equivalent-translation (name string)
 
2010
  "Search a PO file NAME for a `msgid' STRING having a non-empty `msgstr'.
 
2011
STRING is the full quoted msgid field, including the `msgid' keyword.  When
 
2012
found, display the file over the current window, with the `msgstr' field
 
2013
possibly highlighted, the cursor at start of msgid, then return `t'.
 
2014
Otherwise, move nothing, and just return `nil'."
 
2015
  (let ((current (current-buffer))
 
2016
        (buffer (find-file-noselect name)))
 
2017
    (set-buffer buffer)
 
2018
    (let ((start (point))
 
2019
          found)
 
2020
      (goto-char (point-min))
 
2021
      (while (and (not found) (search-forward string nil t))
 
2022
        ;; Screen out longer `msgid's.
 
2023
        (if (looking-at "^msgstr ")
 
2024
            (progn
 
2025
              (po-find-span-of-entry)
 
2026
              ;; Ignore an untranslated entry.
 
2027
              (or (string-equal
 
2028
                   (buffer-substring po-start-of-msgstr po-end-of-entry)
 
2029
                   "msgstr \"\"\n")
 
2030
                  (setq found t)))))
 
2031
      (if found
 
2032
          (progn
 
2033
            (switch-to-buffer buffer)
 
2034
            (po-find-span-of-entry)
 
2035
            (if po-highlighting
 
2036
                (progn
 
2037
                  (goto-char po-start-of-entry)
 
2038
                  (re-search-forward po-any-msgstr-regexp nil t)
 
2039
                  (let ((end (1- (match-end 0))))
 
2040
                    (goto-char (match-beginning 0))
 
2041
                    (re-search-forward "msgstr +" nil t)
 
2042
                    ;; FIXME:
 
2043
                    (po-highlight (po-create-overlay) (point) end))))
 
2044
            (goto-char po-start-of-msgid))
 
2045
        (goto-char start)
 
2046
        (po-find-span-of-entry)
 
2047
        (select-buffer current))
 
2048
      found)))
 
2049
 
 
2050
(defun po-cycle-auxiliary ()
 
2051
  "Select the next auxiliary file having an entry with same `msgid'."
 
2052
  (interactive)
 
2053
  (po-find-span-of-entry)
 
2054
  (if po-auxiliary-list
 
2055
      (let ((string (buffer-substring po-start-of-msgid po-start-of-msgstr))
 
2056
            (cursor po-auxiliary-cursor)
 
2057
            found name)
 
2058
        (while (and (not found) cursor)
 
2059
          (setq name (car (car cursor)))
 
2060
          (if (and (not (string-equal buffer-file-name name))
 
2061
                   (po-seek-equivalent-translation name string))
 
2062
              (setq found t
 
2063
                    po-auxiliary-cursor cursor))
 
2064
          (setq cursor (cdr cursor)))
 
2065
        (setq cursor po-auxiliary-list)
 
2066
        (while (and (not found) cursor)
 
2067
          (setq name (car (car cursor)))
 
2068
          (if (and (not (string-equal buffer-file-name name))
 
2069
                   (po-seek-equivalent-translation name string))
 
2070
              (setq found t
 
2071
                    po-auxiliary-cursor cursor))
 
2072
          (setq cursor (cdr cursor)))
 
2073
        (or found (message (_"No other translation found")))
 
2074
        found)))
 
2075
 
 
2076
(defun po-subedit-cycle-auxiliary ()
 
2077
  "Cycle auxiliary file, but from the translation edit buffer."
 
2078
  (interactive)
 
2079
  (if po-buffer-of-edited-entry
 
2080
      (let ((buffer (current-buffer)))
 
2081
        (pop-to-buffer po-buffer-of-edited-entry)
 
2082
        (po-cycle-auxiliary)
 
2083
        (pop-to-buffer buffer))
 
2084
    (error (_"Not editing a PO file entry"))))
 
2085
 
 
2086
(defun po-select-auxiliary ()
 
2087
  "Select one of the available auxiliary files and locate an equivalent
 
2088
entry.  If an entry having the same `msgid' cannot be found, merely select
 
2089
the file without moving its cursor."
 
2090
  (interactive)
 
2091
  (po-find-span-of-entry)
 
2092
  (if po-auxiliary-list
 
2093
      (let ((string (buffer-substring po-start-of-msgid po-start-of-msgstr))
 
2094
            (name (car (assoc (completing-read (_"Which auxiliary file? ")
 
2095
                                               po-auxiliary-list nil t)
 
2096
                              po-auxiliary-list))))
 
2097
        (po-consider-as-auxiliary)
 
2098
        (or (po-seek-equivalent-translation name string)
 
2099
            (find-file name)))))
 
2100
 
 
2101
;;; Original program sources as context.
 
2102
 
 
2103
(defun po-show-source-path ()
 
2104
  "Echo the current source search path in the message area."
 
2105
  (if po-search-path
 
2106
      (let ((cursor po-search-path)
 
2107
            string)
 
2108
        (while cursor
 
2109
          (setq string (concat string (if string " ") (car (car cursor)))
 
2110
                cursor (cdr cursor)))
 
2111
        (message string))
 
2112
    (message (_"Empty source path."))))
 
2113
 
 
2114
(defun po-consider-source-path (directory)
 
2115
  "Add a given DIRECTORY, requested interactively, to the source search path."
 
2116
  (interactive "DDirectory for search path: ")
 
2117
  (setq po-search-path (cons (list (if (string-match "/$" directory)
 
2118
                                         directory
 
2119
                                       (concat directory "/")))
 
2120
                             po-search-path))
 
2121
  (setq po-reference-check 0)
 
2122
  (po-show-source-path))
 
2123
 
 
2124
(defun po-ignore-source-path ()
 
2125
  "Delete a directory, selected with completion, from the source search path."
 
2126
  (interactive)
 
2127
  (setq po-search-path
 
2128
        (delete (list (completing-read (_"Directory to remove? ")
 
2129
                                       po-search-path nil t))
 
2130
                po-search-path))
 
2131
  (setq po-reference-check 0)
 
2132
  (po-show-source-path))
 
2133
 
 
2134
(defun po-ensure-source-references ()
 
2135
  "Extract all references into a list, with paths resolved, if necessary."
 
2136
  (po-find-span-of-entry)
 
2137
  (if (= po-start-of-entry po-reference-check)
 
2138
      ()
 
2139
    (setq po-reference-alist nil)
 
2140
    (save-excursion
 
2141
      (goto-char po-start-of-entry)
 
2142
      (if (re-search-forward "^#:" po-start-of-msgid t)
 
2143
          (while (looking-at "\\(\n#:\\)? *\\([^: ]+\\):\\([0-9]+\\)")
 
2144
            (goto-char (match-end 0))
 
2145
            (let* ((name (po-buffer-substring (match-beginning 2)
 
2146
                                              (match-end 2)))
 
2147
                   (line (po-buffer-substring (match-beginning 3)
 
2148
                                              (match-end 3)))
 
2149
                   (path po-search-path)
 
2150
                   file)
 
2151
              (while (and (progn (setq file (concat (car (car path)) name))
 
2152
                                 (not (file-exists-p file)))
 
2153
                          path)
 
2154
                (setq path (cdr path)))
 
2155
              (if path
 
2156
                  (setq po-reference-alist
 
2157
                        (cons (list (concat file ":" line)
 
2158
                                    file
 
2159
                                    (string-to-int line))
 
2160
                              po-reference-alist)))))))
 
2161
    (setq po-reference-alist (nreverse po-reference-alist)
 
2162
          po-reference-cursor po-reference-alist
 
2163
          po-reference-check po-start-of-entry)))
 
2164
 
 
2165
(defun po-show-source-context (triplet)
 
2166
  "Show the source context given a TRIPLET which is (PROMPT FILE LINE)."
 
2167
  (find-file-other-window (car (cdr triplet)))
 
2168
  (goto-line (car (cdr (cdr triplet))))
 
2169
  (other-window 1)
 
2170
  (let ((maximum 0)
 
2171
        position
 
2172
        (cursor po-reference-alist))
 
2173
    (while (not (eq triplet (car cursor)))
 
2174
      (setq maximum (1+ maximum)
 
2175
            cursor (cdr cursor)))
 
2176
    (setq position (1+ maximum)
 
2177
          po-reference-cursor cursor)
 
2178
    (while cursor
 
2179
      (setq maximum (1+ maximum)
 
2180
            cursor (cdr cursor)))
 
2181
    (message (_"Displaying %d/%d: \"%s\"") position maximum (car triplet))))
 
2182
 
 
2183
(defun po-cycle-source-reference ()
 
2184
  "Display some source context for the current entry.
 
2185
If the command is repeated many times in a row, cycle through contexts."
 
2186
  (interactive)
 
2187
  (po-ensure-source-references)
 
2188
  (if po-reference-cursor
 
2189
      (po-show-source-context
 
2190
       (car (if (eq last-command 'po-cycle-source-reference)
 
2191
                (or (cdr po-reference-cursor) po-reference-alist)
 
2192
              po-reference-cursor)))
 
2193
    (error (_"No resolved source references"))))
 
2194
 
 
2195
(defun po-select-source-reference ()
 
2196
  "Select one of the available source contexts for the current entry."
 
2197
  (interactive)
 
2198
  (po-ensure-source-references)
 
2199
  (if po-reference-alist
 
2200
      (po-show-source-context
 
2201
       (assoc
 
2202
        (completing-read (_"Which source context? ") po-reference-alist nil t)
 
2203
        po-reference-alist))
 
2204
    (error (_"No resolved source references"))))
 
2205
 
 
2206
;;; Program sources strings though tags table.
 
2207
 
 
2208
;;; C mode.
 
2209
 
 
2210
;;; A few long string cases (submitted by Ben Pfaff).
 
2211
 
 
2212
;; #define string "This is a long string " \
 
2213
;; "that is continued across several lines " \
 
2214
;; "in a macro in order to test \\ quoting\\" \
 
2215
;; "\\ with goofy strings.\\"
 
2216
 
 
2217
;; char *x = "This is just an ordinary string "
 
2218
;; "continued across several lines without needing "
 
2219
;; "to use \\ characters at end-of-line.";
 
2220
 
 
2221
;; char *y = "Here is a string continued across \
 
2222
;; several lines in the manner that was sanctioned \
 
2223
;; in K&R C compilers and still works today, \
 
2224
;; even though the method used above is more esthetic.";
 
2225
 
 
2226
;;; End of long string cases.
 
2227
 
 
2228
(defun po-find-c-string (keywords)
 
2229
  "Find the next C string, excluding those marked by any of KEYWORDS.
 
2230
Returns (START . END) for the found string, or (nil . nil) if none found."
 
2231
  (let (start end)
 
2232
    (while (and (not start)
 
2233
                (re-search-forward "\\([\"']\\|/\\*\\|//\\)" nil t))
 
2234
      (cond ((= (preceding-char) ?*)
 
2235
             ;; Disregard comments.
 
2236
             (search-forward "*/"))
 
2237
 
 
2238
            ((= (preceding-char) ?/)
 
2239
             ;; Disregard C++ comments.
 
2240
             (end-of-line)
 
2241
             (forward-char 1))
 
2242
 
 
2243
            ((= (preceding-char) ?\')
 
2244
             ;; Disregard character constants.
 
2245
             (forward-char (if (= (following-char) ?\\) 3 2)))
 
2246
 
 
2247
            ((save-excursion
 
2248
               (beginning-of-line)
 
2249
               (looking-at "^# *\\(include\\|line\\)"))
 
2250
             ;; Disregard lines being #include or #line directives.
 
2251
             (end-of-line))
 
2252
 
 
2253
            ;; Else, find the end of the (possibly concatenated) string.
 
2254
            (t (setq start (1- (point))
 
2255
                     end nil)
 
2256
               (while (not end)
 
2257
                 (cond ((= (following-char) ?\")
 
2258
                        (if (looking-at "\"[ \t\n\\\\]*\"")
 
2259
                            (goto-char (match-end 0))
 
2260
                          (forward-char 1)
 
2261
                          (setq end (point))))
 
2262
                       ((= (following-char) ?\\) (forward-char 2))
 
2263
                       (t (skip-chars-forward "^\"\\\\"))))
 
2264
 
 
2265
               ;; Check before string for keyword and opening parenthesis.
 
2266
               (goto-char start)
 
2267
               (skip-chars-backward " \n\t")
 
2268
               (if (= (preceding-char) ?\()
 
2269
                   (progn
 
2270
                     (backward-char 1)
 
2271
                     (skip-chars-backward " \n\t")
 
2272
                     (let ((end-keyword (point)))
 
2273
                       (skip-chars-backward "_A-Za-z0-9")
 
2274
                       (if (member (list (po-buffer-substring (point)
 
2275
                                                              end-keyword))
 
2276
                                   keywords)
 
2277
 
 
2278
                           ;; Disregard already marked strings.
 
2279
                           (progn
 
2280
                             (goto-char end)
 
2281
                             (setq start nil
 
2282
                                   end nil)))))))))
 
2283
 
 
2284
    ;; Return the found string, if any.
 
2285
    (cons start end)))
 
2286
 
 
2287
(defun po-mark-c-string (start end keyword)
 
2288
  "Mark the C string, from START to END, with KEYWORD.
 
2289
Return the adjusted value for END."
 
2290
  (goto-char end)
 
2291
  (insert ")")
 
2292
  (goto-char start)
 
2293
  (insert keyword)
 
2294
  (if (not (string-equal keyword "_"))
 
2295
      (progn (insert " ") (setq end (1+ end))))
 
2296
  (insert "(")
 
2297
  (+ end 2 (length keyword)))
 
2298
 
 
2299
;;; Emacs LISP mode.
 
2300
 
 
2301
(defun po-find-emacs-lisp-string (keywords)
 
2302
  "Find the next Emacs LISP string, excluding those marked by any of KEYWORDS.
 
2303
Returns (START . END) for the found string, or (nil . nil) if none found."
 
2304
  (let (start end)
 
2305
    (while (and (not start)
 
2306
                (re-search-forward "[;\"?]" nil t))
 
2307
 
 
2308
      (cond ((= (preceding-char) ?\;)
 
2309
             ;; Disregard comments.
 
2310
             (search-forward "\n"))
 
2311
 
 
2312
            ((= (preceding-char) ?\?)
 
2313
             ;; Disregard character constants.
 
2314
             (forward-char (if (= (following-char) ?\\) 2 1)))
 
2315
 
 
2316
            ;; Else, find the end of the string.
 
2317
            (t (setq start (1- (point)))
 
2318
               (while (not (= (following-char) ?\"))
 
2319
                 (skip-chars-forward "^\"\\\\")
 
2320
                 (if (= (following-char) ?\\) (forward-char 2)))
 
2321
               (forward-char 1)
 
2322
               (setq end (point))
 
2323
 
 
2324
               ;; Check before string for keyword and opening parenthesis.
 
2325
               (goto-char start)
 
2326
               (skip-chars-backward " \n\t")
 
2327
               (let ((end-keyword (point)))
 
2328
                 (skip-chars-backward "-_A-Za-z0-9")
 
2329
                 (if (and (= (preceding-char) ?\()
 
2330
                          (member (list (po-buffer-substring (point)
 
2331
                                                             end-keyword))
 
2332
                                  keywords))
 
2333
 
 
2334
                     ;; Disregard already marked strings.
 
2335
                     (progn
 
2336
                       (goto-char end)
 
2337
                       (setq start nil
 
2338
                             end nil)))))))
 
2339
 
 
2340
    ;; Return the found string, if any.
 
2341
    (cons start end)))
 
2342
 
 
2343
(defun po-mark-emacs-lisp-string (start end keyword)
 
2344
  "Mark the Emacs LISP string, from START to END, with KEYWORD.
 
2345
Return the adjusted value for END."
 
2346
  (goto-char end)
 
2347
  (insert ")")
 
2348
  (goto-char start)
 
2349
  (insert "(" keyword)
 
2350
  (if (not (string-equal keyword "_"))
 
2351
      (progn (insert " ") (setq end (1+ end))))
 
2352
  (+ end 2 (length keyword)))
 
2353
 
 
2354
;;; Processing generic to all programming modes.
 
2355
 
 
2356
(eval-and-compile
 
2357
  (autoload 'visit-tags-table-buffer "etags"))
 
2358
 
 
2359
(defun po-tags-search (restart)
 
2360
  "Find an unmarked translatable string through all files in tags table.
 
2361
Disregard some simple strings which are most probably non-translatable.
 
2362
With prefix argument, restart search at first file."
 
2363
  (interactive "P")
 
2364
 
 
2365
  ;; Take care of restarting the search if necessary.
 
2366
  (if restart (setq po-next-file-list nil))
 
2367
 
 
2368
  ;; Loop doing things until an interesting string is found.
 
2369
  (let ((keywords po-keywords)
 
2370
        found buffer start
 
2371
        (end po-string-end))
 
2372
    (while (not found)
 
2373
 
 
2374
      ;; Reinitialize the source file list if necessary.
 
2375
      (if (not po-next-file-list)
 
2376
          (progn
 
2377
            (setq po-next-file-list
 
2378
                  (save-excursion
 
2379
                    (visit-tags-table-buffer)
 
2380
                    (copy-sequence (tags-table-files))))
 
2381
            (or po-next-file-list (error (_"No files to process")))
 
2382
            (setq end nil)))
 
2383
 
 
2384
      ;; Try finding a string after resuming the search position.
 
2385
      (message (_"Scanning %s...") (car po-next-file-list))
 
2386
      (save-excursion
 
2387
        (setq buffer (find-file-noselect (car po-next-file-list)))
 
2388
        (set-buffer buffer)
 
2389
        (goto-char (or end (point-min)))
 
2390
 
 
2391
        (cond ((member mode-name '("C" "C++"))
 
2392
               (let ((pair (po-find-c-string keywords)))
 
2393
                 (setq start (car pair)
 
2394
                       end (cdr pair))))
 
2395
              ((string-equal mode-name "Emacs-Lisp")
 
2396
               (let ((pair (po-find-emacs-lisp-string keywords)))
 
2397
                 (setq start (car pair)
 
2398
                       end (cdr pair))))
 
2399
              (t (message (_"Unknown source mode for PO mode, skipping..."))
 
2400
                 (setq start nil
 
2401
                       end nil))))
 
2402
 
 
2403
      ;; Advance to next file if no string was found.
 
2404
      (if (not start)
 
2405
          (progn
 
2406
            (setq po-next-file-list (cdr po-next-file-list))
 
2407
            (if po-next-file-list
 
2408
                (setq end nil)
 
2409
              (setq po-string-end nil)
 
2410
              (and po-highlighting (po-dehighlight po-marking-overlay))
 
2411
              (error (_"All files processed"))))
 
2412
 
 
2413
        ;; Push the string just found string into a work buffer for study.
 
2414
        (po-with-temp-buffer
 
2415
         (insert (po-extract-unquoted buffer start end))
 
2416
         (goto-char (point-min))
 
2417
 
 
2418
         ;; Do not disregard if at least three letters in a row.
 
2419
         (if (re-search-forward "[A-Za-z][A-Za-z][A-Za-z]" nil t)
 
2420
             (setq found t)
 
2421
 
 
2422
           ;; Disregard if two letters, and more punctuations than letters.
 
2423
           (if (re-search-forward "[A-Za-z][A-Za-z]" nil t)
 
2424
               (let ((total (buffer-size)))
 
2425
                 (goto-char (point-min))
 
2426
                 (while (re-search-forward "[A-Za-z]+" nil t)
 
2427
                   (replace-match "" t t))
 
2428
                 (if (< (* 2 (buffer-size)) total)
 
2429
                     (setq found t))))
 
2430
 
 
2431
           ;; Disregard if single letters or no letters at all.
 
2432
           ))))
 
2433
 
 
2434
    ;; Ensure the string is being displayed.
 
2435
 
 
2436
    (if (one-window-p t) (split-window) (other-window 1))
 
2437
    (switch-to-buffer buffer)
 
2438
    (goto-char start)
 
2439
    (or (pos-visible-in-window-p start) (recenter '(nil)))
 
2440
    (if (pos-visible-in-window-p end)
 
2441
        (goto-char end)
 
2442
      (goto-char end)
 
2443
      (recenter -1))
 
2444
    (other-window 1)
 
2445
    (and po-highlighting (po-highlight po-marking-overlay start end buffer))
 
2446
 
 
2447
    ;; Save the string for later commands.
 
2448
    (message (_"Scanning %s...done") (car po-next-file-list))
 
2449
    (setq po-string-start start
 
2450
          po-string-end end)))
 
2451
 
 
2452
(defun po-mark-found-string (keyword)
 
2453
  "Mark last found string in program sources as translatable, using KEYWORD."
 
2454
  (and po-highlighting (po-dehighlight po-marking-overlay))
 
2455
  (let ((buffer (find-file-noselect (car po-next-file-list)))
 
2456
        (start po-string-start)
 
2457
        (end po-string-end)
 
2458
        line string)
 
2459
 
 
2460
    ;; Mark string in program sources.
 
2461
    (setq string (po-extract-unquoted buffer start end))
 
2462
    (save-excursion
 
2463
      (set-buffer buffer)
 
2464
      (setq line (count-lines (point-min) start)
 
2465
            end (cond ((member mode-name '("C" "C++"))
 
2466
                       (po-mark-c-string start end keyword))
 
2467
                      ((string-equal mode-name "Emacs-Lisp")
 
2468
                       (po-mark-emacs-lisp-string start end keyword))
 
2469
                      (t (error (_"Cannot mark in unknown source mode"))))))
 
2470
    (setq po-string-end end)
 
2471
 
 
2472
    ;; Add PO file entry.
 
2473
    (let ((buffer-read-only po-read-only))
 
2474
      (goto-char (point-max))
 
2475
      (insert "\n" (format "#: %s:%d\n" (car po-next-file-list) line))
 
2476
      (save-excursion
 
2477
        (insert (po-eval-requoted string "msgid" nil) "msgstr \"\"\n"))
 
2478
      (setq po-untranslated-counter (1+ po-untranslated-counter))
 
2479
      (po-update-mode-line-string))))
 
2480
 
 
2481
(defun po-mark-translatable ()
 
2482
  "Mark last found string in program sources as translatable, using `_'."
 
2483
  (interactive)
 
2484
  (if (and po-string-start po-string-end)
 
2485
      (progn
 
2486
        (po-mark-found-string "_")
 
2487
        (setq po-string-start nil))
 
2488
    (error (_"No such string"))))
 
2489
 
 
2490
(defun po-select-mark-and-mark (arg)
 
2491
  "Mark last found string in program sources as translatable, ask for keywoard,
 
2492
using completion.  With prefix argument, just ask the name of a preferred
 
2493
keyword for subsequent commands, also added to possible completions."
 
2494
  (interactive "P")
 
2495
  (if arg
 
2496
      (let ((keyword (list (read-from-minibuffer (_"Keyword: ")))))
 
2497
        (setq po-keywords (cons keyword (delete keyword po-keywords))))
 
2498
    (if (and po-string-start po-string-end)
 
2499
        (let* ((default (car (car po-keywords)))
 
2500
               (keyword (completing-read (format (_"Mark with keywoard? [%s] ")
 
2501
                                                 default)
 
2502
                                         po-keywords nil t )))
 
2503
          (if (string-equal keyword "") (setq keyword default))
 
2504
          (po-mark-found-string keyword)
 
2505
          (setq po-string-start nil))
 
2506
      (error (_"No such string")))))
 
2507
 
 
2508
;;; Miscellaneous features.
 
2509
 
 
2510
(defun po-help ()
 
2511
  "Provide an help window for PO mode."
 
2512
  (interactive)
 
2513
  (po-with-temp-buffer
 
2514
   (insert po-help-display-string)
 
2515
   (goto-char (point-min))
 
2516
   (save-window-excursion
 
2517
     (switch-to-buffer (current-buffer))
 
2518
     (delete-other-windows)
 
2519
     (message (_"Type any character to continue"))
 
2520
     (po-read-event))))
 
2521
 
 
2522
(defun po-undo ()
 
2523
  "Undo the last change to the PO file."
 
2524
  (interactive)
 
2525
  (let ((buffer-read-only po-read-only))
 
2526
    (undo))
 
2527
  (po-compute-counters nil))
 
2528
 
 
2529
(defun po-statistics ()
 
2530
  "Say how many entries in each category, and the current position."
 
2531
  (interactive)
 
2532
  (po-compute-counters t))
 
2533
 
 
2534
(defun po-validate ()
 
2535
  "Use `msgfmt' for validating the current PO file contents."
 
2536
  (interactive)
 
2537
 
 
2538
  ;; If modifications were done already, change the last revision date.
 
2539
  (if (buffer-modified-p)
 
2540
      (po-replace-revision-date))
 
2541
 
 
2542
  ;; This `let' is for protecting the previous value of compile-command.
 
2543
  (let ((compile-command (concat po-msgfmt-program
 
2544
                                 " --statistics -c -v -o /dev/null "
 
2545
                                 buffer-file-name)))
 
2546
    (compile compile-command)))
 
2547
 
 
2548
(defun po-guess-archive-name ()
 
2549
  "Return the ideal file name for this PO file in the central archives."
 
2550
  (let (start-of-header end-of-header package version team)
 
2551
    (save-excursion
 
2552
      ;; Find the PO file header entry.
 
2553
      (goto-char (point-min))
 
2554
      (re-search-forward po-any-msgstr-regexp)
 
2555
      (setq start-of-header (match-beginning 0)
 
2556
            end-of-header (match-end 0))
 
2557
      ;; Get the package and version.
 
2558
      (goto-char start-of-header)
 
2559
      (if (re-search-forward
 
2560
           "\n\"Project-Id-Version:\\( GNU\\)? \\([^\n ]+\\) \\([^\n ]+\\)\\\\n\"$"
 
2561
           end-of-header t)
 
2562
          (setq package (buffer-substring (match-beginning 2) (match-end 2))
 
2563
                version (buffer-substring (match-beginning 3) (match-end 3))))
 
2564
      (if (or (not package) (string-equal package "PACKAGE")
 
2565
              (not version) (string-equal version "VERSION"))
 
2566
          (error (_"Project-Id-Version field does not have a proper value")))
 
2567
      ;; Get the team.
 
2568
      (goto-char start-of-header)
 
2569
      (if (re-search-forward "\n\"Language-Team:.*<\\(.*\\)@li.org>\\\\n\"$"
 
2570
                             end-of-header t)
 
2571
          (setq team (buffer-substring (match-beginning 1) (match-end 1))))
 
2572
      (if (or (not team) (string-equal team "LL"))
 
2573
          (error (_"Language-Team field does not have a proper value")))
 
2574
      ;; Compose the name.
 
2575
      (concat package "-" version "." team ".po"))))
 
2576
 
 
2577
(defun po-guess-team-address ()
 
2578
  "Return the team address related to this PO file."
 
2579
  (let (team)
 
2580
    (save-excursion
 
2581
      (goto-char (point-min))
 
2582
      (re-search-forward po-any-msgstr-regexp)
 
2583
      (goto-char (match-beginning 0))
 
2584
      (if (re-search-forward
 
2585
           "\n\"Language-Team: +\\(.*<\\(.*\\)@li.org>\\)\\\\n\"$"
 
2586
           (match-end 0) t)
 
2587
          (setq team (buffer-substring (match-beginning 2) (match-end 2))))
 
2588
      (if (or (not team) (string-equal team "LL"))
 
2589
          (error (_"Language-Team field does not have a proper value")))
 
2590
      (buffer-substring (match-beginning 1) (match-end 1)))))
 
2591
 
 
2592
(defun po-send-mail ()
 
2593
  "Start composing a letter, possibly including the current PO file."
 
2594
  (interactive)
 
2595
  (let* ((team-flag (y-or-n-p
 
2596
                     (_"\
 
2597
Write to your team? (`n' means writing to translation project) ")))
 
2598
         (address (if team-flag
 
2599
                      (po-guess-team-address)
 
2600
                    po-translation-project-address)))
 
2601
    (if (not (y-or-n-p (_"Include current PO file? ")))
 
2602
        (apply po-compose-mail-function address
 
2603
               (read-string (_"Subject? ")) nil)
 
2604
      (if (buffer-modified-p)
 
2605
          (error (_"The file is not even saved, you did not validate it.")))
 
2606
      (if (and (y-or-n-p (_"You validated (`V') this file, didn't you? "))
 
2607
               (or (zerop po-untranslated-counter)
 
2608
                   (y-or-n-p
 
2609
                    (format (_"%d entries are untranslated, include anyway? ")
 
2610
                            po-untranslated-counter)))
 
2611
               (or (zerop po-fuzzy-counter)
 
2612
                   (y-or-n-p
 
2613
                    (format (_"%d entries are still fuzzy, include anyway? ")
 
2614
                            po-fuzzy-counter)))
 
2615
               (or (zerop po-obsolete-counter)
 
2616
                   (y-or-n-p
 
2617
                    (format (_"%d entries are obsolete, include anyway? ")
 
2618
                            po-obsolete-counter))))
 
2619
          (let ((buffer (current-buffer))
 
2620
                (name (po-guess-archive-name))
 
2621
                (transient-mark-mode nil))
 
2622
            (apply po-compose-mail-function address
 
2623
                   (if team-flag
 
2624
                       (read-string (_"Subject? "))
 
2625
                     (format "TP-Robot %s" name))
 
2626
                   nil)
 
2627
            (goto-char (point-min))
 
2628
            (re-search-forward
 
2629
             (concat "^" (regexp-quote mail-header-separator) "\n"))
 
2630
            (save-excursion
 
2631
              (insert-buffer buffer)
 
2632
              (shell-command-on-region
 
2633
               (region-beginning) (region-end)
 
2634
               (concat po-gzip-uuencode-command " " name ".gz") t))))))
 
2635
  (message ""))
 
2636
 
 
2637
(defun po-confirm-and-quit ()
 
2638
  "Confirm if quit should be attempted and then, do it.
 
2639
This is a failsafe.  Confirmation is asked if only the real quit would not."
 
2640
  (interactive)
 
2641
  (if (po-check-all-pending-edits)
 
2642
      (progn
 
2643
        (if (or (buffer-modified-p)
 
2644
                (> po-untranslated-counter 0)
 
2645
                (> po-fuzzy-counter 0)
 
2646
                (> po-obsolete-counter 0)
 
2647
                (y-or-n-p (_"Really quit editing this PO file? ")))
 
2648
            (po-quit))
 
2649
        (message ""))))
 
2650
 
 
2651
(defun po-quit ()
 
2652
  "Save the PO file and kill buffer.  However, offer validation if
 
2653
appropriate and ask confirmation if untranslated strings remain."
 
2654
  (interactive)
 
2655
  (if (po-check-all-pending-edits)
 
2656
      (let ((quit t))
 
2657
 
 
2658
        ;; Offer validation of newly modified entries.
 
2659
        (if (and (buffer-modified-p)
 
2660
                 (not (y-or-n-p
 
2661
                       (_"File was modified; skip validation step? "))))
 
2662
            (progn
 
2663
              (message "")
 
2664
              (po-validate)
 
2665
              ;; If we knew that the validation was all successful, we should
 
2666
              ;; just quit.  But since we do not know yet, as the validation
 
2667
              ;; might be asynchronous with PO mode commands, the safest is to
 
2668
              ;; stay within PO mode, even if this implies that another
 
2669
              ;; `po-quit' command will be later required to exit for true.
 
2670
              (setq quit nil)))
 
2671
 
 
2672
        ;; Offer to work on untranslated entries.
 
2673
        (if (and quit
 
2674
                 (or (> po-untranslated-counter 0)
 
2675
                     (> po-fuzzy-counter 0)
 
2676
                     (> po-obsolete-counter 0))
 
2677
                 (not (y-or-n-p
 
2678
                       (_"Unprocessed entries remain; quit anyway? "))))
 
2679
            (progn
 
2680
              (setq quit nil)
 
2681
              (po-auto-select-entry)))
 
2682
 
 
2683
        ;; Clear message area.
 
2684
        (message "")
 
2685
 
 
2686
        ;; Or else, kill buffers and quit for true.
 
2687
        (if quit
 
2688
            (progn
 
2689
              (and (buffer-modified-p) (po-replace-revision-date))
 
2690
              (save-buffer)
 
2691
              (kill-buffer (current-buffer)))))))
 
2692
 
 
2693
;;; po-mode.el ends here