summaryrefslogtreecommitdiff
path: root/lisp
diff options
context:
space:
mode:
authorXue Fuqiao <xfq.free@gmail.com>2013-08-04 10:59:08 +0800
committerXue Fuqiao <xfq.free@gmail.com>2013-08-04 10:59:08 +0800
commit99191b89ff64172740add88e67f163619a07830c (patch)
tree828d1ac7c917076703b9d4a3746ff7480bd97f0d /lisp
parentab419665caa6e2ad7465cf59ef902cc4ad1d2117 (diff)
parent2ad0a067728ccc7f8b32b0c3db1677ca351943fe (diff)
downloademacs-99191b89ff64172740add88e67f163619a07830c.tar.gz
emacs-99191b89ff64172740add88e67f163619a07830c.tar.bz2
emacs-99191b89ff64172740add88e67f163619a07830c.zip
Merge from mainline.
Diffstat (limited to 'lisp')
-rw-r--r--lisp/ChangeLog197
-rw-r--r--lisp/autorevert.el12
-rw-r--r--lisp/custom.el85
-rw-r--r--lisp/desktop.el630
-rw-r--r--lisp/emacs-lisp/advice.el1
-rw-r--r--lisp/emacs-lisp/easy-mmode.el12
-rw-r--r--lisp/emacs-lisp/lisp-mode.el7
-rw-r--r--lisp/emacs-lisp/package.el2
-rw-r--r--lisp/emacs-lisp/re-builder.el2
-rw-r--r--lisp/files.el206
-rw-r--r--lisp/frameset.el693
-rw-r--r--lisp/gnus/ChangeLog71
-rw-r--r--lisp/gnus/gnus-delay.el2
-rw-r--r--lisp/gnus/gnus-group.el15
-rw-r--r--lisp/gnus/gnus-icalendar.el837
-rw-r--r--lisp/gnus/gnus-int.el4
-rw-r--r--lisp/gnus/gnus-score.el13
-rw-r--r--lisp/gnus/gnus-start.el6
-rw-r--r--lisp/gnus/gnus-sum.el3
-rw-r--r--lisp/gnus/gnus-util.el15
-rw-r--r--lisp/gnus/gnus-uu.el4
-rw-r--r--lisp/gnus/message.el2
-rw-r--r--lisp/gnus/mm-decode.el21
-rw-r--r--lisp/gnus/mml2015.el24
-rw-r--r--lisp/gnus/nnmbox.el53
-rw-r--r--lisp/gnus/nnrss.el36
-rw-r--r--lisp/gnus/rfc2047.el161
-rw-r--r--lisp/ibuf-ext.el2
-rw-r--r--lisp/ibuffer.el24
-rw-r--r--lisp/mh-e/mh-e.el1
-rw-r--r--lisp/minibuffer.el11
-rw-r--r--lisp/net/browse-url.el4
-rw-r--r--lisp/net/eww.el2
-rw-r--r--lisp/net/network-stream.el6
-rw-r--r--lisp/net/shr.el9
-rw-r--r--lisp/net/tramp-adb.el86
-rw-r--r--lisp/net/tramp-gvfs.el136
-rw-r--r--lisp/net/tramp-sh.el137
-rw-r--r--lisp/net/tramp-smb.el19
-rw-r--r--lisp/net/tramp.el88
-rw-r--r--lisp/server.el2
-rw-r--r--lisp/speedbar.el2
-rw-r--r--lisp/textmodes/reftex-toc.el4
-rw-r--r--lisp/url/ChangeLog5
-rw-r--r--lisp/url/url-handlers.el10
-rw-r--r--lisp/vc/log-view.el43
-rw-r--r--lisp/vc/vc-dir.el2
-rw-r--r--lisp/vc/vc-hooks.el4
-rw-r--r--lisp/whitespace.el13
49 files changed, 2603 insertions, 1121 deletions
diff --git a/lisp/ChangeLog b/lisp/ChangeLog
index d914582c280..5dfca6b8e2f 100644
--- a/lisp/ChangeLog
+++ b/lisp/ChangeLog
@@ -22,15 +22,208 @@
(vc-bzr-ignore-completion-table):
(vc-bzr-find-ignore-file): New functions.
+2013-08-04 Juanma Barranquero <lekktu@gmail.com>
+
+ * frameset.el (frameset-p, frameset-save): Fix autoload cookies.
+ (frameset-filter-minibuffer): Doc fix.
+ (frameset-restore): Fix autoload cookie. Fix typo in docstring.
+
+ * desktop.el (desktop-clear): Only delete frames when called
+ interactively and desktop-restore-frames is non-nil. Doc fix.
+ (desktop-read): Set desktop-saved-frameset to nil.
+
+2013-08-03 Juanma Barranquero <lekktu@gmail.com>
+
+ * frameset.el (frameset-prop): New function and setter.
+ (frameset-save): Do not modify frame list passed by the caller.
+
+2013-08-03 Stefan Monnier <monnier@iro.umontreal.ca>
+
+ * emacs-lisp/package.el (package-desc-from-define): Ignore unknown keys.
+
+2013-08-02 Stefan Monnier <monnier@iro.umontreal.ca>
+
+ * emacs-lisp/easy-mmode.el (define-globalized-minor-mode)
+ (easy-mmode-define-navigation): Avoid ((lambda (..) ..) ...).
+
+ * custom.el (custom-initialize-default, custom-initialize-set)
+ (custom-initialize-reset, custom-initialize-changed): Affect the
+ toplevel-default-value (bug#6275, bug#14586).
+ * emacs-lisp/advice.el (ad-compile-function): Undo previous workaround
+ for bug#6275.
+
+2013-08-02 Juanma Barranquero <lekktu@gmail.com>
+
+ * emacs-lisp/lisp-mode.el (lisp-imenu-generic-expression):
+ Add cl-def* expressions.
+
+ * frameset.el (frameset-filter-params): Fix order of arguments.
+
+2013-08-02 Juanma Barranquero <lekktu@gmail.com>
+
+ Move code related to saving frames to frameset.el.
+ * desktop.el: Require frameset.
+ (desktop-restore-frames): Doc fix.
+ (desktop-restore-reuses-frames): Rename from
+ desktop-restoring-reuses-frames.
+ (desktop-saved-frameset): Rename from desktop-saved-frame-states.
+ (desktop-clear): Clear frames too.
+ (desktop-filter-parameters-alist): Set from frameset-filter-alist.
+ (desktop--filter-tty*, desktop-save, desktop-read):
+ Use frameset functions.
+ (desktop-before-saving-frames-functions, desktop--filter-*-color)
+ (desktop--filter-minibuffer, desktop--filter-restore-desktop-parm)
+ (desktop--filter-save-desktop-parm, desktop--filter-iconified-position)
+ (desktop-restore-in-original-display-p, desktop--filter-frame-parms)
+ (desktop--process-minibuffer-frames, desktop-save-frames)
+ (desktop--reuse-list, desktop--compute-pos, desktop--move-onscreen)
+ (desktop--find-frame, desktop--select-frame, desktop--make-frame)
+ (desktop--sort-states, desktop-restoring-frames-p)
+ (desktop-restore-frames): Remove. Most code moved to frameset.el.
+ (desktop-restoring-frameset-p, desktop-restore-frameset)
+ (desktop--check-dont-save, desktop-save-frameset): New functions.
+ (desktop--app-id): New constant.
+ (desktop-first-buffer, desktop-buffer-ok-count)
+ (desktop-buffer-fail-count): Move before first use.
+ * frameset.el: New file.
+
+2013-08-01 Stefan Monnier <monnier@iro.umontreal.ca>
+
+ * files.el: Use lexical-binding.
+ (dir-locals-read-from-file): Remove unused `err' variable.
+ (hack-dir-local-variables--warned-coding): New var.
+ (hack-dir-local-variables): Use it to avoid repeated warnings.
+ (make-backup-file-name--default-function): New function.
+ (make-backup-file-name-function): Use it as default.
+ (buffer-stale--default-function): New function.
+ (buffer-stale-function): Use it as default.
+ (revert-buffer-insert-file-contents--default-function): New function.
+ (revert-buffer-insert-file-contents-function): Use it as default.
+ (insert-directory): Avoid add-to-list.
+
+ * autorevert.el (auto-revert-handler): Simplify.
+ Use buffer-stale--default-function.
+
+2013-08-01 Tassilo Horn <tsdh@gnu.org>
+
+ * speedbar.el (speedbar-query-confirmation-method): Doc fix.
+
+ * whitespace.el (whitespace-ensure-local-variables): New function.
+ (whitespace-cleanup-region): Call it.
+ (whitespace-turn-on): Call it.
+
+2013-08-01 Michael Albinus <michael.albinus@gmx.de>
+
+ Complete file name handlers.
+
+ * net/tramp.el (tramp-handle-set-visited-file-modtime)
+ (tramp-handle-verify-visited-file-modtime)
+ (tramp-handle-file-notify-rm-watch): New functions.
+ (tramp-call-process): Do not bind `default-directory'.
+
+ * net/tramp-adb.el (tramp-adb-file-name-handler-alist):
+ Order alphabetically.
+ [access-file, add-name-to-file, dired-call-process]:
+ [dired-compress-file, file-acl, file-notify-rm-watch]:
+ [file-ownership-preserved-p, file-selinux-context]:
+ [make-directory-internal, make-symbolic-link, set-file-acl]:
+ [set-file-selinux-context, set-visited-file-modtime]:
+ [verify-visited-file-modtime]: Add handler.
+ (tramp-adb-handle-write-region): Apply `set-visited-file-modtime'.
+
+ * net/tramp-gvfs.el (tramp-gvfs-file-name-handler-alist)
+ [file-notify-add-watch, file-notify-rm-watch]:
+ [set-file-times, set-visited-file-modtime]:
+ [verify-visited-file-modtime]: Add handler.
+ (with-tramp-gvfs-error-message)
+ (tramp-gvfs-handle-set-visited-file-modtime)
+ (tramp-gvfs-fuse-file-name): Remove.
+ (tramp-gvfs-handle-file-notify-add-watch)
+ (tramp-gvfs-file-gvfs-monitor-file-process-filter): New defuns.
+ (tramp-gvfs-handle-write-region): Fix error in moving tmpfile.
+
+ * net/tramp-sh.el (tramp-sh-file-name-handler-alist):
+ Order alphabetically.
+ [file-notify-rm-watch ]: Use default Tramp handler.
+ [executable-find]: Remove private handler.
+ (tramp-do-copy-or-rename-file-out-of-band): Do not bind
+ `default-directory'.
+ (tramp-sh-handle-executable-find)
+ (tramp-sh-handle-file-notify-rm-watch): Remove functions.
+ (tramp-sh-file-gvfs-monitor-dir-process-filter)
+ (tramp-sh-file-inotifywait-process-filter, tramp-set-remote-path):
+ Do not use `format' in `tramp-message'.
+
+ * net/tramp-smb.el (tramp-smb-file-name-handler-alist)
+ [file-notify-rm-watch, set-visited-file-modtime]:
+ [verify-visited-file-modtime]: Add handler.
+ (tramp-smb-call-winexe): Do not bind `default-directory'.
+
+2013-08-01 Xue Fuqiao <xfq.free@gmail.com>
+
+ * vc/vc-hooks.el (vc-menu-map): Fix menu entry for vc-ignore.
+
+2013-07-31 Dmitry Gutov <dgutov@yandex.ru>
+
+ * vc/log-view.el (log-view-diff): Extract `log-view-diff-common',
+ use it.
+ (log-view-diff-changeset): Same.
+ (log-view-diff-common): Call backend command `previous-revision'
+ to find out the previous revision, in both cases. Swap the
+ variables `to' and `fr', so that `fr' usually refers to the
+ earlier revision (Bug#14989).
+
+2013-07-31 Kan-Ru Chen <kanru@kanru.info>
+
+ * ibuf-ext.el (ibuffer-filter-by-filename):
+ Make it work with dired buffers too.
+
+2013-07-31 Dmitry Antipov <dmantipov@yandex.ru>
+
+ * emacs-lisp/re-builder.el (reb-color-display-p):
+ * files.el (save-buffers-kill-terminal):
+ * net/browse-url.el (browse-url):
+ * server.el (server-save-buffers-kill-terminal):
+ * textmodes/reftex-toc.el (reftex-toc, reftex-toc-revert):
+ Prefer nil to selected-frame for the first arg of frame-parameter.
+
+2013-07-31 Xue Fuqiao <xfq.free@gmail.com>
+
+ * vc/vc-hooks.el (vc-menu-map): Add menu entry for vc-ignore.
+
+2013-07-30 Stephen Berman <stephen.berman@gmx.net>
+
+ * minibuffer.el (completion--twq-all): Try and preserve each
+ completion's case choice (bug#14907).
+
+2013-07-30 Lars Magne Ingebrigtsen <larsi@gnus.org>
+
+ * net/network-stream.el (open-network-stream): Mention the new
+ :nogreeting parameter.
+ (network-stream-open-starttls): Use the :nogreeting parameter
+ (bug#14938).
+
+ * net/shr.el (shr-mouse-browse-url): Remove and use `shr-browse-url'.
+
+ * net/eww.el (eww-setup-buffer): Switching to the buffer seems
+ more natural than popping.
+
+ * net/shr.el (shr-urlify): Put `follow-link' on URLs (bug#14815).
+ (shr-urlify): Highlight under mouse.
+
2013-07-30 Xue Fuqiao <xfq.free@gmail.com>
+ * vc/vc-hooks.el (vc-prefix-map): Add key binding for vc-ignore.
+
+ * vc/vc-dir.el (vc-dir-mode-map): Change key binding for vc-dir-ignore.
+
* vc/vc-svn.el (vc-svn-ignore): Remove `interactive'. Use `*vc*'
buffer for output.
* vc/vc-hg.el (vc-hg-ignore): Remove `interactive'. Do not assume
point-min==1. Fix search string. Fix parentheses missing.
- * vc/vc-git.el (vc-git-ignore): Remove `interactive'. Do not
+ * vc/vc-git.el (vc-git-ignore): Remove `interactive'. Do not
assume point-min==1. Fix search string. Fix parentheses missing.
* vc/vc-cvs.el (vc-cvs-ignore): Remove `interactive'.
@@ -94,7 +287,7 @@
* progmodes/cc-engine.el (c-beginning-of-statement-1)
(c-after-conditional): Adapt to deal with c-block-stmt-1-2-key.
* progmodes/cc-fonts.el (c-font-lock-declarations): Adapt to deal
- with c-block-stmet-1-2-key.
+ with c-block-stmt-1-2-key.
2013-07-27 Juanma Barranquero <lekktu@gmail.com>
diff --git a/lisp/autorevert.el b/lisp/autorevert.el
index 1617a31cd82..978a834cb4c 100644
--- a/lisp/autorevert.el
+++ b/lisp/autorevert.el
@@ -595,14 +595,14 @@ This is an internal function used by Auto-Revert Mode."
(setq size
(nth 7 (file-attributes
buffer-file-name)))))
- (and (file-readable-p buffer-file-name)
- (not (verify-visited-file-modtime buffer)))))
+ (funcall (or buffer-stale-function
+ #'buffer-stale--default-function)
+ t)))
(and (or auto-revert-mode
global-auto-revert-non-file-buffers)
- revert-buffer-function
- (boundp 'buffer-stale-function)
- (functionp buffer-stale-function)
- (funcall buffer-stale-function t))))
+ (funcall (or buffer-stale-function
+ #'buffer-stale--default-function)
+ t))))
eob eoblist)
(setq auto-revert-notify-modified-p nil)
(when revert
diff --git a/lisp/custom.el b/lisp/custom.el
index f2d58084e9e..3db34e4d1fb 100644
--- a/lisp/custom.el
+++ b/lisp/custom.el
@@ -49,63 +49,66 @@ Users should not set it.")
;;; The `defcustom' Macro.
-(defun custom-initialize-default (symbol value)
- "Initialize SYMBOL with VALUE.
+(defun custom-initialize-default (symbol exp)
+ "Initialize SYMBOL with EXP.
This will do nothing if symbol already has a default binding.
Otherwise, if symbol has a `saved-value' property, it will evaluate
the car of that and use it as the default binding for symbol.
-Otherwise, VALUE will be evaluated and used as the default binding for
+Otherwise, EXP will be evaluated and used as the default binding for
symbol."
- (eval `(defvar ,symbol ,(if (get symbol 'saved-value)
- (car (get symbol 'saved-value))
- value))))
+ (eval `(defvar ,symbol ,(let ((sv (get symbol 'saved-value)))
+ (if sv (car sv) exp)))))
-(defun custom-initialize-set (symbol value)
- "Initialize SYMBOL based on VALUE.
+(defun custom-initialize-set (symbol exp)
+ "Initialize SYMBOL based on EXP.
If the symbol doesn't have a default binding already,
then set it using its `:set' function (or `set-default' if it has none).
The value is either the value in the symbol's `saved-value' property,
-if any, or VALUE."
- (unless (default-boundp symbol)
- (funcall (or (get symbol 'custom-set) 'set-default)
- symbol
- (eval (if (get symbol 'saved-value)
- (car (get symbol 'saved-value))
- value)))))
-
-(defun custom-initialize-reset (symbol value)
- "Initialize SYMBOL based on VALUE.
+if any, or the value of EXP."
+ (condition-case nil
+ (default-toplevel-value symbol)
+ (error
+ (funcall (or (get symbol 'custom-set) #'set-default-toplevel-value)
+ symbol
+ (eval (let ((sv (get symbol 'saved-value)))
+ (if sv (car sv) exp)))))))
+
+(defun custom-initialize-reset (symbol exp)
+ "Initialize SYMBOL based on EXP.
Set the symbol, using its `:set' function (or `set-default' if it has none).
The value is either the symbol's current value
(as obtained using the `:get' function), if any,
or the value in the symbol's `saved-value' property if any,
-or (last of all) VALUE."
- (funcall (or (get symbol 'custom-set) 'set-default)
+or (last of all) the value of EXP."
+ (funcall (or (get symbol 'custom-set) #'set-default-toplevel-value)
symbol
- (cond ((default-boundp symbol)
- (funcall (or (get symbol 'custom-get) 'default-value)
- symbol))
- ((get symbol 'saved-value)
- (eval (car (get symbol 'saved-value))))
- (t
- (eval value)))))
-
-(defun custom-initialize-changed (symbol value)
- "Initialize SYMBOL with VALUE.
+ (condition-case nil
+ (let ((def (default-toplevel-value symbol))
+ (getter (get symbol 'custom-get)))
+ (if getter (funcall getter symbol) def))
+ (error
+ (eval (let ((sv (get symbol 'saved-value)))
+ (if sv (car sv) exp)))))))
+
+(defun custom-initialize-changed (symbol exp)
+ "Initialize SYMBOL with EXP.
Like `custom-initialize-reset', but only use the `:set' function if
not using the standard setting.
For the standard setting, use `set-default'."
- (cond ((default-boundp symbol)
- (funcall (or (get symbol 'custom-set) 'set-default)
- symbol
- (funcall (or (get symbol 'custom-get) 'default-value)
- symbol)))
- ((get symbol 'saved-value)
- (funcall (or (get symbol 'custom-set) 'set-default)
- symbol
- (eval (car (get symbol 'saved-value)))))
- (t
- (set-default symbol (eval value)))))
+ (condition-case nil
+ (let ((def (default-toplevel-value symbol)))
+ (funcall (or (get symbol 'custom-set) #'set-default-toplevel-value)
+ symbol
+ (let ((getter (get symbol 'custom-get)))
+ (if getter (funcall getter symbol) def))))
+ (error
+ (cond
+ ((get symbol 'saved-value)
+ (funcall (or (get symbol 'custom-set) #'set-default-toplevel-value)
+ symbol
+ (eval (car (get symbol 'saved-value)))))
+ (t
+ (set-default symbol (eval exp)))))))
(defvar custom-delayed-init-variables nil
"List of variables whose initialization is pending.")
diff --git a/lisp/desktop.el b/lisp/desktop.el
index 299bdc0eeb4..778c37484e1 100644
--- a/lisp/desktop.el
+++ b/lisp/desktop.el
@@ -134,6 +134,7 @@
;;; Code:
(require 'cl-lib)
+(require 'frameset)
(defvar desktop-file-version "206"
"Version number of desktop file format.
@@ -372,7 +373,7 @@ modes are restored automatically; they should not be listed here."
:group 'desktop)
(defcustom desktop-restore-frames t
- "When non-nil, save window/frame configuration to desktop file."
+ "When non-nil, save frames to desktop file."
:type 'boolean
:group 'desktop
:version "24.4")
@@ -399,7 +400,7 @@ few pixels, especially near the right / bottom borders of the screen."
:group 'desktop
:version "24.4")
-(defcustom desktop-restoring-reuses-frames t
+(defcustom desktop-restore-reuses-frames t
"If t, restoring frames reuses existing frames.
If nil, existing frames are deleted.
If `keep', existing frames are kept and not reused."
@@ -409,13 +410,6 @@ If `keep', existing frames are kept and not reused."
:group 'desktop
:version "24.4")
-(defcustom desktop-before-saving-frames-functions nil
- "Abnormal hook run before saving frames.
-Functions in this hook are called with one argument, a live frame."
- :type 'hook
- :group 'desktop
- :version "24.4")
-
(defcustom desktop-file-name-format 'absolute
"Format in which desktop file names should be saved.
Possible values are:
@@ -599,7 +593,7 @@ DIRNAME omitted or nil means use `desktop-dirname'."
"Checksum of the last auto-saved contents of the desktop file.
Used to avoid writing contents unchanged between auto-saves.")
-(defvar desktop-saved-frame-states nil
+(defvar desktop-saved-frameset nil
"Saved state of all frames.
Only valid during frame saving & restoring; intended for internal use.")
@@ -649,7 +643,10 @@ DIRNAME omitted or nil means use `desktop-dirname'."
"Empty the Desktop.
This kills all buffers except for internal ones and those with names matched by
a regular expression in the list `desktop-clear-preserve-buffers'.
-Furthermore, it clears the variables listed in `desktop-globals-to-clear'."
+Furthermore, it clears the variables listed in `desktop-globals-to-clear'.
+When called interactively and `desktop-restore-frames' is non-nil, it also
+deletes all frames except the selected one (and its minibuffer frame,
+if different)."
(interactive)
(desktop-lazy-abort)
(dolist (var desktop-globals-to-clear)
@@ -667,7 +664,21 @@ Furthermore, it clears the variables listed in `desktop-globals-to-clear'."
(unless (or (eq (aref bufname 0) ?\s) ;; Don't kill internal buffers
(string-match-p preserve-regexp bufname))
(kill-buffer buffer)))))
- (delete-other-windows))
+ (delete-other-windows)
+ (when (and desktop-restore-frames
+ ;; Non-interactive calls to desktop-clear happen before desktop-read
+ ;; which already takes care of frame restoration and deletion.
+ (called-interactively-p 'any))
+ (let* ((this (selected-frame))
+ (mini (window-frame (minibuffer-window this)))) ; in case they difer
+ (dolist (frame (sort (frame-list) #'frameset-sort-frames-for-deletion))
+ (condition-case err
+ (unless (or (eq frame this)
+ (eq frame mini)
+ (frame-parameter frame 'desktop-dont-clear))
+ (delete-frame frame))
+ (error
+ (delay-warning 'desktop (error-message-string err))))))))
;; ----------------------------------------------------------------------------
(unless noninteractive
@@ -890,223 +901,41 @@ DIRNAME must be the directory in which the desktop file will be saved."
;; ----------------------------------------------------------------------------
(defvar desktop-filter-parameters-alist
- '((background-color . desktop--filter-*-color)
- (buffer-list . t)
- (buffer-predicate . t)
- (buried-buffer-list . t)
- (desktop--font . desktop--filter-restore-desktop-parm)
- (desktop--fullscreen . desktop--filter-restore-desktop-parm)
- (desktop--height . desktop--filter-restore-desktop-parm)
- (desktop--width . desktop--filter-restore-desktop-parm)
- (font . desktop--filter-save-desktop-parm)
- (font-backend . t)
- (foreground-color . desktop--filter-*-color)
- (fullscreen . desktop--filter-save-desktop-parm)
- (height . desktop--filter-save-desktop-parm)
- (left . desktop--filter-iconified-position)
- (minibuffer . desktop--filter-minibuffer)
- (name . t)
- (outer-window-id . t)
- (parent-id . t)
- (top . desktop--filter-iconified-position)
- (tty . desktop--filter-tty*)
- (tty-type . desktop--filter-tty*)
- (width . desktop--filter-save-desktop-parm)
- (window-id . t)
- (window-system . t))
+ (append '((font-backend . t)
+ (name . t)
+ (outer-window-id . t)
+ (parent-id . t)
+ (tty . desktop--filter-tty*)
+ (tty-type . desktop--filter-tty*)
+ (window-id . t)
+ (window-system . t))
+ frameset-filter-alist)
"Alist of frame parameters and filtering functions.
-
-Each element is a cons (PARAM . FILTER), where PARAM is a parameter
-name (a symbol identifying a frame parameter), and FILTER can be t
-\(meaning the parameter is removed from the parameter list on saving
-and restoring), or a function that will be called with three args:
-
- CURRENT a cons (PARAM . VALUE), where PARAM is the one being
- filtered and VALUE is its current value
- PARAMETERS the complete alist of parameters being filtered
- SAVING non-nil if filtering before saving state, nil otherwise
-
-The FILTER function must return:
- nil CURRENT is removed from the list
- t CURRENT is left as is
- (PARAM' . VALUE') replace CURRENT with this
-
-Frame parameters not on this list are passed intact.")
-
-(defvar desktop--target-display nil
- "Either (minibuffer . VALUE) or nil.
-This refers to the current frame config being processed inside
-`frame--restore-frames' and its auxiliary functions (like filtering).
-If nil, there is no need to change the display.
-If non-nil, display parameter to use when creating the frame.
-Internal use only.")
-
-(defun desktop-switch-to-gui-p (parameters)
- "True when switching to a graphic display.
-Return t if PARAMETERS describes a text-only terminal and
-the target is a graphic display; otherwise return nil.
-Only meaningful when called from a filtering function in
-`desktop-filter-parameters-alist'."
- (and desktop--target-display ; we're switching
- (null (cdr (assq 'display parameters))) ; from a tty
- (cdr desktop--target-display))) ; to a GUI display
-
-(defun desktop-switch-to-tty-p (parameters)
- "True when switching to a text-only terminal.
-Return t if PARAMETERS describes a graphic display and
-the target is a text-only terminal; otherwise return nil.
-Only meaningful when called from a filtering function in
-`desktop-filter-parameters-alist'."
- (and desktop--target-display ; we're switching
- (cdr (assq 'display parameters)) ; from a GUI display
- (null (cdr desktop--target-display)))) ; to a tty
+Its format is identical to `frameset-filter-alist' (which see).")
(defun desktop--filter-tty* (_current parameters saving)
;; Remove tty and tty-type parameters when switching
;; to a GUI frame.
(or saving
- (not (desktop-switch-to-gui-p parameters))))
+ (not (frameset-switch-to-gui-p parameters))))
-(defun desktop--filter-*-color (current parameters saving)
- ;; Remove (foreground|background)-color parameters
- ;; when switching to a GUI frame if they denote an
- ;; "unspecified" color.
- (or saving
- (not (desktop-switch-to-gui-p parameters))
- (not (stringp (cdr current)))
- (not (string-match-p "^unspecified-[fb]g$" (cdr current)))))
-
-(defun desktop--filter-minibuffer (current _parameters saving)
- ;; When minibuffer is a window, save it as minibuffer . t
- (or (not saving)
- (if (windowp (cdr current))
- '(minibuffer . t)
- t)))
-
-(defun desktop--filter-restore-desktop-parm (current parameters saving)
- ;; When switching to a GUI frame, convert desktop--XXX parameter to XXX
- (or saving
- (not (desktop-switch-to-gui-p parameters))
- (let ((val (cdr current)))
- (if (eq val :desktop-processed)
- nil
- (cons (intern (substring (symbol-name (car current))
- 9)) ;; (length "desktop--")
- val)))))
-
-(defun desktop--filter-save-desktop-parm (current parameters saving)
- ;; When switching to a tty frame, save parameter XXX as desktop--XXX so it
- ;; can be restored in a subsequent GUI session, unless it already exists.
- (cond (saving t)
- ((desktop-switch-to-tty-p parameters)
- (let ((sym (intern (format "desktop--%s" (car current)))))
- (if (assq sym parameters)
- nil
- (cons sym (cdr current)))))
- ((desktop-switch-to-gui-p parameters)
- (let* ((dtp (assq (intern (format "desktop--%s" (car current)))
- parameters))
- (val (cdr dtp)))
- (if (eq val :desktop-processed)
- nil
- (setcdr dtp :desktop-processed)
- (cons (car current) val))))
- (t t)))
-
-(defun desktop--filter-iconified-position (_current parameters saving)
- ;; When saving an iconified frame, top & left are meaningless,
- ;; so remove them to allow restoring to a default position.
- (not (and saving (eq (cdr (assq 'visibility parameters)) 'icon))))
-
-(defun desktop-restore-in-original-display-p ()
- "True if saved frames' displays should be honored."
- (cond ((daemonp) t)
- ((eq system-type 'windows-nt) nil)
- (t (null desktop-restore-in-current-display))))
-
-(defun desktop--filter-frame-parms (parameters saving)
- "Filter frame parameters and return filtered list.
-PARAMETERS is a parameter alist as returned by `frame-parameters'.
-If SAVING is non-nil, filtering is happening before saving frame state;
-otherwise, filtering is being done before restoring frame state.
-Parameters are filtered according to the setting of
-`desktop-filter-parameters-alist' (which see).
-Internal use only."
- (let ((filtered nil))
- (dolist (param parameters)
- (let ((filter (cdr (assq (car param) desktop-filter-parameters-alist)))
- this)
- (cond (;; no filter: pass param
- (null filter)
- (push param filtered))
- (;; filter = t; skip param
- (eq filter t))
- (;; filter func returns nil: skip param
- (null (setq this (funcall filter param parameters saving))))
- (;; filter func returns t: pass param
- (eq this t)
- (push param filtered))
- (;; filter func returns a new param: use it
- t
- (push this filtered)))))
- ;; Set the display parameter after filtering, so that filter functions
- ;; have access to its original value.
- (when desktop--target-display
- (let ((display (assq 'display filtered)))
- (if display
- (setcdr display (cdr desktop--target-display))
- (push desktop--target-display filtered))))
- filtered))
-
-(defun desktop--process-minibuffer-frames (frames)
- ;; Adds a desktop--mini parameter to frames
- ;; desktop--mini is a list (MINIBUFFER NUMBER DEFAULT?) where
- ;; MINIBUFFER t if the frame (including minibuffer-only) owns a minibuffer
- ;; NUMBER if MINIBUFFER = t, an ID for the frame; if nil, the ID of
- ;; the frame containing the minibuffer used by this frame
- ;; DEFAULT? if t, this frame is the value of default-minibuffer-frame
- (let ((count 0))
- ;; Reset desktop--mini for all frames
- (dolist (frame (frame-list))
- (set-frame-parameter frame 'desktop--mini nil))
- ;; Number all frames with its own minibuffer
- (dolist (frame (minibuffer-frame-list))
- (set-frame-parameter frame 'desktop--mini
- (list t
- (cl-incf count)
- (eq frame default-minibuffer-frame))))
- ;; Now link minibufferless frames with their minibuffer frames
- (dolist (frame frames)
- (unless (frame-parameter frame 'desktop--mini)
- (let ((mb-frame (window-frame (minibuffer-window frame))))
- ;; Frames whose minibuffer frame has been filtered out will have
- ;; desktop--mini = nil, so desktop-restore-frames will restore them
- ;; according to their minibuffer parameter. Set up desktop--mini
- ;; for the rest.
- (when (memq mb-frame frames)
- (set-frame-parameter frame 'desktop--mini
- (list nil
- (cl-second (frame-parameter mb-frame 'desktop--mini))
- nil))))))))
-
-(defun desktop-save-frames ()
- "Save frame state in `desktop-saved-frame-states'.
-Runs the hook `desktop-before-saving-frames-functions'.
+(defun desktop--check-dont-save (frame)
+ (not (frame-parameter frame 'desktop-dont-save)))
+
+(defconst desktop--app-id `(desktop . ,desktop-file-version))
+
+(defun desktop-save-frameset ()
+ "Save the state of existing frames in `desktop-saved-frameset'.
Frames with a non-nil `desktop-dont-save' parameter are not saved."
- (setq desktop-saved-frame-states
+ (setq desktop-saved-frameset
(and desktop-restore-frames
- (let ((frames (cl-delete-if
- (lambda (frame)
- (run-hook-with-args 'desktop-before-saving-frames-functions frame)
- (frame-parameter frame 'desktop-dont-save))
- (frame-list))))
- ;; In case some frame was deleted by a hook function
- (setq frames (cl-delete-if-not #'frame-live-p frames))
- (desktop--process-minibuffer-frames frames)
- (mapcar (lambda (frame)
- (cons (desktop--filter-frame-parms (frame-parameters frame) t)
- (window-state-get (frame-root-window frame) t)))
- frames)))))
+ (let ((name (concat user-login-name "@" system-name
+ (format-time-string " %Y-%m-%d %T"))))
+ (frameset-save nil
+ :filters desktop-filter-parameters-alist
+ :predicate #'desktop--check-dont-save
+ :properties (list :app desktop--app-id
+ :name name))))))
;;;###autoload
(defun desktop-save (dirname &optional release auto-save)
@@ -1148,11 +977,11 @@ and don't save the buffer if they are the same."
(insert "\n;; Global section:\n")
;; Called here because we save the window/frame state as a global
;; variable for compatibility with previous Emacsen.
- (desktop-save-frames)
- (unless (memq 'desktop-saved-frame-states desktop-globals-to-save)
- (desktop-outvar 'desktop-saved-frame-states))
+ (desktop-save-frameset)
+ (unless (memq 'desktop-saved-frameset desktop-globals-to-save)
+ (desktop-outvar 'desktop-saved-frameset))
(mapc (function desktop-outvar) desktop-globals-to-save)
- (setq desktop-saved-frame-states nil) ; after saving desktop-globals-to-save
+ (setq desktop-saved-frameset nil) ; after saving desktop-globals-to-save
(when (memq 'kill-ring desktop-globals-to-save)
(insert
"(setq kill-ring-yank-pointer (nthcdr "
@@ -1210,319 +1039,26 @@ This function also sets `desktop-dirname' to nil."
(defvar desktop-lazy-timer nil)
;; ----------------------------------------------------------------------------
-(defvar desktop--reuse-list nil
- "Internal use only.")
-
-(defun desktop--compute-pos (value left/top right/bottom)
- (pcase value
- (`(+ ,val) (+ left/top val))
- (`(- ,val) (+ right/bottom val))
- (val val)))
-
-(defun desktop--move-onscreen (frame)
- "If FRAME is offscreen, move it back onscreen and, if necessary, resize it.
-When forced onscreen, frames wider than the monitor's workarea are converted
-to fullwidth, and frames taller than the workarea are converted to fullheight.
-NOTE: This only works for non-iconified frames."
- (pcase-let* ((`(,left ,top ,width ,height) (cl-cdadr (frame-monitor-attributes frame)))
- (right (+ left width -1))
- (bottom (+ top height -1))
- (fr-left (desktop--compute-pos (frame-parameter frame 'left) left right))
- (fr-top (desktop--compute-pos (frame-parameter frame 'top) top bottom))
- (ch-width (frame-char-width frame))
- (ch-height (frame-char-height frame))
- (fr-width (max (frame-pixel-width frame) (* ch-width (frame-width frame))))
- (fr-height (max (frame-pixel-height frame) (* ch-height (frame-height frame))))
- (fr-right (+ fr-left fr-width -1))
- (fr-bottom (+ fr-top fr-height -1)))
- (when (pcase desktop-restore-forces-onscreen
- ;; Any corner is outside the screen.
- (`all (or (< fr-bottom top) (> fr-bottom bottom)
- (< fr-left left) (> fr-left right)
- (< fr-right left) (> fr-right right)
- (< fr-top top) (> fr-top bottom)))
- ;; Displaced to the left, right, above or below the screen.
- (`t (or (> fr-left right)
- (< fr-right left)
- (> fr-top bottom)
- (< fr-bottom top)))
- (_ nil))
- (let ((fullwidth (> fr-width width))
- (fullheight (> fr-height height))
- (params nil))
- ;; Position frame horizontally.
- (cond (fullwidth
- (push `(left . ,left) params))
- ((> fr-right right)
- (push `(left . ,(+ left (- width fr-width))) params))
- ((< fr-left left)
- (push `(left . ,left) params)))
- ;; Position frame vertically.
- (cond (fullheight
- (push `(top . ,top) params))
- ((> fr-bottom bottom)
- (push `(top . ,(+ top (- height fr-height))) params))
- ((< fr-top top)
- (push `(top . ,top) params)))
- ;; Compute fullscreen state, if required.
- (when (or fullwidth fullheight)
- (push (cons 'fullscreen
- (cond ((not fullwidth) 'fullheight)
- ((not fullheight) 'fullwidth)
- (t 'maximized)))
- params))
- ;; Finally, move the frame back onscreen.
- (when params
- (modify-frame-parameters frame params))))))
-
-(defun desktop--find-frame (predicate display &rest args)
- "Find a suitable frame in `desktop--reuse-list'.
-Look through frames whose display property matches DISPLAY and
-return the first one for which (PREDICATE frame ARGS) returns t.
-If PREDICATE is nil, it is always satisfied. Internal use only.
-This is an auxiliary function for `desktop--select-frame'."
- (cl-find-if (lambda (frame)
- (and (equal (frame-parameter frame 'display) display)
- (or (null predicate)
- (apply predicate frame args))))
- desktop--reuse-list))
-
-(defun desktop--select-frame (display frame-cfg)
- "Look for an existing frame to reuse.
-DISPLAY is the display where the frame will be shown, and FRAME-CFG
-is the parameter list of the frame being restored. Internal use only."
- (if (eq desktop-restoring-reuses-frames t)
- (let ((frame nil)
- mini)
- ;; There are no fancy heuristics there. We could implement some
- ;; based on frame size and/or position, etc., but it is not clear
- ;; that any "gain" (in the sense of reduced flickering, etc.) is
- ;; worth the added complexity. In fact, the code below mainly
- ;; tries to work nicely when M-x desktop-read is used after a desktop
- ;; session has already been loaded. The other main use case, which
- ;; is the initial desktop-read upon starting Emacs, should usually
- ;; only have one, or very few, frame(s) to reuse.
- (cond ((null display)
- ;; When the target is tty, every existing frame is reusable.
- (setq frame (desktop--find-frame nil display)))
- ((car (setq mini (cdr (assq 'desktop--mini frame-cfg))))
- ;; If the frame has its own minibuffer, let's see whether
- ;; that frame has already been loaded (which can happen after
- ;; M-x desktop-read).
- (setq frame (desktop--find-frame
- (lambda (f m)
- (equal (frame-parameter f 'desktop--mini) m))
- display mini))
- ;; If it has not been loaded, and it is not a minibuffer-only frame,
- ;; let's look for an existing non-minibuffer-only frame to reuse.
- (unless (or frame (eq (cdr (assq 'minibuffer frame-cfg)) 'only))
- (setq frame (desktop--find-frame
- (lambda (f)
- (let ((w (frame-parameter f 'minibuffer)))
- (and (window-live-p w)
- (window-minibuffer-p w)
- (eq (window-frame w) f))))
- display))))
- (mini
- ;; For minibufferless frames, check whether they already exist,
- ;; and that they are linked to the right minibuffer frame.
- (setq frame (desktop--find-frame
- (lambda (f n)
- (pcase-let (((and m `(,hasmini ,num))
- (frame-parameter f 'desktop--mini)))
- (and m
- (null hasmini)
- (= num n)
- (equal (cl-second (frame-parameter
- (window-frame (minibuffer-window f))
- 'desktop--mini))
- n))))
- display (cl-second mini))))
- (t
- ;; Default to just finding a frame in the same display.
- (setq frame (desktop--find-frame nil display))))
- ;; If found, remove from the list.
- (when frame
- (setq desktop--reuse-list (delq frame desktop--reuse-list)))
- frame)
- nil))
-
-(defun desktop--make-frame (frame-cfg window-cfg)
- "Set up a frame according to its saved state.
-That means either creating a new frame or reusing an existing one.
-FRAME-CFG is the parameter list of the new frame; WINDOW-CFG is
-its window state. Internal use only."
- (let* ((fullscreen (cdr (assq 'fullscreen frame-cfg)))
- (lines (assq 'tool-bar-lines frame-cfg))
- (filtered-cfg (desktop--filter-frame-parms frame-cfg nil))
- (display (cdr (assq 'display filtered-cfg))) ;; post-filtering
- alt-cfg frame)
-
- ;; This works around bug#14795 (or feature#14795, if not a bug :-)
- (setq filtered-cfg (assq-delete-all 'tool-bar-lines filtered-cfg))
- (push '(tool-bar-lines . 0) filtered-cfg)
-
- (when fullscreen
- ;; Currently Emacs has the limitation that it does not record the size
- ;; and position of a frame before maximizing it, so we cannot save &
- ;; restore that info. Instead, when restoring, we resort to creating
- ;; invisible "fullscreen" frames of default size and then maximizing them
- ;; (and making them visible) which at least is somewhat user-friendly
- ;; when these frames are later de-maximized.
- (let ((width (and (eq fullscreen 'fullheight) (cdr (assq 'width filtered-cfg))))
- (height (and (eq fullscreen 'fullwidth) (cdr (assq 'height filtered-cfg))))
- (visible (assq 'visibility filtered-cfg)))
- (setq filtered-cfg (cl-delete-if (lambda (p)
- (memq p '(visibility fullscreen width height)))
- filtered-cfg :key #'car))
- (when width
- (setq filtered-cfg (append `((user-size . t) (width . ,width))
- filtered-cfg)))
- (when height
- (setq filtered-cfg (append `((user-size . t) (height . ,height))
- filtered-cfg)))
- ;; These are parameters to apply after creating/setting the frame.
- (push visible alt-cfg)
- (push (cons 'fullscreen fullscreen) alt-cfg)))
-
- ;; Time to find or create a frame an apply the big bunch of parameters.
- ;; If a frame needs to be created and it falls partially or wholly offscreen,
- ;; sometimes it gets "pushed back" onscreen; however, moving it afterwards is
- ;; allowed. So we create the frame as invisible and then reapply the full
- ;; parameter list (including position and size parameters).
- (setq frame (or (desktop--select-frame display filtered-cfg)
- (make-frame-on-display display
- (cons '(visibility)
- (cl-loop
- for param in '(left top width height minibuffer)
- collect (assq param filtered-cfg))))))
- (modify-frame-parameters frame
- (if (eq (frame-parameter frame 'fullscreen) fullscreen)
- ;; Workaround for bug#14949
- (assq-delete-all 'fullscreen filtered-cfg)
- filtered-cfg))
-
- ;; If requested, force frames to be onscreen.
- (when (and desktop-restore-forces-onscreen
- ;; FIXME: iconified frames should be checked too,
- ;; but it is impossible without deiconifying them.
- (not (eq (frame-parameter frame 'visibility) 'icon)))
- (desktop--move-onscreen frame))
-
- ;; Let's give the finishing touches (visibility, tool-bar, maximization).
- (when lines (push lines alt-cfg))
- (when alt-cfg (modify-frame-parameters frame alt-cfg))
- ;; Now restore window state.
- (window-state-put window-cfg (frame-root-window frame) 'safe)
- frame))
-
-(defun desktop--sort-states (state1 state2)
- ;; Order: default minibuffer frame
- ;; other frames with minibuffer, ascending ID
- ;; minibufferless frames, ascending ID
- (pcase-let ((`(,_p1 ,hasmini1 ,num1 ,default1) (assq 'desktop--mini (car state1)))
- (`(,_p2 ,hasmini2 ,num2 ,default2) (assq 'desktop--mini (car state2))))
- (cond (default1 t)
- (default2 nil)
- ((eq hasmini1 hasmini2) (< num1 num2))
- (t hasmini1))))
-
-(defun desktop-restoring-frames-p ()
- "True if calling `desktop-restore-frames' will actually restore frames."
- (and desktop-restore-frames desktop-saved-frame-states t))
-
-(defun desktop-restore-frames ()
- "Restore window/frame configuration.
-This function depends on the value of `desktop-saved-frame-states'
+(defun desktop-restoring-frameset-p ()
+ "True if calling `desktop-restore-frameset' will actually restore it."
+ (and desktop-restore-frames desktop-saved-frameset t))
+
+(defun desktop-restore-frameset ()
+ "Restore the state of a set of frames.
+This function depends on the value of `desktop-saved-frameset'
being set (usually, by reading it from the desktop)."
- (when (desktop-restoring-frames-p)
- (let* ((frame-mb-map nil) ;; Alist of frames with their own minibuffer
- (delete-saved (eq desktop-restore-in-current-display 'delete))
- (forcing (not (desktop-restore-in-original-display-p)))
- (target (and forcing (cons 'display (frame-parameter nil 'display)))))
-
- ;; Sorting saved states allows us to easily restore minibuffer-owning frames
- ;; before minibufferless ones.
- (setq desktop-saved-frame-states (sort desktop-saved-frame-states
- #'desktop--sort-states))
- ;; Potentially all existing frames are reusable. Later we will decide which ones
- ;; to reuse, and how to deal with any leftover.
- (setq desktop--reuse-list (frame-list))
-
- (dolist (state desktop-saved-frame-states)
- (condition-case err
- (pcase-let* ((`(,frame-cfg . ,window-cfg) state)
- ((and d-mini `(,hasmini ,num ,default))
- (cdr (assq 'desktop--mini frame-cfg)))
- (frame nil) (to-tty nil))
- ;; Only set target if forcing displays and the target display is different.
- (if (or (not forcing)
- (equal target (or (assq 'display frame-cfg) '(display . nil))))
- (setq desktop--target-display nil)
- (setq desktop--target-display target
- to-tty (null (cdr target))))
- ;; Time to restore frames and set up their minibuffers as they were.
- ;; We only skip a frame (thus deleting it) if either:
- ;; - we're switching displays, and the user chose the option to delete, or
- ;; - we're switching to tty, and the frame to restore is minibuffer-only.
- (unless (and desktop--target-display
- (or delete-saved
- (and to-tty
- (eq (cdr (assq 'minibuffer frame-cfg)) 'only))))
-
- ;; Restore minibuffers. Some of this stuff could be done in a filter
- ;; function, but it would be messy because restoring minibuffers affects
- ;; global state; it's best to do it here than add a bunch of global
- ;; variables to pass info back-and-forth to/from the filter function.
- (cond
- ((null d-mini)) ;; No desktop--mini. Process as normal frame.
- (to-tty) ;; Ignore minibuffer stuff and process as normal frame.
- (hasmini ;; Frame has minibuffer (or it is minibuffer-only).
- (when (eq (cdr (assq 'minibuffer frame-cfg)) 'only)
- (setq frame-cfg (append '((tool-bar-lines . 0) (menu-bar-lines . 0))
- frame-cfg))))
- (t ;; Frame depends on other frame's minibuffer window.
- (let ((mb-frame (cdr (assq num frame-mb-map))))
- (unless (frame-live-p mb-frame)
- (error "Minibuffer frame %s not found" num))
- (let ((mb-param (assq 'minibuffer frame-cfg))
- (mb-window (minibuffer-window mb-frame)))
- (unless (and (window-live-p mb-window)
- (window-minibuffer-p mb-window))
- (error "Not a minibuffer window %s" mb-window))
- (if mb-param
- (setcdr mb-param mb-window)
- (push (cons 'minibuffer mb-window) frame-cfg))))))
- ;; OK, we're ready at last to create (or reuse) a frame and
- ;; restore the window config.
- (setq frame (desktop--make-frame frame-cfg window-cfg))
- ;; Set default-minibuffer if required.
- (when default (setq default-minibuffer-frame frame))
- ;; Store NUM/frame to assign to minibufferless frames.
- (when hasmini (push (cons num frame) frame-mb-map))))
- (error
- (delay-warning 'desktop (error-message-string err) :error))))
-
- ;; In case we try to delete the initial frame, we want to make sure that
- ;; other frames are already visible (discussed in thread for bug#14841).
- (sit-for 0 t)
-
- ;; Delete remaining frames, but do not fail if some resist being deleted.
- (unless (eq desktop-restoring-reuses-frames 'keep)
- (dolist (frame desktop--reuse-list)
- (condition-case err
- (delete-frame frame)
- (error
- (delay-warning 'desktop (error-message-string err))))))
- (setq desktop--reuse-list nil)
- ;; Make sure there's at least one visible frame, and select it.
- (unless (or (daemonp)
- (cl-find-if #'frame-visible-p (frame-list)))
- (let ((visible (if (frame-live-p default-minibuffer-frame)
- default-minibuffer-frame
- (car (frame-list)))))
- (make-frame-visible visible)
- (select-frame-set-input-focus visible))))))
+ (when (desktop-restoring-frameset-p)
+ (frameset-restore desktop-saved-frameset
+ :filters desktop-filter-parameters-alist
+ :reuse-frames desktop-restore-reuses-frames
+ :force-display desktop-restore-in-current-display
+ :force-onscreen desktop-restore-forces-onscreen)))
+
+;; Just to silence the byte compiler.
+;; Dynamicaly bound in `desktop-read'.
+(defvar desktop-first-buffer)
+(defvar desktop-buffer-ok-count)
+(defvar desktop-buffer-fail-count)
;;;###autoload
(defun desktop-read (&optional dirname)
@@ -1583,7 +1119,7 @@ Using it may cause conflicts. Use it anyway? " owner)))))
(file-error (message "Couldn't record use of desktop file")
(sit-for 1))))
- (unless (desktop-restoring-frames-p)
+ (unless (desktop-restoring-frameset-p)
;; `desktop-create-buffer' puts buffers at end of the buffer list.
;; We want buffers existing prior to evaluating the desktop (and
;; not reused) to be placed at the end of the buffer list, so we
@@ -1593,9 +1129,14 @@ Using it may cause conflicts. Use it anyway? " owner)))))
(switch-to-buffer (car (buffer-list))))
(run-hooks 'desktop-delay-hook)
(setq desktop-delay-hook nil)
- (desktop-restore-frames)
+ (desktop-restore-frameset)
(run-hooks 'desktop-after-read-hook)
- (message "Desktop: %d buffer%s restored%s%s."
+ (message "Desktop: %s%d buffer%s restored%s%s."
+ (if desktop-saved-frameset
+ (let ((fn (length (frameset-states desktop-saved-frameset))))
+ (format "%d frame%s, "
+ fn (if (= fn 1) "" "s")))
+ "")
desktop-buffer-ok-count
(if (= 1 desktop-buffer-ok-count) "" "s")
(if (< 0 desktop-buffer-fail-count)
@@ -1605,7 +1146,7 @@ Using it may cause conflicts. Use it anyway? " owner)))))
(format ", %d to restore lazily"
(length desktop-buffer-args-list))
""))
- (unless (desktop-restoring-frames-p)
+ (unless (desktop-restoring-frameset-p)
;; Bury the *Messages* buffer to not reshow it when burying
;; the buffer we switched to above.
(when (buffer-live-p (get-buffer "*Messages*"))
@@ -1618,6 +1159,7 @@ Using it may cause conflicts. Use it anyway? " owner)))))
(walk-window-tree (lambda (window)
(set-window-prev-buffers window nil)
(set-window-next-buffers window nil))))
+ (setq desktop-saved-frameset nil)
t))
;; No desktop file found.
(desktop-clear)
@@ -1743,14 +1285,6 @@ integer, start a new timer to call `desktop-auto-save' in that many seconds."
;; Create a buffer, load its file, set its mode, ...;
;; called from Desktop file only.
-;; Just to silence the byte compiler.
-
-(defvar desktop-first-buffer) ; Dynamically bound in `desktop-read'
-
-;; Bound locally in `desktop-read'.
-(defvar desktop-buffer-ok-count)
-(defvar desktop-buffer-fail-count)
-
(defun desktop-create-buffer
(file-version
buffer-filename
diff --git a/lisp/emacs-lisp/advice.el b/lisp/emacs-lisp/advice.el
index 3d03e894534..eb1d63e788b 100644
--- a/lisp/emacs-lisp/advice.el
+++ b/lisp/emacs-lisp/advice.el
@@ -2280,7 +2280,6 @@ For that it has to be fbound with a non-autoload definition."
(defun ad-compile-function (function)
"Byte-compile the assembled advice function."
(require 'bytecomp)
- (require 'warnings) ;To define warning-suppress-types before we let-bind it.
(let ((byte-compile-warnings byte-compile-warnings)
;; Don't pop up windows showing byte-compiler warnings.
(warning-suppress-types '((bytecomp))))
diff --git a/lisp/emacs-lisp/easy-mmode.el b/lisp/emacs-lisp/easy-mmode.el
index ed10080cc35..f8fb65be4d3 100644
--- a/lisp/emacs-lisp/easy-mmode.el
+++ b/lisp/emacs-lisp/easy-mmode.el
@@ -419,7 +419,7 @@ See `%s' for more information on %s."
;; Go through existing buffers.
(dolist (buf (buffer-list))
(with-current-buffer buf
- (if ,global-mode (,turn-on) (when ,mode (,mode -1))))))
+ (if ,global-mode (funcall #',turn-on) (when ,mode (,mode -1))))))
;; Autoloading define-globalized-minor-mode autoloads everything
;; up-to-here.
@@ -449,8 +449,8 @@ See `%s' for more information on %s."
(if ,mode
(progn
(,mode -1)
- (,turn-on))
- (,turn-on))))
+ (funcall #',turn-on))
+ (funcall #',turn-on))))
(setq ,MODE-major-mode major-mode)))))
(put ',MODE-enable-in-buffers 'definition-name ',global-mode)
@@ -589,7 +589,7 @@ BODY is executed after moving to the destination location."
(prog1 (or (< (- (point-max) (point-min)) (buffer-size)))
(widen))))
,body
- (when was-narrowed (,narrowfun)))))))
+ (when was-narrowed (funcall #',narrowfun)))))))
(unless name (setq name base-name))
`(progn
(defun ,next-sym (&optional count)
@@ -601,13 +601,13 @@ BODY is executed after moving to the destination location."
,(funcall when-narrowed
`(if (not (re-search-forward ,re nil t count))
(if (looking-at ,re)
- (goto-char (or ,(if endfun `(,endfun)) (point-max)))
+ (goto-char (or ,(if endfun `(funcall #',endfun)) (point-max)))
(user-error "No next %s" ,name))
(goto-char (match-beginning 0))
(when (and (eq (current-buffer) (window-buffer (selected-window)))
(called-interactively-p 'interactive))
(let ((endpt (or (save-excursion
- ,(if endfun `(,endfun)
+ ,(if endfun `(funcall #',endfun)
`(re-search-forward ,re nil t 2)))
(point-max))))
(unless (pos-visible-in-window-p endpt nil t)
diff --git a/lisp/emacs-lisp/lisp-mode.el b/lisp/emacs-lisp/lisp-mode.el
index af30deca4cc..3cbd6d4a585 100644
--- a/lisp/emacs-lisp/lisp-mode.el
+++ b/lisp/emacs-lisp/lisp-mode.el
@@ -110,7 +110,9 @@ It has `lisp-mode-abbrev-table' as its parent."
"define-compiler-macro" "define-modify-macro"
"defsetf" "define-setf-expander"
"define-method-combination"
- "defgeneric" "defmethod") t))
+ "defgeneric" "defmethod"
+ "cl-defun" "cl-defsubst" "cl-defmacro"
+ "cl-define-compiler-macro") t))
"\\s-+\\(\\(\\sw\\|\\s_\\)+\\)"))
2)
(list (purecopy "Variables")
@@ -132,7 +134,8 @@ It has `lisp-mode-abbrev-table' as its parent."
(regexp-opt
'("defgroup" "deftheme" "deftype" "defstruct"
"defclass" "define-condition" "define-widget"
- "defface" "defpackage") t))
+ "defface" "defpackage" "cl-deftype"
+ "cl-defstruct") t))
"\\s-+'?\\(\\(\\sw\\|\\s_\\)+\\)"))
2))
diff --git a/lisp/emacs-lisp/package.el b/lisp/emacs-lisp/package.el
index 68d2880d33e..add73fd4bde 100644
--- a/lisp/emacs-lisp/package.el
+++ b/lisp/emacs-lisp/package.el
@@ -296,7 +296,7 @@ contrast, `package-user-dir' contains packages for personal use."
(:constructor
package-desc-from-define
(name-string version-string &optional summary requirements
- &key kind archive
+ &key kind archive &allow-other-keys
&aux
(name (intern name-string))
(version (version-to-list version-string))
diff --git a/lisp/emacs-lisp/re-builder.el b/lisp/emacs-lisp/re-builder.el
index 9b73bea065f..d463bfac412 100644
--- a/lisp/emacs-lisp/re-builder.el
+++ b/lisp/emacs-lisp/re-builder.el
@@ -319,7 +319,7 @@ Except for Lisp syntax this is the same as `reb-regexp'.")
(eq 'color
;; emacs/xemacs compatibility
(if (fboundp 'frame-parameter)
- (frame-parameter (selected-frame) 'display-type)
+ (frame-parameter nil 'display-type)
(if (fboundp 'frame-property)
(frame-property (selected-frame) 'display-type)))))
diff --git a/lisp/files.el b/lisp/files.el
index 10d66e0b2e0..526c535450b 100644
--- a/lisp/files.el
+++ b/lisp/files.el
@@ -1,4 +1,4 @@
-;;; files.el --- file input and output commands for Emacs
+;;; files.el --- file input and output commands for Emacs -*- lexical-binding:t -*-
;; Copyright (C) 1985-1987, 1992-2013 Free Software Foundation, Inc.
@@ -3645,20 +3645,19 @@ is found. Returns the new class name."
(with-temp-buffer
;; This is with-demoted-errors, but we want to mention dir-locals
;; in any error message.
- (let (err)
- (condition-case err
- (progn
- (insert-file-contents file)
- (unless (zerop (buffer-size))
- (let* ((dir-name (file-name-directory file))
- (class-name (intern dir-name))
- (variables (let ((read-circle nil))
- (read (current-buffer)))))
- (dir-locals-set-class-variables class-name variables)
- (dir-locals-set-directory-class dir-name class-name
- (nth 5 (file-attributes file)))
- class-name)))
- (error (message "Error reading dir-locals: %S" err) nil)))))
+ (condition-case err
+ (progn
+ (insert-file-contents file)
+ (unless (zerop (buffer-size))
+ (let* ((dir-name (file-name-directory file))
+ (class-name (intern dir-name))
+ (variables (let ((read-circle nil))
+ (read (current-buffer)))))
+ (dir-locals-set-class-variables class-name variables)
+ (dir-locals-set-directory-class dir-name class-name
+ (nth 5 (file-attributes file)))
+ class-name)))
+ (error (message "Error reading dir-locals: %S" err) nil))))
(defcustom enable-remote-dir-locals nil
"Non-nil means dir-local variables will be applied to remote files."
@@ -3666,6 +3665,8 @@ is found. Returns the new class name."
:type 'boolean
:group 'find-file)
+(defvar hack-dir-local-variables--warned-coding nil)
+
(defun hack-dir-local-variables ()
"Read per-directory local variables for the current buffer.
Store the directory-local variables in `dir-local-variables-alist'
@@ -3697,8 +3698,10 @@ This does nothing if either `enable-local-variables' or
(when variables
(dolist (elt variables)
(if (eq (car elt) 'coding)
- (display-warning :warning
- "Coding cannot be specified by dir-locals")
+ (unless hack-dir-local-variables--warned-coding
+ (setq hack-dir-local-variables--warned-coding t)
+ (display-warning :warning
+ "Coding cannot be specified by dir-locals"))
(unless (memq (car elt) '(eval mode))
(setq dir-local-variables-alist
(assq-delete-all (car elt) dir-local-variables-alist)))
@@ -4145,9 +4148,9 @@ FILENAME defaults to `buffer-file-name'."
(file-name-sans-extension
(file-name-nondirectory (or filename (buffer-file-name)))))
-(defcustom make-backup-file-name-function nil
+(defcustom make-backup-file-name-function
+ #'make-backup-file-name--default-function
"A function to use instead of the default `make-backup-file-name'.
-A value of nil gives the default `make-backup-file-name' behavior.
This could be buffer-local to do something special for specific
files. If you define it, you may need to change `backup-file-name-p'
@@ -4155,8 +4158,7 @@ and `file-name-sans-versions' too.
See also `backup-directory-alist'."
:group 'backup
- :type '(choice (const :tag "Default" nil)
- (function :tag "Your function")))
+ :type '(function :tag "Your function"))
(defcustom backup-directory-alist nil
"Alist of filename patterns and backup directory names.
@@ -4216,24 +4218,26 @@ Checks for files in `temporary-file-directory',
Normally this will just be the file's name with `~' appended.
Customization hooks are provided as follows.
-If the variable `make-backup-file-name-function' is non-nil, its value
-should be a function which will be called with FILE as its argument;
-the resulting name is used.
+The value of `make-backup-file-name-function' should be a function which
+will be called with FILE as its argument; the resulting name is used.
-Otherwise a match for FILE is sought in `backup-directory-alist'; see
+By default, a match for FILE is sought in `backup-directory-alist'; see
the documentation of that variable. If the directory for the backup
doesn't exist, it is created."
- (if make-backup-file-name-function
- (funcall make-backup-file-name-function file)
- (if (and (eq system-type 'ms-dos)
- (not (msdos-long-file-names)))
- (let ((fn (file-name-nondirectory file)))
- (concat (file-name-directory file)
- (or (and (string-match "\\`[^.]+\\'" fn)
- (concat (match-string 0 fn) ".~"))
- (and (string-match "\\`[^.]+\\.\\(..?\\)?" fn)
- (concat (match-string 0 fn) "~")))))
- (concat (make-backup-file-name-1 file) "~"))))
+ (funcall (or make-backup-file-name-function
+ #'make-backup-file-name--default-function)
+ file))
+
+(defun make-backup-file-name--default-function (file)
+ (if (and (eq system-type 'ms-dos)
+ (not (msdos-long-file-names)))
+ (let ((fn (file-name-nondirectory file)))
+ (concat (file-name-directory file)
+ (or (and (string-match "\\`[^.]+\\'" fn)
+ (concat (match-string 0 fn) ".~"))
+ (and (string-match "\\`[^.]+\\.\\(..?\\)?" fn)
+ (concat (match-string 0 fn) "~")))))
+ (concat (make-backup-file-name-1 file) "~")))
(defun make-backup-file-name-1 (file)
"Subroutine of `make-backup-file-name' and `find-backup-file-name'."
@@ -5254,14 +5258,20 @@ It also has access to the `preserve-modes' argument of `revert-buffer'
via the `revert-buffer-preserve-modes' dynamic variable.")
(put 'revert-buffer-insert-file-contents-function 'permanent-local t)
-(defvar revert-buffer-insert-file-contents-function nil
+(defvar revert-buffer-insert-file-contents-function
+ #'revert-buffer-insert-file-contents--default-function
"Function to use to insert contents when reverting this buffer.
Gets two args, first the nominal file name to use,
and second, t if reading the auto-save file.
The function you specify is responsible for updating (or preserving) point.")
-(defvar buffer-stale-function nil
+(defun buffer-stale--default-function (&optional _noconfirm)
+ (and buffer-file-name
+ (file-readable-p buffer-file-name)
+ (not (verify-visited-file-modtime (current-buffer)))))
+
+(defvar buffer-stale-function #'buffer-stale--default-function
"Function to check whether a non-file buffer needs reverting.
This should be a function with one optional argument NOCONFIRM.
Auto Revert Mode passes t for NOCONFIRM. The function should return
@@ -5382,62 +5392,11 @@ non-nil, it is called instead of rereading visited file contents."
(local-hook (when (local-variable-p 'after-revert-hook)
after-revert-hook))
(inhibit-read-only t))
- (cond
- (revert-buffer-insert-file-contents-function
- (unless (eq buffer-undo-list t)
- ;; Get rid of all undo records for this buffer.
- (setq buffer-undo-list nil))
- ;; Don't make undo records for the reversion.
- (let ((buffer-undo-list t))
- (funcall revert-buffer-insert-file-contents-function
- file-name auto-save-p)))
- ((not (file-exists-p file-name))
- (error (if buffer-file-number
- "File %s no longer exists!"
- "Cannot revert nonexistent file %s")
- file-name))
- ((not (file-readable-p file-name))
- (error (if buffer-file-number
- "File %s no longer readable!"
- "Cannot revert unreadable file %s")
- file-name))
- (t
- ;; Bind buffer-file-name to nil
- ;; so that we don't try to lock the file.
- (let ((buffer-file-name nil))
- (or auto-save-p
- (unlock-buffer)))
- (widen)
- (let ((coding-system-for-read
- ;; Auto-saved file should be read by Emacs's
- ;; internal coding.
- (if auto-save-p 'auto-save-coding
- (or coding-system-for-read
- (and
- buffer-file-coding-system-explicit
- (car buffer-file-coding-system-explicit))))))
- (if (and (not enable-multibyte-characters)
- coding-system-for-read
- (not (memq (coding-system-base
- coding-system-for-read)
- '(no-conversion raw-text))))
- ;; As a coding system suitable for multibyte
- ;; buffer is specified, make the current
- ;; buffer multibyte.
- (set-buffer-multibyte t))
-
- ;; This force after-insert-file-set-coding
- ;; (called from insert-file-contents) to set
- ;; buffer-file-coding-system to a proper value.
- (kill-local-variable 'buffer-file-coding-system)
-
- ;; Note that this preserves point in an intelligent way.
- (if revert-buffer-preserve-modes
- (let ((buffer-file-format buffer-file-format))
- (insert-file-contents file-name (not auto-save-p)
- nil nil t))
- (insert-file-contents file-name (not auto-save-p)
- nil nil t)))))
+ ;; FIXME: Throw away undo-log when preserve-modes is nil?
+ (funcall
+ (or revert-buffer-insert-file-contents-function
+ #'revert-buffer-insert-file-contents--default-function)
+ file-name auto-save-p)
;; Recompute the truename in case changes in symlinks
;; have changed the truename.
(setq buffer-file-truename
@@ -5452,6 +5411,56 @@ non-nil, it is called instead of rereading visited file contents."
(run-hooks 'revert-buffer-internal-hook))
t)))))
+(defun revert-buffer-insert-file-contents--default-function (file-name auto-save-p)
+ (cond
+ ((not (file-exists-p file-name))
+ (error (if buffer-file-number
+ "File %s no longer exists!"
+ "Cannot revert nonexistent file %s")
+ file-name))
+ ((not (file-readable-p file-name))
+ (error (if buffer-file-number
+ "File %s no longer readable!"
+ "Cannot revert unreadable file %s")
+ file-name))
+ (t
+ ;; Bind buffer-file-name to nil
+ ;; so that we don't try to lock the file.
+ (let ((buffer-file-name nil))
+ (or auto-save-p
+ (unlock-buffer)))
+ (widen)
+ (let ((coding-system-for-read
+ ;; Auto-saved file should be read by Emacs's
+ ;; internal coding.
+ (if auto-save-p 'auto-save-coding
+ (or coding-system-for-read
+ (and
+ buffer-file-coding-system-explicit
+ (car buffer-file-coding-system-explicit))))))
+ (if (and (not enable-multibyte-characters)
+ coding-system-for-read
+ (not (memq (coding-system-base
+ coding-system-for-read)
+ '(no-conversion raw-text))))
+ ;; As a coding system suitable for multibyte
+ ;; buffer is specified, make the current
+ ;; buffer multibyte.
+ (set-buffer-multibyte t))
+
+ ;; This force after-insert-file-set-coding
+ ;; (called from insert-file-contents) to set
+ ;; buffer-file-coding-system to a proper value.
+ (kill-local-variable 'buffer-file-coding-system)
+
+ ;; Note that this preserves point in an intelligent way.
+ (if revert-buffer-preserve-modes
+ (let ((buffer-file-format buffer-file-format))
+ (insert-file-contents file-name (not auto-save-p)
+ nil nil t))
+ (insert-file-contents file-name (not auto-save-p)
+ nil nil t))))))
+
(defun recover-this-file ()
"Recover the visited file--get contents from its last auto-save file."
(interactive)
@@ -6204,9 +6213,10 @@ normally equivalent short `-D' option is just passed on to
;; directory if FILE is a symbolic link.
(unless full-directory-p
(setq switches
- (if (stringp switches)
- (concat switches " -d")
- (add-to-list 'switches "-d" 'append))))
+ (cond
+ ((stringp switches) (concat switches " -d"))
+ ((member "-d" switches) switches)
+ (t (append switches '("-d"))))))
(apply 'call-process
insert-directory-program nil t nil
(append
@@ -6479,7 +6489,7 @@ With prefix ARG, silently save all file-visiting buffers, then kill.
If emacsclient was started with a list of filenames to edit, then
only these files will be asked to be saved."
(interactive "P")
- (if (frame-parameter (selected-frame) 'client)
+ (if (frame-parameter nil 'client)
(server-save-buffers-kill-terminal arg)
(save-buffers-kill-emacs arg)))
diff --git a/lisp/frameset.el b/lisp/frameset.el
new file mode 100644
index 00000000000..63ee9af23fc
--- /dev/null
+++ b/lisp/frameset.el
@@ -0,0 +1,693 @@
+;;; frameset.el --- save and restore frame and window setup -*- lexical-binding: t -*-
+
+;; Copyright (C) 2013 Free Software Foundation, Inc.
+
+;; Author: Juanma Barranquero <lekktu@gmail.com>
+;; Keywords: convenience
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs 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 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs 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 GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This file provides a set of operations to save a frameset (the state
+;; of all or a subset of the existing frames and windows), both
+;; in-session and persistently, and restore it at some point in the
+;; future.
+;;
+;; It should be noted that restoring the frames' windows depends on
+;; the buffers they are displaying, but this package does not provide
+;; any way to save and restore sets of buffers (see desktop.el for
+;; that). So, it's up to the user of frameset.el to make sure that
+;; any relevant buffer is loaded before trying to restore a frameset.
+;; When a window is restored and a buffer is missing, the window will
+;; be deleted unless it is the last one in the frame, in which case
+;; some previous buffer will be shown instead.
+
+;;; Code:
+
+(require 'cl-lib)
+
+
+;; Framesets have two fields:
+;; - properties: a property list to store both frameset-specific and
+;; user-defined serializable data. Currently defined properties
+;; include:
+;; :version ID - Identifies the version of the frameset struct;
+;; this is the only property always present and
+;; must not be modified.
+;; :app APPINFO - Freeform. Can be used by applications and
+;; packages to indicate the intended (but by no
+;; means exclusive) use of the frameset. For
+;; example, currently desktop.el sets :app to
+;; `(desktop . ,desktop-file-version).
+;; :name NAME - The name of the frameset instance; a string.
+;; :desc TEXT - A description for user consumption (to choose
+;; among framesets, etc.); a string.
+;; - states: an alist of items (FRAME-PARAMETERS . WINDOW-STATE) in
+;; no particular order. Each item represents a frame to be
+;; restored.
+
+(cl-defstruct (frameset (:type list) :named
+ (:copier nil)
+ (:predicate nil))
+ properties ;; property list
+ states) ;; list of conses (frame-state . window-state)
+
+(defun copy-frameset (frameset)
+ "Return a copy of FRAMESET.
+This is a deep copy done with `copy-tree'."
+ (copy-tree frameset t))
+
+;;;###autoload
+(defun frameset-p (frameset)
+ "If FRAMESET is a frameset, return its :version.
+Else return nil."
+ (and (eq (car-safe frameset) 'frameset)
+ (plist-get (cl-second frameset) :version)))
+
+;; A setf'able accessor to the frameset's properties
+(defun frameset-prop (frameset prop)
+ "Return the value of the PROP property of FRAMESET.
+
+Properties other than :version can be set with
+
+ (setf (frameset-prop FRAMESET PROP) NEW-VALUE)"
+ (plist-get (frameset-properties frameset) prop))
+
+(gv-define-setter frameset-prop (val fs prop)
+ (macroexp-let2 nil v val
+ `(progn
+ (cl-assert (not (eq ,prop :version)) t ":version can not be set")
+ (setf (frameset-properties ,fs)
+ (plist-put (frameset-properties ,fs) ,prop ,v))
+ ,v)))
+
+
+;; Filtering
+
+(defvar frameset-filter-alist
+ '((background-color . frameset-filter-sanitize-color)
+ (buffer-list . t)
+ (buffer-predicate . t)
+ (buried-buffer-list . t)
+ (font . frameset-filter-save-parm)
+ (foreground-color . frameset-filter-sanitize-color)
+ (fullscreen . frameset-filter-save-parm)
+ (GUI:font . frameset-filter-restore-parm)
+ (GUI:fullscreen . frameset-filter-restore-parm)
+ (GUI:height . frameset-filter-restore-parm)
+ (GUI:width . frameset-filter-restore-parm)
+ (height . frameset-filter-save-parm)
+ (left . frameset-filter-iconified)
+ (minibuffer . frameset-filter-minibuffer)
+ (top . frameset-filter-iconified)
+ (width . frameset-filter-save-parm))
+ "Alist of frame parameters and filtering functions.
+
+Each element is a cons (PARAM . ACTION), where PARAM is a parameter
+name (a symbol identifying a frame parameter), and ACTION can be:
+
+ t The parameter is always removed from the parameter list.
+ :save The parameter is removed when saving the frame.
+ :restore The parameter is removed when restoring the frame.
+ FILTER A filter function.
+
+FILTER can be a symbol FILTER-FUN, or a list (FILTER-FUN ARGS...).
+It will be called with four arguments CURRENT, FILTERED, PARAMETERS
+and SAVING, plus any additional ARGS:
+
+ CURRENT A cons (PARAM . VALUE), where PARAM is the one being
+ filtered and VALUE is its current value.
+ FILTERED The alist of parameters filtered so far.
+ PARAMETERS The complete alist of parameters being filtered,
+ SAVING Non-nil if filtering before saving state, nil otherwise.
+
+The FILTER-FUN function must return:
+ nil CURRENT is removed from the list.
+ t CURRENT is left as is.
+ (PARAM' . VALUE') Replace CURRENT with this.
+
+Frame parameters not on this list are passed intact.")
+
+(defvar frameset--target-display nil
+ ;; Either (minibuffer . VALUE) or nil.
+ ;; This refers to the current frame config being processed inside
+ ;; `frame--restore-frames' and its auxiliary functions (like filtering).
+ ;; If nil, there is no need to change the display.
+ ;; If non-nil, display parameter to use when creating the frame.
+ "Internal use only.")
+
+(defun frameset-switch-to-gui-p (parameters)
+ "True when switching to a graphic display.
+Return t if PARAMETERS describes a text-only terminal and
+the target is a graphic display; otherwise return nil.
+Only meaningful when called from a filtering function in
+`frameset-filter-alist'."
+ (and frameset--target-display ; we're switching
+ (null (cdr (assq 'display parameters))) ; from a tty
+ (cdr frameset--target-display))) ; to a GUI display
+
+(defun frameset-switch-to-tty-p (parameters)
+ "True when switching to a text-only terminal.
+Return t if PARAMETERS describes a graphic display and
+the target is a text-only terminal; otherwise return nil.
+Only meaningful when called from a filtering function in
+`frameset-filter-alist'."
+ (and frameset--target-display ; we're switching
+ (cdr (assq 'display parameters)) ; from a GUI display
+ (null (cdr frameset--target-display)))) ; to a tty
+
+(defun frameset-filter-sanitize-color (current _filtered parameters saving)
+ "When switching to a GUI frame, remove \"unspecified\" colors.
+Useful as a filter function for tty-specific parameters."
+ (or saving
+ (not (frameset-switch-to-gui-p parameters))
+ (not (stringp (cdr current)))
+ (not (string-match-p "^unspecified-[fb]g$" (cdr current)))))
+
+(defun frameset-filter-minibuffer (current _filtered _parameters saving)
+ "When saving, convert (minibuffer . #<window>) parameter to (minibuffer . t)."
+ (or (not saving)
+ (if (windowp (cdr current))
+ '(minibuffer . t)
+ t)))
+
+(defun frameset-filter-save-parm (current _filtered parameters saving
+ &optional prefix)
+ "When switching to a tty frame, save parameter P as PREFIX:P.
+The parameter can be later restored with `frameset-filter-restore-parm'.
+PREFIX defaults to `GUI'."
+ (unless prefix (setq prefix 'GUI))
+ (cond (saving t)
+ ((frameset-switch-to-tty-p parameters)
+ (let ((prefix:p (intern (format "%s:%s" prefix (car current)))))
+ (if (assq prefix:p parameters)
+ nil
+ (cons prefix:p (cdr current)))))
+ ((frameset-switch-to-gui-p parameters)
+ (not (assq (intern (format "%s:%s" prefix (car current))) parameters)))
+ (t t)))
+
+(defun frameset-filter-restore-parm (current filtered parameters saving)
+ "When switching to a GUI frame, restore PREFIX:P parameter as P.
+CURRENT must be of the form (PREFIX:P . value)."
+ (or saving
+ (not (frameset-switch-to-gui-p parameters))
+ (let* ((prefix:p (symbol-name (car current)))
+ (p (intern (substring prefix:p
+ (1+ (string-match-p ":" prefix:p)))))
+ (val (cdr current))
+ (found (assq p filtered)))
+ (if (not found)
+ (cons p val)
+ (setcdr found val)
+ nil))))
+
+(defun frameset-filter-iconified (_current _filtered parameters saving)
+ "Remove CURRENT when saving an iconified frame.
+This is used for positions parameters `left' and `top', which are
+meaningless in an iconified frame, so the frame is restored in a
+default position."
+ (not (and saving (eq (cdr (assq 'visibility parameters)) 'icon))))
+
+(defun frameset-keep-original-display-p (force-display)
+ "True if saved frames' displays should be honored."
+ (cond ((daemonp) t)
+ ((eq system-type 'windows-nt) nil)
+ (t (null force-display))))
+
+(defun frameset-filter-params (parameters filter-alist saving)
+ "Filter parameter list PARAMETERS and return a filtered list.
+FILTER-ALIST is an alist of parameter filters, in the format of
+`frameset-filter-alist' (which see).
+SAVING is non-nil while filtering parameters to save a frameset,
+nil while the filtering is done to restore it."
+ (let ((filtered nil))
+ (dolist (current parameters)
+ (pcase (cdr (assq (car current) filter-alist))
+ (`nil
+ (push current filtered))
+ (`t
+ nil)
+ (:save
+ (unless saving (push current filtered)))
+ (:restore
+ (when saving (push current filtered)))
+ ((or `(,fun . ,args) (and fun (pred fboundp)))
+ (let ((this (apply fun current filtered parameters saving args)))
+ (when this
+ (push (if (eq this t) current this) filtered))))
+ (other
+ (delay-warning 'frameset (format "Unknown filter %S" other) :error))))
+ ;; Set the display parameter after filtering, so that filter functions
+ ;; have access to its original value.
+ (when frameset--target-display
+ (let ((display (assq 'display filtered)))
+ (if display
+ (setcdr display (cdr frameset--target-display))
+ (push frameset--target-display filtered))))
+ filtered))
+
+
+;; Saving framesets
+
+(defun frameset--set-id (frame)
+ "Set FRAME's `frameset-id' if not yet set.
+Internal use only."
+ (unless (frame-parameter frame 'frameset-id)
+ (set-frame-parameter frame
+ 'frameset-id
+ (mapconcat (lambda (n) (format "%04X" n))
+ (cl-loop repeat 4 collect (random 65536))
+ "-"))))
+
+(defun frameset--process-minibuffer-frames (frame-list)
+ "Process FRAME-LIST and record minibuffer relationships.
+FRAME-LIST is a list of frames."
+ ;; Record frames with their own minibuffer
+ (dolist (frame (minibuffer-frame-list))
+ (when (memq frame frame-list)
+ (frameset--set-id frame)
+ ;; For minibuffer-owning frames, frameset--mini is a cons
+ ;; (t . DEFAULT?), where DEFAULT? is a boolean indicating whether
+ ;; the frame is the one pointed out by `default-minibuffer-frame'.
+ (set-frame-parameter frame
+ 'frameset--mini
+ (cons t (eq frame default-minibuffer-frame)))))
+ ;; Now link minibufferless frames with their minibuffer frames
+ (dolist (frame frame-list)
+ (unless (frame-parameter frame 'frameset--mini)
+ (frameset--set-id frame)
+ (let* ((mb-frame (window-frame (minibuffer-window frame)))
+ (id (and mb-frame (frame-parameter mb-frame 'frameset-id))))
+ (if (null id)
+ (error "Minibuffer frame %S for %S is excluded" mb-frame frame)
+ ;; For minibufferless frames, frameset--mini is a cons
+ ;; (nil . FRAME-ID), where FRAME-ID is the frameset-id of
+ ;; the frame containing its minibuffer window.
+ (set-frame-parameter frame
+ 'frameset--mini
+ (cons nil id)))))))
+
+;;;###autoload
+(cl-defun frameset-save (frame-list &key filters predicate properties)
+ "Return the frameset of FRAME-LIST, a list of frames.
+If nil, FRAME-LIST defaults to all live frames.
+FILTERS is an alist of parameter filters; defaults to `frameset-filter-alist'.
+PREDICATE is a predicate function, which must return non-nil for frames that
+should be saved; it defaults to saving all frames from FRAME-LIST.
+PROPERTIES is a user-defined property list to add to the frameset."
+ (let ((frames (cl-delete-if-not #'frame-live-p
+ (cl-delete-if-not (or predicate #'framep)
+ (or (copy-sequence frame-list)
+ (frame-list))))))
+ (frameset--process-minibuffer-frames frames)
+ (make-frameset :properties (append '(:version 1) properties)
+ :states (mapcar
+ (lambda (frame)
+ (cons
+ (frameset-filter-params (frame-parameters frame)
+ (or filters
+ frameset-filter-alist)
+ t)
+ (window-state-get (frame-root-window frame) t)))
+ frames))))
+
+
+;; Restoring framesets
+
+(defvar frameset--reuse-list nil
+ "Internal use only.")
+
+(defun frameset--compute-pos (value left/top right/bottom)
+ (pcase value
+ (`(+ ,val) (+ left/top val))
+ (`(- ,val) (+ right/bottom val))
+ (val val)))
+
+(defun frameset--move-onscreen (frame force-onscreen)
+ "If FRAME is offscreen, move it back onscreen and, if necessary, resize it.
+For the description of FORCE-ONSCREEN, see `frameset-restore'.
+When forced onscreen, frames wider than the monitor's workarea are converted
+to fullwidth, and frames taller than the workarea are converted to fullheight.
+NOTE: This only works for non-iconified frames. Internal use only."
+ (pcase-let* ((`(,left ,top ,width ,height) (cl-cdadr (frame-monitor-attributes frame)))
+ (right (+ left width -1))
+ (bottom (+ top height -1))
+ (fr-left (frameset--compute-pos (frame-parameter frame 'left) left right))
+ (fr-top (frameset--compute-pos (frame-parameter frame 'top) top bottom))
+ (ch-width (frame-char-width frame))
+ (ch-height (frame-char-height frame))
+ (fr-width (max (frame-pixel-width frame) (* ch-width (frame-width frame))))
+ (fr-height (max (frame-pixel-height frame) (* ch-height (frame-height frame))))
+ (fr-right (+ fr-left fr-width -1))
+ (fr-bottom (+ fr-top fr-height -1)))
+ (when (pcase force-onscreen
+ ;; Any corner is outside the screen.
+ (`all (or (< fr-bottom top) (> fr-bottom bottom)
+ (< fr-left left) (> fr-left right)
+ (< fr-right left) (> fr-right right)
+ (< fr-top top) (> fr-top bottom)))
+ ;; Displaced to the left, right, above or below the screen.
+ (`t (or (> fr-left right)
+ (< fr-right left)
+ (> fr-top bottom)
+ (< fr-bottom top)))
+ ;; Fully inside, no need to do anything.
+ (_ nil))
+ (let ((fullwidth (> fr-width width))
+ (fullheight (> fr-height height))
+ (params nil))
+ ;; Position frame horizontally.
+ (cond (fullwidth
+ (push `(left . ,left) params))
+ ((> fr-right right)
+ (push `(left . ,(+ left (- width fr-width))) params))
+ ((< fr-left left)
+ (push `(left . ,left) params)))
+ ;; Position frame vertically.
+ (cond (fullheight
+ (push `(top . ,top) params))
+ ((> fr-bottom bottom)
+ (push `(top . ,(+ top (- height fr-height))) params))
+ ((< fr-top top)
+ (push `(top . ,top) params)))
+ ;; Compute fullscreen state, if required.
+ (when (or fullwidth fullheight)
+ (push (cons 'fullscreen
+ (cond ((not fullwidth) 'fullheight)
+ ((not fullheight) 'fullwidth)
+ (t 'maximized)))
+ params))
+ ;; Finally, move the frame back onscreen.
+ (when params
+ (modify-frame-parameters frame params))))))
+
+(defun frameset--find-frame (predicate display &rest args)
+ "Find a frame in `frameset--reuse-list' satisfying PREDICATE.
+Look through available frames whose display property matches DISPLAY
+and return the first one for which (PREDICATE frame ARGS) returns t.
+If PREDICATE is nil, it is always satisfied. Internal use only."
+ (cl-find-if (lambda (frame)
+ (and (equal (frame-parameter frame 'display) display)
+ (or (null predicate)
+ (apply predicate frame args))))
+ frameset--reuse-list))
+
+(defun frameset--reuse-frame (display frame-cfg)
+ "Look for an existing frame to reuse.
+DISPLAY is the display where the frame will be shown, and FRAME-CFG
+is the parameter list of the frame being restored. Internal use only."
+ (let ((frame nil)
+ mini)
+ ;; There are no fancy heuristics there. We could implement some
+ ;; based on frame size and/or position, etc., but it is not clear
+ ;; that any "gain" (in the sense of reduced flickering, etc.) is
+ ;; worth the added complexity. In fact, the code below mainly
+ ;; tries to work nicely when M-x desktop-read is used after a
+ ;; desktop session has already been loaded. The other main use
+ ;; case, which is the initial desktop-read upon starting Emacs,
+ ;; will usually have only one frame, and should already work.
+ (cond ((null display)
+ ;; When the target is tty, every existing frame is reusable.
+ (setq frame (frameset--find-frame nil display)))
+ ((car (setq mini (cdr (assq 'frameset--mini frame-cfg))))
+ ;; If the frame has its own minibuffer, let's see whether
+ ;; that frame has already been loaded (which can happen after
+ ;; M-x desktop-read).
+ (setq frame (frameset--find-frame
+ (lambda (f id)
+ (string= (frame-parameter f 'frameset-id) id))
+ display (cdr mini)))
+ ;; If it has not been loaded, and it is not a minibuffer-only frame,
+ ;; let's look for an existing non-minibuffer-only frame to reuse.
+ (unless (or frame (eq (cdr (assq 'minibuffer frame-cfg)) 'only))
+ (setq frame (frameset--find-frame
+ (lambda (f)
+ (let ((w (frame-parameter f 'minibuffer)))
+ (and (window-live-p w)
+ (window-minibuffer-p w)
+ (eq (window-frame w) f))))
+ display))))
+ (mini
+ ;; For minibufferless frames, check whether they already exist,
+ ;; and that they are linked to the right minibuffer frame.
+ (setq frame (frameset--find-frame
+ (lambda (f id mini-id)
+ (and (string= (frame-parameter f 'frameset-id) id)
+ (string= (frame-parameter (window-frame (minibuffer-window f))
+ 'frameset-id)
+ mini-id)))
+ display (cdr (assq 'frameset-id frame-cfg)) (cdr mini))))
+ (t
+ ;; Default to just finding a frame in the same display.
+ (setq frame (frameset--find-frame nil display))))
+ ;; If found, remove from the list.
+ (when frame
+ (setq frameset--reuse-list (delq frame frameset--reuse-list)))
+ frame))
+
+(defun frameset--get-frame (frame-cfg window-cfg filters force-onscreen)
+ "Set up and return a frame according to its saved state.
+That means either reusing an existing frame or creating one anew.
+FRAME-CFG is the frame's parameter list; WINDOW-CFG is its window state.
+For the meaning of FORCE-ONSCREEN, see `frameset-restore'."
+ (let* ((fullscreen (cdr (assq 'fullscreen frame-cfg)))
+ (lines (assq 'tool-bar-lines frame-cfg))
+ (filtered-cfg (frameset-filter-params frame-cfg filters nil))
+ (display (cdr (assq 'display filtered-cfg))) ;; post-filtering
+ alt-cfg frame)
+
+ ;; This works around bug#14795 (or feature#14795, if not a bug :-)
+ (setq filtered-cfg (assq-delete-all 'tool-bar-lines filtered-cfg))
+ (push '(tool-bar-lines . 0) filtered-cfg)
+
+ (when fullscreen
+ ;; Currently Emacs has the limitation that it does not record the size
+ ;; and position of a frame before maximizing it, so we cannot save &
+ ;; restore that info. Instead, when restoring, we resort to creating
+ ;; invisible "fullscreen" frames of default size and then maximizing them
+ ;; (and making them visible) which at least is somewhat user-friendly
+ ;; when these frames are later de-maximized.
+ (let ((width (and (eq fullscreen 'fullheight) (cdr (assq 'width filtered-cfg))))
+ (height (and (eq fullscreen 'fullwidth) (cdr (assq 'height filtered-cfg))))
+ (visible (assq 'visibility filtered-cfg)))
+ (setq filtered-cfg (cl-delete-if (lambda (p)
+ (memq p '(visibility fullscreen width height)))
+ filtered-cfg :key #'car))
+ (when width
+ (setq filtered-cfg (append `((user-size . t) (width . ,width))
+ filtered-cfg)))
+ (when height
+ (setq filtered-cfg (append `((user-size . t) (height . ,height))
+ filtered-cfg)))
+ ;; These are parameters to apply after creating/setting the frame.
+ (push visible alt-cfg)
+ (push (cons 'fullscreen fullscreen) alt-cfg)))
+
+ ;; Time to find or create a frame an apply the big bunch of parameters.
+ ;; If a frame needs to be created and it falls partially or fully offscreen,
+ ;; sometimes it gets "pushed back" onscreen; however, moving it afterwards is
+ ;; allowed. So we create the frame as invisible and then reapply the full
+ ;; parameter list (including position and size parameters).
+ (setq frame (or (and frameset--reuse-list
+ (frameset--reuse-frame display filtered-cfg))
+ (make-frame-on-display display
+ (cons '(visibility)
+ (cl-loop
+ for param in '(left top width height minibuffer)
+ collect (assq param filtered-cfg))))))
+ (modify-frame-parameters frame
+ (if (eq (frame-parameter frame 'fullscreen) fullscreen)
+ ;; Workaround for bug#14949
+ (assq-delete-all 'fullscreen filtered-cfg)
+ filtered-cfg))
+
+ ;; If requested, force frames to be onscreen.
+ (when (and force-onscreen
+ ;; FIXME: iconified frames should be checked too,
+ ;; but it is impossible without deiconifying them.
+ (not (eq (frame-parameter frame 'visibility) 'icon)))
+ (frameset--move-onscreen frame force-onscreen))
+
+ ;; Let's give the finishing touches (visibility, tool-bar, maximization).
+ (when lines (push lines alt-cfg))
+ (when alt-cfg (modify-frame-parameters frame alt-cfg))
+ ;; Now restore window state.
+ (window-state-put window-cfg (frame-root-window frame) 'safe)
+ frame))
+
+(defun frameset--sort-states (state1 state2)
+ "Predicate to sort frame states in a suitable order to be created.
+It sorts minibuffer-owning frames before minibufferless ones."
+ (pcase-let ((`(,hasmini1 ,id-def1) (assq 'frameset--mini (car state1)))
+ (`(,hasmini2 ,id-def2) (assq 'frameset--mini (car state2))))
+ (cond ((eq id-def1 t) t)
+ ((eq id-def2 t) nil)
+ ((not (eq hasmini1 hasmini2)) (eq hasmini1 t))
+ ((eq hasmini1 nil) (string< id-def1 id-def2))
+ (t t))))
+
+(defun frameset-sort-frames-for-deletion (frame1 _frame2)
+ "Predicate to sort live frames for deletion.
+Minibufferless frames must go first to avoid errors when attempting
+to delete a frame whose minibuffer window is used by another frame."
+ (not (frame-parameter frame1 'minibuffer)))
+
+;;;###autoload
+(cl-defun frameset-restore (frameset &key filters reuse-frames force-display force-onscreen)
+ "Restore a FRAMESET into the current display(s).
+
+FILTERS is an alist of parameter filters; defaults to `frameset-filter-alist'.
+
+REUSE-FRAMES describes how to reuse existing frames while restoring a frameset:
+ t Reuse any existing frame if possible; delete leftover frames.
+ nil Restore frameset in new frames and delete existing frames.
+ keep Restore frameset in new frames and keep the existing ones.
+ LIST A list of frames to reuse; only these will be reused, if possible,
+ and any leftover one will be deleted; other frames not on this
+ list will be kept.
+
+FORCE-DISPLAY can be:
+ t Frames will be restored in the current display.
+ nil Frames will be restored, if possible, in their original displays.
+ delete Frames in other displays will be deleted instead of restored.
+
+FORCE-ONSCREEN can be:
+ all Force onscreen any frame fully or partially offscreen.
+ t Force onscreen only those frames that are fully offscreen.
+ nil Do not force any frame back onscreen.
+
+All keywords default to nil."
+
+ (cl-assert (frameset-p frameset))
+
+ (let* ((delete-saved (eq force-display 'delete))
+ (forcing (not (frameset-keep-original-display-p force-display)))
+ (target (and forcing (cons 'display (frame-parameter nil 'display))))
+ other-frames)
+
+ ;; frameset--reuse-list is a list of frames potentially reusable. Later we
+ ;; will decide which ones can be reused, and how to deal with any leftover.
+ (pcase reuse-frames
+ ((or `nil `keep)
+ (setq frameset--reuse-list nil
+ other-frames (frame-list)))
+ ((pred consp)
+ (setq frameset--reuse-list (copy-sequence reuse-frames)
+ other-frames (cl-delete-if (lambda (frame)
+ (memq frame frameset--reuse-list))
+ (frame-list))))
+ (_
+ (setq frameset--reuse-list (frame-list)
+ other-frames nil)))
+
+ ;; Sort saved states to guarantee that minibufferless frames will be created
+ ;; after the frames that contain their minibuffer windows.
+ (dolist (state (sort (copy-sequence (frameset-states frameset))
+ #'frameset--sort-states))
+ (condition-case-unless-debug err
+ (pcase-let* ((`(,frame-cfg . ,window-cfg) state)
+ ((and d-mini `(,hasmini . ,mb-id))
+ (cdr (assq 'frameset--mini frame-cfg)))
+ (default (and (booleanp mb-id) mb-id))
+ (frame nil) (to-tty nil))
+ ;; Only set target if forcing displays and the target display is different.
+ (if (or (not forcing)
+ (equal target (or (assq 'display frame-cfg) '(display . nil))))
+ (setq frameset--target-display nil)
+ (setq frameset--target-display target
+ to-tty (null (cdr target))))
+ ;; If keeping non-reusable frames, and the frame-id of one of them
+ ;; matches the frame-id of a frame being restored (because, for example,
+ ;; the frameset has already been read in the same session), remove the
+ ;; frame-id from the non-reusable frame, which is not useful anymore.
+ (when (and other-frames
+ (or (eq reuse-frames 'keep) (consp reuse-frames)))
+ (let ((dup (cl-find (cdr (assq 'frameset-frame-id frame-cfg))
+ other-frames
+ :key (lambda (frame)
+ (frame-parameter frame 'frameset-frame-id))
+ :test #'string=)))
+ (when dup
+ (set-frame-parameter dup 'frameset-frame-id nil))))
+ ;; Time to restore frames and set up their minibuffers as they were.
+ ;; We only skip a frame (thus deleting it) if either:
+ ;; - we're switching displays, and the user chose the option to delete, or
+ ;; - we're switching to tty, and the frame to restore is minibuffer-only.
+ (unless (and frameset--target-display
+ (or delete-saved
+ (and to-tty
+ (eq (cdr (assq 'minibuffer frame-cfg)) 'only))))
+
+ ;; Restore minibuffers. Some of this stuff could be done in a filter
+ ;; function, but it would be messy because restoring minibuffers affects
+ ;; global state; it's best to do it here than add a bunch of global
+ ;; variables to pass info back-and-forth to/from the filter function.
+ (cond
+ ((null d-mini)) ;; No frameset--mini. Process as normal frame.
+ (to-tty) ;; Ignore minibuffer stuff and process as normal frame.
+ (hasmini ;; Frame has minibuffer (or it is minibuffer-only).
+ (when (eq (cdr (assq 'minibuffer frame-cfg)) 'only)
+ (setq frame-cfg (append '((tool-bar-lines . 0) (menu-bar-lines . 0))
+ frame-cfg))))
+ (t ;; Frame depends on other frame's minibuffer window.
+ (let* ((mb-frame (or (cl-find-if
+ (lambda (f)
+ (string= (frame-parameter f 'frameset-id)
+ mb-id))
+ (frame-list))
+ (error "Minibuffer frame %S not found" mb-id)))
+ (mb-param (assq 'minibuffer frame-cfg))
+ (mb-window (minibuffer-window mb-frame)))
+ (unless (and (window-live-p mb-window)
+ (window-minibuffer-p mb-window))
+ (error "Not a minibuffer window %s" mb-window))
+ (if mb-param
+ (setcdr mb-param mb-window)
+ (push (cons 'minibuffer mb-window) frame-cfg))))))
+ ;; OK, we're ready at last to create (or reuse) a frame and
+ ;; restore the window config.
+ (setq frame (frameset--get-frame frame-cfg window-cfg
+ (or filters frameset-filter-alist)
+ force-onscreen))
+ ;; Set default-minibuffer if required.
+ (when default (setq default-minibuffer-frame frame)))
+ (error
+ (delay-warning 'frameset (error-message-string err) :error))))
+
+ ;; In case we try to delete the initial frame, we want to make sure that
+ ;; other frames are already visible (discussed in thread for bug#14841).
+ (sit-for 0 t)
+
+ ;; Delete remaining frames, but do not fail if some resist being deleted.
+ (unless (eq reuse-frames 'keep)
+ (dolist (frame (sort (nconc (if (listp reuse-frames) nil other-frames)
+ frameset--reuse-list)
+ #'frameset-sort-frames-for-deletion))
+ (condition-case err
+ (delete-frame frame)
+ (error
+ (delay-warning 'frameset (error-message-string err))))))
+ (setq frameset--reuse-list nil)
+
+ ;; Make sure there's at least one visible frame.
+ (unless (or (daemonp) (visible-frame-list))
+ (make-frame-visible (car (frame-list))))))
+
+(provide 'frameset)
+
+;;; frameset.el ends here
diff --git a/lisp/gnus/ChangeLog b/lisp/gnus/ChangeLog
index 4230d010a5d..2164fd96b46 100644
--- a/lisp/gnus/ChangeLog
+++ b/lisp/gnus/ChangeLog
@@ -1,5 +1,76 @@
+2013-08-02 Lars Magne Ingebrigtsen <larsi@gnus.org>
+
+ * gnus-delay.el (gnus-delay-article): Fix typo.
+
+ * gnus-group.el (gnus-group-delete-articles): Allow deleting only "old"
+ articles.
+
+ * gnus-delay.el (gnus-delay-article): Run `message-send-hook' so that
+ we can get spell-checking etc.
+
+2013-08-02 Katsumi Yamaoka <yamaoka@jpl.org>
+
+ * rfc2047.el (rfc2047-encode-message-header): Unify charsets into
+ a single one used for encoding the whole text in a header.
+
+2013-08-01 Lars Magne Ingebrigtsen <larsi@gnus.org>
+
+ * message.el (message-ignored-news-headers): Delete X-Gnus-Delayed
+ before sending.
+
+ * mm-decode.el (mm-command-output): New face.
+ (mm-display-external): Use it.
+
+2013-08-01 Kan-Ru Chen (陳侃如) <kanru@kanru.info> (tiny change)
+
+ * nnmbox.el (nnmbox-request-article): Don't change point.
+
+2013-08-01 Lars Magne Ingebrigtsen <larsi@gnus.org>
+
+ * gnus-icalendar.el (gnus-icalendar-event:inline-reply-buttons):
+ Include `handle' parameter.
+
+2013-08-01 Jan Tatarik <jan.tatarik@gmail.com>
+
+ * gnus-icalendar.el: New file.
+
+2013-08-01 Lars Magne Ingebrigtsen <larsi@gnus.org>
+
+ * gnus-int.el (gnus-warp-to-article): Mention that warp means jump.
+
+ * gnus-uu.el (gnus-uu-mark-thread, gnus-uu-unmark-thread): Work with
+ dummy roots, too.
+
+2013-08-01 David Edmondson <dme@dme.org>
+
+ * mml2015.el (mml2015-epg-key-image-to-string): Protect against bugging
+ out on ttys.
+
+2013-08-01 Lars Magne Ingebrigtsen <larsi@gnus.org>
+
+ * gnus-start.el (gnus-dribble-save): Only save the dribble file if it's
+ not empty.
+
+ * nnrss.el (nnrss-discover-feed): Indent.
+
+2013-08-01 Katsumi Yamaoka <yamaoka@jpl.org>
+
+ * gnus-util.el (gnus-emacs-completing-read): Isolate XEmacs stuff.
+
+2013-07-30 Lars Magne Ingebrigtsen <larsi@gnus.org>
+
+ * gnus-start.el (gnus-read-active-for-groups): Always mark the data as
+ dirty to ensure nnimap data being saved.
+
2013-07-30 Tassilo Horn <tsdh@gnu.org>
+ * gnus-sum.el (gnus-summary-make-menu-bar): Add "Current thread score"
+ menu entry.
+
+ * gnus-score.el (gnus-summary-current-score): Use prefix arg to show
+ the current thread's total score instead of the current article's
+ score.
+
* gnus-sum.el (gnus-subthread-sort-functions): New defcustom.
(gnus-sort-threads-recursively): Delete defcustom.
(gnus-sort-threads-recursive): Adapt accordingly.
diff --git a/lisp/gnus/gnus-delay.el b/lisp/gnus/gnus-delay.el
index 921d24109c2..2cdafe1565b 100644
--- a/lisp/gnus/gnus-delay.el
+++ b/lisp/gnus/gnus-delay.el
@@ -80,6 +80,8 @@ DELAY is a string, giving the length of the time. Possible values are:
(list (read-string
"Target date (YYYY-MM-DD), time (hh:mm), or length of delay (units in [mhdwMY]): "
gnus-delay-default-delay)))
+ ;; Allow spell checking etc.
+ (run-hooks 'message-send-hook)
(let (num unit days year month day hour minute deadline)
(cond ((string-match
"\\([0-9][0-9][0-9]?[0-9]?\\)-\\([0-9]+\\)-\\([0-9]+\\)"
diff --git a/lisp/gnus/gnus-group.el b/lisp/gnus/gnus-group.el
index 8050f5d59d7..9533f5819a4 100644
--- a/lisp/gnus/gnus-group.el
+++ b/lisp/gnus/gnus-group.el
@@ -2796,14 +2796,21 @@ server."
(lambda (group)
(gnus-group-delete-group group nil t))))))
-(defun gnus-group-delete-articles (group)
- "Delete all articles in the current group."
- (interactive (list (gnus-group-group-name)))
+(defun gnus-group-delete-articles (group &optional oldp)
+ "Delete all articles in the current group.
+If OLDP (the prefix), only delete articles that are \"old\",
+according to the expiry settings. Note that this will delete old
+not-expirable articles, too."
+ (interactive (list (gnus-group-group-name)
+ current-prefix-arg))
(let ((articles (gnus-uncompress-range (gnus-active group))))
(when (gnus-yes-or-no-p
(format "Do you really want to delete these %d articles forever? "
(length articles)))
- (gnus-request-expire-articles articles group 'force))))
+ (gnus-request-expire-articles articles group
+ (if current-prefix-arg
+ nil
+ 'force)))))
(defun gnus-group-delete-group (group &optional force no-prompt)
"Delete the current group. Only meaningful with editable groups.
diff --git a/lisp/gnus/gnus-icalendar.el b/lisp/gnus/gnus-icalendar.el
new file mode 100644
index 00000000000..0286fd5dd89
--- /dev/null
+++ b/lisp/gnus/gnus-icalendar.el
@@ -0,0 +1,837 @@
+;;; gnus-icalendar.el --- reply to iCalendar meeting requests
+
+;; Copyright (C) 2013 Free Software Foundation, Inc.
+
+;; Author: Jan Tatarik <Jan.Tatarik@gmail.com>
+;; Keywords: mail, icalendar, org
+
+;; 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 3 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, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; To install:
+;; (require 'gnus-icalendar)
+;; (gnus-icalendar-setup)
+
+;; to enable optional iCalendar->Org sync functionality
+;; NOTE: both the capture file and the headline(s) inside must already exist
+;; (setq gnus-icalendar-org-capture-file "~/org/notes.org")
+;; (setq gnus-icalendar-org-capture-headline '("Calendar"))
+;; (gnus-icalendar-org-setup)
+
+
+;;; Code:
+
+(require 'icalendar)
+(require 'eieio)
+(require 'mm-decode)
+(require 'gnus-sum)
+
+(eval-when-compile (require 'cl))
+
+(defun gnus-icalendar-find-if (pred seq)
+ (catch 'found
+ (while seq
+ (when (funcall pred (car seq))
+ (throw 'found (car seq)))
+ (pop seq))))
+
+;;;
+;;; ical-event
+;;;
+
+(defclass gnus-icalendar-event ()
+ ((organizer :initarg :organizer
+ :accessor gnus-icalendar-event:organizer
+ :initform ""
+ :type (or null string))
+ (summary :initarg :summary
+ :accessor gnus-icalendar-event:summary
+ :initform ""
+ :type (or null string))
+ (description :initarg :description
+ :accessor gnus-icalendar-event:description
+ :initform ""
+ :type (or null string))
+ (location :initarg :location
+ :accessor gnus-icalendar-event:location
+ :initform ""
+ :type (or null string))
+ (start :initarg :start
+ :accessor gnus-icalendar-event:start
+ :initform ""
+ :type (or null string))
+ (end :initarg :end
+ :accessor gnus-icalendar-event:end
+ :initform ""
+ :type (or null string))
+ (recur :initarg :recur
+ :accessor gnus-icalendar-event:recur
+ :initform ""
+ :type (or null string))
+ (uid :initarg :uid
+ :accessor gnus-icalendar-event:uid
+ :type string)
+ (method :initarg :method
+ :accessor gnus-icalendar-event:method
+ :initform "PUBLISH"
+ :type (or null string))
+ (rsvp :initarg :rsvp
+ :accessor gnus-icalendar-event:rsvp
+ :initform nil
+ :type (or null boolean)))
+ "generic iCalendar Event class")
+
+(defclass gnus-icalendar-event-request (gnus-icalendar-event)
+ nil
+ "iCalendar class for REQUEST events")
+
+(defclass gnus-icalendar-event-cancel (gnus-icalendar-event)
+ nil
+ "iCalendar class for CANCEL events")
+
+(defclass gnus-icalendar-event-reply (gnus-icalendar-event)
+ nil
+ "iCalendar class for REPLY events")
+
+(defmethod gnus-icalendar-event:recurring-p ((event gnus-icalendar-event))
+ "Return t if EVENT is recurring."
+ (not (null (gnus-icalendar-event:recur event))))
+
+(defmethod gnus-icalendar-event:recurring-freq ((event gnus-icalendar-event))
+ "Return recurring frequency of EVENT."
+ (let ((rrule (gnus-icalendar-event:recur event)))
+ (string-match "FREQ=\\([[:alpha:]]+\\)" rrule)
+ (match-string 1 rrule)))
+
+(defmethod gnus-icalendar-event:recurring-interval ((event gnus-icalendar-event))
+ "Return recurring interval of EVENT."
+ (let ((rrule (gnus-icalendar-event:recur event))
+ (default-interval 1))
+
+ (string-match "INTERVAL=\\([[:digit:]]+\\)" rrule)
+ (or (match-string 1 rrule)
+ default-interval)))
+
+(defmethod gnus-icalendar-event:start-time ((event gnus-icalendar-event))
+ "Return time value of the EVENT start date."
+ (date-to-time (gnus-icalendar-event:start event)))
+
+(defmethod gnus-icalendar-event:end-time ((event gnus-icalendar-event))
+ "Return time value of the EVENT end date."
+ (date-to-time (gnus-icalendar-event:end event)))
+
+
+(defun gnus-icalendar-event--decode-datefield (ical field zone-map &optional date-style)
+ (let* ((calendar-date-style (or date-style 'european))
+ (date (icalendar--get-event-property ical field))
+ (date-zone (icalendar--find-time-zone
+ (icalendar--get-event-property-attributes
+ ical field)
+ zone-map))
+ (date-decoded (icalendar--decode-isodatetime date nil date-zone)))
+
+ (concat (icalendar--datetime-to-iso-date date-decoded "-")
+ " "
+ (icalendar--datetime-to-colontime date-decoded))))
+
+(defun gnus-icalendar-event--find-attendee (ical name-or-email)
+ (let* ((event (car (icalendar--all-events ical)))
+ (event-props (caddr event)))
+ (labels ((attendee-name (att) (plist-get (cadr att) 'CN))
+ (attendee-email (att)
+ (replace-regexp-in-string "^.*MAILTO:" "" (caddr att)))
+ (attendee-prop-matches-p (prop)
+ (and (eq (car prop) 'ATTENDEE)
+ (or (member (attendee-name prop) name-or-email)
+ (let ((att-email (attendee-email prop)))
+ (gnus-icalendar-find-if (lambda (email)
+ (string-match email att-email))
+ name-or-email))))))
+
+ (gnus-icalendar-find-if #'attendee-prop-matches-p event-props))))
+
+
+(defun gnus-icalendar-event-from-ical (ical &optional attendee-name-or-email)
+ (let* ((event (car (icalendar--all-events ical)))
+ (zone-map (icalendar--convert-all-timezones ical))
+ (organizer (replace-regexp-in-string
+ "^.*MAILTO:" ""
+ (or (icalendar--get-event-property event 'ORGANIZER) "")))
+ (prop-map '((summary . SUMMARY)
+ (description . DESCRIPTION)
+ (location . LOCATION)
+ (recur . RRULE)
+ (uid . UID)))
+ (method (caddr (assoc 'METHOD (caddr (car (nreverse ical))))))
+ (attendee (when attendee-name-or-email
+ (gnus-icalendar-event--find-attendee ical attendee-name-or-email)))
+ (args (list :method method
+ :organizer organizer
+ :start (gnus-icalendar-event--decode-datefield event 'DTSTART zone-map)
+ :end (gnus-icalendar-event--decode-datefield event 'DTEND zone-map)
+ :rsvp (string= (plist-get (cadr attendee) 'RSVP)
+ "TRUE")))
+ (event-class (pcase method
+ ("REQUEST" 'gnus-icalendar-event-request)
+ ("CANCEL" 'gnus-icalendar-event-cancel)
+ ("REPLY" 'gnus-icalendar-event-reply)
+ (_ 'gnus-icalendar-event))))
+
+ (labels ((map-property (prop)
+ (let ((value (icalendar--get-event-property event prop)))
+ (when value
+ ;; ugly, but cannot get
+ ;;replace-regexp-in-string work with "\\" as
+ ;;REP, plus we should also handle "\\;"
+ (replace-regexp-in-string
+ "\\\\," ","
+ (replace-regexp-in-string
+ "\\\\n" "\n" (substring-no-properties value))))))
+ (accumulate-args (mapping)
+ (destructuring-bind (slot . ical-property) mapping
+ (setq args (append (list
+ (intern (concat ":" (symbol-name slot)))
+ (map-property ical-property))
+ args)))))
+
+ (mapc #'accumulate-args prop-map)
+ (apply 'make-instance event-class args))))
+
+(defun gnus-icalendar-event-from-buffer (buf &optional attendee-name-or-email)
+ "Parse RFC5545 iCalendar in buffer BUF and return an event object.
+
+Return a gnus-icalendar-event object representing the first event
+contained in the invitation. Return nil for calendars without an event entry.
+
+ATTENDEE-NAME-OR-EMAIL is a list of strings that will be matched
+against the event's attendee names and emails. Invitation rsvp
+status will be retrieved from the first matching attendee record."
+ (let ((ical (with-current-buffer (icalendar--get-unfolded-buffer (get-buffer buf))
+ (goto-char (point-min))
+ (icalendar--read-element nil nil))))
+
+ (when ical
+ (gnus-icalendar-event-from-ical ical attendee-name-or-email))))
+
+;;;
+;;; gnus-icalendar-event-reply
+;;;
+
+(defun gnus-icalendar-event--build-reply-event-body (ical-request status identities)
+ (let ((summary-status (capitalize (symbol-name status)))
+ (attendee-status (upcase (symbol-name status)))
+ reply-event-lines)
+ (labels ((update-summary (line)
+ (if (string-match "^[^:]+:" line)
+ (replace-match (format "\\&%s: " summary-status) t nil line)
+ line))
+ (update-dtstamp ()
+ (format-time-string "DTSTAMP:%Y%m%dT%H%M%SZ" nil t))
+ (attendee-matches-identity (line)
+ (gnus-icalendar-find-if (lambda (name) (string-match-p name line))
+ identities))
+ (update-attendee-status (line)
+ (when (and (attendee-matches-identity line)
+ (string-match "\\(PARTSTAT=\\)[^;]+" line))
+ (replace-match (format "\\1%s" attendee-status) t nil line)))
+ (process-event-line (line)
+ (when (string-match "^\\([^;:]+\\)" line)
+ (let* ((key (match-string 0 line))
+ ;; NOTE: not all of the below fields are mandatory,
+ ;; but they are often present in other clients'
+ ;; replies. Can be helpful for debugging, too.
+ (new-line (pcase key
+ ("ATTENDEE" (update-attendee-status line))
+ ("SUMMARY" (update-summary line))
+ ("DTSTAMP" (update-dtstamp))
+ ((or "ORGANIZER" "DTSTART" "DTEND"
+ "LOCATION" "DURATION" "SEQUENCE"
+ "RECURRENCE-ID" "UID") line)
+ (_ nil))))
+ (when new-line
+ (push new-line reply-event-lines))))))
+
+ (mapc #'process-event-line (split-string ical-request "\n"))
+
+ (unless (gnus-icalendar-find-if (lambda (x) (string-match "^ATTENDEE" x))
+ reply-event-lines)
+ (error "Could not find an event attendee matching given identity"))
+
+ (mapconcat #'identity `("BEGIN:VEVENT"
+ ,@(nreverse reply-event-lines)
+ "END:VEVENT")
+ "\n"))))
+
+(defun gnus-icalendar-event-reply-from-buffer (buf status identities)
+ "Build a calendar event reply for request contained in BUF.
+The reply will have STATUS (`accepted', `tentative' or `declined').
+The reply will be composed for attendees matching any entry
+on the IDENTITIES list."
+ (flet ((extract-block (blockname)
+ (save-excursion
+ (let ((block-start-re (format "^BEGIN:%s" blockname))
+ (block-end-re (format "^END:%s" blockname))
+ start)
+ (when (re-search-forward block-start-re nil t)
+ (setq start (line-beginning-position))
+ (re-search-forward block-end-re)
+ (buffer-substring-no-properties start (line-end-position)))))))
+
+ (let (zone event)
+ (with-current-buffer (icalendar--get-unfolded-buffer (get-buffer buf))
+ (goto-char (point-min))
+ (setq zone (extract-block "VTIMEZONE")
+ event (extract-block "VEVENT")))
+
+ (when event
+ (let ((contents (list "BEGIN:VCALENDAR"
+ "METHOD:REPLY"
+ "PRODID:Gnus"
+ "VERSION:2.0"
+ zone
+ (gnus-icalendar-event--build-reply-event-body event status identities)
+ "END:VCALENDAR")))
+
+ (mapconcat #'identity (delq nil contents) "\n"))))))
+
+;;;
+;;; gnus-icalendar-org
+;;;
+;;; TODO: this is an optional feature, and it's only available with org-mode
+;;; 7+, so will need to properly handle emacsen with no/outdated org-mode
+
+(require 'org)
+(require 'org-capture)
+
+(defgroup gnus-icalendar-org nil
+ "Settings for Calendar Event gnus/org integration."
+ :group 'gnus-icalendar
+ :prefix "gnus-icalendar-org-")
+
+(defcustom gnus-icalendar-org-capture-file nil
+ "Target Org file for storing captured calendar events."
+ :type 'file
+ :group 'gnus-icalendar-org)
+
+(defcustom gnus-icalendar-org-capture-headline nil
+ "Target outline in `gnus-icalendar-org-capture-file' for storing captured events."
+ :type '(repeat string)
+ :group 'gnus-icalendar-org)
+
+(defcustom gnus-icalendar-org-template-name "used by gnus-icalendar-org"
+ "Org-mode template name."
+ :type '(string)
+ :group 'gnus-icalendar-org)
+
+(defcustom gnus-icalendar-org-template-key "#"
+ "Org-mode template hotkey."
+ :type '(string)
+ :group 'gnus-icalendar-org)
+
+(defvar gnus-icalendar-org-enabled-p nil)
+
+
+(defmethod gnus-icalendar-event:org-repeat ((event gnus-icalendar-event))
+ "Return `org-mode' timestamp repeater string for recurring EVENT.
+Return nil for non-recurring EVENT."
+ (when (gnus-icalendar-event:recurring-p event)
+ (let* ((freq-map '(("HOURLY" . "h")
+ ("DAILY" . "d")
+ ("WEEKLY" . "w")
+ ("MONTHLY" . "m")
+ ("YEARLY" . "y")))
+ (org-freq (cdr (assoc (gnus-icalendar-event:recurring-freq event) freq-map))))
+
+ (when org-freq
+ (format "+%s%s" (gnus-icalendar-event:recurring-interval event) org-freq)))))
+
+(defmethod gnus-icalendar-event:org-timestamp ((event gnus-icalendar-event))
+ "Build `org-mode' timestamp from EVENT start/end dates and recurrence info."
+ (let* ((start (gnus-icalendar-event:start-time event))
+ (end (gnus-icalendar-event:end-time event))
+ (start-date (format-time-string "%Y-%m-%d %a" start t))
+ (start-time (format-time-string "%H:%M" start t))
+ (end-date (format-time-string "%Y-%m-%d %a" end t))
+ (end-time (format-time-string "%H:%M" end t))
+ (org-repeat (gnus-icalendar-event:org-repeat event))
+ (repeat (if org-repeat (concat " " org-repeat) "")))
+
+ (if (equal start-date end-date)
+ (format "<%s %s-%s%s>" start-date start-time end-time repeat)
+ (format "<%s %s>--<%s %s>" start-date start-time end-date end-time))))
+
+;; TODO: make the template customizable
+(defmethod gnus-icalendar-event->org-entry ((event gnus-icalendar-event) reply-status)
+ "Return string with new `org-mode' entry describing EVENT."
+ (with-temp-buffer
+ (org-mode)
+ (with-slots (organizer summary description location
+ recur uid) event
+ (let* ((reply (if reply-status (capitalize (symbol-name reply-status))
+ "Not replied yet"))
+ (props `(("ICAL_EVENT" . "t")
+ ("ID" . ,uid)
+ ("DT" . ,(gnus-icalendar-event:org-timestamp event))
+ ("ORGANIZER" . ,(gnus-icalendar-event:organizer event))
+ ("LOCATION" . ,(gnus-icalendar-event:location event))
+ ("RRULE" . ,(gnus-icalendar-event:recur event))
+ ("REPLY" . ,reply))))
+
+ (insert (format "* %s (%s)\n\n" summary location))
+ (mapc (lambda (prop)
+ (org-entry-put (point) (car prop) (cdr prop)))
+ props))
+
+ (when description
+ (save-restriction
+ (narrow-to-region (point) (point))
+ (insert description)
+ (indent-region (point-min) (point-max) 2)
+ (fill-region (point-min) (point-max))))
+
+ (buffer-string))))
+
+(defun gnus-icalendar--deactivate-org-timestamp (ts)
+ (replace-regexp-in-string "[<>]"
+ (lambda (m) (pcase m ("<" "[") (">" "]")))
+ ts))
+
+(defun gnus-icalendar-find-org-event-file (event &optional org-file)
+ "Return the name of the file containing EVENT org entry.
+Return nil when not found.
+
+All org agenda files are searched for the EVENT entry. When
+the optional ORG-FILE argument is specified, only that one file
+is searched."
+ (let ((uid (gnus-icalendar-event:uid event))
+ (files (or org-file (org-agenda-files t 'ifmode))))
+ (flet
+ ((find-event-in (file)
+ (org-check-agenda-file file)
+ (with-current-buffer (find-file-noselect file)
+ (let ((event-pos (org-find-entry-with-id uid)))
+ (when (and event-pos
+ (string= (cdr (assoc "ICAL_EVENT" (org-entry-properties event-pos)))
+ "t"))
+ (throw 'found file))))))
+
+ (gnus-icalendar-find-if #'find-event-in files))))
+
+
+(defun gnus-icalendar--show-org-event (event &optional org-file)
+ (let ((file (gnus-icalendar-find-org-event-file event org-file)))
+ (when file
+ (switch-to-buffer (find-file file))
+ (goto-char (org-find-entry-with-id (gnus-icalendar-event:uid event)))
+ (org-show-entry))))
+
+
+(defun gnus-icalendar--update-org-event (event reply-status &optional org-file)
+ (let ((file (gnus-icalendar-find-org-event-file event org-file)))
+ (when file
+ (with-current-buffer (find-file-noselect file)
+ (with-slots (uid summary description organizer location recur) event
+ (let ((event-pos (org-find-entry-with-id uid)))
+ (when event-pos
+ (goto-char event-pos)
+
+ ;; update the headline, keep todo, priority and tags, if any
+ (save-excursion
+ (let* ((priority (org-entry-get (point) "PRIORITY"))
+ (headline (delq nil (list
+ (org-entry-get (point) "TODO")
+ (when priority (format "[#%s]" priority))
+ (format "%s (%s)" summary location)
+ (org-entry-get (point) "TAGS")))))
+
+ (re-search-forward "^\\*+ " (line-end-position))
+ (delete-region (point) (line-end-position))
+ (insert (mapconcat #'identity headline " "))))
+
+ ;; update props and description
+ (let ((entry-end (org-entry-end-position))
+ (entry-outline-level (org-outline-level)))
+
+ ;; delete body of the entry, leave org drawers intact
+ (save-restriction
+ (org-narrow-to-element)
+ (goto-char entry-end)
+ (re-search-backward "^[\t ]*:END:")
+ (forward-line)
+ (delete-region (point) entry-end))
+
+ ;; put new event description in the entry body
+ (when description
+ (save-restriction
+ (narrow-to-region (point) (point))
+ (insert "\n" (replace-regexp-in-string "[\n]+$" "\n" description) "\n")
+ (indent-region (point-min) (point-max) (1+ entry-outline-level))
+ (fill-region (point-min) (point-max))))
+
+ ;; update entry properties
+ (org-entry-put event-pos "DT" (gnus-icalendar-event:org-timestamp event))
+ (org-entry-put event-pos "ORGANIZER" organizer)
+ (org-entry-put event-pos "LOCATION" location)
+ (org-entry-put event-pos "RRULE" recur)
+ (when reply-status (org-entry-put event-pos "REPLY"
+ (capitalize (symbol-name reply-status))))
+ (save-buffer)))))))))
+
+
+(defun gnus-icalendar--cancel-org-event (event &optional org-file)
+ (let ((file (gnus-icalendar-find-org-event-file event org-file)))
+ (when file
+ (with-current-buffer (find-file-noselect file)
+ (let ((event-pos (org-find-entry-with-id (gnus-icalendar-event:uid event))))
+ (when event-pos
+ (let ((ts (org-entry-get event-pos "DT")))
+ (when ts
+ (org-entry-put event-pos "DT" (gnus-icalendar--deactivate-org-timestamp ts))
+ (save-buffer)))))))))
+
+
+(defun gnus-icalendar--get-org-event-reply-status (event &optional org-file)
+ (let ((file (gnus-icalendar-find-org-event-file event org-file)))
+ (when file
+ (save-excursion
+ (with-current-buffer (find-file-noselect file)
+ (let ((event-pos (org-find-entry-with-id (gnus-icalendar-event:uid event))))
+ (org-entry-get event-pos "REPLY")))))))
+
+
+(defun gnus-icalendar-insinuate-org-templates ()
+ (unless (gnus-icalendar-find-if (lambda (x) (string= (cadr x) gnus-icalendar-org-template-name))
+ org-capture-templates)
+ (setq org-capture-templates
+ (append `((,gnus-icalendar-org-template-key
+ ,gnus-icalendar-org-template-name
+ entry
+ (file+olp ,gnus-icalendar-org-capture-file ,@gnus-icalendar-org-capture-headline)
+ "%i"
+ :immediate-finish t))
+ org-capture-templates))
+
+ ;; hide the template from interactive template selection list
+ ;; (org-capture)
+ ;; NOTE: doesn't work when capturing from string
+ ;; (when (boundp 'org-capture-templates-contexts)
+ ;; (push `(,gnus-icalendar-org-template-key "" ((in-mode . "gnus-article-mode")))
+ ;; org-capture-templates-contexts))
+ ))
+
+(defun gnus-icalendar:org-event-save (event reply-status)
+ (with-temp-buffer
+ (org-capture-string (gnus-icalendar-event->org-entry event reply-status)
+ gnus-icalendar-org-template-key)))
+
+(defun gnus-icalendar-show-org-agenda (event)
+ (let* ((time-delta (time-subtract (gnus-icalendar-event:end-time event)
+ (gnus-icalendar-event:start-time event)))
+ (duration-days (1+ (/ (+ (* (car time-delta) (expt 2 16))
+ (cadr time-delta))
+ 86400))))
+
+ (org-agenda-list nil (gnus-icalendar-event:start event) duration-days)))
+
+(defmethod gnus-icalendar-event:sync-to-org ((event gnus-icalendar-event-request) reply-status)
+ (if (gnus-icalendar-find-org-event-file event)
+ (gnus-icalendar--update-org-event event reply-status)
+ (gnus-icalendar:org-event-save event reply-status)))
+
+(defmethod gnus-icalendar-event:sync-to-org ((event gnus-icalendar-event-cancel))
+ (when (gnus-icalendar-find-org-event-file event)
+ (gnus-icalendar--cancel-org-event event)))
+
+(defun gnus-icalendar-org-setup ()
+ (if (and gnus-icalendar-org-capture-file gnus-icalendar-org-capture-headline)
+ (progn
+ (gnus-icalendar-insinuate-org-templates)
+ (setq gnus-icalendar-org-enabled-p t))
+ (message "Cannot enable Calendar->Org: missing capture file, headline")))
+
+;;;
+;;; gnus-icalendar
+;;;
+
+(defgroup gnus-icalendar nil
+ "Settings for inline display of iCalendar invitations."
+ :group 'gnus-article
+ :prefix "gnus-icalendar-")
+
+(defcustom gnus-icalendar-reply-bufname "*CAL*"
+ "Buffer used for building iCalendar invitation reply."
+ :type '(string)
+ :group 'gnus-icalendar)
+
+(make-variable-buffer-local
+ (defvar gnus-icalendar-reply-status nil))
+
+(make-variable-buffer-local
+ (defvar gnus-icalendar-event nil))
+
+(make-variable-buffer-local
+ (defvar gnus-icalendar-handle nil))
+
+(defvar gnus-icalendar-identities
+ (apply #'append
+ (mapcar (lambda (x) (if (listp x) x (list x)))
+ (list user-full-name (regexp-quote user-mail-address)
+ ; NOTE: this one can be a list
+ gnus-ignored-from-addresses))))
+
+;; TODO: make the template customizable
+(defmethod gnus-icalendar-event->gnus-calendar ((event gnus-icalendar-event) &optional reply-status)
+ "Format an overview of EVENT details."
+ (flet ((format-header (x)
+ (format "%-12s%s"
+ (propertize (concat (car x) ":") 'face 'bold)
+ (cadr x))))
+
+ (with-slots (organizer summary description location recur uid method rsvp) event
+ (let ((headers `(("Summary" ,summary)
+ ("Location" ,location)
+ ("Time" ,(gnus-icalendar-event:org-timestamp event))
+ ("Organizer" ,organizer)
+ ("Method" ,method))))
+
+ (when (and (not (gnus-icalendar-event-reply-p event)) rsvp)
+ (setq headers (append headers
+ `(("Status" ,(or reply-status "Not replied yet"))))))
+
+ (concat
+ (mapconcat #'format-header headers "\n")
+ "\n\n"
+ description)))))
+
+(defmacro gnus-icalendar-with-decoded-handle (handle &rest body)
+ "Execute BODY in buffer containing the decoded contents of HANDLE."
+ (let ((charset (make-symbol "charset")))
+ `(let ((,charset (cdr (assoc 'charset (mm-handle-type ,handle)))))
+ (with-temp-buffer
+ (mm-insert-part ,handle)
+ (when (string= ,charset "utf-8")
+ (mm-decode-coding-region (point-min) (point-max) 'utf-8))
+
+ ,@body))))
+
+
+(defun gnus-icalendar-event-from-handle (handle &optional attendee-name-or-email)
+ (gnus-icalendar-with-decoded-handle handle
+ (gnus-icalendar-event-from-buffer (current-buffer) attendee-name-or-email)))
+
+(defun gnus-icalendar-insert-button (text callback data)
+ ;; FIXME: the gnus-mime-button-map keymap does not make sense for this kind
+ ;; of button.
+ (let ((start (point)))
+ (gnus-add-text-properties
+ start
+ (progn
+ (insert "[ " text " ]")
+ (point))
+ `(gnus-callback
+ ,callback
+ keymap ,gnus-mime-button-map
+ face ,gnus-article-button-face
+ gnus-data ,data))
+ (widget-convert-button 'link start (point)
+ :action 'gnus-widget-press-button
+ :button-keymap gnus-widget-button-keymap)))
+
+(defun gnus-icalendar-send-buffer-by-mail (buffer-name subject)
+ (let ((message-signature nil))
+ (with-current-buffer gnus-summary-buffer
+ (gnus-summary-reply)
+ (message-goto-body)
+ (mml-insert-multipart "alternative")
+ (mml-insert-empty-tag 'part 'type "text/plain")
+ (mml-attach-buffer buffer-name "text/calendar; method=REPLY; charset=UTF-8")
+ (message-goto-subject)
+ (delete-region (line-beginning-position) (line-end-position))
+ (insert "Subject: " subject)
+ (message-send-and-exit))))
+
+(defun gnus-icalendar-reply (data)
+ (let* ((handle (car data))
+ (status (cadr data))
+ (event (caddr data))
+ (reply (gnus-icalendar-with-decoded-handle handle
+ (gnus-icalendar-event-reply-from-buffer
+ (current-buffer) status gnus-icalendar-identities))))
+
+ (when reply
+ (flet ((fold-icalendar-buffer ()
+ (goto-char (point-min))
+ (while (re-search-forward "^\\(.\\{72\\}\\)\\(.+\\)$" nil t)
+ (replace-match "\\1\n \\2")
+ (goto-char (line-beginning-position)))))
+ (let ((subject (concat (capitalize (symbol-name status))
+ ": " (gnus-icalendar-event:summary event))))
+
+ (with-current-buffer (get-buffer-create gnus-icalendar-reply-bufname)
+ (delete-region (point-min) (point-max))
+ (insert reply)
+ (fold-icalendar-buffer)
+ (gnus-icalendar-send-buffer-by-mail (buffer-name) subject))
+
+ ;; Back in article buffer
+ (setq-local gnus-icalendar-reply-status status)
+ (when gnus-icalendar-org-enabled-p
+ (gnus-icalendar--update-org-event event status)
+ ;; refresh article buffer to update the reply status
+ (with-current-buffer gnus-summary-buffer
+ (gnus-summary-show-article))))))))
+
+(defun gnus-icalendar-sync-event-to-org (event)
+ (gnus-icalendar-event:sync-to-org event gnus-icalendar-reply-status))
+
+(defmethod gnus-icalendar-event:inline-reply-buttons ((event gnus-icalendar-event) handle)
+ (when (gnus-icalendar-event:rsvp event)
+ `(("Accept" gnus-icalendar-reply (,handle accepted ,event))
+ ("Tentative" gnus-icalendar-reply (,handle tentative ,event))
+ ("Decline" gnus-icalendar-reply (,handle declined ,event)))))
+
+(defmethod gnus-icalendar-event:inline-reply-buttons ((event gnus-icalendar-event-reply) handle)
+ "No buttons for REPLY events."
+ nil)
+
+(defmethod gnus-icalendar-event:inline-reply-status ((event gnus-icalendar-event))
+ (or (when gnus-icalendar-org-enabled-p
+ (gnus-icalendar--get-org-event-reply-status event))
+ "Not replied yet"))
+
+(defmethod gnus-icalendar-event:inline-reply-status ((event gnus-icalendar-event-reply))
+ "No reply status for REPLY events."
+ nil)
+
+
+(defmethod gnus-icalendar-event:inline-org-buttons ((event gnus-icalendar-event))
+ (let* ((org-entry-exists-p (gnus-icalendar-find-org-event-file event))
+ (export-button-text (if org-entry-exists-p "Update Org Entry" "Export to Org")))
+
+ (delq nil (list
+ `("Show Agenda" gnus-icalendar-show-org-agenda ,event)
+ (when (gnus-icalendar-event-request-p event)
+ `(,export-button-text gnus-icalendar-sync-event-to-org ,event))
+ (when org-entry-exists-p
+ `("Show Org Entry" gnus-icalendar--show-org-event ,event))))))
+
+(defun gnus-icalendar-mm-inline (handle)
+ (let ((event (gnus-icalendar-event-from-handle handle gnus-icalendar-identities)))
+
+ (setq gnus-icalendar-reply-status nil)
+
+ (when event
+ (flet ((insert-button-group (buttons)
+ (when buttons
+ (mapc (lambda (x)
+ (apply 'gnus-icalendar-insert-button x)
+ (insert " "))
+ buttons)
+ (insert "\n\n"))))
+
+ (insert-button-group
+ (gnus-icalendar-event:inline-reply-buttons event handle))
+
+ (when gnus-icalendar-org-enabled-p
+ (insert-button-group (gnus-icalendar-event:inline-org-buttons event)))
+
+ (setq gnus-icalendar-event event
+ gnus-icalendar-handle handle)
+
+ (insert (gnus-icalendar-event->gnus-calendar
+ event
+ (gnus-icalendar-event:inline-reply-status event)))))))
+
+(defun gnus-icalendar-save-part (handle)
+ (let (event)
+ (when (and (equal (car (mm-handle-type handle)) "text/calendar")
+ (setq event (gnus-icalendar-event-from-handle handle gnus-icalendar-identities)))
+
+ (gnus-icalendar-event:sync-to-org event))))
+
+
+(defun gnus-icalendar-save-event ()
+ "Save the Calendar event in the text/calendar part under point."
+ (interactive)
+ (gnus-article-check-buffer)
+ (let ((data (get-text-property (point) 'gnus-data)))
+ (when data
+ (gnus-icalendar-save-part data))))
+
+(defun gnus-icalendar-reply-accept ()
+ "Accept invitation in the current article."
+ (interactive)
+ (with-current-buffer gnus-article-buffer
+ (gnus-icalendar-reply (list gnus-icalendar-handle 'accepted gnus-icalendar-event))
+ (setq-local gnus-icalendar-reply-status 'accepted)))
+
+(defun gnus-icalendar-reply-tentative ()
+ "Send tentative response to invitation in the current article."
+ (interactive)
+ (with-current-buffer gnus-article-buffer
+ (gnus-icalendar-reply (list gnus-icalendar-handle 'tentative gnus-icalendar-event))
+ (setq-local gnus-icalendar-reply-status 'tentative)))
+
+(defun gnus-icalendar-reply-decline ()
+ "Decline invitation in the current article."
+ (interactive)
+ (with-current-buffer gnus-article-buffer
+ (gnus-icalendar-reply (list gnus-icalendar-handle 'declined gnus-icalendar-event))
+ (setq-local gnus-icalendar-reply-status 'declined)))
+
+(defun gnus-icalendar-event-export ()
+ "Export calendar event to `org-mode', or update existing agenda entry."
+ (interactive)
+ (with-current-buffer gnus-article-buffer
+ (gnus-icalendar-sync-event-to-org gnus-icalendar-event))
+ ;; refresh article buffer in case the reply had been sent before initial org
+ ;; export
+ (with-current-buffer gnus-summary-buffer
+ (gnus-summary-show-article)))
+
+(defun gnus-icalendar-event-show ()
+ "Display `org-mode' agenda entry related to the calendar event."
+ (interactive)
+ (gnus-icalendar--show-org-event
+ (with-current-buffer gnus-article-buffer
+ gnus-icalendar-event)))
+
+(defun gnus-icalendar-event-check-agenda ()
+ "Display `org-mode' agenda for days between event start and end dates."
+ (interactive)
+ (gnus-icalendar-show-org-agenda
+ (with-current-buffer gnus-article-buffer gnus-icalendar-event)))
+
+(defun gnus-icalendar-setup ()
+ (add-to-list 'mm-inlined-types "text/calendar")
+ (add-to-list 'mm-automatic-display "text/calendar")
+ (add-to-list 'mm-inline-media-tests '("text/calendar" gnus-icalendar-mm-inline identity))
+
+ (gnus-define-keys (gnus-summary-calendar-map "i" gnus-summary-mode-map)
+ "a" gnus-icalendar-reply-accept
+ "t" gnus-icalendar-reply-tentative
+ "d" gnus-icalendar-reply-decline
+ "c" gnus-icalendar-event-check-agenda
+ "e" gnus-icalendar-event-export
+ "s" gnus-icalendar-event-show)
+
+ (require 'gnus-art)
+ (add-to-list 'gnus-mime-action-alist
+ (cons "save calendar event" 'gnus-icalendar-save-event)
+ t))
+
+(provide 'gnus-icalendar)
+
+;;; gnus-icalendar.el ends here
diff --git a/lisp/gnus/gnus-int.el b/lisp/gnus/gnus-int.el
index 2378b598eeb..6aa874f0347 100644
--- a/lisp/gnus/gnus-int.el
+++ b/lisp/gnus/gnus-int.el
@@ -582,8 +582,8 @@ This is the string that Gnus uses to identify the group."
(gnus-group-method group)))
(defun gnus-warp-to-article ()
- "Warps from an article in a virtual group to the article in its
-real group. Does nothing on a real group."
+ "Jump from an article in a virtual group to the article in its real group.
+Does nothing in a real group."
(interactive)
(when (gnus-virtual-group-p gnus-newsgroup-name)
(let ((gnus-command-method
diff --git a/lisp/gnus/gnus-score.el b/lisp/gnus/gnus-score.el
index da5c31325bd..5f91246761e 100644
--- a/lisp/gnus/gnus-score.el
+++ b/lisp/gnus/gnus-score.el
@@ -1071,10 +1071,15 @@ EXTRA is the possible non-standard header."
(push (cons article n) gnus-newsgroup-scored)))
(gnus-summary-update-line)))
-(defun gnus-summary-current-score ()
- "Return the score of the current article."
- (interactive)
- (gnus-message 1 "%s" (gnus-summary-article-score)))
+(defun gnus-summary-current-score (arg)
+ "Return the score of the current article.
+ With prefix ARG, return the total score of the current (sub)thread."
+ (interactive "P")
+ (gnus-message 1 "%s" (if arg
+ (gnus-thread-total-score
+ (gnus-id-to-thread
+ (mail-header-id (gnus-summary-article-header))))
+ (gnus-summary-article-score))))
(defun gnus-score-change-score-file (file)
"Change current score alist."
diff --git a/lisp/gnus/gnus-start.el b/lisp/gnus/gnus-start.el
index 05cf290cac9..9f3f469ad43 100644
--- a/lisp/gnus/gnus-start.el
+++ b/lisp/gnus/gnus-start.el
@@ -944,7 +944,8 @@ If REGEXP is given, lines that match it will be deleted."
(when (and gnus-dribble-buffer
(buffer-name gnus-dribble-buffer))
(with-current-buffer gnus-dribble-buffer
- (save-buffer))))
+ (when (> (buffer-size) 0)
+ (save-buffer)))))
(defun gnus-dribble-clear ()
(when (gnus-buffer-exists-p gnus-dribble-buffer)
@@ -1807,6 +1808,9 @@ backend check whether the group actually exists."
(or (not (gnus-agent-method-p method))
(gnus-online method)))
(gnus-finish-retrieve-group-infos method infos early-data)
+ ;; We may have altered the data now, so mark the dribble buffer
+ ;; as dirty so that it gets saved.
+ (gnus-dribble-touch)
(gnus-agent-save-active method))
;; Most backends have -retrieve-groups.
((gnus-check-backend-function 'retrieve-groups (car method))
diff --git a/lisp/gnus/gnus-sum.el b/lisp/gnus/gnus-sum.el
index a7269baee74..94f4e703180 100644
--- a/lisp/gnus/gnus-sum.el
+++ b/lisp/gnus/gnus-sum.el
@@ -2359,7 +2359,8 @@ increase the score of each group you read."
["Mark above" gnus-summary-mark-above t]
["Tick above" gnus-summary-tick-above t]
["Clear above" gnus-summary-clear-above t])
- ["Current score" gnus-summary-current-score t]
+ ["Current article score" gnus-summary-current-score t]
+ ["Current thread score" (gnus-summary-current-score 'total) t]
["Set score" gnus-summary-set-score t]
["Switch current score file..." gnus-score-change-score-file t]
["Set mark below..." gnus-score-set-mark-below t]
diff --git a/lisp/gnus/gnus-util.el b/lisp/gnus/gnus-util.el
index 0f0e9675c71..1d2ab2da248 100644
--- a/lisp/gnus/gnus-util.el
+++ b/lisp/gnus/gnus-util.el
@@ -1558,12 +1558,15 @@ SPEC is a predicate specifier that contains stuff like `or', `and',
"Call standard `completing-read-function'."
(let ((completion-styles gnus-completion-styles))
(completing-read prompt
- ;; Old XEmacs (at least 21.4) expect an alist,
- ;; in which the car of each element is a string,
- ;; for collection.
- (mapcar (lambda (elem)
- (list (format "%s" (or (car-safe elem) elem))))
- collection)
+ (if (featurep 'xemacs)
+ ;; Old XEmacs (at least 21.4) expect an alist,
+ ;; in which the car of each element is a string,
+ ;; for collection.
+ (mapcar
+ (lambda (elem)
+ (list (format "%s" (or (car-safe elem) elem))))
+ collection)
+ collection)
nil require-match initial-input history def)))
(autoload 'ido-completing-read "ido")
diff --git a/lisp/gnus/gnus-uu.el b/lisp/gnus/gnus-uu.el
index c50dcde0034..16ed4f17801 100644
--- a/lisp/gnus/gnus-uu.el
+++ b/lisp/gnus/gnus-uu.el
@@ -640,7 +640,7 @@ When called interactively, prompt for REGEXP."
(let ((level (gnus-summary-thread-level)))
(while (and (gnus-summary-set-process-mark
(gnus-summary-article-number))
- (zerop (gnus-summary-next-subject 1 nil t))
+ (zerop (forward-line 1))
(> (gnus-summary-thread-level) level)))))
(gnus-summary-position-point))
@@ -650,7 +650,7 @@ When called interactively, prompt for REGEXP."
(let ((level (gnus-summary-thread-level)))
(while (and (gnus-summary-remove-process-mark
(gnus-summary-article-number))
- (zerop (gnus-summary-next-subject 1))
+ (zerop (forward-line 1))
(> (gnus-summary-thread-level) level))))
(gnus-summary-position-point))
diff --git a/lisp/gnus/message.el b/lisp/gnus/message.el
index b35eb9dca12..d6d6b3f8bed 100644
--- a/lisp/gnus/message.el
+++ b/lisp/gnus/message.el
@@ -264,7 +264,7 @@ This is a list of regexps and regexp matches."
:type 'sexp)
(defcustom message-ignored-news-headers
- "^NNTP-Posting-Host:\\|^Xref:\\|^[BGF]cc:\\|^Resent-Fcc:\\|^X-Draft-From:\\|^X-Gnus-Agent-Meta-Information:\\|^X-Message-SMTP-Method:"
+ "^NNTP-Posting-Host:\\|^Xref:\\|^[BGF]cc:\\|^Resent-Fcc:\\|^X-Draft-From:\\|^X-Gnus-Agent-Meta-Information:\\|^X-Message-SMTP-Method:\\|^X-Gnus-Delayed:"
"*Regexp of headers to be removed unconditionally before posting."
:group 'message-news
:group 'message-headers
diff --git a/lisp/gnus/mm-decode.el b/lisp/gnus/mm-decode.el
index 98be1c5def2..7274708f014 100644
--- a/lisp/gnus/mm-decode.el
+++ b/lisp/gnus/mm-decode.el
@@ -63,6 +63,18 @@
:group 'news
:group 'multimedia)
+(defface mm-command-output
+ '((((class color)
+ (background dark))
+ (:foreground "ForestGreen"))
+ (((class color)
+ (background light))
+ (:foreground "red3"))
+ (t
+ (:italic t)))
+ "Face used for displaying output from commands."
+ :group 'mime-display)
+
;;; Convenience macros.
(defmacro mm-handle-buffer (handle)
@@ -983,9 +995,12 @@ external if displayed external."
(let ((buffer-read-only nil)
(point (point)))
(forward-line 2)
- (mm-insert-inline
- handle (with-current-buffer buffer
- (buffer-string)))
+ (let ((start (point)))
+ (mm-insert-inline
+ handle (with-current-buffer buffer
+ (buffer-string)))
+ (put-text-property start (point)
+ 'face 'mm-command-output))
(goto-char point))))
(when (buffer-live-p buffer)
(kill-buffer buffer)))
diff --git a/lisp/gnus/mml2015.el b/lisp/gnus/mml2015.el
index 2c2187a5f8d..3efa5c23bb3 100644
--- a/lisp/gnus/mml2015.el
+++ b/lisp/gnus/mml2015.el
@@ -885,17 +885,19 @@ If set, it overrides the setting of `mml2015-sign-with-sender'."
(defun mml2015-epg-key-image-to-string (key-id)
"Return a string with the image of a key, if any"
- (let* ((result "")
- (key-image (mml2015-epg-key-image key-id)))
- (when key-image
- (setq result " ")
- (put-text-property
- 1 2 'display
- (gnus-rescale-image key-image
- (cons mml2015-maximum-key-image-dimension
- mml2015-maximum-key-image-dimension))
- result))
- result))
+ (let ((key-image (mml2015-epg-key-image key-id)))
+ (if (not key-image)
+ ""
+ (condition-case error
+ (let ((result " "))
+ (put-text-property
+ 1 2 'display
+ (gnus-rescale-image key-image
+ (cons mml2015-maximum-key-image-dimension
+ mml2015-maximum-key-image-dimension))
+ result)
+ result)
+ (error "")))))
(defun mml2015-epg-signature-to-string (signature)
(concat (epg-signature-to-string signature)
diff --git a/lisp/gnus/nnmbox.el b/lisp/gnus/nnmbox.el
index 3228eacdd0a..c605541e7f1 100644
--- a/lisp/gnus/nnmbox.el
+++ b/lisp/gnus/nnmbox.el
@@ -148,28 +148,29 @@
(deffoo nnmbox-request-article (article &optional newsgroup server buffer)
(nnmbox-possibly-change-newsgroup newsgroup server)
(with-current-buffer nnmbox-mbox-buffer
- (when (nnmbox-find-article article)
- (let (start stop)
- (re-search-backward (concat "^" message-unix-mail-delimiter) nil t)
- (setq start (point))
- (forward-line 1)
- (setq stop (if (re-search-forward (concat "^"
- message-unix-mail-delimiter)
- nil 'move)
- (match-beginning 0)
- (point)))
- (let ((nntp-server-buffer (or buffer nntp-server-buffer)))
- (set-buffer nntp-server-buffer)
- (erase-buffer)
- (insert-buffer-substring nnmbox-mbox-buffer start stop)
- (goto-char (point-min))
- (while (looking-at "From ")
- (delete-char 5)
- (insert "X-From-Line: ")
- (forward-line 1))
- (if (numberp article)
- (cons nnmbox-current-group article)
- (nnmbox-article-group-number nil)))))))
+ (save-excursion
+ (when (nnmbox-find-article article)
+ (let (start stop)
+ (re-search-backward (concat "^" message-unix-mail-delimiter) nil t)
+ (setq start (point))
+ (forward-line 1)
+ (setq stop (if (re-search-forward (concat "^"
+ message-unix-mail-delimiter)
+ nil 'move)
+ (match-beginning 0)
+ (point)))
+ (let ((nntp-server-buffer (or buffer nntp-server-buffer)))
+ (set-buffer nntp-server-buffer)
+ (erase-buffer)
+ (insert-buffer-substring nnmbox-mbox-buffer start stop)
+ (goto-char (point-min))
+ (while (looking-at "From ")
+ (delete-char 5)
+ (insert "X-From-Line: ")
+ (forward-line 1))
+ (if (numberp article)
+ (cons nnmbox-current-group article)
+ (nnmbox-article-group-number nil))))))))
(deffoo nnmbox-request-group (group &optional server dont-check info)
(nnmbox-possibly-change-newsgroup nil server)
@@ -255,14 +256,14 @@
(if (setq is-old
(nnmail-expired-article-p
newsgroup
- (buffer-substring
- (point) (progn (end-of-line) (point))) force))
+ (buffer-substring (point) (line-end-position))
+ force))
(progn
(unless (eq nnmail-expiry-target 'delete)
(with-temp-buffer
(nnmbox-request-article (car articles)
- newsgroup server
- (current-buffer))
+ newsgroup server
+ (current-buffer))
(let ((nnml-current-directory nil))
(nnmail-expiry-target-group
nnmail-expiry-target newsgroup)))
diff --git a/lisp/gnus/nnrss.el b/lisp/gnus/nnrss.el
index b1e5c4cc9fd..80bb7c4f7df 100644
--- a/lisp/gnus/nnrss.el
+++ b/lisp/gnus/nnrss.el
@@ -933,30 +933,30 @@ whether they are `offsite' or `onsite'."
rss-offsite-in rdf-offsite-in xml-offsite-in)))
(defun nnrss-discover-feed (url)
- "Given a page, find an RSS feed using Mark Pilgrim's
-`ultra-liberal rss locator'."
-
+ "Given a page, find an RSS feed.
+Use Mark Pilgrim's `ultra-liberal rss locator'."
(let ((parsed-page (nnrss-fetch url)))
-
-;; 1. if this url is the rss, use it.
+ ;; 1. if this url is the rss, use it.
(if (nnrss-rss-p parsed-page)
(let ((rss-ns (nnrss-get-namespace-prefix parsed-page "http://purl.org/rss/1.0/")))
(nnrss-rss-title-description rss-ns parsed-page url))
-;; 2. look for the <link rel="alternate"
-;; type="application/rss+xml" and use that if it is there.
+ ;; 2. look for the <link rel="alternate"
+ ;; type="application/rss+xml" and use that if it is there.
(let ((links (nnrss-get-rsslinks parsed-page)))
(if links
(let* ((xml (nnrss-fetch
(cdr (assoc 'href (cadar links)))))
- (rss-ns (nnrss-get-namespace-prefix xml "http://purl.org/rss/1.0/")))
- (nnrss-rss-title-description rss-ns xml (cdr (assoc 'href (cadar links)))))
-
-;; 3. look for links on the site in the following order:
-;; - onsite links ending in .rss, .rdf, or .xml
-;; - onsite links containing any of the above
-;; - offsite links ending in .rss, .rdf, or .xml
-;; - offsite links containing any of the above
+ (rss-ns (nnrss-get-namespace-prefix
+ xml "http://purl.org/rss/1.0/")))
+ (nnrss-rss-title-description
+ rss-ns xml (cdr (assoc 'href (cadar links)))))
+
+ ;; 3. look for links on the site in the following order:
+ ;; - onsite links ending in .rss, .rdf, or .xml
+ ;; - onsite links containing any of the above
+ ;; - offsite links ending in .rss, .rdf, or .xml
+ ;; - offsite links containing any of the above
(let* ((base-uri (progn (string-match ".*://[^/]+/?" url)
(match-string 0 url)))
(hrefs (nnrss-order-hrefs
@@ -969,9 +969,9 @@ whether they are `offsite' or `onsite'."
(setq rss-link (nnrss-rss-title-description
rss-ns href-data (car hrefs))))
(setq hrefs (cdr hrefs)))))
- (if rss-link rss-link
-
-;; 4. check syndic8
+ (if rss-link
+ rss-link
+ ;; 4. check syndic8
(nnrss-find-rss-via-syndic8 url))))))))
(defun nnrss-find-rss-via-syndic8 (url)
diff --git a/lisp/gnus/rfc2047.el b/lisp/gnus/rfc2047.el
index a9498d46e79..ebf597423b8 100644
--- a/lisp/gnus/rfc2047.el
+++ b/lisp/gnus/rfc2047.el
@@ -235,85 +235,96 @@ Should be called narrowed to the head of the message."
(interactive "*")
(save-excursion
(goto-char (point-min))
- (let (alist elem method)
+ (let (alist elem method charsets)
(while (not (eobp))
(save-restriction
(rfc2047-narrow-to-field)
(setq method nil
- alist rfc2047-header-encoding-alist)
- (while (setq elem (pop alist))
- (when (or (and (stringp (car elem))
- (looking-at (car elem)))
- (eq (car elem) t))
- (setq alist nil
- method (cdr elem))))
- (if (not (rfc2047-encodable-p))
- (prog2
- (when (eq method 'address-mime)
- (rfc2047-quote-special-characters-in-quoted-strings))
- (if (and (eq (mm-body-7-or-8) '8bit)
- (mm-multibyte-p)
- (mm-coding-system-p
- (car message-posting-charset)))
- ;; 8 bit must be decoded.
- (mm-encode-coding-region
- (point-min) (point-max)
- (mm-charset-to-coding-system
- (car message-posting-charset))))
- ;; No encoding necessary, but folding is nice
- (when nil
- (rfc2047-fold-region
- (save-excursion
- (goto-char (point-min))
- (skip-chars-forward "^:")
- (when (looking-at ": ")
- (forward-char 2))
- (point))
- (point-max))))
- ;; We found something that may perhaps be encoded.
- (re-search-forward "^[^:]+: *" nil t)
- (cond
- ((eq method 'address-mime)
- (rfc2047-encode-region (point) (point-max)))
- ((eq method 'mime)
- (let ((rfc2047-encoding-type 'mime))
- (rfc2047-encode-region (point) (point-max))))
- ((eq method 'default)
- (if (and (featurep 'mule)
- (if (boundp 'enable-multibyte-characters)
- (default-value 'enable-multibyte-characters))
- mail-parse-charset)
- (mm-encode-coding-region (point) (point-max)
- mail-parse-charset)))
- ;; We get this when CC'ing messages to newsgroups with
- ;; 8-bit names. The group name mail copy just got
- ;; unconditionally encoded. Previously, it would ask
- ;; whether to encode, which was quite confusing for the
- ;; user. If the new behavior is wrong, tell me. I have
- ;; left the old code commented out below.
- ;; -- Per Abrahamsen <abraham@dina.kvl.dk> Date: 2001-10-07.
- ;; Modified by Dave Love, with the commented-out code changed
- ;; in accordance with changes elsewhere.
- ((null method)
- (rfc2047-encode-region (point) (point-max)))
-;;; ((null method)
-;;; (if (or (message-options-get
-;;; 'rfc2047-encode-message-header-encode-any)
-;;; (message-options-set
-;;; 'rfc2047-encode-message-header-encode-any
-;;; (y-or-n-p
-;;; "Some texts are not encoded. Encode anyway?")))
-;;; (rfc2047-encode-region (point-min) (point-max))
-;;; (error "Cannot send unencoded text")))
- ((mm-coding-system-p method)
- (if (or (and (featurep 'mule)
- (if (boundp 'enable-multibyte-characters)
- (default-value 'enable-multibyte-characters)))
- (featurep 'file-coding))
- (mm-encode-coding-region (point) (point-max) method)))
- ;; Hm.
- (t)))
- (goto-char (point-max)))))))
+ alist rfc2047-header-encoding-alist
+ charsets (mm-find-mime-charset-region (point-min) (point-max)))
+ ;; M$ Outlook boycotts decoding of a header if it consists
+ ;; of two or more encoded words and those charsets differ;
+ ;; it seems to decode all words in a header from a charset
+ ;; found first in the header. So, we unify the charsets into
+ ;; a single one used for encoding the whole text in a header.
+ (let ((mm-coding-system-priorities
+ (if (= (length charsets) 1)
+ (cons (mm-charset-to-coding-system (car charsets))
+ mm-coding-system-priorities)
+ mm-coding-system-priorities)))
+ (while (setq elem (pop alist))
+ (when (or (and (stringp (car elem))
+ (looking-at (car elem)))
+ (eq (car elem) t))
+ (setq alist nil
+ method (cdr elem))))
+ (if (not (rfc2047-encodable-p))
+ (prog2
+ (when (eq method 'address-mime)
+ (rfc2047-quote-special-characters-in-quoted-strings))
+ (if (and (eq (mm-body-7-or-8) '8bit)
+ (mm-multibyte-p)
+ (mm-coding-system-p
+ (car message-posting-charset)))
+ ;; 8 bit must be decoded.
+ (mm-encode-coding-region
+ (point-min) (point-max)
+ (mm-charset-to-coding-system
+ (car message-posting-charset))))
+ ;; No encoding necessary, but folding is nice
+ (when nil
+ (rfc2047-fold-region
+ (save-excursion
+ (goto-char (point-min))
+ (skip-chars-forward "^:")
+ (when (looking-at ": ")
+ (forward-char 2))
+ (point))
+ (point-max))))
+ ;; We found something that may perhaps be encoded.
+ (re-search-forward "^[^:]+: *" nil t)
+ (cond
+ ((eq method 'address-mime)
+ (rfc2047-encode-region (point) (point-max)))
+ ((eq method 'mime)
+ (let ((rfc2047-encoding-type 'mime))
+ (rfc2047-encode-region (point) (point-max))))
+ ((eq method 'default)
+ (if (and (featurep 'mule)
+ (if (boundp 'enable-multibyte-characters)
+ (default-value 'enable-multibyte-characters))
+ mail-parse-charset)
+ (mm-encode-coding-region (point) (point-max)
+ mail-parse-charset)))
+ ;; We get this when CC'ing messages to newsgroups with
+ ;; 8-bit names. The group name mail copy just got
+ ;; unconditionally encoded. Previously, it would ask
+ ;; whether to encode, which was quite confusing for the
+ ;; user. If the new behavior is wrong, tell me. I have
+ ;; left the old code commented out below.
+ ;; -- Per Abrahamsen <abraham@dina.kvl.dk> Date: 2001-10-07.
+ ;; Modified by Dave Love, with the commented-out code changed
+ ;; in accordance with changes elsewhere.
+ ((null method)
+ (rfc2047-encode-region (point) (point-max)))
+;;; ((null method)
+;;; (if (or (message-options-get
+;;; 'rfc2047-encode-message-header-encode-any)
+;;; (message-options-set
+;;; 'rfc2047-encode-message-header-encode-any
+;;; (y-or-n-p
+;;; "Some texts are not encoded. Encode anyway?")))
+;;; (rfc2047-encode-region (point-min) (point-max))
+;;; (error "Cannot send unencoded text")))
+ ((mm-coding-system-p method)
+ (if (or (and (featurep 'mule)
+ (if (boundp 'enable-multibyte-characters)
+ (default-value 'enable-multibyte-characters)))
+ (featurep 'file-coding))
+ (mm-encode-coding-region (point) (point-max) method)))
+ ;; Hm.
+ (t)))
+ (goto-char (point-max))))))))
;; Fixme: This, and the require below may not be the Right Thing, but
;; should be safe just before release. -- fx 2001-02-08
diff --git a/lisp/ibuf-ext.el b/lisp/ibuf-ext.el
index de36c6c86ce..1ba0b0f0779 100644
--- a/lisp/ibuf-ext.el
+++ b/lisp/ibuf-ext.el
@@ -1050,7 +1050,7 @@ currently used by buffers."
"Toggle current view to buffers with filename matching QUALIFIER."
(:description "filename"
:reader (read-from-minibuffer "Filter by filename (regexp): "))
- (ibuffer-awhen (buffer-local-value 'buffer-file-name buf)
+ (ibuffer-awhen (with-current-buffer buf (ibuffer-buffer-file-name))
(string-match qualifier it)))
;;;###autoload (autoload 'ibuffer-filter-by-size-gt "ibuf-ext")
diff --git a/lisp/ibuffer.el b/lisp/ibuffer.el
index a4f18201a3f..09d7c143346 100644
--- a/lisp/ibuffer.el
+++ b/lisp/ibuffer.el
@@ -2631,29 +2631,7 @@ will be inserted before the group at point."
;;; Start of automatically extracted autoloads.
-;;;### (autoloads (ibuffer-do-occur ibuffer-mark-dired-buffers ibuffer-mark-read-only-buffers
-;;;;;; ibuffer-mark-special-buffers ibuffer-mark-old-buffers ibuffer-mark-compressed-file-buffers
-;;;;;; ibuffer-mark-help-buffers ibuffer-mark-dissociated-buffers
-;;;;;; ibuffer-mark-unsaved-buffers ibuffer-mark-modified-buffers
-;;;;;; ibuffer-mark-by-mode ibuffer-mark-by-file-name-regexp ibuffer-mark-by-mode-regexp
-;;;;;; ibuffer-mark-by-name-regexp ibuffer-copy-filename-as-kill
-;;;;;; ibuffer-diff-with-file ibuffer-jump-to-buffer ibuffer-do-kill-lines
-;;;;;; ibuffer-backwards-next-marked ibuffer-forward-next-marked
-;;;;;; ibuffer-add-to-tmp-show ibuffer-add-to-tmp-hide ibuffer-bs-show
-;;;;;; ibuffer-invert-sorting ibuffer-toggle-sorting-mode ibuffer-switch-to-saved-filters
-;;;;;; ibuffer-add-saved-filters ibuffer-delete-saved-filters ibuffer-save-filters
-;;;;;; ibuffer-or-filter ibuffer-negate-filter ibuffer-exchange-filters
-;;;;;; ibuffer-decompose-filter ibuffer-pop-filter ibuffer-filter-disable
-;;;;;; ibuffer-switch-to-saved-filter-groups ibuffer-delete-saved-filter-groups
-;;;;;; ibuffer-save-filter-groups ibuffer-yank-filter-group ibuffer-yank
-;;;;;; ibuffer-kill-line ibuffer-kill-filter-group ibuffer-jump-to-filter-group
-;;;;;; ibuffer-clear-filter-groups ibuffer-decompose-filter-group
-;;;;;; ibuffer-pop-filter-group ibuffer-set-filter-groups-by-mode
-;;;;;; ibuffer-filters-to-filter-group ibuffer-included-in-filters-p
-;;;;;; ibuffer-backward-filter-group ibuffer-forward-filter-group
-;;;;;; ibuffer-toggle-filter-group ibuffer-mouse-toggle-filter-group
-;;;;;; ibuffer-interactive-filter-by-mode ibuffer-mouse-filter-by-mode
-;;;;;; ibuffer-auto-mode) "ibuf-ext" "ibuf-ext.el" "2c628e6cde385119c5f7b43cc1efe1a1")
+;;;### (autoloads nil "ibuf-ext" "ibuf-ext.el" "d06b2735a74954e0c6922a811de7608c")
;;; Generated autoloads from ibuf-ext.el
(autoload 'ibuffer-auto-mode "ibuf-ext" "\
diff --git a/lisp/mh-e/mh-e.el b/lisp/mh-e/mh-e.el
index 303d817dede..752e2c5e9c6 100644
--- a/lisp/mh-e/mh-e.el
+++ b/lisp/mh-e/mh-e.el
@@ -1019,6 +1019,7 @@ windows in the frame are removed."
(when delete-other-windows-flag
(delete-other-windows)))
+;; FIXME: Maybe out of date? --xfq
(if (boundp 'customize-package-emacs-version-alist)
(add-to-list 'customize-package-emacs-version-alist
'(MH-E ("6.0" . "22.1") ("6.1" . "22.1") ("7.0" . "22.1")
diff --git a/lisp/minibuffer.el b/lisp/minibuffer.el
index 8bcf3afae05..e07d28a54d0 100644
--- a/lisp/minibuffer.el
+++ b/lisp/minibuffer.el
@@ -568,6 +568,17 @@ for use at QPOS."
(cl-assert (string-prefix-p prefix completion 'ignore-case) t)
(let* ((new (substring completion (length prefix)))
(qnew (funcall qfun new))
+ (qprefix
+ (if (not completion-ignore-case)
+ qprefix
+ ;; Make qprefix inherit the case from `completion'.
+ (let* ((rest (substring completion
+ 0 (length prefix)))
+ (qrest (funcall qfun rest)))
+ (if (completion--string-equal-p qprefix qrest)
+ (propertize qrest 'face
+ 'completions-common-part)
+ qprefix))))
(qcompletion (concat qprefix qnew)))
;; FIXME: Similarly here, Cygwin's mapping trips this
;; assertion.
diff --git a/lisp/net/browse-url.el b/lisp/net/browse-url.el
index feae435c779..70173dbc0b3 100644
--- a/lisp/net/browse-url.el
+++ b/lisp/net/browse-url.el
@@ -816,8 +816,8 @@ first, if that exists."
;; When connected to various displays, be careful to use the display of
;; the currently selected frame, rather than the original start display,
;; which may not even exist any more.
- (if (stringp (frame-parameter (selected-frame) 'display))
- (setenv "DISPLAY" (frame-parameter (selected-frame) 'display)))
+ (if (stringp (frame-parameter nil 'display))
+ (setenv "DISPLAY" (frame-parameter nil 'display)))
(if (and (consp function)
(not (functionp function)))
;; The `function' can be an alist; look down it for first match
diff --git a/lisp/net/eww.el b/lisp/net/eww.el
index d65932ae7c9..70c11c3201f 100644
--- a/lisp/net/eww.el
+++ b/lisp/net/eww.el
@@ -309,7 +309,7 @@ word(s) will be searched for via `eww-search-prefix'."
(goto-char (point-min))))
(defun eww-setup-buffer ()
- (pop-to-buffer (get-buffer-create "*eww*"))
+ (switch-to-buffer (get-buffer-create "*eww*"))
(let ((inhibit-read-only t))
(remove-overlays)
(erase-buffer))
diff --git a/lisp/net/network-stream.el b/lisp/net/network-stream.el
index fd21997ba28..ab7d02cc802 100644
--- a/lisp/net/network-stream.el
+++ b/lisp/net/network-stream.el
@@ -128,6 +128,9 @@ values:
:use-starttls-if-possible is a boolean that says to do opportunistic
STARTTLS upgrades even if Emacs doesn't have built-in TLS functionality.
+:nogreeting is a boolean that can be used to inhibit waiting for
+a greeting from the server.
+
:nowait is a boolean that says the connection should be made
asynchronously, if possible."
(unless (featurep 'make-network-process)
@@ -211,7 +214,8 @@ STARTTLS upgrades even if Emacs doesn't have built-in TLS functionality.
;; Return (STREAM GREETING CAPABILITIES RESULTING-TYPE)
(stream (make-network-process :name name :buffer buffer
:host host :service service))
- (greeting (network-stream-get-response stream start eoc))
+ (greeting (and (not (plist-get parameters :nogreeting))
+ (network-stream-get-response stream start eoc)))
(capabilities (network-stream-command stream capability-command
eo-capa))
(resulting-type 'plain)
diff --git a/lisp/net/shr.el b/lisp/net/shr.el
index 6ddf8d2af90..89791511e09 100644
--- a/lisp/net/shr.el
+++ b/lisp/net/shr.el
@@ -143,7 +143,7 @@ cid: URL as the argument.")
(define-key map [tab] 'shr-next-link)
(define-key map [backtab] 'shr-previous-link)
(define-key map [follow-link] 'mouse-face)
- (define-key map [mouse-2] 'shr-mouse-browse-url)
+ (define-key map [mouse-2] 'shr-browse-url)
(define-key map "I" 'shr-insert-image)
(define-key map "w" 'shr-copy-url)
(define-key map "u" 'shr-copy-url)
@@ -664,10 +664,11 @@ size, and full-buffer size."
(mouse-set-point ev)
(shr-browse-url))
-(defun shr-browse-url (&optional external)
+(defun shr-browse-url (&optional external mouse-event)
"Browse the URL under point.
If EXTERNAL, browse the URL using `shr-external-browser'."
- (interactive "P")
+ (interactive (list current-prefix-arg last-nonmenu-event))
+ (mouse-set-point mouse-event)
(let ((url (get-text-property (point) 'shr-url)))
(cond
((not url)
@@ -832,6 +833,8 @@ START, and END. Note that START and END should be markers."
start (point)
(list 'shr-url url
'help-echo (if title (format "%s (%s)" url title) url)
+ 'follow-link t
+ 'mouse-face 'highlight
'keymap shr-map)))
(defun shr-encode-url (url)
diff --git a/lisp/net/tramp-adb.el b/lisp/net/tramp-adb.el
index 82b017fa230..33e9e5aa44b 100644
--- a/lisp/net/tramp-adb.el
+++ b/lisp/net/tramp-adb.el
@@ -85,53 +85,74 @@
(cons 'tramp-adb-file-name-p 'tramp-adb-file-name-handler))
(defconst tramp-adb-file-name-handler-alist
- '((directory-file-name . tramp-handle-directory-file-name)
+ '((access-file . ignore)
+ (add-name-to-file . tramp-adb-handle-copy-file)
+ ;; `byte-compiler-base-file-name' performed by default handler.
+ ;; `copy-directory' performed by default handler.
+ (copy-file . tramp-adb-handle-copy-file)
+ (delete-directory . tramp-adb-handle-delete-directory)
+ (delete-file . tramp-adb-handle-delete-file)
+ ;; `diff-latest-backup-file' performed by default handler.
+ (directory-file-name . tramp-handle-directory-file-name)
+ (directory-files . tramp-handle-directory-files)
+ (directory-files-and-attributes
+ . tramp-adb-handle-directory-files-and-attributes)
+ (dired-call-process . ignore)
+ (dired-compress-file . ignore)
(dired-uncache . tramp-handle-dired-uncache)
+ (expand-file-name . tramp-adb-handle-expand-file-name)
+ (file-accessible-directory-p . tramp-handle-file-accessible-directory-p)
+ (file-acl . ignore)
+ (file-attributes . tramp-adb-handle-file-attributes)
+ (file-directory-p . tramp-adb-handle-file-directory-p)
+ ;; `file-equal-p' performed by default handler.
+ ;; FIXME: This is too sloppy.
+ (file-executable-p . tramp-handle-file-exists-p)
+ (file-exists-p . tramp-handle-file-exists-p)
+ ;; `file-in-directory-p' performed by default handler.
+ (file-local-copy . tramp-adb-handle-file-local-copy)
+ (file-modes . tramp-handle-file-modes)
+ (file-name-all-completions . tramp-adb-handle-file-name-all-completions)
(file-name-as-directory . tramp-handle-file-name-as-directory)
(file-name-completion . tramp-handle-file-name-completion)
- (file-name-all-completions . tramp-adb-handle-file-name-all-completions)
- (file-attributes . tramp-adb-handle-file-attributes)
(file-name-directory . tramp-handle-file-name-directory)
(file-name-nondirectory . tramp-handle-file-name-nondirectory)
- (file-truename . tramp-adb-handle-file-truename)
+ ;; `file-name-sans-versions' performed by default handler.
(file-newer-than-file-p . tramp-handle-file-newer-than-file-p)
- (file-name-as-directory . tramp-handle-file-name-as-directory)
+ (file-notify-add-watch . tramp-handle-file-notify-add-watch)
+ (file-notify-rm-watch . tramp-handle-file-notify-rm-watch)
+ (file-ownership-preserved-p . ignore)
+ (file-readable-p . tramp-handle-file-exists-p)
(file-regular-p . tramp-handle-file-regular-p)
(file-remote-p . tramp-handle-file-remote-p)
- (file-accessible-directory-p . tramp-handle-file-accessible-directory-p)
- (file-directory-p . tramp-adb-handle-file-directory-p)
+ (file-selinux-context . ignore)
(file-symlink-p . tramp-handle-file-symlink-p)
- ;; FIXME: This is too sloppy.
- (file-executable-p . tramp-handle-file-exists-p)
- (file-exists-p . tramp-handle-file-exists-p)
- (file-readable-p . tramp-handle-file-exists-p)
+ (file-truename . tramp-adb-handle-file-truename)
(file-writable-p . tramp-adb-handle-file-writable-p)
- (file-local-copy . tramp-adb-handle-file-local-copy)
- (file-modes . tramp-handle-file-modes)
- (file-notify-add-watch . tramp-handle-file-notify-add-watch)
- (file-notify-rm-watch . ignore)
- (expand-file-name . tramp-adb-handle-expand-file-name)
(find-backup-file-name . tramp-handle-find-backup-file-name)
- (directory-files . tramp-handle-directory-files)
- (directory-files-and-attributes
- . tramp-adb-handle-directory-files-and-attributes)
- (make-directory . tramp-adb-handle-make-directory)
- (delete-directory . tramp-adb-handle-delete-directory)
- (delete-file . tramp-adb-handle-delete-file)
- (load . tramp-handle-load)
+ ;; `find-file-noselect' performed by default handler.
+ ;; `get-file-buffer' performed by default handler.
(insert-directory . tramp-adb-handle-insert-directory)
(insert-file-contents . tramp-handle-insert-file-contents)
- (substitute-in-file-name . tramp-handle-substitute-in-file-name)
- (unhandled-file-name-directory . tramp-handle-unhandled-file-name-directory)
- (vc-registered . ignore) ;no vc control files on Android devices
- (write-region . tramp-adb-handle-write-region)
+ (load . tramp-handle-load)
+ ;; `make-auto-save-file-name' performed by default handler.
+ (make-directory . tramp-adb-handle-make-directory)
+ (make-directory-internal . ignore)
+ (make-symbolic-link . ignore)
+ (process-file . tramp-adb-handle-process-file)
+ (rename-file . tramp-adb-handle-rename-file)
+ (set-file-acl . ignore)
(set-file-modes . tramp-adb-handle-set-file-modes)
+ (set-file-selinux-context . ignore)
(set-file-times . tramp-adb-handle-set-file-times)
- (copy-file . tramp-adb-handle-copy-file)
- (rename-file . tramp-adb-handle-rename-file)
- (process-file . tramp-adb-handle-process-file)
+ (set-visited-file-modtime . tramp-handle-set-visited-file-modtime)
(shell-command . tramp-adb-handle-shell-command)
- (start-file-process . tramp-adb-handle-start-file-process))
+ (start-file-process . tramp-adb-handle-start-file-process)
+ (substitute-in-file-name . tramp-handle-substitute-in-file-name)
+ (unhandled-file-name-directory . tramp-handle-unhandled-file-name-directory)
+ (vc-registered . ignore)
+ (verify-visited-file-modtime . tramp-handle-verify-visited-file-modtime)
+ (write-region . tramp-adb-handle-write-region))
"Alist of handler functions for Tramp ADB method.")
;; It must be a `defsubst' in order to push the whole code into
@@ -599,6 +620,9 @@ But handle the case, if the \"test\" command is not available."
(tramp-error v 'file-error "Cannot write: `%s' filename"))
(delete-file tmpfile)))
+ (when (or (eq visit t) (stringp visit))
+ (set-visited-file-modtime))
+
(unless (equal curbuf (current-buffer))
(tramp-error
v 'file-error
diff --git a/lisp/net/tramp-gvfs.el b/lisp/net/tramp-gvfs.el
index e25c9bd4caf..5bb30b04643 100644
--- a/lisp/net/tramp-gvfs.el
+++ b/lisp/net/tramp-gvfs.el
@@ -403,10 +403,10 @@ Every entry is a list (NAME ADDRESS).")
;; New handlers should be added here.
(defconst tramp-gvfs-file-name-handler-alist
- '(
- (access-file . ignore)
+ '((access-file . ignore)
(add-name-to-file . tramp-gvfs-handle-copy-file)
;; `byte-compiler-base-file-name' performed by default handler.
+ ;; `copy-directory' performed by default handler.
(copy-file . tramp-gvfs-handle-copy-file)
(delete-directory . tramp-gvfs-handle-delete-directory)
(delete-file . tramp-gvfs-handle-delete-file)
@@ -418,14 +418,15 @@ Every entry is a list (NAME ADDRESS).")
(dired-call-process . ignore)
(dired-compress-file . ignore)
(dired-uncache . tramp-handle-dired-uncache)
- ;; `executable-find' is not official yet. performed by default handler.
(expand-file-name . tramp-gvfs-handle-expand-file-name)
(file-accessible-directory-p . tramp-handle-file-accessible-directory-p)
(file-acl . ignore)
(file-attributes . tramp-gvfs-handle-file-attributes)
(file-directory-p . tramp-gvfs-handle-file-directory-p)
+ ;; `file-equal-p' performed by default handler.
(file-executable-p . tramp-gvfs-handle-file-executable-p)
(file-exists-p . tramp-handle-file-exists-p)
+ ;; `file-in-directory-p' performed by default handler.
(file-local-copy . tramp-gvfs-handle-file-local-copy)
(file-modes . tramp-handle-file-modes)
(file-name-all-completions . tramp-gvfs-handle-file-name-all-completions)
@@ -435,8 +436,8 @@ Every entry is a list (NAME ADDRESS).")
(file-name-nondirectory . tramp-handle-file-name-nondirectory)
;; `file-name-sans-versions' performed by default handler.
(file-newer-than-file-p . tramp-handle-file-newer-than-file-p)
- (file-notify-add-watch . tramp-handle-file-notify-add-watch)
- (file-notify-rm-watch . ignore)
+ (file-notify-add-watch . tramp-gvfs-handle-file-notify-add-watch)
+ (file-notify-rm-watch . tramp-handle-file-notify-rm-watch)
(file-ownership-preserved-p . ignore)
(file-readable-p . tramp-gvfs-handle-file-readable-p)
(file-regular-p . tramp-handle-file-regular-p)
@@ -451,6 +452,7 @@ Every entry is a list (NAME ADDRESS).")
(insert-directory . tramp-gvfs-handle-insert-directory)
(insert-file-contents . tramp-gvfs-handle-insert-file-contents)
(load . tramp-handle-load)
+ ;; `make-auto-save-file-name' performed by default handler.
(make-directory . tramp-gvfs-handle-make-directory)
(make-directory-internal . ignore)
(make-symbolic-link . ignore)
@@ -459,15 +461,15 @@ Every entry is a list (NAME ADDRESS).")
(set-file-acl . ignore)
(set-file-modes . ignore)
(set-file-selinux-context . ignore)
- (set-visited-file-modtime . tramp-gvfs-handle-set-visited-file-modtime)
+ (set-file-times . ignore)
+ (set-visited-file-modtime . tramp-handle-set-visited-file-modtime)
(shell-command . ignore)
(start-file-process . ignore)
(substitute-in-file-name . tramp-handle-substitute-in-file-name)
(unhandled-file-name-directory . tramp-handle-unhandled-file-name-directory)
(vc-registered . ignore)
- ;; `verify-visited-file-modtime' performed by default handler.
- (write-region . tramp-gvfs-handle-write-region)
-)
+ (verify-visited-file-modtime . tramp-handle-verify-visited-file-modtime)
+ (write-region . tramp-gvfs-handle-write-region))
"Alist of handler functions for Tramp GVFS method.
Operations not mentioned here will be handled by the default Emacs primitives.")
@@ -555,28 +557,6 @@ will be traced by Tramp with trace level 6."
(tramp-compat-font-lock-add-keywords
'emacs-lisp-mode '("\\<with-tramp-dbus-call-method\\>"))
-(defmacro with-tramp-gvfs-error-message (filename handler &rest args)
- "Apply a Tramp GVFS `handler'.
-In case of an error, modify the error message by replacing
-`filename' with its GVFS mounted name."
- `(let ((fuse-file-name (regexp-quote (tramp-gvfs-fuse-file-name ,filename)))
- elt)
- (condition-case err
- (tramp-compat-funcall ,handler ,@args)
- (error
- (setq elt (cdr err))
- (while elt
- (when (and (stringp (car elt))
- (string-match fuse-file-name (car elt)))
- (setcar elt (replace-match ,filename t t (car elt))))
- (setq elt (cdr elt)))
- (signal (car err) (cdr err))))))
-
-(put 'with-tramp-gvfs-error-message 'lisp-indent-function 2)
-(put 'with-tramp-gvfs-error-message 'edebug-form-spec '(form symbolp body))
-(tramp-compat-font-lock-add-keywords
- 'emacs-lisp-mode '("\\<with-tramp-gvfs-error-message\\>"))
-
(defvar tramp-gvfs-dbus-event-vector nil
"Current Tramp file name to be used, as vector.
It is needed when D-Bus signals or errors arrive, because there
@@ -943,6 +923,64 @@ is no information where to trace the message.")
v (concat localname filename)
"file-name-all-completions" result))))))))
+(defun tramp-gvfs-handle-file-notify-add-watch (file-name flags callback)
+ "Like `file-notify-add-watch' for Tramp files."
+ (setq file-name (expand-file-name file-name))
+ (with-parsed-tramp-file-name file-name nil
+ (let ((p (start-process
+ "gvfs-monitor-file" (generate-new-buffer " *gvfs-monitor-file*")
+ "gvfs-monitor-file" (tramp-gvfs-url-file-name file-name))))
+ (if (not (processp p))
+ (tramp-error
+ v 'file-notify-error "gvfs-monitor-file failed to start")
+ (tramp-compat-set-process-query-on-exit-flag p nil)
+ (set-process-filter p 'tramp-gvfs-file-gvfs-monitor-file-process-filter)
+ (with-current-buffer (process-buffer p)
+ (setq default-directory (file-name-directory file-name)))
+ p))))
+
+(defun tramp-gvfs-file-gvfs-monitor-file-process-filter (proc string)
+ "Read output from \"gvfs-monitor-file\" and add corresponding file-notify events."
+ (let* ((rest-string (tramp-compat-process-get proc 'rest-string))
+ (dd (with-current-buffer (process-buffer proc) default-directory))
+ (ddu (regexp-quote (tramp-gvfs-url-file-name dd))))
+ (when rest-string
+ (tramp-message proc 10 "Previous string:\n%s" rest-string))
+ (tramp-message proc 6 "%S\n%s" proc string)
+ (setq string (concat rest-string string)
+ ;; Attribute change is returned in unused wording.
+ string (replace-regexp-in-string
+ "ATTRIB CHANGED" "ATTRIBUTE_CHANGED" string))
+
+ (while (string-match
+ (concat "^[\n\r]*"
+ "File Monitor Event:[\n\r]+"
+ "File = \\([^\n\r]+\\)[\n\r]+"
+ "Event = \\([^[:blank:]]+\\)[\n\r]+")
+ string)
+ (let ((action (intern-soft
+ (replace-regexp-in-string
+ "_" "-" (downcase (match-string 2 string)))))
+ (file (match-string 1 string)))
+ (setq string (replace-match "" nil nil string))
+ ;; File names are returned as URL paths. We must convert them.
+ (when (string-match ddu file)
+ (setq file (replace-match dd nil nil file)))
+ (while (string-match "%\\([0-9A-F]\\{2\\}\\)" file)
+ (setq file
+ (replace-match
+ (char-to-string (string-to-number (match-string 1 file) 16))
+ nil nil file)))
+ ;; Usually, we would add an Emacs event now. Unfortunately,
+ ;; `unread-command-events' does not accept several events at
+ ;; once. Therefore, we apply the callback directly.
+ (tramp-compat-funcall 'file-notify-callback (list proc action file))))
+
+ ;; Save rest of the string.
+ (when (zerop (length string)) (setq string nil))
+ (when string (tramp-message proc 10 "Rest string:\n%s" string))
+ (tramp-compat-process-put proc 'rest-string string)))
+
(defun tramp-gvfs-handle-file-readable-p (filename)
"Like `file-readable-p' for Tramp files."
(with-parsed-tramp-file-name filename nil
@@ -1054,22 +1092,6 @@ is no information where to trace the message.")
(tramp-flush-file-property v (file-name-directory localname))
(tramp-flush-file-property v localname))))))
-(defun tramp-gvfs-handle-set-visited-file-modtime (&optional time-list)
- "Like `set-visited-file-modtime' for Tramp files."
- (unless (buffer-file-name)
- (error "Can't set-visited-file-modtime: buffer `%s' not visiting a file"
- (buffer-name)))
- (unless time-list
- (let ((f (buffer-file-name)))
- (with-parsed-tramp-file-name f nil
- (let ((remote-file-name-inhibit-cache t)
- (attr (file-attributes f)))
- ;; '(-1 65535) means file doesn't exists yet.
- (setq time-list (or (nth 5 attr) '(-1 65535)))))))
- ;; We use '(0 0) as a don't-know value.
- (unless (not (equal time-list '(0 0)))
- (tramp-run-real-handler 'set-visited-file-modtime (list time-list))))
-
(defun tramp-gvfs-handle-write-region
(start end filename &optional append visit lockname confirm)
"Like `write-region' for Tramp files."
@@ -1082,7 +1104,7 @@ is no information where to trace the message.")
(let ((tmpfile (tramp-compat-make-temp-file filename)))
(write-region start end tmpfile)
(condition-case nil
- (rename-file tmpfile filename)
+ (rename-file tmpfile filename 'ok-if-already-exists)
(error
(delete-file tmpfile)
(tramp-error
@@ -1137,24 +1159,6 @@ is no information where to trace the message.")
(dbus-unescape-from-identifier
(replace-regexp-in-string "^.*/\\([^/]+\\)$" "\\1" object-path)))
-(defun tramp-gvfs-fuse-file-name (filename)
- "Return FUSE file name, which is directly accessible."
- (with-parsed-tramp-file-name (expand-file-name filename) nil
- (tramp-gvfs-maybe-open-connection v)
- (let ((prefix (tramp-get-file-property v "/" "prefix" ""))
- (fuse-mountpoint
- (tramp-get-file-property v "/" "fuse-mountpoint" nil)))
- (unless fuse-mountpoint
- (tramp-error
- v 'file-error "There is no FUSE mount point for `%s'" filename))
- ;; We must hide the prefix, if any.
- (when (string-match (concat "^" (regexp-quote prefix)) localname)
- (setq localname (replace-match "" t t localname)))
- (tramp-message
- v 10 "remote file `%s' is local file `%s'"
- filename (concat fuse-mountpoint localname))
- (concat fuse-mountpoint localname))))
-
(defun tramp-bluez-address (device)
"Return bluetooth device address from a given bluetooth DEVICE name."
(when (stringp device)
diff --git a/lisp/net/tramp-sh.el b/lisp/net/tramp-sh.el
index 441849fd2af..f9e68d0dad0 100644
--- a/lisp/net/tramp-sh.el
+++ b/lisp/net/tramp-sh.el
@@ -801,73 +801,78 @@ existence, and file readability. Input shall be read via
here-document, otherwise the command could exceed maximum length
of command line.")
-;; New handlers should be added here. The following operations can be
-;; handled using the normal primitives: file-name-sans-versions,
-;; get-file-buffer.
+;; New handlers should be added here.
(defconst tramp-sh-file-name-handler-alist
- '((load . tramp-handle-load)
- (make-symbolic-link . tramp-sh-handle-make-symbolic-link)
- (file-name-as-directory . tramp-handle-file-name-as-directory)
- (file-name-directory . tramp-handle-file-name-directory)
- (file-name-nondirectory . tramp-handle-file-name-nondirectory)
- (file-truename . tramp-sh-handle-file-truename)
- (file-exists-p . tramp-sh-handle-file-exists-p)
- (file-accessible-directory-p . tramp-handle-file-accessible-directory-p)
- (file-directory-p . tramp-sh-handle-file-directory-p)
- (file-executable-p . tramp-sh-handle-file-executable-p)
- (file-readable-p . tramp-sh-handle-file-readable-p)
- (file-regular-p . tramp-handle-file-regular-p)
- (file-symlink-p . tramp-handle-file-symlink-p)
- (file-writable-p . tramp-sh-handle-file-writable-p)
- (file-ownership-preserved-p . tramp-sh-handle-file-ownership-preserved-p)
- (file-newer-than-file-p . tramp-sh-handle-file-newer-than-file-p)
- (file-attributes . tramp-sh-handle-file-attributes)
- (file-modes . tramp-handle-file-modes)
- (directory-files . tramp-handle-directory-files)
- (directory-files-and-attributes
- . tramp-sh-handle-directory-files-and-attributes)
- (file-name-all-completions . tramp-sh-handle-file-name-all-completions)
- (file-name-completion . tramp-handle-file-name-completion)
+ '(;; `access-file' performed by default handler.
(add-name-to-file . tramp-sh-handle-add-name-to-file)
- (copy-file . tramp-sh-handle-copy-file)
+ ;; `byte-compiler-base-file-name' performed by default handler.
(copy-directory . tramp-sh-handle-copy-directory)
- (rename-file . tramp-sh-handle-rename-file)
- (set-file-modes . tramp-sh-handle-set-file-modes)
- (set-file-times . tramp-sh-handle-set-file-times)
- (make-directory . tramp-sh-handle-make-directory)
+ (copy-file . tramp-sh-handle-copy-file)
(delete-directory . tramp-sh-handle-delete-directory)
(delete-file . tramp-sh-handle-delete-file)
+ ;; `diff-latest-backup-file' performed by default handler.
(directory-file-name . tramp-handle-directory-file-name)
- ;; `executable-find' is not official yet.
- (executable-find . tramp-sh-handle-executable-find)
- (start-file-process . tramp-sh-handle-start-file-process)
- (process-file . tramp-sh-handle-process-file)
- (shell-command . tramp-handle-shell-command)
- (insert-directory . tramp-sh-handle-insert-directory)
+ (directory-files . tramp-handle-directory-files)
+ (directory-files-and-attributes
+ . tramp-sh-handle-directory-files-and-attributes)
+ ;; `dired-call-process' performed by default handler.
+ (dired-compress-file . tramp-sh-handle-dired-compress-file)
+ (dired-recursive-delete-directory
+ . tramp-sh-handle-dired-recursive-delete-directory)
+ (dired-uncache . tramp-handle-dired-uncache)
(expand-file-name . tramp-sh-handle-expand-file-name)
- (substitute-in-file-name . tramp-handle-substitute-in-file-name)
+ (file-accessible-directory-p . tramp-handle-file-accessible-directory-p)
+ (file-acl . tramp-sh-handle-file-acl)
+ (file-attributes . tramp-sh-handle-file-attributes)
+ (file-directory-p . tramp-sh-handle-file-directory-p)
+ ;; `file-equal-p' performed by default handler.
+ (file-executable-p . tramp-sh-handle-file-executable-p)
+ (file-exists-p . tramp-sh-handle-file-exists-p)
+ ;; `file-in-directory-p' performed by default handler.
(file-local-copy . tramp-sh-handle-file-local-copy)
+ (file-modes . tramp-handle-file-modes)
+ (file-name-all-completions . tramp-sh-handle-file-name-all-completions)
+ (file-name-as-directory . tramp-handle-file-name-as-directory)
+ (file-name-completion . tramp-handle-file-name-completion)
+ (file-name-directory . tramp-handle-file-name-directory)
+ (file-name-nondirectory . tramp-handle-file-name-nondirectory)
+ ;; `file-name-sans-versions' performed by default handler.
+ (file-newer-than-file-p . tramp-sh-handle-file-newer-than-file-p)
+ (file-notify-add-watch . tramp-sh-handle-file-notify-add-watch)
+ (file-notify-rm-watch . tramp-handle-file-notify-rm-watch)
+ (file-ownership-preserved-p . tramp-sh-handle-file-ownership-preserved-p)
+ (file-readable-p . tramp-sh-handle-file-readable-p)
+ (file-regular-p . tramp-handle-file-regular-p)
(file-remote-p . tramp-handle-file-remote-p)
+ (file-selinux-context . tramp-sh-handle-file-selinux-context)
+ (file-symlink-p . tramp-handle-file-symlink-p)
+ (file-truename . tramp-sh-handle-file-truename)
+ (file-writable-p . tramp-sh-handle-file-writable-p)
+ (find-backup-file-name . tramp-handle-find-backup-file-name)
+ ;; `find-file-noselect' performed by default handler.
+ ;; `get-file-buffer' performed by default handler.
+ (insert-directory . tramp-sh-handle-insert-directory)
(insert-file-contents . tramp-handle-insert-file-contents)
(insert-file-contents-literally
. tramp-sh-handle-insert-file-contents-literally)
- (write-region . tramp-sh-handle-write-region)
- (find-backup-file-name . tramp-handle-find-backup-file-name)
+ (load . tramp-handle-load)
(make-auto-save-file-name . tramp-sh-handle-make-auto-save-file-name)
- (unhandled-file-name-directory . tramp-handle-unhandled-file-name-directory)
- (dired-compress-file . tramp-sh-handle-dired-compress-file)
- (dired-recursive-delete-directory
- . tramp-sh-handle-dired-recursive-delete-directory)
- (dired-uncache . tramp-handle-dired-uncache)
- (set-visited-file-modtime . tramp-sh-handle-set-visited-file-modtime)
- (verify-visited-file-modtime . tramp-sh-handle-verify-visited-file-modtime)
- (file-selinux-context . tramp-sh-handle-file-selinux-context)
- (set-file-selinux-context . tramp-sh-handle-set-file-selinux-context)
- (file-acl . tramp-sh-handle-file-acl)
+ (make-directory . tramp-sh-handle-make-directory)
+ (make-symbolic-link . tramp-sh-handle-make-symbolic-link)
+ (process-file . tramp-sh-handle-process-file)
+ (rename-file . tramp-sh-handle-rename-file)
(set-file-acl . tramp-sh-handle-set-file-acl)
+ (set-file-modes . tramp-sh-handle-set-file-modes)
+ (set-file-selinux-context . tramp-sh-handle-set-file-selinux-context)
+ (set-file-times . tramp-sh-handle-set-file-times)
+ (set-visited-file-modtime . tramp-sh-handle-set-visited-file-modtime)
+ (shell-command . tramp-handle-shell-command)
+ (start-file-process . tramp-sh-handle-start-file-process)
+ (substitute-in-file-name . tramp-handle-substitute-in-file-name)
+ (unhandled-file-name-directory . tramp-handle-unhandled-file-name-directory)
(vc-registered . tramp-sh-handle-vc-registered)
- (file-notify-add-watch . tramp-sh-handle-file-notify-add-watch)
- (file-notify-rm-watch . tramp-sh-handle-file-notify-rm-watch))
+ (verify-visited-file-modtime . tramp-sh-handle-verify-visited-file-modtime)
+ (write-region . tramp-sh-handle-write-region))
"Alist of handler functions.
Operations not mentioned here will be handled by the normal Emacs functions.")
@@ -2284,9 +2289,7 @@ The method used must be an out-of-band method."
(tramp-get-method-parameter method 'tramp-copy-env))))
;; Check for program.
- (unless (let ((default-directory
- (tramp-compat-temporary-file-directory)))
- (executable-find copy-program))
+ (unless (executable-find copy-program)
(tramp-error
v 'file-error "Cannot find copy program: %s" copy-program))
@@ -2667,11 +2670,6 @@ the result will be a local, non-Tramp, filename."
;;; Remote commands:
-(defun tramp-sh-handle-executable-find (command)
- "Like `executable-find' for Tramp files."
- (with-parsed-tramp-file-name default-directory nil
- (tramp-find-executable v command (tramp-get-remote-path v) t)))
-
(defun tramp-process-sentinel (proc event)
"Flush file caches."
(unless (memq (process-status proc) '(run open))
@@ -3430,8 +3428,8 @@ Fall back to normal file name handler if no Tramp handler exists."
(file-remote-p default-directory)))
(rest-string (tramp-compat-process-get proc 'rest-string)))
(when rest-string
- (tramp-message proc 10 (format "Previous string:\n%s" rest-string)))
- (tramp-message proc 6 (format "%S\n%s" proc string))
+ (tramp-message proc 10 "Previous string:\n%s" rest-string))
+ (tramp-message proc 6 "%S\n%s" proc string)
(setq string (concat rest-string string)
;; Attribute change is returned in unused wording.
string (replace-regexp-in-string
@@ -3463,12 +3461,12 @@ Fall back to normal file name handler if no Tramp handler exists."
;; Save rest of the string.
(when (zerop (length string)) (setq string nil))
- (when string (tramp-message proc 10 (format "Rest string:\n%s" string)))
+ (when string (tramp-message proc 10 "Rest string:\n%s" string))
(tramp-compat-process-put proc 'rest-string string)))
(defun tramp-sh-file-inotifywait-process-filter (proc string)
"Read output from \"inotifywait\" and add corresponding file-notify events."
- (tramp-message proc 6 (format "%S\n%s" proc string))
+ (tramp-message proc 6 "%S\n%s" proc string)
(dolist (line (split-string string "[\n\r]+" 'omit-nulls))
;; Check, whether there is a problem.
(unless
@@ -3492,15 +3490,6 @@ Fall back to normal file name handler if no Tramp handler exists."
;; once. Therefore, we apply the callback directly.
(tramp-compat-funcall 'file-notify-callback object))))
-(defvar file-notify-descriptors)
-(defun tramp-sh-handle-file-notify-rm-watch (proc)
- "Like `file-notify-rm-watch' for Tramp files."
- ;; The descriptor must be a process object.
- (unless (and (processp proc) (gethash proc file-notify-descriptors))
- (tramp-error proc 'file-notify-error "Not a valid descriptor %S" proc))
- (tramp-message proc 6 (format "Kill %S" proc))
- (kill-process proc))
-
;;; Internal Functions:
(defun tramp-maybe-send-script (vec script name)
@@ -3618,7 +3607,7 @@ This function expects to be in the right *tramp* buffer."
I.e., for each directory in `tramp-remote-path', it is tested
whether it exists and if so, it is added to the environment
variable PATH."
- (tramp-message vec 5 (format "Setting $PATH environment variable"))
+ (tramp-message vec 5 "Setting $PATH environment variable")
(tramp-send-command
vec (format "PATH=%s; export PATH"
(mapconcat 'identity (tramp-get-remote-path vec) ":"))))
diff --git a/lisp/net/tramp-smb.el b/lisp/net/tramp-smb.el
index fee34f856dd..29847556dfe 100644
--- a/lisp/net/tramp-smb.el
+++ b/lisp/net/tramp-smb.el
@@ -177,8 +177,7 @@ See `tramp-actions-before-shell' for more info.")
;; New handlers should be added here.
(defconst tramp-smb-file-name-handler-alist
- '(
- ;; `access-file' performed by default handler.
+ '(;; `access-file' performed by default handler.
(add-name-to-file . tramp-smb-handle-add-name-to-file)
;; `byte-compiler-base-file-name' performed by default handler.
(copy-directory . tramp-smb-handle-copy-directory)
@@ -198,8 +197,10 @@ See `tramp-actions-before-shell' for more info.")
(file-acl . tramp-smb-handle-file-acl)
(file-attributes . tramp-smb-handle-file-attributes)
(file-directory-p . tramp-smb-handle-file-directory-p)
+ ;; `file-equal-p' performed by default handler.
(file-executable-p . tramp-handle-file-exists-p)
(file-exists-p . tramp-handle-file-exists-p)
+ ;; `file-in-directory-p' performed by default handler.
(file-local-copy . tramp-smb-handle-file-local-copy)
(file-modes . tramp-handle-file-modes)
(file-name-all-completions . tramp-smb-handle-file-name-all-completions)
@@ -210,7 +211,7 @@ See `tramp-actions-before-shell' for more info.")
;; `file-name-sans-versions' performed by default handler.
(file-newer-than-file-p . tramp-handle-file-newer-than-file-p)
(file-notify-add-watch . tramp-handle-file-notify-add-watch)
- (file-notify-rm-watch . ignore)
+ (file-notify-rm-watch . tramp-handle-file-notify-rm-watch)
(file-ownership-preserved-p . ignore)
(file-readable-p . tramp-handle-file-exists-p)
(file-regular-p . tramp-handle-file-regular-p)
@@ -225,6 +226,7 @@ See `tramp-actions-before-shell' for more info.")
(insert-directory . tramp-smb-handle-insert-directory)
(insert-file-contents . tramp-handle-insert-file-contents)
(load . tramp-handle-load)
+ ;; `make-auto-save-file-name' performed by default handler.
(make-directory . tramp-smb-handle-make-directory)
(make-directory-internal . tramp-smb-handle-make-directory-internal)
(make-symbolic-link . tramp-smb-handle-make-symbolic-link)
@@ -234,15 +236,14 @@ See `tramp-actions-before-shell' for more info.")
(set-file-modes . tramp-smb-handle-set-file-modes)
(set-file-selinux-context . ignore)
(set-file-times . ignore)
- (set-visited-file-modtime . ignore)
+ (set-visited-file-modtime . tramp-handle-set-visited-file-modtime)
(shell-command . tramp-handle-shell-command)
(start-file-process . tramp-smb-handle-start-file-process)
(substitute-in-file-name . tramp-smb-handle-substitute-in-file-name)
(unhandled-file-name-directory . tramp-handle-unhandled-file-name-directory)
(vc-registered . ignore)
- (verify-visited-file-modtime . ignore)
- (write-region . tramp-smb-handle-write-region)
-)
+ (verify-visited-file-modtime . tramp-handle-verify-visited-file-modtime)
+ (write-region . tramp-smb-handle-write-region))
"Alist of handler functions for Tramp SMB method.
Operations not mentioned here will be handled by the default Emacs primitives.")
@@ -1786,9 +1787,7 @@ Returns nil if an error message has appeared."
(tramp-get-buffer vec)
;; Check for program.
- (unless (let ((default-directory
- (tramp-compat-temporary-file-directory)))
- (executable-find tramp-smb-winexe-program))
+ (unless (executable-find tramp-smb-winexe-program)
(tramp-error
vec 'file-error "Cannot find program: %s" tramp-smb-winexe-program))
diff --git a/lisp/net/tramp.el b/lisp/net/tramp.el
index 48e13004c36..8ce5f2eae9b 100644
--- a/lisp/net/tramp.el
+++ b/lisp/net/tramp.el
@@ -1975,11 +1975,11 @@ ARGS are the arguments OPERATION has been called with."
'dired-compress-file 'dired-uncache
'file-accessible-directory-p 'file-attributes
'file-directory-p 'file-executable-p 'file-exists-p
- 'file-local-copy 'file-remote-p 'file-modes
+ 'file-local-copy 'file-modes
'file-name-as-directory 'file-name-directory
'file-name-nondirectory 'file-name-sans-versions
'file-ownership-preserved-p 'file-readable-p
- 'file-regular-p 'file-symlink-p 'file-truename
+ 'file-regular-p 'file-remote-p 'file-symlink-p 'file-truename
'file-writable-p 'find-backup-file-name 'find-file-noselect
'get-file-buffer 'insert-directory 'insert-file-contents
'load 'make-directory 'make-directory-internal
@@ -2008,7 +2008,7 @@ ARGS are the arguments OPERATION has been called with."
;; Emacs 23+ only.
'copy-directory
;; Emacs 24+ only.
- 'file-in-directory-p 'file-equal-p
+ 'file-equal-p 'file-in-directory-p
;; XEmacs only.
'dired-make-relative-symlink
'vm-imap-move-mail 'vm-pop-move-mail 'vm-spool-move-mail))
@@ -3287,14 +3287,78 @@ beginning of local filename are not substituted."
;; for backward compatibility.
(expand-file-name "~/"))
+(defun tramp-handle-set-visited-file-modtime (&optional time-list)
+ "Like `set-visited-file-modtime' for Tramp files."
+ (unless (buffer-file-name)
+ (error "Can't set-visited-file-modtime: buffer `%s' not visiting a file"
+ (buffer-name)))
+ (unless time-list
+ (let ((remote-file-name-inhibit-cache t))
+ ;; '(-1 65535) means file doesn't exists yet.
+ (setq time-list
+ (or (nth 5 (file-attributes (buffer-file-name))) '(-1 65535)))))
+ ;; We use '(0 0) as a don't-know value.
+ (unless (equal time-list '(0 0))
+ (tramp-run-real-handler 'set-visited-file-modtime (list time-list))))
+
+(defun tramp-handle-verify-visited-file-modtime (&optional buf)
+ "Like `verify-visited-file-modtime' for Tramp files.
+At the time `verify-visited-file-modtime' calls this function, we
+already know that the buffer is visiting a file and that
+`visited-file-modtime' does not return 0. Do not call this
+function directly, unless those two cases are already taken care
+of."
+ (with-current-buffer (or buf (current-buffer))
+ (let ((f (buffer-file-name)))
+ ;; There is no file visiting the buffer, or the buffer has no
+ ;; recorded last modification time, or there is no established
+ ;; connection.
+ (if (or (not f)
+ (eq (visited-file-modtime) 0)
+ (not (tramp-file-name-handler 'file-remote-p f nil 'connected)))
+ t
+ (with-parsed-tramp-file-name f nil
+ (let* ((remote-file-name-inhibit-cache t)
+ (attr (file-attributes f))
+ (modtime (nth 5 attr))
+ (mt (visited-file-modtime)))
+
+ (cond
+ ;; File exists, and has a known modtime.
+ ((and attr (not (equal modtime '(0 0))))
+ (< (abs (tramp-time-diff
+ modtime
+ ;; For compatibility, deal with both the old
+ ;; (HIGH . LOW) and the new (HIGH LOW) return
+ ;; values of `visited-file-modtime'.
+ (if (atom (cdr mt))
+ (list (car mt) (cdr mt))
+ mt)))
+ 2))
+ ;; Modtime has the don't know value.
+ (attr t)
+ ;; If file does not exist, say it is not modified if and
+ ;; only if that agrees with the buffer's record.
+ (t (equal mt '(-1 65535))))))))))
+
(defun tramp-handle-file-notify-add-watch (filename flags callback)
"Like `file-notify-add-watch' for Tramp files."
- ;; This is the default handler. Some packages might have its own one.
+ ;; This is the default handler. tramp-gvfs.el and tramp-sh.el have
+ ;; its own one.
(setq filename (expand-file-name filename))
(with-parsed-tramp-file-name filename nil
(tramp-error
v 'file-notify-error "File notification not supported for `%s'" filename)))
+(defvar file-notify-descriptors)
+(defun tramp-handle-file-notify-rm-watch (proc)
+ "Like `file-notify-rm-watch' for Tramp files."
+ ;; The descriptor must be a process object.
+ (unless (and (processp proc) (gethash proc file-notify-descriptors))
+ (tramp-error proc 'file-notify-error "Not a valid descriptor %S" proc))
+ (tramp-message proc 6 "Kill %S" proc)
+ (kill-process proc))
+
;;; Functions for establishing connection:
;; The following functions are actions to be taken when seeing certain
@@ -3951,16 +4015,12 @@ This is needed because for some Emacs flavors Tramp has
defadvised `call-process' to behave like `process-file'. The
Lisp error raised when PROGRAM is nil is trapped also, returning 1.
Furthermore, traces are written with verbosity of 6."
- (let ((default-directory
- (if (file-remote-p default-directory)
- (tramp-compat-temporary-file-directory)
- default-directory)))
- (tramp-message
- (vector tramp-current-method tramp-current-user tramp-current-host nil nil)
- 6 "%s %s %s" program infile args)
- (if (executable-find program)
- (apply 'call-process program infile destination display args)
- 1)))
+ (tramp-message
+ (vector tramp-current-method tramp-current-user tramp-current-host nil nil)
+ 6 "%s %s %s" program infile args)
+ (if (executable-find program)
+ (apply 'call-process program infile destination display args)
+ 1))
;;;###tramp-autoload
(defun tramp-read-passwd (proc &optional prompt)
diff --git a/lisp/server.el b/lisp/server.el
index 05ac345d904..8a2a466a315 100644
--- a/lisp/server.el
+++ b/lisp/server.el
@@ -1611,7 +1611,7 @@ With ARG non-nil, silently save all file-visiting buffers, then kill.
If emacsclient was started with a list of filenames to edit, then
only these files will be asked to be saved."
- (let ((proc (frame-parameter (selected-frame) 'client)))
+ (let ((proc (frame-parameter nil 'client)))
(cond ((eq proc 'nowait)
;; Nowait frames have no client buffer list.
(if (cdr (frame-list))
diff --git a/lisp/speedbar.el b/lisp/speedbar.el
index 9daa77b740f..2f995219193 100644
--- a/lisp/speedbar.el
+++ b/lisp/speedbar.el
@@ -250,7 +250,7 @@ frame."
(defcustom speedbar-query-confirmation-method 'all
"Query control for file operations.
-The 'always flag means to always query before file operations.
+The 'all flag means to always query before file operations.
The 'none-but-delete flag means to not query before any file
operations, except before a file deletion."
:group 'speedbar
diff --git a/lisp/textmodes/reftex-toc.el b/lisp/textmodes/reftex-toc.el
index 248e36a5299..b7d57733467 100644
--- a/lisp/textmodes/reftex-toc.el
+++ b/lisp/textmodes/reftex-toc.el
@@ -229,7 +229,7 @@ When called with a raw C-u prefix, rescan the document first."
(car (reftex-where-am-I))))
(unsplittable (if (fboundp 'frame-property)
(frame-property (selected-frame) 'unsplittable)
- (frame-parameter (selected-frame) 'unsplittable)))
+ (frame-parameter nil 'unsplittable)))
offset toc-window)
(if (setq toc-window (get-buffer-window
@@ -587,7 +587,7 @@ With prefix arg 1, restrict index to the section at point."
(let ((unsplittable
(if (fboundp 'frame-property)
(frame-property (selected-frame) 'unsplittable)
- (frame-parameter (selected-frame) 'unsplittable)))
+ (frame-parameter nil 'unsplittable)))
(reftex-rebuilding-toc t))
(if unsplittable
(switch-to-buffer
diff --git a/lisp/url/ChangeLog b/lisp/url/ChangeLog
index 254ea5db4e4..120d00002e4 100644
--- a/lisp/url/ChangeLog
+++ b/lisp/url/ChangeLog
@@ -1,3 +1,8 @@
+2013-07-31 Stefan Monnier <monnier@iro.umontreal.ca>
+
+ * url-handlers.el (url-file-name-completion)
+ (url-file-name-all-completions): Don't signal errors (bug#14806).
+
2013-07-22 Stefan Monnier <monnier@iro.umontreal.ca>
* url-http.el (status): Remove, unused.
diff --git a/lisp/url/url-handlers.el b/lisp/url/url-handlers.el
index ae807d6eab9..e9bd1628c99 100644
--- a/lisp/url/url-handlers.el
+++ b/lisp/url/url-handlers.el
@@ -311,11 +311,17 @@ They count bytes from the beginning of the body."
(put 'insert-file-contents 'url-file-handlers 'url-insert-file-contents)
(defun url-file-name-completion (url directory &optional predicate)
- (error "Unimplemented"))
+ ;; Even if it's not implemented, it's not an error to ask for completion,
+ ;; in case it's available (bug#14806).
+ ;; (error "Unimplemented")
+ url)
(put 'file-name-completion 'url-file-handlers 'url-file-name-completion)
(defun url-file-name-all-completions (file directory)
- (error "Unimplemented"))
+ ;; Even if it's not implemented, it's not an error to ask for completion,
+ ;; in case it's available (bug#14806).
+ ;; (error "Unimplemented")
+ nil)
(put 'file-name-all-completions
'url-file-handlers 'url-file-name-all-completions)
diff --git a/lisp/vc/log-view.el b/lisp/vc/log-view.el
index de103c0cdb6..be985866532 100644
--- a/lisp/vc/log-view.el
+++ b/lisp/vc/log-view.el
@@ -562,19 +562,7 @@ file(s)."
(interactive
(list (if (use-region-p) (region-beginning) (point))
(if (use-region-p) (region-end) (point))))
- (let ((fr (log-view-current-tag beg))
- (to (log-view-current-tag end)))
- (when (string-equal fr to)
- (save-excursion
- (goto-char end)
- (log-view-msg-next)
- (setq to (log-view-current-tag))))
- (vc-diff-internal
- t (list log-view-vc-backend
- (if log-view-per-file-logs
- (list (log-view-current-file))
- log-view-vc-fileset))
- to fr)))
+ (log-view-diff-common beg end))
(defun log-view-diff-changeset (beg end)
"Get the diff between two revisions.
@@ -589,20 +577,29 @@ considered file(s)."
(interactive
(list (if (use-region-p) (region-beginning) (point))
(if (use-region-p) (region-end) (point))))
- (when (eq (vc-call-backend log-view-vc-backend 'revision-granularity) 'file)
+ (log-view-diff-common beg end t))
+
+(defun log-view-diff-common (beg end &optional whole-changeset)
+ (when (and whole-changeset
+ (eq (vc-call-backend log-view-vc-backend 'revision-granularity)
+ 'file))
(error "The %s backend does not support changeset diffs" log-view-vc-backend))
- (let ((fr (log-view-current-tag beg))
- (to (log-view-current-tag end)))
+ (let ((to (log-view-current-tag beg))
+ (fr (log-view-current-tag end)))
(when (string-equal fr to)
;; TO and FR are the same, look at the previous revision.
- (setq to (vc-call-backend log-view-vc-backend 'previous-revision nil fr)))
+ (setq fr (vc-call-backend log-view-vc-backend 'previous-revision nil fr)))
(vc-diff-internal
- t
- ;; We want to see the diff for all the files in the changeset, so
- ;; pass NIL for the file list. The value passed here should
- ;; follow what `vc-deduce-fileset' returns.
- (list log-view-vc-backend nil)
- to fr)))
+ t (list log-view-vc-backend
+ ;; The value passed here should follow what
+ ;; `vc-deduce-fileset' returns. If we want to see the
+ ;; diff for all the files in the changeset, pass NIL for
+ ;; the file list.
+ (unless whole-changeset
+ (if log-view-per-file-logs
+ (list (log-view-current-file))
+ log-view-vc-fileset)))
+ fr to)))
(provide 'log-view)
diff --git a/lisp/vc/vc-dir.el b/lisp/vc/vc-dir.el
index 441b3725968..5ddcfd57748 100644
--- a/lisp/vc/vc-dir.el
+++ b/lisp/vc/vc-dir.el
@@ -282,7 +282,7 @@ See `run-hooks'."
(define-key map "Q" 'vc-dir-query-replace-regexp)
(define-key map (kbd "M-s a C-s") 'vc-dir-isearch)
(define-key map (kbd "M-s a M-C-s") 'vc-dir-isearch-regexp)
- (define-key map "I" 'vc-dir-ignore)
+ (define-key map "G" 'vc-dir-ignore)
;; Hook up the menu.
(define-key map [menu-bar vc-dir-mode]
diff --git a/lisp/vc/vc-hooks.el b/lisp/vc/vc-hooks.el
index 284481ee524..ae9aa0118ae 100644
--- a/lisp/vc/vc-hooks.el
+++ b/lisp/vc/vc-hooks.el
@@ -918,6 +918,7 @@ current, and kill the buffer that visits the link."
(define-key map "c" 'vc-rollback)
(define-key map "d" 'vc-dir)
(define-key map "g" 'vc-annotate)
+ (define-key map "G" 'vc-ignore)
(define-key map "h" 'vc-insert-headers)
(define-key map "i" 'vc-register)
(define-key map "l" 'vc-print-log)
@@ -1002,6 +1003,9 @@ current, and kill the buffer that visits the link."
(bindings--define-key map [vc-register]
'(menu-item "Register" vc-register
:help "Register file set into a version control system"))
+ (bindings--define-key map [vc-ignore]
+ '(menu-item "Ignore File..." vc-ignore
+ :help "Ignore a file under current version control system"))
(bindings--define-key map [vc-dir]
'(menu-item "VC Dir" vc-dir
:help "Show the VC status of files in a directory"))
diff --git a/lisp/whitespace.el b/lisp/whitespace.el
index 801cdc52047..b462cf0b811 100644
--- a/lisp/whitespace.el
+++ b/lisp/whitespace.el
@@ -1471,6 +1471,12 @@ documentation."
;; PROBLEM 6: 8 or more SPACEs after TAB
(whitespace-cleanup-region (point-min) (point-max)))))
+(defun whitespace-ensure-local-variables ()
+ "Set `whitespace-indent-tabs-mode' and `whitespace-tab-width' locally."
+ (set (make-local-variable 'whitespace-indent-tabs-mode)
+ indent-tabs-mode)
+ (set (make-local-variable 'whitespace-tab-width)
+ tab-width))
;;;###autoload
(defun whitespace-cleanup-region (start end)
@@ -1517,6 +1523,7 @@ documentation."
;; read-only buffer
(whitespace-warn-read-only "cleanup region")
;; non-read-only buffer
+ (whitespace-ensure-local-variables)
(let ((rstart (min start end))
(rend (copy-marker (max start end)))
(indent-tabs-mode whitespace-indent-tabs-mode)
@@ -2095,7 +2102,6 @@ resultant list will be returned."
(defvar whitespace-display-table-was-local nil
"Used to remember whether a buffer initially had a local display table.")
-
(defun whitespace-turn-on ()
"Turn on whitespace visualization."
;; prepare local hooks
@@ -2108,10 +2114,7 @@ resultant list will be returned."
(if (listp whitespace-style)
whitespace-style
(list whitespace-style)))
- (set (make-local-variable 'whitespace-indent-tabs-mode)
- indent-tabs-mode)
- (set (make-local-variable 'whitespace-tab-width)
- tab-width)
+ (whitespace-ensure-local-variables)
;; turn on whitespace
(when whitespace-active-style
(whitespace-color-on)