forked from rigtorp/dotemacs
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathiflipb.el
269 lines (254 loc) · 10.5 KB
/
iflipb.el
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
;;; iflipb -- interactively flip between recently visited buffers
;;
;; Copyright (C) 2007-2009 Joel Rosdahl
;;
;; Redistribution and use in source and binary forms, with or without
;; modification, are permitted provided that the following conditions
;; are met:
;; 1. Redistributions of source code must retain the above copyright
;; notice, this list of conditions and the following disclaimer.
;; 2. Redistributions in binary form must reproduce the above copyright
;; notice, this list of conditions and the following disclaimer in the
;; documentation and/or other materials provided with the distribution.
;; 3. Neither the name of the author nor the names of its contributors
;; may be used to endorse or promote products derived from this software
;; without specific prior written permission.
;;
;; ============================================================================
;;
;; iflipb lets you flip between recently visited buffers in a way that
;; resembles what Alt-(Shift-)TAB does in Microsoft Windows and other graphical
;; window managers. iflipb treats the buffer list as a stack, and (by design)
;; it doesn't loop around. This means that when you have flipped to the last
;; buffer and continue, you don't get to the first buffer again. This is a good
;; thing.
;;
;;
;; OPERATION
;; =========
;;
;; iflipb provides two commands: iflipb-next-buffer and iflipb-previous-buffer.
;;
;; iflipb-next-buffer behaves like Alt-TAB: it switches to the previously used
;; buffer, just like "C-x b RET" (or C-M-l in XEmacs). However, another
;; consecutive call to iflipb-next-buffer switches to the next buffer in the
;; buffer list, and so on. When such a consecutive call is made, the
;; skipped-over buffer is not regarded as visited.
;;
;; While flipping, the names of the most recent buffers are displayed in the
;; minibuffer, and the currently visited buffer is surrounded by square
;; brackets and marked with a bold face.
;;
;; A key thing to notice here is that iflipb displays the buffer contents after
;; each step forward/backwards (in addition to displaying the buffer names),
;; unlike for instance the buffer switching model of ido-mode where only the
;; buffer names are displayed.
;;
;; iflipb-previous-buffer behaves like Alt-Shift-TAB: it walks backwards in the
;; buffer list.
;;
;; Here is an illustration of what happens in a couple of different scenarios:
;;
;; Minibuffer Actual
;; display buffer list
;; --------------------------------------------
;; Original: A B C D E
;; Forward flip: A [B] C D E B A C D E
;; Forward flip: A B [C] D E C A B D E
;; Forward flip: A B C [D] E D A B C E
;;
;; Original: A B C D E
;; Forward flip: A [B] C D E B A C D E
;; Forward flip: A B [C] D E C A B D E
;; Backward flip: A [B] C D E B A C D E
;;
;; Original: A B C D E
;; Forward flip: A [B] C D E B A C D E
;; Forward flip: A B [C] D E C A B D E
;; [Edit buffer C]: C A B D E
;; Forward flip: C [A] B D E A C B D E
;;
;; iflipb by default ignores buffers whose names start with an asterix or
;; space. You can give a prefix argument to iflipb-next-buffer to make it flip
;; between more buffers. See the documentation of the variables
;; iflipb-ignore-buffers and iflipb-always-ignore-buffers for how to change
;; this.
;;
;;
;; INSTALLATION
;; ============
;;
;; To load iflipb, store iflipb.el in your Emacs load path and put
;;
;; (require 'iflipb)
;;
;; in your .emacs file or equivalent.
;;
;; This file does not install any key bindings for the two commands. I
;; personally use M-h and M-H (i.e., M-S-h) since I don't use the standard
;; binding of M-h (mark-paragraph) and M-h is quick and easy to press. To
;; install iflipb with M-h and M-H as keyboard bindings, put something like
;; this in your .emacs:
;;
;; (global-set-key (kbd "M-h") 'iflipb-next-buffer)
;; (global-set-key (kbd "M-H") 'iflipb-previous-buffer)
;;
;; Another alternative is to use C-tab and C-S-tab:
;;
;; (global-set-key (kbd "<C-tab>") 'iflipb-next-buffer)
;; (global-set-key
;; (if (featurep 'xemacs) (kbd "<C-iso-left-tab>") (kbd "<C-S-iso-lefttab>"))
;; 'iflipb-previous-buffer)
;;
;; Or perhaps use functions keys like F9 and F10:
;;
;; (global-set-key (kbd "<f10>") 'iflipb-next-buffer)
;; (global-set-key (kbd "<f9>") 'iflipb-previous-buffer)
;;
;;
;; ABOUT
;; =====
;;
;; iflipb was inspired by cycle-buffer.el
;; <http://kellyfelkins.org/pub/cycle-buffer.el>. cycle-buffer.el has some more
;; features, but doesn't quite behave like I want, so I wrote my own simple
;; replacement.
;;
;; Have fun!
;;
;; /Joel Rosdahl <[email protected]>
;;
(defvar iflipb-ignore-buffers "^[*]"
"*This variable determines which buffers to ignore when a
prefix argument has not been given to iflipb-next-buffer. The
value may be either a regexp string, a function or a list. If the
value is a regexp string, it describes buffer names to exclude
from the buffer list. If the value is a function, the function
will get a buffer name as an argument (a return value of nil from
the function means include and non-nil means exclude). If the
value is a list, the filter matches if any of the elements in the
value match.")
(defvar iflipb-always-ignore-buffers "^ "
"*This variable determines which buffers to always ignore. The
value may be either a regexp string, a function or a list. If the
value is a regexp string, it describes buffer names to exclude
from the buffer list. If the value is a function, the function
will get a buffer name as an argument (a return value of nil from
the function means include and non-nil means exclude). If the
value is a list, the filter matches if any of the elements in the
value match.")
(defvar iflipb-current-buffer-index 0
"Index of the currently displayed buffer in the buffer list.")
(defvar iflipb-include-more-buffers nil
"Whether all buffers should be included while flipping.")
(defvar iflipb-saved-buffers nil
"Saved buffer list state; the original order of buffers to the left
of iflipb-current-buffer-index.")
(defun iflipb-first-n (n list)
"Returns the first n elements of a list."
(butlast list (- (length list) n)))
(defun iflipb-filter (pred elements)
"Returns elements that satisfy a predicate."
(let ((result nil))
(while elements
(let ((elem (car elements))
(rest (cdr elements)))
(when (funcall pred elem)
(setq result (cons elem result)))
(setq elements rest)))
(nreverse result)))
(defun iflipb-any (elements)
"Returns non-nil if and only if any element in the list is non-nil."
(iflipb-filter (lambda (x) (not (null x))) elements))
(defun iflipb-match-filter (string filter)
"Returns non-nil if string matches filter, otherwise nil."
(cond ((null filter) nil)
((functionp filter) (funcall filter string))
((listp filter)
(iflipb-any (mapcar (lambda (f) (iflipb-match-filter string f))
filter)))
((stringp filter) (string-match filter string))
(t (error "Bad iflipb ignore filter element: %s" filter))))
(defun iflipb-buffers-not-matching-filter (filter)
"Returns a list of buffer names not matching a filter."
(iflipb-filter
(lambda (b) (not (iflipb-match-filter (buffer-name b) filter)))
(buffer-list)))
(defun iflipb-interesting-buffers ()
"Returns buffers that should be included in the displayed
buffer list."
(iflipb-buffers-not-matching-filter
(append
(list iflipb-always-ignore-buffers)
(if iflipb-include-more-buffers
nil
(list iflipb-ignore-buffers)))))
(defun iflipb-first-iflipb-buffer-switch-command ()
"Determines whether this is the first invocation of
iflipb-next-buffer or iflipb-previous-buffer this round."
(not (or (eq last-command 'iflipb-next-buffer)
(eq last-command 'iflipb-previous-buffer))))
(defun iflipb-restore-buffers ()
"Helper function that restores the buffer list to the original state."
(mapc 'switch-to-buffer (reverse iflipb-saved-buffers)))
(defun iflipb-format-buffer (current-buffer buffer)
"Format a buffer name for inclusion in the buffer list in the
minibuffer."
(let ((name (buffer-name buffer)))
(when (eq current-buffer buffer)
(setq name (format "[%s]" name))
(add-text-properties 1 (1- (length name)) '(face bold) name))
name))
(defun iflipb-format-buffers (current-buffer buffers)
"Format buffer names for displaying them in the minibuffer."
(truncate-string-to-width
(mapconcat
(lambda (buffer)
(iflipb-format-buffer current-buffer buffer))
buffers
" ")
(1- (window-width (minibuffer-window)))))
(defun iflipb-select-buffer (index)
"Helper function that shows the buffer with a given index."
(iflipb-restore-buffers)
(setq iflipb-saved-buffers nil)
(let* ((buffers (iflipb-interesting-buffers))
(current-buffer (nth index buffers)))
(setq iflipb-current-buffer-index index)
(setq iflipb-saved-buffers (iflipb-first-n index buffers))
(message (iflipb-format-buffers current-buffer buffers))
(switch-to-buffer current-buffer)))
(defun iflipb-next-buffer (arg)
"Flip to the next buffer in the buffer list. Consecutive
invocations switch to less recent buffers in the buffer list.
Buffers matching iflipb-always-ignore-buffers are always ignored.
Without a prefix argument, buffers matching iflipb-ignore-buffers
are also ignored."
(interactive "P")
(when (iflipb-first-iflipb-buffer-switch-command)
(setq iflipb-current-buffer-index 0)
(setq iflipb-saved-buffers nil))
(if arg
(setq iflipb-include-more-buffers t)
(if (iflipb-first-iflipb-buffer-switch-command)
(setq iflipb-include-more-buffers nil)))
(let ((buffers (iflipb-interesting-buffers)))
(if (or (null buffers)
(and (memq (window-buffer) buffers)
(= iflipb-current-buffer-index
(1- (length buffers)))))
(message "No more buffers.")
(iflipb-select-buffer (1+ iflipb-current-buffer-index)))
(setq last-command 'iflipb-next-buffer)))
(defun iflipb-previous-buffer ()
"Flip to the previous buffer in the buffer list. Consecutive
invocations switch to more recent buffers in the buffer list."
(interactive)
(when (iflipb-first-iflipb-buffer-switch-command)
(setq iflipb-current-buffer-index 0)
(setq iflipb-saved-buffers nil))
(if (= iflipb-current-buffer-index 0)
(message "You are already looking at the top buffer.")
(iflipb-select-buffer (1- iflipb-current-buffer-index)))
(setq last-command 'iflipb-previous-buffer))
(provide 'iflipb)