Mercurial > infra > home / .emacs.d/lib/org-timeline.el
changeset 94: |
978ce75e54af |
author: |
Richard Westhaver <ellis@rwest.io> |
date: |
Fri, 30 Aug 2024 17:07:30 -0400 |
permissions: |
-rw-r--r-- |
description: |
add stumpwm modules and org-timeline |
1 ;;; org-timeline.el --- Add graphical view of agenda to agenda buffer. -*- lexical-binding: t -*- 3 ;; Copyright (C) 2017 Matúš Goljer 5 ;; Author: Matúš Goljer <matus.goljer@gmail.com> 6 ;; Maintainer: Matúš Goljer <matus.goljer@gmail.com> 8 ;; Created: 16th April 2017 9 ;; Package-requires: ((dash "2.13.0") (emacs "24.3")) 11 ;; URL: https://github.com/Fuco1/org-timeline/ 13 ;; This program is free software; you can redistribute it and/or 14 ;; modify it under the terms of the GNU General Public License 15 ;; as published by the Free Software Foundation; either version 3 16 ;; of the License, or (at your option) any later version. 18 ;; This program is distributed in the hope that it will be useful, 19 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 20 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 ;; GNU General Public License for more details. 23 ;; You should have received a copy of the GNU General Public License 24 ;; along with this program. If not, see <http://www.gnu.org/licenses/>. 28 ;; Add graphical view of agenda to agenda buffer. 30 ;; This package adds a graphical view of the agenda after the last 31 ;; agenda line. By default the display starts at 5 AM today and 32 ;; goes up to 4 AM next day (this covers 24 hours). 34 ;; Scheduled tasks or tasks with time ranges are rendered in the 35 ;; display with `org-timeline-block' face. Clocked entires are 36 ;; displayed in `org-timeline-clocked' face. 38 ;; You can use custom color for a task by adding the property 39 ;; `TIMELINE_FACE' with either a string which is a color name or a 40 ;; list which specifies the face properties or a symbol which is 41 ;; taken to be a face name. 49 (defgroup org-timeline () 50 "Graphical view of agenda in agenda buffer." 52 :prefix "org-timeline-") 54 (defgroup org-timeline-faces () 55 "Faces for org-timeline." 58 (defcustom org-timeline-default-duration 60 59 "Default event duration in minutes" 63 (defcustom org-timeline-prepend nil 64 "Option to prepend the timeline to the agenda." 68 (defcustom org-timeline-show-clocked t 69 "Option to show or hide clocked items." 73 (defcustom org-timeline-dedicated-clocked-line t 74 "Option to show clocked items in a dedicated line with 'group-name' '$'." 78 (defcustom org-timeline-overlap-in-new-line nil 79 "Option to create new lines for blocks that would otherwise overlap." 83 (defcustom org-timeline-emphasize-next-block nil 84 "Option to apply the face `org-timeline-next-block' to the next block happening today." 88 (defcustom org-timeline-show-text-in-blocks nil 89 "Option to show the text of the event in the block. 91 If the item has a property `TIMELINE_TEXT', use this as a title. 92 Otherwise, the title will be the item's headline, stripped of its todo state." 96 (defcustom org-timeline-beginning-of-day-hour 5 97 "When the timeline begins. 99 Due to the way 'org-agenda' works, if you set this to any other value than 0 100 \(e.g. 5), then events that happen after midnight will not appear (even though 101 the timeline shows the slots). 102 If you view the agenda in week mode, those events will not appear in any of 105 The workaround for this in day view is to use `org-timeline-keep-elapsed' that 106 will make the timeline show you a 24h cycle. See this variable's documentation 107 for more information." 109 :group 'org-timeline) 111 (defcustom org-timeline-keep-elapsed -1 112 "In day view, for today, keep only this number of fully elapsed hours. 114 For negative values, do not hide elapsed hours. 116 This can be used to see a rolling 24h cycle in the timeline. 117 In order to do that, set `org-timeline-beginning-of-day-hour' to 0, and set 118 `org-timeline-keep-elapsed' to any positive number. 119 Set `org-agenda-span' to 2, and open the day agenda view for today. 120 You will see a rolling 24h cycle, starting `org-timeline-keep-elapsed' hours ago." 122 :group 'org-timeline) 124 (defcustom org-timeline-insert-before-text "\u275A" 125 "String inserted before the block's text. 127 It makes consecutive blocks distinct. 129 The default value '\u275A' is a heavy vertical bar ❚." 131 :group 'org-timeline) 133 (defvar org-timeline-first-line-in-agenda-buffer 0 134 "Line number of the first line of the timeline in the agenda buffer.") 136 (defvar org-timeline-height 0 137 "Final height of the timeline.") 139 (defvar org-timeline-current-info nil 140 "Current displayed info. Used to fix flickering of info.") 142 (defvar org-timeline-slotline (concat (mapconcat 'not (number-sequence 0 24) "| ") "|") 143 "The undecorated slotline string.") 145 (defvar org-timeline-next-task-today nil 146 "The next task happening today.") 148 (cl-defstruct org-timeline-task 152 offset-beg ; in points 153 offset-end ; in points 154 info ; copy of the agenda buffer's line 155 line-in-agenda-buffer 157 day ; absolute, see `calendar-absolute-from-gregorian' 158 type ; "scheduled", "clocked" ... 161 do-not-overlap-p ; make sure this block doesn't overlap with any other 165 (defface org-timeline-block 166 '((t (:inherit secondary-selection))) 167 "Face used for printing blocks with time range information. 169 These are blocks that are scheduled for specific time range or 170 have an active timestamp with a range." 171 :group 'org-timeline-faces) 173 (defface org-timeline-elapsed 174 '((t (:inherit region))) 175 "Face used for highlighting clocked items." 176 :group 'org-timeline-faces) 178 (defface org-timeline-clocked 179 '((t (:inherit highlight))) 180 "Face used for printing clocked blocks. 182 Clocked blocks appear in the agenda when `org-agenda-log-mode' is 184 :group 'org-timeline-faces) 186 (defface org-timeline-overlap 187 '((t (:background "dark red"))) 188 "Face used for printing overlapping blocks." 189 :group 'org-timeline-faces) 191 (defface org-timeline-next-block 192 '((t (:background "dark olive green"))) 193 "Face used for printing the next block happening today. 195 Used when `org-timeline-emphasize-next-block' is non-nil." 196 :group 'org-timeline-faces) 199 (defmacro org-timeline-with-each-line (&rest body) 200 "Execute BODY on each line in buffer." 204 (goto-char (point-min)) 206 (while (= (forward-line) 0) 209 (defun org-timeline--get-face (type) 210 "Get the face with which to draw the current block, according to TYPE." 211 (--if-let (org-entry-get (org-get-at-bol 'org-marker) "TIMELINE_FACE" t) 212 (let ((read-face (car (read-from-string it)))) 213 (if (stringp read-face) 214 (list :background read-face) 216 (if (string= type "clock") 217 (list 'org-timeline-clocked) 218 (list 'org-timeline-block)))) 220 (defun org-timeline--get-block-text () 221 "Get the text to print inside the current block." 222 (let ((item-marker (org-get-at-bol 'org-marker))) 223 (--if-let (org-entry-get item-marker "TIMELINE_TEXT" t) 225 (with-current-buffer (marker-buffer item-marker) 227 (goto-char item-marker) 228 (outline-previous-heading) 229 (org-element-property :raw-value (org-element-context))))))) 231 (defun org-timeline--get-group-name (type) 232 "Get the current block's 'group-name' according to TYPE. 234 The first three chars will be printed at the beginning of the block's line." 235 (--if-let (org-entry-get (org-get-at-bol 'org-marker) "TIMELINE_GROUP" t) 236 (if (< (length it) 3) 237 (concat (substring " " 0 (- 3 (length it))) it) 239 (if (and (string= type "clock") org-timeline-dedicated-clocked-line) 243 (defun org-timeline--get-do-not-overlap (type) 244 "Whether the current block is allowed to overlap in the timeline according to TYPE." 245 (--if-let (org-entry-get (org-get-at-bol 'org-marker) "TIMELINE_DO_NOT_OVERLAP" t) 247 (if (and (not (string= type "clock")) org-timeline-overlap-in-new-line) 251 (defun org-timeline--overlapping-at-point (task) 252 "List of points where an already drawn blocks would overlap with TASK." 254 (let (overlap-points) 255 (goto-char (+ (line-beginning-position) (org-timeline-task-offset-beg task))) 256 (while (and (<= (point) (+ (line-beginning-position) (org-timeline-task-offset-end task))) 257 (< (point) (point-max))) 258 (when (get-text-property (point) 'org-timeline-occupied) 259 (push (point) overlap-points)) 263 (defun org-timeline--new-overlap-line-required-at-point-p (task) 264 "Whether a new overlap line needs to be created to insert TASK." 265 (let* ((overlapping (org-timeline--overlapping-at-point task)) 266 (overlapping-blocks-that-do-not-overlap 267 (delq nil (mapcar (lambda (point) (get-text-property point 'org-timeline-do-not-overlap)) overlapping)))) 268 (and (not (eq overlapping nil)) 269 (or (org-timeline-task-do-not-overlap-p task) 270 (not (eq overlapping-blocks-that-do-not-overlap nil)))))) 272 (defun org-timeline--add-elapsed-face (string) 273 "Add `org-timeline-elapsed' to STRING's elapsed portion. 275 Return new copy of STRING." 276 (let* ((string-copy (copy-sequence string)) 277 (start-offset (* org-timeline-beginning-of-day-hour 60)) 278 (current-time (+ (* 60 (string-to-number (format-time-string "%H"))) 279 (string-to-number (format-time-string "%M")))) 280 (current-offset (/ (- current-time start-offset) 10))) 281 (when (< 0 current-offset) 282 (put-text-property 0 (+ 1 current-offset) 'font-lock-face (list 'org-timeline-elapsed) string-copy)) 285 (defun org-timeline--kill-info () 286 "Kill the info line." 288 (goto-line org-timeline-first-line-in-agenda-buffer) 289 (while (and (not (get-text-property (point) 'org-timeline-info-line)) 290 (eq (forward-line) 0))) 291 (unless (eq (point) (point-max)) ; info line not found 292 (let ((inhibit-read-only t)) 293 (kill-whole-line))))) 295 (defun org-timeline--decorate-info (info) 296 "Make INFO string clickable." 297 (let ((info-keymap (make-sparse-keymap))) 298 (define-key info-keymap [mouse-1] 'org-agenda-goto) 299 (define-key info-keymap [mouse-2] 'org-find-file-at-mouse) 300 (propertize info 'keymap info-keymap 301 'help-echo "mouse-1 jump to org file" 302 'org-timeline-info-line t))) 304 (defun org-timeline--draw-new-info (win info) 305 "Displays INFO about a hovered block. 307 WIN is the agenda buffer's window." 308 (unless (eq info org-timeline-current-info) ; prevents flickering 309 (setq org-timeline-current-info info) 310 (save-window-excursion 312 (select-window win) ; because one can hover blocks without being in the agenda window. 313 (org-timeline--kill-info) 314 (goto-line org-timeline-first-line-in-agenda-buffer) 315 (forward-line (- org-timeline-height 2)) 316 (let ((inhibit-read-only t)) 317 (insert (org-timeline--decorate-info info) "\n")))))) 319 (defun org-timeline--move-to-task-in-agenda-buffer () 320 "Move to a block's correponding task in the agenda buffer." 322 (let ((line (get-text-property (point) 'org-timeline-task-line))) 323 (when org-timeline-prepend 324 (setq line (+ line org-timeline-height -1))) 326 (search-forward (get-text-property (point) 'time)))) ; makes point more visible to user. 328 (defun org-timeline--list-tasks () 329 "Build the list of tasks to display." 332 (start-offset (* org-timeline-beginning-of-day-hour 60)) 333 (current-time (+ (* 60 (string-to-number (format-time-string "%H"))) 334 (string-to-number (format-time-string "%M"))))) 335 (org-timeline-with-each-line 336 (-when-let* ((time-of-day (org-get-at-bol 'time-of-day)) 337 (marker (org-get-at-bol 'org-marker)) 338 (type (org-get-at-bol 'type)) 339 (duration (or (org-get-at-bol 'duration) 340 org-timeline-default-duration))) 341 (when (member type (list "past-scheduled" "scheduled" "clock" "timestamp")) 342 (when (and (numberp duration) 344 (cl-incf duration 1440)) 345 (let* ((hour (/ time-of-day 100)) 346 (minute (mod time-of-day 100)) 347 (beg (+ (* hour 60) minute)) 348 (end (round (+ beg duration)))) 349 (setq beg (max beg start-offset)) 350 (setq end (min end (+ start-offset (* 24 60)))) 351 (setq duration (- end beg)) 352 (when (eq end (* 24 60)) (cl-incf end -1)) ; FIXME fixes a bug that shouldn't happen (crash when events end at midnight). 353 (when (and (>= end start-offset) 354 (<= beg (+ start-offset (* 24 60))) 355 (or org-timeline-show-clocked 356 (not (string= type "clock")))) 357 (push (make-org-timeline-task 361 :offset-beg (+ 5 (- (/ beg 10) (* 6 org-timeline-beginning-of-day-hour))) 362 :offset-end (+ 5 (- (/ end 10) (* 6 org-timeline-beginning-of-day-hour))) 363 :info (buffer-substring (line-beginning-position) (line-end-position)) 364 :line-in-agenda-buffer (line-number-at-pos) 365 :face (org-timeline--get-face type) 366 :day (org-get-at-bol 'day) 368 :text (org-timeline--get-block-text) 369 :group-name (org-timeline--get-group-name type) 370 :do-not-overlap-p (org-timeline--get-do-not-overlap type) 374 ;; find the next task 375 (setq org-timeline-next-task nil) 377 (let* ((beg (org-timeline-task-beg task)) 378 (end (org-timeline-task-end task)) 379 (today (calendar-absolute-from-gregorian (calendar-current-date))) 380 (is-today (eq today (org-timeline-task-day task))) 381 (is-now (and (<= beg current-time) 382 (>= end current-time))) 383 (is-after (> beg current-time)) 384 (is-closer-to-now (and is-after 385 (or (eq org-timeline-next-task nil) 386 (< beg (org-timeline-task-beg org-timeline-next-task)))))) 387 (when (and is-today (or is-now is-closer-to-now)) 388 (setq org-timeline-next-task task)))) 389 ;; change the next task's face 390 (when (and org-timeline-emphasize-next-block 391 org-timeline-next-task) 393 (when (eq (org-timeline-task-id task) (org-timeline-task-id org-timeline-next-task)) 394 (setf (org-timeline-task-face task) (list 'org-timeline-next-block))))) 397 (defun org-timeline--goto-block-position (task) 398 "Go to TASK's block's line and position cursor in line... 400 Return t if this task will overlap another one when inserted." 401 (let* ((slotline (org-timeline--add-elapsed-face org-timeline-slotline)) 402 (offset-beg (org-timeline-task-offset-beg task)) 403 (offset-end (org-timeline-task-offset-end task)) 404 (day (org-timeline-task-day task)) 405 (group-name (org-timeline-task-group-name task)) 406 (do-not-overlap (org-timeline-task-do-not-overlap-p task))) 408 (while (and (not (eq (get-text-property (point) 'org-timeline-day) day)) 409 (not (eq (forward-line) 1)))) 410 (unless (eq (get-text-property (point) 'org-timeline-day) day) 411 (insert (concat "\n" ; creating the necessary lines, up to the current task's day 412 (mapconcat (lambda (line-day) 413 (propertize (concat (calendar-day-name (mod line-day 7) t t) ; found in https://github.com/deopurkar/org-timeline 416 'org-timeline-day line-day 'org-timeline-group-name " ")) 417 (if-let ((last-day (get-text-property (line-beginning-position) 'org-timeline-day))) 418 (number-sequence (+ 1 last-day) day) 421 ;; cursor is now at beginning of the task's day's first line 422 (while (and (not (string= (get-text-property (point) 'org-timeline-group-name) group-name)) 423 (eq (get-text-property (point) 'org-timeline-day) day)) 425 (unless (string= (-if-let (group-here (get-text-property (point) 'org-timeline-group-name)) group-here " ") group-name) 426 (when (not (eq (line-end-position) (point-max))) (forward-line -1)) 427 (goto-char (line-end-position)) 429 (propertize (concat group-name " " slotline) 'org-timeline-day day 'org-timeline-group-name group-name))) 430 ;; cursor is now at beginning of the task's group's first line 431 (let ((new-overlap-line-required-flag (org-timeline--new-overlap-line-required-at-point-p task))) 432 (while (and (org-timeline--new-overlap-line-required-at-point-p task) 433 (eq (get-text-property (point) 'org-timeline-day) day) 434 (eq (get-text-property (point) 'org-timeline-group-name) group-name) 435 (not (eq (line-end-position) (point-max)))) 436 (setq new-overlap-line-required-flag t) 438 (let ((decorated-slotline (propertize (concat " " " " slotline) 439 'org-timeline-day day 440 'org-timeline-group-name group-name))) 441 (when new-overlap-line-required-flag 443 (insert "\n" decorated-slotline)))) 444 ;; cursor is now placed on the right line, at the right position. 445 (goto-char (+ (line-beginning-position) offset-beg)))) 447 (defun org-timeline--make-basic-block (task) 448 "Make TASK's block and return it as a propertized string. 450 This does not take the block's context (e.g. overlap) into account." 451 (let* ((blank-block (mapconcat 'not (number-sequence 1 24) " ")) 452 (id (org-timeline-task-id task)) 453 (offset-beg (org-timeline-task-offset-beg task)) 454 (offset-end (org-timeline-task-offset-end task)) 455 (info (org-timeline-task-info task)) 456 (face (org-timeline-task-face task)) 457 (line (org-timeline-task-line-in-agenda-buffer task)) 458 (group-name (org-timeline-task-group-name task)) 459 (do-not-overlap (org-timeline-task-do-not-overlap-p task)) 460 (move-to-task-map '(keymap mouse-1 . org-timeline--move-to-task-in-agenda-buffer)) 461 (block-length (- offset-end offset-beg)) 462 (props (list 'font-lock-face face 463 'org-timeline-occupied t 464 'org-timeline-do-not-overlap do-not-overlap 465 'org-timeline-task-id id 466 'org-timeline-group-name group-name 467 'mouse-face '(:highlight t :box t) 468 'keymap move-to-task-map 470 'help-echo (lambda (w obj pos) ; called on block hover 471 (org-timeline--draw-new-info w info) 473 'org-timeline-task-line line)) 474 (title (concat org-timeline-insert-before-text 475 (org-timeline-task-text task) 477 (block (if org-timeline-show-text-in-blocks 480 (add-text-properties 0 block-length props block) 481 (substring block 0 block-length))) 483 (defun org-timeline--make-and-insert-block (task) 484 "Insert the TASK's block at the right position in the timeline. 486 Changes the block's face according to context." 487 (org-timeline--goto-block-position task) 488 (let ((overlapp (not (eq (org-timeline--overlapping-at-point task) nil))) 489 (is-next (if (not (eq org-timeline-next-task nil)) 490 (eq (org-timeline-task-id task) (org-timeline-task-id org-timeline-next-task)) 492 (block (org-timeline--make-basic-block task))) 493 (when overlapp (setq block (propertize block 'font-lock-face (list 'org-timeline-overlap)))) 494 (when is-next (setq block (propertize block 'font-lock-face (list 'org-timeline-next-block)))) 495 (unless (get-text-property (- (point) 1) 'org-timeline-overline) 496 (add-text-properties 0 (length block) 497 (list 'org-timeline-overline t 498 'font-lock-face (append (get-text-property 0 'font-lock-face block) '((:overline t))) 499 'mouse-face (append (get-text-property 0 'mouse-face block) '((:overline t)))) 501 (setq block (substring block 0 (min (length block) (- (line-end-position) (point))))) 502 (delete-char (length block)) 505 (defun org-timeline--merge-for-24h-cycle () 506 "Kill elapsed columns in day's line according to `org-timeline-keep-elapsed'. 508 Move tomorrow's line to the right of today's line, to show a complete 24h cycle. 509 See the documentation of `org-timeline-keep-elapsed' for more information." 510 ;; FIXME: quite hacky. This should probably be done directly when making the tasks list, 511 ;; maybe by making all those events happen the same fake '0' day and change the offsets accordingly. 512 (let* ((today (calendar-absolute-from-gregorian (calendar-current-date))) 513 (current-hour (string-to-number (format-time-string "%H"))) 514 (current-time (+ (* 60 current-hour) 515 (string-to-number (format-time-string "%M")))) 516 (elapsed-hours (- (floor (/ current-time 60)) org-timeline-beginning-of-day-hour)) 517 (number-of-columns-tomorrow (max 0 (- elapsed-hours org-timeline-keep-elapsed))) 518 (number-of-columns-today (- 24 number-of-columns-tomorrow)) 519 (hourline-piece (delete-and-extract-region 6 (+ 6 (* 6 number-of-columns-tomorrow)))) 520 (today-line-pieces nil) 521 (tomorrow-line-pieces nil) 522 (cycle-offset (* 6 (- (max org-timeline-beginning-of-day-hour (- current-hour org-timeline-keep-elapsed)) org-timeline-beginning-of-day-hour))) 523 (blank-today-line-piece (concat " " (substring (org-timeline--add-elapsed-face org-timeline-slotline) 525 (+ cycle-offset (* 6 number-of-columns-today))) 527 (blank-tomorrow-line-piece (concat " " (substring org-timeline-slotline 0 (* 6 number-of-columns-tomorrow))))) 529 (goto-char (line-end-position)) 530 (insert hourline-piece) 531 ;; build (today|tomorrow)-line-pieces lists. 532 (while (not (eq (line-end-position) (point-max))) 534 (let* ((lbeg (line-beginning-position)) 535 (lend (line-end-position)) 536 (today-portion (concat (buffer-substring lbeg (+ lbeg 4)) 537 (buffer-substring (- lend (* 6 number-of-columns-today) 1) lend))) 538 (tomorrow-portion (buffer-substring (+ 5 lbeg) (+ 5 lbeg (* 6 number-of-columns-tomorrow))))) 539 (when (eq (get-text-property lbeg 'org-timeline-day) today) 540 (setq today-line-pieces (append today-line-pieces (list today-portion)))) 541 (when (eq (get-text-property lbeg 'org-timeline-day) (+ today 1)) 542 (setq tomorrow-line-pieces (append tomorrow-line-pieces (list tomorrow-portion)))))) 543 ;; handle groups and balance lines 544 ;; FIXME: not efficient, doesn't jump once group done 546 ;; (dolist (line today-line-pieces) (print line)) 547 ;; (print "tomorrow") 548 ;; (dolist (line tomorrow-line-pieces) (print line)) 549 (let (groups-handled) 550 (dotimes (i (length today-line-pieces)) 551 (let* ((group-handled (get-text-property 0 'org-timeline-group-name (seq-elt today-line-pieces i))) 552 (group-handled-p (lambda (piece) (string= (get-text-property 1 'org-timeline-group-name piece) group-handled))) 553 (prev-pieces-today (seq-take today-line-pieces i)) 554 (next-pieces-today (seq-drop today-line-pieces i)) 555 (same-group-pieces-today (seq-filter group-handled-p next-pieces-today)) 556 (rest-of-pieces-today (seq-remove group-handled-p next-pieces-today)) 557 (prev-pieces-tomorrow (seq-take tomorrow-line-pieces i)) 558 (next-pieces-tomorrow (seq-drop tomorrow-line-pieces i)) 559 (same-group-pieces-tomorrow (seq-filter group-handled-p next-pieces-tomorrow)) 560 (rest-of-pieces-tomorrow (seq-remove group-handled-p next-pieces-tomorrow))) 562 (let* ((line-diff (- (length same-group-pieces-tomorrow) (length same-group-pieces-today))) 563 (number-of-blank-lines-to-add-today (max 0 line-diff)) 564 (number-of-blank-lines-to-add-tomorrow (max 0 (- 0 line-diff)))) 565 (dotimes (n number-of-blank-lines-to-add-today) 566 (setq same-group-pieces-today (append same-group-pieces-today (list blank-today-line-piece)))) 567 (dotimes (n number-of-blank-lines-to-add-tomorrow) 568 (setq same-group-pieces-tomorrow (append same-group-pieces-tomorrow (list blank-tomorrow-line-piece))))) 569 ;; rebuild the pieces lists 570 (setq today-line-pieces (append prev-pieces-today same-group-pieces-today rest-of-pieces-today)) 571 (setq tomorrow-line-pieces (append prev-pieces-tomorrow same-group-pieces-tomorrow rest-of-pieces-tomorrow)))) 572 (let* ((unhandled-groups-tomorrow (seq-drop tomorrow-line-pieces (length today-line-pieces)))) 573 (dolist (piece unhandled-groups-tomorrow) 574 (if (member (get-text-property 0 'org-timeline-group-name piece) groups-handled) 575 (setq today-line-pieces (append today-line-pieces (list blank-today-line-piece))) 576 (setq today-line-pieces (append today-line-pieces (list (concat (get-text-property 0 'org-timeline-group-name piece) 577 (substring blank-today-line-piece 3 nil)))))) 578 (push (get-text-property 0 'org-timeline-group-name piece) groups-handled)))) 580 ;; (dolist (line today-line-pieces) (print line)) 581 ;; (print "tomorrow") 582 ;; (dolist (line tomorrow-line-pieces) (print line)) 585 (let ((hourline (buffer-substring 1 (line-end-position)))) 586 (delete-region (point-min) (point-max)) 588 (dolist (piece today-line-pieces) 591 (dolist (piece tomorrow-line-pieces) 592 (goto-char (line-end-position)) 595 ;; remove elapsed face from tomorrow lines 597 (put-text-property (+ 5 (* 6 number-of-columns-today)) (line-end-position) 'face nil) 598 (while (and (eq (forward-line) 0) 599 (not (eq (point) (point-max)))) 600 (forward-char (+ 5 (* 6 number-of-columns-today))) 601 (dotimes (i (- (line-end-position) (point))) 602 (when (not (get-text-property (point) 'org-timeline-occupied)) 603 (put-text-property (point) (+ (point) 1) 'face nil)) 606 ;; Some ideas for the the generation of the timeline were inspired by the 607 ;; forked repo: https://github.com/deopurkar/org-timeline. 608 (defun org-timeline--generate-timeline () 609 "Generate the timeline string that will represent current agenda view." 610 (let* ((hourline (concat " " 611 (org-timeline--add-elapsed-face 613 (mapconcat (lambda (x) (format "%02d:00" (mod x 24))) 614 (number-sequence org-timeline-beginning-of-day-hour (+ org-timeline-beginning-of-day-hour 23)) 617 (tasks (org-timeline--list-tasks)) 618 (today (calendar-absolute-from-gregorian (calendar-current-date))) 619 (today-onlyp (eq 0 (length (delq nil (mapcar (lambda (task) (if (eq (org-timeline-task-day task) today) nil task)) tasks))))) 620 (today-or-tomorrow-only-p (eq 0 (length (delq nil (mapcar (lambda (task) (if (member (org-timeline-task-day task) `(,today ,(+ today 1))) nil task)) tasks)))))) 624 ;; (print (buffer-substring (point-min) (point-max))) 625 (org-timeline--make-and-insert-block task)) 626 ;; (print (buffer-substring (point-min) (point-max))) 627 (when (and (>= org-timeline-keep-elapsed 0) 628 today-or-tomorrow-only-p 629 (> (length tasks) 0)) 630 (org-timeline--merge-for-24h-cycle)) 631 ;; display the next block's info 632 (goto-char (point-max)) 633 (unless (eq (length tasks) 0) 635 (if (eq org-timeline-next-task nil) 636 (propertize " no incoming event" 'org-timeline-info-line t) 637 (org-timeline--decorate-info (org-timeline-task-info org-timeline-next-task))))) 641 (defun org-timeline-insert-timeline () 642 "Insert graphical timeline into agenda buffer." 643 (unless (buffer-narrowed-p) 644 (goto-char (point-min)) 645 (unless org-timeline-prepend 646 (while (and (eq (get-text-property (line-beginning-position) 'org-agenda-type) 'agenda) 650 (let ((inhibit-read-only t)) 651 (setq org-timeline-first-line-in-agenda-buffer (line-number-at-pos)) 652 (insert (propertize (concat (make-string (window-width) ?─)) 'face 'org-time-grid) "\n") 653 (insert (org-timeline--generate-timeline)) 654 (insert (propertize (concat "\n" (make-string (window-width) ?─)) 'face 'org-time-grid 'org-timeline-end t) "\n") 655 (setq org-timeline-height (- (line-number-at-pos) org-timeline-first-line-in-agenda-buffer))) 656 ;; enable `font-lock-mode' in agenda view to display the "chart" 659 (provide 'org-timeline) 660 ;;; org-timeline.el ends here