aboutsummaryrefslogtreecommitdiff
path: root/bard-elisp/bard-writing.el
blob: 0f18c46572bc0159aef54ee59cdb4da18bbdface (plain)
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
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
(require 'consult)
(require 'beframe)
(require 'calendar)
(require 'org-roam-node)
(require 'denote)

(defvar bard/consult--source-notes
  `(:name     "Note Buffers"
    :narrow   ?n
    :category buffer
    :face     consult-buffer
    :history  buffer-name-history
    :items    ,(lambda ()
                 (mapcar #'buffer-name
                         (seq-filter
                          (lambda (buf)
                            (string-prefix-p "[Note]" (buffer-name buf)))
                          (beframe-buffer-list))))
    :action   ,#'switch-to-buffer
    :state    ,#'consult--buffer-state)
  "Consult source for note buffers (limited to beframe buffers).")


(defun bard/find-notes-file ()
  (interactive)
  (consult-find "~/Notes/denote"))

(defun bard/search-notes-directory ()
  (interactive)
  (consult-grep "~/Notes/denote"))

(defun bard/consult-buffer-notes ()
  "Show `consult-buffer` limited to buffers starting with [Note]."
  (interactive)
  (consult-buffer '(bard/consult--source-notes)))

(defun bard/ibuffer-notes ()
  "Open `ibuffer` limited to buffers starting with [Note]."
  (interactive)
  (ibuffer nil "*Ibuffer-Notes*"
           '((name . "^\\[Note\\]"))))

(defun bard/denote-insert-id-at-top ()
  "Insert or replace a top-level :ID: property at the very top of the current file."
  (interactive)
  (org-with-wide-buffer
   (goto-char (point-min))
   ;; If file already starts with a :PROPERTIES: drawer, remove it
   (when (looking-at ":PROPERTIES:")
     (let ((end (save-excursion
                  (re-search-forward ":END:" nil t))))
       (when end
         (delete-region (point-min) (min (point-max) (1+ end)))))))
  ;; Insert fresh ID drawer at very top
  (goto-char (point-min))
  (let ((id (org-id-new)))
    (insert (format ":PROPERTIES:\n:ID:       %s\n:END:\n" id)))
  (save-buffer))

(defun bard/denote-maybe-insert-id ()
  "Insert a top-level :ID: unless this is a denote journal file."
  (interactive)
  (let ((file (buffer-file-name)))
    (unless (and file
                 (boundp 'denote-journal-directory)
                 (string-prefix-p (expand-file-name denote-journal-directory)
                                  (expand-file-name file)))
      (bard/denote-insert-id-at-top))))

(defun bard/insert-or-create-node ()
  "Insert an Org-roam link if the note exists; otherwise create via Denote."
  (interactive)
  ;; Prompt for an existing node, but do not auto-create
  (let* ((node (org-roam-node-read))
         (title (org-roam-node-title node)))
    (if node
        ;; Node exists: insert Org-roam link at point
        (let ((id (org-roam-node-id node)))
          (if id
              (insert (org-link-make-string
                           (concat "id:" id)
                           title))
            ;; Node does not exist (no id): create via Denote
            (bard/denote-insert-node title))))))

(defun bard/denote-insert-node (title)
  "Denote analogy for `org-roam-insert-node', takes TITLE as node title."
  (interactive)
  (let* ((keywords (denote-keywords-prompt))
         (file (denote title keywords nil nil nil nil nil nil)))

    (when-let ((buf (find-buffer-visiting file)))
      (with-current-buffer buf
        (save-buffer)
        (kill-buffer buf)))

    ;; extract uuid from file that is autogenerated by `bard/denote-insert-id-at-top'
    (let ((uuid
           (with-temp-buffer
             (insert-file-contents file)
             (goto-char (point-min))
             (when (re-search-forward "^:ID:\\s-+\\([[:alnum:]-]+\\)" nil t)
               (match-string 1)))))
      (unless uuid
        (error "No :ID: found in Denote file %s" file))

      ;; Insert link at point
      (insert (format "[[id:%s][%s]]" uuid title)))))

(defun denote-sequence-region ()
  "Call `denote-sequence' and insert therein the text of the active region.

Note that, currently, `denote-save-buffers' and
`denote-kill-buffers' are NOT respected.  The buffer is not
saved or killed at the end of `denote-sequence-region'."
  (declare (interactive-only t))
  (interactive)
  (if-let* (((region-active-p))
            ;; We capture the text early, otherwise it will be empty
            ;; the moment `insert' is called.
            (text (buffer-substring-no-properties (region-beginning) (region-end))))
      (progn
        (let ((denote-ignore-region-in-denote-command t)
              ;; FIXME: Find a way to insert the region before the buffer is
              ;; saved/killed by the creation command.
              (denote-save-buffers nil)
              (denote-kill-buffers nil))
          (call-interactively 'denote-sequence))
        (push-mark (point))
        (insert text)
        (run-hook-with-args 'denote-region-after-new-note-functions (mark) (point)))
    (call-interactively 'denote-sequence)))

(defvar bard/class-dirs
  '(("ANTH 204" . "~/Documents/dox/Uni/FALL2025-ANTH 204/")
    ("CHEM 201" . "~/Documents/dox/Uni/FALL2025-CHEM 201/")
    ("CHEM 207" . "~/Documents/dox/Uni/FALL2025-CHEM 207/")
    ("ENGL 105" . "~/Documents/dox/Uni/FALL2025-ENGL 105/")
    ("ENGR 101" . "~/Documents/dox/Uni/FALL2025-ENGR 101/")
    ("ENGR 110" . "~/Documents/dox/Uni/FALL2025-ENGR 110/"))
  "Mapping of class names to their document directories.")

(defvar bard/uni-notes-file "~/Notes/denote/uni.org"
  "Path to the main university org file.")

(defun bard/jump-to-class (class)
  "Jump to CLASS heading in `bard/uni-notes-file` and open its dir in dired."
  (interactive
   (list (completing-read "Class: " (mapcar #'car bard/class-dirs))))
  (let* ((dir (cdr (assoc class bard/class-dirs))))
    ;; split windows
    (delete-other-windows)
    (let ((notes-window (selected-window))
          (dired-window (split-window-right)))
      ;; open notes file and jump to heading
      (with-selected-window notes-window
        (find-file bard/uni-notes-file)
        (widen)
        (goto-char (point-min))
        (message class)
        (search-forward class nil nil))
      ;; open dired in right window
      (with-selected-window dired-window
        (dired dir)))))

;; (defun bard/jump-to-class-new-frame (class)
;;   "Open CLASS notes and dir in a new frame titled after CLASS."
;;   (interactive
;;    (list (completing-read "Class: " (mapcar #'car bard/class-dirs))))
;;   (let* ((dir (cdr (assoc class bard/class-dirs)))
;;          ;; make a new frame with title
;;          (frame (make-frame `((name . ,class)))))
;;     (select-frame-set-input-focus frame)
;;     (delete-other-windows)
;;     (let ((notes-window (selected-window))
;;           (dired-window (split-window-right)))
;;       ;; open notes file and jump to heading
;;       (with-selected-window notes-window
;;         (find-file bard/uni-notes-file)
;;         (widen)
;;         (goto-char (point-min))
;;         (search-forward class nil nil))
;;       ;; open dired in right window
;;       (with-selected-window dired-window
;;         (dired dir)))))

(defun bard/jump-to-class-new-frame (class)
  "Open CLASS notes and dir in a new frame titled after CLASS, even with beframe."
  (interactive
   (list (completing-read "Class: " (mapcar #'car bard/class-dirs))))
  (let* ((dir (cdr (assoc class bard/class-dirs)))
         (frame (make-frame `((frame-title-format . ,class)))))
    (select-frame-set-input-focus frame)
    (delete-other-windows)
    (let ((notes-window (selected-window))
          (dired-window (split-window-right)))
      (with-selected-window notes-window
        (find-file bard/uni-notes-file)
        (widen)
        (goto-char (point-min))
        (search-forward class nil nil))
      (with-selected-window dired-window
        (dired dir)
        (beframe-rename-current-frame)))))

;; Optional: bind to a key
(global-set-key (kbd "C-c u") #'bard/jump-to-class)
(global-set-key (kbd "C-c U") #'bard/jump-to-class-new-frame)

(defun bard/denote-todo-template ()
  "Return string for daily tasks heading in `denote-journal' entries"
  (format "* Tasks for %s\n\n* Notes for today"
          (format-time-string "%Y-%m-%d (%a)")))

(provide 'bard-writing)