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