diff --git a/etc/ChangeLog b/etc/ChangeLog index fb751b8ac22..70c90e122c7 100644 --- a/etc/ChangeLog +++ b/etc/ChangeLog @@ -1,4 +1,4 @@ -2005-09-00 Kevin Ryde +2005-09-09 Kevin Ryde * MORE.STUFF: Update url for calculator.el. diff --git a/lisp/ChangeLog b/lisp/ChangeLog index 92b5fc68bfd..2074b5d3f02 100644 --- a/lisp/ChangeLog +++ b/lisp/ChangeLog @@ -1,3 +1,74 @@ +2005-09-13 Chong Yidong + + * custom.el (custom-push-theme): Handle the case where a symbol is + bound but face properties have not yet been assigned. + + * mail/sendmail.el (mail): Use new buffer if `noerase' argument is + `new'. + +2005-09-12 Richard M. Stallman + + * font-lock.el (font-lock-keywords): Add autoload. + + * help-fns.el (describe-variable): Rearrange to put source link + in a predictable place. + + * net/newsticker.el: New file. + +2005-09-12 Stefan Monnier + + * calendar/calendar.el (calendar-for-loop): Add edebug info. + (calendar-basic-setup): Use the new nil arg. + (number-of-diary-entries): Move to diary-lib.el. + + * calendar/diary-lib.el: Use with-current-buffer, match-string. + (diary-list-entries): Use with-syntax-table and dolist. + Rename from list-diary-entries. + Use number-of-diary-entries if `number' is nil. + (diary, diary-view-entries): Use this new name and new nil arg value. + (number-of-diary-entries): Move from calendar.el. + (diary-unhide-everything): New function. + (include-other-diary-files, fancy-diary-display) + (diary-show-all-entries, make-diary-entry): Use it. + (diary-mail-entries): Use buffer-string. + (mark-diary-entries): Fix long standing paren typo. + (diary-sexp-entry): Use count-lines. + (make-diary-entry): Avoid `previous-line'. + (diary-mode-map): New var. + (diary-mode): Redraw cal after saving. Setup header-line. + (fancy-diary-display-mode): Use local-set-key. + + * startup.el (command-line): Try calling various terminal-init-foo-bar + functions by stripping hyphenated suffixes from $TERM. + + * files.el (normal-mode): Check boundness of font-lock-keywords. + +2005-09-12 Richard M. Stallman + + * progmodes/compile.el: Don't decide a file's directory + until the user actually tries to go there. + (compilation-next-error-function): + Pass compilation-find-file the directory from the file-struct. + (compilation-internal-error-properties): Separate local FILE-STRUCT + from FILE. Doc the args better. Rename arg FMT to FMTS. + (compilation-find-file): Arg DIR renamed to DIRECTORY. + Expand it, and if nil, use default-directory. + (compilation-get-file-structure): Don't mix specified directory + with default directory. Put specified directory into + file-struct. Don't make the file name absolute. + + * progmodes/compile.el (compilation-error-regexp-alist): Doc fix. + (compile-command): Add autoload. + (compilation-disable-input): Add autoload. + +2005-09-11 Stephen Gildea + + * time-stamp.el: Mention variable `time-stamp-pattern' in doc + strings of the variables it can override. + + (time-stamp): New (as yet undocumented) time-stamp-format escapes + %Q and %q, for fully-qualified domain name and unqualified host name. + 2005-09-11 Kim F. Storm * emacs-lisp/authors.el (authors-aliases): Update list. @@ -15,14 +86,14 @@ 2005-09-10 Pascal Dupuis (tiny change) - * progmodes/octave-inf.el (inferior-octave-startup): Resync - current dir at the end. + * progmodes/octave-inf.el (inferior-octave-startup): + Resync current dir at the end. 2005-09-10 Emilio C. Lopes - * woman.el (woman-topic-at-point-default): Renamed to - woman-use-topic-at-point-default. - (woman-topic-at-point): Renamed to woman-use-topic-at-point. + * woman.el (woman-topic-at-point-default): + Rename to woman-use-topic-at-point-default. + (woman-topic-at-point): Rename to woman-use-topic-at-point. (woman-file-name): Reflect renames above. Automatically use the word at point as topic if woman-use-topic-at-point is non-nil. Otherwise offer it as default but don't insert it in the @@ -40,28 +111,26 @@ (menu-bar-non-minibuffer-window-p): New functions. ("Split Window", "Save As..."): Use them. ("Postscript Print Buffer (B+W)", "Postscript Print Buffer") - ("Print Buffer", "Truncate Long Lines in this Buffer"): Use - menu-bar-menu-frame-live-and-visible-p. + ("Print Buffer", "Truncate Long Lines in this Buffer"): + Use menu-bar-menu-frame-live-and-visible-p. ("Save Buffer", "Insert File", "Open Directory...") - ("Open File...", "Visit New File..."): Use - menu-bar-non-minibuffer-window-p. - (kill-this-buffer-enabled-p, dired ): Use - menu-bar-non-minibuffer-window-p. + ("Open File...", "Visit New File..."): + Use menu-bar-non-minibuffer-window-p. + (kill-this-buffer-enabled-p, dired ): + Use menu-bar-non-minibuffer-window-p. 2005-09-09 Eli Zaretskii * cus-start.el (all): Don't complain about fringe-related built-ins if fringes are not supported. Ditto about - selection-related built-ins. Fix the test for GTK-related - built-ins. + selection-related built-ins. Fix the test for GTK-related built-ins. * menu-bar.el ("Split Window", "Postscript Print Buffer (B+W)") ("Postscript Print Buffer", "Print Region", "Save As...") ("Save", "Insert File...", "Open Directory...") ("Open File...", "Visit New File..."") ("Truncate Long Lines in this Buffer"): Don't look at - menu-updating-frame if this display does not support multiple - frames. + menu-updating-frame if this display does not support multiple frames. 2005-09-09 Frederik Fouvry @@ -94,15 +163,6 @@ * descr-text.el (describe-property-list): Handle non-symbol prop names. -2005-09-06 Stefan Monnier - - * net/ange-ftp.el (ange-ftp-process-filter): Revert to ^#+$. - Use with-current-buffer. - (ange-ftp-gwp-start): Remove unused var `gw-user'. - (ange-ftp-guess-hash-mark-size): Remove unused var `result'. - (ange-ftp-insert-directory): Remove unused var `short'. - (ange-ftp-file-name-sans-versions): Remove unused var `host-type'. - 2005-08-30 Richard M. Stallman * simple.el (blink-matching-open): Get rid of text props from @@ -138,6 +198,13 @@ (recentf-open-files): Use it. (recentf-open-file-with-key): New command. +2005-09-08 Chong Yidong + + * buff-menu.el (Buffer-menu-sort-by-column): New function. + Suggested by Kim F. Storm. + (Buffer-menu-sort-button-map): Global keymap for sort buttons. + (Buffer-menu-make-sort-button): Use global keymap. + 2005-09-07 Michael Albinus * woman.el (top): Remap `man' command by `woman' in `woman-mode-map'. @@ -163,15 +230,15 @@ * calc/calc-poly.el (math-expand-term): Multiply out any powers when in matrix mode. -2005-09-08 Chong Yidong - - * buff-menu.el (Buffer-menu-sort-by-column): New function. - Suggested by Kim F. Storm. - (Buffer-menu-sort-button-map): Global keymap for sort buttons. - (Buffer-menu-make-sort-button): Use global keymap. - 2005-09-06 Stefan Monnier + * net/ange-ftp.el (ange-ftp-process-filter): Revert to ^#+$. + Use with-current-buffer. + (ange-ftp-gwp-start): Remove unused var `gw-user'. + (ange-ftp-guess-hash-mark-size): Remove unused var `result'. + (ange-ftp-insert-directory): Remove unused var `short'. + (ange-ftp-file-name-sans-versions): Remove unused var `host-type'. + * buff-menu.el (Buffer-menu-make-sort-button): Add docstrings, use non-anonymous functions. diff --git a/lisp/calendar/calendar.el b/lisp/calendar/calendar.el index 3e075b9d6bd..2d2e5256977 100644 --- a/lisp/calendar/calendar.el +++ b/lisp/calendar/calendar.el @@ -163,35 +163,6 @@ be overridden by the value of `calendar-setup'." :type 'boolean :group 'diary) -;;;###autoload -(defcustom number-of-diary-entries 1 - "*Specifies how many days of diary entries are to be displayed initially. -This variable affects the diary display when the command \\[diary] is used, -or if the value of the variable `view-diary-entries-initially' is t. For -example, if the default value 1 is used, then only the current day's diary -entries will be displayed. If the value 2 is used, then both the current -day's and the next day's entries will be displayed. - -The value can also be a vector such as [0 2 2 2 2 4 1]; this value -says to display no diary entries on Sunday, the display the entries -for the current date and the day after on Monday through Thursday, -display Friday through Monday's entries on Friday, and display only -Saturday's entries on Saturday. - -This variable does not affect the diary display with the `d' command -from the calendar; in that case, the prefix argument controls the -number of days of diary entries displayed." - :type '(choice (integer :tag "Entries") - (vector :value [0 0 0 0 0 0 0] - (integer :tag "Sunday") - (integer :tag "Monday") - (integer :tag "Tuesday") - (integer :tag "Wednesday") - (integer :tag "Thursday") - (integer :tag "Friday") - (integer :tag "Saturday"))) - :group 'diary) - ;;;###autoload (defcustom mark-diary-entries-in-calendar nil "*Non-nil means mark dates with diary entries, in the calendar window. @@ -393,7 +364,7 @@ functions that move by days and weeks." For example, - (add-hook 'calendar-move-hook (lambda () (view-diary-entries 1))) + (add-hook 'calendar-move-hook (lambda () (diary-view-entries 1))) redisplays the diary for whatever date the cursor is moved to." :type 'hook @@ -1335,6 +1306,7 @@ A negative YR is interpreted as BC; -1 being 1 BC, and so on." (defmacro calendar-for-loop (var from init to final do &rest body) "Execute a for loop." + (declare (debug (symbolp "from" form "to" form "do" body))) `(let ((,var (1- ,init))) (while (>= ,final (setq ,var (1+ ,var))) ,@body))) @@ -1651,10 +1623,7 @@ to be replaced by asterisks to highlight it whenever it is in the window." (increment-calendar-month month year (- calendar-offset)) (generate-calendar-window month year) (if (and view-diary-entries-initially (calendar-date-is-visible-p date)) - (view-diary-entries - (if (vectorp number-of-diary-entries) - (aref number-of-diary-entries (calendar-day-of-week date)) - number-of-diary-entries)))) + (diary-view-entries))) (let* ((diary-buffer (get-file-buffer diary-file)) (diary-window (if diary-buffer (get-buffer-window diary-buffer))) (split-height-threshold (if diary-window 2 1000))) @@ -1662,7 +1631,7 @@ to be replaced by asterisks to highlight it whenever it is in the window." (list-calendar-holidays))) (run-hooks 'initial-calendar-window-hook)) -(autoload 'view-diary-entries "diary-lib" +(autoload 'diary-view-entries "diary-lib" "Prepare and display a buffer with diary entries. Searches your diary file for entries that match ARG days starting with the date indicated by the cursor position in the displayed three-month @@ -2272,7 +2241,7 @@ movement commands will not work correctly." (define-key calendar-mode-map "x" 'mark-calendar-holidays) (define-key calendar-mode-map "u" 'calendar-unmark) (define-key calendar-mode-map "m" 'mark-diary-entries) - (define-key calendar-mode-map "d" 'view-diary-entries) + (define-key calendar-mode-map "d" 'diary-view-entries) (define-key calendar-mode-map "D" 'view-other-diary-entries) (define-key calendar-mode-map "s" 'show-all-diary-entries) (define-key calendar-mode-map "pd" 'calendar-print-day-of-year) @@ -2493,8 +2462,7 @@ the STRINGS are just concatenated and the result truncated." (defun update-calendar-mode-line () "Update the calendar mode line with the current date and date style." (if (bufferp (get-buffer calendar-buffer)) - (save-excursion - (set-buffer calendar-buffer) + (with-current-buffer calendar-buffer (setq mode-line-format (calendar-string-spread (let ((date (condition-case nil @@ -2589,14 +2557,15 @@ ERROR is t, otherwise just returns nil." (list month (string-to-number (buffer-substring (1+ (point)) (+ 4 (point)))) year)) - (if (looking-at "\\*") - (save-excursion - (re-search-backward "[^*]") - (if (looking-at ".\\*\\*") - (list month calendar-starred-day year) - (if error (error "Not on a date!")))) + (if (and (looking-at "\\*") + (save-excursion + (re-search-backward "[^*]") + (looking-at ".\\*\\*"))) + (list month calendar-starred-day year) (if error (error "Not on a date!")))))) +(add-to-list 'debug-ignored-errors "Not on a date!") + ;; The following version of calendar-gregorian-from-absolute is preferred for ;; reasons of clarity, BUT it's much slower than the version that follows it. @@ -3071,8 +3040,7 @@ Defaults to today's date if DATE is not given." "Show dates on other calendars for date under the cursor." (interactive) (let* ((date (calendar-cursor-to-date t))) - (save-excursion - (set-buffer (get-buffer-create other-calendars-buffer)) + (with-current-buffer (get-buffer-create other-calendars-buffer) (setq buffer-read-only nil) (calendar-set-mode-line (concat (calendar-date-string date) " (Gregorian)")) @@ -3138,9 +3106,9 @@ Defaults to today's date if DATE is not given." (provide 'calendar) -;;; Local variables: -;;; byte-compile-dynamic: t -;;; End: +;; Local variables: +;; byte-compile-dynamic: t +;; End: -;;; arch-tag: 19c61596-c8fb-4c69-bcf1-7dd739919cd8 +;; arch-tag: 19c61596-c8fb-4c69-bcf1-7dd739919cd8 ;;; calendar.el ends here diff --git a/lisp/calendar/diary-lib.el b/lisp/calendar/diary-lib.el index 1501131c2ae..b35b7287a44 100644 --- a/lisp/calendar/diary-lib.el +++ b/lisp/calendar/diary-lib.el @@ -58,21 +58,17 @@ does nothing. This function is suitable for execution in a `.emacs' file." (interactive "P") (diary-check-diary-file) (let ((date (calendar-current-date))) - (list-diary-entries - date - (cond (arg (prefix-numeric-value arg)) - ((vectorp number-of-diary-entries) - (aref number-of-diary-entries (calendar-day-of-week date))) - (t number-of-diary-entries))))) + (diary-list-entries date (if arg (prefix-numeric-value arg))))) -(defun view-diary-entries (arg) +(define-obsolete-function-alias 'view-diary-entries 'diary-view-entries) +(defun diary-view-entries (&optional arg) "Prepare and display a buffer with diary entries. Searches the file named in `diary-file' for entries that match ARG days starting with the date indicated by the cursor position in the displayed three-month calendar." (interactive "p") (diary-check-diary-file) - (list-diary-entries (calendar-cursor-to-date t) arg)) + (diary-list-entries (calendar-cursor-to-date t) arg)) (defun view-other-diary-entries (arg d-file) "Prepare and display buffer of diary entries from an alternative diary file. @@ -182,14 +178,15 @@ The holidays are those in the list `calendar-holidays'.") "Local time of candle lighting diary entry--applies if date is a Friday. No diary entry if there is no sunset on that date.") -(defvar diary-syntax-table (copy-syntax-table (standard-syntax-table)) +(defvar diary-syntax-table + (let ((st (copy-syntax-table (standard-syntax-table)))) + (modify-syntax-entry ?* "w" st) + (modify-syntax-entry ?: "w" st) + st) "The syntax table used when parsing dates in the diary file. It is the standard syntax table used in Fundamental mode, but with the syntax of `*' and `:' changed to be word constituents.") -(modify-syntax-entry ?* "w" diary-syntax-table) -(modify-syntax-entry ?: "w" diary-syntax-table) - (defvar diary-entries-list) (defvar displayed-year) (defvar displayed-month) @@ -243,9 +240,7 @@ search." regexp (concat diary-glob-file-regexp-prefix regexp)) (setq attrvalue nil) (if (re-search-forward regexp (point-max) t) - (setq attrvalue (buffer-substring-no-properties - (match-beginning regnum) - (match-end regnum)))) + (setq attrvalue (match-string-no-properties regnum))) (if (and attrvalue (setq attrvalue (diary-attrtype-convert attrvalue type))) (setq ret-attr (append ret-attr (list attrname attrvalue)))) @@ -264,9 +259,7 @@ search." (setq attrvalue nil) (if (string-match regexp entry) (progn - (setq attrvalue (substring-no-properties entry - (match-beginning regnum) - (match-end regnum))) + (setq attrvalue (match-string-no-properties regnum entry)) (setq entry (replace-match "" t t entry)))) (if (and attrvalue (setq attrvalue (diary-attrtype-convert attrvalue type))) @@ -299,8 +292,38 @@ Only used if `diary-header-line-flag' is non-nil." (defvar diary-saved-point) ; internal -(defun list-diary-entries (date number) - "Create and display a buffer containing the relevant lines in diary-file. + +(defcustom number-of-diary-entries 1 + "Specifies how many days of diary entries are to be displayed initially. +This variable affects the diary display when the command \\[diary] is used, +or if the value of the variable `view-diary-entries-initially' is t. For +example, if the default value 1 is used, then only the current day's diary +entries will be displayed. If the value 2 is used, then both the current +day's and the next day's entries will be displayed. + +The value can also be a vector such as [0 2 2 2 2 4 1]; this value +says to display no diary entries on Sunday, the display the entries +for the current date and the day after on Monday through Thursday, +display Friday through Monday's entries on Friday, and display only +Saturday's entries on Saturday. + +This variable does not affect the diary display with the `d' command +from the calendar; in that case, the prefix argument controls the +number of days of diary entries displayed." + :type '(choice (integer :tag "Entries") + (vector :value [0 0 0 0 0 0 0] + (integer :tag "Sunday") + (integer :tag "Monday") + (integer :tag "Tuesday") + (integer :tag "Wednesday") + (integer :tag "Thursday") + (integer :tag "Friday") + (integer :tag "Saturday"))) + :group 'diary) + +(define-obsolete-function-alias 'list-diary-entries 'diary-list-entries) +(defun diary-list-entries (date number) + "Create and display a buffer containing the relevant lines in `diary-file'. The arguments are DATE and NUMBER; the entries selected are those for NUMBER days starting with date DATE. The other entries are hidden using selective display. If NUMBER is less than 1, this function does nothing. @@ -332,10 +355,12 @@ These hooks have the following distinct roles: `diary-hook' is run last. This can be used for an appointment notification function." - + (unless number + (setq number (if (vectorp number-of-diary-entries) + (aref number-of-diary-entries (calendar-day-of-week date)) + number-of-diary-entries))) (when (> number 0) (let ((original-date date);; save for possible use in the hooks - old-diary-syntax-table diary-entries-list file-glob-attrs (date-string (calendar-date-string date)) @@ -356,100 +381,94 @@ These hooks have the following distinct roles: (setq selective-display-ellipses nil) (if diary-header-line-flag (setq header-line-format diary-header-line-format)) - (setq old-diary-syntax-table (syntax-table)) - (set-syntax-table diary-syntax-table) - (unwind-protect - (let ((buffer-read-only nil) - (diary-modified (buffer-modified-p)) - (mark (regexp-quote diary-nonmarking-symbol))) - ;; First and last characters must be ^M or \n for - ;; selective display to work properly - (goto-char (1- (point-max))) - (if (not (looking-at "\^M\\|\n")) - (progn - (goto-char (point-max)) - (insert "\^M"))) - (goto-char (point-min)) - (if (not (looking-at "\^M\\|\n")) - (insert "\^M")) - (subst-char-in-region (point-min) (point-max) ?\n ?\^M t) - (calendar-for-loop - i from 1 to number do - (let ((d diary-date-forms) - (month (extract-calendar-month date)) - (day (extract-calendar-day date)) - (year (extract-calendar-year date)) - (entry-found (list-sexp-diary-entries date))) - (while d - (let* - ((date-form (if (equal (car (car d)) 'backup) - (cdr (car d)) - (car d))) - (backup (equal (car (car d)) 'backup)) - (dayname - (format "%s\\|%s\\.?" - (calendar-day-name date) - (calendar-day-name date 'abbrev))) - (monthname - (format "\\*\\|%s\\|%s\\.?" - (calendar-month-name month) - (calendar-month-name month 'abbrev))) - (month (concat "\\*\\|0*" (int-to-string month))) - (day (concat "\\*\\|0*" (int-to-string day))) - (year - (concat - "\\*\\|0*" (int-to-string year) - (if abbreviated-calendar-year - (concat "\\|" (format "%02d" (% year 100))) - ""))) - (regexp - (concat - "\\(\\`\\|\^M\\|\n\\)" mark "?\\(" - (mapconcat 'eval date-form "\\)\\(") - "\\)")) - (case-fold-search t)) - (goto-char (point-min)) - (while (re-search-forward regexp nil t) - (if backup (re-search-backward "\\<" nil t)) - (if (and (or (char-equal (preceding-char) ?\^M) - (char-equal (preceding-char) ?\n)) - (not (looking-at " \\|\^I"))) - ;; Diary entry that consists only of date. - (backward-char 1) - ;; Found a nonempty diary entry--make it - ;; visible and add it to the list. - (setq entry-found t) - (let ((entry-start (point)) - date-start temp) - (re-search-backward "\^M\\|\n\\|\\`") - (setq date-start (point)) - (re-search-forward "\^M\\|\n" nil t 2) - (while (looking-at " \\|\^I") - (re-search-forward "\^M\\|\n" nil t)) - (backward-char 1) - (subst-char-in-region date-start - (point) ?\^M ?\n t) - (setq entry (buffer-substring entry-start (point)) - temp (diary-pull-attrs entry file-glob-attrs) - entry (nth 0 temp)) - (add-to-diary-list - date - entry - (buffer-substring - (1+ date-start) (1- entry-start)) - (copy-marker entry-start) (nth 1 temp)))))) - (setq d (cdr d))) - (or entry-found - (not diary-list-include-blanks) - (setq diary-entries-list - (append diary-entries-list - (list (list date "" "" "" ""))))) - (setq date - (calendar-gregorian-from-absolute - (1+ (calendar-absolute-from-gregorian date)))) - (setq entry-found nil))) - (set-buffer-modified-p diary-modified)) - (set-syntax-table old-diary-syntax-table)) + (with-syntax-table diary-syntax-table + (let ((buffer-read-only nil) + (diary-modified (buffer-modified-p)) + (mark (regexp-quote diary-nonmarking-symbol))) + ;; First and last characters must be ^M or \n for + ;; selective display to work properly + (goto-char (1- (point-max))) + (if (not (looking-at "\^M\\|\n")) + (progn + (goto-char (point-max)) + (insert "\^M"))) + (goto-char (point-min)) + (if (not (looking-at "\^M\\|\n")) + (insert "\^M")) + (subst-char-in-region (point-min) (point-max) ?\n ?\^M t) + (calendar-for-loop + i from 1 to number do + (let ((month (extract-calendar-month date)) + (day (extract-calendar-day date)) + (year (extract-calendar-year date)) + (entry-found (list-sexp-diary-entries date))) + (dolist (date-form diary-date-forms) + (let* + ((backup (when (eq (car date-form) 'backup) + (setq date-form (cdr date-form)) + t)) + (dayname + (format "%s\\|%s\\.?" + (calendar-day-name date) + (calendar-day-name date 'abbrev))) + (monthname + (format "\\*\\|%s\\|%s\\.?" + (calendar-month-name month) + (calendar-month-name month 'abbrev))) + (month (concat "\\*\\|0*" (int-to-string month))) + (day (concat "\\*\\|0*" (int-to-string day))) + (year + (concat + "\\*\\|0*" (int-to-string year) + (if abbreviated-calendar-year + (concat "\\|" (format "%02d" (% year 100))) + ""))) + (regexp + (concat + "\\(\\`\\|\^M\\|\n\\)" mark "?\\(" + (mapconcat 'eval date-form "\\)\\(") + "\\)")) + (case-fold-search t)) + (goto-char (point-min)) + (while (re-search-forward regexp nil t) + (if backup (re-search-backward "\\<" nil t)) + (if (and (or (char-equal (preceding-char) ?\^M) + (char-equal (preceding-char) ?\n)) + (not (looking-at " \\|\^I"))) + ;; Diary entry that consists only of date. + (backward-char 1) + ;; Found a nonempty diary entry--make it + ;; visible and add it to the list. + (setq entry-found t) + (let ((entry-start (point)) + date-start temp) + (re-search-backward "\^M\\|\n\\|\\`") + (setq date-start (point)) + (re-search-forward "\^M\\|\n" nil t 2) + (while (looking-at " \\|\^I") + (re-search-forward "\^M\\|\n" nil t)) + (backward-char 1) + (subst-char-in-region date-start + (point) ?\^M ?\n t) + (setq entry (buffer-substring entry-start (point)) + temp (diary-pull-attrs entry file-glob-attrs) + entry (nth 0 temp)) + (add-to-diary-list + date + entry + (buffer-substring + (1+ date-start) (1- entry-start)) + (copy-marker entry-start) (nth 1 temp))))))) + (or entry-found + (not diary-list-include-blanks) + (setq diary-entries-list + (append diary-entries-list + (list (list date "" "" "" ""))))) + (setq date + (calendar-gregorian-from-absolute + (1+ (calendar-absolute-from-gregorian date)))) + (setq entry-found nil))) + (set-buffer-modified-p diary-modified))) (goto-char (point-min)) (run-hooks 'nongregorian-diary-listing-hook 'list-diary-entries-hook) @@ -459,6 +478,14 @@ These hooks have the following distinct roles: (run-hooks 'diary-hook) diary-entries-list)))))) +(defun diary-unhide-everything () + (setq selective-display nil) + (let ((inhibit-read-only t) + (modified (buffer-modified-p))) + (subst-char-in-region (point-min) (point-max) ?\^M ?\n t) + (set-buffer-modified-p modified)) + (kill-local-variable 'mode-line-format)) + (defun include-other-diary-files () "Include the diary entries from other diary files with those of diary-file. This function is suitable for use in `list-diary-entries-hook'; @@ -471,34 +498,24 @@ changing the variable `diary-include-string'." (goto-char (point-min)) (while (re-search-forward (concat - "\\(\\`\\|\^M\\|\n\\)" + "\\(?:\\`\\|\^M\\|\n\\)" (regexp-quote diary-include-string) " \"\\([^\"]*\\)\"") nil t) (let* ((diary-file (substitute-in-file-name - (buffer-substring-no-properties - (match-beginning 2) (match-end 2)))) + (match-string-no-properties 1))) (diary-list-include-blanks nil) (list-diary-entries-hook 'include-other-diary-files) (diary-display-hook 'ignore) - (diary-hook nil) - (d-buffer (find-buffer-visiting diary-file)) - (diary-modified (if d-buffer - (save-excursion - (set-buffer d-buffer) - (buffer-modified-p))))) + (diary-hook nil)) (if (file-exists-p diary-file) (if (file-readable-p diary-file) (unwind-protect (setq diary-entries-list (append diary-entries-list (list-diary-entries original-date number))) - (save-excursion - (set-buffer (find-buffer-visiting diary-file)) - (let ((inhibit-read-only t)) - (subst-char-in-region (point-min) (point-max) ?\^M ?\n t)) - (setq selective-display nil) - (set-buffer-modified-p diary-modified))) + (with-current-buffer (find-buffer-visiting diary-file) + (diary-unhide-everything))) (beep) (message "Can't read included diary file %s" diary-file) (sleep-for 2)) @@ -564,13 +581,9 @@ changing the variable `diary-include-string'." (defun fancy-diary-display () "Prepare a diary buffer with relevant entries in a fancy, noneditable form. This function is provided for optional use as the `diary-display-hook'." - (save-excursion;; Turn off selective-display in the diary file's buffer. - (set-buffer (find-buffer-visiting (substitute-in-file-name diary-file))) - (let ((diary-modified (buffer-modified-p))) - (subst-char-in-region (point-min) (point-max) ?\^M ?\n t) - (setq selective-display nil) - (kill-local-variable 'mode-line-format) - (set-buffer-modified-p diary-modified))) + (with-current-buffer ;; Turn off selective-display in the diary file's buffer. + (find-buffer-visiting (substitute-in-file-name diary-file)) + (diary-unhide-everything)) (if (or (not diary-entries-list) (and (not (cdr diary-entries-list)) (string-equal (car (cdr (car diary-entries-list))) ""))) @@ -740,7 +753,8 @@ the actual printing." (kill-buffer temp-buffer))) (error "You don't have a diary buffer!"))))) -(defun show-all-diary-entries () +(define-obsolete-function-alias 'show-all-diary-entries 'diary-show-all-entries) +(defun diary-show-all-entries () "Show all of the diary entries in the diary file. This function gets rid of the selective display of the diary file so that all entries, not just some, are visible. If there is no diary buffer, one @@ -748,16 +762,9 @@ is created." (interactive) (let ((d-file (diary-check-diary-file)) (pop-up-frames (window-dedicated-p (selected-window)))) - (save-excursion - (set-buffer (or (find-buffer-visiting d-file) - (find-file-noselect d-file t))) - (let ((buffer-read-only nil) - (diary-modified (buffer-modified-p))) - (subst-char-in-region (point-min) (point-max) ?\^M ?\n t) - (setq selective-display nil - mode-line-format default-mode-line-format) - (display-buffer (current-buffer)) - (set-buffer-modified-p diary-modified))))) + (with-current-buffer (or (find-buffer-visiting d-file) + (find-file-noselect d-file t)) + (diary-unhide-everything)))) (defcustom diary-mail-addr (if (boundp 'user-mail-address) user-mail-address "") @@ -807,9 +814,7 @@ to run it every morning at 1am." (calendar-date-string (calendar-current-date)))) (insert (if (get-buffer fancy-diary-buffer) - (save-excursion - (set-buffer fancy-diary-buffer) - (buffer-substring (point-min) (point-max))) + (with-current-buffer fancy-diary-buffer (buffer-string)) "No entries found")) (call-interactively (get mail-user-agent 'sendfunc)))) @@ -844,7 +849,7 @@ marked. After the entries are marked, the hooks `nongregorian-diary-marking-hook' and `mark-diary-entries-hook' are run. If the optional argument REDRAW is non-nil (which is the case interactively, for example) then any existing diary -marks are first removed. This is intended to deal with deleted +marks are first removed. This is intended to deal with deleted diary entries." (interactive "p") ;; To remove any deleted diary entries. Do not redraw when: @@ -858,8 +863,7 @@ diary entries." (redraw-calendar)) (let ((marking-diary-entries t) file-glob-attrs marks) - (save-excursion - (set-buffer (find-file-noselect (diary-check-diary-file) t)) + (with-current-buffer (find-file-noselect (diary-check-diary-file) t) (setq mark-diary-entries-in-calendar t) (message "Marking diary entries...") (setq file-glob-attrs (nth 1 (diary-pull-attrs nil '()))) @@ -902,30 +906,20 @@ diary entries." (while (re-search-forward regexp nil t) (let* ((dd-name (if d-name-pos - (buffer-substring-no-properties - (match-beginning d-name-pos) - (match-end d-name-pos)))) + (match-string-no-properties d-name-pos))) (mm-name (if m-name-pos - (buffer-substring-no-properties - (match-beginning m-name-pos) - (match-end m-name-pos)))) + (match-string-no-properties m-name-pos))) (mm (string-to-number (if m-pos - (buffer-substring-no-properties - (match-beginning m-pos) - (match-end m-pos)) + (match-string-no-properties m-pos) ""))) (dd (string-to-number (if d-pos - (buffer-substring-no-properties - (match-beginning d-pos) - (match-end d-pos)) + (match-string-no-properties d-pos) ""))) (y-str (if y-pos - (buffer-substring-no-properties - (match-beginning y-pos) - (match-end y-pos)))) + (match-string-no-properties y-pos))) (yy (if (not y-str) 0 (if (and (= (length y-str) 2) @@ -941,13 +935,13 @@ diary entries." (if (> (- current-y y) 50) (+ y 100) y))) - (string-to-number y-str)))) - (save-excursion - (setq entry (buffer-substring-no-properties - (point) (line-end-position)) - temp (diary-pull-attrs entry file-glob-attrs) - entry (nth 0 temp) - marks (nth 1 temp)))) + (string-to-number y-str))))) + (save-excursion + (setq entry (buffer-substring-no-properties + (point) (line-end-position)) + temp (diary-pull-attrs entry file-glob-attrs) + entry (nth 0 temp) + marks (nth 1 temp))) (if dd-name (mark-calendar-days-named (cdr (assoc-string @@ -982,8 +976,7 @@ is marked. See the documentation for the function `list-sexp-diary-entries'." sexp-mark "(diary-remind\\)")) (file-glob-attrs (nth 1 (diary-pull-attrs nil '()))) m y first-date last-date mark file-glob-attrs) - (save-excursion - (set-buffer calendar-buffer) + (with-current-buffer calendar-buffer (setq m displayed-month) (setq y displayed-year)) (increment-calendar-month m y -1) @@ -1048,12 +1041,12 @@ changing the variable `diary-include-string'." (goto-char (point-min)) (while (re-search-forward (concat - "\\(\\`\\|\^M\\|\n\\)" + "\\(?:\\`\\|\^M\\|\n\\)" (regexp-quote diary-include-string) " \"\\([^\"]*\\)\"") nil t) (let* ((diary-file (substitute-in-file-name - (match-string-no-properties 2))) + (match-string-no-properties 1))) (mark-diary-entries-hook 'mark-included-diary-files) (dbuff (find-buffer-visiting diary-file))) (if (file-exists-p diary-file) @@ -1073,8 +1066,7 @@ changing the variable `diary-include-string'." (defun mark-calendar-days-named (dayname &optional color) "Mark all dates in the calendar window that are day DAYNAME of the week. 0 means all Sundays, 1 means all Mondays, and so on." - (save-excursion - (set-buffer calendar-buffer) + (with-current-buffer calendar-buffer (let ((prev-month displayed-month) (prev-year displayed-year) (succ-month displayed-month) @@ -1094,8 +1086,7 @@ changing the variable `diary-include-string'." (defun mark-calendar-date-pattern (month day year &optional color) "Mark all dates in the calendar window that conform to MONTH/DAY/YEAR. A value of 0 in any position is a wildcard." - (save-excursion - (set-buffer calendar-buffer) + (with-current-buffer calendar-buffer (let ((m displayed-month) (y displayed-year)) (increment-calendar-month m y -1) @@ -1152,22 +1143,17 @@ be used instead of a colon (:) to separate the hour and minute parts." (cond ((string-match ; Military time "\\`[ \t\n\\^M]*\\([0-9]?[0-9]\\)[:.]?\\([0-9][0-9]\\)\\(\\>\\|[^ap]\\)" s) - (+ (* 100 (string-to-number - (substring s (match-beginning 1) (match-end 1)))) - (string-to-number (substring s (match-beginning 2) (match-end 2))))) + (+ (* 100 (string-to-number (match-string 1 s))) + (string-to-number (match-string 2 s)))) ((string-match ; Hour only XXam or XXpm "\\`[ \t\n\\^M]*\\([0-9]?[0-9]\\)\\([ap]\\)m\\>" s) - (+ (* 100 (% (string-to-number - (substring s (match-beginning 1) (match-end 1))) - 12)) + (+ (* 100 (% (string-to-number (match-string 1 s)) 12)) (if (equal ?a (downcase (aref s (match-beginning 2)))) 0 1200))) ((string-match ; Hour and minute XX:XXam or XX:XXpm "\\`[ \t\n\\^M]*\\([0-9]?[0-9]\\)[:.]\\([0-9][0-9]\\)\\([ap]\\)m\\>" s) - (+ (* 100 (% (string-to-number - (substring s (match-beginning 1) (match-end 1))) - 12)) - (string-to-number (substring s (match-beginning 2) (match-end 2))) + (+ (* 100 (% (string-to-number (match-string 1 s)) 12)) + (string-to-number (match-string 2 s)) (if (equal ?a (downcase (aref s (match-beginning 3)))) 0 1200))) (t diary-unknown-time)))) ; Unrecognizable @@ -1404,14 +1390,7 @@ best if they are nonmarking." (error (beep) (message "Bad sexp at line %d in %s: %s" - (save-excursion - (save-restriction - (narrow-to-region 1 (point)) - (goto-char (point-min)) - (let ((lines 1)) - (while (re-search-forward "\n\\|\^M" nil t) - (setq lines (1+ lines))) - lines))) + (count-lines (point-min) (point)) diary-file sexp) (sleep-for 2)))))) (cond ((stringp result) result) @@ -1688,12 +1667,9 @@ If omitted, NONMARKING defaults to nil and FILE defaults to redrawn with the new entry marked, if necessary." (let ((pop-up-frames (window-dedicated-p (selected-window)))) (find-file-other-window (substitute-in-file-name (or file diary-file)))) - (add-hook 'write-contents-functions 'diary-redraw-calendar nil t) - (when selective-display - (subst-char-in-region (point-min) (point-max) ?\^M ?\n t) - (setq selective-display nil) - (kill-local-variable 'mode-line-format)) + (add-hook 'after-save-hook 'diary-redraw-calendar nil t) (widen) + (diary-unhide-everything) (goto-char (point-max)) (when (let ((case-fold-search t)) (search-backward "Local Variables:" @@ -1701,7 +1677,7 @@ redrawn with the new entry marked, if necessary." t)) (beginning-of-line) (insert "\n") - (previous-line 1)) + (forward-line -1)) (insert (if (bolp) "" "\n") (if nonmarking diary-nonmarking-symbol "") @@ -1798,19 +1774,29 @@ Prefix arg will make the entry nonmarking." (calendar-date-string (calendar-cursor-to-date t) nil t)) arg))) +(defvar diary-mode-map + (let ((map (make-sparse-keymap))) + (define-key map "\C-c\C-s" 'diary-show-all-entries) + (define-key map "\C-c\C-q" 'quit-window) + map) + "Keymap for `diary-mode'.") + ;;;###autoload -(define-derived-mode diary-mode fundamental-mode - "Diary" +(define-derived-mode diary-mode fundamental-mode "Diary" "Major mode for editing the diary file." (set (make-local-variable 'font-lock-defaults) - '(diary-font-lock-keywords t))) + '(diary-font-lock-keywords t)) + (add-to-invisibility-spec '(diary . nil)) + (add-hook 'after-save-hook 'diary-redraw-calendar nil t) + (if diary-header-line-flag + (setq header-line-format diary-header-line-format))) (define-derived-mode fancy-diary-display-mode fundamental-mode "Diary" "Major mode used while displaying diary entries using Fancy Display." (set (make-local-variable 'font-lock-defaults) '(fancy-diary-font-lock-keywords t)) - (define-key (current-local-map) "q" 'quit-window)) + (local-set-key "q" 'quit-window)) (defvar fancy-diary-font-lock-keywords @@ -1836,7 +1822,7 @@ Prefix arg will make the entry nonmarking." "Keywords to highlight in fancy diary display") -(defun font-lock-diary-sexps (limit) +(defun diary-font-lock-sexps (limit) "Recognize sexp diary entry for font-locking." (if (re-search-forward (concat "^" (regexp-quote diary-nonmarking-symbol) @@ -1851,7 +1837,7 @@ Prefix arg will make the entry nonmarking." t)) (error t)))) -(defun font-lock-diary-date-forms (month-array &optional symbol abbrev-array) +(defun diary-font-lock-date-forms (month-array &optional symbol abbrev-array) "Create font-lock patterns for `diary-date-forms' using MONTH-ARRAY. If given, optional SYMBOL must be a prefix to entries. If optional ABBREV-ARRAY is present, the abbreviations constructed @@ -1865,7 +1851,7 @@ names." (month "\\([0-9]+\\|\\*\\)") (day "\\([0-9]+\\|\\*\\)") (year "-?\\([0-9]+\\|\\*\\)")) - (mapcar '(lambda (x) + (mapcar (lambda (x) (cons (concat "^" (regexp-quote diary-nonmarking-symbol) "?" (if symbol (regexp-quote symbol) "") "\\(" @@ -1873,7 +1859,7 @@ names." ;; If backup, omit first item (backup) ;; and last item (not part of date) (if (equal (car x) 'backup) - (reverse (cdr (reverse (cdr x)))) + (nreverse (cdr (reverse (cdr x)))) x) "") ;; With backup, last item is not part of date @@ -1888,14 +1874,14 @@ names." (defvar diary-font-lock-keywords (append - (font-lock-diary-date-forms calendar-month-name-array + (diary-font-lock-date-forms calendar-month-name-array nil calendar-month-abbrev-array) (when (or (memq 'mark-hebrew-diary-entries nongregorian-diary-marking-hook) (memq 'list-hebrew-diary-entries nongregorian-diary-listing-hook)) (require 'cal-hebrew) - (font-lock-diary-date-forms + (diary-font-lock-date-forms calendar-hebrew-month-name-array-leap-year hebrew-diary-entry-symbol)) (when (or (memq 'mark-islamic-diary-entries @@ -1903,7 +1889,7 @@ names." (memq 'list-islamic-diary-entries nongregorian-diary-listing-hook)) (require 'cal-islam) - (font-lock-diary-date-forms + (diary-font-lock-date-forms calendar-islamic-month-name-array islamic-diary-entry-symbol)) (list @@ -1925,10 +1911,10 @@ names." (concat "^" (regexp-quote diary-nonmarking-symbol) "?\\(" (regexp-quote islamic-diary-entry-symbol) "\\)") '(1 font-lock-reference-face)) - '(font-lock-diary-sexps . font-lock-keyword-face) + '(diary-font-lock-sexps . font-lock-keyword-face) '("[0-9]?[0-9]\\([:.]?[0-9][0-9]\\)?\\(am\\|pm\\|AM\\|PM\\)\\(-[0-9]?[0-9]\\([:.]?[0-9][0-9]\\)?\\(am\\|pm\\|AM\\|PM\\)\\)?" . font-lock-function-name-face))) - "Forms to highlight in diary-mode") + "Forms to highlight in `diary-mode'.") ;; Following code from Dave Love . @@ -2087,5 +2073,5 @@ user is asked to confirm its addition." (provide 'diary-lib) -;;; arch-tag: 22dd506e-2e33-410d-9ae1-095a0c1b2010 +;; arch-tag: 22dd506e-2e33-410d-9ae1-095a0c1b2010 ;;; diary-lib.el ends here diff --git a/lisp/custom.el b/lisp/custom.el index d634160e534..cf6ef88456e 100644 --- a/lisp/custom.el +++ b/lisp/custom.el @@ -649,17 +649,16 @@ See `custom-known-themes' for a list of known themes." (progn (setcar (cdr setting) mode) (setcar (cddr setting) value)) - (if (and (null old) - (boundp symbol)) - (setq old - (list - (list 'standard 'set - (if (eq prop 'theme-value) - (symbol-value symbol) - (list - (append - '(t) - (custom-face-attributes-get symbol nil)))))))) + ;; If no custom theme has been applied yet, first save the + ;; current values to the 'standard theme. + (if (null old) + (if (and (eq prop 'theme-value) + (boundp symbol)) + (setq old + (list (list 'standard 'set (symbol-value symbol)))) + (if (facep symbol) + (setq old (list (list 'standard 'set (list + (append '(t) (custom-face-attributes-get symbol nil))))))))) (put symbol prop (cons (list theme mode value) old))) ;; Record, for each theme, all its settings. (put theme 'theme-settings diff --git a/lisp/faces.el b/lisp/faces.el index 505b53bcc05..10ffc38395f 100644 --- a/lisp/faces.el +++ b/lisp/faces.el @@ -1834,20 +1834,27 @@ created." ;; User init file can set term-file-prefix to nil to prevent this. (with-selected-frame frame (unless (null term-file-prefix) - (let ((term (frame-parameter frame 'tty-type)) - hyphend term-init-func) + (let* ((term (frame-parameter frame 'tty-type)) + (term2 term) + hyphend term-init-func) (while (and term - (not (fboundp - (setq term-init-func (intern (concat "terminal-init-" term))))) (not (load (concat term-file-prefix term) t t))) ;; Strip off last hyphen and what follows, then try again (setq term (if (setq hyphend (string-match "[-_][^-_]+$" term)) (substring term 0 hyphend) nil))) - (when (and term (fboundp term-init-func)) - ;; The terminal file has been loaded, now call the terminal - ;; specific initialization function. + ;; The terminal file has been loaded, now find and call the + ;; terminal specific initialization function. + (while (and term2 + (not (fboundp + (setq term-init-func (intern (concat "terminal-init-" term2)))))) + ;; Strip off last hyphen and what follows, then try again + (setq term2 + (if (setq hyphend (string-match "[-_][^-_]+$" term2)) + (substring term2 0 hyphend) + nil))) + (when (fboundp term-init-func) (funcall term-init-func)))))) ;; Called from C function init_display to initialize faces of the diff --git a/lisp/files.el b/lisp/files.el index 68a85f07df8..33fc39dea1c 100644 --- a/lisp/files.el +++ b/lisp/files.el @@ -1738,7 +1738,11 @@ in that case, this function acts as if `enable-local-variables' were t." (hack-local-variables))) ;; Turn font lock off and on, to make sure it takes account of ;; whatever file local variables are relevant to it. - (when (and font-lock-mode (eq (car font-lock-keywords) t)) + (when (and font-lock-mode + ;; Font-lock-mode (now in font-core.el) can be ON when + ;; font-lock.el still hasn't been loaded. + (boundp 'font-lock-keywords) + (eq (car font-lock-keywords) t)) (setq font-lock-keywords (cadr font-lock-keywords)) (font-lock-mode 1)) diff --git a/lisp/font-lock.el b/lisp/font-lock.el index 12b265661ea..75a2ba3c21a 100644 --- a/lisp/font-lock.el +++ b/lisp/font-lock.el @@ -340,6 +340,7 @@ This can be an \"!\" or the \"n\" in \"ifndef\".") ;; Fontification variables: +;;;###autoload (defvar font-lock-keywords nil "A list of the keywords to highlight. There are two kinds of values: user-level, and compiled. diff --git a/lisp/help-fns.el b/lisp/help-fns.el index ed58234ffad..fc8277e7414 100644 --- a/lisp/help-fns.el +++ b/lisp/help-fns.el @@ -522,7 +522,7 @@ it is displayed along with the global value." (message "You did not specify a variable") (save-excursion (let ((valvoid (not (with-current-buffer buffer (boundp variable)))) - val locus) + val val-start-pos locus) ;; Extract the value before setting up the output buffer, ;; in case `buffer' *is* the output buffer. (unless valvoid @@ -535,104 +535,6 @@ it is displayed along with the global value." (with-output-to-temp-buffer (help-buffer) (with-current-buffer buffer (prin1 variable) - (if valvoid - (princ " is void") - (with-current-buffer standard-output - (princ "'s value is ") - (terpri) - (let ((from (point))) - (pp val) - ;; Hyperlinks in variable's value are quite frequently - ;; inappropriate e.g C-h v features - ;; (help-xref-on-pp from (point)) - (if (< (point) (+ from 20)) - (delete-region (1- from) from))))) - (terpri) - (when locus - (if (bufferp locus) - (princ (format "%socal in buffer %s; " - (if (get variable 'permanent-local) - "Permanently l" "L") - (buffer-name))) - (princ (format "It is a frame-local variable; "))) - (if (not (default-boundp variable)) - (princ "globally void") - (let ((val (default-value variable))) - (with-current-buffer standard-output - (princ "global value is ") - (terpri) - ;; Fixme: pp can take an age if you happen to - ;; ask for a very large expression. We should - ;; probably print it raw once and check it's a - ;; sensible size before prettyprinting. -- fx - (let ((from (point))) - (pp val) - ;; See previous comment for this function. - ;; (help-xref-on-pp from (point)) - (if (< (point) (+ from 20)) - (delete-region (1- from) from)))))) - (terpri)) - (terpri) - (with-current-buffer standard-output - (when (> (count-lines (point-min) (point-max)) 10) - ;; Note that setting the syntax table like below - ;; makes forward-sexp move over a `'s' at the end - ;; of a symbol. - (set-syntax-table emacs-lisp-mode-syntax-table) - (goto-char (point-min)) - (if valvoid - (forward-line 1) - (forward-sexp 1) - (delete-region (point) (progn (end-of-line) (point))) - (save-excursion - (insert "\n\nValue:") - (set (make-local-variable 'help-button-cache) - (point-marker))) - (insert " value is shown ") - (insert-button "below" - 'action help-button-cache - 'follow-link t - 'help-echo "mouse-2, RET: show value") - (insert ".\n\n"))) - ;; Add a note for variables that have been make-var-buffer-local. - (when (and (local-variable-if-set-p variable) - (or (not (local-variable-p variable)) - (with-temp-buffer - (local-variable-if-set-p variable)))) - (save-excursion - (forward-line -1) - (insert "Automatically becomes buffer-local when set in any fashion.\n")))) - ;; Mention if it's an alias - (let* ((alias (condition-case nil - (indirect-variable variable) - (error variable))) - (obsolete (get variable 'byte-obsolete-variable)) - (doc (or (documentation-property variable 'variable-documentation) - (documentation-property alias 'variable-documentation)))) - (unless (eq alias variable) - (princ (format "This variable is an alias for `%s'." alias)) - (terpri) - (terpri)) - (when obsolete - (princ "This variable is obsolete") - (if (cdr obsolete) (princ (format " since %s" (cdr obsolete)))) - (princ ";") (terpri) - (princ (if (stringp (car obsolete)) (car obsolete) - (format "use `%s' instead." (car obsolete)))) - (terpri) - (terpri)) - (princ (or doc "Not documented as a variable."))) - ;; Make a link to customize if this variable can be customized. - (if (custom-variable-p variable) - (let ((customize-label "customize")) - (terpri) - (terpri) - (princ (concat "You can " customize-label " this variable.")) - (with-current-buffer standard-output - (save-excursion - (re-search-backward - (concat "\\(" customize-label "\\)") nil t) - (help-xref-button 1 'help-customize-variable variable))))) ;; Make a hyperlink to the library if appropriate. (Don't ;; change the format of the buffer's initial line in case ;; anything expects the current format.) @@ -656,16 +558,117 @@ it is displayed along with the global value." (if (get-buffer " *DOC*") (help-C-file-name variable 'var) 'C-source))) - (when file-name - (princ "\n\nDefined in `") - (princ (if (eq file-name 'C-source) "C source code" file-name)) - (princ "'.") - (with-current-buffer standard-output - (save-excursion - (re-search-backward "`\\([^`']+\\)'" nil t) - (help-xref-button 1 'help-variable-def - variable file-name))))) + (if file-name + (progn + (princ " is a variable defined in `") + (princ (if (eq file-name 'C-source) "C source code" file-name)) + (princ "'.\n") + (with-current-buffer standard-output + (save-excursion + (re-search-backward "`\\([^`']+\\)'" nil t) + (help-xref-button 1 'help-variable-def + variable file-name))) + (if valvoid + (princ "It is void as a variable.\n") + (princ "Its "))) + (if valvoid + (princ " is void as a variable.\n") + (princ "'s ")))) + (if valvoid + nil + (with-current-buffer standard-output + (setq val-start-pos (point)) + (princ "value is ") + (terpri) + (let ((from (point))) + (pp val) + ;; Hyperlinks in variable's value are quite frequently + ;; inappropriate e.g C-h v features + ;; (help-xref-on-pp from (point)) + (if (< (point) (+ from 20)) + (delete-region (1- from) from))))) + (terpri) + (when locus + (if (bufferp locus) + (princ (format "%socal in buffer %s; " + (if (get variable 'permanent-local) + "Permanently l" "L") + (buffer-name))) + (princ (format "It is a frame-local variable; "))) + (if (not (default-boundp variable)) + (princ "globally void") + (let ((val (default-value variable))) + (with-current-buffer standard-output + (princ "global value is ") + (terpri) + ;; Fixme: pp can take an age if you happen to + ;; ask for a very large expression. We should + ;; probably print it raw once and check it's a + ;; sensible size before prettyprinting. -- fx + (let ((from (point))) + (pp val) + ;; See previous comment for this function. + ;; (help-xref-on-pp from (point)) + (if (< (point) (+ from 20)) + (delete-region (1- from) from))))))) + ;; Add a note for variables that have been make-var-buffer-local. + (when (and (local-variable-if-set-p variable) + (or (not (local-variable-p variable)) + (with-temp-buffer + (local-variable-if-set-p variable)))) + (princ "\nAutomatically becomes buffer-local when set in any fashion.\n")) + (terpri) + + ;; If the value is large, move it to the end. + (with-current-buffer standard-output + (when (> (count-lines (point-min) (point-max)) 10) + ;; Note that setting the syntax table like below + ;; makes forward-sexp move over a `'s' at the end + ;; of a symbol. + (set-syntax-table emacs-lisp-mode-syntax-table) + (goto-char val-start-pos) + (delete-region (point) (progn (end-of-line) (point))) + (save-excursion + (insert "\n\nValue:") + (set (make-local-variable 'help-button-cache) + (point-marker))) + (insert "value is shown ") + (insert-button "below" + 'action help-button-cache + 'follow-link t + 'help-echo "mouse-2, RET: show value") + (insert ".\n\n"))) + + ;; Mention if it's an alias + (let* ((alias (condition-case nil + (indirect-variable variable) + (error variable))) + (obsolete (get variable 'byte-obsolete-variable)) + (doc (or (documentation-property variable 'variable-documentation) + (documentation-property alias 'variable-documentation)))) + (unless (eq alias variable) + (princ (format "\nThis variable is an alias for `%s'.\n" alias))) + (when obsolete + (princ "\nThis variable is obsolete") + (if (cdr obsolete) (princ (format " since %s" (cdr obsolete)))) + (princ ";") (terpri) + (princ (if (stringp (car obsolete)) (car obsolete) + (format "use `%s' instead." (car obsolete)))) + (terpri)) + (princ "Documentation:\n") + (princ (or doc "Not documented as a variable."))) + ;; Make a link to customize if this variable can be customized. + (if (custom-variable-p variable) + (let ((customize-label "customize")) + (terpri) + (terpri) + (princ (concat "You can " customize-label " this variable.")) + (with-current-buffer standard-output + (save-excursion + (re-search-backward + (concat "\\(" customize-label "\\)") nil t) + (help-xref-button 1 'help-customize-variable variable))))) (print-help-return-message) (save-excursion (set-buffer standard-output) diff --git a/lisp/mail/sendmail.el b/lisp/mail/sendmail.el index f5c34ff80ca..32328a33177 100644 --- a/lisp/mail/sendmail.el +++ b/lisp/mail/sendmail.el @@ -1591,8 +1591,13 @@ is inserted. The normal hook `mail-setup-hook' is run after the message is initialized. It can add more default fields to the message. -When calling from a program, the first argument if non-nil says -not to erase the existing contents of the `*mail*' buffer. +The first argument, NOERASE, determines what to do when there is +an existing modified `*mail*' buffer. If NOERASE is nil, the +existing mail buffer is used, and the user is prompted whether to +keep the old contents or to erase them. If NOERASE has the value +`new', a new mail buffer will be created instead of using the old +one. Any other non-nil value means to always select the old +buffer without erasing the contents. The second through fifth arguments, TO, SUBJECT, IN-REPLY-TO and CC, specify if non-nil @@ -1649,7 +1654,11 @@ The seventh argument ACTIONS is a list of actions to take ;;; (file-exists-p buffer-auto-save-file-name)) ;;; (message "Auto save file for draft message exists; consider M-x mail-recover")) ;;; t)) - (pop-to-buffer "*mail*") + + (if (eq noerase 'new) + (pop-to-buffer (generate-new-buffer "*mail*")) + (pop-to-buffer "*mail*")) + ;; Avoid danger that the auto-save file can't be written. (let ((dir (expand-file-name (file-name-as-directory mail-default-directory)))) @@ -1664,7 +1673,8 @@ The seventh argument ACTIONS is a list of actions to take ;; (in case the user has actually visited a file *mail*). ; (set-visited-file-name nil) (let (initialized) - (and (not noerase) + (and (or (not noerase) + (eq noerase 'new)) (if buffer-file-name (if (buffer-modified-p) (when (y-or-n-p "Buffer has unsaved changes; reinitialize it and discard them? ") diff --git a/lisp/net/newsticker.el b/lisp/net/newsticker.el new file mode 100644 index 00000000000..c6597695354 --- /dev/null +++ b/lisp/net/newsticker.el @@ -0,0 +1,4919 @@ +;;; newsticker.el --- A Newsticker for Emacs. + +;; Copyright (C) 2003, 2004, 2005 Free Software Foundation, Inc. + +;; This file is part of GNU Emacs. + +;; Author: Ulf Jasper +;; Filename: newsticker.el +;; URL: http://www.nongnu.org/newsticker +;; Created: 17. June 2003 +;; Keywords: News, RSS +;; Time-stamp: "26. August 2005, 16:33:46 (ulf)" +;; CVS-Version: $Id: newsticker.el,v 1.2 2005/09/12 22:54:28 miles Exp $ + +(defconst newsticker-version "1.8" "Version number of newsticker.el.") + +;; ====================================================================== + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation; either version 2 of the License, or (at +;; your option) any later version. + +;; This program is distributed in the hope that it will be useful, but +;; WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +;; General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program; if not, write to the Free Software +;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +;; USA + +;; ====================================================================== +;;; Commentary: + +;; Overview +;; -------- + +;; Newsticker provides a newsticker for Emacs. A newsticker is a thing that +;; asynchronously retrieves headlines from a list of news sites, prepares +;; these headlines for reading, and allows for loading the corresponding +;; articles in a web browser. + +;; Headlines consist of a title and (possibly) a small description. They +;; are contained in RSS (RDF Site Summary) files. Newsticker should work +;; with all RSS files that follow the RDF Rich Site Summary 1.0 +;; specification. It should also work with version 2.0 as well as +;; other/older/alternative RSS formats (like 0.9 or such). In +;; other words: Newsticker is a "RSS reader" or "RSS aggregator". + +;; Newsticker provides several commands for reading headlines, navigating +;; through them, marking them as read/unread, hiding old headlines +;; etc. Headlines can be displayed as plain text or as rendered HTML. + +;; Headlines can be displayed in the echo area, either scrolling like +;; messages in a stock-quote ticker, or just changing. + +;; Newsticker allows for automatic processing of headlines by providing +;; hooks and (sample) functions for automatically downloading images and +;; enclosed files (as delivered by podcasts, e.g.). + + + +;; Requirements +;; ------------ +;; Newsticker can be used with GNU Emacs version 21.1 or later as well as +;; XEmacs. It requires an XML-parser (`xml.el') which is part of GNU +;; Emacs. If you are using XEmacs you want to get the `net-utils' package +;; which contains `xml.el' for XEmacs. + +;; Newsticker requires a program which can retrieve files via http and +;; prints them to stdout. By default Newsticker will use wget for this +;; task. + +;; Installation +;; ------------ +;; Place Newsticker in a directory where Emacs can find it. Add the +;; following line to your Emacs startup file (`~/.emacs'). +;; (add-to-list 'load-path "/path/to/newsticker/") +;; (autoload 'newsticker-start "newsticker" "Emacs Newsticker" t) +;; (autoload 'newsticker-show-news "newsticker" "Emacs Newsticker" t) + +;; Newsticker-mode supports imenu. It allows for navigating with the help +;; of a menu. In order to use this feature you should also add the +;; following. +;; (add-hook 'newsticker-mode-hook 'imenu-add-menubar-index) + +;; That's it. + +;; Usage +;; ----- +;; The command newsticker-show-news will display all available headlines in +;; a special buffer, called `*newsticker*'. It will also start the +;; asynchronous download of headlines. The modeline in the `*newsticker*' +;; buffer informs whenever new headlines have arrived. Clicking +;; mouse-button 2 or pressing RET in this buffer on a headline will call +;; browse-url to load the corresponding news story in your favourite web +;; browser. + +;; The scrolling, or flashing of headlines in the echo area, can be started +;; with the command newsticker-start-ticker. It can be stopped with +;; newsticker-stop-ticker. + +;; If you just want to start the periodic download of headlines use the +;; command newsticker-start. Calling newsticker-stop will stop the periodic +;; download, but will call newsticker-stop-ticker as well. + +;; Configuration +;; ------------- +;; All Newsticker options are customizable, i.e. they can be changed with +;; Emacs customization methods: Call the command customize-group and enter +;; `newsticker' for the customization group. + +;; All Newsticker options have reasonable default values, so that in most +;; cases it is not necessary to customize settings before starting +;; Newsticker for the first time. + +;; Newsticker options are organized in the following groups. + +;; * newsticker-feed contains options that define which news +;; feeds are retrieved and how this is done. +;; o newsticker-url-list defines the list of headlines which are +;; retrieved. +;; o newsticker-retrieval-interval defines how often headlines are +;; retrieved. +;; * newsticker-headline-processing contains options that define how the +;; retrieved headlines are processed. +;; o newsticker-keep-obsolete-items decides whether unread headlines that +;; have been removed from the feed are kept in the Newsticker cache. +;; * newsticker-layout contains options that define how the buffer for +;; reading RSS headlines is formatted. +;; o newsticker-item-format defines how the title of a headline is +;; formatted. +;; * newsticker-ticker contains options that define how headlines are shown +;; in the echo area. +;; o newsticker-display-interval and newsticker-scroll-smoothly define +;; how headlines are shown in the echo area. +;; * newsticker-hooks contains options for hooking other Emacs commands to +;; newsticker functions. +;; o newsticker-new-item-functions allows for automatic processing of +;; headlines. See `newsticker-download-images', and +;; `newsticker-download-enclosures' for sample functions. +;; * newsticker-miscellaneous contains other Newsticker options. + +;; Please have a look at the customization buffers for the complete list of +;; options. + +;; Remarks +;; ------- +;; This newsticker is designed do its job silently in the background +;; without disturbing you. However, it is probably impossible to prevent +;; such a tool from slightly attenuating your Editor's responsiveness every +;; once in a while. + +;; Byte-compiling newsticker.el is recommended. + +;; ====================================================================== +;;; History: + +;; 1.8 (2005-08-26) +;; * Added commands `newsticker-show-extra' and `newsticker-hide-extra' +;; to show and hide extra RSS elements, bound to "sx" and "hx" +;; resp. Changed default value of `newsticker-show-all-rss-elements' +;; to nil. +;; * mode-line: Introduced special mode-line-format for newsticker. +;; * Get feed logos only once every 24 h. +;; * Default faces changed. +;; * Minor fixes. + +;; 1.7 (2005-06-25) +;; * Tool-bar support: most important commands can be called from +;; tool-bar buttons. +;; * Auto-Narrowing introduced: *newsticker* buffer can be narrowed to +;; a single item (bound to key `xi') or a single feed (bound to `xf'). +;; * Enclosure support: enclosed items are shown (see +;; `newsticker-enclosure-face') and can be (automatically) downloaded +;; (see below). For those of you who read "podcasts". +;; * Added variable `newsticker-auto-mark-filter' for automatically +;; marking items as immortal or old. +;; * Added hook variable `newsticker-new-item-functions' for handling +;; new items. Added sample functions `newsticker-download-images', +;; and `newsticker-download-enclosures'. +;; * Added hook variable `newsticker-select-item-hook' which is run +;; after `newsticker-(next|previous)-(new-)?-item'. +;; * Added hook variable `newsticker-select-feed-hook' which is run +;; after `newsticker-(next|previous)-feed'. +;; * Added hook variable `newsticker-buffer-change-hook' which is run +;; after the contents or visibility of the newsticker buffer has +;; changed, e.g. after `newsticker-buffer-update' or +;; `newsticker-show-feed-desc'. +;; * Added command `newsticker-handle-url' for interactively launching +;; arbitrary programs for URLs, bound to `C-RET'. +;; * URLs in extra elements are clickable. +;; * Better support for w3, added command +;; `newsticker-w3m-show-inline-images' for displaying all inline +;; images. +;; * Insert an artificial headline which notifies about failed retrievals. +;; * Use pubDate element (RSS 2.0) instead of retrieval time when +;; available. +;; * Customizable options grouped. +;; * Bugfixes: `newsticker--imenu-create-index'; strip whitespace +;; from links; apply coding-system to extra-elements; time-comparison +;; for obsolete items; and others which I have forgotten. +;; * Workaround for another bug in xml-parse-region -- thanks to anonymous +;; for sending patch. +;; * Renamed invisible buffers ` *wget-newsticker-*' to +;; ` *newsticker-wget-*'. +;; * Tested with GNU Emacs versions 21.3 and 22.0 and XEmacs 21.something. + +;; 1.6 * Support for (some) optional RSS elements: guid, dc:date. See +;; `newsticker-show-all-rss-elements' `newsticker-extra-face'. +;; * Better support for w3m -- `newsticker-default-face' is obsolete +;; now, removed `newsticker-w3m-toggle-inline-image'. +;; * Added `newsticker-desc-comp-max' -- comparison of item descriptions +;; can take quite some time. +;; * Added `newsticker--buffer-make-item-completely-visible' to +;; ensure that the current item is fully visible. +;; * Allow for non-positive retrieval-interval, which make newsticker +;; get news only once. +;; * Use :set for customizable variables. +;; * Added `newsticker-buffer-force-update', bound to key `U'. +;; * Added concept of obsolete items, see +;; `newsticker-keep-obsolete-items', `newsticker-obsolete-item-face', +;; `newsticker-obsolete-item-max-age'. +;; * Added `newsticker-add-url'. +;; * OPML export. +;; * Save pre-formatted titles => even better performance!! +;; * `newsticker-*-new-item' wraps at beginning/end of buffer. +;; * Always sort obsolete items to end of item list. +;; * Bugfixes: +;; - newsticker-hide-entry, +;; - changes of feed-titles led to duplicate feed items, +;; - faces for rendered HTML texts, +;; - length of ticker-text (for "exotic"/multibyte texts), +;; Thanks to Hiroshi Maruyama. +;; - suppress items with empty title and description +;; - newsticker-sort-method was ignored! +;; - prevent call of fill-region on HTML-rendered descriptions. + +;; 1.5 * Rewrote the visibility stuff. newsticker does not inherit +;; outline anymore. Now you have complete freedom for +;; `newsticker-*-format'. +;; * Save pre-formatted descriptions => incredible performance boost!! +;; * Introduced `newsticker-(start|stop)-ticker'. +;; * Introduced statistics for heading-format and +;; `newsticker-statistics-face'. +;; * Introduced `newsticker-enable-logo-manipulations'. +;; * Compare link of items (as well as title and desc). +;; * Added `newsticker-start-hook' and `newsticker-stop-hook', thanks +;; to mace. +;; * Bugfixes -- thanks to Ryan Yeske, Jari Aalto, Bruce Ingalls. +;; * Tested with Emacs 21.3.50, 21.3.1, 21.2, 21.1; XEmacs 21.4.15 + +;; 1.4 * Enabled HTML rendering, added `newsticker-html-renderer' to +;; choose a HTML rendering engine, thanks to Greg Scott for testing +;; * New Outline handling using text properties instead of "**" +;; prefixes. +;; * Added possibility to mark single item as old (bound to key +;; `o' (`newsticker-mark-item-at-point-as-read'). +;; * Added possibility to mark single item as immortal (bound to key +;; `i' (`newsticker-mark-item-at-point-as-immortal'). +;; * Added possibility to display feed logos. +;; * Added `newsticker-heading-format', `newsticker-item-format'. +;; * Added `newsticker-date-format'. +;; * Added `newsticker-justification'. +;; * Added `newsticker-automatically-mark-visited-items-as-old'. +;; * Added `newsticker-w3m-toggle-inline-image' which calls +;; `w3m-toggle-inline-image' if `newsticker-html-renderer' is +;; `w3m-region'. Exists for convenience only (bound to key +;; `RET'). + +;; 1.3 * Compare title AND desc to check whether item is old, except +;; for feed desc +;; * Mark as not-up-to-date only after new items have arrived. +;; * Added XEmacs compatibility code, tested with XEmacs 21.4.13. +;; * Tested with Emacs 21.3.50 and Emacs 21.2.something. +;; * Bugfix: Apply coding-systems to feed title and description, +;; thanks to OHASHI Akira +;; * Bugfix: xml-parser-workaround did not work for japanese texts, +;; thanks to OHASHI Akira +;; * Kill wget-buffers unless newsticker-debug is not nil. +;; * Bugfix: xml-parser-workaround for "DOCTYPE rdf:RDF" + +;; 1.2 Peter S Galbraith +;; * Added `newsticker-url-list-defaults', splitting the URLs into +;; a customizable selection list, and a user add-on list. +;; * Minor checkdoc fixes. + +;; 1.1 * Introduced optional feed-specific wget-arguments. +;; * Keep order of feeds as given in `newsticker-url-list' in +;; *newsticker* buffer. +;; * Ignore unsupported coding systems. + +;; 1.0 * Introduced feed-specific retrieval-timers. +;; * Removed dependency on 'cl (cddddr). +;; * Thanks to Kevin Rodgers and T.V. Raman for their help. +;; * Use utf-8 for reading and writing cache data. +;; * Reported to work with Emacs 21.3.50. + +;; 0.99 * Minor tweaks. +;; * Tested with Emacs 21.3.2 + +;; 0.98 * Check exit status of wget processes. Keep cache data if +;; something went wrong. Throw error when old wget-processes +;; are hanging around. +;; * Introduced newsticker-specific faces. +;; * Added `newsticker-show-descriptions-of-new-items'. +;; * Added `newsticker-hide-old-items-in-newsticker-buffer'. +;; * Added `newsticker-(hide|show)-old-items'. + +;; 0.97 * Minor tweaks. + +;; 0.96 * Added caching. +;; * newsticker-mode inherits outline-mode. +;; * newsticker-mode supports imenu. +;; * Easy buffer-navigation with newsticker-mode's keymap. +;; * Some bugs fixed. +;; * Thanks to Moritz Epple for documentation tips. + +;; 0.95 * Added newsticker-mode -- Thanks to T.V. Raman. +;; * Catch xml-parser errors -- Thanks to T.V. Raman. +;; * Remove stupid newlines in titles (headlines) -- Thanks to +;; Jeff Rancier. + +;; 0.94 * Added clickerability and description for channel headings. +;; * Made it work for (at least some) rss 0.9 feeds. + +;; 0.93 * Added some more sites. +;; * Do not flood the *Messages* buffer. +;; * First attempt at handling coding systems. + +;; 0.92 * Added `newsticker-wget-name'. +;; * Try to display message only if minibuffer and echo area are +;; not in use already. +;; * Dirty workaround for newer versions of xml.el: Remove +;; whitespace in rdf. +;; * Tested with Emacs 21.3.2 and CVS-snapshot of 2003-06-21. + +;; 0.91 * First bugfix: *newsticker* is read-only. + +;; 0.9 * First release. +;; * Tested with Emacs 21.3.2 and wget 1.8.2. + +;; ====================================================================== +;;; To Do: + +;; * Image handling for XEmacs (create-image does not exist) + +;; ====================================================================== +;;; Code: + +(require 'derived) +(require 'xml) + +;; ====================================================================== +;;; Customizables +;; ====================================================================== +(defgroup newsticker nil + "RSS aggregator." + :group 'applications) + +(defconst newsticker--raw-url-list-defaults + '(("CNET News.com" + "http://export.cnet.com/export/feeds/news/rss/1,11176,,00.xml") + ("Debian Security Advisories" + "http://www.debian.org/security/dsa.en.rdf") + ("Debian Security Advisories - Long format" + "http://www.debian.org/security/dsa-long.en.rdf") + ("Emacs Wiki" + "http://www.emacswiki.org/cgi-bin/wiki.pl?action=rss" + nil + 3600) + ("Freshmeat.net" + "http://freshmeat.net/backend/fm.rdf") + ("Kuro5hin.org" + "http://www.kuro5hin.org/backend.rdf") + ("LWN (Linux Weekly News)" + "http://lwn.net/headlines/rss") + ("NewsForge" + "http://newsforge.com/index.rss") + ("NY Times: Technology" + "http://partners.userland.com/nytRss/technology.xml") + ("NY Times" + "http://partners.userland.com/nytRss/nytHomepage.xml") + ("Quote of the day" + "http://www.quotationspage.com/data/qotd.rss" + "07:00" + 86400) + ("The Register" + "http://www.theregister.co.uk/tonys/slashdot.rdf") + ("slashdot" + "http://slashdot.org/index.rss" + nil + 3600) ;/. will ban you if under 3600 seconds! + ("Wired News" + "http://www.wired.com/news_drop/netcenter/netcenter.rdf") + ("Heise News (german)" + "http://www.heise.de/newsticker/heise.rdf") + ("Tagesschau (german)" + "http://www.tagesschau.de/newsticker.rdf" + nil + 1800) + ("Telepolis (german)" + "http://www.heise.de/tp/news.rdf")) + "Default URL list in raw form. +This list is fed into defcustom via `newsticker--splicer'.") + +(defun newsticker--splicer (item) + "Convert ITEM for splicing into `newsticker-url-list-defaults'." + (let ((result (list 'list :tag (nth 0 item) (list 'const (nth 0 item)))) + (element (cdr item))) + (while element + (setq result (append result (list (list 'const (car element))))) + (setq element (cdr element))) + result)) + +;; ====================================================================== +;;; Customization +;; ====================================================================== +(defun newsticker--set-customvar (symbol value) + "Set newsticker-variable SYMBOL value to VALUE. + +Calls all necessary actions which are necessary in order to make +the new value effective. Changing `newsticker-url-list', for example, +will re-start the retrieval-timers." + (unless (condition-case nil + (eq (symbol-value symbol) value) + (error nil)) + (set symbol value) + (cond ((eq symbol 'newsticker-sort-method) + (when (fboundp 'newsticker--cache-sort) + (message "Applying new sort method...") + (newsticker--cache-sort) + (newsticker--buffer-set-uptodate nil) + (message "Applying new sort method...done"))) + ((memq symbol '(newsticker-url-list-defaults + newsticker-url-list + newsticker-retrieval-interval)) + (when (and (fboundp 'newsticker-running-p) + (newsticker-running-p)) + (message "Restarting newsticker") + (newsticker-stop) + (newsticker-start))) + ((eq symbol 'newsticker-display-interval) + (when (and (fboundp 'newsticker-running-p) + (newsticker-running-p)) + (message "Restarting ticker") + (newsticker-stop-ticker) + (newsticker-start-ticker) + (message ""))) + ((memq symbol '(newsticker-hide-old-items-in-echo-area + newsticker-hide-obsolete-items-in-echo-area + newsticker-hide-immortal-items-in-echo-area)) + (when (and (fboundp 'newsticker-running-p) + (newsticker-running-p)) + (message "Restarting newsticker") + (newsticker-stop-ticker) + (newsticker--ticker-text-setup) + (newsticker-start-ticker) + (message ""))) + ((memq symbol '(newsticker-hide-old-items-in-newsticker-buffer + newsticker-show-descriptions-of-new-items)) + (when (fboundp 'newsticker--buffer-set-uptodate) + (newsticker--buffer-set-uptodate nil))) + ((memq symbol '(newsticker-heading-format + newsticker-item-format + newsticker-desc-format + newsticker-date-format + newsticker-statistics-format + newsticker-justification + newsticker-use-full-width + newsticker-html-renderer + newsticker-feed-face + newsticker-new-item-face + newsticker-old-item-face + newsticker-immortal-item-face + newsticker-obsolete-item-face + newsticker-date-face + newsticker-statistics-face + ;;newsticker-default-face + )) + (when (fboundp 'newsticker--forget-preformatted) + (newsticker--forget-preformatted))) + (t + (error "Ooops %s" symbol))))) + +;; customization group feed +(defgroup newsticker-feed nil + "Settings for the RSS feeds." + :group 'newsticker) + +(defcustom newsticker-url-list-defaults + '(("Emacs Wiki" + "http://www.emacswiki.org/cgi-bin/wiki.pl?action=rss" + nil + 3600)) + "A customizable list of news feeds to select from. +These were mostly extracted from the Radio Community Server at +http://subhonker6.userland.com/rcsPublic/rssHotlist. + +You may add other entries in `newsticker-url-list'." + :type `(set ,@(mapcar `newsticker--splicer + newsticker--raw-url-list-defaults)) + :set 'newsticker--set-customvar + :group 'newsticker-feed) + +(defcustom newsticker-url-list nil + "The news feeds which you like to watch. + +This alist will be used in addition to selection made customizing +`newsticker-url-list-defaults'. + +This is an alist. Each element consists of two items: a LABEL and a URL, +optionally followed by a START-TIME, INTERVAL specifier and WGET-ARGUMENTS. + +The LABEL gives the name of the news feed. It can be an arbitrary string. + +The URL gives the location of the news feed. It must point to a valid +RSS file. The RSS file is retrieved by calling wget, or whatever you +specify as `newsticker-wget-name'. + +The START-TIME can be either a string, or nil. If it is a string it +specifies a fixed time at which this feed shall be retrieved for the +first time. (Examples: \"11:00pm\", \"23:00\"). If it is nil (or +unspecified), this feed will be retrieved immediately after calling +`newsticker-start'. + +The INTERVAL specifies the time between retrievals for this feed. If it +is nil (or unspecified) the default interval value as set in +`newsticker-retrieval-interval' is used. + +\(newsticker.el calls `run-at-time'. The newsticker-parameters START-TIME +and INTERVAL correspond to the `run-at-time'-parameters TIME and REPEAT.) + +WGET-ARGUMENTS specifies arguments for wget (see `newsticker-wget-name') +which apply for this feed only, overriding the value of +`newsticker-wget-arguments'." + :type '(repeat (list :tag "News feed" + (string :tag "Label") + (string :tag "URI") + (choice :tag "Start" + (const :tag "Default" nil) + (string :tag "Fixed Time")) + (choice :tag "Interval" + (const :tag "Default" nil) + (const :tag "Hourly" 3600) + (const :tag "Daily" 86400) + (integer :tag "Interval")) + (choice :tag "Wget Arguments" + (const :tag "Default arguments" nil) + (repeat :tag "Special arguments" string)))) + :set 'newsticker--set-customvar + :group 'newsticker-feed) + +(defcustom newsticker-wget-name + "wget" + "Name of the program which is called to retrieve news from the web. +The canonical choice is wget but you may take any other program which is +able to return the contents of a news feed file on stdout." + :type 'string + :group 'newsticker-feed) + +(defcustom newsticker-wget-arguments + '("-q" "-O" "-") + "Arguments which are passed to wget. +There is probably no reason to change the default settings, unless you +are living behind a firewall." + :type '(repeat (string :tag "Argument")) + :group 'newsticker-feed) + +(defcustom newsticker-retrieval-interval + 3600 + "Time interval for retrieving new news items (seconds). +If this value is not positive (i.e. less than or equal to 0) +items are retrieved only once! +Please note that some feeds, e.g. Slashdot, will ban you if you +make it less than 1800 seconds (30 minutes)!" + :type '(choice :tag "Interval" + (const :tag "No automatic retrieval" 0) + (const :tag "Hourly" 3600) + (const :tag "Daily" 86400) + (integer :tag "Interval")) + :set 'newsticker--set-customvar + :group 'newsticker-feed) + +(defcustom newsticker-desc-comp-max + 100 + "Relevant length of headline descriptions. +This value gives the maximum number of characters which will be +taken into account when newsticker compares two headline +descriptions." + :type 'integer + :group 'newsticker-feed) + +;; customization group behaviour +(defgroup newsticker-headline-processing nil + "Settings for the automatic processing of RSS headlines." + :group 'newsticker) + +(defcustom newsticker-automatically-mark-items-as-old + t + "Decides whether to automatically mark items as old. +If t a new item is considered as new only after its first retrieval. As +soon as it is retrieved a second time, it becomes old. If not t all +items stay new until you mark them as old. This is done in the +*newsticker* buffer." + :type 'boolean + :group 'newsticker-headline-processing) + +(defcustom newsticker-automatically-mark-visited-items-as-old + t + "Decides whether to automatically mark visited items as old. +If t an item is marked as old as soon as the associated link is +visited, i.e. after pressing RET or mouse2 on the item's +headline." + + :type 'boolean + :group 'newsticker-headline-processing) + +(defcustom newsticker-keep-obsolete-items + t + "Decides whether to keep unread items which have been removed from feed. +If t a new item, which has been removed from the feed, is kept in +the cache until it is marked as read." + :type 'boolean + :group 'newsticker-headline-processing) + +(defcustom newsticker-obsolete-item-max-age + (* 60 60 24) + "Maximal age of obsolete items, in seconds. +Obsolete items which are older than this value will be silently +deleted at the next retrieval." + :type 'integer + :group 'newsticker-headline-processing) + +(defcustom newsticker-auto-mark-filter + nil + "A filter for automatically marking headlines. + +This is an alist of the form (FEED-NAME OLD-LIST IMMORTAL-LIST). I.e. each +element consists of a FEED-NAME and two lists. Each list consists a set of +regular expressions. The first list contains patterns of headlines which +will be marked as old. The second list contains patterns of headlines which +will be marked as immortal. + +This filter is checked after a new headline has been retrieved. If +FEED-NAME matches the name of the corresponding news feed, both sublists +are checked: If the title of the headline matches any of the regular +expressions in OLD-LIST, this headline is marked as old, if it matches any +of the expressions in IMMORTAL-LIST it is marked as immortal. + +If, for example, `newsticker-auto-mark-filter' looks like + \((slashdot (\"^Forget me!$\") (\"^Read me$\" \"important\"))) +then all articles from slashdot are marked as old if they have the title +\"Forget me!\". All articles which have the title \"Read me\" and all +articles which contain the string \"important\" in their title are marked +as immortal." + :type '(repeat (list :tag "Feed filter rule" + (string :tag "Feed name") + ;;(choice ,@(mapcar (lambda (i) + ;; (list :tag (car i) (car i))) + ;; newsticker-url-list)) + (repeat :tag "Mark as old" string) + (repeat :tag "Mark as immortal" string))) + :group 'newsticker-headline-processing) + +;; customization group layout +(defgroup newsticker-layout nil + "Settings for layout of the RSS reader." + :group 'newsticker) + +(defcustom newsticker-sort-method + 'sort-by-original-order + "Sort method for news items. +The following sort methods are available: +* `sort-by-original-order' keeps the order in which the items + appear in the RSS file (please note that for immortal items, + which have been removed from the news feed, there is no original + order), +* `sort-by-time' looks at the time at which an item has been seen + the first time. The most recent item is put at top, +* `sort-by-title' will put the items in an alphabetical order." + :type '(choice + (const :tag "Keep original order" sort-by-original-order) + (const :tag "Sort by time" sort-by-time) + (const :tag "Sort by title" sort-by-title)) + :set 'newsticker--set-customvar + :group 'newsticker-layout) + +(defcustom newsticker-hide-old-items-in-newsticker-buffer + nil + "Decides whether to automatically hide old items in the *newsticker* buffer. +If set to t old items will be completely folded and only new items +will show up in the *newsticker* buffer. Otherwise old as well as new +items will be visible." + :type 'boolean + :set 'newsticker--set-customvar + :group 'newsticker-layout) + +(defcustom newsticker-show-descriptions-of-new-items + t + "Whether to automatically show descriptions of new items in *newsticker*. +If set to t old items will be folded and new items will be +unfolded. Otherwise old as well as new items will be folded." + :type 'boolean + :set 'newsticker--set-customvar + :group 'newsticker-layout) + +(defcustom newsticker-heading-format + "%l +%t %d %s" + "Format string for feed headings. +The following printf-like specifiers can be used: +%d The date the feed was retrieved. See `newsticker-date-format'. +%l The logo (image) of the feed. Most RSS feeds provide a small + image as logo. Newsticker can display them, if Emacs can -- + see `image-types' for a list of supported image types. +%L The logo (image) of the feed. If the logo is not available + the title of the feed is used. +%s The statistical data of the feed. See `newsticker-statistics-format'. +%t The title of the feed, i.e. its name." + :type 'string + :set 'newsticker--set-customvar + :group 'newsticker-layout) + +(defcustom newsticker-item-format + "%t %d" + "Format string for news item headlines. +The following printf-like specifiers can be used: +%d The date the item was (first) retrieved. See `newsticker-date-format'. +%l The logo (image) of the feed. Most RSS feeds provide a small + image as logo. Newsticker can display them, if Emacs can -- + see `image-types' for a list of supported image types. +%L The logo (image) of the feed. If the logo is not available + the title of the feed is used. +%t The title of the item." + :type 'string + :set 'newsticker--set-customvar + :group 'newsticker-layout) + +(defcustom newsticker-desc-format + "%d %c" + "Format string for news descriptions (contents). +The following printf-like specifiers can be used: +%c The contents (description) of the item. +%d The date the item was (first) retrieved. See `newsticker-date-format'." + :type 'string + :set 'newsticker--set-customvar + :group 'newsticker-layout) + +(defcustom newsticker-date-format + "(%A, %H:%M)" + "Format for the date part in item and feed lines. +See `format-time-string' for a list of valid specifiers." + :type 'string + :set 'newsticker--set-customvar + :group 'newsticker-layout) + +(defcustom newsticker-statistics-format + "[%n + %i + %o + %O = %a]" + "Format for the statistics part in feed lines. +The following printf-like specifiers can be used: +%a The number of all items in the feed. +%i The number of immortal items in the feed. +%n The number of new items in the feed. +%o The number of old items in the feed. +%O The number of obsolete items in the feed." + :type 'string + :set 'newsticker--set-customvar + :group 'newsticker-layout) + +(defcustom newsticker-show-all-rss-elements + nil + "Show all RSS elements." + :type 'boolean + ;;:set 'newsticker--set-customvar + :group 'newsticker-layout) + +;; image related things +(defcustom newsticker-enable-logo-manipulations + t + "If non-nil newsticker manipulates logo images. +This enables the following image properties: heuristic mask for all +logos, and laplace-conversion for images without new items." + :type 'boolean + :group 'newsticker-layout) + + +;; rendering +(defcustom newsticker-justification + 'left + "How to fill item descriptions. +If non-nil newsticker calls `fill-region' to wrap long lines in +item descriptions. However, if an item description contains HTML +text and `newsticker-html-renderer' is non-nil, filling is not +done." + :type '(choice :tag "Justification" + (const :tag "No filling" nil) + (const :tag "Left" left) + (const :tag "Right" right) + (const :tag "Center" center) + (const :tag "Full" full)) + :set 'newsticker--set-customvar + :group 'newsticker-layout) + +(defcustom newsticker-use-full-width + t + "Decides whether to use the full window width when filling. +If non-nil newsticker sets `fill-column' so that the whole +window is used when filling. See also `newsticker-justification'." + :type 'boolean + :set 'newsticker--set-customvar + :group 'newsticker-layout) + +(defcustom newsticker-html-renderer + nil + "Function for rendering HTML contents. +If non-nil, newsticker.el will call this function whenever it finds +HTML-like tags in item descriptions. Possible functions are, for +example, `w3m-region', `w3-region', and (if you have htmlr.el installed) +`newsticker-htmlr-render'. + +In order to make sure that the HTML renderer is loaded when you +run newsticker, you should add one of the following statements to +your .emacs. If you use w3m, + + (autoload 'w3m-region \"w3m\" + \"Render region in current buffer and replace with result.\" t) + +or, if you use w3, + + (require 'w3-auto) + +or, if you use htmlr + + (require 'htmlr)" + :type '(choice :tag "Function" + (const :tag "None" nil) + (const :tag "w3" w3-region) + (const :tag "w3m" w3m-region) + (const :tag "htmlr" newsticker-htmlr-render)) + :set 'newsticker--set-customvar + :group 'newsticker-layout) + + +;; faces +(defgroup newsticker-faces nil + "Settings for the faces of the RSS reader." + :group 'newsticker-layout) + +(defface newsticker-feed-face + '((((class color) (background dark)) + (:family "helvetica" :bold t :height 1.2 :foreground "misty rose")) + (((class color) (background light)) + (:family "helvetica" :bold t :height 1.2 :foreground "black"))) + "Face for news feeds." + :group 'newsticker-faces) + +(defface newsticker-new-item-face + '((((class color) (background dark)) + (:family "helvetica" :bold t)) + (((class color) (background light)) + (:family "helvetica" :bold t))) + "Face for old news items." + :group 'newsticker-faces) + +(defface newsticker-old-item-face + '((((class color) (background dark)) + (:family "helvetica" :bold t :foreground "orange3")) + (((class color) (background light)) + (:family "helvetica" :bold t :foreground "red4"))) + "Face for old news items." + :group 'newsticker-faces) + +(defface newsticker-immortal-item-face + '((((class color) (background dark)) + (:family "helvetica" :bold t :italic t :foreground "orange")) + (((class color) (background light)) + (:family "helvetica" :bold t :italic t :foreground "blue"))) + "Face for immortal news items." + :group 'newsticker-faces) + +(defface newsticker-obsolete-item-face + '((((class color) (background dark)) + (:family "helvetica" :bold t :strike-through t)) + (((class color) (background light)) + (:family "helvetica" :bold t :strike-through t))) + "Face for old news items." + :group 'newsticker-faces) + +(defface newsticker-date-face + '((((class color) (background dark)) + (:family "helvetica" :italic t :height 0.8)) + (((class color) (background light)) + (:family "helvetica" :italic t :height 0.8))) + "Face for newsticker dates." + :group 'newsticker-faces) + +(defface newsticker-statistics-face + '((((class color) (background dark)) + (:family "helvetica" :italic t :height 0.8)) + (((class color) (background light)) + (:family "helvetica" :italic t :height 0.8))) + "Face for newsticker dates." + :group 'newsticker-faces) + +(defface newsticker-enclosure-face + '((((class color) (background dark)) + (:bold t :background "orange")) + (((class color) (background light)) + (:bold t :background "orange"))) + "Face for enclosed elements." + :group 'newsticker-faces) + +(defface newsticker-extra-face + '((((class color) (background dark)) + (:italic t :foreground "gray50" :height 0.8)) + (((class color) (background light)) + (:italic t :foreground "gray50" :height 0.8))) + "Face for newsticker dates." + :group 'newsticker-faces) + +;; (defface newsticker-default-face +;; '((((class color) (background dark)) +;; (:inherit default)) +;; (((class color) (background light)) +;; (:inherit default))) +;; "Face for the description of news items." +;; ;;:set 'newsticker--set-customvar +;; :group 'newsticker-faces) + + +;; customization group ticker +(defgroup newsticker-ticker nil + "Settings for the RSS ticker." + :group 'newsticker) + +(defcustom newsticker-display-interval + 0.3 + "Time interval for displaying news items in the echo area (seconds). +If equal or less than 0 no messages are shown in the echo area. For +smooth display (see `newsticker-scroll-smoothly') a value of 0.3 seems +reasonable. For non-smooth display a value of 10 is a good starting +point." + :type 'number + :set 'newsticker--set-customvar + :group 'newsticker-ticker) + +(defcustom newsticker-scroll-smoothly + t + "Decides whether to flash or scroll news items. +If t the news headlines are scrolled (more-or-less) smoothly in the echo +area. If nil one headline after another is displayed in the echo area. +The variable `newsticker-display-interval' determines how fast this +display moves/changes and whether headlines are shown in the echo area +at all. If you change `newsticker-scroll-smoothly' you should also change +`newsticker-display-interval'." + :type 'boolean + :group 'newsticker-ticker) + +(defcustom newsticker-hide-immortal-items-in-echo-area + t + "Decides whether to show immortal/non-expiring news items in the ticker. +If t the echo area will not show immortal items. See also +`newsticker-hide-old-items-in-echo-area." + :type 'boolean + :set 'newsticker--set-customvar + :group 'newsticker-ticker) + +(defcustom newsticker-hide-old-items-in-echo-area + t + "Decides whether to show only the newest news items in the ticker. +If t the echo area will show only new items, i.e. only items which have +been added between the last two retrievals." + :type 'boolean + :set 'newsticker--set-customvar + :group 'newsticker-ticker) + +(defcustom newsticker-hide-obsolete-items-in-echo-area + t + "Decides whether to show obsolete items items in the ticker. +If t the echo area will not show obsolete items. See also +`newsticker-hide-old-items-in-echo-area." + :type 'boolean + :set 'newsticker--set-customvar + :group 'newsticker-ticker) + +(defgroup newsticker-hooks nil + "Settings for newsticker hooks." + :group 'newsticker) + +(defcustom newsticker-start-hook + nil + "Hook run when starting newsticker. +This hook is run at the very end of `newsticker-start'." + :options '(newsticker-start-ticker) + :type 'hook + :group 'newsticker-hooks) + +(defcustom newsticker-stop-hook + nil + "Hook run when stopping newsticker. +This hook is run at the very end of `newsticker-stop'." + :options nil + :type 'hook + :group 'newsticker-hooks) + +(defcustom newsticker-new-item-functions + nil + "List of functions run after a new headline has been retrieved. +Each function is called with the following three arguments: +FEED the name of the corresponding news feed, +TITLE the title of the headline, +DESC the decoded description of the headline. + +See `newsticker-download-images', and +`newsticker-download-enclosures' for sample functions. + +Please note that these functions are called only once for a +headline after it has been retrieved for the first time." + :type 'hook + :options '(newsticker-download-images + newsticker-download-enclosures) + :group 'newsticker-hooks) + +(defcustom newsticker-select-item-hook + 'newsticker--buffer-make-item-completely-visible + "List of functions run after a headline has been selected. +Each function is called after one of `newsticker-next-item', +`newsticker-next-new-item', `newsticker-previous-item', +`newsticker-previous-new-item' has been called. + +The default value 'newsticker--buffer-make-item-completely-visible +assures that the current item is always completely visible." + :type 'hook + :options '(newsticker--buffer-make-item-completely-visible) + :group 'newsticker-hooks) + +(defcustom newsticker-select-feed-hook + 'newsticker--buffer-make-item-completely-visible + "List of functions run after a feed has been selected. +Each function is called after one of `newsticker-next-feed', and +`newsticker-previous-feed' has been called. + +The default value 'newsticker--buffer-make-item-completely-visible +assures that the current feed is completely visible." + :type 'hook + :options '(newsticker--buffer-make-item-completely-visible) + :group 'newsticker-hooks) + +(defcustom newsticker-buffer-change-hook + 'newsticker-w3m-show-inline-images + "List of functions run after the newsticker buffer has been updated. +Each function is called after `newsticker-buffer-update' has been called. + +The default value '`newsticker-w3m-show-inline-images' loads inline +images." + :type 'hook + :group 'newsticker-hooks) + +(defcustom newsticker-narrow-hook + 'newsticker-w3m-show-inline-images + "List of functions run after narrowing in newsticker buffer has changed. +Each function is called after +`newsticker-toggle-auto-narrow-to-feed' or +`newsticker-toggle-auto-narrow-to-item' has been called. + +The default value '`newsticker-w3m-show-inline-images' loads inline +images." + :type 'hook + :group 'newsticker-hooks) + +(defgroup newsticker-miscellaneous nil + "Miscellaneous newsticker settings." + :group 'newsticker) + +(defcustom newsticker-cache-filename + "~/.newsticker-cache" + "Name of the newsticker cache file." + :type 'string + :group 'newsticker-miscellaneous) + +(defcustom newsticker-imagecache-dirname + "~/.newsticker-images" + "Name of the directory where newsticker stores cached images." + :type 'string + :group 'newsticker-miscellaneous) + +;; debugging +(defcustom newsticker-debug + nil + "Enables some features needed for debugging newsticker.el. + +If set to t newsticker.el will print lots of debugging messages, and the +buffers *newsticker-wget-* will not be closed." + :type 'boolean + ;;:set 'newsticker--set-customvar + :group 'newsticker-miscellaneous) + +;; ====================================================================== +;;; Compatibility section, XEmacs, Emacs +;; ====================================================================== +(unless (fboundp 'time-add) + (require 'time-date);;FIXME + (defun time-add (t1 t2) + (seconds-to-time (+ (time-to-seconds t1) (time-to-seconds t2))))) + +(unless (fboundp 'match-string-no-properties) + (defalias 'match-string-no-properties 'match-string)) + +(unless (fboundp 'replace-regexp-in-string) + (defun replace-regexp-in-string (re rp st) + (save-match-data ;; apparently XEmacs needs save-match-data + (replace-in-string st re rp)))) + +;; copied from subr.el +(unless (fboundp 'add-to-invisibility-spec) + (defun add-to-invisibility-spec (arg) + "Add elements to `buffer-invisibility-spec'. +See documentation for `buffer-invisibility-spec' for the kind of elements +that can be added." + (if (eq buffer-invisibility-spec t) + (setq buffer-invisibility-spec (list t))) + (setq buffer-invisibility-spec + (cons arg buffer-invisibility-spec)))) + +;; copied from subr.el +(unless (fboundp 'remove-from-invisibility-spec) + (defun remove-from-invisibility-spec (arg) + "Remove elements from `buffer-invisibility-spec'." + (if (consp buffer-invisibility-spec) + (setq buffer-invisibility-spec (delete arg buffer-invisibility-spec))))) + +;; ====================================================================== +;;; Internal variables +;; ====================================================================== +(defvar newsticker--display-timer nil + "Timer for newsticker display.") +(defvar newsticker--retrieval-timer-list nil + "List of timers for news retrieval. +This is an alist, each element consisting of (feed-name . timer)") +(defvar newsticker--item-list nil + "List of newsticker items.") +(defvar newsticker--item-position 0 + "Actual position in list of newsticker items.") +(defvar newsticker--prev-message "There was no previous message yet!" + "Last message that the newsticker displayed.") +(defvar newsticker--scrollable-text "" + "The text which is scrolled smoothly in the echo area.") +(defvar newsticker--buffer-uptodate-p nil + "Tells whether the newsticker buffer is up to date.") +(defvar newsticker--latest-update-time (current-time) + "The time at which the latest news arrived.") + +(defvar newsticker--cache nil "Cached newsticker data. +This is a list of the form + + ((label1 + (title description link time age index preformatted-contents + preformatted-title) + ...) + (label2 + (title description link time age index preformatted-contents + preformatted-title) + ...) + ...) + +where LABEL is a symbol. TITLE, DESCRIPTION, and LINK are +strings. TIME is a time value as returned by `current-time'. +AGE is a symbol: 'new, 'old, 'immortal, and 'obsolete denote +ordinary news items, whereas 'feed denotes an item which is not a +headline but describes the feed itself. INDEX denotes the +original position of the item -- used for restoring the original +order. PREFORMATTED-CONTENTS and PREFORMATTED-TITLE hold the +formatted contents of the item's description and title. This +speeds things up if HTML rendering is used, which is rather +slow.") + +(defvar newsticker--auto-narrow-to-feed nil + "Automatically narrow to current news feed. +If non-nil only the items of the current news feed are visible.") + +(defvar newsticker--auto-narrow-to-item nil + "Automatically narrow to current news item. +If non-nil only the current headline is visible.") + +(defconst newsticker--error-headline + "[COULD NOT DOWNLOAD HEADLINES!]" + "Title of error headline which will be inserted if news retrieval fails.") + +;; ====================================================================== +;;; Toolbar +;; ====================================================================== +(defconst newsticker--next-item-image + (if (fboundp 'create-image) + (create-image "/* XPM */ +static char * next_xpm[] = { +\"24 24 42 1\", +\" c None\", +\". c #000000\", +\"+ c #7EB6DE\", +\"@ c #82BBE2\", +\"# c #85BEE4\", +\"$ c #88C1E7\", +\"% c #8AC3E8\", +\"& c #87C1E6\", +\"* c #8AC4E9\", +\"= c #8CC6EA\", +\"- c #8CC6EB\", +\"; c #88C2E7\", +\"> c #8BC5E9\", +\", c #8DC7EB\", +\"' c #87C0E6\", +\") c #8AC4E8\", +\"! c #8BC5EA\", +\"~ c #8BC4E9\", +\"{ c #88C1E6\", +\"] c #89C3E8\", +\"^ c #86BFE5\", +\"/ c #83BBE2\", +\"( c #82BBE1\", +\"_ c #86C0E5\", +\": c #87C0E5\", +\"< c #83BCE2\", +\"[ c #81B9E0\", +\"} c #81BAE1\", +\"| c #78B0D9\", +\"1 c #7BB3DB\", +\"2 c #7DB5DD\", +\"3 c #7DB6DD\", +\"4 c #72A9D4\", +\"5 c #75ACD6\", +\"6 c #76AED7\", +\"7 c #77AFD8\", +\"8 c #6BA1CD\", +\"9 c #6EA4CF\", +\"0 c #6FA6D1\", +\"a c #6298C6\", +\"b c #659BC8\", +\"c c #5C91C0\", +\" \", +\" \", +\" . \", +\" .. \", +\" .+. \", +\" .@#. \", +\" .#$%. \", +\" .&*=-. \", +\" .;>,,,. \", +\" .;>,,,=. \", +\" .')!==~;. \", +\" .#{]*%;^/. \", +\" .(#_':#<. \", +\" .+[@ c #8DC7EB\", +\", c #89C3E8\", +\"' c #8AC4E8\", +\") c #8BC5EA\", +\"! c #88C1E6\", +\"~ c #8AC4E9\", +\"{ c #8AC3E8\", +\"] c #86C0E5\", +\"^ c #87C0E6\", +\"/ c #87C0E5\", +\"( c #82BBE2\", +\"_ c #81BAE1\", +\": c #7FB7DF\", +\"< c #7DB6DD\", +\"[ c #7DB5DD\", +\"} c #7CB4DC\", +\"| c #79B1DA\", +\"1 c #76ADD7\", +\"2 c #77AFD8\", +\"3 c #73AAD4\", +\"4 c #70A7D1\", +\"5 c #6EA5D0\", +\"6 c #6CA2CE\", +\"7 c #689ECB\", +\"8 c #6399C7\", +\"9 c #6095C4\", +\"0 c #5C90C0\", +\" \", +\" \", +\" . \", +\" .. \", +\" .+. \", +\" .@#. \", +\" .$%&. \", +\" .*=-;. \", +\" .>>*,%. \", +\" .>>>*,%. \", +\" .')**=-;. \", +\" .;!,~{-%&. \", +\" .;]^/;@#. \", +\" .(@&_:+. \", +\" .<[}|1. \", +\" .2134. \", +\" .567. \", +\" .89. \", +\" .0. \", +\" .. \", +\" . \", +\" \", +\" \", +\" \"}; +" + 'xpm t) + "Image for the previous item button.")) + +(defconst newsticker--previous-feed-image + (if (fboundp 'create-image) + (create-image "/* XPM */ +static char * prev_feed_xpm[] = { +\"24 24 52 1\", +\" c None\", +\". c #000000\", +\"+ c #70A7D2\", +\"@ c #75ADD6\", +\"# c #71A8D3\", +\"$ c #79B1DA\", +\"% c #7BB3DB\", +\"& c #7DB5DD\", +\"* c #83BBE2\", +\"= c #7EB6DE\", +\"- c #78B0D9\", +\"; c #7FB7DE\", +\"> c #88C2E7\", +\", c #85BEE4\", +\"' c #80B9E0\", +\") c #80B8DF\", +\"! c #8CC6EA\", +\"~ c #89C3E8\", +\"{ c #86BFE5\", +\"] c #81BAE1\", +\"^ c #7CB4DC\", +\"/ c #7FB8DF\", +\"( c #8DC7EB\", +\"_ c #7BB3DC\", +\": c #7EB7DE\", +\"< c #8BC4E9\", +\"[ c #8AC4E9\", +\"} c #8AC3E8\", +\"| c #87C0E6\", +\"1 c #87C0E5\", +\"2 c #83BCE2\", +\"3 c #75ACD6\", +\"4 c #7FB7DF\", +\"5 c #77AED8\", +\"6 c #71A8D2\", +\"7 c #70A7D1\", +\"8 c #76ADD7\", +\"9 c #6CA2CE\", +\"0 c #699FCC\", +\"a c #73AAD4\", +\"b c #6BA1CD\", +\"c c #669CC9\", +\"d c #6298C5\", +\"e c #689ECB\", +\"f c #6499C7\", +\"g c #6095C3\", +\"h c #5C91C0\", +\"i c #5E93C2\", +\"j c #5B90C0\", +\"k c #588CBC\", +\"l c #578CBC\", +\"m c #5589BA\", +\" \", +\" \", +\" ... . \", +\" .+. .. \", +\" .@. .#. \", +\" .$. .%@. \", +\" .&. .*=-. \", +\" .;. .>,'%. \", +\" .). .!~{]^. \", +\" ./. .(!~{]_. \", +\" .:. .!!<>,'%. \", +\" .&. .~[}>{*=-. \", +\" .$. .|1,2/%@. \", +\" .3. .*]4%56. \", +\" .7. .^$8#9. \", +\" .0. .a7bc. \", +\" .d. .efg. \", +\" .h. .ij. \", +\" .k. .l. \", +\" .m. .. \", +\" ... . \", +\" \", +\" \", +\" \"}; +" + 'xpm t) + "Image for the previous feed button.")) + +(defconst newsticker--next-feed-image + (if (fboundp 'create-image) + (create-image "/* XPM */ +static char * next_feed_xpm[] = { +\"24 24 57 1\", +\" c None\", +\". c #000000\", +\"+ c #6CA2CE\", +\"@ c #75ADD6\", +\"# c #71A8D3\", +\"$ c #79B1DA\", +\"% c #7EB7DE\", +\"& c #7DB5DD\", +\"* c #81BAE1\", +\"= c #85BEE4\", +\"- c #78B0D9\", +\"; c #7FB7DE\", +\"> c #83BCE3\", +\", c #87C1E6\", +\"' c #8AC4E9\", +\") c #7BB3DB\", +\"! c #80B8DF\", +\"~ c #88C2E7\", +\"{ c #8BC5E9\", +\"] c #8DC7EB\", +\"^ c #7CB4DC\", +\"/ c #7FB8DF\", +\"( c #84BDE3\", +\"_ c #7BB3DC\", +\": c #83BCE2\", +\"< c #87C0E6\", +\"[ c #8AC4E8\", +\"} c #8BC5EA\", +\"| c #8CC6EA\", +\"1 c #88C1E6\", +\"2 c #89C3E8\", +\"3 c #8AC3E8\", +\"4 c #7EB6DE\", +\"5 c #82BBE1\", +\"6 c #86C0E5\", +\"7 c #87C0E5\", +\"8 c #75ACD6\", +\"9 c #7AB2DA\", +\"0 c #81B9E0\", +\"a c #82BBE2\", +\"b c #71A8D2\", +\"c c #70A7D1\", +\"d c #74ACD6\", +\"e c #699FCC\", +\"f c #6EA5D0\", +\"g c #72A9D4\", +\"h c #669CC9\", +\"i c #6298C5\", +\"j c #679DCA\", +\"k c #6BA1CD\", +\"l c #6095C3\", +\"m c #5C91C0\", +\"n c #5F94C2\", +\"o c #5B90C0\", +\"p c #588CBC\", +\"q c #578CBC\", +\"r c #5589BA\", +\" \", +\" \", +\" . ... \", +\" .. .+. \", +\" .@. .#. \", +\" .$%. .@. \", +\" .&*=. .-. \", +\" .;>,'. .). \", +\" .!=~{]. .^. \", +\" ./(~{]]. ._. \", +\" .%:<[}||. .). \", +\" .&*=12'3~. .-. \", +\" .$45=6<7. .@. \", +\" .8940a:. .b. \", +\" .cd-)&. .+. \", +\" .efg8. .h. \", +\" .ijk. .l. \", +\" .mn. .o. \", +\" .p. .q. \", +\" .. .r. \", +\" . ... \", +\" \", +\" \", +\" \"}; +" + 'xpm t) + "Image for the next feed button.")) + +(defconst newsticker--mark-read-image + (if (fboundp 'create-image) + (create-image "/* XPM */ +static char * mark_read_xpm[] = { +\"24 24 44 1\", +\" c None\", +\". c #C20000\", +\"+ c #BE0000\", +\"@ c #C70000\", +\"# c #CE0000\", +\"$ c #C90000\", +\"% c #BD0000\", +\"& c #CB0000\", +\"* c #D10000\", +\"= c #D70000\", +\"- c #D30000\", +\"; c #CD0000\", +\"> c #C60000\", +\", c #D40000\", +\"' c #DA0000\", +\") c #DE0000\", +\"! c #DB0000\", +\"~ c #D60000\", +\"{ c #D00000\", +\"] c #DC0000\", +\"^ c #E00000\", +\"/ c #E40000\", +\"( c #E10000\", +\"_ c #DD0000\", +\": c #D80000\", +\"< c #E50000\", +\"[ c #E70000\", +\"} c #E60000\", +\"| c #E20000\", +\"1 c #E90000\", +\"2 c #E80000\", +\"3 c #E30000\", +\"4 c #DF0000\", +\"5 c #D90000\", +\"6 c #CC0000\", +\"7 c #C10000\", +\"8 c #C30000\", +\"9 c #BF0000\", +\"0 c #B90000\", +\"a c #BC0000\", +\"b c #BB0000\", +\"c c #B80000\", +\"d c #B50000\", +\"e c #B70000\", +\" \", +\" \", +\" \", +\" . + \", +\" +@# $.% \", +\" &*= -;> \", +\" ,') !~{ \", +\" ]^/ (_: \", +\" (<[ }|) \", +\" <[1 2<| \", +\" }222[< \", +\" }}}< \", +\" 333| \", +\" _4^4)] \", +\" ~:' 5=- \", +\" 6{- *#$ \", +\" 7>$ @89 \", +\" 0a+ %bc \", +\" ddc edd \", +\" ddd ddd \", +\" d d \", +\" \", +\" \", +\" \"}; +" + 'xpm t) + "Image for the next feed button.")) + +(defconst newsticker--mark-immortal-image + (if (fboundp 'create-image) + (create-image "/* XPM */ +static char * mark_immortal_xpm[] = { +\"24 24 93 2\", +\" c None\", +\". c #171717\", +\"+ c #030303\", +\"@ c #000000\", +\"# c #181818\", +\"$ c #090909\", +\"% c #FFC960\", +\"& c #FFCB61\", +\"* c #FFCB62\", +\"= c #FFC961\", +\"- c #FFC75F\", +\"; c #FFC65E\", +\"> c #FFCA61\", +\", c #FFCD63\", +\"' c #FFCF65\", +\") c #FFD065\", +\"! c #FFCE64\", +\"~ c #FFC35C\", +\"{ c #FFC45D\", +\"] c #FFD166\", +\"^ c #FFD267\", +\"/ c #FFD368\", +\"( c #FFD167\", +\"_ c #FFC05A\", +\": c #010101\", +\"< c #040404\", +\"[ c #FFCC62\", +\"} c #FFD569\", +\"| c #FFD56A\", +\"1 c #FFC860\", +\"2 c #FFC25B\", +\"3 c #FFBB56\", +\"4 c #020202\", +\"5 c #060606\", +\"6 c #FFC15B\", +\"7 c #FFC85F\", +\"8 c #FFD469\", +\"9 c #FFD66A\", +\"0 c #FFBC57\", +\"a c #1B1B1B\", +\"b c #070707\", +\"c c #FFBA55\", +\"d c #FFB451\", +\"e c #FFB954\", +\"f c #FFB350\", +\"g c #FFB652\", +\"h c #FFBE58\", +\"i c #FFCD64\", +\"j c #FFD066\", +\"k c #FFC059\", +\"l c #FFB14E\", +\"m c #0B0B0B\", +\"n c #FFBB55\", +\"o c #FFC15A\", +\"p c #FFB552\", +\"q c #FFAD4B\", +\"r c #080808\", +\"s c #FFAF4C\", +\"t c #FFB853\", +\"u c #FFA948\", +\"v c #050505\", +\"w c #FFB04E\", +\"x c #FFB753\", +\"y c #FFBC56\", +\"z c #FFC55D\", +\"A c #FFC55E\", +\"B c #FFC45C\", +\"C c #FFBD57\", +\"D c #FFB854\", +\"E c #FFB34F\", +\"F c #FFAB4A\", +\"G c #FFA545\", +\"H c #FFAA49\", +\"I c #FFB04D\", +\"J c #FFB551\", +\"K c #FFBF58\", +\"L c #FFB24F\", +\"M c #FFAC4A\", +\"N c #FFA646\", +\"O c #FFA344\", +\"P c #FFA848\", +\"Q c #FFB14F\", +\"R c #FFAF4D\", +\"S c #FFA546\", +\"T c #FFA243\", +\"U c #FFA445\", +\"V c #FFAE4C\", +\"W c #FFA444\", +\"X c #FFA142\", +\"Y c #FF9F41\", +\"Z c #0A0A0A\", +\"` c #FF9E40\", +\" . c #FF9F40\", +\" \", +\" \", +\" \", +\" . + @ @ + # \", +\" $ @ % & * * = - + + \", +\" @ ; > , ' ) ' ! * - ~ @ \", +\" @ { > ! ] ^ / / ( ' * ; _ : \", +\" < _ ; [ ) / } | } / ] , 1 2 3 4 \", +\" 5 6 7 , ] 8 9 9 9 } ^ ! = ~ 0 a \", +\" b c 6 - , ] 8 9 9 9 } ^ ! % ~ 0 d 5 \", +\" : e _ ; * ) / 8 } } / ] , 1 2 3 f 5 \", +\" : g h { = i j ^ / ^ ] ! * ; k e l m \", +\" : f n o ; > , ' ) ' ! * - 2 0 p q r \", +\" : s g 0 6 ; % > * * = - ~ h t l u r \", +\" v u w x y k ~ z A z B o C D E F G b \", +\" 5 H I J e 0 h K h C c x L M N . \", +\" 4 O P q Q d g x g J L R H S T < \", +\" @ T U P F q V q M H N W X + \", +\" @ Y T O W G G W O X Y @ \", +\" 4 Z ` Y Y Y .` 4 4 \", +\" 5 : : @ @ Z \", +\" \", +\" \", +\" \"}; +" + 'xpm t) + "Image for the next feed button.")) + + +(defconst newsticker--narrow-image + (if (fboundp 'create-image) + (create-image "/* XPM */ +static char * narrow_xpm[] = { +\"24 24 48 1\", +\" c None\", +\". c #000000\", +\"+ c #969696\", +\"@ c #9E9E9E\", +\"# c #A4A4A4\", +\"$ c #AAAAAA\", +\"% c #AEAEAE\", +\"& c #B1B1B1\", +\"* c #B3B3B3\", +\"= c #B4B4B4\", +\"- c #B2B2B2\", +\"; c #AFAFAF\", +\"> c #ABABAB\", +\", c #A6A6A6\", +\"' c #A0A0A0\", +\") c #989898\", +\"! c #909090\", +\"~ c #73AAD4\", +\"{ c #7AB2DA\", +\"] c #7FB8DF\", +\"^ c #84BDE3\", +\"/ c #88C2E7\", +\"( c #8BC5E9\", +\"_ c #8DC7EB\", +\": c #8CC6EA\", +\"< c #89C3E8\", +\"[ c #86BFE5\", +\"} c #81BAE1\", +\"| c #7BB3DC\", +\"1 c #75ACD6\", +\"2 c #6DA4CF\", +\"3 c #979797\", +\"4 c #A3A3A3\", +\"5 c #A8A8A8\", +\"6 c #ADADAD\", +\"7 c #ACACAC\", +\"8 c #A9A9A9\", +\"9 c #A5A5A5\", +\"0 c #9A9A9A\", +\"a c #929292\", +\"b c #8C8C8C\", +\"c c #808080\", +\"d c #818181\", +\"e c #838383\", +\"f c #848484\", +\"g c #858585\", +\"h c #868686\", +\"i c #828282\", +\" \", +\" \", +\" \", +\" .................. \", +\" .+@#$%&*=*-;>,')!. \", +\" .................. \", +\" \", +\" \", +\" .................. \", +\" .~{]^/(___:<[}|12. \", +\" .................. \", +\" \", +\" \", +\" .................. \", +\" .!3@45>666789'0ab. \", +\" .................. \", +\" \", +\" \", +\" .................. \", +\" .cccdefghhgficccc. \", +\" .................. \", +\" \", +\" \", +\" \"}; +" + 'xpm t) + "Image for the next feed button.")) + +(defconst newsticker--get-all-image + (if (fboundp 'create-image) + (create-image "/* XPM */ +static char * get_all_xpm[] = { +\"24 24 70 1\", +\" c None\", +\". c #000000\", +\"+ c #F3DA00\", +\"@ c #F5DF00\", +\"# c #F7E300\", +\"$ c #F9E700\", +\"% c #FAEA00\", +\"& c #FBEC00\", +\"* c #FBED00\", +\"= c #FCEE00\", +\"- c #FAEB00\", +\"; c #F9E800\", +\"> c #F8E500\", +\", c #F6E000\", +\"' c #F4DB00\", +\") c #F1D500\", +\"! c #EFD000\", +\"~ c #B7CA00\", +\"{ c #BFD100\", +\"] c #C5D700\", +\"^ c #CBDB00\", +\"/ c #CFDF00\", +\"( c #D2E200\", +\"_ c #D4E400\", +\": c #D3E300\", +\"< c #D0E000\", +\"[ c #CCDD00\", +\"} c #C7D800\", +\"| c #C1D300\", +\"1 c #BACC00\", +\"2 c #B1C500\", +\"3 c #A8BC00\", +\"4 c #20A900\", +\"5 c #22AF00\", +\"6 c #24B500\", +\"7 c #26B900\", +\"8 c #27BC00\", +\"9 c #27BE00\", +\"0 c #28BF00\", +\"a c #27BD00\", +\"b c #26BA00\", +\"c c #25B600\", +\"d c #23B100\", +\"e c #21AB00\", +\"f c #1FA400\", +\"g c #1C9B00\", +\"h c #21AA00\", +\"i c #24B300\", +\"j c #25B800\", +\"k c #25B700\", +\"l c #24B400\", +\"m c #23B000\", +\"n c #1FA500\", +\"o c #1D9E00\", +\"p c #20A800\", +\"q c #21AC00\", +\"r c #23B200\", +\"s c #22AD00\", +\"t c #1D9F00\", +\"u c #20A700\", +\"v c #1EA100\", +\"w c #1C9C00\", +\"x c #1DA000\", +\"y c #1B9800\", +\"z c #1A9600\", +\"A c #1A9700\", +\"B c #1A9500\", +\"C c #199200\", +\"D c #189100\", +\"E c #178C00\", +\" \", +\" \", +\" \", +\" \", +\" ................... \", +\" .+@#$%&*=*&-;>,')!. \", +\" ................... \", +\" \", +\" ................... \", +\" .~{]^/(___:<[}|123. \", +\" ................... \", +\" \", +\" ................... \", +\" .45678909abcdefg. \", +\" .h5icj7jklmeno. \", +\" .pq5drrmshft. \", +\" .fu4h4pnvw. \", +\" .oxvxtwy. \", +\" .zAAzB. \", +\" .CCD. \", +\" .E. \", +\" . \", +\" \", +\" \"}; +" + 'xpm t) + "Image for the next feed button.")) + + +(defconst newsticker--update-image + (if (fboundp 'create-image) + (create-image "/* XPM */ +static char * update_xpm[] = { +\"24 24 37 1\", +\" c None\", +\". c #076D00\", +\"+ c #0A8600\", +\"@ c #0A8800\", +\"# c #098400\", +\"$ c #087200\", +\"% c #087900\", +\"& c #098500\", +\"* c #098100\", +\"= c #087600\", +\"- c #097E00\", +\"; c #097F00\", +\"> c #0A8700\", +\", c #0A8C00\", +\"' c #097C00\", +\") c #098300\", +\"! c #0A8900\", +\"~ c #0A8E00\", +\"{ c #0B9200\", +\"] c #087700\", +\"^ c #076E00\", +\"/ c #076C00\", +\"( c #076B00\", +\"_ c #076A00\", +\": c #076900\", +\"< c #076800\", +\"[ c #066700\", +\"} c #066500\", +\"| c #066400\", +\"1 c #066300\", +\"2 c #066600\", +\"3 c #066200\", +\"4 c #076700\", +\"5 c #065E00\", +\"6 c #066100\", +\"7 c #065F00\", +\"8 c #066000\", +\" \", +\" \", +\" \", +\" . +@@@+# \", +\" $% &@ +* \", +\" =-# ; \", +\" %*>, ' \", +\" ')!~{ = \", +\" ]$ \", +\" ^ ^ \", +\" . . \", +\" / ( \", +\" _ : \", +\" < [ \", +\" } | \", +\" [[ \", +\" 1 $.:23 \", +\" 3 4}35 \", +\" 6 655 \", +\" 76 85 55 \", +\" 5555555 5 \", +\" \", +\" \", +\" \"}; +" + 'xpm t) + "Image for the update button.")) + +(defconst newsticker--browse-image + (if (fboundp 'create-image) + (create-image "/* XPM */ +static char * visit_xpm[] = { +\"24 24 39 1\", +\" c None\", +\". c #000000\", +\"+ c #FFFFFF\", +\"@ c #00E63D\", +\"# c #00E83E\", +\"$ c #00E73D\", +\"% c #00E93E\", +\"& c #00E63C\", +\"* c #00E53C\", +\"= c #00E23B\", +\"- c #00E33B\", +\"; c #00E83D\", +\"> c #00E13A\", +\", c #00DD38\", +\"' c #00DE38\", +\") c #00E23A\", +\"! c #00E43C\", +\"~ c #00DF39\", +\"{ c #00DB37\", +\"] c #00D634\", +\"^ c #00D734\", +\"/ c #00E039\", +\"( c #00DC37\", +\"_ c #00D835\", +\": c #00D332\", +\"< c #00CD2F\", +\"[ c #00DB36\", +\"} c #00D433\", +\"| c #00CF30\", +\"1 c #00DA36\", +\"2 c #00D936\", +\"3 c #00D533\", +\"4 c #00D131\", +\"5 c #00CE2F\", +\"6 c #00CC2F\", +\"7 c #00CA2D\", +\"8 c #00C62B\", +\"9 c #00C52A\", +\"0 c #00BE27\", +\" \", +\" \", +\" . \", +\" .+. \", +\" .+++. \", +\" .++.++. \", +\" .++.@.++. \", +\" .++.##$.++. \", +\" .++.%%%#&.++. \", +\" .++.$%%%#*=.++. \", +\" .++.-@;##$*>,.++. \", +\" .++.')!&@@*=~{].++. \", +\" .++.^{~>---)/(_:<.++. \", +\" .++.^[,~/~'(_}|.++. \", +\" .++.]_1[12^:|.++. \", +\" .++.:}33:45.++. \", +\" .++.<5567.++. \", +\" .++.889.++. \", +\" .++.0.++. \", +\" .++.++. \", +\" .+++. \", +\" .+. \", +\" . \", +\" \"}; +" + 'xpm t) + "Image for the browse button.")) + + +(defvar newsticker-tool-bar-map + (if (featurep 'xemacs) + nil + (let ((tool-bar-map (make-sparse-keymap))) + (define-key tool-bar-map [newsticker-sep-1] + (list 'menu-item "--double-line")) + (define-key tool-bar-map [newsticker-browse-url] + (list 'menu-item "newsticker-browse-url" 'newsticker-browse-url + :visible t + :help "Browse URL for item at point" + :image newsticker--browse-image)) + (define-key tool-bar-map [newsticker-buffer-force-update] + (list 'menu-item "newsticker-buffer-force-update" + 'newsticker-buffer-force-update + :visible t + :help "Update newsticker buffer" + :image newsticker--update-image + :enable '(not newsticker--buffer-uptodate-p))) + (define-key tool-bar-map [newsticker-get-all-news] + (list 'menu-item "newsticker-get-all-news" 'newsticker-get-all-news + :visible t + :help "Get news for all feeds" + :image newsticker--get-all-image)) + (define-key tool-bar-map [newsticker-mark-item-at-point-as-read] + (list 'menu-item "newsticker-mark-item-at-point-as-read" + 'newsticker-mark-item-at-point-as-read + :visible t + :image newsticker--mark-read-image + :help "Mark current item as read" + :enable '(newsticker-item-not-old-p))) + (define-key tool-bar-map [newsticker-mark-item-at-point-as-immortal] + (list 'menu-item "newsticker-mark-item-at-point-as-immortal" + 'newsticker-mark-item-at-point-as-immortal + :visible t + :image newsticker--mark-immortal-image + :help "Mark current item as immortal" + :enable '(newsticker-item-not-immortal-p))) + (define-key tool-bar-map [newsticker-toggle-auto-narrow-to-feed] + (list 'menu-item "newsticker-toggle-auto-narrow-to-feed" + 'newsticker-toggle-auto-narrow-to-feed + :visible t + :help "Toggle visibility of other feeds" + :image newsticker--narrow-image)) + (define-key tool-bar-map [newsticker-next-feed] + (list 'menu-item "newsticker-next-feed" 'newsticker-next-feed + :visible t + :help "Go to next feed" + :image newsticker--next-feed-image + :enable '(newsticker-next-feed-available-p))) + (define-key tool-bar-map [newsticker-next-item] + (list 'menu-item "newsticker-next-item" 'newsticker-next-item + :visible t + :help "Go to next item" + :image newsticker--next-item-image + :enable '(newsticker-next-item-available-p))) + (define-key tool-bar-map [newsticker-previous-item] + (list 'menu-item "newsticker-previous-item" 'newsticker-previous-item + :visible t + :help "Go to previous item" + :image newsticker--previous-item-image + :enable '(newsticker-previous-item-available-p))) + (define-key tool-bar-map [newsticker-previous-feed] + (list 'menu-item "newsticker-previous-feed" 'newsticker-previous-feed + :visible t + :help "Go to previous feed" + :image newsticker--previous-feed-image + :enable '(newsticker-previous-feed-available-p))) + ;; standard icons / actions + (tool-bar-add-item "close" + 'newsticker-close-buffer + 'newsticker-close-buffer + :help "Close newsticker buffer") + (tool-bar-add-item "preferences" + 'newsticker-customize + 'newsticker-customize + :help "Customize newsticker") + tool-bar-map))) + +;; ====================================================================== +;;; Newsticker mode +;; ====================================================================== + +(define-derived-mode newsticker-mode fundamental-mode + "NewsTicker" + "Viewing RSS news feeds in Emacs." + (set (make-local-variable 'tool-bar-map) newsticker-tool-bar-map) + (set (make-local-variable 'imenu-sort-function) nil) + (set (make-local-variable 'scroll-conservatively) 999) + (setq imenu-create-index-function 'newsticker--imenu-create-index) + (setq imenu-default-goto-function 'newsticker--imenu-goto) + (setq buffer-read-only t) + (auto-fill-mode -1) ;; turn auto-fill off! + (font-lock-mode -1) ;; turn off font-lock!! + (set (make-local-variable 'font-lock-defaults) nil) + (set (make-local-variable 'line-move-ignore-invisible) t) + (setq mode-line-format + (list "-" + 'mode-line-mule-info + 'mode-line-modified + 'mode-line-frame-identification + " Newsticker (" + '(newsticker--buffer-uptodate-p + "up to date" + "NEED UPDATE") + ") -- " + '(:eval (newsticker--buffer-get-feed-title-at-point)) + ": " + '(:eval (newsticker--buffer-get-item-title-at-point)) + " %-")) + (unless newsticker-show-all-rss-elements + (add-to-invisibility-spec 'extra)) + (newsticker--buffer-set-uptodate nil)) + +;; refine its mode-map +(define-key newsticker-mode-map "sO" 'newsticker-show-old-items) +(define-key newsticker-mode-map "hO" 'newsticker-hide-old-items) +(define-key newsticker-mode-map "sa" 'newsticker-show-all-desc) +(define-key newsticker-mode-map "ha" 'newsticker-hide-all-desc) +(define-key newsticker-mode-map "sf" 'newsticker-show-feed-desc) +(define-key newsticker-mode-map "hf" 'newsticker-hide-feed-desc) +(define-key newsticker-mode-map "so" 'newsticker-show-old-item-desc) +(define-key newsticker-mode-map "ho" 'newsticker-hide-old-item-desc) +(define-key newsticker-mode-map "sn" 'newsticker-show-new-item-desc) +(define-key newsticker-mode-map "hn" 'newsticker-hide-new-item-desc) +(define-key newsticker-mode-map "se" 'newsticker-show-entry) +(define-key newsticker-mode-map "he" 'newsticker-hide-entry) +(define-key newsticker-mode-map "sx" 'newsticker-show-extra) +(define-key newsticker-mode-map "hx" 'newsticker-hide-extra) + +(define-key newsticker-mode-map " " 'scroll-up) +(define-key newsticker-mode-map "q" 'newsticker-close-buffer) +(define-key newsticker-mode-map "p" 'newsticker-previous-item) +(define-key newsticker-mode-map "P" 'newsticker-previous-new-item) +(define-key newsticker-mode-map "F" 'newsticker-previous-feed) +(define-key newsticker-mode-map "\t" 'newsticker-next-item) +(define-key newsticker-mode-map "n" 'newsticker-next-item) +(define-key newsticker-mode-map "N" 'newsticker-next-new-item) +(define-key newsticker-mode-map "f" 'newsticker-next-feed) +(define-key newsticker-mode-map "M" 'newsticker-mark-all-items-as-read) +(define-key newsticker-mode-map "m" + 'newsticker-mark-all-items-at-point-as-read) +(define-key newsticker-mode-map "o" 'newsticker-mark-item-at-point-as-read) +(define-key newsticker-mode-map "G" 'newsticker-get-all-news) +(define-key newsticker-mode-map "g" 'newsticker-get-news-at-point) +(define-key newsticker-mode-map "u" 'newsticker-buffer-update) +(define-key newsticker-mode-map "U" 'newsticker-buffer-force-update) +(define-key newsticker-mode-map "a" 'newsticker-add-url) + +(define-key newsticker-mode-map "i" + 'newsticker-mark-item-at-point-as-immortal) + +(define-key newsticker-mode-map "xf" 'newsticker-toggle-auto-narrow-to-feed) +(define-key newsticker-mode-map "xi" 'newsticker-toggle-auto-narrow-to-item) + +;; maps for the clickable portions +(defvar newsticker--url-keymap (make-sparse-keymap) + "Key map for click-able headings in the newsticker buffer.") +(define-key newsticker--url-keymap [mouse-2] + 'newsticker-mouse-browse-url) +(define-key newsticker--url-keymap "\n" + 'newsticker-browse-url) +(define-key newsticker--url-keymap "\C-m" + 'newsticker-browse-url) +(define-key newsticker--url-keymap [(control return)] + 'newsticker-handle-url) + +;; newsticker menu +(defvar newsticker-menu (make-sparse-keymap "Newsticker")) + +(define-key newsticker-menu [newsticker-browse-url] + '("Browse URL for item at point" . newsticker-browse-url)) +(define-key newsticker-menu [newsticker-separator-1] + '("--")) +(define-key newsticker-menu [newsticker-buffer-update] + '("Update buffer" . newsticker-buffer-update)) +(define-key newsticker-menu [newsticker-separator-2] + '("--")) +(define-key newsticker-menu [newsticker-get-all-news] + '("Get news from all feeds" . newsticker-get-all-news)) +(define-key newsticker-menu [newsticker-get-news-at-point] + '("Get news from feed at point" . newsticker-get-news-at-point)) +(define-key newsticker-menu [newsticker-separator-3] + '("--")) +(define-key newsticker-menu [newsticker-mark-all-items-as-read] + '("Mark all items as read" . newsticker-mark-all-items-as-read)) +(define-key newsticker-menu [newsticker-mark-all-items-at-point-as-read] + '("Mark all items in feed at point as read" . + newsticker-mark-all-items-at-point-as-read)) +(define-key newsticker-menu [newsticker-mark-item-at-point-as-read] + '("Mark item at point as read" . + newsticker-mark-item-at-point-as-read)) +(define-key newsticker-menu [newsticker-mark-item-at-point-as-immortal] + '("Toggle immortality for item at point" . + newsticker-mark-item-at-point-as-immortal)) +(define-key newsticker-menu [newsticker-separator-4] + '("--")) +(define-key newsticker-menu [newsticker-hide-old-items] + '("Hide old items" . newsticker-hide-old-items)) +(define-key newsticker-menu [newsticker-show-old-items] + '("Show old items" . newsticker-show-old-items)) +(define-key newsticker-menu [newsticker-next-item] + '("Go to next item" . newsticker-next-item)) +(define-key newsticker-menu [newsticker-previous-item] + '("Go to previous item" . newsticker-previous-item)) + +;; bind menu to mouse +(define-key newsticker-mode-map [down-mouse-3] newsticker-menu) +;; Put menu in menu-bar +(define-key newsticker-mode-map [menu-bar Newsticker] + (cons "Newsticker" newsticker-menu)) + + +;; ====================================================================== +;;; shortcuts +;; ====================================================================== +(defsubst newsticker--title (item) + "Return title of ITEM." + (nth 0 item)) +(defsubst newsticker--desc (item) + "Return description of ITEM." + (nth 1 item)) +(defsubst newsticker--link (item) + "Return link of ITEM." + (nth 2 item)) +(defsubst newsticker--time (item) + "Return time of ITEM." + (nth 3 item)) +(defsubst newsticker--age (item) + "Return age of ITEM." + (nth 4 item)) +(defsubst newsticker--pos (item) + "Return position/index of ITEM." + (nth 5 item)) +(defsubst newsticker--preformatted-contents (item) + "Return pre-formatted text of ITEM." + (nth 6 item)) +(defsubst newsticker--preformatted-title (item) + "Return pre-formatted title of ITEM." + (nth 7 item)) +(defsubst newsticker--extra (item) + "Return extra attributes of ITEM." + (nth 8 item)) +(defsubst newsticker--guid (item) + "Return guid of ITEM." + (let ((guid (assoc 'guid (newsticker--extra item)))) + (if (stringp guid) + guid + (car (xml-node-children guid))))) +(defsubst newsticker--enclosure (item) + "Return enclosure element of ITEM in the form \(...FIXME...\)or nil." + (let ((enclosure (assoc 'enclosure (newsticker--extra item)))) + (if enclosure + (xml-node-attributes enclosure)))) + +;; ====================================================================== +;;; User fun +;; ====================================================================== + +(defun newsticker-start (&optional do-not-complain-if-running) + "Start the newsticker. +Start the timers for display and retrieval. If the newsticker, i.e. the +timers, are running already a warning message is printed unless +DO-NOT-COMPLAIN-IF-RUNNING is not nil. +Run `newsticker-start-hook' if newsticker was not running already." + (interactive) + (let ((running (newsticker-running-p))) + ;; read old cache if it exists and newsticker is not running + (unless running + (let* ((coding-system-for-read 'utf-8) + (buf (find-file-noselect newsticker-cache-filename))) + (when buf + (set-buffer buf) + (goto-char (point-min)) + (condition-case nil + (setq newsticker--cache (read buf)) + (error + (message "Error while reading newsticker cache file!") + (setq newsticker--cache nil)))))) + ;; start retrieval timers -- for sake of simplicity we will start + ;; one timer for each feed + (mapc (lambda (item) + (let* ((feed-name (car item)) + (start-time (nth 2 item)) + (interval (or (nth 3 item) + newsticker-retrieval-interval)) + (timer (assoc (car item) + newsticker--retrieval-timer-list))) + (if timer + (or do-not-complain-if-running + (message "Timer for %s is running already!" + feed-name)) + (newsticker--debug-msg "Starting timer for %s: %s, %d" + feed-name start-time interval) + ;; do not repeat retrieval if interval not positive + (if (<= interval 0) + (setq interval nil)) + ;; Suddenly XEmacs doesn't like start-time 0 + (if (or (not start-time) + (and (numberp start-time) (= start-time 0))) + (setq start-time 1)) + (message "start-time %s" start-time) + (setq timer (run-at-time start-time interval + 'newsticker-get-news feed-name)) + (if interval + (add-to-list 'newsticker--retrieval-timer-list + (cons feed-name timer)))))) + (append newsticker-url-list-defaults newsticker-url-list)) + (unless running + (run-hooks 'newsticker-start-hook) + (message "Newsticker started!")))) + +(defun newsticker-start-ticker () + "Start newsticker's ticker (but not the news retrieval. +Start display timer for the actual ticker if wanted and not +running already." + (interactive) + (if (and (> newsticker-display-interval 0) + (not newsticker--display-timer)) + (setq newsticker--display-timer + (run-at-time newsticker-display-interval + newsticker-display-interval + 'newsticker--display-tick)))) + +(defun newsticker-stop () + "Stop the newsticker and the newsticker-ticker. +Cancel the timers for display and retrieval. Run `newsticker-stop-hook' +if newsticker has been running." + (interactive) + (newsticker--cache-update t) + (newsticker-stop-ticker) + (when (newsticker-running-p) + (mapc (lambda (name-and-timer) + (cancel-timer (cdr name-and-timer))) + newsticker--retrieval-timer-list) + (setq newsticker--retrieval-timer-list nil) + (run-hooks 'newsticker-stop-hook) + (message "Newsticker stopped!"))) + +(defun newsticker-stop-ticker () + "Stop newsticker's ticker (but not the news retrieval)." + (interactive) + (when newsticker--display-timer + (cancel-timer newsticker--display-timer) + (setq newsticker--display-timer nil))) + +;; the functions we need for retrieval and display +(defun newsticker-show-news () + "Switch to newsticker buffer. You may want to bind this to a key." + (interactive) + (newsticker-start t) ;; will start only if not running + (newsticker-buffer-update) + (switch-to-buffer "*newsticker*")) + +(defun newsticker-buffer-force-update () + "Update the newsticker buffer, even if not necessary." + (interactive) + (newsticker-buffer-update t)) + +(defun newsticker-buffer-update (&optional force) + "Update the *newsticker* buffer. +Unless FORCE is t this is donly only if necessary, i.e. when the +*newsticker* buffer is not up-to-date." + (interactive) + ;; bring cache data into proper order.... + (newsticker--cache-sort) + ;; fill buffer + (save-excursion + (let ((buf (get-buffer "*newsticker*"))) + (if buf + (switch-to-buffer buf) + (switch-to-buffer (get-buffer-create "*newsticker*")) + (newsticker--buffer-set-uptodate nil))) + (when (or force + (not newsticker--buffer-uptodate-p)) + (message "Preparing newsticker buffer...") + (setq buffer-undo-list t) + (let ((inhibit-read-only t)) + (set-buffer-modified-p nil) + (erase-buffer) + (newsticker-mode) + ;; Emacs 21.3.50 does not care if we turn off auto-fill in the + ;; definition of newsticker-mode, so we do it here (again) + (auto-fill-mode -1) + + (set-buffer-file-coding-system 'utf-8) + + (if newsticker-use-full-width + (set (make-local-variable 'fill-column) (1- (window-width)))) + (newsticker--buffer-insert-all-items) + + ;; FIXME: needed for methods buffer in ecb + ;; (set-visited-file-name "*newsticker*") + + (set-buffer-modified-p nil) + (newsticker-hide-all-desc) + (if newsticker-hide-old-items-in-newsticker-buffer + (newsticker-hide-old-items)) + (if newsticker-show-descriptions-of-new-items + (newsticker-show-new-item-desc)) + ) + (message "")) + (newsticker--buffer-set-uptodate t) + (run-hooks 'newsticker-buffer-change-hook))) + +(defun newsticker-get-all-news () + "Launch retrieval of news from all configured newsticker sites. +This does NOT start the retrieval timers." + (interactive) + ;; launch retrieval of news + (mapc (lambda (item) + (newsticker-get-news (car item))) + (append newsticker-url-list-defaults newsticker-url-list))) + +(defun newsticker-get-news-at-point () + "Launch retrieval of news for the feed point is in. +This does NOT start the retrieval timers." + (interactive) + ;; launch retrieval of news + (let ((feed (get-text-property (point) 'feed))) + (when feed + (newsticker--debug-msg "Getting news for %s" (symbol-name feed)) + (newsticker-get-news (symbol-name feed))))) + +(defun newsticker-add-url (url name) + "Add given URL under given NAME to `newsticker-url-list'. +If URL is nil it is searched at point." + (interactive + (list + (read-string "URL: " + (save-excursion + (end-of-line) + (and + (re-search-backward + "http://" + (if (> (point) (+ (point-min) 100)) + (- (point) 100) + (point-min)) + t) + (re-search-forward + "http://[-a-zA-Z0-9&/_.]*" + (if (< (point) (- (point-max) 200)) + (+ (point) 200) + (point-max)) + t) + (buffer-substring-no-properties (match-beginning 0) + (match-end 0))))) + (read-string "Name: "))) + (add-to-list 'newsticker-url-list (list name url nil nil nil) t) + (customize-variable 'newsticker-url-list)) + +(defun newsticker-w3m-show-inline-images () + "Show inline images in visible text ranges. +In-line images in invisible text ranges are hidden. This function +calls `w3m-toggle-inline-image'. It works only if +`newsticker-html-renderer' is set to `w3m-region'" + (interactive) + (if (eq newsticker-html-renderer 'w3m-region) + (let ((inhibit-read-only t)) + (save-excursion + (save-restriction + (widen) + (goto-char (point-min)) + (let ((pos (point))) + (while pos + (setq pos (next-single-property-change pos 'w3m-image)) + (when pos + (goto-char pos) + (when (get-text-property pos 'w3m-image) + (let ((invis (newsticker--lists-intersect-p + (get-text-property (1- (point)) 'invisible) + buffer-invisibility-spec))) + (if invis + (w3m-remove-image + pos (next-single-property-change pos 'w3m-image)) + (w3m-toggle-inline-image t)))))))))))) + +;; ====================================================================== +;;; keymap stuff +;; ====================================================================== +(defun newsticker-close-buffer () + "Close the newsticker buffer." + (interactive) + (newsticker--cache-update t) + (bury-buffer)) + +(defun newsticker-next-new-item (&optional do-not-wrap-at-eob) + "Go to next new news item. +If no new item is found behind point, search is continued at +beginning of buffer unless optional argument DO-NOT-WRAP-AT-EOB +is non-nil." + (interactive) + (widen) + (let ((go-ahead t)) + (while go-ahead + (unless (newsticker--buffer-goto '(item) 'new) + ;; found nothing -- wrap + (unless do-not-wrap-at-eob + (goto-char (point-min)) + (newsticker-next-new-item t)) + (setq go-ahead nil)) + (unless (newsticker--lists-intersect-p + (get-text-property (point) 'invisible) + buffer-invisibility-spec) + ;; this item is invisible -- continue search + (setq go-ahead nil)))) + (run-hooks 'newsticker-select-item-hook) + (point)) + +(defun newsticker-previous-new-item (&optional do-not-wrap-at-bob) + "Go to previous new news item. +If no new item is found before point, search is continued at +beginning of buffer unless optional argument DO-NOT-WRAP-AT-BOB +is non-nil." + (interactive) + (widen) + (let ((go-ahead t)) + (while go-ahead + (unless (newsticker--buffer-goto '(item) 'new t) + (unless do-not-wrap-at-bob + (goto-char (point-max)) + (newsticker--buffer-goto '(item) 'new t))) + (unless (newsticker--lists-intersect-p + (get-text-property (point) 'invisible) + buffer-invisibility-spec) + (setq go-ahead nil)))) + (run-hooks 'newsticker-select-item-hook) + (point)) + +(defun newsticker-next-item (&optional do-not-wrap-at-eob) + "Go to next news item. +Return new buffer position. +If no item is found below point, search is continued at beginning +of buffer unless optional argument DO-NOT-WRAP-AT-EOB is +non-nil." + (interactive) + (widen) + (let ((go-ahead t) + (search-list '(item))) + (if newsticker--auto-narrow-to-item + (setq search-list '(item feed))) + (while go-ahead + (unless (newsticker--buffer-goto search-list) + ;; found nothing -- wrap + (unless do-not-wrap-at-eob + (goto-char (point-min))) + (setq go-ahead nil)) + (unless (newsticker--lists-intersect-p + (get-text-property (point) 'invisible) + buffer-invisibility-spec) + (setq go-ahead nil)))) + (run-hooks 'newsticker-select-item-hook) + (point)) + +(defun newsticker-previous-item (&optional do-not-wrap-at-bob) + "Go to previous news item. +Return new buffer position. +If no item is found before point, search is continued at +beginning of buffer unless optional argument DO-NOT-WRAP-AT-BOB +is non-nil." + (interactive) + (widen) + (let ((go-ahead t) + (search-list '(item))) + (if newsticker--auto-narrow-to-item + (setq search-list '(item feed))) + (when (bobp) + (unless do-not-wrap-at-bob + (goto-char (point-max)))) + (while go-ahead + (if (newsticker--buffer-goto search-list nil t) + (unless (newsticker--lists-intersect-p + (get-text-property (point) 'invisible) + buffer-invisibility-spec) + (setq go-ahead nil)) + (goto-char (point-min)) + (setq go-ahead nil)))) + (run-hooks 'newsticker-select-item-hook) + (point)) + +(defun newsticker-next-feed () + "Go to next news feed. +Return new buffer position." + (interactive) + (widen) + (newsticker--buffer-goto '(feed)) + (run-hooks 'newsticker-select-feed-hook) + (point)) + +(defun newsticker-previous-feed () + "Go to previous news feed. +Return new buffer position." + (interactive) + (widen) + (newsticker--buffer-goto '(feed) nil t) + (run-hooks 'newsticker-select-feed-hook) + (point)) + +(defun newsticker-mark-all-items-at-point-as-read () + "Mark all items as read and clear ticker contents." + (interactive) + (when (or newsticker--buffer-uptodate-p + (y-or-n-p + "Buffer is not up to date -- really mark items as read? ")) + (let ((feed (get-text-property (point) 'feed)) + (pos (point))) + (when feed + (message "Marking all items as read for %s" (symbol-name feed)) + (newsticker--cache-replace-age newsticker--cache feed 'new 'old) + (newsticker--cache-replace-age newsticker--cache feed 'obsolete 'old) + (newsticker--cache-update) + (newsticker--buffer-set-uptodate nil) + (newsticker--ticker-text-setup) + (newsticker-buffer-update) + ;; go back to where we came frome + (goto-char pos) + (end-of-line) + (newsticker--buffer-goto '(feed) nil t))))) + +(defun newsticker-mark-item-at-point-as-read (&optional respect-immortality) + "Mark item at point as read. +If optional argument RESPECT-IMMORTALITY is not nil immortal items do +not get changed." + (interactive) + (when (or newsticker--buffer-uptodate-p + (y-or-n-p + "Buffer is not up to date -- really mark this item as read? ")) + (let ((feed (get-text-property (point) 'feed)) + (item nil)) + (when feed + (save-excursion + (newsticker--buffer-beginning-of-item) + (let ((inhibit-read-only t) + (age (get-text-property (point) 'nt-age)) + (title (get-text-property (point) 'nt-title)) + (guid (get-text-property (point) 'nt-guid)) + (nt-desc (get-text-property (point) 'nt-desc)) + (pos (save-excursion (newsticker--buffer-end-of-item)))) + (when (or (eq age 'new) + (eq age 'obsolete) + (and (eq age 'immortal) + (not respect-immortality))) + ;; find item + (setq item (newsticker--cache-contains newsticker--cache + feed title nt-desc + nil nil guid)) + ;; mark as old + (when item + (setcar (nthcdr 4 item) 'old) + (newsticker--do-forget-preformatted item)) + ;; clean up ticker + (if (or (and (eq age 'new) + newsticker-hide-immortal-items-in-echo-area) + (and (memq age '(old immortal)) + (not + (eq newsticker-hide-old-items-in-newsticker-buffer + newsticker-hide-immortal-items-in-echo-area)))) + (newsticker--ticker-text-remove feed title)) + ;; set faces etc. + (save-excursion + (save-restriction + (widen) + (put-text-property (point) pos 'nt-age 'old) + (newsticker--buffer-set-faces (point) pos))) + (set-buffer-modified-p nil)))) + ;; move forward + (newsticker-next-item t))))) + +(defun newsticker-mark-item-at-point-as-immortal () + "Mark item at point as read." + (interactive) + (when (or newsticker--buffer-uptodate-p + (y-or-n-p + "Buffer is not up to date -- really mark this item as read? ")) + (let ((feed (get-text-property (point) 'feed)) + (item nil)) + (when feed + (save-excursion + (newsticker--buffer-beginning-of-item) + (let ((inhibit-read-only t) + (oldage (get-text-property (point) 'nt-age)) + (title (get-text-property (point) 'nt-title)) + (guid (get-text-property (point) 'nt-guid)) + (pos (save-excursion (newsticker--buffer-end-of-item)))) + (let ((newage 'immortal)) + (if (eq oldage 'immortal) + (setq newage 'old)) + (setq item (newsticker--cache-contains newsticker--cache + feed title nil nil nil + guid)) + ;; change age + (when item + (setcar (nthcdr 4 item) newage) + (newsticker--do-forget-preformatted item)) + (if (or (and (eq newage 'immortal) + newsticker-hide-immortal-items-in-echo-area) + (and (eq newage 'obsolete) + newsticker-hide-obsolete-items-in-echo-area) + (and (eq oldage 'immortal) + (not + (eq newsticker-hide-old-items-in-newsticker-buffer + newsticker-hide-immortal-items-in-echo-area)))) + (newsticker--ticker-text-remove feed title) + (newsticker--ticker-text-setup)) + (save-excursion + (save-restriction + (widen) + (put-text-property (point) pos 'nt-age newage) + (if (eq newage 'immortal) + (put-text-property (point) pos 'nt-age 'immortal) + (put-text-property (point) pos 'nt-age 'old)) + (newsticker--buffer-set-faces (point) pos)))))) + (if item + (newsticker-next-item t)))))) + +(defun newsticker-mark-all-items-as-read () + "Mark all items as read and clear ticker contents." + (interactive) + (when (or newsticker--buffer-uptodate-p + (y-or-n-p + "Buffer is not up to date -- really mark items as read? ")) + (newsticker--cache-replace-age newsticker--cache 'any 'new 'old) + (newsticker--buffer-set-uptodate nil) + (newsticker--ticker-text-setup) + (newsticker--cache-update) + (newsticker-buffer-update))) + +(defun newsticker-hide-extra () + "Hide the extra elements of items." + (interactive) + (newsticker--buffer-hideshow 'extra nil) + (newsticker--buffer-redraw)) + +(defun newsticker-show-extra () + "Show the extra elements of items." + (interactive) + (newsticker--buffer-hideshow 'extra t) + (newsticker--buffer-redraw)) + +(defun newsticker-hide-old-item-desc () + "Hide the description of old items." + (interactive) + (newsticker--buffer-hideshow 'desc-old nil) + (newsticker--buffer-redraw)) + +(defun newsticker-show-old-item-desc () + "Show the description of old items." + (interactive) + (newsticker--buffer-hideshow 'item-old t) + (newsticker--buffer-hideshow 'desc-old t) + (newsticker--buffer-redraw)) + +(defun newsticker-hide-new-item-desc () + "Hide the description of new items." + (interactive) + (newsticker--buffer-hideshow 'desc-new nil) + (newsticker--buffer-hideshow 'desc-immortal nil) + (newsticker--buffer-hideshow 'desc-obsolete nil) + (newsticker--buffer-redraw)) + +(defun newsticker-show-new-item-desc () + "Show the description of new items." + (interactive) + (newsticker--buffer-hideshow 'desc-new t) + (newsticker--buffer-hideshow 'desc-immortal t) + (newsticker--buffer-hideshow 'desc-obsolete t) + (newsticker--buffer-redraw)) + +(defun newsticker-hide-feed-desc () + "Hide the description of feeds." + (interactive) + (newsticker--buffer-hideshow 'desc-feed nil) + (newsticker--buffer-redraw)) + +(defun newsticker-show-feed-desc () + "Show the description of old items." + (interactive) + (newsticker--buffer-hideshow 'desc-feed t) + (newsticker--buffer-redraw)) + +(defun newsticker-hide-all-desc () + "Hide the descriptions of feeds and all items." + (interactive) + (newsticker--buffer-hideshow 'desc-feed nil) + (newsticker--buffer-hideshow 'desc-immortal nil) + (newsticker--buffer-hideshow 'desc-obsolete nil) + (newsticker--buffer-hideshow 'desc-new nil) + (newsticker--buffer-hideshow 'desc-old nil) + (newsticker--buffer-redraw)) + +(defun newsticker-show-all-desc () + "Show the descriptions of feeds and all items." + (interactive) + (newsticker--buffer-hideshow 'desc-feed t) + (newsticker--buffer-hideshow 'desc-immortal t) + (newsticker--buffer-hideshow 'desc-obsolete t) + (newsticker--buffer-hideshow 'desc-new t) + (newsticker--buffer-hideshow 'desc-old t) + (newsticker--buffer-redraw)) + +(defun newsticker-hide-old-items () + "Hide old items." + (interactive) + (newsticker--buffer-hideshow 'desc-old nil) + (newsticker--buffer-hideshow 'item-old nil) + (newsticker--buffer-redraw)) + +(defun newsticker-show-old-items () + "Show old items." + (interactive) + (newsticker--buffer-hideshow 'desc-old t) + (newsticker--buffer-hideshow 'item-old t) + (newsticker--buffer-redraw)) + +(defun newsticker-hide-entry () + "Hide description of entry at point." + (interactive) + (save-excursion + (let* (pos1 pos2 + (inhibit-read-only t) + inv-prop org-inv-prop + is-invisible) + (newsticker--buffer-beginning-of-item) + (newsticker--buffer-goto '(desc)) + (setq pos1 (max (point-min) (1- (point)))) + (newsticker--buffer-goto '(extra feed item)) + (setq pos2 (max (point-min) (1- (point)))) + (setq inv-prop (get-text-property pos1 'invisible)) + (setq org-inv-prop (get-text-property pos1 'org-invisible)) + (cond ((eq inv-prop t) + ;; do nothing + ) + ((eq org-inv-prop nil) + (add-text-properties pos1 pos2 (list 'invisible t + 'org-invisible inv-prop))) + (t + ;; toggle + (add-text-properties pos1 pos2 (list 'invisible org-inv-prop)) + (remove-text-properties pos1 pos2 '(org-invisible)))))) + (newsticker--buffer-redraw)) + +(defun newsticker-show-entry () + "Show description of entry at point." + (interactive) + (save-excursion + (let* (pos1 pos2 + (inhibit-read-only t) + inv-prop org-inv-prop + is-invisible) + (newsticker--buffer-beginning-of-item) + (newsticker--buffer-goto '(desc)) + (setq pos1 (max (point-min) (1- (point)))) + (newsticker--buffer-goto '(extra feed item)) + (setq pos2 (max (point-min) (1- (point)))) + (setq inv-prop (get-text-property pos1 'invisible)) + (setq org-inv-prop (get-text-property pos1 'org-invisible)) + (cond ((eq org-inv-prop nil) + (add-text-properties pos1 pos2 (list 'invisible nil + 'org-invisible inv-prop))) + (t + ;; toggle + (add-text-properties pos1 pos2 (list 'invisible org-inv-prop)) + (remove-text-properties pos1 pos2 '(org-invisible)))))) + (newsticker--buffer-redraw)) + +(defun newsticker-toggle-auto-narrow-to-feed () + "Toggle narrowing to current news feed. +If auto-narrowing is active, only news item of the current feed +are visible." + (interactive) + (newsticker-set-auto-narrow-to-feed (not newsticker--auto-narrow-to-feed))) + +(defun newsticker-set-auto-narrow-to-feed (value) + "Turn narrowing to current news feed on or off. +If VALUE is nil, auto-narrowing is turned off, otherwise it is turned on." + (interactive) + (setq newsticker--auto-narrow-to-item nil) + (setq newsticker--auto-narrow-to-feed value) + (widen) + (run-hooks 'newsticker-narrow-hook)) + +(defun newsticker-toggle-auto-narrow-to-item () + "Toggle narrowing to current news item. +If auto-narrowing is active, only one item of the current feed +is visible." + (interactive) + (newsticker-set-auto-narrow-to-item (not newsticker--auto-narrow-to-item))) + +(defun newsticker-set-auto-narrow-to-item (value) + "Turn narrowing to current news item on or off. +If VALUE is nil, auto-narrowing is turned off, otherwise it is turned on." + (interactive) + (setq newsticker--auto-narrow-to-feed nil) + (setq newsticker--auto-narrow-to-item value) + (widen) + (run-hooks 'newsticker-narrow-hook)) + +(defun newsticker-customize () + "Open the newsticker customization group." + (interactive) + (customize-group "newsticker")) + +(defun newsticker-next-feed-available-p () + "Return t if position is before last feed, nil otherwise." + (save-excursion + (let ((p (point))) + (newsticker--buffer-goto '(feed)) + (not (= p (point)))))) + +(defun newsticker-previous-feed-available-p () + "Return t if position is behind first feed, nil otherwise." + (save-excursion + (let ((p (point))) + (newsticker--buffer-goto '(feed) nil t) + (not (= p (point)))))) + +(defun newsticker-next-item-available-p () + "Return t if position is before last feed, nil otherwise." + (save-excursion + (catch 'result + (while (< (point) (point-max)) + (unless (newsticker--buffer-goto '(item)) + (throw 'result nil)) + (unless (newsticker--lists-intersect-p + (get-text-property (point) 'invisible) + buffer-invisibility-spec) + (throw 'result t)))))) + +(defun newsticker-previous-item-available-p () + "Return t if position is behind first item, nil otherwise." + (save-excursion + (catch 'result + (while (> (point) (point-min)) + (unless (newsticker--buffer-goto '(item) nil t) + (throw 'result nil)) + (unless (newsticker--lists-intersect-p + (get-text-property (point) 'invisible) + buffer-invisibility-spec) + (throw 'result t)))))) + +(defun newsticker-item-not-old-p () + "Return t if there is an item at point which is not old, nil otherwise." + (when (get-text-property (point) 'feed) + (save-excursion + (newsticker--buffer-beginning-of-item) + (let ((age (get-text-property (point) 'nt-age))) + (and (memq age '(new immortal obsolete)) t))))) + +(defun newsticker-item-not-immortal-p () + "Return t if there is an item at point which is not immortal, nil otherwise." + (when (get-text-property (point) 'feed) + (save-excursion + (newsticker--buffer-beginning-of-item) + (let ((age (get-text-property (point) 'nt-age))) + (and (memq age '(new old obsolete)) t))))) + +;; ====================================================================== +;;; local stuff +;; ====================================================================== +(defun newsticker-running-p () + "Check whether newsticker is running. +Return t if newsticker is running, nil otherwise. Newsticker is +considered to be running if the newsticker timer list is not empty." + (> (length newsticker--retrieval-timer-list) 0)) + +(defun newsticker-ticker-running-p () + "Check whether newsticker's actual ticker is running. +Return t if ticker is running, nil otherwise. Newsticker is +considered to be running if the newsticker timer list is not +empty." + (timerp newsticker--display-timer)) + +;; ====================================================================== +;;; local stuff +;; ====================================================================== +(defun newsticker-get-news (feed-name) + "Get news from the site FEED-NAME and load feed logo. +FEED-NAME must be a string which occurs as the label (i.e. the first element) +in an element of `newsticker-url-list' or `newsticker-url-list-defaults'." + (newsticker--debug-msg "%s: Getting news for %s" + (format-time-string "%A, %H:%M" (current-time)) + feed-name) + (let* ((buffername (concat " *newsticker-wget-" feed-name "*")) + (item (or (assoc feed-name newsticker-url-list) + (assoc feed-name newsticker-url-list-defaults) + (error + "Cannot get news for %s: Check newsticker-url-list" + feed-name))) + (url (cadr item)) + (wget-arguments (or (car (cdr (cdr (cdr (cdr item))))) + newsticker-wget-arguments))) + (save-excursion + (set-buffer (get-buffer-create buffername)) + (erase-buffer) + ;; throw an error if there is an old wget-process around + (if (get-process feed-name) + (error "Another wget-process is running for %s" feed-name)) + ;; start wget + (let* ((args (append wget-arguments (list url))) + (proc (apply 'start-process feed-name buffername + newsticker-wget-name args))) + (set-process-coding-system proc 'no-conversion 'no-conversion) + (set-process-sentinel proc 'newsticker--sentinel))))) + + +(defun newsticker-mouse-browse-url (event) + "Call `browse-url' for the link of the item at which the EVENT occurred." + (interactive "e") + (save-excursion + (switch-to-buffer (window-buffer (posn-window (event-end event)))) + (let ((url (get-text-property (posn-point (event-end event)) + 'nt-link))) + (when url + (browse-url url) + (save-excursion + (goto-char (posn-point (event-end event))) + (if newsticker-automatically-mark-visited-items-as-old + (newsticker-mark-item-at-point-as-read t))))))) + +(defun newsticker-browse-url () + "Call `browse-url' for the link of the item at point." + (interactive) + (let ((url (get-text-property (point) 'nt-link))) + (when url + (browse-url url) + (if newsticker-automatically-mark-visited-items-as-old + (newsticker-mark-item-at-point-as-read t))))) + +(defvar newsticker-open-url-history + '("wget" "xmms" "realplay") + "...") + +(defun newsticker-handle-url () + "Ask for a program to open the link of the item at point." + (interactive) + (let ((url (get-text-property (point) 'nt-link))) + (when url + (let ((prog (read-string "Open url with: " nil + 'newsticker-open-url-history))) + (when prog + (message "%s %s" prog url) + (start-process prog prog prog url) + (if newsticker-automatically-mark-visited-items-as-old + (newsticker-mark-item-at-point-as-read t))))))) + +(defun newsticker--sentinel (process event) + "Sentinel for extracting news titles from an RDF buffer. +Argument PROCESS is the process which has just changed its state. +Argument EVENT tells what has happened to the process." + (let* ((p-status (process-status process)) + (exit-status (process-exit-status process)) + (time (current-time)) + (name (process-name process)) + (name-symbol (intern name)) + (something-was-added nil)) + ;; catch known errors (zombie processes, rubbish-xml etc. + ;; if an error occurs the news feed is not updated! + (catch 'oops + (unless (and (eq p-status 'exit) + (= exit-status 0)) + (setq newsticker--cache + (newsticker--cache-add + newsticker--cache + name-symbol + newsticker--error-headline + (format + (concat "%s: Newsticker could not retrieve news from %s.\n" + "Return status: `%s'\n" + "Command was `%s'") + (format-time-string "%A, %H:%M" (current-time)) + name event (process-command process)) + "" + (current-time) + 'new + 0 nil)) + (message "%s: Error while retrieving news from %s" + (format-time-string "%A, %H:%M" (current-time)) + (process-name process)) + (throw 'oops nil)) + (let* ((coding-system nil) + (node-list + (save-current-buffer + (set-buffer (process-buffer process)) + ;; a very very dirty workaround to overcome the + ;; problems with the newest (20030621) xml.el: + ;; remove all unnecessary whitespace + (goto-char (point-min)) + (while (re-search-forward ">[ \t\r\n]+<" nil t) + (replace-match "><" nil t)) + ;; and another brutal workaround (20031105)! For some + ;; reason the xml parser does not like the colon in the + ;; doctype name "rdf:RDF" + (goto-char (point-min)) + (if (re-search-forward "" nil t) + (replace-match "" nil t)) + ;; And another one (20050618)! (Fixed in GNU Emacs 22.0.50.18) + ;; Remove comments to avoid this xml-parsing bug: + ;; "XML files can have only one toplevel tag" + (goto-char (point-min)) + (while (search-forward "" nil t) + (error "Can't find end of comment")) + (delete-region start (point)))) + ;; And another one (20050702)! If description is HTML + ;; encoded and starts with a `<', wrap the whole + ;; description in a CDATA expression. This happened for + ;; http://www.thefreedictionary.com/_/WoD/rss.aspx?type=quote + (goto-char (point-min)) + (while (re-search-forward + "\\(" nil t) + (replace-match + "")) + ;; + (set-buffer-modified-p nil) + (goto-char (point-min)) + (if (re-search-forward "encoding=\"\\([^\"]+\\)\"" + nil t) + (setq coding-system (intern + (downcase(match-string 1))))) + (condition-case errordata + ;; The xml parser might fail + ;; or the xml might be bugged + (xml-parse-region (point-min) (point-max)) + (error (message "Could not parse %s: %s" + (buffer-name) (cadr errordata)) + (throw 'oops nil))))) + (topnode (car node-list)) + (channelnode (car (xml-get-children topnode 'channel))) + (imageurl nil) + (position 0)) + ;; mark all items as obsolete + (newsticker--cache-replace-age newsticker--cache + name-symbol + 'new 'obsolete-new) + (newsticker--cache-replace-age newsticker--cache + name-symbol + 'old 'obsolete-old) + (newsticker--cache-replace-age newsticker--cache + name-symbol + 'feed 'obsolete-old) + ;; gather the news + (if (eq (xml-node-name topnode) 'rss) + ;; this is RSS 0.91 or something similar + ;; all items are inside the channel node + (setq topnode channelnode)) + (setq imageurl + (car (xml-node-children + (car (xml-get-children + (car (xml-get-children + topnode + 'image)) + 'url))))) + (let ((title (or (car (xml-node-children (car (xml-get-children + channelnode 'title)))) + "[untitled]")) + (link (or (car (xml-node-children (car (xml-get-children + channelnode 'link)))) + "")) + (desc (or (car (xml-node-children (car (xml-get-children + channelnode + 'content:encoded)))) + (car (xml-node-children (car (xml-get-children + channelnode + 'description)))) + "[No description available]")) + (old-item nil)) + ;; check coding system + (setq coding-system + (condition-case nil + (check-coding-system coding-system) + (coding-system-error + (message "newsticker.el: %s %s %s %s" + "ignoring coding system " + coding-system + " for " + name) + nil))) + ;; apply coding system + (when coding-system + (setq title (newsticker--decode-coding-string title coding-system)) + (if desc + (setq desc (newsticker--decode-coding-string desc + coding-system))) + (setq link (newsticker--decode-coding-string link coding-system))) + ;; decode numeric entities + (setq title (newsticker--decode-numeric-entities title)) + (setq desc (newsticker--decode-numeric-entities desc)) + (setq link (newsticker--decode-numeric-entities link)) + ;; remove whitespace from title, desc, and link + (setq title (newsticker--remove-whitespace title)) + (setq desc (newsticker--remove-whitespace desc)) + (setq link (newsticker--remove-whitespace link)) + + ;; handle the feed itself + (unless (newsticker--cache-contains newsticker--cache + name-symbol title + desc link 'feed) + (setq something-was-added t)) + (setq newsticker--cache + (newsticker--cache-add + newsticker--cache name-symbol + title desc link time 'feed position + (xml-node-children channelnode) + 'feed time)) + ;; gather all items for this feed + (mapc (lambda (node) + (when (eq (xml-node-name node) 'item) + (setq position (1+ position)) + (setq title (or (car (xml-node-children + (car (xml-get-children + node 'title)))) + "[untitled]")) + (setq link (or (car (xml-node-children + (car (xml-get-children + node 'link)))) + "")) + (setq desc (or + (car (xml-node-children + (car (xml-get-children + node 'content:encoded)))) + (car (xml-node-children + (car (xml-get-children + node 'description)))))) + ;; use pubDate value if present + (setq time (or (newsticker--decode-rfc822-date + (car (xml-node-children + (car (xml-get-children + node 'pubDate))))) + time)) + ;; use dc:date value if present + (setq time (or (newsticker--decode-iso8601-date + (car (xml-node-children + (car (xml-get-children + node 'dc:date))))) + time)) + ;; It happened that the title or description + ;; contained evil HTML code that confused the + ;; xml parser. Therefore: + (unless (stringp title) + (setq title (prin1-to-string title))) + (unless (or (stringp desc) (not desc)) + (setq desc (prin1-to-string desc))) + ;; ignore items with empty title AND empty desc + (when (or (> (length title) 0) + (> (length desc) 0)) + ;; apply coding system + (when coding-system + (setq title (newsticker--decode-coding-string + title coding-system)) + (if desc + (setq desc (newsticker--decode-coding-string desc + coding-system))) + (setq link (newsticker--decode-coding-string + link coding-system))) + ;; decode numeric entities + (setq title (newsticker--decode-numeric-entities title)) + (when desc + (setq desc (newsticker--decode-numeric-entities desc))) + (setq link (newsticker--decode-numeric-entities link)) + ;; remove whitespace from title, desc, and link + (setq title (newsticker--remove-whitespace title)) + (setq desc (newsticker--remove-whitespace desc)) + (setq link (newsticker--remove-whitespace link)) + ;; add data to cache + ;; do we have this item already? + (let* ((tguid (assoc 'guid (xml-node-children node))) + (guid (if (stringp tguid) + tguid + (car (xml-node-children tguid))))) + ;;(message "guid=%s" guid) + (setq old-item + (newsticker--cache-contains newsticker--cache + name-symbol title + desc link nil guid))) + ;; add this item, or mark it as old, or do nothing + (let ((age1 'new) + (age2 'old) + (item-new-p nil)) + (if old-item + (let ((prev-age (newsticker--age old-item))) + (unless + newsticker-automatically-mark-items-as-old + (if (eq prev-age 'obsolete-old) + (setq age2 'old) + (setq age2 'new))) + (if (eq prev-age 'immortal) + (setq age2 'immortal))) + ;; item was not there + (setq item-new-p t) + (setq something-was-added t)) + (setq newsticker--cache + (newsticker--cache-add + newsticker--cache name-symbol title desc link + time age1 position (xml-node-children node) + age2)) + (when item-new-p + (let ((item (newsticker--cache-contains + newsticker--cache + name-symbol title + desc link nil))) + (if newsticker-auto-mark-filter + (newsticker--run-auto-mark-filter name item)) + (run-hook-with-args + 'newsticker-new-item-functions name item))))))) + (xml-get-children topnode 'item))) + ;; Remove those old items from cache which have been removed from + ;; the feed + (newsticker--cache-replace-age newsticker--cache + name-symbol 'obsolete-old 'deleteme) + (newsticker--cache-remove newsticker--cache name-symbol + 'deleteme) + ;; Remove those new items from cache which have been removed from + ;; the feed. Or keep them as `obsolete' + (if (not newsticker-keep-obsolete-items) + (newsticker--cache-remove newsticker--cache + name-symbol 'obsolete-new) + (setq newsticker--cache + (newsticker--cache-mark-expired + newsticker--cache name-symbol 'obsolete 'obsolete-expired + newsticker-obsolete-item-max-age)) + (newsticker--cache-remove newsticker--cache + name-symbol 'obsolete-expired) + (newsticker--cache-replace-age newsticker--cache + name-symbol 'obsolete-new + 'obsolete)) + ;; bring cache data into proper order.... + ;; (newsticker--cache-sort) + ;; setup scrollable text + (newsticker--ticker-text-setup) + (setq newsticker--latest-update-time (current-time)) + (when something-was-added + ;; FIXME: should we care about removed items as well? + (newsticker--cache-update) + (newsticker--buffer-set-uptodate nil)) + ;; kill the process buffer if wanted + (unless newsticker-debug + (kill-buffer (process-buffer process))) + ;; launch retrieval of image + (when (and imageurl + (string-match "%l" newsticker-heading-format)) + (newsticker--image-get name imageurl)))))) + +(defun newsticker--display-tick () + "Called from the display timer. +This function calls a display function, according to the variable +`newsticker-scroll-smoothly'." + (if newsticker-scroll-smoothly + (newsticker--display-scroll) + (newsticker--display-jump))) + +(defsubst newsticker--echo-area-clean-p () + "Check whether somebody is using the echo area / minibuffer. +Return t if echo area and minibuffer are unused." + (not (or (active-minibuffer-window) + (and (current-message) + (not (string= (current-message) + newsticker--prev-message)))))) + +(defun newsticker--display-jump () + "Called from the display timer. +This function displays the next ticker item in the echo area, unless +there is another message displayed or the minibuffer is active." + (let ((message-log-max nil));; prevents message text from being logged + (when (newsticker--echo-area-clean-p) + (setq newsticker--item-position (1+ newsticker--item-position)) + (when (>= newsticker--item-position (length newsticker--item-list)) + (setq newsticker--item-position 0)) + (setq newsticker--prev-message + (nth newsticker--item-position newsticker--item-list)) + (message newsticker--prev-message)))) + +(defun newsticker--display-scroll () + "Called from the display timer. +This function scrolls the ticker items in the echo area, unless +there is another message displayed or the minibuffer is active." + (when (newsticker--echo-area-clean-p) + (let* ((width (- (frame-width) 1)) + (message-log-max nil);; prevents message text from being logged + (i newsticker--item-position) + subtext + (s-text newsticker--scrollable-text) + (l (length s-text))) + ;; don't show anything if there is nothing to show + (unless (< (length s-text) 1) + ;; repeat the ticker string if it is shorter than frame width + (while (< (length s-text) width) + (setq s-text (concat s-text s-text))) + ;; get the width of the printed string + (setq l (length s-text)) + (cond ((< i (- l width)) + (setq subtext (substring s-text i (+ i width)))) + (t + (setq subtext (concat + (substring s-text i l) + (substring s-text 0 (- width (- l i))))))) + ;; Take care of multibyte strings, for which (string-width) is + ;; larger than (length). + ;; Actually, such strings may be smaller than (frame-width) + ;; because return values of (string-width) are too large: + ;; (string-width "") => 2 + (let ((t-width (1- (length subtext)))) + (while (> (string-width subtext) width) + (setq subtext (substring subtext 0 t-width)) + (setq t-width (1- t-width)))) + ;; show the ticker text and save current position + (message subtext) + (setq newsticker--prev-message subtext) + (setq newsticker--item-position (1+ i)) + (when (>= newsticker--item-position l) + (setq newsticker--item-position 0)))))) + +;; ====================================================================== +;;; misc +;; ====================================================================== +(defun newsticker--decode-coding-string (string coding-system) + "Wrapper around `decode-coding-string'. +This functions passes the arguments STRING and CODING-SYSTEM to +`decode-coding-string'. If the decoding is successful the +decoded string is returned, otherwise the unmodified input string +is returned." + (condition-case nil + (decode-coding-string string coding-system) + (error + (message "Cannot decode encoded string `%s'" string) + string))) + +(defun newsticker--decode-numeric-entities (string) + "Decode SGML numeric entities by their respective utf characters. +This function replaces numeric entities in the input STRING and +returns the modified string. For example \"*\" gets replaced +by \"*\"." + (let ((start 0)) + (while (string-match "&#\\([0-9]+\\);" string start) + (condition-case nil + (setq string (replace-match + (string (read (substring string (match-beginning 1) + (match-end 1)))) + nil nil string)) + (error nil)) + (setq start (1+ (match-beginning 0)))) + string)) + +(defun newsticker--remove-whitespace (string) + "Remove leading and trailing whitespace from STRING." + ;; we must have ...+ but not ...* in the regexps otherwise xemacs loops + ;; endlessly... + (when string + (replace-regexp-in-string + "[ \t\r\n]+$" "" + (replace-regexp-in-string "^[ \t\r\n]+" "" string)))) + +(defun newsticker--do-forget-preformatted (item) + "Forget all cached pre-formatted data. +Remove the pre-formatted from `newsticker--cache'." + (if (nthcdr 7 item) + (setcar (nthcdr 7 item) nil)) + (if (nthcdr 6 item) + (setcar (nthcdr 6 item) nil))) + +(defun newsticker--forget-preformatted () + "Forget all cached pre-formatted data. +Remove the pre-formatted from `newsticker--cache'." + (mapc (lambda (feed) + (mapc 'newsticker--do-forget-preformatted + (cdr feed))) + newsticker--cache) + (newsticker--buffer-set-uptodate nil)) + +(defun newsticker--debug-msg (string &rest args) + "Print newsticker debug messages. +This function calls `message' with arguments STRING and ARGS, if +`newsticker-debug' is non-nil." + (and newsticker-debug + ;;(not (active-minibuffer-window)) + ;;(not (current-message)) + (apply 'message string args))) + +(defun newsticker--decode-iso8601-date (iso8601-string) + "Return ISO8601-STRING in format like `decode-time'. +Converts from ISO-8601 to Emacs representation. If ISO8601-STRING +Examples: +2004-09-17T05:09:49+00:00 +2004-09-17T05:09+00:00 +2004-09-17T05:09:49 +2004-09-17T05:09 +2004-09-17 +2004-09 +2004" + (if iso8601-string + (when (string-match + (concat + "\\([0-9]\\{4\\}\\)" + "\\(-\\([0-9]\\{2\\}\\)" + "\\(-\\([0-9]\\{2\\}\\)" + "\\(T" + "\\([0-9]\\{2\\}\\):\\([0-9]\\{2\\}\\)" + "\\(:\\([0-9]\\{2\\}\\)\\)?" + "\\(\\([-+Z]\\)\\(\\([0-9]\\{2\\}\\):\\([0-9]\\{2\\}\\)\\)?\\)?" + "\\)?\\)?\\)?") + iso8601-string) + (let ((year (read (match-string 1 iso8601-string))) + (month (read (match-string 3 iso8601-string))) + (day (read (match-string 5 iso8601-string))) + (hour (read (or (match-string 7 iso8601-string) + "0"))) + (minute (read (or (match-string 8 iso8601-string) + "0"))) + ;;(second (read (or (match-string 10 iso8601-string) + ;; "0"))) + (sign (match-string 12 iso8601-string)) + (offset-hour (read (or (match-string 14 iso8601-string) + "0"))) + (offset-minute (read (or (match-string 15 iso8601-string) + "0"))) + (second 0)) + (cond ((string= sign "+") + (setq hour (- hour offset-hour)) + (setq minute (- minute offset-minute))) + ((string= sign "-") + (setq hour (+ hour offset-hour)) + (setq minute (+ minute offset-minute)))) + ;; if UTC subtract current-time-zone offset + ;;(setq second (+ (car (current-time-zone)) second))) + + (condition-case nil + (encode-time second minute hour day month year t) + (error + (message "Cannot decode \"%s\"" iso8601-string) + nil)))) + nil)) + +(defun newsticker--decode-rfc822-date (rfc822-string) + "Return RFC822-STRING in format like `decode-time'. +Converts from RFC822 to Emacs representation. +Examples: +Sat, 07 Sep 2002 00:00:01 GMT +07 Sep 2002 00:00:01 GMT" + (if rfc822-string + (when (string-match + (concat + "\\s-*" + ;; week day + "\\(\\(Mon\\|Tue\\|Wed\\|Thu\\|Fri\\|Sat\\|Sun\\)\\s-*,?\\)\\s-+" + ;; day + "\\([0-9]\\{1,2\\}\\)\\s-+" + ;; month + "\\(Jan\\|Feb\\|Mar\\|Apr\\|May\\|Jun\\|Jul\\|Aug\\|" + "Sep\\|Oct\\|Nov\\|Dec\\)\\s-+" + ;; year + "\\([0-9]\\{2,4\\}\\)\\s-+" + ;; hour + "\\([0-9]\\{2\\}\\)" + ;; minute + ":\\([0-9]\\{2\\}\\)" + ;; second + "\\(:\\([0-9]\\{2\\}\\)\\)?" + ;; zone -- fixme + "\\(\\s-+.*\\)?") + rfc822-string) + (let ((day (read (match-string 3 rfc822-string))) + (month-name (match-string 4 rfc822-string)) + (month 0) + (year (read (match-string 5 rfc822-string))) + (hour (read (match-string 6 rfc822-string))) + (minute (read (match-string 7 rfc822-string))) + (second (read (or (match-string 9 rfc822-string) + "0"))) + ;;(zone (match-string 10 rfc822-string)) + ) + (condition-case error-data + (let ((i 1)) + (mapc (lambda (m) + (if (string= month-name m) + (setq month i)) + (setq i (1+ i))) + '("Jan" "Feb" "Mar" "Apr" "May" "Jun" "Jul" "Aug" + "Sep" "Oct" "Nov" "Dec")) + (encode-time second minute hour day month year t)) + (error + (message "Cannot decode \"%s\": %s %s" rfc822-string + (car error-data) (cdr error-data)) + nil)))) + nil)) + +(defun newsticker--lists-intersect-p (list1 list2) + "Return t if LIST1 and LIST2 share elements." + (let ((result nil)) + (mapc (lambda (elt) + (if (memq elt list2) + (setq result t))) + list1) + result)) + +;; ====================================================================== +;;; images +;; ====================================================================== +(defun newsticker--image-get (feed-name url) + "Get image of the news site FEED-NAME from URL. +If the image has been downloaded in the last 24h do nothing." + (let ((image-name (concat newsticker-imagecache-dirname "/" + feed-name))) + (if (and (file-exists-p image-name) + (time-less-p (current-time) + (time-add (nth 5 (file-attributes image-name)) + (seconds-to-time 86400)))) + (newsticker--debug-msg "%s: Getting image for %s skipped" + (format-time-string "%A, %H:%M" (current-time)) + feed-name) + ;; download + (newsticker--debug-msg "%s: Getting image for %s" + (format-time-string "%A, %H:%M" (current-time)) + feed-name) + (let* ((buffername (concat " *newsticker-wget-image-" feed-name "*")) + (item (or (assoc feed-name newsticker-url-list) + (assoc feed-name newsticker-url-list-defaults) + (error + "Cannot get news for %s: Check newsticker-url-list" + feed-name))) + (wget-arguments (or (car (cdr (cdr (cdr (cdr item))))) + newsticker-wget-arguments))) + (save-excursion + (set-buffer (get-buffer-create buffername)) + (erase-buffer) + ;; throw an error if there is an old wget-process around + (if (get-process feed-name) + (error "Another wget-process is running for image %s" + feed-name)) + ;; start wget + (let* ((args (append wget-arguments (list url))) + (proc (apply 'start-process feed-name buffername + newsticker-wget-name args))) + (set-process-coding-system proc 'no-conversion 'no-conversion) + (set-process-sentinel proc 'newsticker--image-sentinel))))))) + +(defun newsticker--image-sentinel (process event) + "Sentinel for image-retrieving PROCESS caused by EVENT." + (let* ((p-status (process-status process)) + (exit-status (process-exit-status process)) + (feed-name (process-name process))) + ;; catch known errors (zombie processes, rubbish-xml etc. + ;; if an error occurs the news feed is not updated! + (catch 'oops + (unless (and (eq p-status 'exit) + (= exit-status 0)) + (message "%s: Error while retrieving image from %s" + (format-time-string "%A, %H:%M" (current-time)) + feed-name) + (throw 'oops nil)) + (let (image-name) + (save-excursion + (set-buffer (process-buffer process)) + (setq image-name (concat newsticker-imagecache-dirname "/" + feed-name)) + (set-buffer-file-coding-system 'no-conversion) + ;; make sure the cache dir exists + (unless (file-directory-p newsticker-imagecache-dirname) + (make-directory newsticker-imagecache-dirname)) + ;; write and close buffer + (let ((require-final-newline nil) + (backup-inhibited t) + (coding-system-for-write 'no-conversion)) + (write-region nil nil image-name nil 'quiet)) + (set-buffer-modified-p nil) + (kill-buffer (current-buffer))))))) + +(defun newsticker--image-read (feed-name-symbol disabled) + "Read the cached image for FEED-NAME-SYMBOL from disk. +If DISABLED is non-nil the image will be converted to a disabled look +\(unless `newsticker-enable-logo-manipulations' is not t\). +Return the image." + (let ((image-name (concat newsticker-imagecache-dirname "/" + (symbol-name feed-name-symbol))) + (img nil)) + (when (file-exists-p image-name) + (condition-case error-data + (setq img (create-image + image-name nil nil + :conversion (and newsticker-enable-logo-manipulations + disabled + 'disabled) + :mask (and newsticker-enable-logo-manipulations + 'heuristic) + :ascent 70)) + (error + (message "Error: cannot create image: %s" + (cadr error-data))))) + img)) + +;; ====================================================================== +;;; imenu stuff +;; ====================================================================== +(defun newsticker--imenu-create-index () + "Scan newsticker buffer and return an index for imenu." + (save-excursion + (goto-char (point-min)) + (let ((index-alist nil) + (feed-list nil) + (go-ahead t)) + (while go-ahead + (let ((type (get-text-property (point) 'nt-type)) + (title (get-text-property (point) 'nt-title))) + (cond ((eq type 'feed) + ;; we're on a feed heading + (when feed-list + (if index-alist + (nconc index-alist (list feed-list)) + (setq index-alist (list feed-list)))) + (setq feed-list (list title))) + (t + (nconc feed-list + (list (cons title (point))))))) + (setq go-ahead (newsticker--buffer-goto '(item feed)))) + (if index-alist + (nconc index-alist (list feed-list)) + (setq index-alist (list feed-list))) + index-alist))) + +(defun newsticker--imenu-goto (name pos &rest args) + "Go item NAME at position POS and show item. +ARGS are ignored." + (goto-char pos) + (newsticker-show-entry)) + +;; ====================================================================== +;;; buffer stuff +;; ====================================================================== +(defun newsticker--buffer-set-uptodate (value) + "Set the uptodate-status of the newsticker buffer to VALUE. +The mode-line is changed accordingly." + (setq newsticker--buffer-uptodate-p value) + (let ((b (get-buffer "*newsticker*"))) + (when b + (save-excursion + (set-buffer b) + (if value + (setq mode-name "Newsticker -- up to date -- ") + (setq mode-name "Newsticker -- NEED UPDATE -- "))) + (sit-for 0)))) + +(defun newsticker--buffer-redraw () + "Sometimes (CVS) Emacs forgets to update the window..." + (if (fboundp 'force-window-update) + (force-window-update (current-buffer)) + (redraw-frame (selected-frame))) + (run-hooks 'newsticker-buffer-change-hook) + (sit-for 0)) + +(defun newsticker--buffer-insert-all-items () + "Insert all cached newsticker items into the current buffer. +Keeps order of feeds as given in `newsticker-url-list' and +`newsticker-url-list-defaults'." + (goto-char (point-min)) + (mapc (lambda (url-item) + (let* ((feed-name (car url-item)) + (feed-name-symbol (intern feed-name)) + (feed (assoc feed-name-symbol newsticker--cache)) + (items (cdr feed)) + (pos (point))) + (when feed + ;; insert the feed description + (mapc (lambda (item) + (when (eq (newsticker--age item) 'feed) + (newsticker--buffer-insert-item item + feed-name-symbol))) + items) + ;;insert the items + (mapc (lambda (item) + (if (memq (newsticker--age item) '(new immortal old + obsolete)) + (newsticker--buffer-insert-item item + feed-name-symbol))) + items) + (put-text-property pos (point) 'feed (car feed)) + + ;; insert empty line between feeds + (let ((p (point))) + (insert "\n") + (put-text-property p (point) 'hard t))))) + (append newsticker-url-list newsticker-url-list-defaults)) + + (newsticker--buffer-set-faces (point-min) (point-max)) + (newsticker--buffer-set-invisibility (point-min) (point-max)) + (goto-char (point-min))) + +(defun newsticker--buffer-insert-item (item &optional feed-name-symbol) + "Insert a news item in the current buffer. +Insert the string PREFIX and a formatted representation of the +ITEM. The optional parameter FEED-NAME-SYMBOL determines how the +item is formatted and whether the item-retrieval time is added as +well." + ;; insert headline + (if (eq (newsticker--age item) 'feed) + (newsticker--buffer-do-insert-text item 'feed feed-name-symbol) + (newsticker--buffer-do-insert-text item 'item feed-name-symbol)) + ;; insert the description + (newsticker--buffer-do-insert-text item 'desc feed-name-symbol)) + +(defun newsticker--buffer-do-insert-text (item type feed-name-symbol) + "Actually insert contents of news item, format it, render it and all that. +ITEM is a news item, TYPE tells which part of the item shall be inserted, +FEED-NAME-SYMBOL tells to which feed this item belongs." + (let* ((pos (point)) + (format newsticker-desc-format) + (pos-date-start nil) + (pos-date-end nil) + (pos-stat-start nil) + (pos-stat-end nil) + (pos-text-start nil) + (pos-text-end nil) + (pos-extra-start nil) + (pos-extra-end nil) + (pos-enclosure-start nil) + (pos-enclosure-end nil) + (age (newsticker--age item)) + (preformatted-contents (newsticker--preformatted-contents item)) + (preformatted-title (newsticker--preformatted-title item))) + (cond ((and preformatted-contents + (not (eq (aref preformatted-contents 0) ?\n));; we must + ;; NOT have a line + ;; break! + (eq type 'desc)) + (insert preformatted-contents)) + ((and preformatted-title + (not (eq (aref preformatted-title 0) ?\n));; we must NOT have a + ;; line break! + (eq type 'item)) + (insert preformatted-title)) + (t + ;; item was not formatted before. + ;; Let's go. + (if (eq type 'item) + (setq format newsticker-item-format) + (if (eq type 'feed) + (setq format newsticker-heading-format))) + + (while (> (length format) 0) + (let ((prefix (if (> (length format) 1) + (substring format 0 2) + ""))) + (cond ((string= "%c" prefix) + ;; contents + (when (newsticker--desc item) + (setq pos-text-start (point-marker)) + (insert (newsticker--desc item)) + (setq pos-text-end (point-marker))) + (setq format (substring format 2))) + ((string= "%d" prefix) + ;; date + (setq pos-date-start (point-marker)) + (if (newsticker--time item) + (insert (format-time-string newsticker-date-format + (newsticker--time item)))) + (setq pos-date-end (point-marker)) + (setq format (substring format 2))) + ((string= "%l" prefix) + ;; logo + (let ((disabled (cond ((eq (newsticker--age item) 'feed) + (= (newsticker--stat-num-items + feed-name-symbol 'new) 0)) + (t + (not (eq (newsticker--age item) + 'new)))))) + (let ((img (newsticker--image-read feed-name-symbol + disabled))) + (when img + (newsticker--insert-image img (car item))))) + (setq format (substring format 2))) + ((string= "%L" prefix) + ;; logo or title + (let ((disabled (cond ((eq (newsticker--age item) 'feed) + (= (newsticker--stat-num-items + feed-name-symbol 'new) 0)) + (t + (not (eq (newsticker--age item) + 'new)))))) + (let ((img (newsticker--image-read feed-name-symbol + disabled))) + (if img + (newsticker--insert-image img (car item)) + (when (car item) + (setq pos-text-start (point-marker)) + (if (eq (newsticker--age item) 'feed) + (insert (newsticker--title item)) + ;; FIXME: This is not the "real" title! + (insert (format "%s" + (car (newsticker--cache-get-feed + feed-name-symbol))))) + (setq pos-text-end (point-marker)))))) + (setq format (substring format 2))) + ((string= "%s" prefix) + ;; statistics + (setq pos-stat-start (point-marker)) + (if (eq (newsticker--age item) 'feed) + (insert (newsticker--buffer-statistics + feed-name-symbol))) + (setq pos-stat-end (point-marker)) + (setq format (substring format 2))) + ((string= "%t" prefix) + ;; title + (when (car item) + (setq pos-text-start (point-marker)) + (insert (car item)) + (setq pos-text-end (point-marker))) + (setq format (substring format 2))) + ((string-match "%." prefix) + ;; unknown specifier! + (insert prefix) + (setq format (substring format 2))) + ((string-match "^\\([^%]*\\)\\(.*\\)" format) ;; FIXME! + ;; everything else + (let ((p (point))) + (insert (substring format + (match-beginning 1) (match-end 1))) + ;; in case that the format string contained newlines + (put-text-property p (point) 'hard t)) + (setq format (substring format (match-beginning 2))))))) + + ;; decode HTML if possible... + (let ((is-rendered-HTML nil)) + (when (and newsticker-html-renderer pos-text-start pos-text-end) + (condition-case error-data + (save-excursion + ;; check whether it is necessary to call html renderer + ;; (regexp inspired by htmlr.el) + (goto-char pos-text-start) + (when (re-search-forward + " pos-enclosure-end pos-enclosure-start)) + (put-text-property pos-enclosure-start (1- pos-enclosure-end) + 'nt-face 'enclosure)) + + ;; left margin + ;;(unless (memq type '(feed item)) + ;;(set-left-margin pos (1- (point)) 1)) + + ;; save rendered stuff + (cond ((eq type 'desc) + ;; preformatted contents + (newsticker--cache-set-preformatted-contents + item (buffer-substring pos (point)))) + ((eq type 'item) + ;; preformatted title + (newsticker--cache-set-preformatted-title + item (buffer-substring pos (point))))))))) + +(defun newsticker--buffer-print-extra-element (extra-element) + "Insert EXTRA-ELEMENT in a pretty form into the current buffer." + (insert (format "%s:\t" (car extra-element))) + (let (;;(attributes (cadr extra-element)) ;FIXME!!!! + (contents (cddr extra-element))) + (cond ((listp contents) + (mapc (lambda (i) + (if (and (stringp i) + (string-match "^http://.*" i)) + (let ((pos (point))) + (insert i " ") ; avoid self-reference from the + ; nt-link thing + (add-text-properties + pos (point) + (list 'mouse-face 'highlight + 'nt-link i + 'help-echo + (format "mouse-2: visit (%s)" i) + 'keymap newsticker--url-keymap))) + (insert (format "%s" i)))) + contents)) + (t + (insert (format "%s" contents)))) + (insert "\n"))) + +(defun newsticker--buffer-insert-enclosure (item) + "Insert enclosure element of an RSS ITEM into the current buffer." + (let ((enclosure (newsticker--enclosure item)) + (beg (point))) + (when enclosure + (let ((url (cdr (assoc 'url enclosure))) + (length (cdr (assoc 'length enclosure))) + (type (cdr (assoc 'type enclosure)))) + (insert (format "Enclosed file (%s, %1.2f kBytes)" type + (/ (string-to-number length) 1024))) + (add-text-properties beg (point) + (list 'mouse-face 'highlight + 'nt-link url + 'help-echo (format + "mouse-2: visit (%s)" url) + 'keymap newsticker--url-keymap + 'nt-face 'enclosure + 'nt-type 'desc)) + (insert "\n"))))) + +(defun newsticker--buffer-statistics (feed-name-symbol) + "Return a statistic string for the feed given by FEED-NAME-SYMBOL. +See `newsticker-statistics-format'." + (let ((case-fold-search nil)) + (replace-regexp-in-string + "%a" + (format "%d" (newsticker--stat-num-items feed-name-symbol)) + (replace-regexp-in-string + "%i" + (format "%d" (newsticker--stat-num-items feed-name-symbol 'immortal)) + (replace-regexp-in-string + "%n" + (format "%d" (newsticker--stat-num-items feed-name-symbol 'new)) + (replace-regexp-in-string + "%o" + (format "%d" (newsticker--stat-num-items feed-name-symbol 'old)) + (replace-regexp-in-string + "%O" + (format "%d" (newsticker--stat-num-items feed-name-symbol 'obsolete)) + newsticker-statistics-format))))))) + +(defun newsticker--buffer-set-faces (start end) + "Add face properties according to mark property. +Scans the buffer between START and END." + (save-excursion + ;;(put-text-property start end 'face 'newsticker-default-face) + (goto-char start) + (let ((pos1 start) + (pos2 1) + (nt-face (get-text-property start 'nt-face)) + (nt-age (get-text-property start 'nt-age))) + (when nt-face + (setq pos2 (next-single-property-change (point) 'nt-face)) + (newsticker--set-face-properties pos1 pos2 nt-face nt-age) + (setq nt-face (get-text-property pos2 'nt-face)) + (setq pos1 pos2)) + (while (and (setq pos2 (next-single-property-change pos1 'nt-face)) + (<= pos2 end) + (> pos2 pos1)) + (newsticker--set-face-properties pos1 pos2 nt-face nt-age) + (setq nt-face (get-text-property pos2 'nt-face)) + (setq nt-age (get-text-property pos2 'nt-age)) + (setq pos1 pos2))))) + +(defun newsticker--buffer-set-invisibility (start end) + "Add invisibility properties according to nt-type property. +Scans the buffer between START and END. Sets the 'invisible +property to '(- )." + (save-excursion + ;; reset invisibility settings + (put-text-property start end 'invisible nil) + ;; let's go + (goto-char start) + (let ((pos1 start) + (pos2 1) + (nt-type (get-text-property start 'nt-type)) + (nt-age (get-text-property start 'nt-age))) + (when nt-type + (setq pos2 (next-single-property-change (point) 'nt-type)) + (put-text-property (max (point-min) pos1) (1- pos2) + 'invisible + (list (intern + (concat + (symbol-name + (if (eq nt-type 'extra) 'desc nt-type)) + "-" + (symbol-name nt-age))) + nt-type + nt-age)) + (setq nt-type (get-text-property pos2 'nt-type)) + (setq pos1 pos2)) + (while (and (setq pos2 (next-single-property-change pos1 'nt-type)) + (<= pos2 end) + (> pos2 pos1)) + ;; must shift one char to the left in order to handle inivisible + ;; newlines, motion in invisible text areas and all that correctly + (put-text-property (1- pos1) (1- pos2) + 'invisible + (list (intern + (concat + (symbol-name + (if (eq nt-type 'extra) 'desc nt-type)) + "-" + (symbol-name nt-age))) + nt-type + nt-age)) + (setq nt-type (get-text-property pos2 'nt-type)) + (setq nt-age (get-text-property pos2 'nt-age)) + (setq pos1 pos2))))) + +(defun newsticker--set-face-properties (pos1 pos2 nt-face age) + "Set the face for the text between the positions POS1 and POS2. +The face is chosen according the values of NT-FACE and AGE." + (let ((face (cond ((eq nt-face 'feed) + 'newsticker-feed-face) + ((eq nt-face 'item) + (cond ((eq age 'new) + 'newsticker-new-item-face) + ((eq age 'old) + 'newsticker-old-item-face) + ((eq age 'immortal) + 'newsticker-immortal-item-face) + ((eq age 'obsolete) + 'newsticker-obsolete-item-face))) + ((eq nt-face 'date) + 'newsticker-date-face) + ((eq nt-face 'stat) + 'newsticker-statistics-face) + ((eq nt-face 'extra) + 'newsticker-extra-face) + ((eq nt-face 'enclosure) + 'newsticker-enclosure-face)))) + (when face + (put-text-property pos1 (max pos1 pos2) 'face face)))) + +(defun newsticker--insert-image (img string) + "Insert IMG with STRING at point. +This is a work-around for a strange behavior of Emacs versions before +21.3.50. Images inserted with `insert-image' vanished after doing +`fill-region'." + ;; This should work: + ;;(insert-image img string) + ;; but it does not. Therefore we do this, which should be equivalent! + (let ((pos (point))) + ;;(insert string) + (insert ":-)") + (add-text-properties pos (point) (list 'display img)))) + +;; ====================================================================== +;;; HTML rendering +;; ====================================================================== +(defun newsticker-htmlr-render (pos1 pos2) ; + "Replacement for `htmlr-render'. +Renders the HTML code in the region POS1 to POS2 using htmlr." + (let ((str (buffer-substring-no-properties pos1 pos2))) + (delete-region pos1 pos2) + (insert + (with-temp-buffer + (insert str) + (goto-char (point-min)) + ;; begin original htmlr-render + (htmlr-reset) + ;; something omitted here... + (while (< (point) (point-max)) + (htmlr-step)) + ;; end original htmlr-render + (newsticker--remove-whitespace (buffer-string)))))) + +;; ====================================================================== +;;; Functions working on the *newsticker* buffer +;; ====================================================================== +(defun newsticker--buffer-make-item-completely-visible () + "Scroll buffer until current item is completely visible." + (switch-to-buffer (get-buffer-create "*newsticker*")) + (when newsticker--auto-narrow-to-feed + (let* ((min (or (save-excursion (newsticker--buffer-beginning-of-feed)) + (point-min))) + (max (or (save-excursion (newsticker--buffer-end-of-feed)) + (point-max)))) + (narrow-to-region min max))) + (when newsticker--auto-narrow-to-item + (let* ((min (or (save-excursion (newsticker--buffer-beginning-of-item)) + (point-min))) + (max (or (save-excursion (newsticker--buffer-end-of-item)) + (point-max)))) + (narrow-to-region min max))) + (sit-for 0) + ;; do not count lines and stuff because that does not work when images + ;; are displayed. Do it the simple way: + (save-excursion + (newsticker--buffer-end-of-item) + (unless (pos-visible-in-window-p) + (recenter -1))) + (unless (pos-visible-in-window-p) + (recenter 0))) + +(defun newsticker--buffer-get-feed-title-at-point () + "Return feed symbol of headline at point." + (format "%s" (or (get-text-property (point) 'feed) " "))) + +(defun newsticker--buffer-get-item-title-at-point () + "Return feed symbol of headline at point." + (format "%s" (or (get-text-property (point) 'nt-title) " "))) + +(defun newsticker--buffer-goto (types &optional age backwards) + "Search next occurrence of TYPES in current buffer. +TYPES is a list of symbols. If TYPES is found point is moved, if +not point is left unchanged. If optional parameter AGE is not +nil, the type AND the age must match. If BACKWARDS is t, search +backwards." + (let ((pos (save-excursion + (save-restriction + (widen) + (catch 'found + (let ((tpos (point))) + (while (setq tpos + (if backwards + (if (eq tpos (point-min)) + nil + (or (previous-single-property-change + tpos 'nt-type) + (point-min))) + (next-single-property-change + tpos 'nt-type))) + (and (memq (get-text-property tpos 'nt-type) types) + (or (not age) + (eq (get-text-property tpos 'nt-age) age)) + (throw 'found tpos))))))))) + (when pos + (goto-char pos)) + pos)) + +(defun newsticker--buffer-hideshow (mark-age onoff ) + "Hide or show items of type MARK-AGE. +If ONOFF is nil the item is hidden, otherwise it is shown." + (if onoff + (remove-from-invisibility-spec mark-age ) + (add-to-invisibility-spec mark-age))) + +(defun newsticker--buffer-beginning-of-item () + "Move point to the beginning of the item at point. +Return new position." + (if (bobp) + (point) + (let ((type (get-text-property (point) 'nt-type)) + (typebefore (get-text-property (1- (point)) 'nt-type))) + (if (and (memq type '(item feed)) + (not (eq type typebefore))) + (point) + (newsticker--buffer-goto '(item feed) nil t) + (point))))) + +(defun newsticker--buffer-beginning-of-feed () + "Move point to the beginning of the feed at point. +Return new position." + (if (bobp) + (point) + (let ((type (get-text-property (point) 'nt-type)) + (typebefore (get-text-property (1- (point)) 'nt-type))) + (if (and (memq type '(feed)) + (not (eq type typebefore))) + (point) + (newsticker--buffer-goto '(feed) nil t) + (point))))) + +(defun newsticker--buffer-end-of-item () + "Move point to the end of the item at point. +Take care: end of item is at the end of its last line!" + (when (newsticker--buffer-goto '(item feed nil)) + (point))) + +(defun newsticker--buffer-end-of-feed () + "Move point to the end of the last item of the feed at point. +Take care: end of item is at the end of its last line!" + (when (newsticker--buffer-goto '(feed nil)) + (backward-char 1) + (point))) + +;; ====================================================================== +;;; manipulation of ticker text +;; ====================================================================== +(defun newsticker--ticker-text-setup () + "Build the ticker text which is scrolled or flashed in the echo area." + ;; reset scrollable text + (setq newsticker--scrollable-text "") + (setq newsticker--item-list nil) + (setq newsticker--item-position 0) + ;; build scrollable text from cache data + (let ((have-something nil)) + (mapc + (lambda (feed) + (let ((feed-name (symbol-name (car feed)))) + (let ((num-new (newsticker--stat-num-items (car feed) 'new)) + (num-old (newsticker--stat-num-items (car feed) 'old)) + (num-imm (newsticker--stat-num-items (car feed) 'immortal)) + (num-obs (newsticker--stat-num-items (car feed) 'obsolete))) + (when (or (> num-new 0) + (and (> num-old 0) + (not newsticker-hide-old-items-in-echo-area)) + (and (> num-imm 0) + (not newsticker-hide-immortal-items-in-echo-area)) + (and (> num-obs 0) + (not newsticker-hide-obsolete-items-in-echo-area))) + (setq have-something t) + (mapc + (lambda (item) + (let ((title (replace-regexp-in-string + "[\r\n]+" " " + (newsticker--title item))) + (age (newsticker--age item))) + (unless (string= title newsticker--error-headline) + (when + (or (eq age 'new) + (and (eq age 'old) + (not newsticker-hide-old-items-in-echo-area)) + (and (eq age 'obsolete) + (not + newsticker-hide-obsolete-items-in-echo-area)) + (and (eq age 'immortal) + (not + newsticker-hide-immortal-items-in-echo-area))) + (setq title (newsticker--remove-whitespace title)) + ;; add to flash list + (add-to-list 'newsticker--item-list + (concat feed-name ": " title) t) + ;; and to the scrollable text + (setq newsticker--scrollable-text + (concat newsticker--scrollable-text + " " feed-name ": " title " +++")))))) + (cdr feed)))))) + newsticker--cache) + (when have-something + (setq newsticker--scrollable-text + (concat "+++ " + (format-time-string "%A, %H:%M" + newsticker--latest-update-time) + " ++++++" newsticker--scrollable-text))))) + +(defun newsticker--ticker-text-remove (feed title) + "Remove the item of FEED with TITLE from the ticker text." + ;; reset scrollable text + (setq newsticker--item-position 0) + (let ((feed-name (symbol-name feed)) + (t-title (replace-regexp-in-string "[\r\n]+" " " title))) + ;; remove from flash list + (setq newsticker--item-list (remove (concat feed-name ": " t-title) + newsticker--item-list)) + ;; and from the scrollable text + (setq newsticker--scrollable-text + (replace-regexp-in-string + (regexp-quote (concat " " feed-name ": " t-title " +++")) + "" + newsticker--scrollable-text)) + (if (string-match (concat "^\\+\\+\\+ [A-Z][a-z]+, " + "[012]?[0-9]:[0-9][0-9] \\+\\+\\+\\+\\+\\+$") + newsticker--scrollable-text) + (setq newsticker--scrollable-text "")))) + +;; ====================================================================== +;;; manipulation of cached data +;; ====================================================================== +(defun newsticker--cache-set-preformatted-contents (item contents) + "Set preformatted contents of ITEM to CONTENTS." + (if (nthcdr 6 item) + (setcar (nthcdr 6 item) contents) + (setcdr (nthcdr 5 item) (list contents)))) + +(defun newsticker--cache-set-preformatted-title (item title) + "Set preformatted title of ITEM to TITLE." + (if (nthcdr 7 item) + (setcar (nthcdr 7 item) title) + (setcdr (nthcdr 6 item) title))) + +(defun newsticker--cache-replace-age (data feed old-age new-age) + "Mark all items in DATA in FEED which carry age OLD-AGE with NEW-AGE. +If FEED is 'any it applies to all feeds. If OLD-AGE is 'any, +all marks are replaced by NEW-AGE. Removes all pre-formatted contents." + (mapc (lambda (a-feed) + (when (or (eq feed 'any) + (eq (car a-feed) feed)) + (let ((items (cdr a-feed))) + (mapc (lambda (item) + (when (or (eq old-age 'any) + (eq (newsticker--age item) old-age)) + (setcar (nthcdr 4 item) new-age) + (newsticker--do-forget-preformatted item))) + items)))) + data) + data) + +(defun newsticker--cache-mark-expired (data feed old-age new-age time) + "Mark all expired entries. +This function sets the age entries in DATA in the feed FEED. If +an item's age is OLD-AGE it is set to NEW-AGE if the item is +older than TIME." + (mapc + (lambda (a-feed) + (when (or (eq feed 'any) + (eq (car a-feed) feed)) + (let ((items (cdr a-feed))) + (mapc + (lambda (item) + (when (eq (newsticker--age item) old-age) + (let ((exp-time (time-add (newsticker--time item) + (seconds-to-time time)))) + (when (time-less-p exp-time (current-time)) + (newsticker--debug-msg + "Item `%s' from %s has expired on %s" + (newsticker--title item) + (format-time-string "%Y-%02m-%d, %H:%M" + (newsticker--time item)) + (format-time-string "%Y-%02m-%d, %H:%M" exp-time)) + (setcar (nthcdr 4 item) new-age))))) + items)))) + data) + data) + +(defun newsticker--cache-contains (data feed title desc link age + &optional guid) + "Check DATA whether FEED contains an item with the given properties. +This function returns the contained item or nil if it is not +contained. +The properties which are checked are TITLE, DESC, LINK, AGE, and +GUID. In general all properties must match in order to return a +certain item, except for the following cases. + +If AGE equals 'feed the TITLE, DESCription and LINK do not +matter. If DESC is nil it is ignored as well. If +`newsticker-desc-comp-max' is non-nil, only the first +`newsticker-desc-comp-max' characters of DESC are taken into +account. + +If GUID is non-nil it is sufficient to match this value, and the +other properties are ignored." + (condition-case nil + (catch 'found + (when (and desc newsticker-desc-comp-max + (> (length desc) newsticker-desc-comp-max)) + (setq desc (substring desc 0 newsticker-desc-comp-max))) + (mapc + (lambda (this-feed) + (when (eq (car this-feed) feed) + (mapc (lambda (anitem) + (when (or + ;; global unique id can match + (and guid + (string= guid (newsticker--guid anitem))) + ;; or title, desc, etc. + (and + ;;(or (not (eq age 'feed)) + ;; (eq (newsticker--age anitem) 'feed)) + (string= (newsticker--title anitem) + title) + (or (not link) + (string= (newsticker--link anitem) + link)) + (or (not desc) + (if (and desc newsticker-desc-comp-max + (> (length (newsticker--desc anitem)) + newsticker-desc-comp-max)) + (string= (substring + (newsticker--desc anitem) + 0 newsticker-desc-comp-max) + desc) + (string= (newsticker--desc anitem) + desc))))) + (throw 'found anitem))) + (cdr this-feed)))) + data) + nil) + (error nil))) + +(defun newsticker--cache-add (data feed-name-symbol title desc link time age + position extra-elements + &optional updated-age updated-time + preformatted-contents + preformatted-title) + "Add another item to cache data. +Add to DATA in the FEED-NAME-SYMBOL an item with TITLE, DESC, +LINK, TIME, AGE, POSITION, and EXTRA-ELEMENTS. If this item is +contained already, its mark is set to UPDATED-AGE, its time is +set to UPDATED-TIME, and its pre-formatted contents is set to +PREFORMATTED-CONTENTS and PREFORMATTED-TITLE. Returns the age +which the item got." + (let ((item (newsticker--cache-contains data feed-name-symbol title + desc link age))) + (if item + ;; does exist already -- change age, update time and position + (progn + (if (nthcdr 5 item) + (setcar (nthcdr 5 item) position) + (setcdr (nthcdr 4 item) (list position))) + (setcar (nthcdr 4 item) updated-age) + (if updated-time + (setcar (nthcdr 3 item) updated-time)) + ;; replace cached pre-formatted contents + (newsticker--cache-set-preformatted-contents + item preformatted-contents) + (newsticker--cache-set-preformatted-title + item preformatted-title)) + ;; did not exist or age equals 'feed-name-symbol + (catch 'found + (mapc (lambda (this-feed) + (when (eq (car this-feed) feed-name-symbol) + (setcdr this-feed (nconc (cdr this-feed) + (list (list title desc link + time age position + preformatted-contents + preformatted-title + extra-elements)))) + (throw 'found this-feed))) + data) + ;; the feed is not contained + (add-to-list 'data (list feed-name-symbol + (list title desc link time age position + preformatted-contents + preformatted-title + extra-elements)) + t)))) + data) + +(defun newsticker--cache-remove (data feed-symbol age) + "Remove all entries from DATA in the feed FEED-SYMBOL with AGE. +FEED-SYMBOL may be 'any. Entries from old feeds, which are no longer in +`newsticker-url-list' or `newsticker-url-list-defaults', are removed as +well." + (let* ((pos data) + (feed (car pos)) + (last-pos nil)) + (while feed + (if (or (assoc (symbol-name (car feed)) newsticker-url-list) + (assoc (symbol-name (car feed)) newsticker-url-list-defaults)) + ;; feed is still valid=active + ;; (message "Keeping feed %s" (car feed)) + (if (or (eq feed-symbol 'any) + (eq feed-symbol (car feed))) + (let* ((item-pos (cdr feed)) + (item (car item-pos)) + (prev-pos nil)) + (while item + ;;(message "%s" (car item)) + (if (eq age (newsticker--age item)) + ;; remove this item + (progn + ;;(message "Removing item %s" (car item)) + (if prev-pos + (setcdr prev-pos (cdr item-pos)) + (setcdr feed (cdr item-pos)))) + ;;(message "Keeping item %s" (car item)) + (setq prev-pos item-pos)) + (setq item-pos (cdr item-pos)) + (setq item (car item-pos))))) + ;; feed is not active anymore + ;; (message "Removing feed %s" (car feed)) + (if last-pos + (setcdr last-pos (cdr pos)) + (setq data (cdr pos)))) + (setq last-pos pos) + (setq pos (cdr pos)) + (setq feed (car pos))))) + +;; ====================================================================== +;;; Sorting +;; ====================================================================== +(defun newsticker--cache-item-compare-by-time (item1 item2) + "Compare two news items ITEM1 and ITEM2 by comparing their time values." + (catch 'result + (let ((age1 (newsticker--age item1)) + (age2 (newsticker--age item2))) + (if (not (eq age1 age2)) + (cond ((eq age1 'obsolete) + (throw 'result nil)) + ((eq age2 'obsolete) + (throw 'result t))))) + (let* ((time1 (newsticker--time item1)) + (time2 (newsticker--time item2))) + (cond ((< (nth 0 time1) (nth 0 time2)) + nil) + ((> (nth 0 time1) (nth 0 time2)) + t) + ((< (nth 1 time1) (nth 1 time2)) + nil) + ((> (nth 1 time1) (nth 1 time2)) + t) + ((< (or (nth 2 time1) 0) (or (nth 2 time2) 0)) + nil) + ((> (or (nth 2 time1) 0) (or (nth 2 time2) 0)) + t) + (t + nil))))) + +(defun newsticker--cache-item-compare-by-title (item1 item2) + "Compare ITEM1 and ITEM2 by comparing their titles." + (catch 'result + (let ((age1 (newsticker--age item1)) + (age2 (newsticker--age item2))) + (if (not (eq age1 age2)) + (cond ((eq age1 'obsolete) + (throw 'result nil)) + ((eq age2 'obsolete) + (throw 'result t))))) + (string< (newsticker--title item1) (newsticker--title item2)))) + +(defun newsticker--cache-item-compare-by-position (item1 item2) + "Compare ITEM1 and ITEM2 by comparing their original positions." + (catch 'result + (let ((age1 (newsticker--age item1)) + (age2 (newsticker--age item2))) + (if (not (eq age1 age2)) + (cond ((eq age1 'obsolete) + (throw 'result nil)) + ((eq age2 'obsolete) + (throw 'result t))))) + (< (or (newsticker--pos item1) 0) (or (newsticker--pos item2) 0)))) + +(defun newsticker--cache-sort () + "Sort the newsticker cache data." + (let ((sort-fun (cond ((eq newsticker-sort-method 'sort-by-time) + 'newsticker--cache-item-compare-by-time) + ((eq newsticker-sort-method 'sort-by-title) + 'newsticker--cache-item-compare-by-title) + ((eq newsticker-sort-method 'sort-by-original-order) + 'newsticker--cache-item-compare-by-position)))) + (mapc (lambda (feed-list) + (setcdr feed-list (sort (cdr feed-list) + sort-fun))) + newsticker--cache))) + +(defun newsticker--cache-update (&optional save) + "Update newsticker cache file. +If optional argument SAVE is not nil the cache file is saved to disk." + (save-excursion + (let ((coding-system-for-write 'utf-8) + (buf (find-file-noselect newsticker-cache-filename))) + (when buf + (set-buffer buf) + (setq buffer-undo-list t) + (erase-buffer) + (insert ";; -*- coding: utf-8 -*-\n") + (insert (prin1-to-string newsticker--cache)) + (when save + (save-buffer)))))) + +(defun newsticker--cache-get-feed (feed) + "Return the cached data for the feed FEED. +FEED is a symbol!" + (assoc feed newsticker--cache)) + +;; ====================================================================== +;;; Statistics +;; ====================================================================== +(defun newsticker--stat-num-items (feed &optional age) + "Return number of items in the given FEED which have the given AGE. +If AGE is nil, the total number items is returned." + (let ((items (cdr (newsticker--cache-get-feed feed))) + (num 0)) + (while items + (if age + (if (eq (newsticker--age (car items)) age) + (setq num (1+ num))) + (if (memq (newsticker--age (car items)) '(new old immortal obsolete)) + (setq num (1+ num)))) + (setq items (cdr items))) + num)) + +;; ====================================================================== +;;; OPML +;; ====================================================================== +(defun newsticker-opml-export () + "OPML subscription export. +Export subscriptions to a buffer in OPML Format." + (interactive) + (with-current-buffer (get-buffer-create "*OPML Export*") + (set-buffer-file-coding-system 'utf-8) + (insert (concat + "\n" + "\n" + "\n" + " \n" + " mySubscriptions\n" + " " (format-time-string "%a, %d %b %Y %T %z") + "\n" + " " user-mail-address "\n" + " " (user-full-name) "\n" + " \n" + " \n")) + (mapc (lambda (sub) + (insert " \n")) + (append newsticker-url-list newsticker-url-list-defaults)) + (insert " \n\n")) + (pop-to-buffer "*OPML Export*") + (when (fboundp 'sgml-mode) + (sgml-mode))) + +(defun newsticker-opml-import (filename) + "Import OPML data from FILENAME." + (interactive "fOPML file: ") + (set-buffer (find-file-noselect filename)) + (goto-char (point-min)) + (let* ((node-list (xml-parse-region (point-min) (point-max))) + (body (car (xml-get-children (car node-list) 'body))) + (outlines (xml-get-children body 'outline))) + (mapc (lambda (outline) + (let ((name (xml-get-attribute outline 'text)) + (url (xml-get-attribute outline 'xmlUrl))) + (add-to-list 'newsticker-url-list + (list name url nil nil nil) t))) + outlines)) + (customize-variable 'newsticker-url-list)) + +;; ====================================================================== +;;; Auto marking +;; ====================================================================== +(defun newsticker--run-auto-mark-filter (feed item) + "Automatically mark an item as old or immortal. +This function checks the variable `newsticker-auto-mark-filter' +for an entry that matches FEED and ITEM." + (let ((case-fold-search t)) + (mapc (lambda (filter) + (let ((filter-feed (car filter)) + (old-list (nth 1 filter)) + (imm-list (nth 2 filter))) + (when (string-match filter-feed feed) + (newsticker--do-run-auto-mark-filter item 'old old-list) + (newsticker--do-run-auto-mark-filter item 'immortal imm-list)))) + newsticker-auto-mark-filter))) + +(defun newsticker--do-run-auto-mark-filter (item age list) + "Actually compare ITEM AGE LIST against `newsticker-auto-mark-filter'." + (mapc (lambda (pattern) + (when (string-match pattern (newsticker--title item)) + (setcar (nthcdr 4 item) age))) + list)) + + +;; ====================================================================== +;;; hook samples +;; ====================================================================== +(defun newsticker-new-item-functions-sample (feed item) + "Demonstrate the use of the `newsticker-new-item-functions' hook. +This function just prints out the values of the FEED and title of the ITEM." + (message (concat "newsticker-new-item-functions-sample: feed=`%s', " + "title=`%s'") + feed (newsticker--title item))) + +(defun newsticker-download-images (feed item) + "Download the first image. +If FEED equals \"imagefeed\" download the first image URL found +in the description=contents of ITEM to the directory +\"~/tmp/newsticker/FEED/TITLE\" where TITLE is the title of the item." + (when (string= feed "imagefeed") + (let ((title (newsticker--title item)) + (desc (newsticker--desc item))) + (when (string-match " (car x) loc) ; still bigger (setq marker-line x) (if (> (- (or (car marker-line) 1) loc) @@ -710,17 +714,18 @@ FILE should be (ABSOLUTE-FILENAME) or (RELATIVE-FILENAME . DIRNAME) or nil." (forward-to-indentation 0)) (setq marker (list (point-marker)))))) - (setq loc (compilation-assq line (cdr file))) + (setq loc (compilation-assq line (cdr file-struct))) (if end-line - (setq end-loc (compilation-assq end-line (cdr file)) + (setq end-loc (compilation-assq end-line (cdr file-struct)) end-loc (compilation-assq end-col end-loc)) (if end-col ; use same line element (setq end-loc (compilation-assq end-col loc)))) (setq loc (compilation-assq col loc)) ;; If they are new, make the loc(s) reference the file they point to. - (or (cdr loc) (setcdr loc `(,line ,file ,@marker))) + (or (cdr loc) (setcdr loc `(,line ,file-struct ,@marker))) (if end-loc - (or (cdr end-loc) (setcdr end-loc `(,(or end-line line) ,file ,@end-marker)))) + (or (cdr end-loc) + (setcdr end-loc `(,(or end-line line) ,file-struct ,@end-marker)))) ;; Must start with face `(face ,compilation-message-face @@ -1570,8 +1575,7 @@ This is the value of `next-error-function' in Compilation buffers." ;; markers for that file. (unless (and (nth 3 loc) (marker-buffer (nth 3 loc))) (with-current-buffer (compilation-find-file marker (caar (nth 2 loc)) - (or (cdar (nth 2 loc)) - default-directory)) + (cadr (car (nth 2 loc)))) (save-restriction (widen) (goto-char (point-min)) @@ -1734,16 +1738,21 @@ and overlay is highlighted between MK and END-MK." (copy-marker (line-beginning-position)))))) -(defun compilation-find-file (marker filename dir &rest formats) +(defun compilation-find-file (marker filename directory &rest formats) "Find a buffer for file FILENAME. Search the directories in `compilation-search-path'. A nil in `compilation-search-path' means to try the -current directory, which is passed in DIR. +\"current\" directory, which is passed in DIRECTORY. +If DIRECTORY. is relative, it is combined with `default-directory'. +If DIRECTORY. is nil, that means use `default-directory'. If FILENAME is not found at all, ask the user where to find it. Pop up the buffer containing MARKER and scroll to MARKER if we ask the user." (or formats (setq formats '("%s"))) (save-excursion (let ((dirs compilation-search-path) + (spec-dir (if directory + (expand-file-name directory) + default-directory)) buffer thisdir fmts name) (if (file-name-absolute-p filename) ;; The file name is absolute. Use its explicit directory as @@ -1753,7 +1762,7 @@ Pop up the buffer containing MARKER and scroll to MARKER if we ask the user." filename (file-name-nondirectory filename))) ;; Now search the path. (while (and dirs (null buffer)) - (setq thisdir (or (car dirs) dir) + (setq thisdir (or (car dirs) spec-dir) fmts formats) ;; For each directory, try each format string. (while (and fmts (null buffer)) @@ -1771,7 +1780,7 @@ Pop up the buffer containing MARKER and scroll to MARKER if we ask the user." (read-file-name (format "Find this %s in: (default %s) " compilation-error filename) - dir filename t)))) + spec-dir filename t)))) (if (file-directory-p name) (setq name (expand-file-name filename name))) (setq buffer (and (file-exists-p name) @@ -1785,26 +1794,32 @@ Pop up the buffer containing MARKER and scroll to MARKER if we ask the user." (defun compilation-get-file-structure (file &optional fmt) "Retrieve FILE's file-structure or create a new one. -FILE should be (ABSOLUTE-FILENAME) or (RELATIVE-FILENAME . DIRNAME)." +FILE should be (FILENAME) or (RELATIVE-FILENAME . DIRNAME). +In the former case, FILENAME may be relative or absolute. +The file-structure looks like this: + (list (list FILENAME [DIR-FROM-PREV-MSG]) FMT LINE-STRUCT...) +" (or (gethash file compilation-locs) ;; File was not previously encountered, at least not in the form passed. ;; Let's normalize it and look again. (let ((filename (car file)) - (default-directory (if (cdr file) - (file-truename (cdr file)) - default-directory))) + ;; Get the specified directory from FILE. + (spec-directory (if (cdr file) + (file-truename (cdr file))))) ;; Check for a comint-file-name-prefix and prepend it if appropriate. ;; (This is very useful for compilation-minor-mode in an rlogin-mode ;; buffer.) - (if (boundp 'comint-file-name-prefix) - (if (file-name-absolute-p filename) - (setq filename - (concat (with-no-warnings comint-file-name-prefix) filename)) - (setq default-directory - (file-truename - (concat (with-no-warnings comint-file-name-prefix) default-directory))))) + (when (and (boundp 'comint-file-name-prefix) + (not (equal comint-file-name-prefix ""))) + (if (file-name-absolute-p filename) + (setq filename + (concat comint-file-name-prefix filename)) + (if spec-directory + (setq spec-directory + (file-truename + (concat comint-file-name-prefix spec-directory)))))) ;; If compilation-parse-errors-filename-function is ;; defined, use it to process the filename. @@ -1820,20 +1835,13 @@ FILE should be (ABSOLUTE-FILENAME) or (RELATIVE-FILENAME . DIRNAME)." ;; name and fix them. (setq filename (command-line-normalize-file-name filename)) - ;; Now eliminate any "..", because find-file would get them wrong. - ;; Make relative and absolute filenames, with or without links, the - ;; same. - (setq filename - (list (abbreviate-file-name - (file-truename (if (cdr file) - (expand-file-name filename) - filename))))) - ;; Store it for the possibly unnormalized name (puthash file ;; Retrieve or create file-structure for normalized name - (or (gethash filename compilation-locs) - (puthash filename (list filename fmt) compilation-locs)) + (or (gethash (list filename) compilation-locs) + (puthash (list filename) + (list (list filename spec-directory) fmt) + compilation-locs)) compilation-locs)))) (add-to-list 'debug-ignored-errors "^No more [-a-z ]+s yet$") diff --git a/lisp/time-stamp.el b/lisp/time-stamp.el index 0f3aeb06895..7020dd0c2b7 100644 --- a/lisp/time-stamp.el +++ b/lisp/time-stamp.el @@ -5,7 +5,7 @@ ;; This file is part of GNU Emacs. -;; Maintainer's Time-stamp: <2004-06-13 19:04:36 teirllm> +;; Maintainer's Time-stamp: <2004-09-25 20:55:35 gildea> ;; Maintainer: Stephen Gildea ;; Keywords: tools @@ -143,20 +143,26 @@ the first (last) `time-stamp-line-limit' lines of the file for the file to be time-stamped by \\[time-stamp]. A value of 0 searches the entire buffer (use with care). -Do not change `time-stamp-line-limit', `time-stamp-start', or -`time-stamp-end' for yourself or you will be incompatible -with other people's files! If you must change them for some application, -do so in the local variables section of the time-stamped file itself.") +This value can also be set with the variable `time-stamp-pattern'. + +Do not change `time-stamp-line-limit', `time-stamp-start', +`time-stamp-end', or `time-stamp-pattern' for yourself or you will be +incompatible with other people's files! If you must change them for some +application, do so in the local variables section of the time-stamped file +itself.") (defvar time-stamp-start "Time-stamp:[ \t]+\\\\?[\"<]+" ;Do not change! "Regexp after which the time stamp is written by \\[time-stamp]. See also the variables `time-stamp-end' and `time-stamp-line-limit'. -Do not change `time-stamp-line-limit', `time-stamp-start', or -`time-stamp-end' for yourself or you will be incompatible -with other people's files! If you must change them for some application, -do so in the local variables section of the time-stamped file itself.") +This value can also be set with the variable `time-stamp-pattern'. + +Do not change `time-stamp-line-limit', `time-stamp-start', +`time-stamp-end', or `time-stamp-pattern' for yourself or you will be +incompatible with other people's files! If you must change them for some +application, do so in the local variables section of the time-stamped file +itself.") (defvar time-stamp-end "\\\\?[\">]" ;Do not change! @@ -165,13 +171,15 @@ do so in the local variables section of the time-stamped file itself.") and the following match of `time-stamp-end', then writes the time stamp specified by `time-stamp-format' between them. +This value can also be set with the variable `time-stamp-pattern'. + The end text normally starts on the same line as the start text ends, but if there are any newlines in `time-stamp-format', the same number of newlines must separate the start and end. \\[time-stamp] tries to not change the number of lines in the buffer. `time-stamp-inserts-lines' controls this behavior. -Do not change `time-stamp-line-limit', `time-stamp-start', `time-stamp-end', +Do not change `time-stamp-start', `time-stamp-end', `time-stamp-pattern', or `time-stamp-inserts-lines' for yourself or you will be incompatible with other people's files! If you must change them for some application, do so in the local variables section of the time-stamped file itself.") @@ -231,9 +239,11 @@ Examples: \"@set Time-stamp: %:b %:d, %:y$\" \"newcommand{\\\\\\\\timestamp}{%%}\" -Do not change `time-stamp-pattern' for yourself or you will be incompatible -with other people's files! Set it only in the local variables section -of the time-stamped file itself.") +Do not change `time-stamp-pattern' `time-stamp-line-limit', +`time-stamp-start', or `time-stamp-end' for yourself or you will be +incompatible with other people's files! If you must change them for +some application, do so only in the local variables section of the +time-stamped file itself.") @@ -251,10 +261,11 @@ look like one of the following: The time stamp is written between the brackets or quotes: Time-stamp: <2001-02-18 10:20:51 gildea> The time stamp is updated only if the variable `time-stamp-active' is non-nil. -The format of the time stamp is set by the variable `time-stamp-format'. -The variables `time-stamp-line-limit', `time-stamp-start', `time-stamp-end', -`time-stamp-count', and `time-stamp-inserts-lines' control finding the -template." +The format of the time stamp is set by the variable `time-stamp-pattern' or +`time-stamp-format'. The variables `time-stamp-pattern', +`time-stamp-line-limit', `time-stamp-start', `time-stamp-end', +`time-stamp-count', and `time-stamp-inserts-lines' control finding +the template." (interactive) (let ((line-limit time-stamp-line-limit) (ts-start time-stamp-start) @@ -588,6 +599,13 @@ and all `time-stamp-format' compatibility." (user-full-name)) ((eq cur-char ?h) ;mail host name (time-stamp-mail-host-name)) + ((eq cur-char ?q) ;(undocumented unqual hostname) + (let ((qualname (system-name))) + (if (string-match "\\." qualname) + (substring qualname 0 (match-beginning 0)) + qualname))) + ((eq cur-char ?Q) ;(undocumented fully-qualified host) + (system-name)) )) (let ((padded-result (format (format "%%%s%c" diff --git a/src/ChangeLog b/src/ChangeLog index 13f8c409cde..29f3929ccd8 100644 --- a/src/ChangeLog +++ b/src/ChangeLog @@ -1,3 +1,40 @@ +2005-09-12 Kim F. Storm + + * composite.c (compose_chars_in_text): Fix setup of `pend'. + Unconditionally reload `ptr' and `pend' after eval. + + * xdisp.c (message3): Pass copy of lisp string to message_dolog. + + * print.c (print_error_message): Pass copy of caller name to + message_dolog. + + * fileio.c (auto_save_error): Pass copy of lisp string to message2. + +2005-09-12 Kenichi Handa + + * xdisp.c (display_mode_element): Be sure to make variables THIS + and LISP_STRING point into a string data of ELT. + +2005-09-12 Kim F. Storm + + * editfns.c (Ftranslate_region_internal): Reload `tt' after + signal_after_change that may have GC'ed. + (Fmessage, Fmessage_box, Fmessage_or_box): Doc fix. + + * keymap.c (Fdescribe_buffer_bindings): Reload `translate' + after insert while runs signal_after_change. + + * minibuf.c (Fminibuffer_complete_word): Move `completion_string' + declaration to where it is used. + + * w32.c (check_windows_init_file): Fix allocation of error buffer. + + * xfns.c (x_encode_text): Declare static. Add FREEP arg. + (x_set_name_internal): Call x_encode_text with new FREEP arg to + know if xfree is needed instead of guessing. + + * xterm.h (x_encode_text): Remove prototype. + 2005-09-11 Chris Prince (tiny change) * w32term.c (x_bitmap_icon): Load small icons too. diff --git a/src/composite.c b/src/composite.c index daa6dceb3c6..02abb66e1e5 100644 --- a/src/composite.c +++ b/src/composite.c @@ -616,7 +616,7 @@ compose_chars_in_text (start, end, string) GCPRO1 (string); stop = end; ptr = SDATA (string) + string_char_to_byte (string, start); - pend = ptr + SBYTES (string); + pend = SDATA (string) + SBYTES (string); } else { @@ -680,10 +680,19 @@ compose_chars_in_text (start, end, string) { start += XINT (val); if (STRINGP (string)) - ptr = SDATA (string) + string_char_to_byte (string, start); + { + ptr = SDATA (string) + string_char_to_byte (string, start); + pend = SDATA (string) + SBYTES (string); + } else ptr = CHAR_POS_ADDR (start); } + else if (STRINGP (string)) + { + start++; + ptr = SDATA (string) + string_char_to_byte (string, start); + pend = SDATA (string) + SBYTES (string); + } else { start++; diff --git a/src/editfns.c b/src/editfns.c index a05d2396448..e67e56d2efa 100644 --- a/src/editfns.c +++ b/src/editfns.c @@ -2857,6 +2857,8 @@ It returns the number of characters changed. */) { if (tt) { + /* Reload as signal_after_change in last iteration may GC. */ + tt = SDATA (table); if (string_multibyte) { str = tt + string_char_to_byte (table, oc); @@ -3118,8 +3120,9 @@ The message also goes into the `*Messages*' buffer. The first argument is a format control string, and the rest are data to be formatted under control of the string. See `format' for details. -If the first argument is nil, the function clears any existing message; -this lets the minibuffer contents show. See also `current-message'. +If the first argument is nil or the empty string, the function clears +any existing message; this lets the minibuffer contents show. See +also `current-message'. usage: (message STRING &rest ARGS) */) (nargs, args) @@ -3148,8 +3151,8 @@ If a dialog box is not available, use the echo area. The first argument is a format control string, and the rest are data to be formatted under control of the string. See `format' for details. -If the first argument is nil, clear any existing message; let the -minibuffer contents show. +If the first argument is nil or the empty string, clear any existing +message; let the minibuffer contents show. usage: (message-box STRING &rest ARGS) */) (nargs, args) @@ -3210,8 +3213,8 @@ Otherwise, use the echo area. The first argument is a format control string, and the rest are data to be formatted under control of the string. See `format' for details. -If the first argument is nil, clear any existing message; let the -minibuffer contents show. +If the first argument is nil or the empty string, clear any existing +message; let the minibuffer contents show. usage: (message-or-box STRING &rest ARGS) */) (nargs, args) diff --git a/src/fileio.c b/src/fileio.c index 53b989f52e2..56557da6a30 100644 --- a/src/fileio.c +++ b/src/fileio.c @@ -5771,6 +5771,8 @@ auto_save_error (error) Lisp_Object args[3], msg; int i, nbytes; struct gcpro gcpro1; + char *msgbuf; + USE_SAFE_ALLOCA; ring_bell (XFRAME (selected_frame)); @@ -5780,13 +5782,15 @@ auto_save_error (error) msg = Fformat (3, args); GCPRO1 (msg); nbytes = SBYTES (msg); + SAFE_ALLOCA (msgbuf, char *, nbytes); + bcopy (SDATA (msg), msgbuf, nbytes); for (i = 0; i < 3; ++i) { if (i == 0) - message2 (SDATA (msg), nbytes, STRING_MULTIBYTE (msg)); + message2 (msgbuf, nbytes, STRING_MULTIBYTE (msg)); else - message2_nolog (SDATA (msg), nbytes, STRING_MULTIBYTE (msg)); + message2_nolog (msgbuf, nbytes, STRING_MULTIBYTE (msg)); Fsleep_for (make_number (1), Qnil); } diff --git a/src/keymap.c b/src/keymap.c index 9d67f4400c0..8fa4d91a9c7 100644 --- a/src/keymap.c +++ b/src/keymap.c @@ -743,7 +743,7 @@ usage: (map-keymap FUNCTION KEYMAP) */) Fsignal (Qinvalid_function, Fcons (function, Qnil)); if (! NILP (sort_first)) return call3 (intern ("map-keymap-internal"), function, keymap, Qt); - + map_keymap (keymap, map_keymap_call, function, NULL, 1); return Qnil; } @@ -2837,6 +2837,9 @@ You type Translation\n\ insert (buf, bufend - buf); insert ("\n", 1); + + /* Insert calls signal_after_change which may GC. */ + translate = SDATA (Vkeyboard_translate_table); } insert ("\n", 1); diff --git a/src/minibuf.c b/src/minibuf.c index a66d0f8f5c8..34cadfc3e24 100644 --- a/src/minibuf.c +++ b/src/minibuf.c @@ -2164,7 +2164,6 @@ Return nil if there is no valid completion, else t. */) { Lisp_Object completion, tem, tem1; register int i, i_byte; - register const unsigned char *completion_string; struct gcpro gcpro1, gcpro2; int prompt_end_charpos = XINT (Fminibuffer_prompt_end ()); @@ -2295,7 +2294,7 @@ Return nil if there is no valid completion, else t. */) { int len, c; int bytes = SBYTES (completion); - completion_string = SDATA (completion); + register const unsigned char *completion_string = SDATA (completion); for (; i_byte < SBYTES (completion); i_byte += len, i++) { c = STRING_CHAR_AND_LENGTH (completion_string + i_byte, diff --git a/src/print.c b/src/print.c index 65c48b6d82b..2ba749aff44 100644 --- a/src/print.c +++ b/src/print.c @@ -1025,7 +1025,9 @@ print_error_message (data, stream, context, caller) *Messages*. */ if (!NILP (caller) && SYMBOLP (caller)) { - const char *name = SDATA (SYMBOL_NAME (caller)); + Lisp_Object cname = SYMBOL_NAME (caller); + char *name = alloca (SBYTES (cname)); + bcopy (SDATA (cname), name, SBYTES (cname)); message_dolog (name, strlen (name), 0, 0); message_dolog (": ", 2, 0, 0); } diff --git a/src/w32.c b/src/w32.c index c7f6e3172f9..9a51233527d 100644 --- a/src/w32.c +++ b/src/w32.c @@ -3894,7 +3894,9 @@ check_windows_init_file () Lisp_Object load_path_print = Fprin1_to_string (full_load_path, Qnil); char *init_file_name = SDATA (init_file); char *load_path = SDATA (load_path_print); - char *buffer = alloca (1024); + char *buffer = alloca (1024 + + strlen (init_file_name) + + strlen (load_path)); sprintf (buffer, "The Emacs Windows initialization file \"%s.el\" " diff --git a/src/xdisp.c b/src/xdisp.c index c8ca187f3df..031260c48ad 100644 --- a/src/xdisp.c +++ b/src/xdisp.c @@ -7048,7 +7048,15 @@ message3 (m, nbytes, multibyte) /* First flush out any partial line written with print. */ message_log_maybe_newline (); if (STRINGP (m)) - message_dolog (SDATA (m), nbytes, 1, multibyte); + { + char *buffer; + USE_SAFE_ALLOCA; + + SAFE_ALLOCA (buffer, char *, nbytes); + bcopy (SDATA (m), buffer, nbytes); + message_dolog (buffer, nbytes, 1, multibyte); + SAFE_FREE (); + } message3_nolog (m, nbytes, multibyte); UNGCPRO; @@ -16183,6 +16191,8 @@ display_mode_element (it, depth, field_width, precision, elt, props, risky) else /* c == 0 */ break; } + this += SDATA (elt) - lisp_string; + lisp_string = SDATA (elt); } } break; diff --git a/src/xfns.c b/src/xfns.c index 11f4c2eadb1..84521939ec2 100644 --- a/src/xfns.c +++ b/src/xfns.c @@ -1524,11 +1524,12 @@ x_set_scroll_bar_background (f, value, oldval) Otherwise store 0 in *STRINGP, which means that the `encoding' of the result should be `COMPOUND_TEXT'. */ -unsigned char * -x_encode_text (string, coding_system, selectionp, text_bytes, stringp) +static unsigned char * +x_encode_text (string, coding_system, selectionp, text_bytes, stringp, freep) Lisp_Object string, coding_system; int *text_bytes, *stringp; int selectionp; + int *freep; { unsigned char *str = SDATA (string); int chars = SCHARS (string); @@ -1545,6 +1546,7 @@ x_encode_text (string, coding_system, selectionp, text_bytes, stringp) /* No multibyte character in OBJ. We need not encode it. */ *text_bytes = bytes; *stringp = 1; + *freep = 0; return str; } @@ -1572,6 +1574,7 @@ x_encode_text (string, coding_system, selectionp, text_bytes, stringp) *stringp = (charset_info == 1 || (!EQ (coding_system, Qcompound_text) && !EQ (coding_system, Qcompound_text_with_extensions))); + *freep = 1; return buf; } @@ -1610,16 +1613,13 @@ x_set_name_internal (f, name) in the future which can encode all Unicode characters. But, for the moment, there's no way to know that the current window manager supports it or not. */ - text.value = x_encode_text (name, coding_system, 0, &bytes, &stringp); + text.value = x_encode_text (name, coding_system, 0, &bytes, &stringp, + &do_free_text_value); text.encoding = (stringp ? XA_STRING : FRAME_X_DISPLAY_INFO (f)->Xatom_COMPOUND_TEXT); text.format = 8; text.nitems = bytes; - /* Check early, because ENCODE_UTF_8 below may GC and name may be - relocated. */ - do_free_text_value = text.value != SDATA (name); - if (NILP (f->icon_name)) { icon = text; @@ -1628,12 +1628,11 @@ x_set_name_internal (f, name) { /* See the above comment "Note: Encoding strategy". */ icon.value = x_encode_text (f->icon_name, coding_system, 0, - &bytes, &stringp); + &bytes, &stringp, &do_free_icon_value); icon.encoding = (stringp ? XA_STRING : FRAME_X_DISPLAY_INFO (f)->Xatom_COMPOUND_TEXT); icon.format = 8; icon.nitems = bytes; - do_free_icon_value = icon.value != SDATA (f->icon_name); } #ifdef USE_GTK @@ -1996,7 +1995,7 @@ xic_create_fontsetname (base_fontname, motif) - the same but with the family also replaced with -*-*-. */ char *p = base_fontname; int i; - + for (i = 0; *p; p++) if (*p == '-') i++; if (i != 14) @@ -2020,7 +2019,7 @@ xic_create_fontsetname (base_fontname, motif) char *allcs = "*-*-*-*-*-*-*"; char *allfamilies = "-*-*-"; char *all = "*-*-*-*-"; - + for (i = 0, p = base_fontname; i < 8; p++) { if (*p == '-') diff --git a/src/xterm.h b/src/xterm.h index aa615d06a17..e2db932a0be 100644 --- a/src/xterm.h +++ b/src/xterm.h @@ -1041,8 +1041,6 @@ extern void x_real_positions P_ ((struct frame *, int *, int *)); extern int defined_color P_ ((struct frame *, char *, XColor *, int)); extern void x_set_border_pixel P_ ((struct frame *, int)); extern void x_set_menu_bar_lines P_ ((struct frame *, Lisp_Object, Lisp_Object)); -extern unsigned char * x_encode_text P_ ((Lisp_Object, Lisp_Object, int, - int *, int *)); extern void x_implicitly_set_name P_ ((struct frame *, Lisp_Object, Lisp_Object)); extern void xic_free_xfontset P_ ((struct frame *)); extern void create_frame_xic P_ ((struct frame *));