Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
marvel
GitHub Repository: marvel/qnf
Path: blob/master/elisp/popup.el
987 views
1
;;; popup.el --- Visual popup interface
2
3
;; Copyright (C) 2009, 2010 Tomohiro Matsuyama
4
5
;; Author: Tomohiro Matsuyama <[email protected]>
6
;; Keywords: lisp
7
;; Version: 0.4
8
9
;; This program is free software; you can redistribute it and/or modify
10
;; it under the terms of the GNU General Public License as published by
11
;; the Free Software Foundation, either version 3 of the License, or
12
;; (at your option) any later version.
13
14
;; This program is distributed in the hope that it will be useful,
15
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
;; GNU General Public License for more details.
18
19
;; You should have received a copy of the GNU General Public License
20
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
21
22
;;; Commentary:
23
24
;;
25
26
;;; Code:
27
28
(eval-when-compile
29
(require 'cl))
30
31
32
33
;; Utilities
34
35
(defvar popup-use-optimized-column-computation t
36
"Use optimized column computation routine.
37
If there is a problem, please set it to nil.")
38
39
;; Borrowed from anything.el
40
(defmacro popup-aif (test-form then-form &rest else-forms)
41
"Anaphoric if. Temporary variable `it' is the result of test-form."
42
(declare (indent 2))
43
`(let ((it ,test-form))
44
(if it ,then-form ,@else-forms)))
45
46
(defun popup-x-to-string (x)
47
"Convert any object to string effeciently.
48
This is faster than prin1-to-string in many cases."
49
(typecase x
50
(string x)
51
(symbol (symbol-name x))
52
(integer (number-to-string x))
53
(float (number-to-string x))
54
(t (format "%s" x))))
55
56
(defun popup-substring-by-width (string width)
57
"Return cons of substring and remaining string by `WIDTH'."
58
;; Expand tabs with 4 spaces
59
(setq string (replace-regexp-in-string "\t" " " string))
60
(loop with len = (length string)
61
with w = 0
62
for l from 0
63
for c in (append string nil)
64
while (<= (incf w (char-width c)) width)
65
finally return
66
(if (< l len)
67
(cons (substring string 0 l) (substring string l))
68
(list string))))
69
70
(defun popup-fill-string (string &optional width max-width justify squeeze)
71
"Split STRING into fixed width strings and return a cons cell like
72
\(WIDTH . ROWS). Here, the car WIDTH indicates the actual maxim width of ROWS.
73
74
The argument WIDTH specifies the width of filling each paragraph. WIDTH nil
75
means don't perform any justification and word wrap. Note that this function
76
doesn't add any padding characters at the end of each row.
77
78
MAX-WIDTH, if WIDTH is nil, specifies the maximum number of columns.
79
80
The optional fourth argument JUSTIFY specifies which kind of justification
81
to do: `full', `left', `right', `center', or `none' (equivalent to nil).
82
A value of t means handle each paragraph as specified by its text properties.
83
84
SQUEEZE nil means leave whitespaces other than line breaks untouched."
85
(if (eq width 0)
86
(error "Can't fill string with 0 width"))
87
(if width
88
(setq max-width width))
89
(with-temp-buffer
90
(let ((tab-width 4)
91
(fill-column width)
92
(left-margin 0)
93
(kinsoku-limit 1)
94
indent-tabs-mode
95
row rows)
96
(insert string)
97
(untabify (point-min) (point-max))
98
(if width
99
(fill-region (point-min) (point-max) justify (not squeeze)))
100
(goto-char (point-min))
101
(setq width 0)
102
(while (prog2
103
(let ((line (buffer-substring
104
(point) (progn (end-of-line) (point)))))
105
(if max-width
106
(while (progn
107
(setq row (truncate-string-to-width line max-width)
108
width (max width (string-width row)))
109
(push row rows)
110
(if (not (= (length row) (length line)))
111
(setq line (substring line (length row))))))
112
(setq width (max width (string-width line)))
113
(push line rows)))
114
(< (point) (point-max))
115
(beginning-of-line 2)))
116
(cons width (nreverse rows)))))
117
118
(defmacro popup-save-buffer-state (&rest body)
119
(declare (indent 0))
120
`(save-excursion
121
(let ((buffer-undo-list t)
122
(buffer-read-only nil)
123
(modified (buffer-modified-p)))
124
(unwind-protect
125
(progn ,@body)
126
(set-buffer-modified-p modified)))))
127
128
(defun popup-preferred-width (list)
129
"Return preferred width of popup to show `LIST' beautifully."
130
(loop with tab-width = 4
131
for item in list
132
for summary = (popup-item-summary item)
133
maximize (string-width (popup-x-to-string item)) into width
134
if (stringp summary)
135
maximize (+ (string-width summary) 2) into summary-width
136
finally return (* (ceiling (/ (+ (or width 0) (or summary-width 0)) 10.0)) 10)))
137
138
;; window-full-width-p is not defined in Emacs 22.1
139
(defun popup-window-full-width-p (&optional window)
140
(if (fboundp 'window-full-width-p)
141
(window-full-width-p window)
142
(= (window-width window) (frame-width (window-frame (or window (selected-window)))))))
143
144
;; truncated-partial-width-window-p is not defined in Emacs 22
145
(defun popup-truncated-partial-width-window-p (&optional window)
146
(unless window
147
(setq window (selected-window)))
148
(unless (popup-window-full-width-p window)
149
(let ((t-p-w-w (buffer-local-value 'truncate-partial-width-windows
150
(window-buffer window))))
151
(if (integerp t-p-w-w)
152
(< (window-width window) t-p-w-w)
153
t-p-w-w))))
154
155
(defun popup-current-physical-column ()
156
(or (when (and popup-use-optimized-column-computation
157
(eq (window-hscroll) 0))
158
(let ((current-column (current-column)))
159
(if (or (popup-truncated-partial-width-window-p)
160
truncate-lines
161
(< current-column (window-width)))
162
current-column)))
163
(car (posn-col-row (posn-at-point)))))
164
165
(defun popup-last-line-of-buffer-p ()
166
(save-excursion (end-of-line) (/= (forward-line) 0)))
167
168
(defun popup-lookup-key-by-event (function event)
169
(or (funcall function (vector event))
170
(if (symbolp event)
171
(popup-aif (get event 'event-symbol-element-mask)
172
(funcall function (vector (logior (or (get (car it) 'ascii-character) 0)
173
(cadr it))))))))
174
175
176
177
;; Popup common
178
179
(defgroup popup nil
180
"Visual popup interface"
181
:group 'lisp
182
:prefix "popup-")
183
184
(defface popup-face
185
'((t (:background "lightgray" :foreground "black")))
186
"Face for popup."
187
:group 'popup)
188
189
(defface popup-scroll-bar-foreground-face
190
'((t (:background "black")))
191
"Foreground face for scroll-bar."
192
:group 'popup)
193
194
(defface popup-scroll-bar-background-face
195
'((t (:background "gray")))
196
"Background face for scroll-bar."
197
:group 'popup)
198
199
(defvar popup-instances nil
200
"Popup instances.")
201
202
(defvar popup-scroll-bar-foreground-char
203
(propertize " " 'face 'popup-scroll-bar-foreground-face)
204
"Foreground character for scroll-bar.")
205
206
(defvar popup-scroll-bar-background-char
207
(propertize " " 'face 'popup-scroll-bar-background-face)
208
"Background character for scroll-bar.")
209
210
(defstruct popup
211
point row column width height min-height direction overlays
212
parent depth
213
face selection-face
214
margin-left margin-right margin-left-cancel scroll-bar symbol
215
cursor offset scroll-top current-height list newlines
216
pattern original-list)
217
218
(defun popup-item-propertize (item &rest properties)
219
"Same to `propertize` but this avoids overriding existed value with `nil` property."
220
(let (props)
221
(while properties
222
(when (cadr properties)
223
(push (car properties) props)
224
(push (cadr properties) props))
225
(setq properties (cddr properties)))
226
(apply 'propertize
227
(popup-x-to-string item)
228
(nreverse props))))
229
230
(defun popup-item-property (item property)
231
(if (stringp item)
232
(get-text-property 0 property item)))
233
234
(defun* popup-make-item (name
235
&key
236
value
237
popup-face
238
selection-face
239
sublist
240
document
241
symbol
242
summary)
243
"Utility function to make popup item.
244
See also `popup-item-propertize'."
245
(popup-item-propertize name
246
'value value
247
'popup-face popup-face
248
'selection-face selection-face
249
'document document
250
'symbol symbol
251
'summary summary
252
'sublist sublist))
253
254
(defsubst popup-item-value (item) (popup-item-property item 'value))
255
(defsubst popup-item-value-or-self (item) (or (popup-item-value item) item))
256
(defsubst popup-item-popup-face (item) (popup-item-property item 'popup-face))
257
(defsubst popup-item-selection-face (item) (popup-item-property item 'selection-face))
258
(defsubst popup-item-document (item) (popup-item-property item 'document))
259
(defsubst popup-item-summary (item) (popup-item-property item 'summary))
260
(defsubst popup-item-symbol (item) (popup-item-property item 'symbol))
261
(defsubst popup-item-sublist (item) (popup-item-property item 'sublist))
262
263
(defun popup-item-documentation (item)
264
(let ((doc (popup-item-document item)))
265
(if (functionp doc)
266
(setq doc (funcall doc (popup-item-value-or-self item))))
267
doc))
268
269
(defun popup-item-show-help-1 (item)
270
(let ((doc (popup-item-documentation item)))
271
(when doc
272
(with-current-buffer (get-buffer-create " *Popup Help*")
273
(erase-buffer)
274
(insert doc)
275
(goto-char (point-min))
276
(display-buffer (current-buffer)))
277
t)))
278
279
(defun popup-item-show-help (item &optional persist)
280
(when item
281
(if (not persist)
282
(save-window-excursion
283
(when (popup-item-show-help-1 item)
284
(block nil
285
(while t
286
(clear-this-command-keys)
287
(let ((key (read-key-sequence-vector nil)))
288
(case (key-binding key)
289
('scroll-other-window
290
(scroll-other-window))
291
('scroll-other-window-down
292
(scroll-other-window-down nil))
293
(t
294
(setq unread-command-events (append key unread-command-events))
295
(return))))))))
296
(popup-item-show-help-1 item))))
297
298
(defun popup-set-list (popup list)
299
(popup-set-filtered-list popup list)
300
(setf (popup-pattern popup) nil)
301
(setf (popup-original-list popup) list))
302
303
(defun popup-set-filtered-list (popup list)
304
(setf (popup-list popup) list
305
(popup-offset popup) (if (> (popup-direction popup) 0)
306
0
307
(max (- (popup-height popup) (length list)) 0))))
308
309
(defun popup-selected-item (popup)
310
(nth (popup-cursor popup) (popup-list popup)))
311
312
(defun popup-selected-line (popup)
313
(- (popup-cursor popup) (popup-scroll-top popup)))
314
315
(defun popup-line-overlay (popup line)
316
(aref (popup-overlays popup) line))
317
318
(defun popup-selected-line-overlay (popup)
319
(popup-line-overlay popup (popup-selected-line popup)))
320
321
(defun popup-hide-line (popup line)
322
(let ((overlay (popup-line-overlay popup line)))
323
(overlay-put overlay 'display nil)
324
(overlay-put overlay 'after-string nil)))
325
326
(defun popup-line-hidden-p (popup line)
327
(let ((overlay (popup-line-overlay popup line)))
328
(and (eq (overlay-get overlay 'display) nil)
329
(eq (overlay-get overlay 'after-string) nil))))
330
331
(defun popup-set-line-item (popup line item face margin-left margin-right scroll-bar-char symbol summary)
332
(let* ((overlay (popup-line-overlay popup line))
333
(content (popup-create-line-string popup (popup-x-to-string item) margin-left margin-right symbol summary))
334
(start 0)
335
(prefix (overlay-get overlay 'prefix))
336
(postfix (overlay-get overlay 'postfix))
337
end)
338
;; Overlap face properties
339
(if (get-text-property start 'face content)
340
(setq start (next-single-property-change start 'face content)))
341
(while (and start (setq end (next-single-property-change start 'face content)))
342
(put-text-property start end 'face face content)
343
(setq start (next-single-property-change end 'face content)))
344
(if start
345
(put-text-property start (length content) 'face face content))
346
(unless (overlay-get overlay 'dangle)
347
(overlay-put overlay 'display (concat prefix (substring content 0 1)))
348
(setq prefix nil
349
content (concat (substring content 1))))
350
(overlay-put overlay
351
'after-string
352
(concat prefix
353
content
354
scroll-bar-char
355
postfix))))
356
357
(defun popup-create-line-string (popup string margin-left margin-right symbol summary)
358
(let* ((popup-width (popup-width popup))
359
(summary-width (string-width summary))
360
(string (car (popup-substring-by-width string
361
(- popup-width
362
(if (> summary-width 0)
363
(+ summary-width 2)
364
0)))))
365
(string-width (string-width string)))
366
(concat margin-left
367
string
368
(make-string (max (- popup-width string-width summary-width) 0) ? )
369
summary
370
symbol
371
margin-right)))
372
373
(defun popup-live-p (popup)
374
(and popup (popup-overlays popup) t))
375
376
(defun popup-child-point (popup &optional offset)
377
(overlay-end (popup-line-overlay popup
378
(or offset
379
(popup-selected-line popup)))))
380
381
(defun* popup-create (point
382
width
383
height
384
&key
385
min-height
386
around
387
(face 'popup-face)
388
(selection-face face)
389
scroll-bar
390
margin-left
391
margin-right
392
symbol
393
parent
394
parent-offset)
395
(or margin-left (setq margin-left 0))
396
(or margin-right (setq margin-right 0))
397
(unless point
398
(setq point
399
(if parent (popup-child-point parent parent-offset) (point))))
400
401
(save-excursion
402
(goto-char point)
403
(let* ((row (line-number-at-pos))
404
(column (popup-current-physical-column))
405
(overlays (make-vector height nil))
406
(popup-width (+ width
407
(if scroll-bar 1 0)
408
margin-left
409
margin-right
410
(if symbol 2 0)))
411
margin-left-cancel
412
(window (selected-window))
413
(window-start (window-start))
414
(window-hscroll (window-hscroll))
415
(window-width (window-width))
416
(right (+ column popup-width))
417
(overflow (and (> right window-width)
418
(>= right popup-width)))
419
(foldable (and (null parent)
420
(>= column popup-width)))
421
(direction (or
422
;; Currently the direction of cascade popup won't be changed
423
(and parent (popup-direction parent))
424
425
;; Calculate direction
426
(if (and (> row height)
427
(> height (- (max 1 (- (window-height)
428
(if mode-line-format 1 0)
429
(if header-line-format 1 0)))
430
(count-lines window-start (point)))))
431
-1
432
1)))
433
(depth (if parent (1+ (popup-depth parent)) 0))
434
(newlines (max 0 (+ (- height (count-lines point (point-max))) (if around 1 0))))
435
current-column)
436
(when (> newlines 0)
437
(popup-save-buffer-state
438
(goto-char (point-max))
439
(insert (make-string newlines ?\n))))
440
441
(if overflow
442
(if foldable
443
(progn
444
(decf column (- popup-width margin-left margin-right))
445
(unless around (move-to-column column)))
446
(when (not truncate-lines)
447
;; Cut out overflow
448
(let ((d (1+ (- popup-width (- window-width column)))))
449
(decf popup-width d)
450
(decf width d)))
451
(decf column margin-left))
452
(decf column margin-left))
453
(when (and (null parent)
454
(< column 0))
455
;; Cancel margin left
456
(setq column 0)
457
(decf popup-width margin-left)
458
(setq margin-left-cancel t))
459
460
(dotimes (i height)
461
(let (overlay begin w (dangle t) (prefix "") (postfix ""))
462
(when around
463
(if (>= emacs-major-version 23)
464
(vertical-motion (cons column direction))
465
(vertical-motion direction)
466
(move-to-column (+ (current-column) column))))
467
(setq around t
468
current-column (popup-current-physical-column))
469
470
(when (> current-column column)
471
(backward-char)
472
(setq current-column (popup-current-physical-column)))
473
(when (< current-column column)
474
;; Extend short buffer lines by popup prefix (line of spaces)
475
(setq prefix (make-string (+ (if (= current-column 0)
476
(- window-hscroll (current-column))
477
0)
478
(- column current-column))
479
? )))
480
481
(setq begin (point))
482
(setq w (+ popup-width (length prefix)))
483
(while (and (not (eolp)) (> w 0))
484
(setq dangle nil)
485
(decf w (char-width (char-after)))
486
(forward-char))
487
(if (< w 0)
488
(setq postfix (make-string (- w) ? )))
489
490
(setq overlay (make-overlay begin (point)))
491
(overlay-put overlay 'window window)
492
(overlay-put overlay 'dangle dangle)
493
(overlay-put overlay 'prefix prefix)
494
(overlay-put overlay 'postfix postfix)
495
(overlay-put overlay 'width width)
496
(aset overlays
497
(if (> direction 0) i (- height i 1))
498
overlay)))
499
(loop for p from (- 10000 (* depth 1000))
500
for overlay in (nreverse (append overlays nil))
501
do (overlay-put overlay 'priority p))
502
(let ((it (make-popup :point point
503
:row row
504
:column column
505
:width width
506
:height height
507
:min-height min-height
508
:direction direction
509
:parent parent
510
:depth depth
511
:face face
512
:selection-face selection-face
513
:margin-left margin-left
514
:margin-right margin-right
515
:margin-left-cancel margin-left-cancel
516
:scroll-bar scroll-bar
517
:symbol symbol
518
:cursor 0
519
:scroll-top 0
520
:current-height 0
521
:list nil
522
:newlines newlines
523
:overlays overlays)))
524
(push it popup-instances)
525
it))))
526
527
(defun popup-delete (popup)
528
(when (popup-live-p popup)
529
(popup-hide popup)
530
(mapc 'delete-overlay (popup-overlays popup))
531
(setf (popup-overlays popup) nil)
532
(setq popup-instances (delq popup popup-instances))
533
(let ((newlines (popup-newlines popup)))
534
(when (> newlines 0)
535
(popup-save-buffer-state
536
(goto-char (point-max))
537
(dotimes (i newlines)
538
(if (= (char-before) ?\n)
539
(delete-char -1)))))))
540
nil)
541
542
(defun popup-draw (popup)
543
(loop with height = (popup-height popup)
544
with min-height = (popup-min-height popup)
545
with popup-face = (popup-face popup)
546
with selection-face = (popup-selection-face popup)
547
with list = (popup-list popup)
548
with length = (length list)
549
with thum-size = (max (/ (* height height) (max length 1)) 1)
550
with page-size = (/ (+ 0.0 (max length 1)) height)
551
with scroll-bar = (popup-scroll-bar popup)
552
with margin-left = (make-string (if (popup-margin-left-cancel popup) 0 (popup-margin-left popup)) ? )
553
with margin-right = (make-string (popup-margin-right popup) ? )
554
with symbol = (popup-symbol popup)
555
with cursor = (popup-cursor popup)
556
with scroll-top = (popup-scroll-top popup)
557
with offset = (popup-offset popup)
558
for o from offset
559
for i from scroll-top
560
while (< o height)
561
for item in (nthcdr scroll-top list)
562
for page-index = (* thum-size (/ o thum-size))
563
for face = (if (= i cursor)
564
(or (popup-item-selection-face item) selection-face)
565
(or (popup-item-popup-face item) popup-face))
566
for empty-char = (propertize " " 'face face)
567
for scroll-bar-char = (if scroll-bar
568
(cond
569
((<= page-size 1)
570
empty-char)
571
((and (> page-size 1)
572
(>= cursor (* page-index page-size))
573
(< cursor (* (+ page-index thum-size) page-size)))
574
popup-scroll-bar-foreground-char)
575
(t
576
popup-scroll-bar-background-char))
577
"")
578
for sym = (if symbol
579
(concat " " (or (popup-item-symbol item) " "))
580
"")
581
for summary = (or (popup-item-summary item) "")
582
583
do
584
;; Show line and set item to the line
585
(popup-set-line-item popup o item face margin-left margin-right scroll-bar-char sym summary)
586
587
finally
588
;; Remember current height
589
(setf (popup-current-height popup) (- o offset))
590
591
;; Hide remaining lines
592
(let ((scroll-bar-char (if scroll-bar (propertize " " 'face popup-face) ""))
593
(symbol (if symbol " " "")))
594
(if (> (popup-direction popup) 0)
595
(progn
596
(when min-height
597
(while (< o min-height)
598
(popup-set-line-item popup o "" popup-face margin-left margin-right scroll-bar-char symbol "")
599
(incf o)))
600
(while (< o height)
601
(popup-hide-line popup o)
602
(incf o)))
603
(loop with h = (if min-height (- height min-height) offset)
604
for o from 0 below offset
605
if (< o h)
606
do (popup-hide-line popup o)
607
if (>= o h)
608
do (popup-set-line-item popup o "" popup-face margin-left margin-right scroll-bar-char symbol ""))))))
609
610
(defun popup-hide (popup)
611
(dotimes (i (popup-height popup))
612
(popup-hide-line popup i)))
613
614
(defun popup-hidden-p (popup)
615
(let ((hidden t))
616
(when (popup-live-p popup)
617
(dotimes (i (popup-height popup))
618
(unless (popup-line-hidden-p popup i)
619
(setq hidden nil))))
620
hidden))
621
622
(defun popup-select (popup i)
623
(setq i (+ i (popup-offset popup)))
624
(when (and (<= 0 i) (< i (popup-height popup)))
625
(setf (popup-cursor popup) i)
626
(popup-draw popup)
627
t))
628
629
(defun popup-next (popup)
630
(let ((height (popup-height popup))
631
(cursor (1+ (popup-cursor popup)))
632
(scroll-top (popup-scroll-top popup))
633
(length (length (popup-list popup))))
634
(cond
635
((>= cursor length)
636
;; Back to first page
637
(setq cursor 0
638
scroll-top 0))
639
((= cursor (+ scroll-top height))
640
;; Go to next page
641
(setq scroll-top (min (1+ scroll-top) (max (- length height) 0)))))
642
(setf (popup-cursor popup) cursor
643
(popup-scroll-top popup) scroll-top)
644
(popup-draw popup)))
645
646
(defun popup-previous (popup)
647
(let ((height (popup-height popup))
648
(cursor (1- (popup-cursor popup)))
649
(scroll-top (popup-scroll-top popup))
650
(length (length (popup-list popup))))
651
(cond
652
((< cursor 0)
653
;; Go to last page
654
(setq cursor (1- length)
655
scroll-top (max (- length height) 0)))
656
((= cursor (1- scroll-top))
657
;; Go to previous page
658
(decf scroll-top)))
659
(setf (popup-cursor popup) cursor
660
(popup-scroll-top popup) scroll-top)
661
(popup-draw popup)))
662
663
(defun popup-scroll-down (popup &optional n)
664
(let ((scroll-top (min (+ (popup-scroll-top popup) (or n 1))
665
(- (length (popup-list popup)) (popup-height popup)))))
666
(setf (popup-cursor popup) scroll-top
667
(popup-scroll-top popup) scroll-top)
668
(popup-draw popup)))
669
670
(defun popup-scroll-up (popup &optional n)
671
(let ((scroll-top (max (- (popup-scroll-top popup) (or n 1))
672
0)))
673
(setf (popup-cursor popup) scroll-top
674
(popup-scroll-top popup) scroll-top)
675
(popup-draw popup)))
676
677
678
679
;; Popup isearch
680
681
(defface popup-isearch-match
682
'((t (:background "sky blue")))
683
"Popup isearch match face."
684
:group 'popup)
685
686
(defvar popup-isearch-cursor-color "blue")
687
688
(defvar popup-isearch-keymap
689
(let ((map (make-sparse-keymap)))
690
;(define-key map "\r" 'popup-isearch-done)
691
(define-key map "\C-g" 'popup-isearch-cancel)
692
(define-key map "\C-h" 'popup-isearch-delete)
693
(define-key map (kbd "DEL") 'popup-isearch-delete)
694
map))
695
696
(defsubst popup-isearch-char-p (char)
697
(and (integerp char)
698
(<= 32 char)
699
(<= char 126)))
700
701
(defun popup-isearch-filter-list (pattern list)
702
(loop with regexp = (regexp-quote pattern)
703
for item in list
704
do
705
(unless (stringp item)
706
(setq item (popup-item-propertize (popup-x-to-string item)
707
'value item)))
708
if (string-match regexp item)
709
collect (let ((beg (match-beginning 0))
710
(end (match-end 0)))
711
(alter-text-property 0 (length item) 'face
712
(lambda (prop)
713
(unless (eq prop 'popup-isearch-match)
714
prop))
715
item)
716
(put-text-property beg end
717
'face 'popup-isearch-match
718
item)
719
item)))
720
721
(defun popup-isearch-prompt (popup pattern)
722
(format "Pattern: %s" (if (= (length (popup-list popup)) 0)
723
(propertize pattern 'face 'isearch-fail)
724
pattern)))
725
726
(defun popup-isearch-update (popup pattern &optional callback)
727
(setf (popup-cursor popup) 0
728
(popup-scroll-top popup) 0
729
(popup-pattern popup) pattern)
730
(let ((list (popup-isearch-filter-list pattern (popup-original-list popup))))
731
(popup-set-filtered-list popup list)
732
(if callback
733
(funcall callback list)))
734
(popup-draw popup))
735
736
(defun* popup-isearch (popup
737
&key
738
(cursor-color popup-isearch-cursor-color)
739
(keymap popup-isearch-keymap)
740
callback
741
help-delay)
742
(let ((list (popup-original-list popup))
743
(pattern (or (popup-pattern popup) ""))
744
(old-cursor-color (frame-parameter (selected-frame) 'cursor-color))
745
prompt key binding done)
746
(unwind-protect
747
(unless (block nil
748
(if cursor-color
749
(set-cursor-color cursor-color))
750
(while t
751
(setq prompt (popup-isearch-prompt popup pattern))
752
(setq key (popup-menu-read-key-sequence keymap prompt help-delay))
753
(if (null key)
754
(unless (funcall popup-menu-show-quick-help-function popup nil :prompt prompt)
755
(clear-this-command-keys)
756
(push (read-event prompt) unread-command-events))
757
(setq binding (lookup-key keymap key))
758
(cond
759
((and (stringp key)
760
(popup-isearch-char-p (aref key 0)))
761
(setq pattern (concat pattern key)))
762
((eq binding 'popup-isearch-done)
763
(return t))
764
((eq binding 'popup-isearch-cancel)
765
(return nil))
766
((eq binding 'popup-isearch-delete)
767
(if (> (length pattern) 0)
768
(setq pattern (substring pattern 0 (1- (length pattern))))))
769
(t
770
(setq unread-command-events
771
(append (listify-key-sequence key) unread-command-events))
772
(return t)))
773
(popup-isearch-update popup pattern callback))))
774
(popup-isearch-update popup "" callback)
775
t) ; Return non-nil if isearch is cancelled
776
(if old-cursor-color
777
(set-cursor-color old-cursor-color)))))
778
779
780
781
;; Popup tip
782
783
(defface popup-tip-face
784
'((t (:background "khaki1" :foreground "black")))
785
"Face for popup tip."
786
:group 'popup)
787
788
(defvar popup-tip-max-width 80)
789
790
(defun* popup-tip (string
791
&key
792
point
793
(around t)
794
width
795
(height 15)
796
min-height
797
truncate
798
margin
799
margin-left
800
margin-right
801
scroll-bar
802
parent
803
parent-offset
804
nowait
805
prompt
806
&aux tip lines)
807
(if (bufferp string)
808
(setq string (with-current-buffer string (buffer-string))))
809
;; TODO strip text (mainly face) properties
810
(setq string (substring-no-properties string))
811
812
(and (eq margin t) (setq margin 1))
813
(or margin-left (setq margin-left margin))
814
(or margin-right (setq margin-right margin))
815
816
(let ((it (popup-fill-string string width popup-tip-max-width)))
817
(setq width (car it)
818
lines (cdr it)))
819
820
(setq tip (popup-create point width height
821
:min-height min-height
822
:around around
823
:margin-left margin-left
824
:margin-right margin-right
825
:scroll-bar scroll-bar
826
:face 'popup-tip-face
827
:parent parent
828
:parent-offset parent-offset))
829
830
(unwind-protect
831
(when (> (popup-width tip) 0) ; not to be corrupted
832
(when (and (not (eq width (popup-width tip))) ; truncated
833
(not truncate))
834
;; Refill once again to lines be fitted to popup width
835
(setq width (popup-width tip))
836
(setq lines (cdr (popup-fill-string string width width))))
837
838
(popup-set-list tip lines)
839
(popup-draw tip)
840
(if nowait
841
tip
842
(clear-this-command-keys)
843
(push (read-event prompt) unread-command-events)
844
t))
845
(unless nowait
846
(popup-delete tip))))
847
848
849
850
;; Popup menu
851
852
(defface popup-menu-face
853
'((t (:background "lightgray" :foreground "black")))
854
"Face for popup menu."
855
:group 'popup)
856
857
(defface popup-menu-selection-face
858
'((t (:background "steelblue" :foreground "white")))
859
"Face for popup menu selection."
860
:group 'popup)
861
862
(defvar popup-menu-show-tip-function 'popup-tip
863
"Function used for showing tooltip by `popup-menu-show-quick-help'.")
864
865
(defvar popup-menu-show-quick-help-function 'popup-menu-show-quick-help
866
"Function used for showing quick help by `popup-menu*'.")
867
868
(defun popup-menu-show-help (menu &optional persist item)
869
(popup-item-show-help (or item (popup-selected-item menu)) persist))
870
871
(defun popup-menu-documentation (menu &optional item)
872
(popup-item-documentation (or item (popup-selected-item menu))))
873
874
(defun popup-menu-show-quick-help (menu &optional item &rest args)
875
(let* ((point (plist-get args :point))
876
(height (or (plist-get args :height) (popup-height menu)))
877
(min-height (min height (popup-current-height menu)))
878
(around nil)
879
(parent-offset (popup-offset menu))
880
(doc (popup-menu-documentation menu item)))
881
(when (stringp doc)
882
(if (popup-hidden-p menu)
883
(setq around t
884
menu nil
885
parent-offset nil)
886
(setq point nil))
887
(let ((popup-use-optimized-column-computation nil)) ; To avoid wrong positioning
888
(apply popup-menu-show-tip-function
889
doc
890
:point point
891
:height height
892
:min-height min-height
893
:around around
894
:parent menu
895
:parent-offset parent-offset
896
args)))))
897
898
(defun popup-menu-read-key-sequence (keymap &optional prompt timeout)
899
(catch 'timeout
900
(let ((timer (and timeout
901
(run-with-timer timeout nil
902
(lambda ()
903
(if (zerop (length (this-command-keys)))
904
(throw 'timeout nil))))))
905
(old-global-map (current-global-map))
906
(temp-global-map (make-sparse-keymap))
907
(overriding-terminal-local-map (make-sparse-keymap)))
908
(substitute-key-definition 'keyboard-quit 'keyboard-quit
909
temp-global-map old-global-map)
910
(define-key temp-global-map [menu-bar] (lookup-key old-global-map [menu-bar]))
911
(define-key temp-global-map [tool-bar] (lookup-key old-global-map [tool-bar]))
912
(set-keymap-parent overriding-terminal-local-map keymap)
913
(if (current-local-map)
914
(define-key overriding-terminal-local-map [menu-bar]
915
(lookup-key (current-local-map) [menu-bar])))
916
(unwind-protect
917
(progn
918
(use-global-map temp-global-map)
919
(clear-this-command-keys)
920
(with-temp-message prompt
921
(read-key-sequence nil)))
922
(use-global-map old-global-map)
923
(if timer (cancel-timer timer))))))
924
925
(defun popup-menu-fallback (event default))
926
927
(defun* popup-menu-event-loop (menu keymap fallback &optional prompt help-delay isearch isearch-cursor-color isearch-keymap isearch-callback &aux key binding)
928
(block nil
929
(while (popup-live-p menu)
930
(and isearch
931
(popup-isearch menu
932
:cursor-color isearch-cursor-color
933
:keymap isearch-keymap
934
:callback isearch-callback
935
:help-delay help-delay)
936
(keyboard-quit))
937
(setq key (popup-menu-read-key-sequence keymap prompt help-delay))
938
(if (null key)
939
(unless (funcall popup-menu-show-quick-help-function menu nil :prompt prompt)
940
(clear-this-command-keys)
941
(push (read-event prompt) unread-command-events))
942
(if (eq (lookup-key (current-global-map) key) 'keyboard-quit)
943
(keyboard-quit))
944
(setq binding (lookup-key keymap key))
945
(cond
946
((eq binding 'popup-close)
947
(if (popup-parent menu)
948
(return)))
949
((memq binding '(popup-select popup-open))
950
(let* ((item (popup-selected-item menu))
951
(sublist (popup-item-sublist item)))
952
(if sublist
953
(popup-aif (popup-cascade-menu sublist
954
:around nil
955
:parent menu
956
:margin-left (popup-margin-left menu)
957
:margin-right (popup-margin-right menu)
958
:scroll-bar (popup-scroll-bar menu))
959
(and it (return it)))
960
(if (eq binding 'popup-select)
961
(return (popup-item-value-or-self item))))))
962
((eq binding 'popup-next)
963
(popup-next menu))
964
((eq binding 'popup-previous)
965
(popup-previous menu))
966
((eq binding 'popup-help)
967
(popup-menu-show-help menu))
968
((eq binding 'popup-isearch)
969
(popup-isearch menu
970
:cursor-color isearch-cursor-color
971
:keymap isearch-keymap
972
:callback isearch-callback
973
:help-delay help-delay))
974
((commandp binding)
975
(call-interactively binding))
976
(t
977
(funcall fallback key (key-binding key))))))))
978
979
;; popup-menu is used by mouse.el unfairly...
980
(defun* popup-menu* (list
981
&key
982
point
983
(around t)
984
(width (popup-preferred-width list))
985
(height 15)
986
margin
987
margin-left
988
margin-right
989
scroll-bar
990
symbol
991
parent
992
parent-offset
993
(keymap popup-menu-keymap)
994
(fallback 'popup-menu-fallback)
995
help-delay
996
prompt
997
isearch
998
(isearch-cursor-color popup-isearch-cursor-color)
999
(isearch-keymap popup-isearch-keymap)
1000
isearch-callback
1001
&aux menu event)
1002
(and (eq margin t) (setq margin 1))
1003
(or margin-left (setq margin-left margin))
1004
(or margin-right (setq margin-right margin))
1005
(if (and scroll-bar
1006
(integerp margin-right)
1007
(> margin-right 0))
1008
;; Make scroll-bar space as margin-right
1009
(decf margin-right))
1010
(setq menu (popup-create point width height
1011
:around around
1012
:face 'popup-menu-face
1013
:selection-face 'popup-menu-selection-face
1014
:margin-left margin-left
1015
:margin-right margin-right
1016
:scroll-bar scroll-bar
1017
:symbol symbol
1018
:parent parent))
1019
(unwind-protect
1020
(progn
1021
(popup-set-list menu list)
1022
(popup-draw menu)
1023
(popup-menu-event-loop menu keymap fallback prompt help-delay isearch
1024
isearch-cursor-color isearch-keymap isearch-callback))
1025
(popup-delete menu)))
1026
1027
(defun popup-cascade-menu (list &rest args)
1028
"Same to `popup-menu', but an element of `LIST' can be
1029
list of submenu."
1030
(apply 'popup-menu*
1031
(mapcar (lambda (item)
1032
(if (consp item)
1033
(popup-make-item (car item)
1034
:sublist (cdr item)
1035
:symbol ">")
1036
item))
1037
list)
1038
:symbol t
1039
args))
1040
1041
(defvar popup-menu-keymap
1042
(let ((map (make-sparse-keymap)))
1043
(define-key map "\r" 'popup-select)
1044
(define-key map "\C-f" 'popup-open)
1045
(define-key map [right] 'popup-open)
1046
(define-key map "\C-b" 'popup-close)
1047
(define-key map [left] 'popup-close)
1048
1049
(define-key map "\C-n" 'popup-next)
1050
(define-key map [down] 'popup-next)
1051
(define-key map "\C-p" 'popup-previous)
1052
(define-key map [up] 'popup-previous)
1053
1054
(define-key map [f1] 'popup-help)
1055
(define-key map (kbd "\C-?") 'popup-help)
1056
1057
(define-key map "\C-s" 'popup-isearch)
1058
map))
1059
1060
(provide 'popup)
1061
;;; popup.el ends here
1062
1063