Project management with org, projectile and skeletor
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

176 lines
6.9KB

  1. ;;; projectorg.el ---
  2. ;; Copyright (C) 2019 Maxime Wack
  3. ;; Author: Maxime Wack <maximewack@free.fr>
  4. ;; Version: 0.1
  5. ;; This file is not part of GNU Emacs.
  6. ;; This program is free software: you can redistribute it and/or modify
  7. ;; it under the terms of the GNU General Public License as published by
  8. ;; the Free Software Foundation, either version 3 of the License, or
  9. ;; (at your option) any later version.
  10. ;; This program is distributed in the hope that it will be useful,
  11. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. ;; GNU General Public License for more details.
  14. ;; You should have received a copy of the GNU General Public License
  15. ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
  16. ;;; Commentary:
  17. ;; Project management using org, projectile and skeletor
  18. ;;; Code:
  19. ;;;; Variables
  20. (defvar projectorg/projects-root ""
  21. "Where all projects are rooted.
  22. This is the directory where projects are stored.
  23. Can contain subdirectories.
  24. The projectorg/org-dir subdirectory contains the notes file for each project, as well as the general notes files and the projects list file.")
  25. (defvar projectorg/org-dir "org/"
  26. "Directory inside projectorg/projects-root containing the org files.
  27. Defaults to \"org\"
  28. Contains the general notes files, the projects list files and each project notes file.")
  29. (defvar projectorg/counsel-org-capture-templates-contexts nil)
  30. (defvar projectorg/counsel-org-capture-templates nil)
  31. ;;;; Functions
  32. (defun projectorg/project-name ()
  33. "Return the full project name.
  34. It is the result of substracting the projectorg/projects-root string from the beginning of the path of the project, as returned by projectile-project-p."
  35. (when (projectile-project-p)
  36. (let ((project-path (projectile-project-p)))
  37. (and
  38. (string= (substring project-path 0 (length projectorg/projects-root))
  39. projectorg/projects-root)
  40. (substring project-path (length projectorg/projects-root) -1)))))
  41. (defun projectorg/notes-file ()
  42. "Returns the notes-file location.
  43. It is an org file, with the same name as the project (including subdirectories), located in the *org* directory in projectorg/projects-root."
  44. (let ((project-name (projectorg/project-name)))
  45. (when project-name
  46. (concat projectorg/projects-root "org/" project-name ".org"))))
  47. (defun projectorg/go-to-inbox ()
  48. "Go to org-default-notes-file."
  49. (interactive)
  50. (find-file-other-window org-default-notes-file))
  51. (defun projectorg/go-to-notes ()
  52. "Go to the projects' notes file if it exists in the *org* directory, or go to default notes file."
  53. (interactive)
  54. (let ((notes-file (projectorg/notes-file)))
  55. (if (and (eq projectile-require-project-root 'prompt)
  56. (not (projectile-project-p)))
  57. (projectorg/go-to-inbox)
  58. (find-file-other-window notes-file))))
  59. (defun projectorg/add-to-project-list (FILENAME &optional WILDCARDS)
  60. "Add the currently visited project to the projectile list.
  61. And switch to a perspective for the project."
  62. (when (projectile-project-p FILENAME)
  63. (persp-switch (projectile-project-name (projectile-project-root FILENAME)))
  64. (projectile-add-known-project (projectile-project-root FILENAME))))
  65. (defun tree-alist-get-all (key list acc)
  66. (cond ((or (not list) (not (listp list))) nil)
  67. ((eq key (car list)) (cons (cadr list) acc))
  68. (t (append (tree-alist-get-all key (car list) acc) (tree-alist-get-all key (cdr list) acc)))))
  69. (defun persp-kill-all-buffers (persp-name)
  70. (mapc '(lambda (buffer) (when (get-buffer buffer) (kill-buffer buffer)))
  71. (tree-alist-get-all 'buffer (persp-window-conf (persp-get-by-name persp-name)) nil)))
  72. (defun projectorg/remove-from-project-list ()
  73. (interactive)
  74. (let ((proj (projectile-project-name)))
  75. (projectile-remove-current-project-from-known-projects)
  76. (projectile-kill-buffers)
  77. (persp-kill-all-buffers proj)
  78. (persp-kill proj)))
  79. (defun projectorg/counsel-org-capture (&optional from-buffer)
  80. "Capture into the current project.
  81. This command is a replacement for `org-capture' (or
  82. `counsel-org-capture') offering project-specific capture
  83. templates, in addition to the regular templates available from
  84. `org-capture'. These project templates, which are \"expanded\"
  85. relatively to the current project, are determined by the
  86. variables `projectorg/counsel-org-capture-templates' and
  87. `projectorg/counsel-org-capture-templates-contexts'. See the
  88. former variable in particular for details.
  89. Optional argument FROM-BUFFER specifies the buffer from which to
  90. capture."
  91. (interactive)
  92. (require 'org-capture)
  93. (require 'counsel-projectile)
  94. (setq counsel-projectile--org-capture-templates-backup org-capture-templates)
  95. (let* ((ivy--actions-list (copy-sequence ivy--actions-list))
  96. (root (ignore-errors (projectile-project-root)))
  97. (name (projectorg/project-name))
  98. (org-capture-templates-contexts
  99. (append (when root
  100. projectorg/counsel-org-capture-templates-contexts)
  101. org-capture-templates-contexts))
  102. (org-capture-templates
  103. (append
  104. (unless counsel-projectile-org-capture-templates-first-p
  105. org-capture-templates)
  106. (when root
  107. (cl-loop
  108. with replace-fun = `(lambda (string)
  109. (replace-regexp-in-string
  110. "\\${[^}]+}"
  111. (lambda (s)
  112. (pcase s
  113. ("${root}" ,root)
  114. ("${name}" ,name)))
  115. string))
  116. for template in projectorg/counsel-org-capture-templates
  117. collect (cl-loop
  118. for item in template
  119. if (= (cl-position item template) 1) ;; template's name
  120. collect (funcall replace-fun item)
  121. else if (= (cl-position item template) 3) ;; template's target
  122. collect (cl-loop
  123. for x in item
  124. if (stringp x)
  125. collect (funcall replace-fun x)
  126. else
  127. collect x)
  128. else
  129. collect item)))
  130. (when counsel-projectile-org-capture-templates-first-p
  131. org-capture-templates))))
  132. (ivy-add-actions
  133. 'counsel-org-capture
  134. counsel-projectile-org-capture-extra-actions)
  135. (with-current-buffer (or from-buffer (current-buffer))
  136. (counsel-org-capture))))
  137. (advice-add 'find-file :before 'projectorg/add-to-project-list) ; For every file opened, check if belongs to a project and add that project to the list of projects
  138. (advice-add 'find-file-other-window :before 'projectorg/add-to-project-list)
  139. (provide 'projectorg)
  140. ;;; projectorg.el ends here