File: racket-show.el

package info (click to toggle)
racket-mode 20210916git0-2
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 1,076 kB
  • sloc: lisp: 10,354; makefile: 58
file content (180 lines) | stat: -rw-r--r-- 7,714 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
;;; racket-show.el -*- lexical-binding: t -*-

;; Copyright (c) 2013-2020 by Greg Hendershott.
;; Portions Copyright (C) 1985-1986, 1999-2013 Free Software Foundation, Inc.

;; Author: Greg Hendershott
;; URL: https://github.com/greghendershott/racket-mode

;; License:
;; This is free software; you can redistribute it and/or modify it
;; under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 2, or (at your option)
;; any later version. This 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. See
;; http://www.gnu.org/licenses/ for details.

(require 'racket-util)
(require 'racket-custom)
(require 'pos-tip)
(require 'cl-macs)

(defun racket-show (val &optional pos)
  "See the variable `racket-show-functions' for information about VAL and POS."
  (dolist (f racket-show-functions)
    (funcall f val pos)))

(defun racket-show-echo-area (v &optional _pos)
  "Show things in the echo area.

A value for the variable `racket-show-functions'."
  (let ((message-log-max nil))
    (if v
        (message "%s" v)
      (message ""))))

(defun racket-show-header-line (v &optional _pos)
  "Show things using a buffer header line.

A value for the variable `racket-show-functions'.

When there is nothing to show, keep a blank header-line. That
way, the buffer below doesn't \"jump up and down\" by a line as
messages appear and disappear. Only when V is nil do we remove
the header line."
  (setq-local header-line-format
              (and v (format "%s" (racket--only-first-line v)))))

(defun racket--only-first-line (str)
  (save-match-data
    (string-match (rx (group (* (not (any ?\n))))) str)
    (match-string 1 str)))

(defun racket-show-pos-tip (v &optional pos)
  "Show things using `pos-tip-show' if available.

A value for the variable `racket-show-functions'."
  (when (racket--pos-tip-available-p)
    (if (racket--non-empty-string-p v)
        (pos-tip-show v nil pos)
      (pos-tip-hide))))

(defun racket--pos-tip-available-p ()
  "Is `pos-tip' available and expected to work on current frame?"
  (and (fboundp 'x-hide-tip)
       (fboundp 'x-show-tip)
       (not (memq window-system (list nil 'pc)))))

(defvar-local racket--pseudo-tooltip-overlays nil)

(defun racket-show-pseudo-tooltip (v &optional pos)
  "Show using an overlay that resembles a tooltip.

This is nicer than `racket-show-pos-tip' because it:

  - Doesn't flicker while navigating.
  - Doesn't disappear after a timeout.
  - Performs well when `x-gtk-use-system-tooltips' is nil.

On the other hand, this does not look as nice when displaying
text that spans multiple lines. In that case, we simply
left-justify everything and do not draw any border."
  (racket--delete-pseudo-tooltip-overlays)
  (when (racket--non-empty-string-p v)
    (setq-local racket--pseudo-tooltip-overlays
                (racket--make-pseudo-tooltip-overlays v pos))))

(defun racket--delete-pseudo-tooltip-overlays ()
  (dolist (ov racket--pseudo-tooltip-overlays)
    (delete-overlay ov))
  (setq-local racket--pseudo-tooltip-overlays nil))

(defun racket--make-pseudo-tooltip-overlays (text pos)
  "Create one or more overlays for a pseudo tooltip, returning them in a list."
  (if (string-match-p "\n" text)
      ;; When text is multi-line, we don't try to simulate a tooltip,
      ;; exactly. Instead we simply "insert" the multiple lines left
      ;; justified, before the next line.
      (let* ((text (propertize (concat text "\n")
                               'face
                               `(:inherit default
                                 :foreground ,(face-foreground 'tooltip)
                                 :background ,(face-background 'tooltip))))
             (eol (save-excursion (goto-char pos) (point-at-eol)))
             (ov (make-overlay eol (1+ eol))))
        (overlay-put ov 'after-string text)
        (list ov))
    ;; Otherwise we simulate a tooltip displayed one line below pos,
    ;; and one column right (although it might start further left
    ;; depending on window-width) "over" any existing text.
    (pcase-let*
        ((text     (propertize (concat " " text " ")
                               'face
                               `(:inherit default
                                 :foreground ,(face-foreground 'tooltip)
                                 :background ,(face-background 'tooltip)
                                 :box (:line-width -1))))
         (text-len (length text))
         (bol      (save-excursion (goto-char pos) (point-at-bol)))
         (eol      (save-excursion (goto-char pos) (point-at-eol)))
         ;; Position the tooltip on the next line, indented to `pos'
         ;; -- but not so far it ends off right edge.
         (indent   (max 0 (min (- pos bol)
                               (- (window-width) text-len))))
         (beg      (+ eol indent 1))
         (next-eol (save-excursion (goto-char (1+ eol)) (point-at-eol))))
      ;; If the tip starts before next-eol, create an overlay with the
      ;; 'display property, covering the span of the tooltip text but
      ;; not beyond next-eol.
      ;;
      ;; As a further wrinkle, when the overlay does not cover the
      ;; entire rest of the line, our new text might not be exactly
      ;; the same pixel width as the text we replace -- causing the
      ;; remaining text to shift. This can happen e.g. due to Unicode
      ;; characters like λ. Furthermore, our replacement text can be
      ;; two pixels wider because :box (:line-width -1) doesn't seem
      ;; to work as advertised.
      ;;
      ;; To avoid this, we add _another_ overlay simply to replace the
      ;; character following our tooltip with a space of the necessary
      ;; pixel width to keep things aligned. Although covering the
      ;; character with a space isn't great -- even if you justify it
      ;; as a sort of "shadow" (?) -- it's better than having the
      ;; remainder of the line jiggle as the tooltip apears and
      ;; disappears.
      (if (< beg next-eol)
          (cl-labels ((text-pixel-width
                       (beg end)
                       (car (window-text-pixel-size nil beg end))))
            (let* ((end  (min next-eol (+ beg text-len)))
                   (ov   (make-overlay beg end))
                   (old  (text-pixel-width (1+ eol) end))
                   (_    (overlay-put ov 'display text))
                   (new  (text-pixel-width (1+ eol) end))
                   (diff (- new old)))
              (cons
               ov
               (when (and (not (zerop diff))
                          (< end next-eol))
                 (let* ((ov-spacer   (make-overlay end (1+ end)))
                        (width       (text-pixel-width end (1+ end)))
                        (space-width (abs (- width diff))))
                   (overlay-put ov-spacer
                                'display
                                `(space
                                  :width (,space-width)))
                   (list ov-spacer))))))
        ;; Else the tip starts after next-eol. So, create an overlay
        ;; on the newline, and use an after-string, where we prefix
        ;; enough blank spaces before the tooltip text itself to get
        ;; the desired indent.
        (let* ((ov (make-overlay (1- next-eol) next-eol))
               (blanks (make-string (- beg next-eol) 32)))
          (overlay-put ov 'after-string (concat blanks text))
          (list ov))))))

(provide 'racket-show)

;; racket-show.el ends here