(require 'slime)
(require 'bridge)
(require 'cl-lib)

(define-slime-contrib slime-presentations
  "Imitate LispM presentations."
  (:authors "Alan Ruttenberg  <alanr-l@mumble.net>"
            "Matthias Koeppe  <mkoeppe@mail.math.uni-magdeburg.de>")
  (:license "GPL")
  (:slime-dependencies slime-repl)
  (:swank-dependencies swank-presentations)
  (:on-load
   (add-hook 'slime-repl-mode-hook
             (lambda ()
               ;; Respect the syntax text properties of presentation.
               (set (make-local-variable 'parse-sexp-lookup-properties) t)
               (add-hook 'after-change-functions
                         'slime-after-change-function 'append t)))
   (add-hook 'slime-event-hooks 'slime-dispatch-presentation-event)
   (setq slime-write-string-function 'slime-presentation-write)
   (add-hook 'slime-connected-hook 'slime-presentations-on-connected)
   (add-hook 'slime-repl-return-hooks 'slime-presentation-on-return-pressed)
   (add-hook 'slime-repl-current-input-hooks 'slime-presentation-current-input)
   (add-hook 'slime-open-stream-hooks 'slime-presentation-on-stream-open)
   (add-hook 'slime-repl-clear-buffer-hook 'slime-clear-presentations)
   (add-hook 'slime-edit-definition-hooks 'slime-edit-presentation)
   (setq sldb-insert-frame-variable-value-function
         'slime-presentation-sldb-insert-frame-variable-value)
   (slime-presentation-init-keymaps)
   (slime-presentation-add-easy-menu)))

;; To get presentations in the inspector as well, add this to your
;; init file.
;;
;; (eval-after-load 'slime-presentations
;;    '(setq slime-inspector-insert-ispec-function
;;           'slime-presentation-inspector-insert-ispec))
;;
(defface slime-repl-output-mouseover-face
    '((t (:box (:line-width 1 :color "black" :style released-button)
          :inherit slime-repl-inputed-output-face)))
  "Face for Lisp output in the SLIME REPL, when the mouse hovers over it"
  :group 'slime-repl)

(defface slime-repl-inputed-output-face
  '((((class color) (background light)) (:foreground "Red"))
    (((class color) (background dark)) (:foreground "light salmon"))
    (t (:slant italic)))
  "Face for the result of an evaluation in the SLIME REPL."
  :group 'slime-repl)

;; FIXME: This conditional is not right - just used because the code
;; here does not work in XEmacs.
(when (boundp 'text-property-default-nonsticky)
  (cl-pushnew '(slime-repl-presentation . t) text-property-default-nonsticky
              :test 'equal)
  (cl-pushnew '(slime-repl-result-face . t) text-property-default-nonsticky
              :test 'equal))

(make-variable-buffer-local
 (defvar slime-presentation-start-to-point (make-hash-table)))

(defun slime-mark-presentation-start (id &optional target)
  "Mark the beginning of a presentation with the given ID.
TARGET can be nil (regular process output) or :repl-result."
  (setf (gethash id slime-presentation-start-to-point)
        ;; We use markers because text can also be inserted before this presentation.
        ;; (Output arrives while we are writing presentations within REPL results.)
        (copy-marker (slime-repl-output-target-marker target) nil)))

(defun slime-mark-presentation-start-handler (process string)
  (if (and string (string-match "<\\([-0-9]+\\)" string))
      (let* ((match (substring string (match-beginning 1) (match-end 1)))
             (id (car (read-from-string match))))
        (slime-mark-presentation-start id))))

(defun slime-mark-presentation-end (id &optional target)
  "Mark the end of a presentation with the given ID.
TARGET can be nil (regular process output) or :repl-result."
  (let ((start (gethash id slime-presentation-start-to-point)))
    (remhash id slime-presentation-start-to-point)
    (when start
      (let* ((marker (slime-repl-output-target-marker target))
             (buffer (and marker (marker-buffer marker))))
        (with-current-buffer buffer
          (let ((end (marker-position marker)))
            (slime-add-presentation-properties start end
                                               id nil)))))))

(defun slime-mark-presentation-end-handler (process string)
  (if (and string (string-match ">\\([-0-9]+\\)" string))
      (let* ((match (substring string (match-beginning 1) (match-end 1)))
             (id (car (read-from-string match))))
        (slime-mark-presentation-end id))))

(cl-defstruct slime-presentation text id)

(defvar slime-presentation-syntax-table
  (let ((table (copy-syntax-table lisp-mode-syntax-table)))
    ;; We give < and > parenthesis syntax, so that #< ... > is treated
    ;; as a balanced expression.  This allows to use C-M-k, C-M-SPC,
    ;; etc. to deal with a whole presentation.  (For Lisp mode, this
    ;; is not desirable, since we do not wish to get a mismatched
    ;; paren highlighted everytime we type < or >.)
    (modify-syntax-entry ?< "(>" table)
    (modify-syntax-entry ?> ")<" table)
    table)
  "Syntax table for presentations.")

(defun slime-add-presentation-properties (start end id result-p)
  "Make the text between START and END a presentation with ID.
RESULT-P decides whether a face for a return value or output text is used."
  (let* ((text (buffer-substring-no-properties start end))
         (presentation (make-slime-presentation :text text :id id)))
    (let ((inhibit-modification-hooks t))
      (add-text-properties start end
                           `(modification-hooks (slime-after-change-function)
                             insert-in-front-hooks (slime-after-change-function)
                             insert-behind-hooks (slime-after-change-function)
                             syntax-table ,slime-presentation-syntax-table
                             rear-nonsticky t))
      ;; Use the presentation as the key of a text property
      (cl-case (- end start)
        (0)
        (1
         (add-text-properties start end
                              `(slime-repl-presentation ,presentation
                                ,presentation :start-and-end)))
        (t
         (add-text-properties start (1+ start)
                              `(slime-repl-presentation ,presentation
                                ,presentation :start))
         (when (> (- end start) 2)
           (add-text-properties (1+ start) (1- end)
                                `(,presentation :interior)))
         (add-text-properties (1- end) end
                              `(slime-repl-presentation ,presentation
                                ,presentation :end))))
      ;; Also put an overlay for the face and the mouse-face.  This enables
      ;; highlighting of nested presentations.  However, overlays get lost
      ;; when we copy a presentation; their removal is also not undoable.
      ;; In these cases the mouse-face text properties need to take over ---
      ;; but they do not give nested highlighting.
      (slime-ensure-presentation-overlay start end presentation))))

(defvar slime-presentation-map (make-sparse-keymap))

(defun slime-ensure-presentation-overlay (start end presentation)
  (unless (cl-find presentation (overlays-at start)
                   :key (lambda (overlay)
                          (overlay-get overlay 'slime-repl-presentation)))
    (let ((overlay (make-overlay start end (current-buffer) t nil)))
      (overlay-put overlay 'slime-repl-presentation presentation)
      (overlay-put overlay 'mouse-face 'slime-repl-output-mouseover-face)
      (overlay-put overlay 'help-echo
                   (if (eq major-mode 'slime-repl-mode)
                       "mouse-2: copy to input; mouse-3: menu"
                     "mouse-2: inspect; mouse-3: menu"))
      (overlay-put overlay 'face 'slime-repl-inputed-output-face)
      (overlay-put overlay 'keymap slime-presentation-map))))

(defun slime-remove-presentation-properties (from to presentation)
  (let ((inhibit-read-only t))
    (remove-text-properties from to
                            `(,presentation t syntax-table t rear-nonsticky t))
    (when (eq (get-text-property from 'slime-repl-presentation) presentation)
      (remove-text-properties from (1+ from) `(slime-repl-presentation t)))
    (when (eq (get-text-property (1- to) 'slime-repl-presentation) presentation)
      (remove-text-properties (1- to) to `(slime-repl-presentation t)))
    (dolist (overlay (overlays-at from))
      (when (eq (overlay-get overlay 'slime-repl-presentation) presentation)
        (delete-overlay overlay)))))

(defun slime-insert-presentation (string output-id &optional rectangle)
  "Insert STRING in current buffer and mark it as a presentation
corresponding to OUTPUT-ID.  If RECTANGLE is true, indent multi-line
strings to line up below the current point."
  (cl-labels ((insert-it ()
                       (if rectangle
                           (slime-insert-indented string)
                         (insert string))))
    (let ((start (point)))
      (insert-it)
      (slime-add-presentation-properties start (point) output-id t))))

(defun slime-presentation-whole-p (presentation start end &optional object)
  (let ((object (or object (current-buffer))))
    (string= (cl-etypecase object
               (buffer (with-current-buffer object
                         (buffer-substring-no-properties start end)))
               (string (substring-no-properties object start end)))
             (slime-presentation-text presentation))))

(defun slime-presentations-around-point (point &optional object)
  (let ((object (or object (current-buffer))))
    (cl-loop for (key value . rest) on (text-properties-at point object) by 'cddr
             when (slime-presentation-p key)
             collect key)))

(defun slime-presentation-start-p (tag)
  (memq tag '(:start :start-and-end)))

(defun slime-presentation-stop-p (tag)
  (memq tag '(:end :start-and-end)))

(cl-defun slime-presentation-start (point presentation
                                          &optional (object (current-buffer)))
  "Find start of `presentation' at `point' in `object'.
Return buffer index and whether a start-tag was found."
  (let* ((this-presentation (get-text-property point presentation object)))
    (while (not (slime-presentation-start-p this-presentation))
      (let ((change-point (previous-single-property-change
                           point presentation object (point-min))))
        (unless change-point
          (cl-return-from slime-presentation-start
            (cl-values (cl-etypecase object
                         (buffer (with-current-buffer object 1))
                         (string 0))
                       nil)))
        (setq this-presentation (get-text-property change-point
                                                   presentation object))
        (unless this-presentation
          (cl-return-from slime-presentation-start
            (cl-values point nil)))
        (setq point change-point)))
    (cl-values point t)))

(cl-defun slime-presentation-end (point presentation
                                        &optional (object (current-buffer)))
  "Find end of presentation at `point' in `object'.  Return buffer
index (after last character of the presentation) and whether an
end-tag was found."
  (let* ((this-presentation (get-text-property point presentation object)))
    (while (not (slime-presentation-stop-p this-presentation))
      (let ((change-point (next-single-property-change
                           point presentation object)))
        (unless change-point
          (cl-return-from slime-presentation-end
            (cl-values (cl-etypecase object
                         (buffer (with-current-buffer object (point-max)))
                         (string (length object)))
                       nil)))
        (setq point change-point)
        (setq this-presentation (get-text-property point
                                                   presentation object))))
    (if this-presentation
        (let ((after-end (next-single-property-change point
                                                      presentation object)))
          (if (not after-end)
              (cl-values (cl-etypecase object
                           (buffer (with-current-buffer object (point-max)))
                           (string (length object)))
                         t)
            (cl-values after-end t)))
      (cl-values point nil))))

(cl-defun slime-presentation-bounds (point presentation
                                           &optional (object (current-buffer)))
  "Return start index and end index of `presentation' around `point'
in `object', and whether the presentation is complete."
  (cl-multiple-value-bind (start good-start)
      (slime-presentation-start point presentation object)
    (cl-multiple-value-bind (end good-end)
        (slime-presentation-end point presentation object)
      (cl-values start end
                 (and good-start good-end
                      (slime-presentation-whole-p presentation
                                                  start end object))))))

(defun slime-presentation-around-point (point &optional object)
  "Return presentation, start index, end index, and whether the
presentation is complete."
  (let ((object (or object (current-buffer)))
        (innermost-presentation nil)
        (innermost-start 0)
        (innermost-end most-positive-fixnum))
    (dolist (presentation (slime-presentations-around-point point object))
      (cl-multiple-value-bind (start end whole-p)
          (slime-presentation-bounds point presentation object)
        (when whole-p
          (when (< (- end start) (- innermost-end innermost-start))
            (setq innermost-start start
                  innermost-end end
                  innermost-presentation presentation)))))
    (cl-values innermost-presentation
               innermost-start innermost-end)))

(defun slime-presentation-around-or-before-point (point &optional object)
  (let ((object (or object (current-buffer))))
    (cl-multiple-value-bind (presentation start end whole-p)
        (slime-presentation-around-point point object)
      (if (or presentation (= point (point-min)))
          (cl-values presentation start end whole-p)
        (slime-presentation-around-point (1- point) object)))))

(defun slime-presentation-around-or-before-point-or-error (point)
  (cl-multiple-value-bind (presentation start end whole-p)
      (slime-presentation-around-or-before-point point)
    (unless presentation
      (error "No presentation at point"))
    (cl-values presentation start end whole-p)))

(cl-defun slime-for-each-presentation-in-region (from to function
                                                      &optional (object (current-buffer)))
  "Call `function' with arguments `presentation', `start', `end',
`whole-p' for every presentation in the region `from'--`to' in the
string or buffer `object'."
  (cl-labels ((handle-presentation (presentation point)
                                   (cl-multiple-value-bind (start end whole-p)
                                       (slime-presentation-bounds point presentation object)
                                     (funcall function presentation start end whole-p))))
    ;; Handle presentations active at `from'.
    (dolist (presentation (slime-presentations-around-point from object))
      (handle-presentation presentation from))
    ;; Use the `slime-repl-presentation' property to search for new presentations.
    (let ((point from))
      (while (< point to)
        (setq point (next-single-property-change point 'slime-repl-presentation
                                                 object to))
        (let* ((presentation (get-text-property point 'slime-repl-presentation object))
               (status (get-text-property point presentation object)))
          (when (slime-presentation-start-p status)
            (handle-presentation presentation point)))))))

;; XEmacs compatibility hack, from message by Stephen J. Turnbull on
;; xemacs-beta@xemacs.org of 18 Mar 2002
(unless (boundp 'undo-in-progress)
  (defvar undo-in-progress nil
    "Placeholder defvar for XEmacs compatibility from SLIME.")
  (defadvice undo-more (around slime activate)
    (let ((undo-in-progress t)) ad-do-it)))

(defun slime-after-change-function (start end &rest ignore)
  "Check all presentations within and adjacent to the change.
When a presentation has been altered, change it to plain text."
  (let ((inhibit-modification-hooks t))
    (let ((real-start (max 1 (1- start)))
          (real-end   (min (1+ (buffer-size)) (1+ end)))
          (any-change nil))
      ;; positions around the change
      (slime-for-each-presentation-in-region
       real-start real-end
       (lambda (presentation from to whole-p)
         (cond
          (whole-p
           (slime-ensure-presentation-overlay from to presentation))
          ((not undo-in-progress)
           (slime-remove-presentation-properties from to
                                                 presentation)
           (setq any-change t)))))
      (when any-change
        (undo-boundary)))))

(defun slime-presentation-around-click (event)
  "Return the presentation around the position of the mouse-click EVENT.
If there is no presentation, signal an error.
Also return the start position, end position, and buffer of the presentation."
  (when (and (featurep 'xemacs) (not (button-press-event-p event)))
    (error "Command must be bound to a button-press-event"))
  (let ((point (if (featurep 'xemacs) (event-point event) (posn-point (event-end event))))
        (window (if (featurep 'xemacs) (event-window event) (caadr event))))
    (with-current-buffer (window-buffer window)
      (cl-multiple-value-bind (presentation start end)
          (slime-presentation-around-point point)
        (unless presentation
          (error "No presentation at click"))
        (cl-values presentation start end (current-buffer))))))

(defun slime-check-presentation (from to buffer presentation)
  (unless (slime-eval `(cl:nth-value 1 (swank:lookup-presented-object
                                        ',(slime-presentation-id presentation))))
    (with-current-buffer buffer
      (slime-remove-presentation-properties from to presentation))))

(defun slime-copy-or-inspect-presentation-at-mouse (event)
  (interactive "e") ; no "@" -- we don't want to select the clicked-at window
  (cl-multiple-value-bind (presentation start end buffer)
      (slime-presentation-around-click event)
    (slime-check-presentation start end buffer presentation)
    (if (with-current-buffer buffer
          (eq major-mode 'slime-repl-mode))
        (slime-copy-presentation-at-mouse-to-repl event)
      (slime-inspect-presentation-at-mouse event))))

(defun slime-inspect-presentation (presentation start end buffer)
  (let ((reset-p
	 (with-current-buffer buffer
	   (not (eq major-mode 'slime-inspector-mode)))))
    (slime-eval-async `(swank:inspect-presentation ',(slime-presentation-id presentation) ,reset-p)
      'slime-open-inspector)))

(defun slime-inspect-presentation-at-mouse (event)
  (interactive "e")
  (cl-multiple-value-bind (presentation start end buffer)
      (slime-presentation-around-click event)
    (slime-inspect-presentation presentation start end buffer)))

(defun slime-inspect-presentation-at-point (point)
  (interactive "d")
  (cl-multiple-value-bind (presentation start end)
      (slime-presentation-around-or-before-point-or-error point)
    (slime-inspect-presentation presentation start end (current-buffer))))


(defun slime-M-.-presentation (presentation start end buffer &optional where)
  (let* ((id (slime-presentation-id presentation))
	 (presentation-string (format "Presentation %s" id))
	 (location (slime-eval `(swank:find-definition-for-thing
				 (swank:lookup-presented-object
				  ',(slime-presentation-id presentation))))))
    (unless (eq (car location) :error)
      (slime-edit-definition-cont
       (and location (list (make-slime-xref :dspec `(,presentation-string)
                                            :location location)))
       presentation-string
       where))))

(defun slime-M-.-presentation-at-mouse (event)
  (interactive "e")
  (cl-multiple-value-bind (presentation start end buffer)
      (slime-presentation-around-click event)
    (slime-M-.-presentation presentation start end buffer)))

(defun slime-M-.-presentation-at-point (point)
  (interactive "d")
  (cl-multiple-value-bind (presentation start end)
      (slime-presentation-around-or-before-point-or-error point)
    (slime-M-.-presentation presentation start end (current-buffer))))

(defun slime-edit-presentation (name &optional where)
  (if (or current-prefix-arg (not (equal (slime-symbol-at-point) name)))
      nil ; NAME came from user explicitly, so decline.
    (cl-multiple-value-bind (presentation start end whole-p)
        (slime-presentation-around-or-before-point (point))
      (when presentation
        (slime-M-.-presentation presentation start end (current-buffer) where)))))

(defun slime-copy-presentation-to-repl (presentation start end buffer)
  (let ((text (with-current-buffer buffer
                ;; we use the buffer-substring rather than the
                ;; presentation text to capture any overlays
                (buffer-substring start end)))
        (id (slime-presentation-id presentation)))
    (unless (integerp id)
      (setq id (slime-eval `(swank:lookup-and-save-presented-object-or-lose ',id))))
    (unless (eql major-mode 'slime-repl-mode)
      (slime-switch-to-output-buffer))
    (cl-flet ((do-insertion ()
                (unless (looking-back "\\s-" (- (point) 1))
                  (insert " "))
                (slime-insert-presentation text id)
                (unless (or (eolp) (looking-at "\\s-"))
                  (insert " "))))
      (if (>= (point) slime-repl-prompt-start-mark)
          (do-insertion)
        (save-excursion
          (goto-char (point-max))
          (do-insertion))))))

(defun slime-copy-presentation-at-mouse-to-repl (event)
  (interactive "e")
  (cl-multiple-value-bind (presentation start end buffer)
      (slime-presentation-around-click event)
    (slime-copy-presentation-to-repl presentation start end buffer)))

(defun slime-copy-presentation-at-point-to-repl (point)
  (interactive "d")
  (cl-multiple-value-bind (presentation start end)
      (slime-presentation-around-or-before-point-or-error point)
    (slime-copy-presentation-to-repl presentation start end (current-buffer))))

(defun slime-copy-presentation-at-mouse-to-point (event)
  (interactive "e")
  (cl-multiple-value-bind (presentation start end buffer)
      (slime-presentation-around-click event)
    (let ((presentation-text
           (with-current-buffer buffer
             (buffer-substring start end))))
      (when (not (string-match "\\s-"
                               (buffer-substring (1- (point)) (point))))
        (insert " "))
      (insert presentation-text)
      (slime-after-change-function (point) (point))
      (when (and (not (eolp)) (not (looking-at "\\s-")))
        (insert " ")))))

(defun slime-copy-presentation-to-kill-ring (presentation start end buffer)
  (let ((presentation-text
         (with-current-buffer buffer
           (buffer-substring start end))))
    (kill-new presentation-text)
    (message "Saved presentation \"%s\" to kill ring" presentation-text)))

(defun slime-copy-presentation-at-mouse-to-kill-ring (event)
  (interactive "e")
  (cl-multiple-value-bind (presentation start end buffer)
      (slime-presentation-around-click event)
    (slime-copy-presentation-to-kill-ring presentation start end buffer)))

(defun slime-copy-presentation-at-point-to-kill-ring (point)
  (interactive "d")
  (cl-multiple-value-bind (presentation start end)
      (slime-presentation-around-or-before-point-or-error point)
    (slime-copy-presentation-to-kill-ring presentation start end (current-buffer))))

(defun slime-describe-presentation (presentation)
  (slime-eval-describe
   `(swank::describe-to-string
     (swank:lookup-presented-object ',(slime-presentation-id presentation)))))

(defun slime-describe-presentation-at-mouse (event)
  (interactive "@e")
  (cl-multiple-value-bind (presentation) (slime-presentation-around-click event)
    (slime-describe-presentation presentation)))

(defun slime-describe-presentation-at-point (point)
  (interactive "d")
  (cl-multiple-value-bind (presentation)
      (slime-presentation-around-or-before-point-or-error point)
    (slime-describe-presentation presentation)))

(defun slime-pretty-print-presentation (presentation)
  (slime-eval-describe
   `(swank::swank-pprint
     (cl:list
      (swank:lookup-presented-object ',(slime-presentation-id presentation))))))

(defun slime-pretty-print-presentation-at-mouse (event)
  (interactive "@e")
  (cl-multiple-value-bind (presentation) (slime-presentation-around-click event)
    (slime-pretty-print-presentation presentation)))

(defun slime-pretty-print-presentation-at-point (point)
  (interactive "d")
  (cl-multiple-value-bind (presentation)
      (slime-presentation-around-or-before-point-or-error point)
    (slime-pretty-print-presentation presentation)))

(defun slime-mark-presentation (point)
  (interactive "d")
  (cl-multiple-value-bind (presentation start end)
      (slime-presentation-around-or-before-point-or-error point)
    (goto-char start)
    (push-mark end nil t)))

(defun slime-previous-presentation (&optional arg)
  "Move point to the beginning of the first presentation before point.
With ARG, do this that many times.
A negative argument means move forward instead."
  (interactive "p")
  (unless arg (setq arg 1))
  (slime-next-presentation (- arg)))

(defun slime-next-presentation (&optional arg)
  "Move point to the beginning of the next presentation after point.
With ARG, do this that many times.
A negative argument means move backward instead."
  (interactive "p")
  (unless arg (setq arg 1))
  (cond
   ((cl-plusp arg)
    (dotimes (i arg)
      ;; First skip outside the current surrounding presentation (if any)
      (cl-multiple-value-bind (presentation start end)
	  (slime-presentation-around-point (point))
	(when presentation
	  (goto-char end)))
      (let ((p (next-single-property-change (point) 'slime-repl-presentation)))
	(unless p
	  (error "No next presentation"))
	(cl-multiple-value-bind (presentation start end)
	    (slime-presentation-around-or-before-point-or-error p)
	  (goto-char start)))))
   ((cl-minusp arg)
    (dotimes (i (- arg))
      ;; First skip outside the current surrounding presentation (if any)
      (cl-multiple-value-bind (presentation start end)
	  (slime-presentation-around-point (point))
	(when presentation
	  (goto-char start)))
      (let ((p (previous-single-property-change (point) 'slime-repl-presentation)))
	(unless p
	  (error "No previous presentation"))
	(cl-multiple-value-bind (presentation start end)
	    (slime-presentation-around-or-before-point-or-error p)
	  (goto-char start)))))))

(define-key  slime-presentation-map [mouse-2] 'slime-copy-or-inspect-presentation-at-mouse)
(define-key  slime-presentation-map [mouse-3] 'slime-presentation-menu)

(when (featurep 'xemacs)
  (define-key  slime-presentation-map [button2] 'slime-copy-or-inspect-presentation-at-mouse)
  (define-key  slime-presentation-map [button3] 'slime-presentation-menu))

;; protocol for handling up a menu.
;; 1. Send lisp message asking for menu choices for this object.
;;    Get back list of strings.
;; 2. Let used choose
;; 3. Call back to execute menu choice, passing nth and string of choice

(defun slime-menu-choices-for-presentation (presentation buffer from to choice-to-lambda)
  "Return a menu for `presentation' at `from'--`to' in `buffer', suitable for `x-popup-menu'."
  (let* ((what (slime-presentation-id presentation))
         (choices (with-current-buffer buffer
                    (slime-eval
                     `(swank::menu-choices-for-presentation-id ',what)))))
    (cl-labels ((savel (f) ;; IMPORTANT - xemacs can't handle lambdas in x-popup-menu. So give them a name
                     (let ((sym (cl-gensym)))
                       (setf (gethash sym choice-to-lambda) f)
                       sym)))
      (cl-etypecase choices
        (list
         `(,(format "Presentation %s" (truncate-string-to-width
                                       (slime-presentation-text presentation)
                                       30 nil nil t))
           (""
            ("Find Definition" . ,(savel 'slime-M-.-presentation-at-mouse))
            ("Inspect" . ,(savel 'slime-inspect-presentation-at-mouse))
            ("Describe" . ,(savel 'slime-describe-presentation-at-mouse))
            ("Pretty-print" . ,(savel 'slime-pretty-print-presentation-at-mouse))
            ("Copy to REPL" . ,(savel 'slime-copy-presentation-at-mouse-to-repl))
            ("Copy to kill ring" . ,(savel 'slime-copy-presentation-at-mouse-to-kill-ring))
            ,@(unless buffer-read-only
                `(("Copy to point" . ,(savel 'slime-copy-presentation-at-mouse-to-point))))
            ,@(let ((nchoice 0))
                (mapcar
                 (lambda (choice)
                   (cl-incf nchoice)
                   (cons choice
                         (savel `(lambda ()
                                   (interactive)
                                   (slime-eval
                                    '(swank::execute-menu-choice-for-presentation-id
                                      ',what ,nchoice ,(nth (1- nchoice) choices)))))))
                 choices)))))
        (symbol                           ; not-present
         (with-current-buffer buffer
           (slime-remove-presentation-properties from to presentation))
         (sit-for 0)                      ; allow redisplay
         `("Object no longer recorded"
           ("sorry" . ,(if (featurep 'xemacs) nil '(nil)))))))))

(defun slime-presentation-menu (event)
  (interactive "e")
  (let* ((point (if (featurep 'xemacs) (event-point event)
                  (posn-point (event-end event))))
         (window (if (featurep 'xemacs) (event-window event) (caadr event)))
         (buffer (window-buffer window))
         (choice-to-lambda (make-hash-table)))
    (cl-multiple-value-bind (presentation from to)
        (with-current-buffer buffer
          (slime-presentation-around-point point))
      (unless presentation
        (error "No presentation at event position"))
      (let ((menu (slime-menu-choices-for-presentation
                   presentation buffer from to choice-to-lambda)))
        (let ((choice (x-popup-menu event menu)))
          (when choice
            (call-interactively (gethash choice choice-to-lambda))))))))

(defun slime-presentation-expression (presentation)
  "Return a string that contains a CL s-expression accessing
the presented object."
  (let ((id (slime-presentation-id presentation)))
    (cl-etypecase id
      (number
       ;; Make sure it works even if *read-base* is not 10.
       (format "(swank:lookup-presented-object-or-lose %d.)" id))
      (list
       ;; for frame variables and inspector parts
       (format "(swank:lookup-presented-object-or-lose '%s)" id)))))

(defun slime-buffer-substring-with-reified-output (start end)
  (let ((str-props (buffer-substring start end))
        (str-no-props (buffer-substring-no-properties start end)))
    (slime-reify-old-output str-props str-no-props)))

(defun slime-reify-old-output (str-props str-no-props)
  (let ((pos (slime-property-position 'slime-repl-presentation str-props)))
    (if (null pos)
        str-no-props
      (cl-multiple-value-bind (presentation start-pos end-pos whole-p)
          (slime-presentation-around-point pos str-props)
        (if (not presentation)
            str-no-props
          (concat (substring str-no-props 0 pos)
                  ;; Eval in the reader so that we play nice with quote.
                  ;; -luke (19/May/2005)
                  "#." (slime-presentation-expression presentation)
                  (slime-reify-old-output (substring str-props end-pos)
                                          (substring str-no-props end-pos))))))))



(defun slime-repl-grab-old-output (replace)
  "Resend the old REPL output at point.
If replace it non-nil the current input is replaced with the old
output; otherwise the new input is appended."
  (cl-multiple-value-bind (presentation beg end)
      (slime-presentation-around-or-before-point (point))
    (slime-check-presentation beg end (current-buffer) presentation)
    (let ((old-output (buffer-substring beg end))) ;;keep properties
      ;; Append the old input or replace the current input
      (cond (replace (goto-char slime-repl-input-start-mark))
            (t (goto-char (point-max))
               (unless (eq (char-before) ?\ )
                 (insert " "))))
      (delete-region (point) (point-max))
      (let ((inhibit-read-only t))
        (insert old-output)))))

;;; Presentation-related key bindings, non-context menu

(defvar slime-presentation-command-map nil
  "Keymap for presentation-related commands. Bound to a prefix key.")

(defvar slime-presentation-bindings
  '((?i slime-inspect-presentation-at-point)
    (?d slime-describe-presentation-at-point)
    (?w slime-copy-presentation-at-point-to-kill-ring)
    (?r slime-copy-presentation-at-point-to-repl)
    (?p slime-previous-presentation)
    (?n slime-next-presentation)
    (?\  slime-mark-presentation)))

(defun slime-presentation-init-keymaps ()
  (slime-init-keymap 'slime-presentation-command-map nil t
		     slime-presentation-bindings)
  (define-key slime-presentation-command-map "\M-o" 'slime-clear-presentations)
  ;; C-c C-v is the prefix for the presentation-command map.
  (define-key slime-prefix-map "\C-v" slime-presentation-command-map))

(defun slime-presentation-around-or-before-point-p ()
  (cl-multiple-value-bind (presentation beg end)
      (slime-presentation-around-or-before-point (point))
    presentation))

(defvar slime-presentation-easy-menu
  (let ((P '(slime-presentation-around-or-before-point-p)))
    `("Presentations"
      [ "Find Definition" slime-M-.-presentation-at-point ,P ]
      [ "Inspect" slime-inspect-presentation-at-point ,P ]
      [ "Describe" slime-describe-presentation-at-point ,P ]
      [ "Pretty-print" slime-pretty-print-presentation-at-point ,P ]
      [ "Copy to REPL" slime-copy-presentation-at-point-to-repl ,P ]
      [ "Copy to kill ring" slime-copy-presentation-at-point-to-kill-ring ,P ]
      [ "Mark" slime-mark-presentation ,P ]
      "--"
      [ "Previous presentation" slime-previous-presentation ]
      [ "Next presentation" slime-next-presentation ]
      "--"
      [ "Clear all presentations" slime-clear-presentations ])))

(defun slime-presentation-add-easy-menu ()
  (easy-menu-define menubar-slime-presentation slime-mode-map "Presentations" slime-presentation-easy-menu)
  (easy-menu-define menubar-slime-presentation slime-repl-mode-map "Presentations" slime-presentation-easy-menu)
  (easy-menu-define menubar-slime-presentation sldb-mode-map "Presentations" slime-presentation-easy-menu)
  (easy-menu-define menubar-slime-presentation slime-inspector-mode-map "Presentations" slime-presentation-easy-menu)
  (easy-menu-add slime-presentation-easy-menu 'slime-mode-map)
  (easy-menu-add slime-presentation-easy-menu 'slime-repl-mode-map)
  (easy-menu-add slime-presentation-easy-menu 'sldb-mode-map)
  (easy-menu-add slime-presentation-easy-menu 'slime-inspector-mode-map))

;;; hook functions (hard to isolate stuff)

(defun slime-dispatch-presentation-event (event)
  (slime-dcase event
    ((:presentation-start id &optional target)
     (slime-mark-presentation-start id target)
     t)
    ((:presentation-end id &optional target)
     (slime-mark-presentation-end id target)
     t)
    (t nil)))

(defun slime-presentation-write-result (string)
  (with-current-buffer (slime-output-buffer)
    (let ((marker (slime-repl-output-target-marker :repl-result))
          (saved-point (point-marker)))
      (goto-char marker)
      (slime-propertize-region `(face slime-repl-result-face
                                      rear-nonsticky (face))
        (insert string))
      ;; Move the input-start marker after the REPL result.
      (set-marker marker (point))
      (set-marker slime-output-end (point))
      ;; Restore point before insertion but only it if was farther
      ;; than `marker'. Omitting this breaks REPL test
      ;; `repl-type-ahead'.
      (when (> saved-point (point))
        (goto-char saved-point)))
    (slime-repl-show-maximum-output)))

(defun slime-presentation-write (string &optional target)
  (cl-case target
    ((nil)                              ; Regular process output
     (slime-repl-emit string))
    (:repl-result
     (slime-presentation-write-result string))
    (t (slime-repl-emit-to-target string target))))

(defun slime-presentation-current-input (&optional until-point-p)
  "Return the current input as string.
The input is the region from after the last prompt to the end of
buffer. Presentations of old results are expanded into code."
  (slime-buffer-substring-with-reified-output (slime-repl-history-yank-start)
                                              (if until-point-p
                                                  (point)
                                                (point-max))))

(defun slime-presentation-on-return-pressed (end-of-input)
  (when (and (car (slime-presentation-around-or-before-point (point)))
             (< (point) slime-repl-input-start-mark))
    (slime-repl-grab-old-output end-of-input)
    (slime-repl-recenter-if-needed)
    t))

(defun slime-presentation-bridge-insert (process output)
  (slime-output-filter process (or output "")))

(defun slime-presentation-on-stream-open (stream)
  (install-bridge)
  (setq bridge-insert-function #'slime-presentation-bridge-insert)
  (setq bridge-destination-insert nil)
  (setq bridge-source-insert nil)
  (setq bridge-handlers
	(cl-list* '("<" . slime-mark-presentation-start-handler)
	          '(">" . slime-mark-presentation-end-handler)
	          bridge-handlers)))

(defun slime-clear-presentations ()
  "Forget all objects associated to SLIME presentations.
This allows the garbage collector to remove these objects
even on Common Lisp implementations without weak hash tables."
  (interactive)
  (slime-eval-async `(swank:clear-repl-results))
  (unless (eql major-mode 'slime-repl-mode)
    (slime-switch-to-output-buffer))
  (slime-for-each-presentation-in-region 1 (1+ (buffer-size))
					 (lambda (presentation from to whole-p)
					   (slime-remove-presentation-properties from to
										 presentation))))

(defun slime-presentation-inspector-insert-ispec (ispec)
  (if (stringp ispec)
      (insert ispec)
    (slime-dcase ispec
      ((:value string id)
       (slime-propertize-region
           (list 'slime-part-number id
                 'mouse-face 'highlight
                 'face 'slime-inspector-value-face)
         (slime-insert-presentation string `(:inspected-part ,id) t)))
      ((:label string)
       (insert (slime-inspector-fontify label string)))
      ((:action string id)
       (slime-insert-propertized (list 'slime-action-number id
                                       'mouse-face 'highlight
                                       'face 'slime-inspector-action-face)
                                 string)))))

(defun slime-presentation-sldb-insert-frame-variable-value (value frame index)
  (slime-insert-presentation
   (sldb-in-face local-value value)
   `(:frame-var ,slime-current-thread ,(car frame) ,index) t))

(defun slime-presentations-on-connected ()
  (slime-eval-async `(swank:init-presentations)))

(provide 'slime-presentations)
