summaryrefslogtreecommitdiffstats
path: root/emacs.d/nxhtml/util/appmenu.el
blob: 1f060efcde8564c7e5fb8f96672066698c134d92 (plain)
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
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
;;; appmenu.el --- A framework for [apps] popup menus.

;; Copyright (C) 2008 by Lennart Borgman

;; Author:  Lennart Borgman <lennart DOT borgman AT gmail DOT com>
;; Created: Thu Jan 05 14:00:26 2006
(defconst appmenu:version "0.63") ;; Version:
;; Last-Updated: 2010-01-04 Mon
;; Keywords:
;; Compatibility:
;;
;; Features that might be required by this library:
;;
;;   None
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;;; Commentary:
;;
;;  appmenu.el is a framework for creating cooperative context
;;  sensitive popup menus with commands from different major and minor
;;  modes.  For more information see `appmenu-mode'.
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;;; Change log:
;;
;; Version 0.61:
;; - Remove support for minor and major menus.
;; - Add support for text and overlay keymaps.
;; - Add customization options.
;;
;; Version 0.62:
;; - Fix problem with keymap at point.
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 2, 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; see the file COPYING.  If not, write to the
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
;; Boston, MA 02111-1307, USA.
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;;; Code:

(eval-when-compile (require 'cl))
(eval-when-compile (require 'flyspell))
(eval-when-compile (require 'help-mode))
(eval-when-compile (require 'ourcomments-util nil t))
(eval-when-compile (require 'mumamo nil t))
;;(eval-when-compile (require 'mlinks nil t))

;;;###autoload
(defgroup appmenu nil
  "Customization group for `appmenu-mode'."
  :group 'convenience)

(defcustom appmenu-show-help nil
  "Non-nil means show AppMenu help on AppMenu popup."
  :type 'boolean
  :group 'appmenu)

(defcustom appmenu-show-point-menu t
  "If non-nil show entries fetched from keymaps at point."
  :type 'boolean
  :group 'appmenu)

(defvar appmenu-alist nil
  "List of additional menu keymaps.
To change this list use `appmenu-add' and `appmenu-remove'.

The entries in this list are lists:

   \(ID PRIORITY TEST TITLE DEFINITION)

ID is a unique identity.

PRIORITY is a number or a variable whose value is a number
telling where to put this entry when showing the menu.

TEST should be a form to evaluate.  The entry is used if \(eval
TEST) returns non-nil.

DEFINITION should be either a keymap or a function that returns a
keymap.

The function must take no argument and return a keymap.  If the
function returns nil then the entry is not shown in the popup
menu.  Using this you can make context sensitive popup menus.

For an example of use see mlinks.el.")

(defun appmenu-sort-by-priority ()
  "Sort `appmenu-alist' entries by priority."
  (setq appmenu-alist
        (sort appmenu-alist
              (lambda (recA recB)
                (let ((priA (nth 1 recA))
                      (priB (nth 1 recB)))
                  (when (symbolp priA) (setq priA (symbol-value priA)))
                  (when (symbolp priB) (setq priB (symbol-value priB)))
                  (< priA priB))))))

;;;###autoload
(defun appmenu-add (id priority test title definition)
  "Add entry to `appmenu-alist'.
Add an entry to this list with ID, PRIORITY, TEST, TITLE and
DEFINITION as explained there."
  (assert (symbolp id))
  (unless priority (setq priority 100))
  (assert (numberp priority))
  (assert (stringp title))
  (let ((rec (list id priority test title definition)))
    (appmenu-remove id)
    (add-to-list 'appmenu-alist rec)))

(defun appmenu-remove (id)
  "Remove entry with id ID from `appmenu-alist'."
  (setq appmenu-alist (assq-delete-all id appmenu-alist)))

(defun appmenu-help ()
  "Show help for minor mode function `appmenu-mode'."
  (interactive)
  (describe-function 'appmenu-mode))

(defun appmenu-keymap-len (map)
  "Return length of keymap MAP."
  (let ((ml 0))
    (map-keymap (lambda (e f) (setq ml (1+ ml))) map)
    ml))

(defvar appmenu-mouse-only
  '((flyspell-correct-word appmenu-flyspell-correct-word-before-point)))

(defun appmenu-flyspell-correct-word-before-point ()
  "Pop up a menu of possible corrections for misspelled word before point.
Special version for AppMenu."
  (interactive)
  (flyspell-correct-word-before-point))

(defcustom appmenu-at-any-point '(ispell-word)
  "Commands that may work at any point in a buffer.
Some important but not too often used commands that may be useful
for most points in a buffer."
  :group 'appmenu)

(defvar appmenu-map-fun) ;; dyn var, silence compiler

(defun appmenu-make-menu-for-point (this-point)
  "Construct a menu based on point THIS-POINT.
This includes some known commands for point and keymap at
point."
  (let ((point-map (get-char-property this-point 'keymap))
        (funs appmenu-at-any-point)
        (map (make-sparse-keymap "At point"))
        (num 0)
        last-prefix
        this-prefix)
    ;; Known for any point
    (when point-map
      (let ((appmenu-map-fun
             (lambda (key fun)
               (if (keymapp fun)
                   (map-keymap appmenu-map-fun fun)
                 (when (and (symbolp fun)
                            (fboundp fun))
                   (let ((mouse-only (assq fun appmenu-mouse-only)))
                     (when mouse-only
                       (setq fun (cadr mouse-only)))
                     (add-to-list 'funs fun)))))))
        (map-keymap appmenu-map-fun point-map)))
    (dolist (fun funs)
      (let ((desc (when fun (documentation fun))))
        (when desc
          (setq desc (car (split-string desc "[\n]")))
          ;;(lwarn t :warning "pk: %s, %s" fun desc)
          (setq this-prefix
                (car (split-string (symbol-name fun) "[-]")))
          (when (and last-prefix
                     (not (string= last-prefix this-prefix)))
            (define-key map
              (vector (intern (format "appmenu-point-div-%s" num)))
              (list 'menu-item "--")))
          (setq last-prefix this-prefix)
          (setq num (1+ num))
          (define-key map
            (vector (intern (format "appmenu-point-%s" num)))
            (list 'menu-item desc fun)))))
    (when (> num 0) map)))

(defvar appmenu-level) ;; dyn var
(defvar appmenu-funs) ;; dyn var
(defvar appmenu-events) ;; dyn var
(defvar appmenu-this-point) ;; dyn var

(defun appmenu-keymap-map-fun (ev def)
  (if (keymapp def)
        (progn
          (add-to-list 'appmenu-funs (list appmenu-level ev))
          (setq appmenu-events (cons ev appmenu-events))
          (setq appmenu-level (1+ appmenu-level))

          (map-keymap 'appmenu-keymap-map-fun def)

          (setq appmenu-events (cdr appmenu-events))
          (setq appmenu-level (1- appmenu-level)))
      (when (and (symbolp def)
                 (fboundp def))
        (let* ((mouse-only (assq def appmenu-mouse-only))
               (fun (if mouse-only (cadr mouse-only) def))
               (doc (when fun
                      (if (not (eq fun 'push-button))
                          (documentation fun)
                        (concat
                         "Button: "
                         (with-current-buffer (marker-buffer appmenu-this-point)
                           (or (get-char-property appmenu-this-point 'help-echo)
                               (let ((action-fun (get-char-property appmenu-this-point 'action)))
                                 (if action-fun
                                     (documentation action-fun)
                                   "No action, ignored"))
                               "No documentation available")))))))
          (add-to-list 'appmenu-funs (list appmenu-level (cons ev appmenu-events) def doc))))))

;;(appmenu-as-help (point))
(defun appmenu-as-help (this-point)
  "Show keybindings specific done current point in buffer.
This shows the binding in the help buffer.

Tip: This may be helpful if you are using `css-color-mode'."
  (interactive (list (copy-marker (point))))
  ;; Split this for debugging
  (let ((menu-here
         (with-current-buffer (or (and (markerp this-point)
                                       (marker-buffer this-point))
                                  (current-buffer))
           (unless (markerp this-point) (setq this-point (copy-marker this-point)))
           (get-char-property this-point 'keymap))))
    ;;(describe-variable 'menu-here)
    (appmenu-as-help-1 menu-here this-point)))

(defun appmenu-as-help-1 (menu-here this-point)
  (let ((appmenu-level 0)
        (appmenu-funs nil)
        (appmenu-events nil)
        (appmenu-this-point this-point))
    (when menu-here
      (map-keymap 'appmenu-keymap-map-fun menu-here))
    ;;(describe-variable 'appmenu-funs)
    ;; Fix-me: collect info first in case we are in help-buffer!
    (with-output-to-temp-buffer (help-buffer)
      (help-setup-xref (list #'appmenu-as-help this-point) (interactive-p))
      (with-current-buffer (help-buffer)
        (let ((fmt " %s%15s     %-30s\n"))
          (insert (propertize
                   ;;"AppMenu: Keys found at point in buffer\n\n"
                   (format "Appmenu: Key bindings specific to point %s in buffer %S\n\n"
                           (+ 0 this-point)
                           (when (markerp this-point)
                             (buffer-name (marker-buffer this-point))))
                   'face 'font-lock-comment-face))
          (if (not menu-here)
              (insert "\n\nThere are no point specific key bindings there now.")
            (insert (propertize (format fmt "" "Key" "Function") 'face 'font-lock-function-name-face))
            (insert (propertize (format fmt "" "---" "--------") 'face 'font-lock-function-name-face))
            (dolist (rec appmenu-funs)
              (let* ((lev (nth 0 rec))
                     (ev  (nth 1 rec))
                     (fun (nth 2 rec))
                     (doc (nth 3 rec))
                     (d1  (when doc (car (split-string doc "[\n]")))))
                (if fun
                    (insert (format fmt
                                    "" ;;(concat "*" (make-string (* 4 lev) ?\ ))
                                    (key-description (reverse ev))
                                    d1)
                            (if nil (format "(%s)" fun) ""))
                  ;;(insert (format "something else=%S\n" rec))
                  )))))))))


(defun appmenu-map ()
  "Return menu keymap to use for popup menu."
  (let* ((map (make-sparse-keymap
               "AppMenu"
               ))
         (map-len (appmenu-keymap-len map))
         (map-init-len map-len)
         (num-minor 0)
         (id 0)
         (point-menu (when appmenu-show-point-menu
                       (appmenu-make-menu-for-point (point)))))
    ;; AppMenu itself
    (when appmenu-show-help
      (define-key map [appmenu-customize]
        (list 'menu-item "Customize AppMenu"
              (lambda () (interactive) (customize-group 'appmenu))
              :help "Customize AppMenu"
              :visible 'appmenu-show-help))
      (define-key map [appmenu-help]
        (list 'menu-item "Help for AppMenu" 'appmenu-help
              :help "Help for how to use AppMenu"
              :visible 'appmenu-show-help))
      (define-key map [appmenu-separator-1]
        (list 'menu-item "--")))
    (setq map-len (appmenu-keymap-len map))
    (appmenu-sort-by-priority)
    (dolist (rec appmenu-alist)
      (let* ((test   (nth 2 rec))
             (title  (nth 3 rec))
             (mapdef (nth 4 rec))
             (usedef (if (symbolp mapdef)
                         (funcall mapdef)
                       mapdef)))
        (when (and usedef
                   (eval test))
          (setq id (1+ id))
          (define-key map
            (vector (intern (format "appmenu-%s" id)))
            (list 'menu-item title usedef)))
        ))
    (when point-menu
      (setq map-len (appmenu-keymap-len map))
      (when (> map-len map-init-len)
        (define-key map [appmenu-at-point-div]
          (list 'menu-item "--")))
      (define-key map [appmenu-at-point]
        (list 'menu-item "Bound To Point"
              point-menu)))
    (setq map-len (appmenu-keymap-len map))
    (when (> map-len map-init-len)
      map)))

;; (defun appmenu-get-submenu (menu-command)
;;   (let (subtitle submenumap)
;;     (if (eq 'menu-item (car menu-command))
;;         (progn (setq subtitle   (cadr  menu-command))
;;                (setq submenumap (caddr menu-command)))
;;       (setq subtitle   (car menu-command))
;;       (setq submenumap (cdr menu-command)))
;;     (unless (keymapp submenumap) (error "Submenu not a keymap=%s" submenumap))
;;     (cons subtitle submenumap)))

(defun appmenu-popup ()
  "Pops up the AppMenu menu."
  (interactive)
  (let* ((mod (event-modifiers last-input-event))
         (is-mouse (or (memq 'click mod)
                       (memq 'down  mod)
                       (memq 'drag  mod))))
    (when is-mouse
      (goto-char (posn-point (event-start last-input-event)))
      (sit-for 0.01))
    (let ((menu (appmenu-map)))
      (if menu
          (popup-menu-at-point menu)
        (message "Appmenu is empty")))))

(defvar appmenu-mode-map
  (let ((map (make-sparse-keymap)))
    (define-key map [apps]         'appmenu-popup)
    (define-key map [mouse-3]      'appmenu-popup)
    (define-key map [(control apps)] 'appmenu-as-help)
    map))


;;(setq appmenu-auto-help 4)
(defcustom appmenu-auto-help 2
  "Automatically show help on keymap at current point.
This shows up after the number of seconds in this variable.
If it it nil this feature is off.

This feature is only on in `appmenu-mode'."
  :type '(choice (number :tag "Number of seconds to wait")
                 (const :tag "Turned off" nil))
  :set (lambda (sym val)
         (set-default sym val)
         (if val
             (add-hook 'post-command-hook 'appmenu-auto-help-post-command nil t)
           (remove-hook 'post-command-hook 'appmenu-auto-help-post-command t)))
  :group 'appmenu)

(defcustom appmenu-auto-match-keymaps
  '(css-color)
  "Keymaps listed here can be avoided."
  :type '(set (const unknown)
              (const mlink)
              (const css-color))
  :group 'appmenu)

(defvar appmenu-auto-help-timer nil)

(defun appmenu-dump-keymap (km)
  (let ((fun (lambda (ev def)
               (message "ev=%S def=%S" ev def)
               (when (keymapp def)
                 (map-keymap fun def)))))
    (map-keymap fun km)))

(defun appmenu-on-keymap (where)
  (setq where (or where (point)))
  (let* ((rec (get-char-property-and-overlay where 'keymap))
         (kmp (car rec))
         (ovl (cdr rec)))
    (when kmp
      (or (memq 'unknown appmenu-auto-match-keymaps)
          (and (memq 'css-color appmenu-auto-match-keymaps)
               (get-text-property where 'css-color-type))
          (and (memq 'mlinks appmenu-auto-match-keymaps)
               (boundp 'mlinks-point-hilighter-overlay)
               (eq ovl mlinks-point-hilighter-overlay))
          ))))

(defsubst appmenu-auto-help-add-wcfg (at-point wcfg)
  (mumamo-with-buffer-prepared-for-jit-lock
   (add-text-properties at-point (1+ at-point)
                        (list 'point-left 'appmenu-auto-help-maybe-remove
                              'appmenu-auto-help-wcfg wcfg))))

(defsubst appmenu-auto-help-remove-wcfg (at-point)
  (mumamo-with-buffer-prepared-for-jit-lock
   (remove-list-of-text-properties at-point (1+ at-point)
                                   '(appmenu-auto-help-wcfg point-left))))

(defun appmenu-auto-help-maybe-remove (at-point new-point)
  "Run in 'point-left property.
Restores window configuration."
  (let ((old-wcfg (get-text-property at-point 'appmenu-auto-help-wcfg)))
    (appmenu-auto-help-remove-wcfg at-point)
    (if (appmenu-on-keymap new-point)
        (appmenu-auto-help-add-wcfg new-point old-wcfg)
      (if old-wcfg
          (set-window-configuration old-wcfg)
        (help-xref-go-back (help-buffer))))))

(defun appmenu-as-help-in-timer (win buf)
  (condition-case err
      (when (and (eq (selected-window) win)
                 (eq (current-buffer) buf)
                 appmenu-auto-help
                 (appmenu-on-keymap (point)))
        (let* ((old-help-win (get-buffer-window (help-buffer)))
               (wcfg (unless old-help-win
                      (current-window-configuration))))
          (unless old-help-win
            (display-buffer (help-buffer)))
          (appmenu-auto-help-add-wcfg (point) wcfg)
          (appmenu-as-help (copy-marker (point)))))
    (error (message "appmenu-as-help-in-timer: %s" (error-message-string err)))))

(defun appmenu-auto-help-cancel-timer ()
  (when (timerp appmenu-auto-help-timer)
    (cancel-timer appmenu-auto-help-timer))
  (setq appmenu-auto-help-timer nil))

(defun appmenu-auto-help-post-command ()
  (when (fboundp 'appmenu-as-help)
    (condition-case err
        (appmenu-auto-help-post-command-1)
      (error (message "css-color-post-command: %s" (error-message-string err))))))

;; #fff  #c9ff33
(defun appmenu-auto-help-post-command-1 ()
  (appmenu-auto-help-cancel-timer)
  (and appmenu-auto-help
       (appmenu-on-keymap (point))
       (not (get-text-property (point) 'appmenu-auto-help-wcfg))
       (setq appmenu-auto-help-timer
             (run-with-idle-timer appmenu-auto-help nil 'appmenu-as-help-in-timer
                                  (selected-window)
                                  (current-buffer)))))


;;;###autoload
(define-minor-mode appmenu-mode
  "Use a context sensitive popup menu.
AppMenu (appmenu.el) is a framework for creating cooperative
context sensitive popup menus with commands from different major
and minor modes. Using this different modes may cooperate about
the use of popup menus.

There is also the command `appmenu-as-help' that shows the key
bindings at current point in the help buffer.

The popup menu and the help buffer version are on these keys:

\\{appmenu-mode-map}

The variable `appmenu-alist' is where the popup menu entries
comes from.

If there is a `keymap' property at point then relevant bindings
from this is also shown in the popup menu.

You can write functions that use whatever information you want in
Emacs to construct these entries. Since this information is only
collected when the popup menu is shown you do not have to care as
much about computation time as for entries in the menu bar."
  :global t
  :keymap appmenu-mode-map
  :group 'appmenu
  (if appmenu-mode
      (add-hook 'post-command-hook 'appmenu-auto-help-post-command)
    (remove-hook 'post-command-hook 'appmenu-auto-help-post-command)))

(when (and appmenu-mode
           (not (boundp 'define-globa-minor-mode-bug)))
  (appmenu-mode 1))

(provide 'appmenu)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; appmenu.el ends here