Skip to content

Commit 6a95fb9

Browse files
committed
Triage and resolve open project issues
1 parent 6f03195 commit 6a95fb9

File tree

7 files changed

+193
-6
lines changed

7 files changed

+193
-6
lines changed

README.org

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,18 @@ You need to set the backend for `org-project-capture` using its class-based stru
3535
(make-instance 'org-project-capture-project-backend))
3636
#+END_SRC
3737

38-
- If using `projectile` via `org-projectile`:
38+
- If using `projectile`:
3939

4040
#+BEGIN_SRC emacs-lisp
41+
(require 'org-projectile)
4142
(setq org-project-capture-default-backend
4243
(make-instance 'org-project-capture-projectile-backend))
4344
#+END_SRC
4445

46+
`org-projectile` remains available as a compatibility layer, but the
47+
preferred setup is to configure `org-project-capture` directly and use the
48+
projectile backend.
49+
4550
*** File Configuration
4651

4752
Specify the location for storing project-specific TODOs:
@@ -56,8 +61,14 @@ Establish a keybinding for easy capturing:
5661

5762
#+BEGIN_SRC emacs-lisp
5863
(global-set-key (kbd "C-c n p") 'org-project-capture-project-todo-completing-read)
64+
(global-set-key (kbd "C-c n t") 'org-project-capture-capture-for-current-project)
65+
(global-set-key (kbd "C-c n a") 'org-project-capture-agenda-for-current-project)
5966
#+END_SRC
6067

68+
`org-project-capture-project-todo-completing-read` prompts for a project.
69+
`org-project-capture-capture-for-current-project` uses the current buffer's
70+
project automatically.
71+
6172
*** Capture Strategy
6273

6374
Determine if TODOs should be in a single file or across individual projects:
@@ -92,6 +103,41 @@ For those utilizing `use-package`, here's a streamlined setup:
92103
(org-project-capture-single-file))
93104
#+END_SRC
94105

106+
** `org-capture` Integration
107+
108+
If you prefer to drive everything through `org-capture`, add a project-aware
109+
template entry:
110+
111+
#+BEGIN_SRC emacs-lisp
112+
(setq org-capture-templates
113+
(append org-capture-templates
114+
(list
115+
(org-project-capture-project-todo-entry
116+
:capture-character "p"
117+
:capture-heading "Project TODO"))))
118+
#+END_SRC
119+
120+
That template uses the current buffer's project automatically. If you want a
121+
custom template body, pass `:capture-template`:
122+
123+
#+BEGIN_SRC emacs-lisp
124+
(org-project-capture-project-todo-entry
125+
:capture-character "p"
126+
:capture-heading "Project TODO"
127+
:capture-template "* NEXT %?\n%U\n")
128+
#+END_SRC
129+
130+
The command-style entry points also accept `:capture-template` directly:
131+
132+
#+BEGIN_SRC emacs-lisp
133+
(global-set-key
134+
(kbd "C-c n T")
135+
(lambda ()
136+
(interactive)
137+
(org-project-capture-capture-for-current-project
138+
:capture-template "* TODO %?\nDEADLINE: %^T\n")))
139+
#+END_SRC
140+
95141
** Customization
96142

97143
There are numerous customization options for `org-project-capture`:
@@ -100,4 +146,9 @@ There are numerous customization options for `org-project-capture`:
100146
M-x customize-group RET org-project-capture RET
101147
#+END_SRC
102148

149+
Project categories come from the active backend's project names. For
150+
`projectile`, that means `projectile-project-name-function` and related
151+
projectile naming overrides are respected. This is the recommended way to
152+
avoid collisions when two different projects would otherwise share the same
153+
basename.
103154

org-project-capture-backend.el

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@
4646
((_backend org-project-capture-backend) _filepath)
4747
"Return the project root containing FILEPATH.")
4848

49+
(cl-defmethod org-project-capture-project-name-for-path
50+
((_backend org-project-capture-backend) project-path)
51+
"Return the backend-specific project name for PROJECT-PATH."
52+
(org-project-capture-category-from-project-root project-path))
53+
4954
(cl-defmethod org-project-capture-current-project
5055
((_backend org-project-capture-backend))
5156
"Return the current project name.")
@@ -55,19 +60,21 @@
5560
"Build an alist mapping category names to project paths for BACKEND."
5661
(mapcar
5762
(lambda (path)
58-
(cons (org-project-capture-category-from-project-root path) path))
63+
(cons (org-project-capture-project-name-for-path backend path) path))
5964
(org-project-capture-get-all-project-paths backend)))
6065

6166
(cl-defmethod org-project-capture-category-from-file
6267
((backend org-project-capture-backend) filepath)
6368
"Get the category for the project containing FILEPATH using BACKEND."
64-
(org-project-capture-category-from-project-root
65-
(org-project-capture-project-root-of-filepath backend filepath)))
69+
(when-let ((project-root
70+
(org-project-capture-project-root-of-filepath backend filepath)))
71+
(org-project-capture-project-name-for-path backend project-root)))
6672

6773
(cl-defmethod org-project-capture-get-all-categories
6874
((backend org-project-capture-backend))
6975
"Return a list of all category names for BACKEND."
70-
(mapcar 'org-project-capture-category-from-project-root
76+
(mapcar (lambda (path)
77+
(org-project-capture-project-name-for-path backend path))
7178
(org-project-capture-get-all-project-paths backend)))
7279

7380

@@ -80,6 +87,13 @@
8087
"Return all known project paths using `project-known-project-roots'."
8188
(project-known-project-roots))
8289

90+
(cl-defmethod org-project-capture-project-name-for-path
91+
((_ org-project-capture-project-backend) project-path)
92+
"Return the `project.el' name for PROJECT-PATH."
93+
(or (when-let ((project (project-current nil project-path)))
94+
(project-name project))
95+
(org-project-capture-category-from-project-root project-path)))
96+
8397
(cl-defmethod org-project-capture-project-root-of-filepath
8498
((_ org-project-capture-project-backend) filepath)
8599
"Return the project root for FILEPATH using `project-current'."

org-project-capture.el

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
(require 'dash)
3434
(require 'eieio)
3535
(require 'org)
36+
(require 'org-agenda)
3637
(require 'org-category-capture)
3738
(require 'org-project-capture-backend)
3839
(require 's)
@@ -425,6 +426,35 @@ ARGS are passed to `completing-read'."
425426
(apply 'completing-read prompt (occ-get-categories org-project-capture-strategy)
426427
args))
427428

429+
(defun org-project-capture-current-project-context ()
430+
"Build an `occ-context' for the current project."
431+
(let* ((backend (org-project-capture-strategy-get-backend
432+
org-project-capture-strategy))
433+
(project-name (org-project-capture-current-project backend)))
434+
(unless project-name
435+
(user-error "Current buffer is not in a recognized project"))
436+
(make-instance 'occ-context
437+
:category project-name
438+
:template org-project-capture-capture-template
439+
:strategy org-project-capture-strategy
440+
:options nil)))
441+
442+
(defun org-project-capture-current-project-agenda-settings ()
443+
"Return agenda settings for the current project.
444+
The return value is a list of the form (PROJECT-NAME CAPTURE-FILE MATCHER)."
445+
(let* ((context (org-project-capture-current-project-context))
446+
(category (oref context category))
447+
(strategy (if (object-of-class-p org-project-capture-strategy
448+
'org-project-capture-combine-strategies)
449+
(org-project-capture-select-strategy-from-context
450+
org-project-capture-strategy context)
451+
org-project-capture-strategy))
452+
(capture-file (occ-get-capture-file strategy category))
453+
(matcher (when (object-of-class-p strategy
454+
'org-project-capture-single-file-strategy)
455+
(format "CATEGORY=%S" category))))
456+
(list category capture-file matcher)))
457+
428458
;;;###autoload
429459
(defun org-project-capture-goto-location-for-project (project)
430460
"Goto the location at which TODOs for PROJECT are stored."
@@ -494,5 +524,23 @@ were part of the capture template definition."
494524
(error (format "%s is not a recognized project."
495525
project-name)))))
496526

527+
;;;###autoload
528+
(defun org-project-capture-agenda-for-current-project (&optional arg)
529+
"Show an agenda view restricted to the current project.
530+
531+
With prefix ARG, pass it through to `org-todo-list' when the current
532+
project uses per-project storage."
533+
(interactive "P")
534+
(pcase-let ((`(,project-name ,capture-file ,matcher)
535+
(org-project-capture-current-project-agenda-settings)))
536+
(unless (file-exists-p capture-file)
537+
(user-error "No TODO file exists for current project: %s" project-name))
538+
(let ((org-agenda-files (list capture-file))
539+
(org-agenda-overriding-header
540+
(format "Project TODOs: %s" project-name)))
541+
(if matcher
542+
(org-tags-view t matcher)
543+
(org-todo-list arg)))))
544+
497545
(provide 'org-project-capture)
498546
;;; org-project-capture.el ends here

org-projectile-helm.el

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
;; Keywords: org projectile todo helm outlines
77
;; URL: https://github.com/IvanMalison/org-projectile
88
;; Version: 0.0.0
9-
;; Package-Requires: ((org-projectile "1.0.0") (helm "2.3.1") (emacs "25"))
9+
;; Package-Requires: ((org-projectile "1.0.0") (helm "2.3.1") (helm-org "1.0") (emacs "25"))
1010

1111
;; This program is free software; you can redistribute it and/or modify
1212
;; it under the terms of the GNU General Public License as published by

org-projectile.el

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,14 @@
4848
"Return all known project paths from projectile."
4949
projectile-known-projects)
5050

51+
(cl-defmethod org-project-capture-project-name-for-path
52+
((_ org-project-capture-projectile-backend) project-path)
53+
"Return the projectile project name for PROJECT-PATH."
54+
(let ((project-name (projectile-project-name project-path)))
55+
(if (string-equal project-name "-")
56+
(org-project-capture-category-from-project-root project-path)
57+
project-name)))
58+
5159
(cl-defmethod org-project-capture-project-root-of-filepath
5260
((_ org-project-capture-projectile-backend) filepath)
5361
"Return the project root for FILEPATH using projectile."

test/org-project-capture-backend-test.el

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,22 @@
8989
(should (assoc "beta" result))
9090
(should (assoc "gamma" result))))))
9191

92+
(ert-deftest test-build-category-to-project-path-prefers-project-name ()
93+
"Test building category map uses `project-name' when available."
94+
(let ((backend (make-instance 'org-project-capture-project-backend)))
95+
(cl-letf (((symbol-function 'project-known-project-roots)
96+
(lambda () '("/path/to/alpha")))
97+
((symbol-function 'project-current)
98+
(lambda (_maybe-prompt dir)
99+
(when (string-prefix-p "/path/to/alpha" dir)
100+
'fake-project)))
101+
((symbol-function 'project-name)
102+
(lambda (project)
103+
(should (eq project 'fake-project))
104+
"renamed-alpha")))
105+
(let ((result (org-project-capture-build-category-to-project-path backend)))
106+
(should (equal result '(("renamed-alpha" . "/path/to/alpha"))))))))
107+
92108
;; Tests for category-from-file
93109

94110
(ert-deftest test-category-from-file ()

test/org-projectile-test.el

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,56 @@
159159
(should (eq (eieio-object-class selected)
160160
'org-project-capture-per-project-strategy)))))
161161

162+
(ert-deftest test-org-project-capture-projectile-backend-uses-custom-project-names ()
163+
"Test that projectile category maps follow `projectile-project-name-function'."
164+
(let* ((backend (make-instance 'org-project-capture-projectile-backend))
165+
(a-project (org-project-capture-test-file "a"))
166+
(b-project (org-project-capture-test-file "b"))
167+
(projectile-known-projects (list a-project b-project))
168+
(projectile-project-name-function
169+
(lambda (project-root)
170+
(format "named-%s"
171+
(file-name-nondirectory (directory-file-name project-root))))))
172+
(should (equal
173+
(org-project-capture-build-category-to-project-path backend)
174+
`(("named-a" . ,a-project)
175+
("named-b" . ,b-project))))
176+
(should (equal-as-sets
177+
(org-project-capture-get-all-categories backend)
178+
'("named-a" "named-b")))))
179+
180+
(ert-deftest test-org-project-capture-current-project-agenda-settings-single-file ()
181+
"Test agenda settings for single-file strategy."
182+
(let ((org-project-capture-strategy
183+
(make-instance 'org-project-capture-single-file-strategy)))
184+
(cl-letf (((symbol-function 'org-project-capture-strategy-get-backend)
185+
(lambda (_strategy) 'fake-backend))
186+
((symbol-function 'org-project-capture-current-project)
187+
(lambda (_backend) "proj1"))
188+
((symbol-function 'occ-get-capture-file)
189+
(lambda (_strategy category)
190+
(should (string-equal category "proj1"))
191+
"/tmp/projects.org")))
192+
(should (equal
193+
(org-project-capture-current-project-agenda-settings)
194+
'("proj1" "/tmp/projects.org" "CATEGORY=\"proj1\""))))))
195+
196+
(ert-deftest test-org-project-capture-current-project-agenda-settings-per-project ()
197+
"Test agenda settings for per-project strategy."
198+
(let ((org-project-capture-strategy
199+
(make-instance 'org-project-capture-per-project-strategy)))
200+
(cl-letf (((symbol-function 'org-project-capture-strategy-get-backend)
201+
(lambda (_strategy) 'fake-backend))
202+
((symbol-function 'org-project-capture-current-project)
203+
(lambda (_backend) "proj1"))
204+
((symbol-function 'occ-get-capture-file)
205+
(lambda (_strategy category)
206+
(should (string-equal category "proj1"))
207+
"/tmp/proj1/TODO.org")))
208+
(should (equal
209+
(org-project-capture-current-project-agenda-settings)
210+
'("proj1" "/tmp/proj1/TODO.org" nil))))))
211+
162212
;; Tests for heading text processing
163213

164214
(ert-deftest test-org-project-capture-get-category-from-heading-strips-links ()

0 commit comments

Comments
 (0)