diff --git a/doom/.config/doom/README.org b/doom/.config/doom/README.org index 772e423..b0762e0 100644 --- a/doom/.config/doom/README.org +++ b/doom/.config/doom/README.org @@ -571,227 +571,219 @@ fc --> UC3 [[/tmp/babel-pg1Nry/plantuml-SHtP1g.png][/tmp/babel-pg1Nry/plantuml-SHtP1g.png]] * Elgantt -#+begin_src emacs-lisp -;; (require 'cl-lib) -;; (require 'dash) -;; (unless (fboundp 'first) (defalias 'first #'car)) +Broken for now.. +#+begin_src emacs-lisp :tangle no +(require 'cl-lib) +(require 'dash) +(require 'elgantt) +(unless (fboundp 'first) (defalias 'first #'car)) -;; ;; Clear rules to ensure the new global color logic takes effect immediately -;; (setq elgantt--display-rules nil) +;; Clear rules to ensure the new global color logic takes effect immediately +(setq elgantt--display-rules nil) -;; (defface gortium/elgantt-weekend-face -;; '((t (:background "#32302f" :extend nil))) -;; "Gruvbox Dark0_Hard/Soft mix for subtle weekend stripes.") +(defface gortium/elgantt-weekend-face + '((t (:background "#32302f" :extend nil))) + "Gruvbox Dark0_Hard/Soft mix for subtle weekend stripes.") -;; (defun gortium/internal--month-to-num (name) -;; "Convert month string to number safely." -;; (let ((case-fold-search t)) -;; (cond ((string-match-p "Jan" name) 1) ((string-match-p "Feb" name) 2) -;; ((string-match-p "Mar" name) 3) ((string-match-p "Apr" name) 4) -;; ((string-match-p "May" name) 5) ((string-match-p "Jun" name) 6) -;; ((string-match-p "Jul" name) 7) ((string-match-p "Aug" name) 8) -;; ((string-match-p "Sep" name) 9) ((string-match-p "Oct" name) 10) -;; ((string-match-p "Nov" name) 11) ((string-match-p "Dec" name) 12) (t 1)))) +(defun gortium/internal--month-to-num (name) + "Convert month string to number safely." + (let ((case-fold-search t)) + (cond ((string-match-p "Jan" name) 1) ((string-match-p "Feb" name) 2) + ((string-match-p "Mar" name) 3) ((string-match-p "Apr" name) 4) + ((string-match-p "May" name) 5) ((string-match-p "Jun" name) 6) + ((string-match-p "Jul" name) 7) ((string-match-p "Aug" name) 8) + ((string-match-p "Sep" name) 9) ((string-match-p "Oct" name) 10) + ((string-match-p "Nov" name) 11) ((string-match-p "Dec" name) 12) (t 1)))) -;; (defun gortium/elgantt-draw-weekend-guides () -;; "Draw weekend guides for the ENTIRE buffer once to prevent scroll lag." -;; (interactive) -;; (when (derived-mode-p 'elgantt-mode) -;; (let* ((inhibit-modification-hooks t) -;; (header-line-1 (save-excursion -;; (goto-char (point-min)) -;; (buffer-substring-no-properties (line-beginning-position) (line-end-position)))) -;; (col-indices '()) -;; (search-pos 0)) +(defun gortium/elgantt-draw-weekend-guides () + "Draw weekend guides for the ENTIRE buffer once to prevent scroll lag." + (interactive) + (when (derived-mode-p 'elgantt-mode) + (let* ((inhibit-modification-hooks t) + (header-line-1 (save-excursion + (goto-char (point-min)) + (buffer-substring-no-properties (line-beginning-position) (line-end-position)))) + (col-indices '()) + (search-pos 0)) -;; (save-excursion -;; (save-restriction -;; (widen) -;; ;; 1. Clear ALL weekend overlays in the entire buffer -;; (remove-overlays (point-min) (point-max) 'gortium-weekend t) + (save-excursion + (save-restriction + (widen) + ;; 1. Clear ALL weekend overlays in the entire buffer + (remove-overlays (point-min) (point-max) 'gortium-weekend t) -;; ;; 2. Parse header once to find column indexes (Fast) -;; (while (string-match "|[[:space:]]*\\([[:alpha:]]+\\)[[:space:]]+\\([0-9]\\{4\\}\\)" header-line-1 search-pos) -;; (let* ((month-start-col (match-beginning 0)) -;; (month-name (match-string 1 header-line-1)) -;; (year (string-to-number (match-string 2 header-line-1))) -;; (month-num (gortium/internal--month-to-num month-name)) -;; (next-pipe (string-match "|" header-line-1 (1+ month-start-col))) -;; (month-width (if next-pipe (- next-pipe month-start-col 1) 31))) -;; (dotimes (d month-width) -;; (let* ((day (1+ d)) -;; (time (condition-case nil (encode-time 0 0 12 day month-num year) (error nil)))) -;; (when time -;; (let ((dow (nth 6 (decode-time time))) -;; (actual-col (+ month-start-col 1 d))) -;; (when (member dow '(0 6)) -;; (push actual-col col-indices)))))) -;; (setq search-pos (or next-pipe (length header-line-1))))) + ;; 2. Parse header once to find column indexes (Fast) + (while (string-match "|[[:space:]]*\\([[:alpha:]]+\\)[[:space:]]+\\([0-9]\\{4\\}\\)" header-line-1 search-pos) + (let* ((month-start-col (match-beginning 0)) + (month-name (match-string 1 header-line-1)) + (year (string-to-number (match-string 2 header-line-1))) + (month-num (gortium/internal--month-to-num month-name)) + (next-pipe (string-match "|" header-line-1 (1+ month-start-col))) + (month-width (if next-pipe (- next-pipe month-start-col 1) 31))) + (dotimes (d month-width) + (let* ((day (1+ d)) + (time (condition-case nil (encode-time 0 0 12 day month-num year) (error nil)))) + (when time + (let ((dow (nth 6 (decode-time time))) + (actual-col (+ month-start-col 1 d))) + (when (member dow '(0 6)) + (push actual-col col-indices)))))) + (setq search-pos (or next-pipe (length header-line-1))))) -;; ;; 3. Apply to the WHOLE buffer line by line -;; (unless (null col-indices) -;; (goto-char (point-min)) -;; (forward-line 2) ;; Skip headers -;; (while (not (eobp)) -;; (let ((line-end (line-end-position))) -;; (dolist (col col-indices) -;; (move-to-column col) -;; (let ((p (point))) -;; ;; Ensure we are still on the same line and at the correct column -;; (when (and (< p line-end) (= (current-column) col)) -;; (let ((ov (make-overlay p (1+ p)))) -;; (overlay-put ov 'face 'gortium/elgantt-weekend-face) -;; (overlay-put ov 'gortium-weekend t) -;; (overlay-put ov 'priority 100) -;; (overlay-put ov 'evaporate t)))))) -;; (forward-line 1))))) -;; (message "Weekend guides rendered for the whole buffer.")))) + ;; 3. Apply to the WHOLE buffer line by line + (unless (null col-indices) + (goto-char (point-min)) + (forward-line 2) ;; Skip headers + (while (not (eobp)) + (let ((line-end (line-end-position))) + (dolist (col col-indices) + (move-to-column col) + (let ((p (point))) + ;; Ensure we are still on the same line and at the correct column + (when (and (< p line-end) (= (current-column) col)) + (let ((ov (make-overlay p (1+ p)))) + (overlay-put ov 'face 'gortium/elgantt-weekend-face) + (overlay-put ov 'gortium-weekend t) + (overlay-put ov 'priority 100) + (overlay-put ov 'evaporate t)))))) + (forward-line 1))))) + (message "Weekend guides rendered for the whole buffer.")))) -;; ;; Run it only once when the buffer is loaded -;; (add-hook 'elgantt-mode-hook #'gortium/elgantt-draw-weekend-guides) +;; Run it only once when the buffer is loaded +(add-hook 'elgantt-mode-hook #'gortium/elgantt-draw-weekend-guides) -;; (use-package! elgantt -;; :commands (elgantt-open elgantt-open-current-org-file) -;; :config -;; ;; --- 1. Environment & UI --- -;; (add-hook 'elgantt-mode-hook -;; (lambda () -;; (setq-local org-phscroll-mode nil) -;; (setq-local image-roll-mode nil) -;; (setq truncate-lines t))) +(use-package! elgantt + :commands (elgantt-open elgantt-open-current-org-file) + :config + ;; --- 1. Environment & UI --- + (add-hook 'elgantt-mode-hook + (lambda () + (setq-local org-phscroll-mode nil) + (setq-local image-roll-mode nil) + (setq truncate-lines t))) -;; (setq elgantt-start-date "2026-01-01") + (setq elgantt-start-date "2026-01-01") -;; (setq elgantt-header-column-offset 40 -;; elgantt-header-type 'root -;; elgantt-show-header-depth t -;; elgantt-insert-blank-line-between-top-level-header t -;; elgantt-startup-folded nil -;; elgantt-draw-overarching-headers nil -;; elgantt-scroll-to-current-month-at-startup nil) + (setq elgantt-header-column-offset 40 + elgantt-header-type 'root + elgantt-show-header-depth t + elgantt-insert-blank-line-between-top-level-header t + elgantt-startup-folded nil + elgantt-draw-overarching-headers nil + elgantt-scroll-to-current-month-at-startup nil) -;; (setq elgantt-user-set-color-priority-counter 0) + (setq elgantt-user-set-color-priority-counter 0) -;; (elgantt-create-display-rule draw-active-timestamp-range -;; :parser ((override-color . ((when-let ((colors (org-entry-get (point) "ELGANTT-COLOR"))) -;; (split-string colors " ")))) -;; (range-dates . ((save-excursion -;; (org-back-to-heading t) -;; (let ((limit (save-excursion (outline-next-heading) (point)))) -;; (when (re-search-forward "<\\([^>]+\\)>--<\\([^>]+\\)>" limit t) -;; (list (match-string 1) (match-string 2)))))))) -;; :args (elgantt-org-id) -;; :body ((when (and elgantt-org-id range-dates) -;; (let* ((colors (or override-color '("#fabd2f" "#fe8019"))) -;; (s-str (substring (car range-dates) 0 10)) -;; (e-str (substring (cadr range-dates) 0 10)) -;; (p1 (save-excursion (when (elgantt--goto-date s-str) (point)))) -;; (p2 (save-excursion (when (elgantt--goto-date e-str) (point))))) -;; (when (and (numberp p1) (numberp p2)) -;; (elgantt--draw-gradient -;; (car colors) (cadr colors) -;; (truncate p1) (truncate p2) nil ;; <-- FIX: Removed (1+ ...) to stop overshoot -;; `(priority ,(setq elgantt-user-set-color-priority-counter -;; (1- elgantt-user-set-color-priority-counter)) -;; :elgantt-user-overlay ,elgantt-org-id)))))))) + ;; --- Rule 1: Active Timestamp Range --- + (elgantt-create-display-rule draw-active-timestamp-range + :parser ((override-color . ((when-let ((colors (org-entry-get (point) "ELGANTT-COLOR"))) + (split-string colors " ")))) + (range-dates . ((save-excursion + (org-back-to-heading t) + (let ((limit (save-excursion (outline-next-heading) (point)))) + (when (re-search-forward "<\\([^>]+\\)>--<\\([^>]+\\)>" limit t) + (list (match-string 1) (match-string 2)))))))) + :args (elgantt-org-id) + :body ((when (and elgantt-org-id range-dates) + (let* ((colors (or override-color '("#fabd2f" "#fe8019"))) + (s-str (substring (car range-dates) 0 10)) + (e-str (substring (cadr range-dates) 0 10)) + (p1 (save-excursion (when (elgantt--goto-date s-str) (point)))) + (p2 (save-excursion (when (elgantt--goto-date e-str) (point))))) + (when (and (numberp p1) (numberp p2)) + (elgantt--draw-gradient + (car colors) (cadr colors) + (truncate p1) (truncate p2) nil + `(priority ,(setq elgantt-user-set-color-priority-counter + (1- elgantt-user-set-color-priority-counter)) + :elgantt-user-overlay ,elgantt-org-id))))))) -;; ;; --- 2. Effort Rule (With Weekend Extension) --- -;; ;; (elgantt-create-display-rule draw-scheduled-to-effort-end -;; ;; :parser ((override-color . ((when-let ((colors (org-entry-get (point) "ELGANTT-COLOR"))) -;; ;; (split-string colors " ")))) -;; ;; (elgantt-effort . ((org-entry-get (point) "EFFORT"))) -;; ;; (wknd-days . ((when-let ((val (org-entry-get (point) "WEEKEND_DAYS"))) -;; ;; (string-to-number val))))) -;; ;; :args (elgantt-scheduled elgantt-effort elgantt-org-id) -;; ;; :body ((when (and elgantt-scheduled elgantt-effort) -;; ;; (let* ((start-ts (ts-parse elgantt-scheduled)) -;; ;; (raw-mins (org-duration-to-minutes elgantt-effort)) -;; ;; ;; Add the weekend jump days to the visual length -;; ;; (total-days (+ (ceiling (/ (float raw-mins) 1440.0)) (or wknd-days 0))) -;; ;; (p1 (save-excursion -;; ;; (elgantt--goto-date (ts-format "%Y-%m-%d" start-ts)) -;; ;; (point))) -;; ;; (colors (or override-color '("#8ec07c" "#458588")))) -;; ;; (when (numberp p1) -;; ;; (if (<= total-days 1) -;; ;; (elgantt--create-overlay (truncate p1) (1+ (truncate p1)) -;; ;; `(face (:background ,(car colors)) -;; ;; priority ,(setq elgantt-user-set-color-priority-counter -;; ;; (1- elgantt-user-set-color-priority-counter)) -;; ;; :elgantt-user-overlay ,elgantt-org-id)) -;; ;; ;; FIX 1: compute p2 by date (handles "|" separators) -;; ;; ;; FIX 2: keep original "Rule of 2" behavior to avoid +1 day overshoot -;; ;; (let* ((end-ts (ts-adjust 'day (- total-days 2) start-ts)) -;; ;; (p2 (save-excursion -;; ;; (elgantt--goto-date (ts-format "%Y-%m-%d" end-ts)) -;; ;; (point)))) -;; ;; (when (numberp p2) -;; ;; (elgantt--draw-gradient -;; ;; (car colors) (cadr colors) -;; ;; (truncate p1) (1+ (truncate p2)) nil -;; ;; `(priority ,(setq elgantt-user-set-color-priority-counter -;; ;; (1- elgantt-user-set-color-priority-counter)) -;; ;; :elgantt-user-overlay ,elgantt-org-id)))))))))) + ;; --- Rule 2: Effort Rule --- + (elgantt-create-display-rule draw-scheduled-to-effort-end + :parser ((override-color . ((when-let ((colors (org-entry-get (point) "ELGANTT-COLOR"))) + (split-string colors " ")))) + (elgantt-effort . ((org-entry-get (point) "EFFORT"))) + (wknd-days . ((when-let ((val (org-entry-get (point) "WEEKEND_DAYS"))) + (string-to-number val))))) + :args (elgantt-scheduled elgantt-effort elgantt-org-id) + :body ((when (and elgantt-scheduled elgantt-effort) + (let* ((start-ts (ts-parse elgantt-scheduled)) + (raw-mins (org-duration-to-minutes elgantt-effort)) + (total-days (+ (ceiling (/ (float raw-mins) 1440.0)) (or wknd-days 0))) + (p1 (save-excursion + (elgantt--goto-date (ts-format "%Y-%m-%d" start-ts)) + (point))) + (colors (or override-color '("#8ec07c" "#458588")))) + (when (numberp p1) + (if (<= total-days 1) + (elgantt--create-overlay (truncate p1) (1+ (truncate p1)) + `(face (:background ,(car colors)) + priority ,(setq elgantt-user-set-color-priority-counter + (1- elgantt-user-set-color-priority-counter)) + :elgantt-user-overlay ,elgantt-org-id)) + (let* ((end-ts (ts-adjust 'day (- total-days 2) start-ts)) + (p2 (save-excursion + (elgantt--goto-date (ts-format "%Y-%m-%d" end-ts)) + (point)))) + (when (numberp p2) + (elgantt--draw-gradient + (car colors) (cadr colors) + (truncate p1) (1+ (truncate p2)) nil + `(priority ,(setq elgantt-user-set-color-priority-counter + (1- elgantt-user-set-color-priority-counter)) + :elgantt-user-overlay ,elgantt-org-id))))))))))) -;; (elgantt-create-display-rule draw-blocker-lines -;; :parser ((blocker-raw . ((org-entry-get (point) "BLOCKER")))) -;; :args (elgantt-org-id elgantt-scheduled) -;; :body ((when (and elgantt-org-id blocker-raw (not (string-empty-p blocker-raw))) -;; ;; 1. GET DESTINATION (Start of current task) -;; ;; We use the built-in elgantt-scheduled arg if available, it's faster and safer. -;; (let* ((p-dest (save-excursion -;; (let ((d-start (or (when (stringp elgantt-scheduled) (substring elgantt-scheduled 0 10)) -;; (elgantt-with-point-at-orig-entry nil -;; (save-excursion -;; (org-back-to-heading t) -;; (when (re-search-forward "<\\([0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\}\\)" (line-end-position) t) -;; (match-string 1))))))) -;; (when (and d-start (elgantt--goto-date d-start)) (point)))))) + ;; --- Rule 3: Blocker Lines --- + (elgantt-create-display-rule draw-blocker-lines + :parser ((blocker-raw . ((org-entry-get (point) "BLOCKER")))) + :args (elgantt-org-id elgantt-scheduled) + :body ((when (and elgantt-org-id blocker-raw (not (string-empty-p blocker-raw))) + (let* ((p-dest (save-excursion + (let ((d-start (or (when (stringp elgantt-scheduled) (substring elgantt-scheduled 0 10)) + (elgantt-with-point-at-orig-entry nil + (save-excursion + (org-back-to-heading t) + (when (re-search-forward "<\\([0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\}\\)" (line-end-position) t) + (match-string 1))))))) + (when (and d-start (elgantt--goto-date d-start)) (point)))))) + (when (numberp p-dest) + (let ((id-list (split-string (if (string-match "ids(\\(.*?\\))" blocker-raw) (match-string 1 blocker-raw) blocker-raw) "[ ,]+" t))) + (dolist (blocker-id id-list) + (save-excursion + (when (elgantt--goto-id blocker-id) + (let ((d-end-str nil) + (row-start (line-beginning-position)) + (row-end (line-end-position))) + (elgantt-with-point-at-orig-entry nil + (save-excursion + (org-back-to-heading t) + (let ((limit (save-excursion (outline-next-heading) (point)))) + (if (re-search-forward "<[^>]+>--<\\([0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\}\\)" limit t) + (setq d-end-str (match-string 1)) + (let ((s (org-entry-get (point) "SCHEDULED")) + (e (org-entry-get (point) "EFFORT")) + (w (string-to-number (or (org-entry-get (point) "WEEKEND_DAYS") "0")))) + (when (and s e) + (setq d-end-str (ts-format "%Y-%m-%d" (ts-adjust 'day (1- (+ (ceiling (/ (float (org-duration-to-minutes e)) 1440.0)) w)) (ts-parse s)))))))))) + (when d-end-str + (save-excursion + (elgantt--goto-date d-end-str) + (let ((p-source (point))) + (if (and (>= p-source row-start) (<= p-source row-end)) + (elgantt--draw-line (truncate p-source) (truncate p-dest) "#b8bb26") + (let ((col-offset (- p-source (save-excursion (goto-char p-source) (line-beginning-position))))) + (goto-char row-start) + (forward-char col-offset) + (elgantt--draw-line (point) (truncate p-dest) "#b8bb26"))))))))))))))))) -;; (when (numberp p-dest) -;; (let ((ids-string (if (string-match "ids(\\(.*?\\))" blocker-raw) (match-string 1 blocker-raw) blocker-raw)) -;; (id-list (split-string (if (string-match "ids(\\(.*?\\))" blocker-raw) (match-string 1 blocker-raw) blocker-raw) "[ ,]+" t))) -;; (dolist (blocker-id id-list) -;; (save-excursion -;; (when (elgantt--goto-id blocker-id) -;; (let ((d-end-str nil) -;; (row-start (line-beginning-position)) -;; (row-end (line-end-position))) -;; ;; 2. GET BLOCKER END DATE -;; (elgantt-with-point-at-orig-entry nil -;; (save-excursion -;; (org-back-to-heading t) -;; (let ((limit (save-excursion (outline-next-heading) (point)))) -;; (if (re-search-forward "<[^>]+>--<\\([0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\}\\)" limit t) -;; (setq d-end-str (match-string 1)) -;; (let ((s (org-entry-get (point) "SCHEDULED")) -;; (e (org-entry-get (point) "EFFORT")) -;; (w (string-to-number (or (org-entry-get (point) "WEEKEND_DAYS") "0")))) -;; (when (and s e) -;; (setq d-end-str (ts-format "%Y-%m-%d" (ts-adjust 'day (1- (+ (ceiling (/ (float (org-duration-to-minutes e)) 1440.0)) w)) (ts-parse s)))))))))) - -;; ;; 3. DRAW -;; (when d-end-str -;; (save-excursion -;; (elgantt--goto-date d-end-str) -;; (let ((p-source (point))) -;; (if (and (>= p-source row-start) (<= p-source row-end)) -;; (elgantt--draw-line (truncate p-source) (truncate p-dest) "#b8bb26") -;; ;; Force to row if it jumped -;; (let ((col-offset (- p-source (save-excursion (goto-char p-source) (line-beginning-position))))) -;; (goto-char row-start) -;; (forward-char col-offset) -;; (elgantt--draw-line (point) (truncate p-dest) "#b8bb26"))))))))))))))) -;; ) - -;; (defun elgantt-open-current-org-file () -;; (interactive) -;; (if-let ((file (buffer-file-name))) -;; (progn -;; (setq elgantt-agenda-files (list file)) -;; (elgantt--reset-org-ql-cache) -;; (elgantt-open)) -;; (message "No file!"))) +(defun elgantt-open-current-org-file () + (interactive) + (if-let ((file (buffer-file-name))) + (progn + (setq elgantt-agenda-files (list file)) + (elgantt--reset-org-ql-cache) + (elgantt-open)) + (message "No file!"))) #+end_src #+RESULTS: @@ -2351,36 +2343,38 @@ Returns nil if no range found (safe, non-blocking)." (when (and (string-match "ids(\\(.*?\\))" clean) (not (string-empty-p (s-trim (match-string 1 clean))))) (dolist (tid (split-string (match-string 1 clean) "[ ,]+" t)) - (let* ((clean-id (replace-regexp-in-string "[\"']\\|id:" "" tid)) - (pos (org-id-find clean-id t)) ;; Returns marker OR (file . pos) + (let* ((clean-id (replace-regexp-in-string "[\"']\\|id: " "" tid)) + (pos (org-id-find clean-id t)) (computed-end (gethash clean-id task-end-map)) - (blocker-end - (cond - (computed-end computed-end) - - ;; Use a temporary buffer context to check properties if pos is a list - ((and pos (let ((m (if (markerp pos) pos (set-marker (make-marker) (cdr pos) (find-file-noselect (car pos)))))) - (with-current-buffer (marker-buffer m) - (org-with-point-at m - (cond - ((org-entry-get nil "CLOSED") - (org-time-string-to-time (org-entry-get nil "CLOSED"))) - ((string-equal "t" (org-entry-get nil "FIXED")) - (gortium/org--get-range-end m)) - (t nil)))))) - ;; This captures the result of the let/with-current-buffer block - (let ((m (if (markerp pos) pos (set-marker (make-marker) (cdr pos) (find-file-noselect (car pos)))))) - (with-current-buffer (marker-buffer m) - (org-with-point-at m - (if (org-entry-get nil "CLOSED") - (org-time-string-to-time (org-entry-get nil "CLOSED")) - (gortium/org--get-range-end m)))))) - (t nil)))) + (blocker-end nil)) + ;; 1. Determine this specific blocker's end time + (setq blocker-end + (cond + (computed-end computed-end) ;; Use what we just calculated in this session + (pos (let ((m (if (markerp pos) pos + (set-marker (make-marker) (cdr pos) + (find-file-noselect (car pos)))))) + (with-current-buffer (marker-buffer m) + (org-with-point-at m + (cond + ;; Priority 1: Use actual CLOSED timestamp if DONE + ((org-entry-get nil "CLOSED") + (org-time-string-to-time (org-entry-get nil "CLOSED"))) + ;; Priority 2: Use range end if FIXED + ((string-equal "t" (org-entry-get nil "FIXED")) + (gortium/org--get-range-end m)) + (t nil)))))) + (t nil))) + + ;; 2. Update the "Latest" tracker (if blocker-end - (setq latest-time (if (or (null latest-time) (time-less-p latest-time blocker-end)) - blocker-end latest-time)) + (when (or (null latest-time) (time-less-p latest-time blocker-end)) + (setq latest-time blocker-end)) + ;; If ANY blocker is not resolved/found, the whole task is not ready (setq all-resolved nil))))) + + ;; Only return a time if EVERY ID in the list was successfully resolved (when all-resolved latest-time))) (defun gortium/internal--update-properties (pos start wknd id end task-end-map) @@ -2458,8 +2452,8 @@ Returns nil if no range found (safe, non-blocking)." (unless (eobp) (insert "\n"))))) - (message "[DEBUG] exit update-properties") - (puthash id end task-end-map))) + (message "[DEBUG] exit update-properties") + (puthash id end task-end-map))) ;; ------------------------------------------------------------ ;; Helper: Detect circular dependencies @@ -2559,10 +2553,14 @@ Returns list of task IDs involved in cycles, or nil if no cycles found." (with-current-buffer buf (org-element-with-disabled-cache (let* ((off-days (if (stringp offset) (string-to-number offset) 0)) - ;; CRITICAL: For fixed tasks, base-start is THEIR start. - ;; For dependent tasks, base-start is the blocker's END. - (base-start (cond (is-fixed (or (gortium/org--get-range-start pos) sched (current-time))) - (t (or blocker-end (or sched (current-time)))))) + (base-start (cond + ;; 1. If FIXED, use its own defined start + (is-fixed (or (gortium/org--get-range-start pos) sched (current-time))) + ;; 2. If it HAS a blocker, it MUST use blocker-end. + ;; If blocker-end is nil, this task isn't 'ready' yet. + (has-blocker blocker-end) + ;; 3. If no blocker and not fixed, use current schedule or now + (t (or sched (current-time))))) (final-start (if is-fixed base-start (gortium/internal--snap-to-working-hours diff --git a/doom/.config/doom/config.el b/doom/.config/doom/config.el index 49ecdea..4e610fc 100644 --- a/doom/.config/doom/config.el +++ b/doom/.config/doom/config.el @@ -400,227 +400,6 @@ ;; Enable plantuml-mode for PlantUML files (add-to-list 'auto-mode-alist '("\\.plantuml\\'" . plantuml-mode)) -;; (require 'cl-lib) -;; (require 'dash) -;; (unless (fboundp 'first) (defalias 'first #'car)) - -;; ;; Clear rules to ensure the new global color logic takes effect immediately -;; (setq elgantt--display-rules nil) - -;; (defface gortium/elgantt-weekend-face -;; '((t (:background "#32302f" :extend nil))) -;; "Gruvbox Dark0_Hard/Soft mix for subtle weekend stripes.") - -;; (defun gortium/internal--month-to-num (name) -;; "Convert month string to number safely." -;; (let ((case-fold-search t)) -;; (cond ((string-match-p "Jan" name) 1) ((string-match-p "Feb" name) 2) -;; ((string-match-p "Mar" name) 3) ((string-match-p "Apr" name) 4) -;; ((string-match-p "May" name) 5) ((string-match-p "Jun" name) 6) -;; ((string-match-p "Jul" name) 7) ((string-match-p "Aug" name) 8) -;; ((string-match-p "Sep" name) 9) ((string-match-p "Oct" name) 10) -;; ((string-match-p "Nov" name) 11) ((string-match-p "Dec" name) 12) (t 1)))) - -;; (defun gortium/elgantt-draw-weekend-guides () -;; "Draw weekend guides for the ENTIRE buffer once to prevent scroll lag." -;; (interactive) -;; (when (derived-mode-p 'elgantt-mode) -;; (let* ((inhibit-modification-hooks t) -;; (header-line-1 (save-excursion -;; (goto-char (point-min)) -;; (buffer-substring-no-properties (line-beginning-position) (line-end-position)))) -;; (col-indices '()) -;; (search-pos 0)) - -;; (save-excursion -;; (save-restriction -;; (widen) -;; ;; 1. Clear ALL weekend overlays in the entire buffer -;; (remove-overlays (point-min) (point-max) 'gortium-weekend t) - -;; ;; 2. Parse header once to find column indexes (Fast) -;; (while (string-match "|[[:space:]]*\\([[:alpha:]]+\\)[[:space:]]+\\([0-9]\\{4\\}\\)" header-line-1 search-pos) -;; (let* ((month-start-col (match-beginning 0)) -;; (month-name (match-string 1 header-line-1)) -;; (year (string-to-number (match-string 2 header-line-1))) -;; (month-num (gortium/internal--month-to-num month-name)) -;; (next-pipe (string-match "|" header-line-1 (1+ month-start-col))) -;; (month-width (if next-pipe (- next-pipe month-start-col 1) 31))) -;; (dotimes (d month-width) -;; (let* ((day (1+ d)) -;; (time (condition-case nil (encode-time 0 0 12 day month-num year) (error nil)))) -;; (when time -;; (let ((dow (nth 6 (decode-time time))) -;; (actual-col (+ month-start-col 1 d))) -;; (when (member dow '(0 6)) -;; (push actual-col col-indices)))))) -;; (setq search-pos (or next-pipe (length header-line-1))))) - -;; ;; 3. Apply to the WHOLE buffer line by line -;; (unless (null col-indices) -;; (goto-char (point-min)) -;; (forward-line 2) ;; Skip headers -;; (while (not (eobp)) -;; (let ((line-end (line-end-position))) -;; (dolist (col col-indices) -;; (move-to-column col) -;; (let ((p (point))) -;; ;; Ensure we are still on the same line and at the correct column -;; (when (and (< p line-end) (= (current-column) col)) -;; (let ((ov (make-overlay p (1+ p)))) -;; (overlay-put ov 'face 'gortium/elgantt-weekend-face) -;; (overlay-put ov 'gortium-weekend t) -;; (overlay-put ov 'priority 100) -;; (overlay-put ov 'evaporate t)))))) -;; (forward-line 1))))) -;; (message "Weekend guides rendered for the whole buffer.")))) - -;; ;; Run it only once when the buffer is loaded -;; (add-hook 'elgantt-mode-hook #'gortium/elgantt-draw-weekend-guides) - -;; (use-package! elgantt -;; :commands (elgantt-open elgantt-open-current-org-file) -;; :config -;; ;; --- 1. Environment & UI --- -;; (add-hook 'elgantt-mode-hook -;; (lambda () -;; (setq-local org-phscroll-mode nil) -;; (setq-local image-roll-mode nil) -;; (setq truncate-lines t))) - -;; (setq elgantt-start-date "2026-01-01") - -;; (setq elgantt-header-column-offset 40 -;; elgantt-header-type 'root -;; elgantt-show-header-depth t -;; elgantt-insert-blank-line-between-top-level-header t -;; elgantt-startup-folded nil -;; elgantt-draw-overarching-headers nil -;; elgantt-scroll-to-current-month-at-startup nil) - -;; (setq elgantt-user-set-color-priority-counter 0) - -;; (elgantt-create-display-rule draw-active-timestamp-range -;; :parser ((override-color . ((when-let ((colors (org-entry-get (point) "ELGANTT-COLOR"))) -;; (split-string colors " ")))) -;; (range-dates . ((save-excursion -;; (org-back-to-heading t) -;; (let ((limit (save-excursion (outline-next-heading) (point)))) -;; (when (re-search-forward "<\\([^>]+\\)>--<\\([^>]+\\)>" limit t) -;; (list (match-string 1) (match-string 2)))))))) -;; :args (elgantt-org-id) -;; :body ((when (and elgantt-org-id range-dates) -;; (let* ((colors (or override-color '("#fabd2f" "#fe8019"))) -;; (s-str (substring (car range-dates) 0 10)) -;; (e-str (substring (cadr range-dates) 0 10)) -;; (p1 (save-excursion (when (elgantt--goto-date s-str) (point)))) -;; (p2 (save-excursion (when (elgantt--goto-date e-str) (point))))) -;; (when (and (numberp p1) (numberp p2)) -;; (elgantt--draw-gradient -;; (car colors) (cadr colors) -;; (truncate p1) (truncate p2) nil ;; <-- FIX: Removed (1+ ...) to stop overshoot -;; `(priority ,(setq elgantt-user-set-color-priority-counter -;; (1- elgantt-user-set-color-priority-counter)) -;; :elgantt-user-overlay ,elgantt-org-id)))))))) - -;; ;; --- 2. Effort Rule (With Weekend Extension) --- -;; ;; (elgantt-create-display-rule draw-scheduled-to-effort-end -;; ;; :parser ((override-color . ((when-let ((colors (org-entry-get (point) "ELGANTT-COLOR"))) -;; ;; (split-string colors " ")))) -;; ;; (elgantt-effort . ((org-entry-get (point) "EFFORT"))) -;; ;; (wknd-days . ((when-let ((val (org-entry-get (point) "WEEKEND_DAYS"))) -;; ;; (string-to-number val))))) -;; ;; :args (elgantt-scheduled elgantt-effort elgantt-org-id) -;; ;; :body ((when (and elgantt-scheduled elgantt-effort) -;; ;; (let* ((start-ts (ts-parse elgantt-scheduled)) -;; ;; (raw-mins (org-duration-to-minutes elgantt-effort)) -;; ;; ;; Add the weekend jump days to the visual length -;; ;; (total-days (+ (ceiling (/ (float raw-mins) 1440.0)) (or wknd-days 0))) -;; ;; (p1 (save-excursion -;; ;; (elgantt--goto-date (ts-format "%Y-%m-%d" start-ts)) -;; ;; (point))) -;; ;; (colors (or override-color '("#8ec07c" "#458588")))) -;; ;; (when (numberp p1) -;; ;; (if (<= total-days 1) -;; ;; (elgantt--create-overlay (truncate p1) (1+ (truncate p1)) -;; ;; `(face (:background ,(car colors)) -;; ;; priority ,(setq elgantt-user-set-color-priority-counter -;; ;; (1- elgantt-user-set-color-priority-counter)) -;; ;; :elgantt-user-overlay ,elgantt-org-id)) -;; ;; ;; FIX 1: compute p2 by date (handles "|" separators) -;; ;; ;; FIX 2: keep original "Rule of 2" behavior to avoid +1 day overshoot -;; ;; (let* ((end-ts (ts-adjust 'day (- total-days 2) start-ts)) -;; ;; (p2 (save-excursion -;; ;; (elgantt--goto-date (ts-format "%Y-%m-%d" end-ts)) -;; ;; (point)))) -;; ;; (when (numberp p2) -;; ;; (elgantt--draw-gradient -;; ;; (car colors) (cadr colors) -;; ;; (truncate p1) (1+ (truncate p2)) nil -;; ;; `(priority ,(setq elgantt-user-set-color-priority-counter -;; ;; (1- elgantt-user-set-color-priority-counter)) -;; ;; :elgantt-user-overlay ,elgantt-org-id)))))))))) - -;; (elgantt-create-display-rule draw-blocker-lines -;; :parser ((blocker-raw . ((org-entry-get (point) "BLOCKER")))) -;; :args (elgantt-org-id elgantt-scheduled) -;; :body ((when (and elgantt-org-id blocker-raw (not (string-empty-p blocker-raw))) -;; ;; 1. GET DESTINATION (Start of current task) -;; ;; We use the built-in elgantt-scheduled arg if available, it's faster and safer. -;; (let* ((p-dest (save-excursion -;; (let ((d-start (or (when (stringp elgantt-scheduled) (substring elgantt-scheduled 0 10)) -;; (elgantt-with-point-at-orig-entry nil -;; (save-excursion -;; (org-back-to-heading t) -;; (when (re-search-forward "<\\([0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\}\\)" (line-end-position) t) -;; (match-string 1))))))) -;; (when (and d-start (elgantt--goto-date d-start)) (point)))))) - -;; (when (numberp p-dest) -;; (let ((ids-string (if (string-match "ids(\\(.*?\\))" blocker-raw) (match-string 1 blocker-raw) blocker-raw)) -;; (id-list (split-string (if (string-match "ids(\\(.*?\\))" blocker-raw) (match-string 1 blocker-raw) blocker-raw) "[ ,]+" t))) -;; (dolist (blocker-id id-list) -;; (save-excursion -;; (when (elgantt--goto-id blocker-id) -;; (let ((d-end-str nil) -;; (row-start (line-beginning-position)) -;; (row-end (line-end-position))) -;; ;; 2. GET BLOCKER END DATE -;; (elgantt-with-point-at-orig-entry nil -;; (save-excursion -;; (org-back-to-heading t) -;; (let ((limit (save-excursion (outline-next-heading) (point)))) -;; (if (re-search-forward "<[^>]+>--<\\([0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\}\\)" limit t) -;; (setq d-end-str (match-string 1)) -;; (let ((s (org-entry-get (point) "SCHEDULED")) -;; (e (org-entry-get (point) "EFFORT")) -;; (w (string-to-number (or (org-entry-get (point) "WEEKEND_DAYS") "0")))) -;; (when (and s e) -;; (setq d-end-str (ts-format "%Y-%m-%d" (ts-adjust 'day (1- (+ (ceiling (/ (float (org-duration-to-minutes e)) 1440.0)) w)) (ts-parse s)))))))))) - -;; ;; 3. DRAW -;; (when d-end-str -;; (save-excursion -;; (elgantt--goto-date d-end-str) -;; (let ((p-source (point))) -;; (if (and (>= p-source row-start) (<= p-source row-end)) -;; (elgantt--draw-line (truncate p-source) (truncate p-dest) "#b8bb26") -;; ;; Force to row if it jumped -;; (let ((col-offset (- p-source (save-excursion (goto-char p-source) (line-beginning-position))))) -;; (goto-char row-start) -;; (forward-char col-offset) -;; (elgantt--draw-line (point) (truncate p-dest) "#b8bb26"))))))))))))))) -;; ) - -;; (defun elgantt-open-current-org-file () -;; (interactive) -;; (if-let ((file (buffer-file-name))) -;; (progn -;; (setq elgantt-agenda-files (list file)) -;; (elgantt--reset-org-ql-cache) -;; (elgantt-open)) -;; (message "No file!"))) - (setq org-roam-directory (file-truename "~/ExoKortex/") org-roam-db-location (file-truename "~/ExoKortex/2-Areas/IT/Roam/org-roam.db") org-attach-id-dir "assets/" @@ -1697,36 +1476,38 @@ Returns nil if no range found (safe, non-blocking)." (when (and (string-match "ids(\\(.*?\\))" clean) (not (string-empty-p (s-trim (match-string 1 clean))))) (dolist (tid (split-string (match-string 1 clean) "[ ,]+" t)) - (let* ((clean-id (replace-regexp-in-string "[\"']\\|id:" "" tid)) - (pos (org-id-find clean-id t)) ;; Returns marker OR (file . pos) + (let* ((clean-id (replace-regexp-in-string "[\"']\\|id: " "" tid)) + (pos (org-id-find clean-id t)) (computed-end (gethash clean-id task-end-map)) - (blocker-end - (cond - (computed-end computed-end) - - ;; Use a temporary buffer context to check properties if pos is a list - ((and pos (let ((m (if (markerp pos) pos (set-marker (make-marker) (cdr pos) (find-file-noselect (car pos)))))) - (with-current-buffer (marker-buffer m) - (org-with-point-at m - (cond - ((org-entry-get nil "CLOSED") - (org-time-string-to-time (org-entry-get nil "CLOSED"))) - ((string-equal "t" (org-entry-get nil "FIXED")) - (gortium/org--get-range-end m)) - (t nil)))))) - ;; This captures the result of the let/with-current-buffer block - (let ((m (if (markerp pos) pos (set-marker (make-marker) (cdr pos) (find-file-noselect (car pos)))))) - (with-current-buffer (marker-buffer m) - (org-with-point-at m - (if (org-entry-get nil "CLOSED") - (org-time-string-to-time (org-entry-get nil "CLOSED")) - (gortium/org--get-range-end m)))))) - (t nil)))) + (blocker-end nil)) + ;; 1. Determine this specific blocker's end time + (setq blocker-end + (cond + (computed-end computed-end) ;; Use what we just calculated in this session + (pos (let ((m (if (markerp pos) pos + (set-marker (make-marker) (cdr pos) + (find-file-noselect (car pos)))))) + (with-current-buffer (marker-buffer m) + (org-with-point-at m + (cond + ;; Priority 1: Use actual CLOSED timestamp if DONE + ((org-entry-get nil "CLOSED") + (org-time-string-to-time (org-entry-get nil "CLOSED"))) + ;; Priority 2: Use range end if FIXED + ((string-equal "t" (org-entry-get nil "FIXED")) + (gortium/org--get-range-end m)) + (t nil)))))) + (t nil))) + + ;; 2. Update the "Latest" tracker (if blocker-end - (setq latest-time (if (or (null latest-time) (time-less-p latest-time blocker-end)) - blocker-end latest-time)) + (when (or (null latest-time) (time-less-p latest-time blocker-end)) + (setq latest-time blocker-end)) + ;; If ANY blocker is not resolved/found, the whole task is not ready (setq all-resolved nil))))) + + ;; Only return a time if EVERY ID in the list was successfully resolved (when all-resolved latest-time))) (defun gortium/internal--update-properties (pos start wknd id end task-end-map) @@ -1804,8 +1585,8 @@ Returns nil if no range found (safe, non-blocking)." (unless (eobp) (insert "\n"))))) - (message "[DEBUG] exit update-properties") - (puthash id end task-end-map))) + (message "[DEBUG] exit update-properties") + (puthash id end task-end-map))) ;; ------------------------------------------------------------ ;; Helper: Detect circular dependencies @@ -1905,10 +1686,14 @@ Returns list of task IDs involved in cycles, or nil if no cycles found." (with-current-buffer buf (org-element-with-disabled-cache (let* ((off-days (if (stringp offset) (string-to-number offset) 0)) - ;; CRITICAL: For fixed tasks, base-start is THEIR start. - ;; For dependent tasks, base-start is the blocker's END. - (base-start (cond (is-fixed (or (gortium/org--get-range-start pos) sched (current-time))) - (t (or blocker-end (or sched (current-time)))))) + (base-start (cond + ;; 1. If FIXED, use its own defined start + (is-fixed (or (gortium/org--get-range-start pos) sched (current-time))) + ;; 2. If it HAS a blocker, it MUST use blocker-end. + ;; If blocker-end is nil, this task isn't 'ready' yet. + (has-blocker blocker-end) + ;; 3. If no blocker and not fixed, use current schedule or now + (t (or sched (current-time))))) (final-start (if is-fixed base-start (gortium/internal--snap-to-working-hours