-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathfzfa-project.el
More file actions
167 lines (148 loc) · 6.35 KB
/
Copy pathfzfa-project.el
File metadata and controls
167 lines (148 loc) · 6.35 KB
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
;;; fzfa-project.el --- Project.el integration for `fzfa' -*- lexical-binding: t; -*-
;; Copyright (C) 2026 James Nguyen
;; Author: James Nguyen <james@jojojames.com>
;; Version: 1.0
;; Package-Requires: ((emacs "29.1"))
;; Homepage: https://github.com/jojojames/fzfa
;; Assisted-by: Claude:claude-opus-4-7
;; SPDX-License-Identifier: GPL-3.0-or-later
;;; Commentary:
;; project.el integration for fzfa: project-scoped file, directory,
;; buffer, recentf, and project-switch commands.
;;
;; Loaded automatically when `project' is in `fzfa-extensions' and
;; `fzfa-setup' has been called. No setup function is registered —
;; the commands are usable immediately.
;;
;; Commands:
;; `fzfa-project-find-file' Find a file in the current project
;; `fzfa-project-find-dir' Open a directory in the current project
;; `fzfa-project-buffer' Switch to a buffer of the current project
;; `fzfa-project-recentf' Open a recent file from the current project
;; `fzfa-project-switch-project' Switch to a known project root
;;
;; File-search and grep commands shipped in sibling extensions
;; (e.g. `fzfa-rg', `fzfa-fd', `fzfa-find', `fzfa-git-ls-files') already
;; honor the project root via `fzfa-project-backend' / `fzfa--default-dir'.
;; This extension covers operations that are project-aware in a way the
;; shell-driven commands are not: candidate sets derived from
;; `project-files', `project-buffers', and `project-known-project-roots'.
;;; Code:
(require 'fzfa)
(require 'cl-lib)
(declare-function project-current "project")
(declare-function project-root "project")
(declare-function project-files "project")
(declare-function project-buffers "project")
(declare-function project-known-project-roots "project")
(declare-function project-switch-project "project")
(defvar recentf-list)
(defun fzfa-project--current ()
"Return the current project, or signal a user-error."
(or (project-current)
(user-error "Not in a project")))
(defun fzfa-project--label (root)
"Return a short label for project ROOT for use in a prompt."
(file-name-nondirectory (directory-file-name root)))
;;;###autoload
(defun fzfa-project-find-file ()
"Find a file in the current project.
Candidate set comes from `project-files', so membership respects
`project-vc-*' and `project-find-functions'."
(interactive)
(require 'project)
(let* ((pr (fzfa-project--current))
(root (expand-file-name (project-root pr)))
(files (project-files pr)))
(unless files
(user-error "No files in current project"))
(when-let* ((sel (fzfa-completing-read
:candidates (mapcar
(lambda (f) (file-relative-name f root))
files)
:prompt (format "project file [%s]: "
(fzfa-project--label root))
:category 'fzfa-file)))
(fzfa-visit-file (expand-file-name sel root)))))
;;;###autoload
(defun fzfa-project-find-dir ()
"Open a directory contained in the current project, in Dired.
Candidates are the unique parent directories of `project-files', plus
the project root itself."
(interactive)
(let* ((pr (fzfa-project--current))
(root (expand-file-name (project-root pr)))
(seen (make-hash-table :test 'equal))
(dirs nil))
(dolist (f (project-files pr))
(let ((d (file-name-directory f)))
(unless (gethash d seen)
(puthash d t seen)
(push (file-relative-name d root) dirs))))
(unless (member "./" dirs) (push "./" dirs))
(when-let* ((sel (fzfa-completing-read
:candidates (sort dirs #'string<)
:prompt (format "project dir [%s]: "
(fzfa-project--label root))
:category 'fzfa-file)))
(dired (expand-file-name sel root)))))
;;;###autoload
(defun fzfa-project-buffer ()
"Switch to a buffer of the current project."
(interactive)
(require 'project)
(let* ((pr (fzfa-project--current))
(root (expand-file-name (project-root pr)))
(names (cl-loop for b in (project-buffers pr)
for name = (buffer-name b)
unless (or (minibufferp b)
(string-prefix-p " " name))
collect name)))
(unless names
(user-error "No buffers in current project"))
(when-let* ((sel (fzfa-completing-read
:candidates names
:prompt (format "project buffer [%s]: "
(fzfa-project--label root))
:category 'fzfa-buffer)))
(fzfa-with-visit (switch-to-buffer sel)))))
;;;###autoload
(defun fzfa-project-recentf ()
"Open a recently visited file under the current project.
Filters `recentf-list' to entries whose expanded path is under the
current project's root."
(interactive)
(require 'project)
(require 'recentf)
(recentf-mode 1)
(let* ((pr (fzfa-project--current))
(root (file-name-as-directory (expand-file-name (project-root pr))))
(files (cl-loop for f in recentf-list
for ef = (expand-file-name f)
when (string-prefix-p root ef)
collect (file-relative-name ef root))))
(unless files
(user-error "No recent files under %s" (abbreviate-file-name root)))
(when-let* ((sel (fzfa-completing-read
:candidates files
:prompt (format "project recentf [%s]: "
(fzfa-project--label root))
:category 'fzfa-file)))
(fzfa-visit-file (expand-file-name sel root)))))
;;;###autoload
(defun fzfa-project-switch-project ()
"Switch to a known project root via fzf.
After selection, dispatches through `project-switch-project' so the
user's `project-switch-commands' menu kicks in."
(interactive)
(require 'project)
(let ((roots (project-known-project-roots)))
(unless roots
(user-error "No known projects"))
(when-let* ((sel (fzfa-completing-read
:candidates (mapcar #'abbreviate-file-name roots)
:prompt "switch project: "
:category 'fzfa-file)))
(project-switch-project (expand-file-name sel)))))
(provide 'fzfa-project)
;;; fzfa-project.el ends here