nixos-dotfiles

nixos-dotfiles

https://git.tonybtw.com/nixos-dotfiles.git git://git.tonybtw.com/nixos-dotfiles.git
12,425 bytes raw
1
;;; telescope.el --- Telescope-like fuzzy finder -*- lexical-binding: t -*-
2
3
(require 'posframe)
4
5
(defgroup telescope nil
6
  "Telescope-like fuzzy finder."
7
  :group 'convenience)
8
9
(defcustom telescope-fd-command "fd -t f --color=never --hidden --exclude .git"
10
  "Command to list files."
11
  :type 'string
12
  :group 'telescope)
13
14
(defcustom telescope-results-width-pct 0.35
15
  "Width of results window as percentage of frame."
16
  :type 'float
17
  :group 'telescope)
18
19
(defcustom telescope-preview-width-pct 0.50
20
  "Width of preview window as percentage of frame."
21
  :type 'float
22
  :group 'telescope)
23
24
(defcustom telescope-height-pct 0.70
25
  "Height of telescope as percentage of frame."
26
  :type 'float
27
  :group 'telescope)
28
29
(defcustom telescope-gap 2
30
  "Gap between results and preview panes in characters."
31
  :type 'integer
32
  :group 'telescope)
33
34
(defcustom telescope-padding 2
35
  "Internal padding within panes in characters."
36
  :type 'integer
37
  :group 'telescope)
38
39
;; Internal state
40
(defvar telescope--input "")
41
(defvar telescope--files nil)
42
(defvar telescope--filtered nil)
43
(defvar telescope--selected 0)
44
(defvar telescope--base-dir nil)
45
(defvar telescope--results-buffer " *telescope-results*")
46
(defvar telescope--preview-buffer " *telescope-preview*")
47
48
(defun telescope--get-files (dir)
49
  "Get all files in DIR using fd."
50
  (let ((default-directory dir))
51
    (split-string
52
     (shell-command-to-string (concat telescope-fd-command " ."))
53
     "\n" t)))
54
55
(defun telescope--fzf-filter (files query)
56
  "Filter FILES with QUERY using fzf --filter."
57
  (if (or (null files) (string-empty-p query))
58
      (seq-take files 100)
59
    (with-temp-buffer
60
      (insert (string-join files "\n"))
61
      (call-process-region (point-min) (point-max) "fzf" t t nil "--filter" query)
62
      (split-string (buffer-string) "\n" t))))
63
64
(defun telescope--render-results ()
65
  "Render the results buffer."
66
  (with-current-buffer (get-buffer-create telescope--results-buffer)
67
    (let* ((inhibit-read-only t)
68
           (pad (make-string telescope-padding ?\s))
69
           (max-results (- telescope--height 5))  ; leave room for prompt/footer/padding
70
           (content-width (- telescope--results-width (* telescope-padding 2) 2))
71
           (separator (make-string (min content-width 80) ?─)))
72
      (erase-buffer)
73
      ;; Top padding
74
      (insert "\n")
75
      ;; Prompt
76
      (insert pad)
77
      (insert (propertize "> " 'face '(:foreground "#87afff" :weight bold)))
78
      (insert telescope--input)
79
      (insert (propertize "█" 'face 'cursor))
80
      (insert "\n")
81
      (insert pad)
82
      (insert (propertize separator 'face '(:foreground "#444444")))
83
      (insert "\n")
84
      ;; Results
85
      (if (null telescope--filtered)
86
          (progn
87
            (insert pad)
88
            (insert (propertize "  No matches\n" 'face '(:foreground "#666666" :slant italic))))
89
        (let ((idx 0))
90
          (dolist (file (seq-take telescope--filtered max-results))
91
            (let* ((max-len (- content-width 4))
92
                   (display (if (> (length file) max-len)
93
                                (concat "..." (substring file (- 3 max-len)))
94
                              file))
95
                   (selected (= idx telescope--selected))
96
                   (face (if selected
97
                             '(:background "#3a3a3a" :weight bold)
98
                           nil))
99
                   (prefix (if selected "→ " "  ")))
100
              (insert pad)
101
              (insert (propertize (concat prefix display "\n") 'face face)))
102
            (cl-incf idx))))
103
      ;; Footer
104
      (insert pad)
105
      (insert (propertize separator 'face '(:foreground "#444444")))
106
      (insert "\n")
107
      (insert pad)
108
      (insert (propertize (format "%d/%d"
109
                                  (min (1+ telescope--selected) (length telescope--filtered))
110
                                  (length telescope--filtered))
111
                          'face '(:foreground "#666666"))))))
112
113
(defun telescope--update-preview ()
114
  "Update preview buffer with selected file content."
115
  (with-current-buffer (get-buffer-create telescope--preview-buffer)
116
    (let ((inhibit-read-only t)
117
          (pad (make-string telescope-padding ?\s)))
118
      (erase-buffer)
119
      (if-let* ((selected (nth telescope--selected telescope--filtered))
120
                (path (expand-file-name selected telescope--base-dir))
121
                (_ (and (file-exists-p path)
122
                        (file-regular-p path)
123
                        (not (file-directory-p path)))))
124
          (condition-case nil
125
              (progn
126
                ;; Top padding
127
                (insert "\n")
128
                ;; File name header
129
                (insert pad)
130
                (insert (propertize (file-name-nondirectory path) 'face '(:foreground "#87afff" :weight bold)))
131
                (insert "\n")
132
                (insert pad)
133
                (insert (propertize (make-string (min 60 (- telescope--preview-width 4)) ?─) 'face '(:foreground "#444444")))
134
                (insert "\n")
135
                ;; Content with left padding
136
                (let ((start (point)))
137
                  (insert-file-contents path nil 0 10000)
138
                  (goto-char start)
139
                  ;; Add padding to each line
140
                  (while (not (eobp))
141
                    (insert pad)
142
                    (forward-line 1)))
143
                (goto-char (point-min))
144
                ;; Apply syntax highlighting
145
                (when-let ((mode (assoc-default path auto-mode-alist 'string-match)))
146
                  (delay-mode-hooks (funcall mode))
147
                  (font-lock-ensure)))
148
            (error
149
             (erase-buffer)
150
             (insert "\n" pad)
151
             (insert (propertize "Cannot preview file" 'face '(:foreground "#666666")))))
152
        (insert "\n" pad)
153
        (insert (propertize "No preview available" 'face '(:foreground "#666666")))))))
154
155
(defvar telescope--results-x 0)
156
(defvar telescope--results-y 0)
157
(defvar telescope--preview-x 0)
158
(defvar telescope--results-width 55)
159
(defvar telescope--preview-width 75)
160
(defvar telescope--height 25)
161
162
(defun telescope--calc-dimensions ()
163
  "Calculate frame dimensions based on percentages."
164
  (let ((frame-cols (frame-width))
165
        (frame-rows (frame-height)))
166
    (setq telescope--results-width (max 30 (floor (* frame-cols telescope-results-width-pct))))
167
    (setq telescope--preview-width (max 40 (floor (* frame-cols telescope-preview-width-pct))))
168
    (setq telescope--height (max 15 (floor (* frame-rows telescope-height-pct))))))
169
170
(defun telescope--calc-positions ()
171
  "Calculate frame positions."
172
  (telescope--calc-dimensions)
173
  (let* ((char-width (frame-char-width))
174
         (char-height (frame-char-height))
175
         (border-px 4)
176
         (gap-px (* telescope-gap char-width))
177
         (total-pixel-width (+ (* telescope--results-width char-width)
178
                               (* telescope--preview-width char-width)
179
                               (* 2 border-px)
180
                               gap-px))
181
         (total-pixel-height (* telescope--height char-height))
182
         (start-x (max 0 (/ (- (frame-pixel-width) total-pixel-width) 2)))
183
         (start-y (max 0 (/ (- (frame-pixel-height) total-pixel-height) 2))))
184
    (setq telescope--results-x start-x)
185
    (setq telescope--results-y start-y)
186
    (setq telescope--preview-x (+ start-x (* telescope--results-width char-width) border-px gap-px))))
187
188
(defun telescope--results-poshandler (_info)
189
  "Position handler for results frame."
190
  (cons telescope--results-x telescope--results-y))
191
192
(defun telescope--preview-poshandler (_info)
193
  "Position handler for preview frame."
194
  (cons telescope--preview-x telescope--results-y))
195
196
(defun telescope--show-frames ()
197
  "Display the telescope posframes."
198
  (telescope--calc-positions)
199
  ;; Results frame (left)
200
  (posframe-show telescope--results-buffer
201
                 :position (point-min)
202
                 :poshandler #'telescope--results-poshandler
203
                 :width telescope--results-width
204
                 :height telescope--height
205
                 :border-width 2
206
                 :border-color "#5f5fff"
207
                 :background-color "#1c1c1c"
208
                 :foreground-color "#d0d0d0"
209
                 :override-parameters '((cursor-type . nil)))
210
  ;; Preview frame (right)
211
  (posframe-show telescope--preview-buffer
212
                 :position (point-min)
213
                 :poshandler #'telescope--preview-poshandler
214
                 :width telescope--preview-width
215
                 :height telescope--height
216
                 :border-width 2
217
                 :border-color "#5f87af"
218
                 :background-color "#1c1c1c"
219
                 :foreground-color "#d0d0d0"
220
                 :override-parameters '((cursor-type . nil))))
221
222
(defun telescope--hide-frames ()
223
  "Hide telescope posframes."
224
  (posframe-hide telescope--results-buffer)
225
  (posframe-hide telescope--preview-buffer))
226
227
(defun telescope--refresh ()
228
  "Refresh filtered results and display."
229
  (setq telescope--filtered (telescope--fzf-filter telescope--files telescope--input))
230
  (setq telescope--selected (min telescope--selected
231
                                  (max 0 (1- (length telescope--filtered)))))
232
  (telescope--render-results)
233
  (telescope--update-preview)
234
  (telescope--show-frames))
235
236
(defun telescope--select-next ()
237
  "Select next item."
238
  (when telescope--filtered
239
    (let ((max-visible (- telescope--height 5)))
240
      (setq telescope--selected
241
            (min (1+ telescope--selected)
242
                 (1- (min (length telescope--filtered) max-visible)))))
243
    (telescope--render-results)
244
    (telescope--update-preview)
245
    (telescope--show-frames)))
246
247
(defun telescope--select-prev ()
248
  "Select previous item."
249
  (setq telescope--selected (max 0 (1- telescope--selected)))
250
  (telescope--render-results)
251
  (telescope--update-preview)
252
  (telescope--show-frames))
253
254
(defun telescope--backspace ()
255
  "Delete last character from input."
256
  (when (> (length telescope--input) 0)
257
    (setq telescope--input (substring telescope--input 0 -1))
258
    (telescope--refresh)))
259
260
(defun telescope--insert-char (char)
261
  "Insert CHAR into input."
262
  (setq telescope--input (concat telescope--input (char-to-string char)))
263
  (telescope--refresh))
264
265
;;;###autoload
266
(defun telescope-find-files (&optional dir)
267
  "Find files in DIR using telescope interface."
268
  (interactive)
269
  (let ((dir (expand-file-name (or dir default-directory))))
270
    (setq telescope--base-dir dir)
271
    (setq telescope--input "")
272
    (setq telescope--selected 0)
273
    (setq telescope--files (telescope--get-files dir))
274
    (setq telescope--filtered (seq-take telescope--files 100))
275
    (telescope--render-results)
276
    (telescope--update-preview)
277
    (telescope--show-frames)
278
    (let ((result nil))
279
      (unwind-protect
280
          (while (null result)
281
            (let ((key (read-key (propertize " " 'face '(:height 0.1)))))
282
              (cond
283
               ;; Quit
284
               ((memq key '(?\e ?\C-g ?\C-c))
285
                (setq result 'cancel))
286
               ;; Confirm selection
287
               ((memq key '(?\r ?\C-m))
288
                (if telescope--filtered
289
                    (setq result (expand-file-name
290
                                   (nth telescope--selected telescope--filtered)
291
                                   telescope--base-dir))
292
                  (setq result 'cancel)))
293
               ;; Navigation
294
               ((or (memq key '(?\C-n ?\C-j)) (equal key 'down))
295
                (telescope--select-next))
296
               ((or (memq key '(?\C-p ?\C-k)) (equal key 'up))
297
                (telescope--select-prev))
298
               ;; Scroll
299
               ((memq key '(?\C-d))
300
                (dotimes (_ 5) (telescope--select-next)))
301
               ((memq key '(?\C-u))
302
                (dotimes (_ 5) (telescope--select-prev)))
303
               ;; Delete
304
               ((memq key '(?\C-h ?\C-? 127 backspace))
305
                (telescope--backspace))
306
               ;; Clear input
307
               ((memq key '(?\C-w))
308
                (setq telescope--input "")
309
                (telescope--refresh))
310
               ;; Regular character input
311
               ((and (characterp key) (>= key 32) (<= key 126))
312
                (telescope--insert-char key)))))
313
        ;; Cleanup
314
        (telescope--hide-frames))
315
      ;; Handle result
316
      (unless (eq result 'cancel)
317
        (find-file result)))))
318
319
(provide 'telescope)
320
;;; telescope.el ends here