Manage and interact with projects.
;;; wal-workspace.el --- Workspace. -*- lexical-binding: t -*-
;;; Commentary:
;;
;; Provide workspace packages.
;;; Code:
(eval-when-compile
(require 'cl-extra)
(require 'wal-useful nil t)
(require 'wal-key-bindings nil t))
(declare-function consult-buffer "ext:consult.el")
(declare-function magit-status "ext:magit.el")
(declare-function project--value-in-dir "ext:project.el")
(declare-function project-find-file-in "ext:project.el")
(declare-function project-name "ext:project.el")
(declare-function project-root "ext:project.el")
(declare-function wal-org-capture--find-project-tasks-heading "wal-org.el")
(defgroup wal-workspace nil
"Change settings used for workspace packages."
:group 'wal
:tag "Workspace")
Library project
has by now become mature enough to replace the otherwise excellent projectile
.
The concept of parent projects is established for mono- and projects that comprise several sub-projects (the action to switch to it is bound to user-prefixed M-y
).
(defvar-local wal-project-parent-project nil
"The current project's parent project.")
(put 'wal-project-parent-project 'safe-local-variable #'stringp)
(defun wal-project-switch-to-parent-project ()
"Switch to the current project's parent project."
(interactive)
(if-let ((parent (wal-project-local-value 'wal-project-parent-project)))
(let* ((child-root (project-root (project-current)))
(expanded (expand-file-name parent child-root)))
(project-switch-project expanded))
(let ((name (if (project-current) (project-name (project-current)) "unknown")))
(user-error "Project '%s' has no parent project (controlled by `wal-project-parent-project')" name))))
(defvar wal-project-find--directory nil)
(defun wal-project-find-relative-path-instead (_type target)
"Find TARGET in a sub-directory.
This is used by `wal-project-find-in-here'."
(when wal-project-find--directory
(cons 'file (expand-file-name target wal-project-find--directory))))
(defun wal-project-find-in-here (&optional include-all)
"Find a project file in the current directory.
If INCLUDE-ALL is t, don't ignore otherwise ignored fils."
(interactive "P")
(when-let ((project (project-current nil))
(wal-project-find--directory default-directory))
(project-find-file-in nil (list default-directory) project include-all)))
(defun wal-project-find-dir-locals ()
"Find the dir-locals file."
(interactive)
(when-let ((project (project-current))
(root (project-root project)))
(find-file (expand-file-name dir-locals-file root))))
Two switch commands (selected when switching projects) are added: one
to find a buffer with consult
, another to show magit
status.
(defun wal-project-consult-buffer ()
"Find an open project buffer using `consult-buffer'."
(interactive)
(defvar consult-project-buffer-sources)
(let ((confirm-nonexistent-file-or-buffer t))
(consult-buffer consult-project-buffer-sources)))
(defun wal-project-magit-status ()
"Show `magit-status' for the current project."
(interactive)
(if-let* ((current (project-current t))
(root (project-root current))
(is-vc (cadr current)))
(magit-status root)
(message "Project at '%s' is not version-controlled" root)))
(defun wal-project-switch-to-tasks ()
"Switch to the current project's tasks."
(interactive)
(when-let* ((marker (wal-org-capture--find-project-tasks-heading))
(buffer (marker-buffer marker)))
(switch-to-buffer buffer)))
These are functions used in other packages.
(defun wal-project-buffer-root (buffer)
"Get the project root for BUFFER."
(with-current-buffer buffer
(when-let* ((dir (cond
(buffer-file-name
(file-name-directory buffer-file-name))
(dired-directory dired-directory)
(t nil)))
(project (project-current nil dir)))
(project-root project))))
(defun wal-project-local-value (symbol &optional project)
"Get the project-local value of SYMBOL.
Optionally the PROJECT may be passed directly."
(and-let* (((boundp symbol))
(project (or project (project-current)))
(root (project-root project)))
(project--value-in-dir symbol root)))
Root markers (how projects are found) and vc
ignores are extended.
(use-package project
:config
;; Allow setting custom names.
(put 'project-vc-name 'safe-local-variable #'stringp)
(advice-add
'embark--project-file-full-path :before-until
#'wal-project-find-relative-path-instead)
:custom
(project-vc-extra-root-markers '("pom.xml"
"package.json"
"project.godot"
"pyproject.toml"
".project-marker"
"build.zig"
"deno.json"))
(project-switch-commands '((project-find-file "Find file" ?f)
(project-find-dir "Find dir" ?d)
(wal-project-switch-to-tasks "Find tasks" ?t)
(wal-project-magit-status "Magit" ?m)
(wal-project-consult-buffer "Consult buffer" ?j)
(wal-rg-project-literal "Find rg" ?n)
(project-dired "Find root dir" ?r)
(ship-mate-select-command "Run command" ?c)))
(project-vc-ignores '("node_modules/"
"build/"
"android/"
"*.lock"
"bundle.js"
"*.min.js"
"*.js.map"
".ccls-cache/"
"coverage/"
".gradle"
".zig-cache"
"zig-out"))
:bind
(:map project-prefix-map
("m" . project-remember-projects-under)
("l" . wal-project-find-dir-locals))
:wal-bind
(("h" . project-find-file)
("M-h" . wal-project-find-in-here)
("M-'" . wal-project-switch-to-parent-project)))
(provide 'wal-workspace)
;;; wal-workspace.el ends here