├── README.org ├── images ├── screenshot1.png └── screenshot2.png ├── notes.org └── org-now.el /README.org: -------------------------------------------------------------------------------- 1 | #+TITLE: org-now 2 | 3 | #+PROPERTY: LOGGING nil 4 | 5 | This package provides commands to conveniently show Org headings in a sidebar window while you're working on them. A heading in one of your Org files is defined as the "now" heading, and other headings are refiled to it with one command, and back to their original location with another. 6 | 7 | The sidebar window is an indirect buffer created with =org-tree-to-indirect-buffer=, so you can work in it as you would a normal buffer. Being a special Emacs side window, it's persistent, resisting being closed by accident by window management commands. 8 | 9 | Note that this package adds Org UUIDs to entries in property drawers when they are refiled, to ensure they are tracked properly while they're being moved. 10 | 11 | * Screenshots 12 | 13 | There's nothing special about the appearance of this package's functionality. But here are two example screenshots. 14 | 15 | By default, the sidebar's outline is shown at level 2, so the "now" heading should be a top-level heading: 16 | [[images/screenshot1.png]] 17 | 18 | The sidebar is just an Org buffer, so you can expand it, type into it, etc: 19 | [[images/screenshot2.png]] 20 | 21 | * Installation 22 | 23 | Install these required packages: 24 | 25 | + [[https://github.com/magnars/dash.el][dash.el]] 26 | 27 | Then put =org-now.el= in your ~load-path~, and put this in your init file: 28 | 29 | #+BEGIN_SRC elisp 30 | (require 'org-now) 31 | #+END_SRC 32 | 33 | Here's a configuration with mnemonic keybindings that you may find convenient, using [[https://github.com/jwiegley/use-package][use-package]] and [[https://github.com/noctuid/general.el][general]]: 34 | 35 | #+BEGIN_SRC elisp 36 | (use-package org-now 37 | :general (:keymaps 'org-mode-map 38 | :prefix "M-SPC" 39 | "rl" #'org-now-link 40 | "rn" #'org-now-refile-to-now 41 | "rp" #'org-now-refile-to-previous-location)) 42 | #+END_SRC 43 | 44 | ** COMMENT MELPA 45 | 46 | # Not on MELPA yet. 47 | 48 | If you installed from MELPA, you're done. 49 | 50 | * Usage 51 | 52 | When using ~org-now~ for the first time, you'll be prompted to configure the =org-now-location= setting to point to a place in one of your Org files where you want to temporarily refile "now" tasks. 53 | 54 | ** Commands 55 | 56 | - ~org-now~ :: Show the =org-now= sidebar window, or select it if it's already open. (The other commands do this automatically.) 57 | 58 | *Refiling commands:* These commands are usable in regular Org buffers. 59 | 60 | - ~org-now-link~ :: Add a link to the entry at point to the =org-now= buffer. This command works in any buffer that ~org-store-link~ can store a link for, not just in Org buffers. 61 | - ~org-now-refile-to-now~ :: Refile entry at point to the =org-now= buffer. 62 | - ~org-now-refile-to-previous-location~ :: Refile entry in =org-now= buffer back to its original location. 63 | 64 | *Agenda commands:* These commands are usable in Org Agenda buffers. 65 | 66 | - =org-now-agenda-link= :: Add a link to the entry at point to the =org-now= buffer. 67 | - ~org-now-agenda-refile-to-now~ :: Refile entry at point to the =org-now= buffer. 68 | - ~org-now-agenda-refile-to-previous-location~ :: Refile entry in =org-now= buffer back to its original location. 69 | 70 | They're also usable as Agenda "bulk" commands on marked entries. You can configure them like this: 71 | 72 | #+BEGIN_SRC elisp 73 | (setf org-agenda-bulk-custom-functions 74 | '((?n org-now-agenda-refile-to-now) 75 | (?P org-now-agenda-refile-to-previous-location))) 76 | #+END_SRC 77 | 78 | *Functions:* 79 | 80 | - =org-now-buffer= :: Return the =org-now= buffer, creating it if necessary. Useful in functions that may display the buffer, e.g. in [[https://github.com/alphapapa/org-sidebar][org-sidebar]]. This function is autoloaded. 81 | 82 | ** Hook examples 83 | 84 | The ~org-now-hook~ option can be used to run user-defined functions after the =org-now= buffer is created. Here are two examples you might find useful. 85 | 86 | + Disable mode-line :: 87 | If you don't need or want the mode-line in the sidebar window, this function will disable it. In this case, it's recommended to use something like ~real-auto-save-mode~ in Org buffers to ensure that refiled headings are automatically saved to disk, because you won't be able to see the buffer's modified status. 88 | 89 | #+BEGIN_SRC elisp 90 | (lambda () 91 | (setq mode-line-format nil)) 92 | #+END_SRC 93 | 94 | + Set =org-level-1= heading to 10 point size :: 95 | This is helpful when your =org-now-location= is a file, in which case headings will be top-level headings. This function can be used to reduce the font size so the headings fit better in the sidebar window. It works especially well if your =org-level-= faces each inherit from their higher-level face (i.e. =org-level-2= inherits from =org-level-1=, etc.). 96 | 97 | #+BEGIN_SRC elisp 98 | (lambda () 99 | (face-remap-add-relative 'org-level-1 '(:height 100))) 100 | #+END_SRC 101 | 102 | * Development 103 | 104 | Bug reports, feature requests, suggestions — /oh my/! 105 | 106 | * License 107 | 108 | GPLv3 109 | 110 | # Local Variables: 111 | # org-export-with-properties: () 112 | # org-export-with-title: t 113 | # End: 114 | 115 | -------------------------------------------------------------------------------- /images/screenshot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphapapa/org-now/d9d6b93daedab2a137a67a14ca41c40426f3c1bf/images/screenshot1.png -------------------------------------------------------------------------------- /images/screenshot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphapapa/org-now/d9d6b93daedab2a137a67a14ca41c40426f3c1bf/images/screenshot2.png -------------------------------------------------------------------------------- /notes.org: -------------------------------------------------------------------------------- 1 | * Tasks 2 | 3 | ** TODO Option to automatically save buffers after refiling 4 | 5 | ** TODO Update screenshots 6 | 7 | Show example of refiling to top-level headings in a file and using the header line. 8 | -------------------------------------------------------------------------------- /org-now.el: -------------------------------------------------------------------------------- 1 | ;;; org-now.el --- Conveniently show current tasks in a sidebar -*- lexical-binding: t; -*- 2 | 3 | ;; Author: Adam Porter 4 | ;; URL: http://github.com/alphapapa/org-now 5 | ;; Version: 0.1-pre 6 | ;; Package-Requires: ((emacs "26.1") (dash)) 7 | ;; Keywords: org 8 | 9 | ;; This file is not part of GNU Emacs. 10 | 11 | ;;; Commentary: 12 | 13 | ;; TODO: Update description, readme, etc. 14 | 15 | ;; This package provides commands to conveniently show Org headings in a sidebar 16 | ;; window while you're working on them. A heading in one of your Org files is 17 | ;; defined as the "now" heading, and other headings are refiled to it with one 18 | ;; command, and back to their original location with another. 19 | 20 | ;; The sidebar window is an indirect buffer created with 21 | ;; `org-tree-to-indirect-buffer', so you can work in it as you would a normal 22 | ;; buffer. Being a special Emacs side window, it's persistent, resisting being 23 | ;; closed by accident by window management commands. 24 | 25 | ;; Note that this package adds Org UUIDs to entries in property drawers when 26 | ;; they are refiled, to ensure they are tracked properly while they're being 27 | ;; moved. 28 | 29 | ;;;; Installation 30 | 31 | ;;;;; MELPA 32 | 33 | ;; If you installed from MELPA, you're done. 34 | 35 | ;;;;; Manual 36 | 37 | ;; Install these required packages: 38 | 39 | ;; + dash 40 | 41 | ;; Then put this file in your load-path, and put this in your init 42 | ;; file: 43 | 44 | ;; (require 'org-now) 45 | 46 | ;;;; Usage 47 | 48 | ;; 1. Run the command `org-now' to show the sidebar. You'll be prompted to 49 | ;; configure the `org-now-location' setting to point to a heading in one of your 50 | ;; Org files where you want to temporarily refile "now" tasks. 51 | ;; 2. Refile tasks to the "now" buffer with the command `org-now-refile-to-now.' 52 | ;; 3. Move tasks back to their original location with the command 53 | ;; `org-now-refile-to-previous-location.' 54 | 55 | ;;; License: 56 | 57 | ;; This program is free software; you can redistribute it and/or modify 58 | ;; it under the terms of the GNU General Public License as published by 59 | ;; the Free Software Foundation, either version 3 of the License, or 60 | ;; (at your option) any later version. 61 | 62 | ;; This program is distributed in the hope that it will be useful, 63 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 64 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 65 | ;; GNU General Public License for more details. 66 | 67 | ;; You should have received a copy of the GNU General Public License 68 | ;; along with this program. If not, see . 69 | 70 | ;;; Code: 71 | 72 | ;;;; Requirements 73 | 74 | (require 'org) 75 | (require 'org-id) 76 | 77 | (require 'dash) 78 | 79 | ;;;; Customization 80 | 81 | (defgroup org-now nil 82 | "Settings for `org-now'." 83 | :link '(url-link "http://github.com/alphapapa/org-now") 84 | :group 'org) 85 | 86 | (defcustom org-now-location nil 87 | "Location of the \"Now\" entry. 88 | A valid Org outline path list, starting with filename. Each 89 | subsequent string should be a heading in the outline hierarchy." 90 | ;; MAYBE: This could also be a UUID, but that might cause Org to spend time 91 | ;; opening buffers looking for it. 92 | :type '(list (file :must-match t) 93 | (choice (repeat :inline t :tag "Outline path" string) 94 | (const :tag "None" nil)))) 95 | 96 | (defcustom org-now-window-side 'right 97 | "Which side to show the sidebar on." 98 | :type '(choice (const :tag "Left" left) 99 | (const :tag "Right" right))) 100 | 101 | (defcustom org-now-default-cycle-level 2 102 | "Org heading level to expand to in side buffer by default." 103 | :type '(choice (integer :tag "Outline level") 104 | (const :tag "Don't cycle" nil))) 105 | 106 | (defcustom org-now-hook nil 107 | "Functions called after creating the `org-now' buffer." 108 | :type '(repeat function)) 109 | 110 | (defcustom org-now-no-other-window nil 111 | "Whether `other-window' should cycle through the `org-now' sidebar window. 112 | See info node `(elisp)Cyclic Window Ordering'." 113 | :type 'boolean) 114 | 115 | (defcustom org-now-window-parameters 116 | `((tab-line-format . none) 117 | (mode-line-format . none)) 118 | "Window parameters used for `org-now' window." 119 | :type '(alist :key-type symbol :value-type sexp)) 120 | 121 | (defface org-now-header 122 | '((t (:weight bold))) 123 | "For header in `org-now' buffer.") 124 | 125 | ;;;; Functions 126 | 127 | ;;;;; Commands 128 | 129 | ;;;###autoload 130 | (defun org-now () 131 | "Toggle `org-now' side window. 132 | If the selected window shows the `org-now' buffer, call 133 | `quit-window'; otherwise, display/select the `org-now' buffer in 134 | a side window as configured." 135 | (interactive) 136 | (if (equal (selected-window) (get-buffer-window (org-now-buffer))) 137 | (quit-window) 138 | (select-window 139 | (or (get-buffer-window (org-now-buffer)) 140 | (display-buffer-in-side-window 141 | (org-now-buffer) 142 | (list (cons 'side org-now-window-side) 143 | (cons 'slot 0) 144 | (cons 'window-parameters (append `((no-delete-other-windows . t) 145 | (no-other-window . ,org-now-no-other-window)) 146 | org-now-window-parameters)))))))) 147 | 148 | ;;;###autoload 149 | (defun org-now-buffer () 150 | "Return the \"now\" buffer, creating it if necessary." 151 | (org-now--ensure-configured) 152 | (or (get-buffer "*org-now*") 153 | (save-window-excursion 154 | (org-with-point-at (org-now--marker) 155 | ;; MAYBE: Optionally hide the mode line. It looks nicer, but it also 156 | ;; hides whether the buffer has been modified, which can be important, 157 | ;; especially for users not using `real-auto-save'. 158 | (switch-to-buffer (clone-indirect-buffer "*org-now*" nil)) 159 | (when (> (length org-now-location) 1) 160 | (org-narrow-to-subtree)) 161 | (setq header-line-format (propertize " org-now" 'face 'org-now-header)) 162 | (toggle-truncate-lines 1) 163 | (rename-buffer "*org-now*") 164 | (run-hooks 'org-now-hook) 165 | (when org-now-default-cycle-level 166 | (org-global-cycle org-now-default-cycle-level)) 167 | (current-buffer))))) 168 | 169 | ;;;###autoload 170 | (defun org-now-link () 171 | "Add link to current Org entry to `org-now' entry. 172 | This command works in any buffer that `org-store-link' can store 173 | a link for, not just in Org buffers." 174 | (interactive) 175 | ;; The docstring for `org-store-link' doesn't mention a return value, but it 176 | ;; currently returns the stored link string, so we can use that. 177 | (let* ((link (org-store-link nil)) 178 | (entry (concat "* " link))) 179 | (with-current-buffer (org-now--link-buffer) 180 | (erase-buffer) ; Just in case 181 | (insert entry) 182 | (org-now-refile-to-now)))) 183 | 184 | ;;;###autoload 185 | (defun org-now-refile-to-now () 186 | "Refile current entry to the `org-now' entry." 187 | (interactive) 188 | (org-now--ensure-configured) 189 | ;; FIXME: When org-now-location is just a file path, entries seem to 190 | ;; get refiled as subheadings rather than top-level headings. 191 | (when-let* ((target-marker (org-now--marker)) 192 | (rfloc (list nil (car org-now-location) nil target-marker)) 193 | (previous-location (or (save-excursion 194 | (when (org-up-heading-safe) 195 | (org-id-get nil 'create))) 196 | (prin1-to-string (cons (buffer-file-name) 197 | (org-get-outline-path 'with-self))))) 198 | ;; Reverse note order so the heading will be refiled at the top of the node. When it's 199 | ;; refiled at the bottom, existing indirect buffers will not show it. 200 | (org-reverse-note-order t)) 201 | (org-set-property "refiled_from" previous-location) 202 | (org-refile nil nil rfloc) 203 | (unless (get-buffer-window (org-now-buffer) (selected-frame)) 204 | ;; If the buffer is not already open and visible, call `org-now', but only 205 | ;; after refiling the entry, so that if it's the only child of the "now" 206 | ;; heading, the new, indirect buffer will contain it. 207 | (org-now)) 208 | (when org-now-default-cycle-level 209 | ;; Re-cycle display levels in side buffer. 210 | (with-current-buffer (get-buffer "*org-now*") 211 | (org-global-cycle org-now-default-cycle-level))))) 212 | 213 | ;;;###autoload 214 | (defun org-now-refile-to-previous-location () 215 | "Refile current entry to its previous location. 216 | Requires the entry to have a \"refiled_from\" property whose 217 | value is a `read'able outline path list or an Org UUID. The 218 | property is removed after refiling." 219 | (interactive) 220 | (-if-let* ((payload-id (org-id-get nil 'create)) 221 | (refiled-from (org-entry-get (point) "refiled_from")) 222 | ((target-file . target-pos) (cond ((string-prefix-p "(" refiled-from) 223 | (--> (org-find-olp (read refiled-from)) 224 | (cons (car it) it))) 225 | ((org-id-find refiled-from))))) 226 | ;; Be extra careful and ensure we don't try to refile to an invalid location. 227 | (when (and target-file target-pos 228 | (org-refile nil nil (list nil target-file nil target-pos))) 229 | ;; Refile complete: remove refiled_from property 230 | (with-current-buffer (find-buffer-visiting target-file) 231 | ;; Copied from `org-find-property'. Unlike it, we don't go to 232 | ;; `point-min', because the entry will be after point. 233 | (save-excursion 234 | (let ((case-fold-search t)) 235 | (cl-loop while (re-search-forward (org-re-property "ID" nil nil payload-id) nil t) 236 | when (org-at-property-p) 237 | ;; TODO: Delete drawer if it's empty, using `org-remove-empty-drawer-at'. 238 | do (org-delete-property "refiled_from") 239 | and return t))))) 240 | (user-error "Heading has no previous location"))) 241 | 242 | ;;;###autoload 243 | (defalias 'org-now-agenda-link 244 | ;; This command works in agenda buffers without modification. For 245 | ;; clarity, we define an alias that matches the other commands 246 | ;; intended for use in the agenda. 247 | #'org-now-link) 248 | 249 | ;;;###autoload 250 | (defun org-now-agenda-refile-to-now () 251 | "Call `org-now-refile-to-now' from an Agenda buffer. 252 | Also usable in `org-agenda-bulk-custom-functions'." 253 | (interactive) 254 | (let* ((marker (get-text-property (point) 'org-hd-marker))) 255 | (cl-assert marker) 256 | (org-with-point-at marker 257 | (call-interactively #'org-now-refile-to-now)))) 258 | 259 | ;;;###autoload 260 | (defun org-now-agenda-refile-to-previous-location () 261 | "Call `org-now-refile-to-previous-location' from an Agenda buffer. 262 | Also usable in `org-agenda-bulk-custom-functions'." 263 | (interactive) 264 | (let* ((marker (get-text-property (point) 'org-hd-marker))) 265 | (cl-assert marker) 266 | (org-with-point-at marker 267 | (call-interactively #'org-now-refile-to-previous-location)))) 268 | 269 | ;;;;; Support 270 | 271 | (defun org-now--ensure-configured () 272 | "Ensure `org-now-location' is set. 273 | If not, open customization and raise an error." 274 | (unless org-now-location 275 | (customize-variable 'org-now-location) 276 | (user-error "Please configure `org-now-location'"))) 277 | 278 | (defun org-now--link-buffer () 279 | "Return buffer for refiling links from. 280 | Using one, hidden buffer for this avoids activating `org-mode' 281 | every time a link is refiled." 282 | (or (get-buffer " *org-now-link-buffer*") 283 | (with-current-buffer (get-buffer-create " *org-now-link-buffer*") 284 | (org-mode) 285 | (current-buffer)))) 286 | 287 | (defun org-now--marker () 288 | "Return marker pointing at `org-now' location." 289 | (pcase (length org-now-location) 290 | (1 (with-current-buffer (or (find-buffer-visiting (car org-now-location)) 291 | (find-file-noselect (car org-now-location))) 292 | (copy-marker (point-min)))) 293 | (_ (org-find-olp org-now-location)))) 294 | 295 | ;;;; Footer 296 | 297 | (provide 'org-now) 298 | 299 | ;;; org-now.el ends here 300 | --------------------------------------------------------------------------------