forked from redguardtoo/emacs.d
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathinit-ivy.el
475 lines (437 loc) · 19.2 KB
/
init-ivy.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
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
;; {{ @see http://oremacs.com/2015/04/19/git-grep-ivy/
(defun counsel-escape (keyword)
(setq keyword (replace-regexp-in-string "\"" "\\\\\"" keyword))
(setq keyword (replace-regexp-in-string "\\?" "\\\\\?" keyword))
(setq keyword (replace-regexp-in-string "\\$" "\\\\x24" keyword))
(setq keyword (replace-regexp-in-string "\\*" "\\\\\*" keyword))
(setq keyword (replace-regexp-in-string "\\." "\\\\\." keyword))
(setq keyword (replace-regexp-in-string "\\[" "\\\\\[" keyword))
(setq keyword (replace-regexp-in-string "\\]" "\\\\\]" keyword))
;; perl-regex support non-ASCII characters
;; Turn on `-P` from `git grep' and `grep'
;; the_silver_searcher needs no setup
(setq keyword (replace-regexp-in-string "(" "\\\\x28" keyword))
(setq keyword (replace-regexp-in-string ")" "\\\\x29" keyword))
(setq keyword (replace-regexp-in-string "{" "\\\\x7b" keyword))
(setq keyword (replace-regexp-in-string "}" "\\\\x7d" keyword))
keyword)
(defun counsel-read-keyword (hint &optional default-when-no-active-region)
(let* (keyword)
(cond
((region-active-p)
(setq keyword (counsel-escape (my-selected-str)))
;; de-select region
(set-mark-command nil))
(t
(setq keyword (if default-when-no-active-region
default-when-no-active-region
(read-string hint)))))
keyword))
(defmacro counsel-git-grep-or-find-api (fn git-cmd hint &optional no-keyword filter)
"Apply FN on the output lines of GIT-CMD. HINT is hint when user input.
Yank the file name at the same time. FILTER is function to filter the collection"
`(let* ((str (if (buffer-file-name) (file-name-base (buffer-file-name)) ""))
(default-directory (locate-dominating-file
default-directory ".git"))
keyword
collection)
(unless ,no-keyword
;; selected region contains no regular expression
(setq keyword (counsel-read-keyword (concat "Enter " ,hint " pattern:" ))))
(setq collection
(split-string (shell-command-to-string (if ,no-keyword ,git-cmd
(format ,git-cmd keyword)))
"\n"
t))
(if ,filter (setq collection (funcall ,filter collection)))
(cond
((and collection (= (length collection) 1))
(funcall ,fn (car collection)))
(t
(ivy-read (if ,no-keyword ,hint (format "matching \"%s\":" keyword))
collection
:action ,fn)))))
(defun counsel--open-grepped-file (val)
(let* ((lst (split-string val ":"))
(linenum (string-to-number (cadr lst))))
;; open file
(find-file (car lst))
;; goto line if line number exists
(when (and linenum (> linenum 0))
(goto-char (point-min))
(forward-line (1- linenum)))))
(defun counsel-git-grep-in-project ()
"Grep in the current git repository.
Extended regex is used, like (pattern1|pattern2)."
(interactive)
(counsel-git-grep-or-find-api 'counsel--open-grepped-file
"git --no-pager grep -P -I --full-name -n --no-color -E -e \"%s\""
"grep"))
(defvar counsel-git-grep-author-regex nil)
;; `git --no-pager blame -w -L 397,+1 --porcelain lisp/init-evil.el'
(defun counsel--filter-grepped-by-author (collection)
(if counsel-git-grep-author-regex
(delq nil
(mapcar
(lambda (v)
(let (blame-cmd (arr (split-string v ":" t)))
(setq blame-cmd
(format "git --no-pager blame -w -L %s,+1 --porcelain %s"
(cadr arr) ; line number
(car arr))) ; file
(if (string-match-p (format "\\(author %s\\|author Not Committed\\)"
counsel-git-grep-author-regex)
(shell-command-to-string blame-cmd))
v)))
collection))
collection))
(defun counsel-git-grep-by-author ()
"Grep in the current git repository.
It's SLOW when more than 20 git blame process start."
(interactive)
(counsel-git-grep-or-find-api 'counsel--open-grepped-file
"git --no-pager grep --full-name -n --no-color -i -e \"%s\""
"grep by author"
nil
'counsel--filter-grepped-by-author))
(defun counsel-git-show-file ()
"Find file in HEAD commit or whose commit hash is selected region."
(interactive)
(counsel-git-grep-or-find-api 'find-file
(format "git --no-pager diff-tree --no-commit-id --name-only -r %s"
(counsel-read-keyword nil "HEAD"))
"files from `git-show' "
t))
(defun counsel-git-diff-file ()
"Find file in `git diff'."
(interactive)
(counsel-git-grep-or-find-api 'find-file
"git --no-pager diff --name-only"
"files from `git-diff' "
t))
(defun counsel-git-find-file ()
"Find file in the current git repository."
(interactive)
(counsel-git-grep-or-find-api 'find-file
"git ls-tree -r HEAD --name-status | grep \"%s\""
"file"))
(defun counsel-insert-grepped-line (val)
(let ((lst (split-string val ":")) text-line)
;; the actual text line could contain ":"
(setq text-line (replace-regexp-in-string (format "^%s:%s:" (car lst) (nth 1 lst)) "" val))
;; trim the text line
(setq text-line (replace-regexp-in-string (rx (* (any " \t\n")) eos) "" text-line))
(kill-new text-line)
(if insert-line (insert text-line))
(message "line from %s:%s => kill-ring" (car lst) (nth 1 lst))))
(defun counsel-replace-current-line (leading-spaces content)
(beginning-of-line)
(kill-line)
(insert (concat leading-spaces content))
(end-of-line))
(defun counsel-git-grep-complete-line (&optional other-grep)
"Complete line using text from (line-beginning-position) to (point).
If OTHER-GREP is not nil, we use the_silver_searcher and grep instead."
(interactive "P")
(let* ((cur-line (my-line-str (point)))
(default-directory (locate-dominating-file
default-directory ".git"))
(keyword (counsel-escape (replace-regexp-in-string "^[ \t]*" "" cur-line)))
(cmd (cond
((not other-grep)
(format "git --no-pager grep --no-color -P -I -h -i -e \"^[ \\t]*%s\" | sed s\"\/^[ \\t]*\/\/\" | sed s\"\/[ \\t]*$\/\/\" | sort | uniq"
keyword))
(t
(concat (my-grep-cli keyword (if (executable-find "ag") "" "-h")) ; tell grep not to output file name
(if (executable-find "ag") " | sed s\"\/^.*:[0-9]*:\/\/\"" "") ; remove file names for ag
" | sed s\"\/^[ \\t]*\/\/\" | sed s\"\/[ \\t]*$\/\/\" | sort | uniq"))))
(leading-spaces "")
(collection (split-string (shell-command-to-string cmd) "[\r\n]+" t)))
(message "cmd=%s" cmd)
;; grep lines without leading/trailing spaces
(when collection
(if (string-match "^\\([ \t]*\\)" cur-line)
(setq leading-spaces (match-string 1 cur-line)))
(cond
((= 1 (length collection))
(counsel-replace-current-line leading-spaces (car collection)))
((> (length collection) 1)
(ivy-read "lines:"
collection
:action (lambda (l)
(counsel-replace-current-line leading-spaces l))))))))
(global-set-key (kbd "C-x C-l") 'counsel-git-grep-complete-line)
(defun counsel-git-grep-yank-line (&optional insert-line)
"Grep in the current git repository and yank the line.
If INSERT-LINE is not nil, insert the line grepped"
(interactive "P")
(counsel-git-grep-or-find-api 'counsel-insert-grepped-line
"git --no-pager grep -I --full-name -n --no-color -i -e \"%s\""
"grep"
nil))
(defvar counsel-my-name-regex ""
"My name used by `counsel-git-find-my-file', support regex like '[Tt]om [Cc]hen'.")
(defun counsel-git-find-my-file (&optional num)
"Find my files in the current git repository.
If NUM is not nil, find files since NUM weeks ago.
Or else, find files since 24 weeks (6 months) ago."
(interactive"P")
(unless (and num (> num 0))
(setq num 24))
(let* ((cmd (concat "git log --pretty=format: --name-only --since=\""
(number-to-string num)
" weeks ago\" --author=\""
counsel-my-name-regex
"\" | grep \"%s\" | sort | uniq")))
;; (message "cmd=%s" cmd)
(counsel-git-grep-or-find-api 'find-file cmd "file" nil)))
;; }}
(defun ivy-imenu-get-candidates-from (alist &optional prefix)
(cl-loop for elm in alist
nconc (if (imenu--subalist-p elm)
(ivy-imenu-get-candidates-from
(cl-loop for (e . v) in (cdr elm) collect
(cons e (if (integerp v) (copy-marker v) v)))
;; pass the prefix to next recursive call
(concat prefix (if prefix ".") (car elm)))
(and (cdr elm) ; bug in imenu, should not be needed.
(setcdr elm (copy-marker (cdr elm))) ; Same as [1].
(let ((key (concat prefix (if prefix ".") (car elm))) )
(list (cons key (cons key (copy-marker (cdr elm)))))
)))))
(defun counsel-imenu-goto ()
"Imenu based on ivy-mode."
(interactive)
(unless (featurep 'imenu)
(require 'imenu nil t))
(let* ((imenu-auto-rescan t)
(items (imenu--make-index-alist t)))
(ivy-read "imenu items:"
(ivy-imenu-get-candidates-from (delete (assoc "*Rescan*" items) items))
:action (lambda (k)
;; minor error handling
(if (listp (cdr k)) (setq k (cdr k)))
;; copied from ido-imenu, don't know the purpose
(push-mark (point))
;; better way to imenu
(imenu k)
(if (memq major-mode '(org-mode))
(org-show-subtree))))))
(defun counsel-bookmark-goto ()
"Open ANY bookmark. Requires bookmark+"
(interactive)
(unless (featurep 'bookmark)
(require 'bookmark))
(bookmark-maybe-load-default-file)
(let* ((bookmarks (and (boundp 'bookmark-alist) bookmark-alist))
(collection (delq nil (mapcar (lambda (bookmark)
(let (key)
;; build key which will be displayed
(cond
((and (assoc 'filename bookmark) (cdr (assoc 'filename bookmark)))
(setq key (format "%s (%s)" (car bookmark) (cdr (assoc 'filename bookmark)))))
((and (assoc 'location bookmark) (cdr (assoc 'location bookmark)))
;; bmkp-jump-w3m is from bookmark+
(setq key (format "%s (%s)" (car bookmark) (cdr (assoc 'location bookmark)))))
(t
(setq key (car bookmark))))
;; re-shape the data so full bookmark be passed to ivy-read:action
(cons key bookmark)))
bookmarks))))
;; do the real thing
(ivy-read "bookmarks:"
collection
:action (lambda (bookmark)
(unless (featurep 'bookmark+)
(require 'bookmark+))
(bookmark-jump bookmark)))))
(defun counsel-git-find-file-committed-with-line-at-point ()
(interactive)
(let* ((default-directory (locate-dominating-file
default-directory ".git"))
(filename (file-truename buffer-file-name))
(linenum (save-restriction
(widen)
(save-excursion
(beginning-of-line)
(1+ (count-lines 1 (point))))))
(git-cmd (format "git --no-pager blame -w -L %d,+1 --porcelain %s"
linenum
filename))
(str (shell-command-to-string git-cmd))
hash)
(cond
((and (string-match "^\\([0-9a-z]\\{40\\}\\) " str)
(not (string= (setq hash (match-string 1 str)) "0000000000000000000000000000000000000000")))
;; (message "hash=%s" hash)
(counsel-git-grep-or-find-api 'counsel--open-grepped-file
(format "git --no-pager show --pretty=\"format:\" --name-only \"%s\"" hash)
(format "files in commit %s:" (substring hash 0 7))
nil
t))
(t
(message "Current line is NOT committed yet!")))))
(defun counsel-yank-bash-history ()
"Yank the bash history."
(interactive)
(shell-command "history -r") ; reload history
(let* ((collection
(nreverse
(split-string (with-temp-buffer
(insert-file-contents (file-truename "~/.bash_history"))
(buffer-string))
"\n"
t))))
(ivy-read (format "Bash history:") collection
:action (lambda (val)
(kill-new val)
(message "%s => kill-ring" val)))))
(defun counsel-git-show-hash-diff-mode (hash)
(let ((show-cmd (format "git --no-pager show --no-color %s" hash)))
(diff-region-open-diff-output (shell-command-to-string show-cmd)
"*Git-show")))
(defun counsel-recentf-goto ()
"Recent files."
(interactive)
(unless recentf-mode (recentf-mode 1))
(if (fboundp 'counsel-recentf)
(counsel-recentf)
(ivy-recentf)))
(defun counsel-goto-recent-directory ()
"Goto recent directories."
(interactive)
(unless recentf-mode (recentf-mode 1))
(let* ((collection (delete-dups
(append (mapcar 'file-name-directory recentf-list)
;; fasd history
(if (executable-find "fasd")
(split-string (shell-command-to-string "fasd -ld") "\n" t))))))
(ivy-read "directories:" collection :action 'dired)))
;; {{ ag/grep
(defvar my-grep-ingore-dirs
'(".git"
".bzr"
".svn"
"bower_components"
"node_modules"
".sass-cache"
".cache"
"test"
"tests"
".metadata"
"logs")
"Directories to ignore when grepping.")
(defvar my-grep-ingore-file-exts
'("log"
"properties"
"session"
"swp")
"File extensions to ignore when grepping.")
(defvar my-grep-ingore-file-names
'("TAGS"
"tags"
"GTAGS"
"GPATH"
".bookmarks.el"
"*.svg"
"history"
"#*#"
"*.min.js"
"*.min.css"
"*~")
"File names to ignore when grepping.")
(defun my-grep-exclude-opts ()
(cond
((executable-find "ag")
(concat (mapconcat (lambda (e) (format "--ignore-dir='%s'" e))
my-grep-ingore-dirs " ")
" "
(mapconcat (lambda (e) (format "--ignore='*.%s'" e))
my-grep-ingore-file-exts " ")
" "
(mapconcat (lambda (e) (format "--ignore='%s'" e))
my-grep-ingore-file-names " ")))
(t
(concat (mapconcat (lambda (e) (format "--exclude-dir='%s'" e))
my-grep-ingore-dirs " ")
" "
(mapconcat (lambda (e) (format "--exclude='*.%s'" e))
my-grep-ingore-file-exts " ")
" "
(mapconcat (lambda (e) (format "--exclude='%s'" e))
my-grep-ingore-file-names " ")))))
(defun my-grep-cli (keyword &optional extra-opts)
"Extended regex is used, like (pattern1|pattern2)."
(let* (opts cmd)
(unless extra-opts (setq extra-opts ""))
(cond
((executable-find "ag")
(setq cmd (format "ag -s --nocolor --nogroup --silent %s %s \"%s\" --"
(my-grep-exclude-opts)
extra-opts
keyword)))
(t
;; use extended regex always
(setq cmd (format "grep -rsnE -P %s \"%s\" *"
(my-grep-exclude-opts)
extra-opts
keyword))))
;; (message "cmd=%s" cmd)
cmd))
(defun my-root-dir ()
(file-name-as-directory (and (fboundp 'ffip-get-project-root-directory)
(ffip-get-project-root-directory))))
(defun my-grep ()
"Grep at project root directory or current directory.
If ag (the_silver_searcher) exists, use ag.
Extended regex is used, like (pattern1|pattern2)."
(interactive)
(let* ((keyword (counsel-read-keyword "Enter grep pattern: "))
(default-directory (my-root-dir))
(collection (split-string (shell-command-to-string (my-grep-cli keyword)) "[\r\n]+" t)))
(ivy-read (format "matching \"%s\" at %s:" keyword (my-root-dir))
collection
:action `(lambda (line)
(let* ((default-directory (my-root-dir)))
(counsel--open-grepped-file line))))))
;; }}
(defun counsel-browse-kill-ring (&optional n)
"Use `browse-kill-ring' if it exists and N is 1.
If N > 1, assume just yank the Nth item in `kill-ring'.
If N is nil, use `ivy-mode' to browse the `kill-ring'."
(interactive "P")
(cond
((or (not n) (and (= n 1) (not (fboundp 'browse-kill-ring))))
;; remove duplicates in `kill-ring'
(let* ((candidates (cl-remove-if
(lambda (s)
(or (< (length s) 5)
(string-match "\\`[\n[:blank:]]+\\'" s)))
(delete-dups kill-ring))))
(let* ((ivy-height (/ (frame-height) 2)))
(ivy-read "Browse `kill-ring':"
(mapcar
(lambda (s)
(let* ((w (frame-width))
;; display kill ring item in one line
(key (replace-regexp-in-string "[ \t]*[\n\r]+[ \t]*" "\\\\n" s)))
;; strip the whitespace
(setq key (replace-regexp-in-string "^[ \t]+" "" key))
;; fit to the minibuffer width
(if (> (length key) w)
(setq key (concat (substring key 0 (- w 4)) "...")))
(cons key s)))
candidates)
:action 'my-insert-str))))
((= n 1)
(browse-kill-ring))
((> n 1)
(setq n (1- n))
(if (< n 0) (setq n 0))
(my-insert-str (nth n kill-ring)))))
(eval-after-load 'ivy
'(progn
;; work around ivy issue.
;; @see https://github.com/abo-abo/swiper/issues/828
(setq ivy-display-style 'fancy)))
(provide 'init-ivy)