1 ;;; tinyef.el --- (E)lectric (f)ile minor mode. Easy C-x C-f filename composing
3 ;; This file is not part of Emacs
7 ;; Copyright (C) 1995-2007 Jari Aalto
8 ;; Keywords: extensions
10 ;; Maintainer: Jari Aalto
12 ;; To get information on this program call M-x tinyef-version.
13 ;; Look at the code with folding.el
17 ;; This program is free software; you can redistribute it and/or modify it
18 ;; under the terms of the GNU General Public License as published by the Free
19 ;; Software Foundation; either version 2 of the License, or (at your option)
22 ;; This program is distributed in the hope that it will be useful, but
23 ;; WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
24 ;; or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
27 ;; You should have received a copy of the GNU General Public License
28 ;; along with program; see the file COPYING. If not, write to the
29 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
30 ;; Boston, MA 02110-1301, USA.
32 ;; Visit <http://www.gnu.org/copyleft/gpl.html> for more information
37 ;; ....................................................... &t-install ...
38 ;; Put this file on your emacs-lisp load path, add following into your
39 ;; ~/.emacs startup file
41 ;; (add-hook 'tinyef-load-hook 'tinyef-minibuffer-define-key-extras)
44 ;; Or use this autoload choice and your ~/.emacs will load quicker.
45 ;; This is the preferred method:
47 ;; (add-hook 'tinyef-load-hook 'tinyef-minibuffer-define-key-extras)
48 ;; (autoload 'turn-on-tinyef-mode "tinyef" "" t)
49 ;; (add-hook 'minibuffer-setup-hook 'turn-on-tinyef-mode)
51 ;; If you have any questions, use this function to contact maintainer
53 ;; M-x tinyef-submit-bug-report
59 ;; ..................................................... &t-commentary ...
65 ;; There was a post in gnu.emacs.sources where Anders Lindgren
66 ;; <andersl@csd.uu.se> presented the basic code that allowed electric
67 ;; ~ and electric / characters to wipe out full (mini)buffer in certain
68 ;; cases. What you see here, is complete rewrite and enchancement of
69 ;; that code. This is a real must for any minibuffer file handling.
71 ;; Overview of features
73 ;; o Easy filename editing. Deletes directories at time, delete line
74 ;; backward, electric tilde, electric slash, electric colon etc.
75 ;; o Useful to go along with `C-x' `C-f' command prompt.
76 ;; o Mouse-3 in minibuffers clears the input.
80 ;; This package is only slightly *electric*, in a sense that it only
81 ;; defines some keys to be electric and it needs some other keys
82 ;; solely to its own use (you can't insert these chars to buffer
83 ;; without `C-q' `CHAR'). The term electric refers to feature where a
84 ;; pressed character behaves differently if the pressing happens
85 ;; around certain other charcters (some condition is met which
86 ;; triggers this other behavior). Other than that, the character
87 ;; behaves normally. Below there is a sample graph to give you an
88 ;; overview of what the so called "electricity" is is practice. In
89 ;; these presented cases cursor it at the end of line. Jusr load this
90 ;; file, press `C-x' `C-f' and experiment with keys "<>|/~".
92 ;; o b>> means what's on the line *before*
93 ;; o a>> means what's there *after*
94 ;; o "" means what you just pressed
95 ;; o [] means which action the character triggered
99 ;; b>> ~/dir1/dir2/dir3/ "<" [step-delete-back]
101 ;; The action wiped previous directory name or until
102 ;; special mark, See code, defaults are ":/@" (ange-ftp things)
104 ;; b>> ~/dir1/dir2/dir3/ ">" [step-delete-fwd]
107 ;; The action wiped one directory forward.
109 ;; b>> ~/dir1/dir2/ "|" [chunk-delete]
111 ;; The action deleted whole line. It deletes until special marks
112 ;; like "@:". If repeated, it deletes constantly backward
116 ;; b>> http:/www.site.com/~userFoo/dir1/dir2/dir3/ "/" [e-slash]
118 ;; The e-slash action wiped out the line, because writing
119 ;; two slashes normally indicates, that you want to give
122 ;; b>> ~/dir1/dir2/dir3/ "~" [e-tilde]
124 ;; The action wiped the line away, because it assumed
125 ;; you want to give "~userFoo" or another "~" relative path
129 ;; b>> ~/dir1/dir2/dir3/ "'" [move-back]
131 ;; a>> ~/dir1/dir2/dir3/
133 ;; The cursor goes backward logical steps.
135 ;; b>> ~/dir1/dir2/dir3/ "*" [move-fwd]
137 ;; a>> ~/dir1/dir2/dir3/
139 ;; The cursor goes forward logical steps.
141 ;; Other minibuffer keys that you can activate with:
143 ;; (add-hook 'tinyef-load-hook 'tinyef-minibuffer-define-key-extras)
145 ;; o `C-c' `C-b' Insert most recent buffer name to prompt
146 ;; o `C-c' `C-d' Insert date: ISO8601 YY-MM-DD--HH-MM into prompt
147 ;; o `C-c' `C-f' Insert most recent buffer's file name to prompt
148 ;; o `C-c' `\t' Complete from minibuffer history
150 ;; Automatic Isntallation
152 ;; This file includes function `tinyef-install' which hooks the mode
153 ;; to the appropriate places. E.g. to your minibuffer. If you're in
154 ;; trouble, you can always turn this mode off with the supplied
155 ;; hotkey, which is by default `C-c' `/'. You can't "see" whether mode
156 ;; is on or off in minibuffer, since it doesn't have its own mode
157 ;; line. But calling the hotkey will tell you the state change. You
158 ;; can also remove this mode completely from your emacs if you need to
159 ;; do that in emergencies. just call following function with some
160 ;; prefix argument like `C-u' to `tinyef-install'
162 ;; Mouse bindings in minibuffer
164 ;; When this package loads, it calls function `tinyef-install-mouse'
165 ;; which defines following bindings to your minibuffer
167 ;; o Mouse-3 = BIG erase backward from point
168 ;; o C-mouse-1 = Small delete backward
170 ;; This should give your free hands to cut, paste and Delete, without
171 ;; lifting your hand off the mouse.
184 (ti::package-use-dynamic-compilation))
187 (autoload 'apropos-internal "apropos"))
189 (ti::package-defgroup-tiny TinyEf tinyef-: extensions
190 "Electric file minor mode. Designed for minibuffer file prompt editing.
193 o Easy filename editing. Deletes directories at time, delete line
194 backward, electric tilde, electric slash, electric colon etc.
195 o This is useful e.g. in minibuffer's C-x C-f promt
199 ;;{{{ setup: variables
201 (defcustom tinyef-:load-hook nil
202 "*Hook that is run when package is loaded."
206 (defcustom tinyef-:mode-key "\C-c/"
207 "*Key to toggle function `tinyef-mode' on/off in minibuffer map."
208 :type '(string :tag "Key sequence")
211 (defcustom tinyef-:mode-key-table
212 '((?\< . step-delete-back)
213 (?\> . step-delete-fwd)
217 (?\~ . e-tilde) ;electric keys
220 "*Map keys to actions.
221 Refer source file's default values for action names.
222 If you change this; you must call function \\[tinyef-mode-map-define-keys]."
225 (character :tag "Electric char")
228 (const step-delete-back)
229 (const step-delete-fwd)
239 (defcustom tinyef-:step-delete-chars "-./@:"
240 "*When using step-delete action, kill until these chars. This is charset.
241 The \"-\" character must be first in the string."
242 :type '(string "Charset")
245 (defcustom tinyef-:mode-defined-maps ;== if you need to change this; report
246 (delq nil ;== change to maintainer
250 'minibuffer-local-map
251 'minibuffer-local-must-match-map ;eg C-x C-f uses this
252 'minibuffer-local-completion-map
254 ;; the minibuffer when spaces are not allowed
255 (if (boundp 'minibuffer-local-ns-map)
256 'minibuffer-local-ns-map)))
257 "*Keymap list where to install Electric file minor mode hotkey-
258 See `tinyef-:mode-key'."
259 :type '(symbol :tag "Keymap")
266 (ti::macrof-version-bug-report
270 "$Id: tinyef.el,v 2.42 2007/05/01 17:20:43 jaalto Exp $"
276 tinyef-:mode-defined-maps
278 tinyef-:mode-key-table)))
281 ;;{{{ code: misc, keys, install
283 ;;;###autoload (autoload 'tinyef-mode "tinyef" "" t)
284 ;;;###autoload (autoload 'turn-off-tinyef-mode "tinyef" "" t)
285 ;;;###autoload (autoload 'turn-on-tinyef-mode "tinyef" "" t)
286 ;;;###autoload (autoload 'tinyef-commentary "tinyef" "" t)
287 ;;;###autoload (autoload 'tinyef-version "tinyef" "" t)
291 (ti::macrof-minor-mode-wizard
292 "tinyef-" " Tef" nil "Tef" 'TinyEf "tinyef-:" ;1-6
294 "Electric file name mode.
295 This mode helps you composing filename more easily. Some keys
296 are \"electric\", meaning that they have two behavior. By default
297 character \"~/$\" are electric. Some other keys have special meaning and you
298 cannot insert them into buffer unless you press C-q before the key-.
299 These special keys do are mapped to movement keys and delete keys.
301 See variable `tinyef-:mode-key-table' which specifies actions
302 for each electric character. Consult also `tinyef-:step-delete-chars'.
303 The default action table is as follows:
305 (setq tinyef-:mode-key-table
306 '((?\< . step-delete-back) ;KEY -- action symbol
307 (?\> . step-delete-fwd)
311 (?\~ . e-tilde) ;electric keys
315 Here is smple graph to give you an overview of what this mode does.
316 In these presented cases cursor it at the end of line.
317 Alternatively, just load this file, press C-x C-f and experiment
320 o b>> means what's on the line *before*
321 o a>> means what's there *after*
322 o `' means what you just pressed
323 o [] means which action the character triggered
325 b>> http:/www.site.com/~userFoo/dir1/dir2/dir3/ `/` [e-slash]
327 The e-slash action wiped out the line, because writing
328 two slashes normally indicates, that you want to give
331 b>> ~/dir1/dir2/dir3/ `~' [e-tilde]
333 The action wiped the line away, because it assumed
334 you want to give `~userFoo or another `~' relative path
336 b>> ~/dir1/dir2/dir3/ `[' [step-delete-back]
338 The action wiped previous directory name or until
339 special mark, See code, defaults are `:/@' (ange-ftp things)
341 b>> ~/dir1/dir2/ `=' [undo]
342 a>> ~/dir1/dir2/dir3/
343 The action works like normal undo.
345 b>> ~/dir1/dir2/ `\' [chunk-delete]
347 The action deleted whole line. It deletes until special marks
348 like `@:'. If repeated, it deletes constantly backward
352 \\{tinyef-:mode-map}"
360 ;;; ----------------------------------------------------------------------
362 (defmacro tinyef-function-macro (action)
363 "Define interactive command ACTION."
364 (let* ((sym (intern (format "tinyef-%s" (symbol-name (` (, action)))))))
368 (tinyef-char nil (quote (, action)))))))
370 (tinyef-function-macro chunk-delete)
371 (tinyef-function-macro step-delete-back)
373 ;;; ----------------------------------------------------------------------
375 (defsubst tinyef-key-p (map key)
376 "Test if function `tinyef-mode' is in MAP with KEY."
377 (eq 'tinyef-mode (lookup-key map key)))
379 ;;; ----------------------------------------------------------------------
381 (defsubst tinyef-action (char)
382 "Return action for CHAR."
383 (cdr-safe (char-assq char tinyef-:mode-key-table)))
385 ;;; ----------------------------------------------------------------------
387 (defun tinyef-install-maps (&optional remove force)
388 "Define Electric file mode's hot key. Optionally REMOVE.
389 The install is done only once, but you can FORCE reinstall.
391 See `tinyef-:mode-defined-maps'."
392 (let* ((key tinyef-:mode-key)
395 (dolist (x tinyef-:mode-defined-maps)
398 ;; eval or symbol-value function
399 (if (tinyef-key-p (eval x) key)
400 (define-key (eval x) key nil))
401 (unless (get 'tinyef-install-maps 'installed)
402 (if (lookup-key map key)
404 ;;(message "TinyMy: tinyef-:mode-key already taken in %s"
407 (define-key (eval x) key fun)))))
409 (put 'tinyef-install-maps 'installed t)))
411 ;;; ----------------------------------------------------------------------
413 (defun tinyef-mode-map-define-keys ()
414 "Define `tinyef-:mode-map' keys.
415 Always clears the keymap first and reinstalls the minor mode."
417 (setq tinyef-:mode-map (make-sparse-keymap)) ;always refresh
418 ;; Minor modes have copy of the keymap. Get rid of it and
419 ;; replace it with new one.
420 (ti::keymap-add-minor-mode 'tinyef-mode nil nil 'remove)
421 (dolist (elt tinyef-:mode-key-table)
422 (define-key tinyef-:mode-map (char-to-string (car elt)) 'tinyef-char))
423 (ti::keymap-add-minor-mode 'tinyef-mode
427 ;;; ----------------------------------------------------------------------
429 (defun tinyef-install (&optional arg)
430 "Install package. With optional ARG, cancel installation."
432 (tinyef-install-mouse arg)
435 (remove-hook 'minibuffer-setup-hook 'tinyef-minibuffer-setup)
436 (remove-hook 'minibuffer-exit-hook 'turn-off-tinyef-mode)
438 (ti::keymap-add-minor-mode 'tinyef-mode nil nil 'remove)
439 (tinyef-install-maps 'remove))
441 (add-hook 'minibuffer-setup-hook 'tinyef-minibuffer-setup 'end)
442 (add-hook 'minibuffer-exit-hook 'turn-off-tinyef-mode 'end)
443 (tinyef-mode-map-define-keys) ;installs also minor-mode
444 (tinyef-install-maps))))
446 ;;; ----------------------------------------------------------------------
448 (defun tinyef-install-mouse (&optional arg)
449 "Install default mouse binding. With ARG, remove."
452 minibuffer-local-must-match-map
453 minibuffer-local-completion-map))
456 ;; Have to bind down event; because MSB occupies it.
457 (define-key map [C-down-mouse-1] 'tinyef-step-delete-back)
458 (define-key map [C-down-mouse-3] 'undo)
459 (define-key map [mouse-3] 'tinyef-chunk-delete))
461 (define-key map [(control button1)] 'tinyef-step-delete-back)
462 (define-key map [(control button3)] 'undo)
463 (define-key map [(button3)] 'tinyef-chunk-delete)))))
467 ;;{{{ code: extra minibuffer commands
469 ;;; ----------------------------------------------------------------------
471 (defun tinyef-buffer-name-not-minibuffer ()
472 "Return the name of current buffer, as a string.
473 If current buffer is the *mini-buffer* return name of previous-window."
474 (buffer-name (if (window-minibuffer-p)
475 (if (eq (get-lru-window) (next-window))
476 (window-buffer (previous-window))
477 (window-buffer (next-window)))
480 ;;; ----------------------------------------------------------------------
482 (defun tinyef-insert-buffer-name ()
483 "Insert buffer name of most recent buffer."
485 (insert (tinyef-buffer-name-not-minibuffer)))
487 ;;; ----------------------------------------------------------------------
489 (defun tinyef-insert-buffer-dir-name ()
490 "Insert dir name of most recent buffer."
492 (let* ((bfn (buffer-file-name
493 (get-buffer (tinyef-buffer-name-not-minibuffer)))))
495 (insert (file-name-directory bfn)))))
497 ;;; ----------------------------------------------------------------------
499 (defun tinyef-insert-buffer-file-name ()
500 "Insert file name of most recent buffer."
502 (let* ((bfn (buffer-file-name
503 (get-buffer (tinyef-buffer-name-not-minibuffer)))))
507 ;;; ----------------------------------------------------------------------
509 (defun tinyef-complete-from-minibuffer-history ()
510 "Take the history list and make it available as a `completions' buffer"
512 (with-output-to-temp-buffer "*Completions*"
513 (display-completion-list (symbol-value minibuffer-history-variable))
515 (set-buffer standard-output)
516 (setq completion-base-size 0))))
518 ;;; ----------------------------------------------------------------------
520 (defun tinyef-insert-current-date-time-minibuf ()
521 "Insert the current date and time."
523 (insert (format-time-string "%Y-%m-%d--%H%-%M" (current-time))))
525 ;;; ----------------------------------------------------------------------
527 (defun tinyef-minibuffer-define-key-extras ()
528 "Define keys to minibuffer maps."
529 (dolist (map (apropos-internal
533 (keymapp (symbol-value var))))))
534 (setq map (symbol-value map))
535 (define-key map "\C-c\C-b" 'tinyef-insert-buffer-name)
536 (define-key map "\C-c\C-d" 'tinyef-insert-buffer-dir-name)
537 (define-key map "\C-c\C-f" 'tinyef-insert-buffer-file-name)
538 (define-key map "\C-c\C-t" 'tinyef-insert-current-date-time-minibuf)
539 (define-key map "\C-c\t" 'tinyef-complete-from-minibuffer-history)))
542 ;;{{{ code: minibuffer
544 ;;; ----------------------------------------------------------------------
545 ;;; by Anders Lindgren.
547 (defun tinyef-minibuffer-setup ()
548 "Turn on function `tinyef-mode' when entering minibuffer."
551 (if (boundp 'minibuffer-completion-table)
552 (eq minibuffer-completion-table 'read-file-name-internal)))
553 (if (and (boundp 'tinypair-mode) ;Turn off TinyPair.el
554 (fboundp 'turn-off-tinypair-mode))
555 (ti::funcall 'turn-off-tinypair-mode)))
560 ;;; ----------------------------------------------------------------------
562 (defun tinyef-step (&optional back)
563 "Position cursor, optionally BACK."
564 (let* ((set tinyef-:step-delete-chars)
565 (rset (concat "^" set)) ;reverse set
566 (func (if back 'skip-chars-backward 'skip-chars-forward))
570 (setq limit (line-beginning-position))
571 (setq limit (line-end-position)))
572 (funcall func rset limit) ;do the movement
573 (when (eq (point) point) ;not moved
574 (funcall func set limit)
575 (funcall func rset limit)) ;try again
576 (when (not (eq (point) point)) ;moved ok
577 (when (and (null back) (not (eolp)))
578 ;; fix position a little
581 ;;; ----------------------------------------------------------------------
583 (defun tinyef-char (&optional character action)
584 "Handle Electric file mode's commands.
585 If there is no action for character insert it as is.
586 If this command is called interactively outside of minibuffer,
587 turn off function `tinyef-mode' and insert character as is.
591 CHARACTER The character is read from input argument or it it is nil, then
592 `last-command-char' is used.
593 ACTION If nil `tinyef-:mode-key-table' is consulted for character.
594 If non-nil, then should ve valid action symbol.
598 \\{tinyef-:mode-map}"
600 (let* ((char (or character last-command-char)) ;char pressed
601 (act (or action (tinyef-action char)))
603 (e-list '(?/ ?@ ?\" ?\'))
610 (if (or (null act) ;no action recognized
612 (not (eq (selected-window) (minibuffer-window)))
614 (setq tinyef-mode nil))))
616 (setq bolp (line-beginning-position) eolp (line-end-position))
617 ;; ... ... ... ... ... ... ... ... ... ... ... ... e-kill-point . .
618 ;; find suitable kill point
622 (if (and (looking-at regexp)
623 (not (eq eolp (match-end 0))))
624 (push (match-end 0) hits)))
625 (if hits (setq point (apply 'max hits))) ;;find longest position
626 (if (eq point eolp) ;;end of line ?
627 (setq point (point)))
629 ((eq pnow point) ;no different than current point?
630 (setq str (buffer-substring bolp pnow))
631 ;; make the end position not to go past string delimiter "
632 (if (not (string-match ".*\"" str))
634 (setq point (+ bolp (match-end 0))))))) ;; cond-save-excursion
636 ;; ... ... ... ... ... ... ... ... ... ... ... ... ... ... undo ..
639 ;; ... ... ... ... ... ... ... ... ... ... ... ... ... .. chunk ..
641 ((eq act 'chunk-delete)
642 (delete-region point (point))) ;; The kill point is already set
643 ;; ... ... ... ... ... ... ... ... ... ... ... ... ... ... step ..
644 ((memq act '(step-delete-fwd move-fwd))
647 (if (eq act 'step-delete-fwd)
648 (delete-region point (point))))
649 ((memq act '(step-delete-back move-back))
652 (if (eq act 'step-delete-back)
653 (delete-region point (point))))
654 ;; ... ... ... ... ... ... ... ... ... ... ... ... ... electric ..
655 ((and (memq act (list 'e-slash))
656 (ti::char-in-list-case (preceding-char) e-list)
657 ;; permit `//hostname/path/to/file'
658 (not (eq (point) (1+ (point-min))))
659 ;; permit `http://url/goes/here'
660 (not (char= ?: (char-after (- (point) 2)))))
661 (delete-region point (point))
664 ((memq act '(e-tilde))
666 ((char= (preceding-char) ?~)
667 ;; /ftp@some:~ pressing "~" now deletes full line
668 (delete-region bolp (point)))
669 ((and (not (ti::win32-p)) (char= (preceding-char) ?:))
670 ;; In NT, it's best to delete immediately, because you have
671 ;; those MS-DOS filename C:/ ...
674 ;; /ftp@some: allow adding "~"
676 ((let ((filename (buffer-substring bolp (point))))
677 (if (not (string= (file-name-nondirectory filename) ""))
678 ;; find file which would have tilde in the name.
679 (file-name-completion (file-name-nondirectory filename)
680 (file-name-directory filename))))
681 ;; skip electric: tilde is part of an existing filename
684 (delete-region point (point))
685 (if (save-excursion (beginning-of-line) (looking-at "[a-z]:[/\\]?"))
686 ;; Kill MS-DOS fabsolute path c:/this/dir
687 (delete-region (line-beginning-position) (point))
688 (delete-region point (point)))))
690 ((memq act '(e-dollar))
691 (delete-region bolp (point))
701 (run-hooks 'tinyef-:load-hook)
703 ;;; tinyef.el ends here