Skip to content

Latest commit

 

History

History
233 lines (174 loc) · 6.91 KB

wal-workspace.org

File metadata and controls

233 lines (174 loc) · 6.91 KB

Workspace

Manage and interact with projects.

Header

;;; 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")

Packages

project

Library project has by now become mature enough to replace the otherwise excellent projectile.

Find commands

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))))

Additional switch commands

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)))

Utility functions

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)))

Package configuration

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)))

Footer

(provide 'wal-workspace)

;;; wal-workspace.el ends here