From mboxrd@z Thu Jan 1 00:00:00 1970 From: Ihor Radchenko Subject: Re: [RFC] Link-type for attachments, more attach options Date: Sat, 27 Jul 2019 22:56:17 +0800 Message-ID: <87y30j34ry.fsf@yantar92-laptop.i-did-not-set--mail-host-address--so-tickle-me> References: <87muqo8o68.fsf@nicolasgoaziou.fr> <877eh8vd52.fsf_-_@nicolasgoaziou.fr> <87y39euadx.fsf@nicolasgoaziou.fr> <87pnnfnfio.fsf@nicolasgoaziou.fr> <87tvby4z01.fsf@nicolasgoaziou.fr> Mime-Version: 1.0 Content-Type: text/plain Content-Transfer-Encoding: 8bit Return-path: Received: from eggs.gnu.org ([2001:470:142:3::10]:51572) by lists.gnu.org with esmtp (Exim 4.86_2) (envelope-from ) id 1hrO8j-0003B9-Kz for emacs-orgmode@gnu.org; Sat, 27 Jul 2019 10:57:51 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1hrO8Z-0008TI-6R for emacs-orgmode@gnu.org; Sat, 27 Jul 2019 10:57:41 -0400 Received: from mail-pf1-x431.google.com ([2607:f8b0:4864:20::431]:33493) by eggs.gnu.org with esmtps (TLS1.0:RSA_AES_128_CBC_SHA1:16) (Exim 4.71) (envelope-from ) id 1hrO8Y-0008Mk-E0 for emacs-orgmode@gnu.org; Sat, 27 Jul 2019 10:57:31 -0400 Received: by mail-pf1-x431.google.com with SMTP id g2so25874947pfq.0 for ; Sat, 27 Jul 2019 07:57:29 -0700 (PDT) In-Reply-To: List-Id: "General discussions about Org-mode." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: emacs-orgmode-bounces+geo-emacs-orgmode=m.gmane.org@gnu.org Sender: "Emacs-orgmode" To: Gustav =?utf-8?Q?Wikstr=C3=B6m?= , Nicolas Goaziou Cc: "emacs-orgmode@gnu.org" Hi, > ATTACH_DIR_INHERIT is no longer supported and is +removed. I just found that removing ATTACH_DIR_INHERIT broke my current configuration. I do not use ATTACH_DIR property - all the attachment folders are created using ID. Also, I use ID property to store links to entries. Therefore, inheriting ATTACH_DIR does nothing for me and inheriting ID always gives the current entry's id value. At the end, I cannot make a common attachment directory for the whole subtree, like I was able to do with ATTACH_DIR_INHERIT. Regards, Ihor Gustav Wikström writes: > Hi! > >> > + (if should-get >> > + (progn (message "Running git annex get \"%s\"." path-relative) >> > + (call-process "git" nil nil nil "annex" "get" path-relative)) >> > + (error "File %s stored in git annex but it is not available, and was not >> retrieved" >> > + path)))))) >> >> Nitpick: >> >> (unless should-get >> (error "File %S stored in git annex but unavailable" path)) >> (message "Running git annex get %S." path-relative) >> (call-process ...) > > Ok, fixed. > >> > +Selective means to respect the inheritance setting in >> > +`org-use-property-inheritance'." >> > :group 'org-attach >> > + :type '(choice >> > + (const :tag "Don't use inheritance" nil) >> > + (const :tag "Inherit parent node attachments" t) >> > + (const :tag "Respect org-use-property-inheritance" selective) >> > + ) >> >> Dangling paren spotted. > > Fixed. > >> > + (setq attachment (or (org-attach-dir) >> > + (quote "Can't find an existing attachment-folder"))) >> >> You forgot to remove that weird quote. Maybe you meant `error'? > > Hmm, actually no. But the code is pretty bad so I've refactored it a > bit. The purpose of the change is for org-attach to give an indication > of the active attachment path, or to signal that there is none. But > for that I don't really need a separate variable. Thus it's slightly > refactored for code-clarity. > >> > + (if attach-dir >> > + (progn (if (not (file-directory-p attach-dir)) >> > + (make-directory attach-dir t)) >> > + attach-dir) >> > + (error "No attachment directory is associated with the current node")))) >> >> Same nitpick as above: >> >> (unless attach-dir >> (error "No attachment ...")) >> (if (file-directory-p attach-dir) attach-dir >> (make-directory attach-dir)) > > Ok, fixed. > >> > +(defun org-attach-dir-from-id (id) >> > + "Creates a path based on `org-attach-id-dir' and ID." >> > + (expand-file-name >> > + (funcall org-attach-id-to-path-function id) >> > + (expand-file-name org-attach-id-dir))) >> >> Creates path -> Return a file name. > > Fixed. > >> > +of the entry. Creates relative links if `org-attach-dir-relative' >> > +is t. >> >> Nitpick: >> >> is t -> is non-nil. > > Ah, true. Fixed. > >> If tests pass, feel free to apply the patches in master. Thank you! > > Got it. Aaand one test failure. That test is unrelated to my changes > though, and fails also on master. Test-org-table/copy-down. So I'll > try to apply my patch asap regardless of that one test failing. > > Just one more thing - a few days back I added a row to lisp/ox-html.el > regarding inline-images. I'm including that change as well since it > relates 100% to the new attachment link. I looked in the other > export-backends too but didn't add anything due to lack of time for > testing. Maybe the additions for other backends is as trivial as for > html. So someone who regularly export to those backends might want to > suggest patches for them to make attachment links to images actually > display as images? > > Final patches attached for full disclosure before applying them. > >> >> Regards, >> >> -- >> Nicolas Goaziou > From 3cbe356b0a9d1a98848df0fa09ba306392995b88 Mon Sep 17 00:00:00 2001 > From: =?UTF-8?q?Gustav=20Wikstr=C3=B6m?= > Date: Sun, 26 May 2019 03:34:34 +0200 > Subject: [PATCH 1/2] org-test, test-org-element, test-org, test-ox, > test-property-inheritance > > * org-test.el: > > Fix org-test-with-temp-text-in-file. Make it work with , as > some tests already expect it to do! Also make it fail more gracefully > by still removing temporary buffers and files. > > Improve org-test-in-example-file. Make it behave similar to > org-test-with-temp-text and org-test-with-temp-text-in-file, in that > it will return the last evaluated expression. > > * testing/lisp/test-org-element.el > > Fix a temp-text strings so that it doesn't have an initial newline. > > * testing/lisp/test-org.el > > Minor cleanup to align code-structure with other tests. Nothing > changes in the test execpt style. > > * testing/lisp/test-ox.el > > Fix a couple of temp-text strings so that they don't have initial > newlines. > > ** test-org-export/expand-include > > Test specification was wrong, due to org-test-with-temp-text-in-file > not previously working with . Since that is fixed in this > patch the test needed to be updated to match the expected outcome. > > * testing/lisp/test-property-inheritance.el > > Fix wrong file-header and file-ending. > --- > testing/lisp/test-org-element.el | 4 +-- > testing/lisp/test-org.el | 12 +++---- > testing/lisp/test-ox.el | 30 ++++++++-------- > testing/lisp/test-property-inheritance.el | 4 +-- > testing/org-test.el | 44 ++++++++++++++--------- > 5 files changed, 53 insertions(+), 41 deletions(-) > > diff --git a/testing/lisp/test-org-element.el b/testing/lisp/test-org-element.el > index 04f97f97a..f2ab38031 100644 > --- a/testing/lisp/test-org-element.el > +++ b/testing/lisp/test-org-element.el > @@ -3264,8 +3264,8 @@ DEADLINE: <2012-03-29 thu.> SCHEDULED: <2012-03-29 thu.> CLOSED: [2012-03-29 thu > > (ert-deftest test-org-element/granularity () > "Test granularity impact on buffer parsing." > - (org-test-with-temp-text " > -* Head 1 > + (org-test-with-temp-text > + "* Head 1 > ** Head 2 > #+BEGIN_CENTER > Centered paragraph. > diff --git a/testing/lisp/test-org.el b/testing/lisp/test-org.el > index abc004689..d671b5c78 100644 > --- a/testing/lisp/test-org.el > +++ b/testing/lisp/test-org.el > @@ -5060,18 +5060,18 @@ Paragraph" > (should > (equal > "1" > - (org-test-with-temp-text "* H\n:PROPERTIES:\n:A: 1\n:END:\n** H2" > - (org-entry-get (point) "A" t)))) > + (org-test-with-temp-text "* H\n:PROPERTIES:\n:A: 1\n:END:\n** H2" > + (org-entry-get (point-max) "A" t)))) > (should > (equal > "1" > - (org-test-with-temp-text "* H\n:PROPERTIES:\n:A: 1\n:END:\n** H2" > + (org-test-with-temp-text "* H\n:PROPERTIES:\n:A: 1\n:END:\n** H2" > (let ((org-use-property-inheritance t)) > - (org-entry-get (point) "A" 'selective))))) > + (org-entry-get (point-max) "A" 'selective))))) > (should-not > - (org-test-with-temp-text "* H\n:PROPERTIES:\n:A: 1\n:END:\n** H2" > + (org-test-with-temp-text "* H\n:PROPERTIES:\n:A: 1\n:END:\n** H2" > (let ((org-use-property-inheritance nil)) > - (org-entry-get (point) "A" 'selective)))) > + (org-entry-get (point-max) "A" 'selective)))) > (should > (equal > "1 2" > diff --git a/testing/lisp/test-ox.el b/testing/lisp/test-ox.el > index 2c2778f82..b8c507e0b 100644 > --- a/testing/lisp/test-ox.el > +++ b/testing/lisp/test-ox.el > @@ -1352,7 +1352,7 @@ Footnotes[fn:2], foot[fn:test] and [fn:inline:inline footnote] > org-test-dir) > (narrow-to-region (point) (point-max)) > (org-export-expand-include-keyword) > - (eq 1 (org-current-level)))) > + (eq 2 (org-current-level)))) > ;; If :minlevel is present do not alter it. > (should > (org-test-with-temp-text > @@ -2003,8 +2003,8 @@ In particular, structure of the document mustn't be altered after > comments removal." > (should > (equal "Para1\n\nPara2\n" > - (org-test-with-temp-text " > -Para1 > + (org-test-with-temp-text > + "Para1 > # Comment > > # Comment > @@ -2012,15 +2012,15 @@ Para2" > (org-export-as (org-test-default-backend))))) > (should > (equal "Para1\n\nPara2\n" > - (org-test-with-temp-text " > -Para1 > + (org-test-with-temp-text > + "Para1 > # Comment > Para2" > (org-export-as (org-test-default-backend))))) > (should > (equal "[fn:1] Para1\n\n\nPara2\n" > - (org-test-with-temp-text " > -\[fn:1] Para1 > + (org-test-with-temp-text > + "[fn:1] Para1 > # Inside definition > > > @@ -2029,8 +2029,8 @@ Para2" > (org-export-as (org-test-default-backend))))) > (should > (equal "[fn:1] Para1\n\nPara2\n" > - (org-test-with-temp-text " > -\[fn:1] Para1 > + (org-test-with-temp-text > + "[fn:1] Para1 > > # Inside definition > > @@ -2040,24 +2040,24 @@ Para2" > (org-export-as (org-test-default-backend))))) > (should > (equal "[fn:1] Para1\n\nPara2\n" > - (org-test-with-temp-text " > -\[fn:1] Para1 > + (org-test-with-temp-text > + "[fn:1] Para1 > # Inside definition > > Para2" > (org-export-as (org-test-default-backend))))) > (should > (equal "[fn:1] Para1\n\nPara2\n" > - (org-test-with-temp-text " > -\[fn:1] Para1 > + (org-test-with-temp-text > + "[fn:1] Para1 > > # Inside definition > Para2" > (org-export-as (org-test-default-backend))))) > (should > (equal "- item 1\n\n- item 2\n" > - (org-test-with-temp-text " > -- item 1 > + (org-test-with-temp-text > + "- item 1 > > # Comment > > diff --git a/testing/lisp/test-property-inheritance.el b/testing/lisp/test-property-inheritance.el > index 0f803e5f7..1d0dcfbe1 100644 > --- a/testing/lisp/test-property-inheritance.el > +++ b/testing/lisp/test-property-inheritance.el > @@ -1,4 +1,4 @@ > -;;; test-ob-R.el --- tests for ob-R.el > +;;; test-property-inheritance.el --- tests for property-inheritance.el > > ;; Copyright (c) 2011-2014, 2019 Eric Schulte > ;; Authors: Eric Schulte > @@ -47,4 +47,4 @@ > > (provide 'test-ob-R) > > -;;; test-ob-R.el ends here > +;;; test-property-inheritance.el ends here > diff --git a/testing/org-test.el b/testing/org-test.el > index 39c346410..295df1919 100644 > --- a/testing/org-test.el > +++ b/testing/org-test.el > @@ -146,7 +146,8 @@ currently executed.") > (declare (indent 1)) > `(let* ((my-file (or ,file org-test-file)) > (visited-p (get-file-buffer my-file)) > - to-be-removed) > + to-be-removed > + results) > (save-window-excursion > (save-match-data > (find-file my-file) > @@ -160,9 +161,10 @@ currently executed.") > (org-show-subtree) > (org-show-all '(blocks))) > (error nil)) > - (save-restriction ,@body))) > + (setq results (save-restriction ,@body)))) > (unless visited-p > - (kill-buffer to-be-removed)))) > + (kill-buffer to-be-removed)) > + results)) > (def-edebug-spec org-test-in-example-file (form body)) > > (defmacro org-test-at-marker (file marker &rest body) > @@ -198,20 +200,30 @@ otherwise place the point at the beginning of the inserted text." > (def-edebug-spec org-test-with-temp-text (form body)) > > (defmacro org-test-with-temp-text-in-file (text &rest body) > - "Run body in a temporary file buffer with Org mode as the active mode." > + "Run body in a temporary file buffer with Org mode as the active mode. > +If the string \"\" appears in TEXT then remove it and > +place the point there before running BODY, otherwise place the > +point at the beginning of the buffer." > (declare (indent 1)) > - (let ((results (cl-gensym))) > - `(let ((file (make-temp-file "org-test")) > - (kill-buffer-query-functions nil) > - (inside-text (if (stringp ,text) ,text (eval ,text))) > - ,results) > - (with-temp-file file (insert inside-text)) > - (find-file file) > - (org-mode) > - (setq ,results (progn ,@body)) > - (save-buffer) (kill-buffer (current-buffer)) > - (delete-file file) > - ,results))) > + `(let ((file (make-temp-file "org-test")) > + (inside-text (if (stringp ,text) ,text (eval ,text))) > + buffer) > + (with-temp-file file (insert inside-text)) > + (unwind-protect > + (progn > + (setq buffer (find-file file)) > + (when (re-search-forward "" nil t) > + (replace-match "")) > + (org-mode) > + (progn ,@body)) > + (let ((kill-buffer-query-functions nil)) > + (when buffer > + (set-buffer buffer) > + ;; Ignore changes, we're deleting the file in the next step > + ;; anyways. > + (set-buffer-modified-p nil) > + (kill-buffer)) > + (delete-file file))))) > (def-edebug-spec org-test-with-temp-text-in-file (form body)) > > (defun org-test-table-target-expect (target &optional expect laps > -- > 2.17.1 > > From ae9cd4370b4daaaca7bc53923d5e438c08955e48 Mon Sep 17 00:00:00 2001 > From: =?UTF-8?q?Gustav=20Wikstr=C3=B6m?= > Date: Sun, 25 Nov 2018 21:38:44 +0100 > Subject: [PATCH 2/2] org-attach*, org, org-manual, org-news, ox-html, > testing/* > > * lisp/org-attach.el > > Changed the way attachments deal with property-inheritance. It now > adheres to the =org-use-property-inheritance= setting by default but > it can be customized if needed (I recommend to enable it!). > The property ATTACH_DIR is deprecated in favour of the shorter and simpler > property DIR. > > Added an explicit option to =org-attach= for unsetting > attachment-directories (i.e. remove DIR property and deal with the > attachments by interaction). > > Added attachment link type with the prefix "attachment:". > > Added customizations: > - org-attach-dir-relative > - org-attach-preferred-new-method > - org-attach-use-inheritance > - org-attach-id-to-path-function > > Hooks added: > - org-attach-after-change-hook > - org-attach-open-hook > > A new linktype "attachment" is added in order to reduce > link-duplication when wanting to link to files in attached folders of > nodes. This works for both ID and DIR properties. The goal is to > make the functionality for attachment links mirror the functionality > for file links. > > * lisp/org-attach-git.el > > New file, existing functionality. Code here has been factored out > from org-attach.el and if GIT-functionality is to be used this module > needs to be required sepatately. It extends org-attach by use of its > hooks. > > Activating git functionality in org-attach is done by loading > org-attach-git from now on, instead of customizing a variable. > > Naming of both functions and tests has been modified to match the move > of functionality into its own module. > > * lisp/org.el > > Inline images are shown also using attachment-links, exactly the same > as it works for file-links today. > > Make org-open-at-point respect ARG when opening attachment-dir. > > * lisp/org-compat.el > > org-attach-directory has been deprecated in favour for > org-attach-id-dir. The new name matches its purpose better. > > * lisp/ox-html.el > > Export attachment links to images as inline images, in the same way as > file links work today. > > * etc/ORG-NEWS > > Mention the changes in this patch. > > * doc/org-manual.org > > The chapter "Refile, Copy, Archive" has been split into two separate > chapters. > - "Refile, Copy and Archiving" for information related to moving > existing data around. > > - "Capture, Attachments, RSS Feeds and Protocols" for information > related to working with external data. > > The attachment-part has been rewritten and extended to match the > changes in this patch. > > The new attachment link type is mentioned both inside the attachments > chapter and in the chapter dealing with links. > > Documentation related to external links has been improved. > > * testing/lisp/test-org-attach-annex.el > > Require org-attach-git instead of org-attach, since this file tests > the GIT-functionality. > > * testing/lisp/test-org-attach.el > > Add tests for org-attach. > > * testing/org-test.el > > Define a symbol for a file to test attachments with. > > * testing/examples/* > > A bunch of new example files and folders are created and are used in > testing of org-attach to verify its functionality. > --- > doc/org-manual.org | 1079 ++++++++++------- > etc/ORG-NEWS | 94 ++ > lisp/org-attach-git.el | 117 ++ > lisp/org-attach.el | 561 +++++---- > lisp/org-compat.el | 3 + > lisp/org.el | 23 +- > lisp/ox-html.el | 1 + > testing/examples/att1/fileA | 1 + > testing/examples/att1/fileB | 1 + > testing/examples/att2/fileC | 1 + > testing/examples/att2/fileD | 1 + > testing/examples/attachments.org | 32 + > testing/examples/data/ab/cd123/fileE | 1 + > ...attach-annex.el => test-org-attach-git.el} | 12 +- > testing/lisp/test-org-attach.el | 69 ++ > testing/lisp/test-org.el | 6 +- > testing/org-test.el | 3 + > 17 files changed, 1307 insertions(+), 698 deletions(-) > create mode 100644 lisp/org-attach-git.el > create mode 100644 testing/examples/att1/fileA > create mode 100644 testing/examples/att1/fileB > create mode 100644 testing/examples/att2/fileC > create mode 100644 testing/examples/att2/fileD > create mode 100644 testing/examples/attachments.org > create mode 100644 testing/examples/data/ab/cd123/fileE > rename testing/lisp/{test-org-attach-annex.el => test-org-attach-git.el} (93%) > > diff --git a/doc/org-manual.org b/doc/org-manual.org > index 8318e7cdc..e81b76f14 100644 > --- a/doc/org-manual.org > +++ b/doc/org-manual.org > @@ -3071,6 +3071,7 @@ point on or at a target. > #+cindex: irc links > #+cindex: URL links > #+cindex: file links > +#+cindex: attachment links > #+cindex: Rmail links > #+cindex: MH-E links > #+cindex: Usenet links > @@ -3082,38 +3083,114 @@ Org supports links to files, websites, Usenet and email messages, BBDB > database entries and links to both IRC conversations and their logs. > External links are URL-like locators. They start with a short > identifying string followed by a colon. There can be no space after > -the colon. The following list shows examples for each link type. > - > -| =https://staff.science.uva.nl/c.dominik/= | on the web | > -| =doi:10.1000/182= | DOI for an electronic resource | > -| =file:/home/dominik/images/jupiter.jpg= | file, absolute path | > -| =/home/dominik/images/jupiter.jpg= | same as above | > -| =file:papers/last.pdf= | file, relative path | > -| =./papers/last.pdf= | same as above | > -| =file:/ssh:me@some.where:papers/last.pdf= | file, path on remote machine | > -| =/ssh:me@some.where:papers/last.pdf= | same as above | > -| =file:sometextfile::NNN= | file, jump to line number | > -| =file:projects.org= | another Org file | > -| =file:projects.org::some words= | text search in Org file[fn:27] | > -| =file:projects.org::*task title= | heading search in Org file | > -| =file+sys:/path/to/file= | open via OS, like double-click | > -| =file+emacs:/path/to/file= | force opening by Emacs | > -| =docview:papers/last.pdf::NNN= | open in doc-view mode at page | > -| =id:B7423F4D-2E8A-471B-8810-C40F074717E9= | link to heading by ID | > -| =news:comp.emacs= | Usenet link | > -| =mailto:adent@galaxy.net= | mail link | > -| =mhe:folder= | MH-E folder link | > -| =mhe:folder#id= | MH-E message link | > -| =rmail:folder= | Rmail folder link | > -| =rmail:folder#id= | Rmail message link | > -| =gnus:group= | Gnus group link | > -| =gnus:group#id= | Gnus article link | > -| =bbdb:R.*Stallman= | BBDB link (with regexp) | > -| =irc:/irc.com/#emacs/bob= | IRC link | > -| =info:org#External links= | Info node link | > -| =shell:ls *.org= | shell command | > -| =elisp:org-agenda= | interactive Elisp command | > -| =elisp:(find-file "Elisp.org")= | Elisp form to evaluate | > +the colon. > + > +Here is the full set of built-in link types: > + > +- =file= :: > + > + File links. File name may be remote, absolute, or relative. > + > + Additionally, you can specify a line number, or a text search. > + In Org files, you may link to a headline name, a custom ID, or a > + code reference instead. > + > + As a special case, "file" prefix may be omitted if the file name > + is complete, e.g., it starts with =./=, or =/=. > + > +- =attachment= :: > + > + Same as file links but for files and folders attached to the current > + node (see [[*Attachments]]). Attachment links are intended to behave > + exactly as file links but for files relative to the attachment > + directory. > + > +- =bbdb= :: > + > + Link to a BBDB record, with possible regexp completion. > + > +- =docview= :: > + > + Link to a document opened with DocView mode. You may specify a page > + number. > + > +- =doi= :: > + > + Link to an electronic ressource, through its handle. > + > +- =elisp= :: > + > + Execute an Elisp command upon activation. > + > +- =gnus=, =rmail=, =mhe= :: > + > + Links to messages or folders from a given Emacs' MUA. > + > +- =http=, =https= :: > + > + Web links. > + > +- =id= :: > + > + Link to a specific headline by its ID property, in an Org file. > + > +- =info= :: > + > + Link to an Info manual, or to a specific node. > + > +- =irc= :: > + > + Link to an IRC channel. > + > +- =mailto= :: > + > + Link to message composition. > + > +- =news= :: > + > + Usenet links. > + > +- =shell= :: > + > + Execute a shell command upon activation. > + > +The following table illustrates the link types above, along with their > +options: > + > +| Link Type | Example | > +|------------+----------------------------------------------------------| > +| http | =http://staff.science.uva.nl/c.dominik/= | > +| https | =https://orgmode.org/= | > +| doi | =doi:10.1000/182= | > +| file | =file:/home/dominik/images/jupiter.jpg= | > +| | =/home/dominik/images/jupiter.jpg= (same as above) | > +| | =file:papers/last.pdf= | > +| | =./papers/last.pdf= (same as above) | > +| | =file:/ssh:me@some.where:papers/last.pdf= (remote) | > +| | =/ssh:me@some.where:papers/last.pdf= (same as above) | > +| | =file:sometextfile::NNN= (jump to line number) | > +| | =file:projects.org= | > +| | =file:projects.org::some words= (text search) [fn:28] | > +| | =file:projects.org::*task title= (headline search) | > +| | =file:projects.org::#custom-id= (headline search) | > +| attachment | =attachment:projects.org= | > +| | =attachment:projects.org::some words= (text search) | > +| docview | =docview:papers/last.pdf::NNN= | > +| id | =id:B7423F4D-2E8A-471B-8810-C40F074717E9= | > +| news | =news:comp.emacs= | > +| mailto | =mailto:adent@galaxy.net= | > +| mhe | =mhe:folder= (folder link) | > +| | =mhe:folder#id= (message link) | > +| rmail | =rmail:folder= (folder link) | > +| | =rmail:folder#id= (message link) | > +| gnus | =gnus:group= (group link) | > +| | =gnus:group#id= (article link) | > +| bbdb | =bbdb:R.*Stallman= (record with regexp) | > +| irc | =irc:/irc.com/#emacs/bob= | > +| info | =info:org#External links= | > +| shell | =shell:ls *.org= | > +| elisp | =elisp:(find-file "Elisp.org")= (Elisp form to evaluate) | > +| | =elisp:org-agenda= (interactive Elisp command) | > > #+cindex: VM links > #+cindex: Wanderlust links > @@ -3465,15 +3542,19 @@ the link completion function like this: > :END: > #+cindex: search option in file links > #+cindex: file links, searching > +#+cindex: attachment links, searching > > -File links can contain additional information to make Emacs jump to > -a particular location in the file when following a link. This can be > -a line number or a search option after a double colon[fn:34]. For > +File links can contain additional information to make Emacs jump to a > +particular location in the file when following a link. This can be a > +line number or a search option after a double colon[fn:34]. For > example, when the command ~org-store-link~ creates a link (see > [[*Handling Links]]) to a file, it encodes the words in the current line > as a search string that can be used to find this line back later when > following the link with {{{kbd(C-c C-o)}}}. > > +Note that all search options apply for Attachment links in the same > +way that they apply for File links. > + > Here is the syntax of the different ways to attach a search to a file > link, together with explanations for each: > > @@ -3483,6 +3564,7 @@ link, together with explanations for each: > [[file:~/xx.org::*My Target]] > [[file:~/xx.org::#my-custom-id]] > [[file:~/xx.org::/regexp/]] > +[[attachment:main.c::255]] > #+end_example > > - =255= :: > @@ -6896,163 +6978,420 @@ same commands. > continue the old one. This command also removes the timer from the > mode line. > > -* Capture, Refile, Archive > -:PROPERTIES: > -:DESCRIPTION: The ins and outs for projects. > -:END: > -#+cindex: capture > - > -An important part of any organization system is the ability to quickly > -capture new ideas and tasks, and to associate reference material with > -them. Org does this using a process called /capture/. It also can > -store files related to a task (/attachments/) in a special directory. > -Once in the system, tasks and projects need to be moved around. > -Moving completed project trees to an archive file keeps the system > -compact and fast. > - > -** Capture > +* Refile, Copy and Archiving > :PROPERTIES: > -:DESCRIPTION: Capturing new stuff. > +:DESCRIPTION: Moving and copying information with ease. > :END: > -#+cindex: capture > +#+cindex: refiling notes > +#+cindex: copying notes > +#+cindex: archiving > > -Capture lets you quickly store notes with little interruption of your > -work flow. Org's method for capturing new items is heavily inspired > -by John Wiegley's excellent Remember package. > +Once information is in the system, it may need to be moved around. > +Org provides Refile, Copy and Archive commands for this. Refile and > +Copy helps with moving and copying outlines. Archiving helps to keep > +the system compact and fast. > > -*** Setting up capture > +** Refile and Copy > :PROPERTIES: > -:DESCRIPTION: Where notes will be stored. > +:DESCRIPTION: Moving/copying a tree from one place to another. > :END: > +#+cindex: refiling notes > +#+cindex: copying notes > > -The following customization sets a default target file for notes. > +When reviewing the captured data, you may want to refile or to copy > +some of the entries into a different list, for example into a project. > +Cutting, finding the right location, and then pasting the note is > +cumbersome. To simplify this process, you can use the following > +special command: > > -#+vindex: org-default-notes-file > -#+begin_src emacs-lisp > -(setq org-default-notes-file (concat org-directory "/notes.org")) > -#+end_src > +- {{{kbd(C-c C-w)}}} (~org-refile~) :: > > -You may also define a global key for capturing new material (see > -[[*Activation]]). > + #+kindex: C-c C-w > + #+findex: org-refile > + #+vindex: org-reverse-note-order > + #+vindex: org-refile-targets > + #+vindex: org-refile-use-outline-path > + #+vindex: org-outline-path-complete-in-steps > + #+vindex: org-refile-allow-creating-parent-nodes > + #+vindex: org-log-refile > + Refile the entry or region at point. This command offers possible > + locations for refiling the entry and lets you select one with > + completion. The item (or all items in the region) is filed below > + the target heading as a subitem. Depending on > + ~org-reverse-note-order~, it is either the first or last subitem. > > -*** Using capture > -:PROPERTIES: > -:DESCRIPTION: Commands to invoke and terminate capture. > -:END: > + By default, all level 1 headlines in the current buffer are > + considered to be targets, but you can have more complex definitions > + across a number of files. See the variable ~org-refile-targets~ for > + details. If you would like to select a location via > + a file-path-like completion along the outline path, see the > + variables ~org-refile-use-outline-path~ and > + ~org-outline-path-complete-in-steps~. If you would like to be able > + to create new nodes as new parents for refiling on the fly, check > + the variable ~org-refile-allow-creating-parent-nodes~. When the > + variable ~org-log-refile~[fn:88] is set, a timestamp or a note is > + recorded whenever an entry is refiled. > > -- {{{kbd(M-x org-capture)}}} (~org-capture~) :: > +- {{{kbd(C-u C-c C-w)}}} :: > > - #+findex: org-capture > - #+cindex: date tree > - Display the capture templates menu. If you have templates defined > - (see [[*Capture templates]]), it offers these templates for selection or > - use a new Org outline node as the default template. It inserts the > - template into the target file and switch to an indirect buffer > - narrowed to this new node. You may then insert the information you > - want. > + #+kindex: C-u C-c C-w > + Use the refile interface to jump to a heading. > > -- {{{kbd(C-c C-c)}}} (~org-capture-finalize~) :: > +- {{{kbd(C-u C-u C-c C-w)}}} (~org-refile-goto-last-stored~) :: > > - #+kindex: C-c C-c @r{(Capture buffer)} > - #+findex: org-capture-finalize > - Once you have finished entering information into the capture buffer, > - {{{kbd(C-c C-c)}}} returns you to the window configuration before > - the capture process, so that you can resume your work without > - further distraction. When called with a prefix argument, finalize > - and then jump to the captured item. > + #+kindex: C-u C-u C-c C-w > + #+findex: org-refile-goto-last-stored > + Jump to the location where ~org-refile~ last moved a tree to. > > -- {{{kbd(C-c C-w)}}} (~org-capture-refile~) :: > +- {{{kbd(C-2 C-c C-w)}}} :: > > - #+kindex: C-c C-w @r{(Capture buffer)} > - #+findex: org-capture-refile > - Finalize the capture process by refiling the note to a different > - place (see [[*Refile and Copy]]). Please realize that this is a normal > - refiling command that will be executed---so point position at the > - moment you run this command is important. If you have inserted > - a tree with a parent and children, first move point back to the > - parent. Any prefix argument given to this command is passed on to > - the ~org-refile~ command. > + #+kindex: C-2 C-c C-w > + Refile as the child of the item currently being clocked. > > -- {{{kbd(C-c C-k)}}} (~org-capture-kill~) :: > +- {{{kbd(C-3 C-c C-w)}}} :: > > - #+kindex: C-c C-k @r{(Capture buffer)} > - #+findex: org-capture-kill > - Abort the capture process and return to the previous state. > + #+kindex: C-3 C-c C-w > + #+vindex: org-refile-keep > + Refile and keep the entry in place. Also see ~org-refile-keep~ to > + make this the default behavior, and beware that this may result in > + duplicated =ID= properties. > > -#+kindex: k c @r{(Agenda)} > -You can also call ~org-capture~ in a special way from the agenda, > -using the {{{kbd(k c)}}} key combination. With this access, any > -timestamps inserted by the selected capture template defaults to the > -date at point in the agenda, rather than to the current date. > +- {{{kbd(C-0 C-c C-w)}}} or {{{kbd(C-u C-u C-u C-c C-w)}}} (~org-refile-cache-clear~) :: > > -To find the locations of the last stored capture, use ~org-capture~ > -with prefix commands: > + #+kindex: C-u C-u C-u C-c C-w > + #+kindex: C-0 C-c C-w > + #+findex: org-refile-cache-clear > + #+vindex: org-refile-use-cache > + Clear the target cache. Caching of refile targets can be turned on > + by setting ~org-refile-use-cache~. To make the command see new > + possible targets, you have to clear the cache with this command. > > -- {{{kbd(C-u M-x org-capture)}}} :: > +- {{{kbd(C-c M-w)}}} (~org-copy~) :: > > - Visit the target location of a capture template. You get to select > - the template in the usual way. > + #+kindex: C-c M-w > + #+findex: org-copy > + Copying works like refiling, except that the original note is not > + deleted. > > -- {{{kbd(C-u C-u M-x org-capture)}}} :: > +** Archiving > +:PROPERTIES: > +:DESCRIPTION: What to do with finished products. > +:END: > +#+cindex: archiving > > - Visit the last stored capture item in its buffer. > +When a project represented by a (sub)tree is finished, you may want to > +move the tree out of the way and to stop it from contributing to the > +agenda. Archiving is important to keep your working files compact and > +global searches like the construction of agenda views fast. > > -#+vindex: org-capture-bookmark > -#+vindex: org-capture-last-stored > -You can also jump to the bookmark ~org-capture-last-stored~, which is > -automatically created unless you set ~org-capture-bookmark~ to ~nil~. > +- {{{kbd(C-c C-x C-a)}}} (~org-archive-subtree-default~) :: > > -To insert the capture at point in an Org buffer, call ~org-capture~ > -with a {{{kbd(C-0)}}} prefix argument. > + #+kindex: C-c C-x C-a > + #+findex: org-archive-subtree-default > + #+vindex: org-archive-default-command > + Archive the current entry using the command specified in the > + variable ~org-archive-default-command~. > > -*** Capture templates > +*** Moving a tree to an archive file > :PROPERTIES: > -:DESCRIPTION: Define the outline of different note types. > +:DESCRIPTION: Moving a tree to an archive file. > +:ALT_TITLE: Moving subtrees > :END: > -#+cindex: templates, for Capture > - > -You can use templates for different types of capture items, and for > -different target locations. The easiest way to create such templates > -is through the customize interface. > +#+cindex: external archiving > > -- {{{kbd(C)}}} :: > +The most common archiving action is to move a project tree to another > +file, the archive file. > > - #+kindex: C @r{(Capture menu} > - #+vindex: org-capture-templates > - Customize the variable ~org-capture-templates~. > +- {{{kbd(C-c C-x C-s)}}} or short {{{kbd(C-c $)}}} (~org-archive-subtree~) :: > > -Before we give the formal description of template definitions, let's > -look at an example. Say you would like to use one template to create > -general TODO entries, and you want to put these entries under the > -heading =Tasks= in your file =~/org/gtd.org=. Also, a date tree in > -the file =journal.org= should capture journal entries. A possible > -configuration would look like: > + #+kindex: C-c C-x C-s > + #+kindex: C-c $ > + #+findex: org-archive-subtree > + #+vindex: org-archive-location > + Archive the subtree starting at point position to the location given > + by ~org-archive-location~. > > -#+begin_src emacs-lisp > -(setq org-capture-templates > - '(("t" "Todo" entry (file+headline "~/org/gtd.org" "Tasks") > - "* TODO %?\n %i\n %a") > - ("j" "Journal" entry (file+datetree "~/org/journal.org") > - "* %?\nEntered on %U\n %i\n %a"))) > -#+end_src > +- {{{kbd(C-u C-c C-x C-s)}}} :: > > -If you then press {{{kbd(t)}}} from the capture menu, Org will prepare > -the template for you like this: > + #+kindex: C-u C-c C-x C-s > + Check if any direct children of the current headline could be moved > + to the archive. To do this, check each subtree for open TODO > + entries. If none is found, the command offers to move it to the > + archive location. If point is /not/ on a headline when this command > + is invoked, check level 1 trees. > > -#+begin_example > -,* TODO > - [[file:LINK TO WHERE YOU INITIATED CAPTURE]] > -#+end_example > +- {{{kbd(C-u C-u C-c C-x C-s)}}} :: > > -#+texinfo: @noindent > -During expansion of the template, =%a= has been replaced by a link to > -the location from where you called the capture command. This can be > -extremely useful for deriving tasks from emails, for example. You > -fill in the task definition, press {{{kbd(C-c C-c)}}} and Org returns > -you to the same place where you started the capture process. > + #+kindex: C-u C-u C-c C-x C-s > + As above, but check subtree for timestamps instead of TODO entries. > + The command offers to archive the subtree if it /does/ contain > + a timestamp, and that timestamp is in the past. > > -To define special keys to capture to a particular template without > -going through the interactive template selection, you can create your > +#+cindex: archive locations > +The default archive location is a file in the same directory as the > +current file, with the name derived by appending =_archive= to the > +current file name. You can also choose what heading to file archived > +items under, with the possibility to add them to a datetree in a file. > +For information and examples on how to specify the file and the > +heading, see the documentation string of the variable > +~org-archive-location~. > + > +There is also an in-buffer option for setting this variable, for > +example: > + > +#+cindex: @samp{ARCHIVE}, keyword > +: #+ARCHIVE: %s_done:: > + > +#+cindex: ARCHIVE, property > +If you would like to have a special archive location for a single > +entry or a (sub)tree, give the entry an =ARCHIVE= property with the > +location as the value (see [[*Properties and Columns]]). > + > +#+vindex: org-archive-save-context-info > +When a subtree is moved, it receives a number of special properties > +that record context information like the file from where the entry > +came, its outline path the archiving time etc. Configure the variable > +~org-archive-save-context-info~ to adjust the amount of information > +added. > + > +*** Internal archiving > +:PROPERTIES: > +:DESCRIPTION: Switch off a tree but keep it in the file. > +:END: > + > +#+cindex: @samp{ARCHIVE}, tag > +If you want to just switch off---for agenda views---certain subtrees > +without moving them to a different file, you can use the =ARCHIVE= > +tag. > + > +A headline that is marked with the =ARCHIVE= tag (see [[*Tags]]) stays at > +its location in the outline tree, but behaves in the following way: > + > +- > + #+vindex: org-cycle-open-archived-trees > + It does not open when you attempt to do so with a visibility cycling > + command (see [[*Visibility Cycling]]). You can force cycling archived > + subtrees with {{{kbd(C-TAB)}}}, or by setting the option > + ~org-cycle-open-archived-trees~. Also normal outline commands, like > + ~outline-show-all~, open archived subtrees. > + > +- > + #+vindex: org-sparse-tree-open-archived-trees > + During sparse tree construction (see [[*Sparse Trees]]), matches in > + archived subtrees are not exposed, unless you configure the option > + ~org-sparse-tree-open-archived-trees~. > + > +- > + #+vindex: org-agenda-skip-archived-trees > + During agenda view construction (see [[*Agenda Views]]), the content of > + archived trees is ignored unless you configure the option > + ~org-agenda-skip-archived-trees~, in which case these trees are > + always included. In the agenda you can press {{{kbd(v a)}}} to get > + archives temporarily included. > + > +- > + #+vindex: org-export-with-archived-trees > + Archived trees are not exported (see [[*Exporting]]), only the headline > + is. Configure the details using the variable > + ~org-export-with-archived-trees~. > + > +- > + #+vindex: org-columns-skip-archived-trees > + Archived trees are excluded from column view unless the variable > + ~org-columns-skip-archived-trees~ is configured to ~nil~. > + > +The following commands help manage the =ARCHIVE= tag: > + > +- {{{kbd(C-c C-x a)}}} (~org-toggle-archive-tag~) :: > + > + #+kindex: C-c C-x a > + #+findex: org-toggle-archive-tag > + Toggle the archive tag for the current headline. When the tag is > + set, the headline changes to a shadowed face, and the subtree below > + it is hidden. > + > +- {{{kbd(C-u C-c C-x a)}}} :: > + > + #+kindex: C-u C-c C-x a > + Check if any direct children of the current headline should be > + archived. To do this, check each subtree for open TODO entries. If > + none is found, the command offers to set the =ARCHIVE= tag for the > + child. If point is /not/ on a headline when this command is > + invoked, check the level 1 trees. > + > +- {{{kbd(C-TAB)}}} (~org-force-cycle-archived~) :: > + > + #+kindex: C-TAB > + Cycle a tree even if it is tagged with =ARCHIVE=. > + > +- {{{kbd(C-c C-x A)}}} (~org-archive-to-archive-sibling~) :: > + > + #+kindex: C-c C-x A > + #+findex: org-archive-to-archive-sibling > + Move the current entry to the /Archive Sibling/. This is a sibling > + of the entry with the heading =Archive= and the archive tag. The > + entry becomes a child of that sibling and in this way retains a lot > + of its original context, including inherited tags and approximate > + position in the outline. > + > +* Capture, Attachments, RSS Feeds and Protocols > +:PROPERTIES: > +:DESCRIPTION: Dealing with external data. > +:END: > +#+cindex: capture > +#+cindex: attachments > +#+cindex: RSS feeds > +#+cindex: Atom feeds > +#+cindex: protocols, for external access > + > +An important part of any organization system is the ability to quickly > +capture new ideas and tasks, and to associate reference material with > +them. Org does this using a process called /capture/. It also can > +store files related to a task (/attachments/) in a special directory. > + > +** Capture > +:PROPERTIES: > +:DESCRIPTION: Capturing new stuff. > +:END: > +#+cindex: capture > + > +Capture lets you quickly store notes with little interruption of your > +work flow. Org's method for capturing new items is heavily inspired > +by John Wiegley's excellent Remember package. > + > +*** Setting up capture > +:PROPERTIES: > +:DESCRIPTION: Where notes will be stored. > +:END: > + > +The following customization sets a default target file for notes. > + > +#+vindex: org-default-notes-file > +#+begin_src emacs-lisp > +(setq org-default-notes-file (concat org-directory "/notes.org")) > +#+end_src > + > +You may also define a global key for capturing new material (see > +[[*Activation]]). > + > +*** Using capture > +:PROPERTIES: > +:DESCRIPTION: Commands to invoke and terminate capture. > +:END: > + > +- {{{kbd(M-x org-capture)}}} (~org-capture~) :: > + > + #+findex: org-capture > + #+cindex: date tree > + Display the capture templates menu. If you have templates defined > + (see [[*Capture templates]]), it offers these templates for selection or > + use a new Org outline node as the default template. It inserts the > + template into the target file and switch to an indirect buffer > + narrowed to this new node. You may then insert the information you > + want. > + > +- {{{kbd(C-c C-c)}}} (~org-capture-finalize~) :: > + > + #+kindex: C-c C-c @r{(Capture buffer)} > + #+findex: org-capture-finalize > + Once you have finished entering information into the capture buffer, > + {{{kbd(C-c C-c)}}} returns you to the window configuration before > + the capture process, so that you can resume your work without > + further distraction. When called with a prefix argument, finalize > + and then jump to the captured item. > + > +- {{{kbd(C-c C-w)}}} (~org-capture-refile~) :: > + > + #+kindex: C-c C-w @r{(Capture buffer)} > + #+findex: org-capture-refile > + Finalize the capture process by refiling the note to a different > + place (see [[*Refile and Copy]]). Please realize that this is a normal > + refiling command that will be executed---so point position at the > + moment you run this command is important. If you have inserted > + a tree with a parent and children, first move point back to the > + parent. Any prefix argument given to this command is passed on to > + the ~org-refile~ command. > + > +- {{{kbd(C-c C-k)}}} (~org-capture-kill~) :: > + > + #+kindex: C-c C-k @r{(Capture buffer)} > + #+findex: org-capture-kill > + Abort the capture process and return to the previous state. > + > +#+kindex: k c @r{(Agenda)} > +You can also call ~org-capture~ in a special way from the agenda, > +using the {{{kbd(k c)}}} key combination. With this access, any > +timestamps inserted by the selected capture template defaults to the > +date at point in the agenda, rather than to the current date. > + > +To find the locations of the last stored capture, use ~org-capture~ > +with prefix commands: > + > +- {{{kbd(C-u M-x org-capture)}}} :: > + > + Visit the target location of a capture template. You get to select > + the template in the usual way. > + > +- {{{kbd(C-u C-u M-x org-capture)}}} :: > + > + Visit the last stored capture item in its buffer. > + > +#+vindex: org-capture-bookmark > +#+vindex: org-capture-last-stored > +You can also jump to the bookmark ~org-capture-last-stored~, which is > +automatically created unless you set ~org-capture-bookmark~ to ~nil~. > + > +To insert the capture at point in an Org buffer, call ~org-capture~ > +with a {{{kbd(C-0)}}} prefix argument. > + > +*** Capture templates > +:PROPERTIES: > +:DESCRIPTION: Define the outline of different note types. > +:END: > +#+cindex: templates, for Capture > + > +You can use templates for different types of capture items, and for > +different target locations. The easiest way to create such templates > +is through the customize interface. > + > +- {{{kbd(C)}}} :: > + > + #+kindex: C @r{(Capture menu} > + #+vindex: org-capture-templates > + Customize the variable ~org-capture-templates~. > + > +Before we give the formal description of template definitions, let's > +look at an example. Say you would like to use one template to create > +general TODO entries, and you want to put these entries under the > +heading =Tasks= in your file =~/org/gtd.org=. Also, a date tree in > +the file =journal.org= should capture journal entries. A possible > +configuration would look like: > + > +#+begin_src emacs-lisp > +(setq org-capture-templates > + '(("t" "Todo" entry (file+headline "~/org/gtd.org" "Tasks") > + "* TODO %?\n %i\n %a") > + ("j" "Journal" entry (file+datetree "~/org/journal.org") > + "* %?\nEntered on %U\n %i\n %a"))) > +#+end_src > + > +If you then press {{{kbd(t)}}} from the capture menu, Org will prepare > +the template for you like this: > + > +#+begin_example > +,* TODO > + [[file:LINK TO WHERE YOU INITIATED CAPTURE]] > +#+end_example > + > +#+texinfo: @noindent > +During expansion of the template, =%a= has been replaced by a link to > +the location from where you called the capture command. This can be > +extremely useful for deriving tasks from emails, for example. You > +fill in the task definition, press {{{kbd(C-c C-c)}}} and Org returns > +you to the same place where you started the capture process. > + > +To define special keys to capture to a particular template without > +going through the interactive template selection, you can create your > key binding like this: > > #+begin_src emacs-lisp > @@ -7437,28 +7776,30 @@ See the docstring of the variable for more information. > > ** Attachments > :PROPERTIES: > -:DESCRIPTION: Add files to tasks. > +:DESCRIPTION: Attach files to outlines. > :END: > #+cindex: attachments > -#+vindex: org-attach-directory > > It is often useful to associate reference material with an outline > -node/task. Small chunks of plain text can simply be stored in the > -subtree of a project. Hyperlinks (see [[*Hyperlinks]]) can establish > -associations with files that live elsewhere on your computer or in the > -cloud, like emails or source code files belonging to a project. > -Another method is /attachments/, which are files located in > -a directory belonging to an outline node. Org uses directories named > -by the unique ID of each entry. These directories are located in the > -=data/= directory which lives in the same directory where your Org > -file lives[fn:87]. If you initialize this directory with =git init=, > -Org automatically commits changes when it sees them. The attachment > -system has been contributed to Org by John Wiegley. > - > -In cases where it seems better to do so, you can attach a directory of > -your choice to an entry. You can also make children inherit the > -attachment directory from a parent, so that an entire subtree uses the > -same attached directory. > +node. Small chunks of plain text can simply be stored in the subtree > +of a project. Hyperlinks (see [[*Hyperlinks]]) can establish associations > +with files that live elsewhere on your computer or in the cloud, like > +emails or source code files belonging to a project. > + > +Another method is /attachments/, which are files located in a > +directory belonging to an outline node. Org uses directories either > +named by a unique ID of each entry, or by a =DIR= property. > + > +*** Attachment defaults and dispatcher > +By default, org-attach will use ID properties when adding attachments > +to outline nodes. This makes working with attachments fully > +automated. There is no decision needed for folder-name or location. > +ID-based directories are by default located in the =data/= directory, > +which lives in the same directory where your Org file lives[fn:87]. > +For more control over the setup, see [[*Attachment options]]. > + > +When attachments are made using ~org-attach~ a default tag =ATTACH= is > +added to the node that gets the attachments. > > The following commands deal with attachments: > > @@ -7543,25 +7884,144 @@ The following commands deal with attachments: > > - {{{kbd(D)}}} (~org-attach-delete-all~) :: > > - #+kindex: C-c C-a D > - Delete all of a task's attachments. A safer way is to open the > - directory in Dired and delete from there. > + #+kindex: C-c C-a D > + Delete all of a task's attachments. A safer way is to open the > + directory in Dired and delete from there. > + > + - {{{kbd(s)}}} (~org-attach-set-directory~) :: > + > + #+kindex: C-c C-a s > + #+cindex: @samp{DIR}, property > + Set a specific directory as the entry's attachment directory. > + This works by putting the directory path into the =DIR= > + property. > + > + - {{{kbd(S)}}} (~org-attach-unset-directory~) :: > + > + #+kindex: C-c C-a S > + #+cindex: @samp{DIR}, property > + Remove the attachment directory. This command removes the =DIR= > + property and asks the user to either move content inside that > + folder, if an =ID= property is set, delete the content, or to > + leave the attachment directory as is but no longer attached to the > + outline node. > + > +*** Attachment options > +There are a couple of options for attachments that are worth > +mentioning. > + > +- ~org-attach-id-dir~ :: > + #+vindex: org-attach-id-dir > + The directory where attachments are stored when =ID= is used as > + method. > + > +- ~org-attach-dir-relative~ :: > + #+vindex: org-attach-dir-relative > + When setting the =DIR= property on a node using {{{kbd(C-c C-a s)}}} > + (~org-attach-set-directory~), absolute links are entered by default. > + This option changes that to relative links. > + > +- ~org-attach-use-inheritance~ :: > + #+vindex: org-attach-use-inheritance > + By default folders attached to an outline node are inherited from > + parents according to ~org-use-property-inheritance~. If one instead > + want to set inheritance specifically for org-attach that can be done > + using ~org-attach-use-inheritance~. Inheriting documents through > + the node hierarchy makes a lot of sense in most cases. Especially > + since the introduction of [[* Attachment links]]. The following example > + shows one use case for attachment inheritance: > + > + #+begin_example > + ,* Chapter A ... > + :PROPERTIES: > + :DIR: Chapter A/ > + :END: > + ,** Introduction > + Some text > + > + #+NAME: Image 1 > + [[Attachment:image 1.jpg]] > + #+end_example > + > + Without inheritance one would not be able to resolve the link to > + image =1.jpg=, since the link is inside a sub-heading to =Chapter > + A=. > + > + Inheritance works the same way for both =ID= and =DIR= property. If > + both properties are defined on the same headline then =DIR= takes > + precedance. This is also true if inheritance is enabled. If =DIR= > + is inherited from a parent node in the outline, that property still > + takes precedence over an =ID= property defined on the node itself. > + > +- ~org-attach-method~ :: > + #+vindex: org-attach-method > + When attaching files using the dispatcher {{{kbd(C-c C-a)}}} it > + defaults to copying files. The behaviour can be changed by > + customizing ~org-attach-method~. Options are Copy, Move/Rename, > + Hard link or Symbolic link. > + > +- ~org-attach-preferred-new-method~ :: > + #+vindex: org-attach-preferred-new-method > + This customization lets you choose the default way to attach to > + nodes without existing =ID= and =DIR= property. It defaults to ~id~ > + but can also be set to ~dir~, ~ask~ or ~nil~. > + > +- ~org-attach-archive-delete~ :: > + #+vindex: org-attach-archive-delete > + Configure this to determine if attachments should be deleted or not > + when a subtree that has attachments is archived. > + > +- ~org-attach-auto-tag~ :: > + #+vindex: org-attach-auto-tag > + When attaching files to a heading it will be assigned a tag > + according to what is set here. > + > +- ~org-attach-id-to-path-function~ :: > + #+vindex: org-attach-id-to-path-function > + When =ID= is used for attachments, the ID is parsed into a part of a > + directory-path. See ~org-attach-id-folder-format~ for the default > + function. Define a new one and add it to > + ~org-attach-id-to-path-function~ if you want the folder structure > + any other way. Note that modifying this makes org-attach dependent > + on your function also for opening attachments, not only setting > + them! > + > +- ~org-attach-expert~ :: > + #+vindex: org-attach-expert > + Do not show the splash buffer with the attach dispatcher when > + ~org-attach-expert~ is set to non-~nil~. > + > +See customization group =Org Attach= if you want to change the > +default settings. > + > +*** Attachment links > +Attached files and folders can be referenced using attachment links. > +This makes it easy to refer to the material added to an outline node. > +Especially if it was attached using the unique ID of the entry! > + > +#+begin_example > +,* TODO Some task > + :PROPERTIES: > + :ID: 95d50008-c12e-479f-a4f2-cc0238205319 > + :END: > +See attached document for more information: [[attachment:info.org]] > +#+end_example > > - - {{{kbd(s)}}} (~org-attach-set-directory~) :: > +See [[*External Links]] for more information about these links. > > - #+kindex: C-c C-a s > - #+cindex: @samp{ATTACH_DIR}, property > - Set a specific directory as the entry's attachment directory. > - This works by putting the directory path into the =ATTACH_DIR= > - property. > +*** Automatic version-control with Git > +If the directory attached to an outline node is a Git repository, Org > +can be configured to automatically commit changes to that repository > +when it sees them. > > - - {{{kbd(i)}}} (~org-attach-set-inherit~) :: > +To make Org mode take care of versioning of attachments for you, add > +the following to your Emacs config: > > - #+kindex: C-c C-a i > - #+cindex: @samp{ATTACH_DIR_INHERIT}, property > - Set the =ATTACH_DIR_INHERIT= property, so that children use the > - same directory for attachments as the parent does. > +#+begin_src emacs-lisp > + (require 'org-attach-git) > +#+end_src > > +*** Attach from Dired > #+cindex: attach from Dired > #+findex: org-attach-dired-to-subtree > It is possible to attach files to a subtree from a Dired buffer. To > @@ -7832,249 +8292,6 @@ valid contents: ~org-protocol-create~ and > ~org-protocol-create-for-org~. The latter is of use if you're editing > an Org file that is part of a publishing project. > > -** Refile and Copy > -:PROPERTIES: > -:DESCRIPTION: Moving/copying a tree from one place to another. > -:END: > -#+cindex: refiling notes > -#+cindex: copying notes > - > -When reviewing the captured data, you may want to refile or to copy > -some of the entries into a different list, for example into a project. > -Cutting, finding the right location, and then pasting the note is > -cumbersome. To simplify this process, you can use the following > -special command: > - > -- {{{kbd(C-c C-w)}}} (~org-refile~) :: > - > - #+kindex: C-c C-w > - #+findex: org-refile > - #+vindex: org-reverse-note-order > - #+vindex: org-refile-targets > - #+vindex: org-refile-use-outline-path > - #+vindex: org-outline-path-complete-in-steps > - #+vindex: org-refile-allow-creating-parent-nodes > - #+vindex: org-log-refile > - Refile the entry or region at point. This command offers possible > - locations for refiling the entry and lets you select one with > - completion. The item (or all items in the region) is filed below > - the target heading as a subitem. Depending on > - ~org-reverse-note-order~, it is either the first or last subitem. > - > - By default, all level 1 headlines in the current buffer are > - considered to be targets, but you can have more complex definitions > - across a number of files. See the variable ~org-refile-targets~ for > - details. If you would like to select a location via > - a file-path-like completion along the outline path, see the > - variables ~org-refile-use-outline-path~ and > - ~org-outline-path-complete-in-steps~. If you would like to be able > - to create new nodes as new parents for refiling on the fly, check > - the variable ~org-refile-allow-creating-parent-nodes~. When the > - variable ~org-log-refile~[fn:88] is set, a timestamp or a note is > - recorded whenever an entry is refiled. > - > -- {{{kbd(C-u C-c C-w)}}} :: > - > - #+kindex: C-u C-c C-w > - Use the refile interface to jump to a heading. > - > -- {{{kbd(C-u C-u C-c C-w)}}} (~org-refile-goto-last-stored~) :: > - > - #+kindex: C-u C-u C-c C-w > - #+findex: org-refile-goto-last-stored > - Jump to the location where ~org-refile~ last moved a tree to. > - > -- {{{kbd(C-2 C-c C-w)}}} :: > - > - #+kindex: C-2 C-c C-w > - Refile as the child of the item currently being clocked. > - > -- {{{kbd(C-3 C-c C-w)}}} :: > - > - #+kindex: C-3 C-c C-w > - #+vindex: org-refile-keep > - Refile and keep the entry in place. Also see ~org-refile-keep~ to > - make this the default behavior, and beware that this may result in > - duplicated =ID= properties. > - > -- {{{kbd(C-0 C-c C-w)}}} or {{{kbd(C-u C-u C-u C-c C-w)}}} (~org-refile-cache-clear~) :: > - > - #+kindex: C-u C-u C-u C-c C-w > - #+kindex: C-0 C-c C-w > - #+findex: org-refile-cache-clear > - #+vindex: org-refile-use-cache > - Clear the target cache. Caching of refile targets can be turned on > - by setting ~org-refile-use-cache~. To make the command see new > - possible targets, you have to clear the cache with this command. > - > -- {{{kbd(C-c M-w)}}} (~org-copy~) :: > - > - #+kindex: C-c M-w > - #+findex: org-copy > - Copying works like refiling, except that the original note is not > - deleted. > - > -** Archiving > -:PROPERTIES: > -:DESCRIPTION: What to do with finished products. > -:END: > -#+cindex: archiving > - > -When a project represented by a (sub)tree is finished, you may want to > -move the tree out of the way and to stop it from contributing to the > -agenda. Archiving is important to keep your working files compact and > -global searches like the construction of agenda views fast. > - > -- {{{kbd(C-c C-x C-a)}}} (~org-archive-subtree-default~) :: > - > - #+kindex: C-c C-x C-a > - #+findex: org-archive-subtree-default > - #+vindex: org-archive-default-command > - Archive the current entry using the command specified in the > - variable ~org-archive-default-command~. > - > -*** Moving a tree to an archive file > -:PROPERTIES: > -:DESCRIPTION: Moving a tree to an archive file. > -:ALT_TITLE: Moving subtrees > -:END: > -#+cindex: external archiving > - > -The most common archiving action is to move a project tree to another > -file, the archive file. > - > -- {{{kbd(C-c C-x C-s)}}} or short {{{kbd(C-c $)}}} (~org-archive-subtree~) :: > - > - #+kindex: C-c C-x C-s > - #+kindex: C-c $ > - #+findex: org-archive-subtree > - #+vindex: org-archive-location > - Archive the subtree starting at point position to the location given > - by ~org-archive-location~. > - > -- {{{kbd(C-u C-c C-x C-s)}}} :: > - > - #+kindex: C-u C-c C-x C-s > - Check if any direct children of the current headline could be moved > - to the archive. To do this, check each subtree for open TODO > - entries. If none is found, the command offers to move it to the > - archive location. If point is /not/ on a headline when this command > - is invoked, check level 1 trees. > - > -- {{{kbd(C-u C-u C-c C-x C-s)}}} :: > - > - #+kindex: C-u C-u C-c C-x C-s > - As above, but check subtree for timestamps instead of TODO entries. > - The command offers to archive the subtree if it /does/ contain > - a timestamp, and that timestamp is in the past. > - > -#+cindex: archive locations > -The default archive location is a file in the same directory as the > -current file, with the name derived by appending =_archive= to the > -current file name. You can also choose what heading to file archived > -items under, with the possibility to add them to a datetree in a file. > -For information and examples on how to specify the file and the > -heading, see the documentation string of the variable > -~org-archive-location~. > - > -There is also an in-buffer option for setting this variable, for > -example: > - > -#+cindex: @samp{ARCHIVE}, keyword > -: #+ARCHIVE: %s_done:: > - > -#+cindex: ARCHIVE, property > -If you would like to have a special archive location for a single > -entry or a (sub)tree, give the entry an =ARCHIVE= property with the > -location as the value (see [[*Properties and Columns]]). > - > -#+vindex: org-archive-save-context-info > -When a subtree is moved, it receives a number of special properties > -that record context information like the file from where the entry > -came, its outline path the archiving time etc. Configure the variable > -~org-archive-save-context-info~ to adjust the amount of information > -added. > - > -*** Internal archiving > -:PROPERTIES: > -:DESCRIPTION: Switch off a tree but keep it in the file. > -:END: > - > -#+cindex: @samp{ARCHIVE}, tag > -If you want to just switch off---for agenda views---certain subtrees > -without moving them to a different file, you can use the =ARCHIVE= > -tag. > - > -A headline that is marked with the =ARCHIVE= tag (see [[*Tags]]) stays at > -its location in the outline tree, but behaves in the following way: > - > -- > - #+vindex: org-cycle-open-archived-trees > - It does not open when you attempt to do so with a visibility cycling > - command (see [[*Visibility Cycling]]). You can force cycling archived > - subtrees with {{{kbd(C-TAB)}}}, or by setting the option > - ~org-cycle-open-archived-trees~. Also normal outline commands, like > - ~outline-show-all~, open archived subtrees. > - > -- > - #+vindex: org-sparse-tree-open-archived-trees > - During sparse tree construction (see [[*Sparse Trees]]), matches in > - archived subtrees are not exposed, unless you configure the option > - ~org-sparse-tree-open-archived-trees~. > - > -- > - #+vindex: org-agenda-skip-archived-trees > - During agenda view construction (see [[*Agenda Views]]), the content of > - archived trees is ignored unless you configure the option > - ~org-agenda-skip-archived-trees~, in which case these trees are > - always included. In the agenda you can press {{{kbd(v a)}}} to get > - archives temporarily included. > - > -- > - #+vindex: org-export-with-archived-trees > - Archived trees are not exported (see [[*Exporting]]), only the headline > - is. Configure the details using the variable > - ~org-export-with-archived-trees~. > - > -- > - #+vindex: org-columns-skip-archived-trees > - Archived trees are excluded from column view unless the variable > - ~org-columns-skip-archived-trees~ is configured to ~nil~. > - > -The following commands help manage the =ARCHIVE= tag: > - > -- {{{kbd(C-c C-x a)}}} (~org-toggle-archive-tag~) :: > - > - #+kindex: C-c C-x a > - #+findex: org-toggle-archive-tag > - Toggle the archive tag for the current headline. When the tag is > - set, the headline changes to a shadowed face, and the subtree below > - it is hidden. > - > -- {{{kbd(C-u C-c C-x a)}}} :: > - > - #+kindex: C-u C-c C-x a > - Check if any direct children of the current headline should be > - archived. To do this, check each subtree for open TODO entries. If > - none is found, the command offers to set the =ARCHIVE= tag for the > - child. If point is /not/ on a headline when this command is > - invoked, check the level 1 trees. > - > -- {{{kbd(C-TAB)}}} (~org-force-cycle-archived~) :: > - > - #+kindex: C-TAB > - Cycle a tree even if it is tagged with =ARCHIVE=. > - > -- {{{kbd(C-c C-x A)}}} (~org-archive-to-archive-sibling~) :: > - > - #+kindex: C-c C-x A > - #+findex: org-archive-to-archive-sibling > - Move the current entry to the /Archive Sibling/. This is a sibling > - of the entry with the heading =Archive= and the archive tag. The > - entry becomes a child of that sibling and in this way retains a lot > - of its original context, including inherited tags and approximate > - position in the outline. > - > * Agenda Views > :PROPERTIES: > :DESCRIPTION: Collecting information into views. > @@ -21244,7 +21461,7 @@ accessed in capture templates in a similar way. > ~org-link-from-user-regexp~. > > [fn:87] If you move entries or Org files from one directory to > -another, you may want to configure ~org-attach-directory~ to contain > +another, you may want to configure ~org-attach-id-dir~ to contain > an absolute path. > > [fn:88] Note the corresponding =STARTUP= options =logrefile=, > diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS > index 4abecdfbc..bbd9dc975 100644 > --- a/etc/ORG-NEWS > +++ b/etc/ORG-NEWS > @@ -111,7 +111,96 @@ every other backend was already using xcolor to set fg and bg, the CLI > alternative was removed and there is no more a :use-xcolor options > since now it's implicitly always true. > > +*** Org-Attach Git commit > +[[*Org-Attach has been refactored and extended][Refactoring of Org-Attach]] affected the Git commit functionality. Not > +much, but the following changes are required if you still need to > +auto-commit attachments to git: > + > +- Customization of ~org-attach-annex-auto-get~ needs to be renamed to > + ~org-attach-git-annex-auto-get~. > + > +- Customization of ~org-attach-commit~ is no longer needed. Instead > + one need to require the =org-attach-git= module in the startup. > + > ** New features > +*** Org-Attach has been refactored and extended > +Org attach has been refactored and the functionality extended. It > +should now be easier to understand how it works. A few improvements > +and extra options have been added as well. > + > +From the initial comment in org-attach source-code: > + > +- Attachments are managed either by using a custom property DIR or by > + using property ID from org-id. When DIR is defined, a location in > + the filesystem is directly attached to the outline node. When > + org-id is used, attachments are stored in a folder named after the > + ID, in a location defined by ~org-attach-id-dir~. DIR has > + precedence over ID when both parameters are defined for the current > + outline node (also when inherited parameters are taken into > + account). > + > +From now on inheritance requires no extra property and will adhere to > +~org-attach-use-inheritance~ by default. Inheritance can be > +customized to always be activated or never be activated in > +~org-attach-use-inheritance~. > + > +The ATTACH_DIR property is deprecated in favour of the shorter > +property DIR. Links to folders inside the DIR property can now be > +declared as relative links. This is not enabled by default, but can > +be set in ~org-attach-dir-relative~. > + > +When adding new attachment to the outline node the preferred way of > +doing so can be customized. Take a look at > +~org-attach-preferred-new-method~. It defaults to using ID since that > +was the behaviour before this change. > + > +If both DIR and ID properties are set on the same node, DIR has > +precedence and will be used. > + > +One can now also choose to build attachment-directory-paths in a > +customized way. This is an advanced topic, but in some case it makes > +sense to parse an ID in a different way than the default one. Create > +your own function and use it is ~org-attach-id-to-path-function~ if > +you want to customize the ID-based folder structure. > + > +If you've used ATTACH_DIR properties to manage attachments, use the > +following code to rename that property to DIR which supports the same > +functionality. ATTACH_DIR_INHERIT is no longer supported and is > +removed. > + > +#+begin_src emacs-lisp > + (defun org-update-attach-properties () > + "Change properties for Org-Attach." > + (interactive) > + (org-with-point-at 1 > + (while (outline-next-heading) > + (let ((DIR (org--property-local-values "ATTACH_DIR" nil))) > + (when DIR > + (org-set-property "DIR" (car DIR)) > + (org-delete-property "ATTACH_DIR")))) > + (org-delete-property-globally "ATTACH_DIR_INHERIT"))) > +#+end_src > + > +For those who hate breaking changes, even though the changes are made > +to clean things up; fear not. ATTACH_DIR will still continue to work. > +It's just not documented any longer. When you get the chance, run the > +code above to clean things up anyways! > + > +**** New hooks > +Two hooks are added to org-attach: > +- org-attach-after-change-hook > +- org-attach-open-hook > + > +They are added mostly for internal restructuring purposes, but can > +ofc. be used for other things as well. > + > +*** New link-type: Attachment > +Attachment-links are now first-class citizens. They mimick file-links > +in everything they do but use the existing attachment-folder as a base > +when expanding the links. Both =DIR= and =ID= properties are used to > +try to resolve the links, in exactly the same way as Org-Attach uses > +those properties. > + > *** Handle overlay specification for notes in Beamer export > > This aligns Beamer notes with slide overlays. > @@ -243,6 +332,11 @@ dynamic block in ~org-dynamic-block-alist~. > ** Removed functions > *** ~org-babel-set-current-result-hash~ > *** ~org-capture-insert-template-here~ > +*** ~org-attach-directory~ > + > +It has been deprecated in favour of ~org-attach-id-dir~ which is less > +ambigous given the restructured org-attach. > + > ** Miscellaneous > *** Change signature for ~org-list-to-subtree~ > The function now accepts the level of the subtree as an optional > diff --git a/lisp/org-attach-git.el b/lisp/org-attach-git.el > new file mode 100644 > index 000000000..f40eb966d > --- /dev/null > +++ b/lisp/org-attach-git.el > @@ -0,0 +1,117 @@ > +;;; org-attach-git.el --- Automatic git commit extention to org-attach -*- lexical-binding: t; -*- > + > +;; Copyright (C) 2019 Free Software Foundation, Inc. > + > +;; Original Author: John Wiegley > +;; Restructurer: Gustav Wikstr¶m > +;; Keywords: org data git > + > +;; This file is part of GNU Emacs. > +;; > +;; GNU Emacs 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. > + > +;; GNU Emacs 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 GNU Emacs. If not, see . > + > +;;; Commentary: > + > +;; An extention to org-attach. If the attachment-directory to an > +;; outline node (using either DIR or ID) is initialized as a Git > +;; repository, then org-attach-git will automatically commit changes > +;; when it sees them. > + > +;;; Code: > + > +(require 'org-attach) > +(require 'vc-git) > + > +(defcustom org-attach-git-annex-cutoff (* 32 1024) > + "If non-nil, files larger than this will be annexed instead of stored." > + :group 'org-attach > + :version "24.4" > + :package-version '(Org . "8.0") > + :type '(choice > + (const :tag "None" nil) > + (integer :tag "Bytes"))) > + > +(defcustom org-attach-git-annex-auto-get 'ask > + "Confirmation preference for automatically getting annex files. > +If \\='ask, prompt using `y-or-n-p'. If t, always get. If nil, never get." > + :group 'org-attach > + :package-version '(Org . "9.0") > + :version "26.1" > + :type '(choice > + (const :tag "confirm with `y-or-n-p'" ask) > + (const :tag "always get from annex if necessary" t) > + (const :tag "never get from annex" nil))) > + > +(defun org-attach-git-use-annex () > + "Return non-nil if git annex can be used." > + (let ((git-dir (vc-git-root (expand-file-name org-attach-id-dir)))) > + (and org-attach-git-annex-cutoff > + (or (file-exists-p (expand-file-name "annex" git-dir)) > + (file-exists-p (expand-file-name ".git/annex" git-dir)))))) > + > +(defun org-attach-git-annex-get-maybe (path) > + "Call git annex get PATH (via shell) if using git annex. > +Signals an error if the file content is not available and it was not retrieved." > + (let* ((default-directory (expand-file-name org-attach-id-dir)) > + (path-relative (file-relative-name path))) > + (when (and (org-attach-git-use-annex) > + (not > + (string-equal > + "found" > + (shell-command-to-string > + (format "git annex find --format=found --in=here %s" > + (shell-quote-argument path-relative)))))) > + (let ((should-get > + (if (eq org-attach-git-annex-auto-get 'ask) > + (y-or-n-p (format "Run git annex get %s? " path-relative)) > + org-attach-git-annex-auto-get))) > + (unless should-get > + (error "File %s stored in git annex but unavailable" path)) > + (message "Running git annex get \"%s\"." path-relative) > + (call-process "git" nil nil nil "annex" "get" path-relative))))) > + > +(defun org-attach-git-commit () > + "Commit changes to git if `org-attach-id-dir' is properly initialized. > +This checks for the existence of a \".git\" directory in that directory." > + (let* ((dir (expand-file-name org-attach-id-dir)) > + (git-dir (vc-git-root dir)) > + (use-annex (org-attach-git-use-annex)) > + (changes 0)) > + (when (and git-dir (executable-find "git")) > + (with-temp-buffer > + (cd dir) > + (dolist (new-or-modified > + (split-string > + (shell-command-to-string > + "git ls-files -zmo --exclude-standard") "\0" t)) > + (if (and use-annex > + (>= (file-attribute-size (file-attributes new-or-modified)) > + org-attach-git-annex-cutoff)) > + (call-process "git" nil nil nil "annex" "add" new-or-modified) > + (call-process "git" nil nil nil "add" new-or-modified)) > + (cl-incf changes)) > + (dolist (deleted > + (split-string > + (shell-command-to-string "git ls-files -z --deleted") "\0" t)) > + (call-process "git" nil nil nil "rm" deleted) > + (cl-incf changes)) > + (when (> changes 0) > + (shell-command "git commit -m 'Synchronized attachments'")))))) > + > +(add-hook 'org-attach-after-change-hook 'org-attach-git-commit) > +(add-hook 'org-attach-open-hook 'org-attach-git-annex-get-maybe) > + > +(provide 'org-attach-git) > + > +;;; org-attach-git.el ends here > diff --git a/lisp/org-attach.el b/lisp/org-attach.el > index a715c89e9..726085014 100644 > --- a/lisp/org-attach.el > +++ b/lisp/org-attach.el > @@ -1,9 +1,9 @@ > -;;; org-attach.el --- Manage file attachments to Org tasks -*- lexical-binding: t; -*- > +;;; org-attach.el --- Manage file attachments to Org outlines -*- lexical-binding: t; -*- > > ;; Copyright (C) 2008-2019 Free Software Foundation, Inc. > > ;; Author: John Wiegley > -;; Keywords: org data task > +;; Keywords: org data attachment > > ;; This file is part of GNU Emacs. > ;; > @@ -24,32 +24,30 @@ > > ;; See the Org manual for information on how to use it. > ;; > -;; Attachments are managed in a special directory called "data", which > -;; lives in the same directory as the org file itself. If this data > -;; directory is initialized as a Git repository, then org-attach will > -;; automatically commit changes when it sees them. > -;; > -;; Attachment directories are identified using a UUID generated for the > -;; task which has the attachments. These are added as property to the > -;; task when necessary, and should not be deleted or changed by the > -;; user, ever. UUIDs are generated by a mechanism defined in the variable > -;; `org-id-method'. > +;; Attachments are managed either by using a custom property DIR or by > +;; using property ID from org-id. When DIR is defined, a location in > +;; the filesystem is directly attached to the outline node. When > +;; org-id is used, attachments are stored in a folder named after the > +;; ID, in a location defined by `org-attach-id-dir'. DIR has > +;; precedence over ID when both parameters are defined for the current > +;; outline node (also when inherited parameters are taken into > +;; account). > > ;;; Code: > > (require 'cl-lib) > (require 'org) > +(require 'ol) > (require 'org-id) > -(require 'vc-git) > > (declare-function dired-dwim-target-directory "dired-aux") > > (defgroup org-attach nil > - "Options concerning entry attachments in Org mode." > + "Options concerning attachments in Org mode." > :tag "Org Attach" > :group 'org) > > -(defcustom org-attach-directory "data/" > +(defcustom org-attach-id-dir "data/" > "The directory where attachments are stored. > If this is a relative path, it will be interpreted relative to the directory > where the Org file lives." > @@ -57,22 +55,13 @@ where the Org file lives." > :type 'directory > :safe #'stringp) > > -(defcustom org-attach-commit t > - "If non-nil commit attachments with git. > -This is only done if the Org file is in a git repository." > +(defcustom org-attach-dir-relative nil > + "Non-nil means directories in DIR property are added as relative links. > +Defaults to absolute location." > :group 'org-attach > :type 'boolean > - :version "26.1" > - :package-version '(Org . "9.0")) > - > -(defcustom org-attach-git-annex-cutoff (* 32 1024) > - "If non-nil, files larger than this will be annexed instead of stored." > - :group 'org-attach > - :version "24.4" > - :package-version '(Org . "8.0") > - :type '(choice > - (const :tag "None" nil) > - (integer :tag "Bytes"))) > + :package-version '(Org . "9.3") > + :safe #'booleanp) > > (defcustom org-attach-auto-tag "ATTACH" > "Tag that will be triggered automatically when an entry has an attachment." > @@ -81,15 +70,27 @@ This is only done if the Org file is in a git repository." > (const :tag "None" nil) > (string :tag "Tag"))) > > -(defcustom org-attach-file-list-property "Attachments" > - "The property used to keep a list of attachment belonging to this entry. > -This is not really needed, so you may set this to nil if you don't want it. > -Also, for entries where children inherit the directory, the list of > -attachments is not kept in this property." > +(defcustom org-attach-preferred-new-method 'id > + "Preferred way to attach to nodes without existing ID and DIR property. > +This choice is used when adding attachments to nodes without ID > +and DIR properties. > + > +Allowed values are: > + > +id Create and use an ID parameter > +dir Create and use a DIR parameter > +ask Ask the user for input of which method to choose > +nil Prefer to not create a new parameter > + > + nil means that ID or DIR has to be created explicitly > + before attaching files." > :group 'org-attach > + :package-version '(org . "9.3") > :type '(choice > - (const :tag "None" nil) > - (string :tag "Tag"))) > + (const :tag "ID parameter" id) > + (const :tag "DIR parameter" dir) > + (const :tag "Ask user" ask) > + (const :tag "Don't create" nil))) > > (defcustom org-attach-method 'cp > "The preferred method to attach a file. > @@ -113,14 +114,24 @@ lns create a symbol link. Note that this is not supported > :group 'org-attach > :type 'boolean) > > -(defcustom org-attach-allow-inheritance t > - "Non-nil means allow attachment directories be inherited." > +(defcustom org-attach-use-inheritance 'selective > + "Attachment inheritance for the outline. > + > +Enabling inheritance for org-attach implies two things. First, > +that attachment links will look through all parent headings until > +it finds the linked attachment. Second, that running org-attach > +inside a node without attachments will make org-attach operate on > +the first parent heading it finds with an attachment. > + > +Selective means to respect the inheritance setting in > +`org-use-property-inheritance'." > :group 'org-attach > + :type '(choice > + (const :tag "Don't use inheritance" nil) > + (const :tag "Inherit parent node attachments" t) > + (const :tag "Respect org-use-property-inheritance" selective)) > :type 'boolean) > > -(defvar org-attach-inherited nil > - "Indicates if the last access to the attachment directory was inherited.") > - > (defcustom org-attach-store-link-p nil > "Non-nil means store a link to a file when attaching it." > :group 'org-attach > @@ -141,16 +152,28 @@ When set to `query', ask the user instead." > (const :tag "Always delete attachments" t) > (const :tag "Query the user" query))) > > -(defcustom org-attach-annex-auto-get 'ask > - "Confirmation preference for automatically getting annex files. > -If \\='ask, prompt using `y-or-n-p'. If t, always get. If nil, never get." > +(defun org-attach-id-folder-format (id) > + "Translate an ID into a folder-path. > +Default format for how Org translates ID properties to a path for > +attachments." > + (format "%s/%s" > + (substring id 0 2) > + (substring id 2))) > + > +(defcustom org-attach-id-to-path-function #'org-attach-id-folder-format > + "Function parsing the ID parameter into a folder-path." > :group 'org-attach > - :package-version '(Org . "9.0") > - :version "26.1" > - :type '(choice > - (const :tag "confirm with `y-or-n-p'" ask) > - (const :tag "always get from annex if necessary" t) > - (const :tag "never get from annex" nil))) > + :package-version '(Org . "9.3") > + :type 'function) > + > +(defvar org-attach-after-change-hook nil > + "Hook to be called when files have been added or removed to the attachment folder.") > + > +(defvar org-attach-open-hook nil > + "Hook that is invoked by `org-attach-open'. > + > +Created mostly to be compatible with org-attach-git after removing > +git-funtionality from this file.") > > (defcustom org-attach-commands > '(((?a ?\C-a) org-attach-attach > @@ -186,9 +209,9 @@ you added attachments yourself.\n") > "Delete all of a task's attachments. A safer way is\n to open the \ > directory in dired and delete from there.\n") > ((?s ?\C-s) org-attach-set-directory > - "Set a specific attachment directory for this entry or reset to default.") > - ((?i ?\C-i) org-attach-set-inherit > - "Make children of the current entry inherit its attachment directory.\n") > + "Set a specific attachment directory for this entry. Sets DIR property.") > + ((?S ?\C-S) org-attach-unset-directory > + "Unset the attachment directory for this entry. Removes DIR property.") > ((?q) (lambda () (interactive) (message "Abort")) "Abort.")) > "The list of commands for the attachment dispatcher. > Each entry in this list is a list of three elements: > @@ -215,7 +238,7 @@ Shows a list of commands and prompts for another key to execute a command." > (setq marker (or (get-text-property (point) 'org-hd-marker) > (get-text-property (point) 'org-marker))) > (unless marker > - (error "No task in current line"))) > + (error "No item in current line"))) > (save-excursion > (when marker > (set-buffer (marker-buffer marker)) > @@ -225,24 +248,28 @@ Shows a list of commands and prompts for another key to execute a command." > (save-window-excursion > (unless org-attach-expert > (with-output-to-temp-buffer "*Org Attach*" > - (princ > - (format "Select an Attachment Command:\n\n%s" > - (mapconcat > - (lambda (entry) > - (pcase entry > - (`((,key . ,_) ,_ ,docstring) > - (format "%c %s" > - key > - (replace-regexp-in-string "\n\\([\t ]*\\)" > - " " > - docstring > - nil nil 1))) > - (_ > - (user-error > - "Invalid `org-attach-commands' item: %S" > - entry)))) > - org-attach-commands > - "\n"))))) > + (princ > + (concat "Attachment folder:\n" > + (or (org-attach-dir) > + "Can't find an existing attachment-folder") > + "\n\n" > + (format "Select an Attachment Command:\n\n%s" > + (mapconcat > + (lambda (entry) > + (pcase entry > + (`((,key . ,_) ,_ ,docstring) > + (format "%c %s" > + key > + (replace-regexp-in-string "\n\\([\t ]*\\)" > + " " > + docstring > + nil nil 1))) > + (_ > + (user-error > + "Invalid `org-attach-commands' item: %S" > + entry)))) > + org-attach-commands > + "\n")))))) > (org-fit-window-to-buffer (get-buffer-window "*Org Attach*")) > (message "Select command: [%s]" > (concat (mapcar #'caar org-attach-commands))) > @@ -256,148 +283,126 @@ Shows a list of commands and prompts for another key to execute a command." > (error "No such attachment command: %c" c)))))) > > (defun org-attach-dir (&optional create-if-not-exists-p) > - "Return the directory associated with the current entry. > -This first checks for a local property ATTACH_DIR, and then for an inherited > -property ATTACH_DIR_INHERIT. If neither exists, the default mechanism > -using the entry ID will be invoked to access the unique directory for the > -current entry. > -If the directory does not exist and CREATE-IF-NOT-EXISTS-P is non-nil, > -the directory and (if necessary) the corresponding ID will be created." > - (let (attach-dir uuid) > - (setq org-attach-inherited (org-entry-get nil "ATTACH_DIR_INHERIT")) > + "Return the directory associated with the current outline node. > +First check for DIR property, then ID property. > +`org-attach-use-inheritance' determines whether inherited > +properties also will be considered. > + > +If an ID property is found the default mechanism using that ID > +will be invoked to access the directory for the current entry. > + > +If CREATE-IF-NOT-EXIST-P is non-nil, `org-attach-dir-get-create' > +is run." > + (let (attach-dir id) > (cond > - ((setq attach-dir (org-entry-get nil "ATTACH_DIR")) > + (create-if-not-exists-p > + (setq attach-dir (org-attach-dir-get-create))) > + ((setq attach-dir (org-entry-get nil "DIR" org-attach-use-inheritance)) > (org-attach-check-absolute-path attach-dir)) > - ((and org-attach-allow-inheritance > - (org-entry-get nil "ATTACH_DIR_INHERIT" t)) > - (setq attach-dir > - (org-with-wide-buffer > - (if (marker-position org-entry-property-inherited-from) > - (goto-char org-entry-property-inherited-from) > - (org-back-to-heading t)) > - (let (org-attach-allow-inheritance) > - (org-attach-dir create-if-not-exists-p)))) > - (org-attach-check-absolute-path attach-dir) > - (setq org-attach-inherited t)) > - (t ; use the ID > + ;; Deprecated and removed from documentation, but still > + ;; works. FIXME: Remove after major nr change. > + ((setq attach-dir (org-entry-get nil "ATTACH_DIR" org-attach-use-inheritance)) > + (org-attach-check-absolute-path attach-dir)) > + ((setq id (org-entry-get nil "ID" org-attach-use-inheritance)) > (org-attach-check-absolute-path nil) > - (setq uuid (org-id-get (point) create-if-not-exists-p)) > - (when (or uuid create-if-not-exists-p) > - (unless uuid (error "ID retrieval/creation failed")) > - (setq attach-dir (expand-file-name > - (format "%s/%s" > - (substring uuid 0 2) > - (substring uuid 2)) > - (expand-file-name org-attach-directory)))))) > - (when attach-dir > - (if (and create-if-not-exists-p > - (not (file-directory-p attach-dir))) > - (make-directory attach-dir t)) > - (and (file-exists-p attach-dir) > - attach-dir)))) > + (setq attach-dir (org-attach-dir-from-id id)))) > + attach-dir)) > + > +(defun org-attach-dir-get-create () > + "Return existing or new directory associated with the current outline node. > + > +`org-attach-preferred-new-method' decides how to attach > +new directory." > + (interactive) > + (let ((attach-dir (org-attach-dir))) > + (unless attach-dir > + (let (answer) > + (when (eq org-attach-preferred-new-method 'ask) > + (message "Create new ID [1] property or DIR [2] property for attachments?") > + (setq answer (read-char-exclusive))) > + (cond > + ((or (eq org-attach-preferred-new-method 'id) (eq answer ?1)) > + (setq attach-dir (org-attach-dir-from-id (org-id-get nil t)))) > + ((or (eq org-attach-preferred-new-method 'dir) (eq answer ?2)) > + (setq attach-dir (org-attach-set-directory))) > + ((eq org-attach-preferred-new-method 'nil) > + (error "No existing directory. DIR or ID property has to be explicitly created"))))) > + (unless attach-dir > + (error "No attachment directory is associated with the current node")) > + (unless (file-directory-p attach-dir) > + (make-directory attach-dir t)) > + attach-dir)) > + > +(defun org-attach-dir-from-id (id) > + "Returns a file name based on `org-attach-id-dir' and ID." > + (expand-file-name > + (funcall org-attach-id-to-path-function id) > + (expand-file-name org-attach-id-dir))) > > (defun org-attach-check-absolute-path (dir) > "Check if we have enough information to root the attachment directory. > When DIR is given, check also if it is already absolute. Otherwise, > -assume that it will be relative, and check if `org-attach-directory' is > +assume that it will be relative, and check if `org-attach-id-dir' is > absolute, or if at least the current buffer has a file name. > Throw an error if we cannot root the directory." > (or (and dir (file-name-absolute-p dir)) > - (file-name-absolute-p org-attach-directory) > + (file-name-absolute-p org-attach-id-dir) > (buffer-file-name (buffer-base-buffer)) > - (error "Need absolute `org-attach-directory' to attach in buffers without filename"))) > + (error "Need absolute `org-attach-id-dir' to attach in buffers without filename"))) > > -(defun org-attach-set-directory (&optional arg) > - "Set the ATTACH_DIR node property and ask to move files there. > +(defun org-attach-set-directory () > + "Set the DIR node property and ask to move files there. > The property defines the directory that is used for attachments > -of the entry. When called with `\\[universal-argument]', reset \ > -the directory to > -the default ID based one." > - (interactive "P") > +of the entry. Creates relative links if `org-attach-dir-relative' > +is non-nil. > + > +Return the directory." > + (interactive) > (let ((old (org-attach-dir)) > - (new > - (progn > - (if arg (org-entry-delete nil "ATTACH_DIR") > - (let ((dir (read-directory-name > - "Attachment directory: " > - (org-entry-get nil > - "ATTACH_DIR" > - (and org-attach-allow-inheritance t))))) > - (org-entry-put nil "ATTACH_DIR" dir))) > - (org-attach-dir t)))) > + (new > + (let* ((attach-dir (read-directory-name > + "Attachment directory: " > + (org-entry-get nil "DIR"))) > + (current-dir (file-name-directory (or default-directory > + buffer-file-name))) > + (attach-dir-relative (file-relative-name attach-dir current-dir))) > + (org-entry-put nil "DIR" (if org-attach-dir-relative > + attach-dir-relative > + attach-dir)) > + attach-dir))) > (unless (or (string= old new) > (not old)) > (when (yes-or-no-p "Copy over attachments from old directory? ") > + (copy-directory old new t t t)) > + (when (yes-or-no-p (concat "Delete " old)) > + (delete-directory old t))) > + new)) > + > +(defun org-attach-unset-directory () > + "Removes DIR node property. > +If attachment folder is changed due to removal of DIR-property > +ask to move attachments to new location and ask to delete old > +attachment-folder. > + > +Change of attachment-folder due to unset might be if an ID > +property is set on the node, or if a separate inherited > +DIR-property exists (that is different than the unset one)." > + (interactive) > + (let ((old (org-attach-dir)) > + (new > + (progn > + (org-entry-delete nil "DIR") > + ;; ATTACH-DIR is deprecated and removed from documentation, > + ;; but still works. Remove code for it after major nr change. > + (org-entry-delete nil "ATTACH_DIR") > + (org-attach-dir)))) > + (unless (or (string= old new) > + (not old)) > + (when (and new (yes-or-no-p "Copy over attachments from old directory? ")) > (copy-directory old new t nil t)) > (when (yes-or-no-p (concat "Delete " old)) > (delete-directory old t))))) > > -(defun org-attach-set-inherit () > - "Set the ATTACH_DIR_INHERIT property of the current entry. > -The property defines the directory that is used for attachments > -of the entry and any children that do not explicitly define (by setting > -the ATTACH_DIR property) their own attachment directory." > - (interactive) > - (org-entry-put nil "ATTACH_DIR_INHERIT" "t") > - (message "Children will inherit attachment directory")) > - > -(defun org-attach-use-annex () > - "Return non-nil if git annex can be used." > - (let ((git-dir (vc-git-root (expand-file-name org-attach-directory)))) > - (and org-attach-git-annex-cutoff > - (or (file-exists-p (expand-file-name "annex" git-dir)) > - (file-exists-p (expand-file-name ".git/annex" git-dir)))))) > - > -(defun org-attach-annex-get-maybe (path) > - "Call git annex get PATH (via shell) if using git annex. > -Signals an error if the file content is not available and it was not retrieved." > - (let* ((default-directory (expand-file-name org-attach-directory)) > - (path-relative (file-relative-name path))) > - (when (and (org-attach-use-annex) > - (not > - (string-equal > - "found" > - (shell-command-to-string > - (format "git annex find --format=found --in=here %s" > - (shell-quote-argument path-relative)))))) > - (let ((should-get > - (if (eq org-attach-annex-auto-get 'ask) > - (y-or-n-p (format "Run git annex get %s? " path-relative)) > - org-attach-annex-auto-get))) > - (if should-get > - (progn (message "Running git annex get \"%s\"." path-relative) > - (call-process "git" nil nil nil "annex" "get" path-relative)) > - (error "File %s stored in git annex but it is not available, and was not retrieved" > - path)))))) > - > -(defun org-attach-commit () > - "Commit changes to git if `org-attach-directory' is properly initialized. > -This checks for the existence of a \".git\" directory in that directory." > - (let* ((dir (expand-file-name org-attach-directory)) > - (git-dir (vc-git-root dir)) > - (use-annex (org-attach-use-annex)) > - (changes 0)) > - (when (and git-dir (executable-find "git")) > - (with-temp-buffer > - (cd dir) > - (dolist (new-or-modified > - (split-string > - (shell-command-to-string > - "git ls-files -zmo --exclude-standard") "\0" t)) > - (if (and use-annex > - (>= (file-attribute-size (file-attributes new-or-modified)) > - org-attach-git-annex-cutoff)) > - (call-process "git" nil nil nil "annex" "add" new-or-modified) > - (call-process "git" nil nil nil "add" new-or-modified)) > - (cl-incf changes)) > - (dolist (deleted > - (split-string > - (shell-command-to-string "git ls-files -z --deleted") "\0" t)) > - (call-process "git" nil nil nil "rm" deleted) > - (cl-incf changes)) > - (when (> changes 0) > - (shell-command "git commit -m 'Synchronized attachments'")))))) > - > (defun org-attach-tag (&optional off) > "Turn the autotag on or (if OFF is set) off." > (when org-attach-auto-tag > @@ -423,22 +428,21 @@ Only do this when `org-attach-store-link-p' is non-nil." > (org-attach-attach url))) > > (defun org-attach-buffer (buffer-name) > - "Attach BUFFER-NAME's contents to current task. > + "Attach BUFFER-NAME's contents to current outline node. > BUFFER-NAME is a string. Signals a `file-already-exists' error > if it would overwrite an existing filename." > (interactive "bBuffer whose contents should be attached: ") > - (let ((output (expand-file-name buffer-name (org-attach-dir t)))) > + (let* ((attach-dir (org-attach-dir 'get-create)) > + (output (expand-file-name buffer-name attach-dir))) > (when (file-exists-p output) > (signal 'file-already-exists (list "File exists" output))) > - (when (and org-attach-file-list-property (not org-attach-inherited)) > - (org-entry-add-to-multivalued-property > - (point) org-attach-file-list-property buffer-name)) > + (run-hook-with-args 'org-attach-after-change-hook attach-dir) > (org-attach-tag) > (with-temp-file output > (insert-buffer-substring buffer-name)))) > > (defun org-attach-attach (file &optional visit-dir method) > - "Move/copy/link FILE into the attachment directory of the current task. > + "Move/copy/link FILE into the attachment directory of the current outline node. > If VISIT-DIR is non-nil, visit the directory with dired. > METHOD may be `cp', `mv', `ln', `lns' or `url' default taken from > `org-attach-method'." > @@ -453,10 +457,7 @@ METHOD may be `cp', `mv', `ln', `lns' or `url' default taken from > nil)) > (setq method (or method org-attach-method)) > (let ((basename (file-name-nondirectory file))) > - (when (and org-attach-file-list-property (not org-attach-inherited)) > - (org-entry-add-to-multivalued-property > - (point) org-attach-file-list-property basename)) > - (let* ((attach-dir (org-attach-dir t)) > + (let* ((attach-dir (org-attach-dir 'get-create)) > (fname (expand-file-name basename attach-dir))) > (cond > ((eq method 'mv) (rename-file file fname)) > @@ -464,8 +465,7 @@ METHOD may be `cp', `mv', `ln', `lns' or `url' default taken from > ((eq method 'ln) (add-name-to-file file fname)) > ((eq method 'lns) (make-symbolic-link file fname)) > ((eq method 'url) (url-copy-file file fname))) > - (when org-attach-commit > - (org-attach-commit)) > + (run-hook-with-args 'org-attach-after-change-hook attach-dir) > (org-attach-tag) > (cond ((eq org-attach-store-link-p 'attached) > (org-attach-store-link fname)) > @@ -473,7 +473,7 @@ METHOD may be `cp', `mv', `ln', `lns' or `url' default taken from > (org-attach-store-link file))) > (if visit-dir > (dired attach-dir) > - (message "File %S is now a task attachment." basename))))) > + (message "File %S is now an attachment." basename))))) > > (defun org-attach-attach-cp () > "Attach a file by copying it." > @@ -498,13 +498,10 @@ On some systems, this apparently does copy the file instead." > (let ((org-attach-method 'lns)) (call-interactively 'org-attach-attach))) > > (defun org-attach-new (file) > - "Create a new attachment FILE for the current task. > + "Create a new attachment FILE for the current outline node. > The attachment is created as an Emacs buffer." > (interactive "sCreate attachment named: ") > - (when (and org-attach-file-list-property (not org-attach-inherited)) > - (org-entry-add-to-multivalued-property > - (point) org-attach-file-list-property file)) > - (let ((attach-dir (org-attach-dir t))) > + (let ((attach-dir (org-attach-dir 'get-create))) > (org-attach-tag) > (find-file (expand-file-name file attach-dir)) > (message "New attachment %s" file))) > @@ -512,7 +509,7 @@ The attachment is created as an Emacs buffer." > (defun org-attach-delete-one (&optional file) > "Delete a single attachment." > (interactive) > - (let* ((attach-dir (org-attach-dir t)) > + (let* ((attach-dir (org-attach-dir)) > (files (org-attach-file-list attach-dir)) > (file (or file > (completing-read > @@ -524,44 +521,32 @@ The attachment is created as an Emacs buffer." > (unless (file-exists-p file) > (error "No such attachment: %s" file)) > (delete-file file) > - (when org-attach-commit > - (org-attach-commit)))) > + (run-hook-with-args 'org-attach-after-change-hook attach-dir))) > > (defun org-attach-delete-all (&optional force) > - "Delete all attachments from the current task. > + "Delete all attachments from the current outline node. > This actually deletes the entire attachment directory. > A safer way is to open the directory in dired and delete from there." > (interactive "P") > - (when (and org-attach-file-list-property (not org-attach-inherited)) > - (org-entry-delete (point) org-attach-file-list-property)) > (let ((attach-dir (org-attach-dir))) > - (when > - (and attach-dir > - (or force > - (y-or-n-p "Are you sure you want to remove all attachments of this entry? "))) > - (shell-command (format "rm -fr %s" attach-dir)) > + (when (and attach-dir > + (or force > + (yes-or-no-p "Really remove all attachments of this entry? "))) > + (delete-directory attach-dir (yes-or-no-p "Recursive?") t) > (message "Attachment directory removed") > - (when org-attach-commit > - (org-attach-commit)) > + (run-hook-with-args 'org-attach-after-change-hook attach-dir) > (org-attach-untag)))) > > (defun org-attach-sync () > - "Synchronize the current tasks with its attachments. > + "Synchronize the current outline node with its attachments. > This can be used after files have been added externally." > (interactive) > - (when org-attach-commit > - (org-attach-commit)) > - (when (and org-attach-file-list-property (not org-attach-inherited)) > - (org-entry-delete (point) org-attach-file-list-property)) > (let ((attach-dir (org-attach-dir))) > (when attach-dir > + (run-hook-with-args 'org-attach-after-change-hook attach-dir) > (let ((files (org-attach-file-list attach-dir))) > - (org-attach-tag (not files)) > - (when org-attach-file-list-property > - (dolist (file files) > - (unless (string-match "^\\.\\.?\\'" file) > - (org-entry-add-to-multivalued-property > - (point) org-attach-file-list-property file)))))))) > + (org-attach-tag (not files)))) > + (unless attach-dir (org-attach-tag t)))) > > (defun org-attach-file-list (dir) > "Return a list of files in the attachment directory. > @@ -570,35 +555,40 @@ This ignores files ending in \"~\"." > (mapcar (lambda (x) (if (string-match "^\\.\\.?\\'" x) nil x)) > (directory-files dir nil "[^~]\\'")))) > > -(defun org-attach-reveal (&optional if-exists) > - "Show the attachment directory of the current task. > +(defun org-attach-reveal () > + "Show the attachment directory of the current outline node. > This will attempt to use an external program to show the directory." > - (interactive "P") > - (let ((attach-dir (org-attach-dir (not if-exists)))) > - (and attach-dir (org-open-file attach-dir)))) > + (interactive) > + (let ((attach-dir (org-attach-dir))) > + (if attach-dir > + (org-open-file attach-dir) > + (error "No attachment directory exist")))) > > (defun org-attach-reveal-in-emacs () > - "Show the attachment directory of the current task in dired." > + "Show the attachment directory of the current outline node in dired." > (interactive) > - (let ((attach-dir (org-attach-dir t))) > - (dired attach-dir))) > + (let ((attach-dir (org-attach-dir))) > + (if attach-dir > + (dired attach-dir) > + (error "No attachment directory exist")))) > > (defun org-attach-open (&optional in-emacs) > - "Open an attachment of the current task. > + "Open an attachment of the current outline node. > If there are more than one attachment, you will be prompted for the file name. > This command will open the file using the settings in `org-file-apps' > and in the system-specific variants of this variable. > If IN-EMACS is non-nil, force opening in Emacs." > (interactive "P") > - (let* ((attach-dir (org-attach-dir t)) > - (files (org-attach-file-list attach-dir)) > - (file (if (= (length files) 1) > - (car files) > - (completing-read "Open attachment: " > - (mapcar #'list files) nil t))) > - (path (expand-file-name file attach-dir))) > - (org-attach-annex-get-maybe path) > - (org-open-file path in-emacs))) > + (let ((attach-dir (org-attach-dir))) > + (if attach-dir > + (let* ((file (pcase (org-attach-file-list attach-dir) > + (`(,file) file) > + (files (completing-read "Open attachment: " > + (mapcar #'list files) nil t)))) > + (path (expand-file-name file attach-dir))) > + (run-hook-with-args 'org-attach-open-hook path) > + (org-open-file path in-emacs)) > + (error "No attachment directory exist")))) > > (defun org-attach-open-in-emacs () > "Open attachment, force opening in Emacs. > @@ -617,6 +607,69 @@ Basically, this adds the path to the attachment directory, and a \"file:\" > prefix." > (concat "file:" (org-attach-expand file))) > > +(org-link-set-parameters "attachment" > + :follow #'org-attach-open-link > + :export #'org-attach-export-link > + :complete #'org-attach-complete-link) > + > +(defun org-attach-open-link (link &optional in-emacs) > + "Attachment link type LINK is expanded with the attached directory and opened. > + > +With optional prefix argument IN-EMACS, Emacs will visit the file. > +With a double \\[universal-argument] \\[universal-argument] \ > +prefix arg, Org tries to avoid opening in Emacs > +and to use an external application to visit the file." > + (interactive "P") > + (let (line search) > + (cond > + ((string-match "::\\([0-9]+\\)\\'" link) > + (setq line (string-to-number (match-string 1 link)) > + link (substring link 0 (match-beginning 0)))) > + ((string-match "::\\(.+\\)\\'" link) > + (setq search (match-string 1 link) > + link (substring link 0 (match-beginning 0))))) > + (if (string-match "[*?{]" (file-name-nondirectory link)) > + (dired (org-attach-expand link)) > + (org-open-file (org-attach-expand link) in-emacs line search)))) > + > +(defun org-attach-complete-link () > + "Advise the user with the available files in the attachment directory." > + (let ((attach-dir (org-attach-dir))) > + (if attach-dir > + (let* ((attached-dir (expand-file-name attach-dir)) > + (file (read-file-name "File: " attached-dir)) > + (pwd (file-name-as-directory attached-dir)) > + (pwd-relative (file-name-as-directory > + (abbreviate-file-name attached-dir)))) > + (cond > + ((string-match (concat "^" (regexp-quote pwd-relative) "\\(.+\\)") file) > + (concat "attachment:" (match-string 1 file))) > + ((string-match (concat "^" (regexp-quote pwd) "\\(.+\\)") > + (expand-file-name file)) > + (concat "attachment:" (match-string 1 (expand-file-name file)))) > + (t (concat "attachment:" file)))) > + (error "No attachment directory exist")))) > + > +(defun org-attach-export-link (link description format) > + "Translate attachment LINK from Org mode format to exported FORMAT. > +Also includes the DESCRIPTION of the link in the export." > + (save-excursion > + (let (path desc) > + (cond > + ((string-match "::\\([0-9]+\\)\\'" link) > + (setq link (substring link 0 (match-beginning 0)))) > + ((string-match "::\\(.+\\)\\'" link) > + (setq link (substring link 0 (match-beginning 0))))) > + (setq path (file-relative-name (org-attach-expand link)) > + desc (or description link)) > + (pcase format > + (`html (format "%s" path desc)) > + (`latex (format "\\href{%s}{%s}" path desc)) > + (`texinfo (format "@uref{%s,%s}" path desc)) > + (`ascii (format "%s (%s)" desc path)) > + (`md (format "[%s](%s)" desc path)) > + (_ path))))) > + > (defun org-attach-archive-delete-maybe () > "Maybe delete subtree attachments when archiving. > This function is called by `org-archive-hook'. The option > @@ -644,7 +697,7 @@ Idea taken from `gnus-dired-attach'." > (interactive > (list (dired-get-marked-files))) > (unless (eq major-mode 'dired-mode) > - (user-error "This command must be triggered in a dired buffer.")) > + (user-error "This command must be triggered in a dired buffer")) > (let ((start-win (selected-window)) > (other-win > (get-window-with-predicate > diff --git a/lisp/org-compat.el b/lisp/org-compat.el > index 9cb396fe9..42fe64379 100644 > --- a/lisp/org-compat.el > +++ b/lisp/org-compat.el > @@ -263,6 +263,9 @@ Counting starts at 1." > (define-obsolete-function-alias 'org-remove-latex-fragment-image-overlays > 'org-clear-latex-preview "Org 9.3") > > +(define-obsolete-variable-alias 'org-attach-directory > + 'org-attach-id-dir "Org 9.3") > + > (defun org-in-fixed-width-region-p () > "Non-nil if point in a fixed-width region." > (save-match-data > diff --git a/lisp/org.el b/lisp/org.el > index 9601ecf2e..5d6cc757d 100644 > --- a/lisp/org.el > +++ b/lisp/org.el > @@ -3847,7 +3847,9 @@ This is needed for font-lock setup.") > (beg end)) > (declare-function org-agenda-set-restriction-lock "org-agenda" (&optional type)) > (declare-function org-agenda-skip "org-agenda" ()) > -(declare-function org-attach-reveal "org-attach" (&optional if-exists)) > +(declare-function org-attach-expand "org-attach" (file)) > +(declare-function org-attach-reveal "org-attach" ()) > +(declare-function org-attach-reveal-in-emacs "org-attach" ()) > (declare-function org-gnus-follow-link "org-gnus" (&optional group article)) > (declare-function org-indent-mode "org-indent" (&optional arg)) > (declare-function org-inlinetask-goto-beginning "org-inlinetask" ()) > @@ -8645,12 +8647,15 @@ a link." > (pcase (org-offer-links-in-entry (current-buffer) (point) arg) > (`(nil . ,_) > (require 'org-attach) > - (org-attach-reveal 'if-exists)) > + (message "Opening attachment-dir") > + (if (equal arg '(4)) > + (org-attach-reveal-in-emacs) > + (org-attach-reveal))) > (`(,links . ,links-end) > (dolist (link (if (stringp links) (list links) links)) > (search-forward link nil links-end) > (goto-char (match-beginning 0)) > - (org-open-at-point)))))) > + (org-open-at-point arg)))))) > ;; On a footnote reference or at definition's label. > ((or (eq type 'footnote-reference) > (and (eq type 'footnote-definition) > @@ -16630,13 +16635,14 @@ boundaries." > ;; "file:" links. Also check link abbreviations since > ;; some might expand to "file" links. > (file-types-re > - (format "\\[\\[\\(?:file%s:\\|[./~]\\)\\|\\]\\[\\( + (format "\\[\\[\\(?:file%s:\\|attachment:\\|[./~]\\)\\|\\]\\[\\( (if (not link-abbrevs) "" > (concat "\\|" (regexp-opt link-abbrevs)))))) > (while (re-search-forward file-types-re end t) > (let* ((link (org-element-lineage > (save-match-data (org-element-context)) > '(link) t)) > + (linktype (org-element-property :type link)) > (inner-start (match-beginning 1)) > (path > (cond > @@ -16650,7 +16656,8 @@ boundaries." > ;; INCLUDE-LINKED is non-nil. > ((or (not (org-element-property :contents-begin link)) > include-linked) > - (and (equal "file" (org-element-property :type link)) > + (and (or (equal "file" linktype) > + (equal "attachment" linktype)) > (org-element-property :path link))) > ;; Link with a description. Check if description > ;; is a filename. Even if Org doesn't have syntax > @@ -16669,7 +16676,11 @@ boundaries." > (match-end 0)) > (match-string 2))))))) > (when (and path (string-match-p file-extension-re path)) > - (let ((file (expand-file-name path))) > + (let ((file (if (equal "attachment" linktype) > + (progn > + (require 'org-attach) > + (org-attach-expand path)) > + (expand-file-name path)))) > (when (file-exists-p file) > (let ((width > ;; Apply `org-image-actual-width' specifications. > diff --git a/lisp/ox-html.el b/lisp/ox-html.el > index f1c06e069..757006321 100644 > --- a/lisp/ox-html.el > +++ b/lisp/ox-html.el > @@ -884,6 +884,7 @@ link to the image." > > (defcustom org-html-inline-image-rules > '(("file" . "\\.\\(jpeg\\|jpg\\|png\\|gif\\|svg\\)\\'") > + ("attachment" . "\\.\\(jpeg\\|jpg\\|png\\|gif\\|svg\\)\\'") > ("http" . "\\.\\(jpeg\\|jpg\\|png\\|gif\\|svg\\)\\'") > ("https" . "\\.\\(jpeg\\|jpg\\|png\\|gif\\|svg\\)\\'")) > "Rules characterizing image files that can be inlined into HTML. > diff --git a/testing/examples/att1/fileA b/testing/examples/att1/fileA > new file mode 100644 > index 000000000..9a0406c0d > --- /dev/null > +++ b/testing/examples/att1/fileA > @@ -0,0 +1 @@ > +Text in fileA > diff --git a/testing/examples/att1/fileB b/testing/examples/att1/fileB > new file mode 100644 > index 000000000..dd2331824 > --- /dev/null > +++ b/testing/examples/att1/fileB > @@ -0,0 +1 @@ > +Text in fileB > diff --git a/testing/examples/att2/fileC b/testing/examples/att2/fileC > new file mode 100644 > index 000000000..2c9a92bfe > --- /dev/null > +++ b/testing/examples/att2/fileC > @@ -0,0 +1 @@ > +Text in fileC > \ No newline at end of file > diff --git a/testing/examples/att2/fileD b/testing/examples/att2/fileD > new file mode 100644 > index 000000000..c706556c5 > --- /dev/null > +++ b/testing/examples/att2/fileD > @@ -0,0 +1 @@ > +text in fileD > diff --git a/testing/examples/attachments.org b/testing/examples/attachments.org > new file mode 100644 > index 000000000..ab4a4e548 > --- /dev/null > +++ b/testing/examples/attachments.org > @@ -0,0 +1,32 @@ > +#+TITLE: Org attach testfile > +Used to test and verify the functionality of org-attach. > + > +* H1 > + :PROPERTIES: > + :DIR: att1 > + :END: > +A link to one attachment: [[attachment:fileA]] > + > +** H1.1 > +A link to another attachment: [[attachment:fileB]] > + > +** H1.2 > + :PROPERTIES: > + :DIR: att2 > + :END: > + > +* H2 > + :PROPERTIES: > + :ID: abcd123 > + :END: > + > +* H3 > + :PROPERTIES: > + :DIR: att1 > + :ID: abcd1234 > + :END: > + > +** H3.1 > + :PROPERTIES: > + :ID: abcd12345 > + :END: > diff --git a/testing/examples/data/ab/cd123/fileE b/testing/examples/data/ab/cd123/fileE > new file mode 100644 > index 000000000..80d337772 > --- /dev/null > +++ b/testing/examples/data/ab/cd123/fileE > @@ -0,0 +1 @@ > +peek-a-boo > diff --git a/testing/lisp/test-org-attach-annex.el b/testing/lisp/test-org-attach-git.el > similarity index 93% > rename from testing/lisp/test-org-attach-annex.el > rename to testing/lisp/test-org-attach-git.el > index 7f2792696..8b826b72f 100644 > --- a/testing/lisp/test-org-attach-annex.el > +++ b/testing/lisp/test-org-attach-git.el > @@ -20,19 +20,19 @@ > > ;;; Code: > (org-test-for-executable "git-annex") > -(require 'org-attach) > +(require 'org-attach-git) > (require 'cl-lib) > > -(defmacro test-org-attach-annex/with-annex (&rest body) > +(defmacro test-org-attach-git/with-annex (&rest body) > `(let ((tmpdir (make-temp-file "org-annex-test" t "/"))) > (unwind-protect > (let ((default-directory tmpdir) > - (org-attach-directory tmpdir)) > + (org-attach-id-dir tmpdir)) > (shell-command "git init") > (shell-command "git annex init") > ,@body)))) > > -(ert-deftest test-org-attach/use-annex () > +(ert-deftest test-org-attach-git/use-annex () > (test-org-attach-annex/with-annex > (let ((org-attach-git-annex-cutoff 1)) > (should (org-attach-use-annex))) > @@ -44,12 +44,12 @@ > (let ((tmpdir (make-temp-file "org-annex-test" t "/"))) > (unwind-protect > (let ((default-directory tmpdir) > - (org-attach-directory tmpdir)) > + (org-attach-id-dir tmpdir)) > (shell-command "git init") > (should-not (org-attach-use-annex))) > (delete-directory tmpdir 'recursive)))) > > -(ert-deftest test-org-attach/get-maybe () > +(ert-deftest test-org-attach-git/get-maybe () > (test-org-attach-annex/with-annex > (let ((path (expand-file-name "test-file")) > (annex-dup (make-temp-file "org-annex-test" t "/"))) > diff --git a/testing/lisp/test-org-attach.el b/testing/lisp/test-org-attach.el > index c2f2be356..5bcfe86fd 100644 > --- a/testing/lisp/test-org-attach.el > +++ b/testing/lisp/test-org-attach.el > @@ -28,6 +28,75 @@ > (require 'org-attach) > (eval-and-compile (require 'cl-lib)) > > +(ert-deftest test-org-attach/dir () > + "Test `org-attach-get' specifications." > + (should (equal "Text in fileA\n" > + (org-test-in-example-file org-test-attachments-file > + (goto-char 157) ;; First attachment link > + (org-open-at-point) > + (buffer-string)))) > + (should-not (equal "Text in fileB\n" > + (org-test-in-example-file org-test-attachments-file > + (goto-char 219) ;; Second attachment link > + (let ((org-attach-use-inheritance nil)) > + (org-open-at-point) > + (buffer-string))))) > + (should (equal "Text in fileB\n" > + (org-test-in-example-file org-test-attachments-file > + (goto-char 219) ;; Second attachment link > + (let ((org-attach-use-inheritance t)) > + (org-open-at-point) > + (buffer-string))))) > + (should-not (equal "att1" > + (org-test-in-example-file org-test-attachments-file > + (goto-char 179) ;; H1.1 > + (let ((org-attach-use-inheritance nil)) > + (org-attach-dir))))) > + (should (equal "att1" > + (org-test-in-example-file org-test-attachments-file > + (goto-char 179) ;; H1.1 > + (let ((org-attach-use-inheritance t)) > + (org-attach-dir))))) > + (should (equal '("fileC" "fileD") > + (org-test-in-example-file org-test-attachments-file > + (goto-char 239) ;; H1.2 > + (org-attach-file-list (org-attach-dir))))) > + (should (equal '("fileC" "fileD") > + (org-test-in-example-file org-test-attachments-file > + (goto-char 239) ;; H1.2 > + (org-attach-file-list (org-attach-dir))))) > + (should (equal '("fileE") > + (org-test-in-example-file org-test-attachments-file > + (goto-char 289) ;; H2 > + (let ((org-attach-id-dir "data/")) > + (org-attach-file-list (org-attach-dir)))))) > + (should (equal "peek-a-boo\n" > + (org-test-in-example-file org-test-attachments-file > + (goto-char 289) ;; H2 > + (let ((org-attach-id-dir "data/")) > + (org-attach-open-in-emacs) > + (buffer-string))))) > + (should (equal '("fileA" "fileB") > + (org-test-in-example-file org-test-attachments-file > + (goto-char 336) ;; H3 > + (org-attach-file-list (org-attach-dir))))) > + (should (equal "data/ab/cd12345" > + (org-test-in-example-file org-test-attachments-file > + (goto-char 401) ;; H3.1 > + (let ((org-attach-use-inheritance nil) > + (org-attach-id-dir "data/")) > + (file-relative-name (org-attach-dir)))))) > + (should (equal '("fileA" "fileB") > + (org-test-in-example-file org-test-attachments-file > + (goto-char 401) ;; H3.1 > + (let ((org-attach-use-inheritance t)) > + ;; This is where it get's a bit sketchy...! DIR always has > + ;; priority over ID, even if ID is declared "higher up" in the > + ;; tree. This can potentially be revised. But it is also > + ;; pretty clean. DIR is always higher in priority than ID right > + ;; now, no matter the depth in the tree. > + (org-attach-file-list (org-attach-dir))))))) > + > (ert-deftest test-org-attach/dired-attach-to-next-best-subtree/1 () > "Attach file at point in dired to subtree." > (should > diff --git a/testing/lisp/test-org.el b/testing/lisp/test-org.el > index d671b5c78..a618da479 100644 > --- a/testing/lisp/test-org.el > +++ b/testing/lisp/test-org.el > @@ -2514,7 +2514,11 @@ Foo Bar > (catch :result > (cl-letf (((symbol-function 'org-tags-view) > (lambda (&rest args) (throw :result t)))) > - (org-open-at-point) > + ;; When point isn't on a tag it's going to try other things, > + ;; possibly trying to open attachments which will return an > + ;; error if there isn't an attachment. Supress that error. > + (ignore-errors > + (org-open-at-point)) > nil))))) > > > diff --git a/testing/org-test.el b/testing/org-test.el > index 295df1919..c3e21eb30 100644 > --- a/testing/org-test.el > +++ b/testing/org-test.el > @@ -87,6 +87,9 @@ org-test searches this directory up the directory tree.") > (defconst org-test-no-heading-file > (expand-file-name "no-heading.org" org-test-example-dir)) > > +(defconst org-test-attachments-file > + (expand-file-name "attachments.org" org-test-example-dir)) > + > (defconst org-test-link-in-heading-file > (expand-file-name "link-in-heading.org" org-test-dir)) > > -- > 2.17.1 >