|
| 1 | +;;; lispy-occur.el --- Select a line within the current top level sexp. -*- lexical-binding: t -*- |
| 2 | + |
| 3 | +;; Copyright (C) 2014-2021 Oleh Krehel |
| 4 | + |
| 5 | +;; This file is not part of GNU Emacs |
| 6 | + |
| 7 | +;; This file is free software; you can redistribute it and/or modify |
| 8 | +;; it under the terms of the GNU General Public License as published by |
| 9 | +;; the Free Software Foundation; either version 3, or (at your option) |
| 10 | +;; any later version. |
| 11 | + |
| 12 | +;; This program is distributed in the hope that it will be useful, |
| 13 | +;; but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 14 | +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 15 | +;; GNU General Public License for more details. |
| 16 | + |
| 17 | +;; For a full copy of the GNU General Public License |
| 18 | +;; see <http://www.gnu.org/licenses/>. |
| 19 | + |
| 20 | +;;; Commentary: |
| 21 | +;; |
| 22 | + |
| 23 | +;;; Code: |
| 24 | +(require 'swiper) |
| 25 | + |
| 26 | +(defcustom lispy-occur-backend 'ivy |
| 27 | + "Method to navigate to a line with `lispy-occur'." |
| 28 | + :type '(choice |
| 29 | + (const :tag "Ivy" ivy) |
| 30 | + (const :tag "Helm" helm))) |
| 31 | + |
| 32 | +(defvar lispy--occur-beg 1 |
| 33 | + "Start position of the top level sexp during `lispy-occur'.") |
| 34 | + |
| 35 | +(defvar lispy--occur-end 1 |
| 36 | + "End position of the top level sexp during `lispy-occur'.") |
| 37 | + |
| 38 | +(defun lispy--occur-candidates (&optional bnd) |
| 39 | + "Return the candidates for `lispy-occur'." |
| 40 | + (setq bnd (or bnd (save-excursion |
| 41 | + (unless (and (bolp) |
| 42 | + (lispy-left-p)) |
| 43 | + (beginning-of-defun)) |
| 44 | + (lispy--bounds-dwim)))) |
| 45 | + (let ((line-number -1) |
| 46 | + candidates) |
| 47 | + (setq lispy--occur-beg (car bnd)) |
| 48 | + (setq lispy--occur-end (cdr bnd)) |
| 49 | + (save-excursion |
| 50 | + (goto-char lispy--occur-beg) |
| 51 | + (while (< (point) lispy--occur-end) |
| 52 | + (push (format "%-3d %s" |
| 53 | + (cl-incf line-number) |
| 54 | + (buffer-substring |
| 55 | + (line-beginning-position) |
| 56 | + (line-end-position))) |
| 57 | + candidates) |
| 58 | + (forward-line 1))) |
| 59 | + (nreverse candidates))) |
| 60 | + |
| 61 | +(defun lispy--occur-preselect () |
| 62 | + "Initial candidate regex for `lispy-occur'." |
| 63 | + (format "^%d" |
| 64 | + (- |
| 65 | + (line-number-at-pos (point)) |
| 66 | + (line-number-at-pos lispy--occur-beg)))) |
| 67 | + |
| 68 | +(defvar helm-input) |
| 69 | +(declare-function helm "ext:helm") |
| 70 | + |
| 71 | +(defun lispy-occur-action-goto-paren (x) |
| 72 | + "Goto line X for `lispy-occur'." |
| 73 | + (setq x (read x)) |
| 74 | + (goto-char lispy--occur-beg) |
| 75 | + (let ((input (if (eq lispy-occur-backend 'helm) |
| 76 | + helm-input |
| 77 | + ivy-text)) |
| 78 | + str-or-comment) |
| 79 | + (cond ((string= input "") |
| 80 | + (forward-line x) |
| 81 | + (back-to-indentation) |
| 82 | + (when (re-search-forward lispy-left (line-end-position) t) |
| 83 | + (goto-char (match-beginning 0)))) |
| 84 | + |
| 85 | + ((setq str-or-comment |
| 86 | + (progn |
| 87 | + (forward-line x) |
| 88 | + (re-search-forward (ivy--regex input) |
| 89 | + (line-end-position) t) |
| 90 | + (lispy--in-string-or-comment-p))) |
| 91 | + (goto-char str-or-comment)) |
| 92 | + |
| 93 | + ((re-search-backward lispy-left (line-beginning-position) t) |
| 94 | + (goto-char (match-beginning 0))) |
| 95 | + |
| 96 | + ((re-search-forward lispy-left (line-end-position) t) |
| 97 | + (goto-char (match-beginning 0))) |
| 98 | + |
| 99 | + (t |
| 100 | + (back-to-indentation))))) |
| 101 | + |
| 102 | +(defun lispy-occur-action-goto-end (x) |
| 103 | + "Goto line X for `lispy-occur'." |
| 104 | + (setq x (read x)) |
| 105 | + (goto-char lispy--occur-beg) |
| 106 | + (forward-line x) |
| 107 | + (re-search-forward (ivy--regex ivy-text) (line-end-position) t)) |
| 108 | + |
| 109 | +(defun lispy-occur-action-goto-beg (x) |
| 110 | + "Goto line X for `lispy-occur'." |
| 111 | + (when (lispy-occur-action-goto-end x) |
| 112 | + (goto-char (match-beginning 0)))) |
| 113 | + |
| 114 | +(defun lispy-occur-action-mc (_x) |
| 115 | + "Make a fake cursor for each `lispy-occur' candidate." |
| 116 | + (let ((cands (nreverse ivy--old-cands)) |
| 117 | + cand) |
| 118 | + (while (setq cand (pop cands)) |
| 119 | + (goto-char lispy--occur-beg) |
| 120 | + (forward-line (read cand)) |
| 121 | + (re-search-forward (ivy--regex ivy-text) (line-end-position) t) |
| 122 | + (when cands |
| 123 | + (mc/create-fake-cursor-at-point)))) |
| 124 | + (multiple-cursors-mode 1)) |
| 125 | + |
| 126 | +(ivy-set-actions |
| 127 | + 'lispy-occur |
| 128 | + '(("m" lispy-occur-action-mc "multiple-cursors") |
| 129 | + ("j" lispy-occur-action-goto-beg "goto start") |
| 130 | + ("k" lispy-occur-action-goto-end "goto end"))) |
| 131 | + |
| 132 | +(defvar ivy-last) |
| 133 | +(declare-function ivy-state-window "ext:ivy") |
| 134 | + |
| 135 | +;;;###autoload |
| 136 | +(defun lispy-occur () |
| 137 | + "Select a line within current top level sexp. |
| 138 | +See `lispy-occur-backend' for the selection back end." |
| 139 | + (interactive) |
| 140 | + (swiper--init) |
| 141 | + (cond ((eq lispy-occur-backend 'helm) |
| 142 | + (require 'helm) |
| 143 | + (add-hook 'helm-move-selection-after-hook |
| 144 | + #'lispy--occur-update-input-helm) |
| 145 | + (add-hook 'helm-update-hook |
| 146 | + #'lispy--occur-update-input-helm) |
| 147 | + (unwind-protect |
| 148 | + (helm :sources |
| 149 | + `((name . "this defun") |
| 150 | + (candidates . ,(lispy--occur-candidates)) |
| 151 | + (action . lispy-occur-action-goto-paren) |
| 152 | + (match-strict . |
| 153 | + (lambda (x) |
| 154 | + (ignore-errors |
| 155 | + (string-match |
| 156 | + (ivy--regex helm-input) x))))) |
| 157 | + :preselect (lispy--occur-preselect) |
| 158 | + :buffer "*lispy-occur*") |
| 159 | + (swiper--cleanup) |
| 160 | + (remove-hook 'helm-move-selection-after-hook |
| 161 | + #'lispy--occur-update-input-helm) |
| 162 | + (remove-hook 'helm-update-hook |
| 163 | + #'lispy--occur-update-input-helm))) |
| 164 | + ((eq lispy-occur-backend 'ivy) |
| 165 | + (unwind-protect |
| 166 | + (ivy-read "pattern: " |
| 167 | + (lispy--occur-candidates) |
| 168 | + :preselect (lispy--occur-preselect) |
| 169 | + :require-match t |
| 170 | + :update-fn (lambda () |
| 171 | + (lispy--occur-update-input |
| 172 | + ivy-text |
| 173 | + (ivy-state-current ivy-last))) |
| 174 | + :action #'lispy-occur-action-goto-paren |
| 175 | + :caller 'lispy-occur) |
| 176 | + (swiper--cleanup) |
| 177 | + (when (null ivy-exit) |
| 178 | + (goto-char swiper--opoint)))) |
| 179 | + (t |
| 180 | + (error "Bad `lispy-occur-backend': %S" lispy-occur-backend)))) |
| 181 | + |
| 182 | +(defun lispy--occur-update-input-helm () |
| 183 | + "Update selection for `lispy-occur' using `helm' back end." |
| 184 | + (lispy--occur-update-input |
| 185 | + helm-input |
| 186 | + (buffer-substring-no-properties |
| 187 | + (line-beginning-position) |
| 188 | + (line-end-position)))) |
| 189 | + |
| 190 | +(defun lispy--occur-update-input (input str) |
| 191 | + "Update selection for `ivy-occur'. |
| 192 | +INPUT is the current input text. |
| 193 | +STR is the full current candidate." |
| 194 | + (swiper--cleanup) |
| 195 | + (let ((re (ivy--regex input)) |
| 196 | + (num (if (string-match "^[0-9]+" str) |
| 197 | + (string-to-number (match-string 0 str)) |
| 198 | + 0))) |
| 199 | + (with-selected-window (ivy-state-window ivy-last) |
| 200 | + (goto-char lispy--occur-beg) |
| 201 | + (when (cl-plusp num) |
| 202 | + (forward-line num) |
| 203 | + (unless (<= (point) lispy--occur-end) |
| 204 | + (recenter))) |
| 205 | + (let ((ov (make-overlay (line-beginning-position) |
| 206 | + (1+ (line-end-position))))) |
| 207 | + (overlay-put ov 'face 'swiper-line-face) |
| 208 | + (overlay-put ov 'window (ivy-state-window ivy-last)) |
| 209 | + (push ov swiper--overlays)) |
| 210 | + (re-search-forward re (line-end-position) t) |
| 211 | + (swiper--add-overlays |
| 212 | + re |
| 213 | + lispy--occur-beg |
| 214 | + lispy--occur-end)))) |
| 215 | + |
| 216 | +;;; lispy-occur.el ends here |
0 commit comments