From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mp2 ([2001:41d0:2:4a6f::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by ms11 with LMTPS id cltvE3zJX19raAAA0tVLHw (envelope-from ) for ; Mon, 14 Sep 2020 19:50:20 +0000 Received: from aspmx1.migadu.com ([2001:41d0:2:4a6f::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by mp2 with LMTPS id EEo+DXzJX18NOQAAB5/wlQ (envelope-from ) for ; Mon, 14 Sep 2020 19:50:20 +0000 Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by aspmx1.migadu.com (Postfix) with ESMTPS id 245629404C9 for ; Mon, 14 Sep 2020 19:50:19 +0000 (UTC) Received: from localhost ([::1]:33140 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1kHuUU-0002vM-30 for larch@yhetil.org; Mon, 14 Sep 2020 15:50:18 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:51614) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1kHuU9-0002vF-BR for emacs-orgmode@gnu.org; Mon, 14 Sep 2020 15:49:57 -0400 Received: from static.214.254.202.116.clients.your-server.de ([116.202.254.214]:57888 helo=ciao.gmane.io) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1kHuU6-00006Y-Ss for emacs-orgmode@gnu.org; Mon, 14 Sep 2020 15:49:57 -0400 Received: from list by ciao.gmane.io with local (Exim 4.92) (envelope-from ) id 1kHuU4-00095D-4T for emacs-orgmode@gnu.org; Mon, 14 Sep 2020 21:49:52 +0200 X-Injected-Via-Gmane: http://gmane.org/ To: emacs-orgmode@gnu.org From: Eric Abrahamsen Subject: Re: [feature request] A new cookie type [!] showing the last note taken Date: Mon, 14 Sep 2020 12:49:38 -0700 Message-ID: <87h7s0yw8d.fsf@ericabrahamsen.net> References: <87zh6eymxs.fsf@localhost> <87h7skldpt.fsf@nicolasgoaziou.fr> <875z90xvqk.fsf@localhost> <871rjo8kgr.fsf@nicolasgoaziou.fr> <873644xr6y.fsf@localhost> <87sgc470lk.fsf@nicolasgoaziou.fr> <87y2lvwny0.fsf@localhost> <87lfhu7tgi.fsf@nicolasgoaziou.fr> <87mu29djhg.fsf@localhost> <87sgbzw77n.fsf@nicolasgoaziou.fr> <871rjgahyj.fsf@localhost> <87lfhjm4nl.fsf@nicolasgoaziou.fr> <877dt2j86g.fsf@localhost> <871rj5hlhv.fsf@nicolasgoaziou.fr> Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/28.0.50 (gnu/linux) Cancel-Lock: sha1:iQ64n11i9V7PSrnn8t+u66WgEnI= Received-SPF: pass client-ip=116.202.254.214; envelope-from=geo-emacs-orgmode@m.gmane-mx.org; helo=ciao.gmane.io X-detected-operating-system: by eggs.gnu.org: First seen = 2020/09/14 15:49:52 X-ACL-Warn: Detected OS = Linux 2.2.x-3.x [generic] [fuzzy] X-Spam_score_int: -16 X-Spam_score: -1.7 X-Spam_bar: - X-Spam_report: (-1.7 / 5.0 requ) BAYES_00=-1.9, HEADER_FROM_DIFFERENT_DOMAINS=0.249, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=no autolearn_force=no X-Spam_action: no action X-BeenThere: emacs-orgmode@gnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: "General discussions about Org-mode." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: emacs-orgmode-bounces+larch=yhetil.org@gnu.org Sender: "Emacs-orgmode" X-Scanner: scn0 Authentication-Results: aspmx1.migadu.com; dkim=none; dmarc=fail reason="SPF not aligned (relaxed), No valid DKIM" header.from=ericabrahamsen.net (policy=none); spf=pass (aspmx1.migadu.com: domain of emacs-orgmode-bounces@gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=emacs-orgmode-bounces@gnu.org X-Spam-Score: 0.59 X-TUID: 1bdstuCuNF3J --=-=-= Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit Nicolas Goaziou writes: > Hello, > > Ihor Radchenko writes: > >> I did not know this and I cannot find any reference about such behaviour >> in manual (info:org#Markup for Rich Contents). > > You can find it looking for "line break" in the index. It points there: > (info "(org) Paragraphs"). > >>>> However, it is unused it unordered lists. We might define a note as a >>>> unnumbered list item with [@note]: >>>> >>>> - [@note] This is note >>> >>> That's a reasonable syntax extension, maybe too English-centered. Maybe >>> a more abstract [@!] would be better. >> >> It also looks better for me. >> Should I open separate bug report proposing this syntax extension? > > Thinking again about it, I'm not sold about this idea. Is extending the > syntax the way to go? > > Sure, in the proposal above, it is relatively cheap. But what is the > meaning of an item marked as a "note"? Everything in an entry could be > a note; this is not limited to items. Moreover, if we are considering it > to be a note just because it was automatically inserted using > `org-add-log-note', then we are binding the tool to the syntax. This is > a mistake. > > I would like to reconsider the solution to your use case. With a non-nil > value for `org-log-into-drawer', it is possible to guess the location of > the last inserted note, assuming manually inserted ones—if there is > any—also follow the same pattern. Under such a reasonable configuration, > it should not be too difficult to write a function extracting the last > note, or a whole library operating on notes (ideas: move log-related > functions to a new "org-log.el" library, simplify it using Org Capture > as the machinery, extend it…). Here's code I've had in my config for quite a while. It gets the logbook, and shows the most recent item, bound to "V" in the agenda. It also includes an aborted function to do more thorough parsing of the log items -- I abandoned that because it was too gross. I actually also have a library called org-log.el around here somewhere, but that does quantitative logging of values, which I don't think has enough general interest. But I'm attaching that, too, just in case. Anyway, I'd be happy to work on this. (defun org-get-log-list () "Return the entry's log items, or nil. The items are returned as a list of 'item elements." (save-excursion (goto-char (org-log-beginning)) (let* ((el (org-element-at-point)) list-string list-items) ;; Should we try harder to make sure it's actually the log list? (when (eq (org-element-type el) 'plain-list) (setq list-string (buffer-substring (org-element-property :contents-begin el) (org-element-property :contents-end el))) (with-temp-buffer (insert list-string) (setq list-items (org-element-map (org-element-parse-buffer) 'item #'identity)))) list-items))) (defun org-agenda-show-log-item () "Echo the text of the entry's most recent log note." (interactive) (let (log-list item) (org-agenda-with-point-at-orig-entry nil (setq log-list (org-get-log-list))) (if (not log-list) (message "Entry has no log") (setq item (if org-log-states-order-reversed (car log-list) (last log-list))) (message (org-element-interpret-data (org-element-contents item)))))) (with-eval-after-load 'org-agenda (org-defkey org-agenda-mode-map "V" 'org-agenda-show-log-item)) (defun org-parse-log-list (log-list) "Turn a parsed plain log list (as returned by `org-get-log-list') into something more interesting. Specifically, this means to extract information from the plain text of each log item, and put it onto the item's property list. Right now this function extracts " (mapcar (lambda (item) (let* ((pars (org-element-map item 'paragraph #'identity)) (head-par (car pars)) ;; This is absolutely horrible: assume that, if the first ;; four characters of the log note heading match a value ;; from `org-log-note-headings', then that's the kind of ;; heading we've got. (horrible (mapcar (lambda (x) (cons (car x) (substring (cdr x) 0 3))) org-log-note-headings)) (item-type (or (rassoc (substring head-par 0 3) horrible) 'uncertain)) (timestamp (car-safe (org-element-map head-par 'timestamp #'identity))) state-from state-to schedule deadline) (cond ((eq item-type 'state) (setq state-to (and (string-match "State \\([^[:space:]]*\\) " head-par) (match-string 1 head-par))) (setq state-from (and (string-match "from \\([^[:space:]]*\\) " head-par) (match-string 1 head-par))) )))) log-list)) --=-=-= Content-Type: text/plain Content-Disposition: attachment; filename=org-log.el ;;; org-log.el --- Quantitative logging for Org headings -*- lexical-binding: t; -*- ;; Copyright (C) 2019 Free Software Foundation, Inc. ;; Author: Eric Abrahamsen ;; Maintainer: Eric Abrahamsen ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;; This library provides hooks and functions for quantitative logging ;; in Org: meaning that storing a log note may prompt for one or more ;; quantitative values, and store them as part of the note. Those ;; values can later be viewed as tables. Essentially this is a ;; generalization of org-clock, to allow logging of other values. ;; Like org-clock, it also includes a command to view logged data as ;; an Org table. ;; In terms of format, a logged value appears as an all-caps label, ;; followed by a colon, followed by the data itself, followed by a ;; semi-colon. For instance: ;; BP: 120/60; PULSE: 45; ;; The `org-log-buffer-setup-hook' is used to prompt the user for log ;; values to store. It does this by looking for a LOG_VALUES property ;; on the heading, which should be a space-separated list of all-caps ;; value labels. These labels can also contain information about the ;; units used for the value. Units are specified immediately after ;; the value label in square brackets, like so: ;; DISTANCE[mi] ;; This unit information will be automatically incorporated into ;; tables created to view logged data. Call the ;; `calc-view-units-table' command to see all valid units; you can ;; define new units using `calc-define-unit', which see. ;;; Code: (require 'org) (require 'calc-units) (defun org-log-prompt () "Prompt the user for log values. Insert values into the current log note." ;; This is called in the log buffer. Only fire for state and note; ;; later add clock-out. (when (memq org-log-note-purpose '(state note)) (let ((values (with-current-buffer (marker-buffer org-log-note-marker) (save-excursion (goto-char org-log-note-marker) (org-entry-get (point) "LOG_VALUES" 'selective))))) (when (and (stringp values) (null (string-empty-p values))) (unless (bolp) ; This might follow a clock line. (insert "; ")) (dolist (val (split-string values)) ;; Maybe strip off units. (setq val (substring val 0 (string-match-p "\\[" val))) (insert val ": ") (insert (read-string (format "%s: " val)) "; ")))))) (defun org-log--collect-data (id) "Collect log data from heading with id ID. When valid data is found, it is returned as a list of lists. Each sublist starts with the timestamp of the log entry, followed by data keys and values, in the order they were found in the log entry. If no valid data is found, return nil." (save-excursion (org-id-goto id) (goto-char (org-log-beginning)) (let* ((struct (org-list-struct)) (labels (org-entry-get (point) "LOG_VALUES" 'selective)) (entries (when (and (stringp labels) (null (string-empty-p labels))) ;; First element is a list of value labels. (list (cons "TIMESTAMP" (mapcar (lambda (str) (substring str 0 (string-match-p "\\[" str))) (split-string labels)))))) elt data) (when (and entries struct) ;; Get onto the list item. (forward-char) (while (equal 'item (org-element-type (setq elt (org-element-at-point)))) ;; Move past state/timestamp line. (forward-line) (while (re-search-forward "[[:upper:]]+: \\([^;]+\\)" (point-at-eol) t) (push (match-string-no-properties 1) data)) (when data (save-excursion (forward-line -1) ;; Get the log entry timestamp. (setq data (cons (if (re-search-forward org-ts-regexp-inactive (point-at-eol) t) (match-string-no-properties 0) "none") (nreverse data)))) (push data entries) (setq data nil)) (goto-char (org-element-property :end elt))) (nreverse entries))))) (defun org-log-create-dblock () "Create a dblock with a table for this heading's log data." (interactive) (let ((id (org-id-get-create))) ;; Anyway, this call is the heart of it. (org-create-dblock `(:name "log" :id ,id)) (org-update-dblock))) (defun org-dblock-write:log (params) "Write the log dblock table." (let ((data (org-log--collect-data (plist-get params :id)))) (when data (save-excursion (insert (format "|%s|\n" (mapconcat #'capitalize (pop data) "|")) "|-|\n" (mapconcat (lambda (row) (format "|%s|" (mapconcat #'identity row "|"))) data "\n"))) (org-table-align)))) (add-hook 'org-log-buffer-setup-hook #'org-log-prompt) (provide 'org-log) ;;; org-log.el ends here --=-=-=--