├── .github └── workflows │ └── test.yml ├── .gitignore ├── Cask ├── Makefile ├── README.org ├── git-hooks └── pre-commit ├── org-onit.el └── tests └── org-onit-test.el /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Main workflow 2 | on: [push] 3 | 4 | jobs: 5 | build: 6 | runs-on: ubuntu-latest 7 | strategy: 8 | matrix: 9 | emacs_version: 10 | - '25.1' 11 | - '25.2' 12 | - '25.3' 13 | - '26.1' 14 | - '26.2' 15 | - '26.3' 16 | - 'snapshot' 17 | include: 18 | - emacs_version: 'snapshot' 19 | allow_failure: true 20 | steps: 21 | - uses: actions/checkout@v1 22 | - uses: actions/setup-python@v1.1.1 23 | - uses: purcell/setup-emacs@master 24 | with: 25 | version: ${{ matrix.emacs_version }} 26 | - uses: conao3/setup-cask@master 27 | 28 | - name: Run tests 29 | if: matrix.allow_failure != true 30 | run: 'make test' 31 | 32 | - name: Run tests (allow failure) 33 | if: matrix.allow_failure == true 34 | run: 'make test || true' 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## .gitignore 2 | 3 | *-autoloads.el 4 | *.elc 5 | /.cask 6 | -------------------------------------------------------------------------------- /Cask: -------------------------------------------------------------------------------- 1 | ;;; Cask 2 | 3 | (source org) 4 | (source gnu) 5 | (source melpa) 6 | 7 | (package-file "org-onit.el") 8 | 9 | (development 10 | (depends-on "org-plus-contrib") ; fetch latest `org' 11 | (depends-on "buttercup")) 12 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ## Makefile 2 | 3 | # Copyright (C) 2019 Naoya Yamashita 4 | 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | all: 19 | 20 | REPO_USER := takaxp 21 | PACKAGE_NAME := org-onit 22 | REPO_NAME := org-onit 23 | 24 | EMACS ?= emacs 25 | ELS := $(shell cask files) 26 | 27 | GIT_HOOKS := pre-commit 28 | 29 | ################################################## 30 | 31 | .PHONY: all git-hook help build test clean 32 | 33 | all: git-hook help 34 | 35 | git-hook: $(GIT_HOOKS:%=.git/hooks/%) 36 | 37 | .git/hooks/%: git-hooks/% 38 | cp -a $< $@ 39 | 40 | help: 41 | $(info ) 42 | $(info Commands) 43 | $(info ========) 44 | $(info - make # Install git-hook to your local .git folder) 45 | $(info - make build # Compile elisp files) 46 | $(info - make test # Conpile elisp files and test $(PACKAGE_NAME)) 47 | $(info ) 48 | $(info Cleaning) 49 | $(info ========) 50 | $(info - make clean # Clean compiled and fetched files) 51 | $(info ) 52 | $(info This Makefile required `cask`) 53 | $(info See https://github.com/$(REPO_USER)/$(REPO_NAME)#contribution) 54 | $(info ) 55 | 56 | ############################## 57 | 58 | %.elc: %.el .cask 59 | cask exec $(EMACS) -Q --batch -f batch-byte-compile $< 60 | 61 | .cask: Cask 62 | cask install 63 | touch $@ 64 | 65 | ############################## 66 | 67 | build: $(ELS:%.el=%.elc) 68 | 69 | test: build 70 | cask exec buttercup -L . 71 | 72 | clean: 73 | rm -rf $(ELS:%.el=%.elc) .cask 74 | -------------------------------------------------------------------------------- /README.org: -------------------------------------------------------------------------------- 1 | #+title: org-onit.el 2 | #+startup: showall 3 | 4 | [[https://github.com/takaxp/org-onit/actions][https://github.com/takaxp/org-onit/workflows/Main%20workflow/badge.svg]] 5 | 6 | * Introduction 7 | 8 | This package provides automated ~org-clock-in~ and ~org-clock-out~ by adding ~Doing~ tag to a heading of an org mode buffer. While the ~Doing~ tag appears in an org buffer, Emacs Org mode maintains the task clocking. 9 | 10 | You can say "I'm on it!". 11 | 12 | #+CAPTION: Workflow with org-onit-toggle-doing, org-clock-into, and org-onit-goto-anchor 13 | [[https://github.com/takaxp/contents/blob/master/org-onit/org-onit-toggle-doing.png]] 14 | 15 | * Install 16 | 17 | Please put ~org-onit.el~ into your =load-path=. 18 | 19 | If you use ~package.el~ or other package manager, no necessary to add =(require 'org-onit)= to your ~init.el~ and just call toggle commands =org-onit-toggle-doing= or =org-onit-toggle-auto= in an org buffer. 20 | 21 | ** Additional package 22 | 23 | It is recommended to install [[https://github.com/alphapapa/org-bookmark-heading][org-bookmark-heading]] because a normal jump by built-in ~bookmark.el~ is not sufficiently accurate for org buffers. [[https://github.com/alphapapa/org-bookmark-heading][org-bookmark-heading]] provides more precise jumping capability by adding ID property to each org mode heading. ~org-onit.el~ will use [[https://github.com/alphapapa/org-bookmark-heading][org-bookmark-heading]] if available but not mandatory. 24 | 25 | * Recommend settings 26 | ** Highly recommended 27 | 28 | #+begin_src emacs-lisp 29 | (with-eval-after-load "org-clock" 30 | (setq org-clock-out-remove-zero-time-clocks t)) 31 | #+end_src 32 | 33 | ** Recommended keybindings 34 | 35 | #+begin_src emacs-lisp 36 | (with-eval-after-load "org" 37 | (global-set-key (kbd "C-") 'org-clock-goto) 38 | (define-key org-mode-map (kbd "") 'org-onit-toggle-doing) 39 | (define-key org-mode-map (kbd "M-") 'org-onit-toggle-auto) 40 | (define-key org-mode-map (kbd "S-") 'org-onit-goto-anchor)) 41 | #+end_src 42 | 43 | ** Optional settings 44 | 45 | You can freely arrange =org-clock-frame-title-format=. If you install [[https://github.com/mallt/org-clock-today-mode][org-clock-toay.el]] in your system, then you can show a working time only for today. 46 | 47 | #+begin_src emacs-lisp 48 | (with-eval-after-load "org" 49 | (add-to-list 'org-tag-faces '("Doing" :foreground "#FF0000")) 50 | (add-hook 'org-cycle-hook #'org-onit-clock-in-when-unfold)) 51 | 52 | (with-eval-after-load "org-clock" 53 | (setq org-clock-clocked-in-display 'frame-title) ;; or 'both 54 | (setq org-clock-frame-title-format 55 | '((:eval (format "%s|%s| %s" 56 | (if org-onit--auto-clocking "Auto " "") 57 | (org-onit-get-sign) 58 | org-mode-line-string))))) 59 | #+end_src 60 | 61 | * How to use 62 | ** Manual approach 63 | 64 | If you apply the above recommended keybindings, then just type == in an org buffer. A tag of a heading having the cursor will be changed to =Doing= and automatically start to =org-clock-in=. You can go any buffers and do anything but you can go back to the =Doing= tagged heading by just typing =C-=. Typing == again, then =org-clock-out= is executed and =Doing= tag will disappear. When you type =S-=, you can go back to the original position you are jumped from. 65 | 66 | *** clock-in and clock-out triggers 67 | **** clock-in triggers 68 | 69 | =org-clock-in= will be called when: 70 | 71 | - calling =org-onit-toggle-doing= if the task is not ~DONE~ 72 | - calling =org-onit-toggle-doing= at any headings if =:wakeup= of =org-onit-basic-options= is =doing= or =both= 73 | - unfolding a heading if =:unfold= of =org-onit-basic-options= is non-nil 74 | 75 | **** clock-out triggers 76 | 77 | =org-clock-out= will be called when: 78 | 79 | - calling =org-onit-toggle-doing= 80 | - making the task ~DONE~ or removing todo state 81 | 82 | ** Automated approach 83 | 84 | Use =M-x org-onit-toggle-auto=. Toggling =org-clock-in= and =org-clock-out= will be done automatically no need to toggle =Doing= tag by yourself. 85 | 86 | *** clock-in and clock-out triggers 87 | 88 | **** clock-in triggers 89 | 90 | =org-clock-in= will be called when: 91 | 92 | - visiting a heading if the task is not ~DONE~ 93 | - visiting a heading if =:nostate= of =org-onit-basic-options= is =auto= or =both= and the task is not ~DONE~ 94 | - making the task not ~DONE~ 95 | 96 | **** clock-out triggers 97 | 98 | =org-clock-out= will be called when: 99 | 100 | - switching to other headings if the task has a todo state (e.g. ~TODO~) 101 | - switching to other headings if =:nostate= of =org-onit-basic-options== is =auto= or =both= and the task is not ~DONE~ 102 | - making the task ~DONE~ 103 | - calling =org-onit-toggle-doing= 104 | 105 | ** Options 106 | - org-onit-wakeup-done (~deprecated~, use =org-onit-basic-options=) 107 | - Allow switching to =org-clock-in= by =org-onit-toggle-doing= when the heading is DONE. 108 | - Default: nil 109 | - org-onit-include-no-state-heading (~deprecated~, use =org-onit-basic-options=) 110 | - Allow switching to =org-clock-in= in any headings except headings in TODO when =org-onit-toggle-auto= is used 111 | - Default: nil 112 | - org-onit-basic-options 113 | - This variable is buffer-local. Please use =setq-default= or =custom-set-variables= in your init.el. 114 | - =:wakeup= allows switching to =org-clock-in= when the heading is DONE. 115 | - =:nostate= allows switching to =org-clock-in= in any headings except headings in TODO 116 | - =:unfold= allows switching to =org-clock-in= when unfolding a heading 117 | - Default: (=:wakeup= nil =:nostate= nil =:unfold= nil) 118 | - =:wakeup= and =:nostate= can take =doing=, =auto=, =both=, and =nil= 119 | - If =doing= is specified, the option will be used in =org-onit-toggle-doing= 120 | - If =auto= is specified, the option will be used in =org-onit-toggle-auto= 121 | - If =both= is specified, the option will be used in =org-onit-toggle-doing= and =org-onit-toggle-auto= 122 | - =:unfold= can take =t= or =nil= 123 | - But =:wakeup= and =:nostate= are given priority over =:unfold= 124 | - org-onit-encure-clock-out-when-exit 125 | - Call =org-clock-out= when killing Emacs if =org-clock-persis= is not =history= or =nil= 126 | - Default: t 127 | - org-onit-keep-no-state 128 | - If non-nil, allow clocking in but keep the heading TODO state none 129 | - If nil and =:nostate= of =org-onit-basic-options= is specified as non-nil, then the subtree will be changed to TODO heading state and clock-in 130 | - Default: t 131 | 132 | ** Helpers 133 | 134 | - org-onit-update-options 135 | #+begin_src emacs-lisp 136 | org-onit-basic-options ;; (:wakeup nil :nostate nil :unfold nil) 137 | (org-onit-update-options '(:nostate doing :unfold t)) ;; update the local variable 138 | org-onit-basic-options ;; (:wakeup nil :nostate doing :unfold t) 139 | #+end_src 140 | 141 | ** Hooks 142 | 143 | - org-onit-switch-task-hook 144 | - org-onit-start-autoclock-hook 145 | - org-onit-stop-autoclock-hook 146 | - org-onit-after-jump-hook 147 | #+begin_src emacs-lisp 148 | (defun my-onit-reveal () 149 | (org-reveal) 150 | (org-cycle-hide-drawers 'all) 151 | (org-show-entry) 152 | (show-children) 153 | (org-show-siblings)) 154 | (add-hook 'org-onit-after-jump-hook #'my-onit-reveal) 155 | #+end_src 156 | 157 | * Contribution 158 | ** Require tools for testing 159 | - cask 160 | - install via brew 161 | #+begin_src shell 162 | brew install cask 163 | #+end_src 164 | 165 | - manual install 166 | #+begin_src shell 167 | cd ~/ 168 | hub clone cask/cask 169 | export PATH="$HOME/.cask/bin:$PATH" 170 | #+end_src 171 | 172 | ** Running test 173 | Below operation flow is recommended. 174 | #+begin_src shell 175 | make # Install git-hooks in local .git 176 | 177 | git branch [feature-branch] # Create branch named [feature-branch] 178 | git checkout [feature-branch] # Checkout branch named [feature-branch] 179 | 180 | # 181 | emacs org-onit.el # Edit something you want 182 | 183 | make test # Test org-onit 184 | git commit -am "brabra" # Commit (auto-run test before commit) 185 | # 186 | 187 | hub fork # Create fork at GitHub 188 | git push [user] [feature-branch] # Push feature-branch to your fork 189 | hub pull-request # Create pull-request 190 | #+end_src 191 | 192 | * ChangeLog 193 | - 1.0.7 (2019-09-30) 194 | - [new] =org-onit-update-options= is added to update =org-onit-basic-options= 195 | - [deprecated] =org-onit-toggle-options= will be =org-onit-basic-options= 196 | - 1.0.6 (2019-09-29) 197 | - [improved] Make =org-onit-toggle-options= buffer local 198 | - 1.0.5 (2019-09-26) 199 | - [new] =org-onit-clock-in-when-unfold= is now public function 200 | - 1.0.4 (2019-09-25) 201 | - [new] =org-onit-keep-no-state= is added 202 | - 1.0.3 (2019-09-24) 203 | - [improved] =org-onit-use-unfold-as-doing= is integrated to =org-onit-toggle-options= 204 | - [deprecated] =org-onit-use-unfold-as-doing= 205 | - 1.0.2 (2019-09-12) 206 | - [new] =org-onit-toggle-options= is introduced 207 | - [improved] =org-clock-in-switch-to-state= is reflected to =org-onit-todo-state= 208 | - [deprecated] =org-onit-wakeup-done= 209 | - [deprecated] =org-onit-include-no-state-heading= 210 | - 1.0.1 (2019-09-01) 211 | - [improved] Rename "todo status" to "todo state" 212 | - [breaking change] rename to =org-onit-include-no-state-heading= 213 | - [new] support to clock-out when removing todo state 214 | - 1.0.0 (2019-09-01) 215 | - initial release 216 | -------------------------------------------------------------------------------- /git-hooks/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | make test -j8 4 | -------------------------------------------------------------------------------- /org-onit.el: -------------------------------------------------------------------------------- 1 | ;;; org-onit.el --- Easy org-clock-in and org-clock-out -*- lexical-binding: t; -*- 2 | 3 | ;; Copyright (C) 2019-2020 Takaaki ISHIKAWA 4 | 5 | ;; Author: Takaaki ISHIKAWA 6 | ;; Keywords: convenience 7 | ;; Version: 1.0.9 8 | ;; Maintainer: Takaaki ISHIKAWA 9 | ;; URL: https://github.com/takaxp/org-onit 10 | ;; Package-Requires: ((emacs "26.1") (org "9.2.4")) 11 | ;; Twitter: @takaxp 12 | 13 | ;; This program is free software: you can redistribute it and/or modify 14 | ;; it under the terms of the GNU General Public License as published by 15 | ;; the Free Software Foundation; either version 3 of the License, or 16 | ;; (at your option) any later version. 17 | 18 | ;; This program is distributed in the hope that it will be useful, 19 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 | ;; GNU General Public License for more details. 22 | 23 | ;; You should have received a copy of the GNU General Public License 24 | ;; along with GNU Emacs; see the file COPYING. If not, write to the 25 | ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 26 | ;; Boston, MA 02110-1301, USA. 27 | 28 | ;;; Commentary: 29 | 30 | ;; This package provides mainly two capabilities: 31 | ;; 1. Toggle to switch `org-clock-in' and `org-clock-out'. 32 | ;; 2. Toggle to activate auto clocking for the current org heading. 33 | ;; 34 | ;; Install: 35 | ;; - Get org-onit.el from GitHub. 36 | ;; 37 | ;; Setup: 38 | ;; - After installing this package, you will be able to call interactively 39 | ;; `org-onit-toggle-doing' and `org-onit-toggle-auto'. 40 | ;; - Once these commands are called, minor mode `org-onit-mode' is 41 | ;; automatically activated for the buffer. 42 | ;; 43 | ;; Keybindings: 44 | ;; - No default keybindings are configured. 45 | ;; - Assigning following keybindings are recommended: 46 | ;; (global-set-key (kbd \"C-\") 'org-clock-goto) 47 | ;; (define-key org-mode-map (kbd \"\") 'org-onit-toggle-doing) 48 | ;; (define-key org-mode-map (kbd \"M-\") 'org-onit-toggle-auto) 49 | ;; (define-key org-mode-map (kbd \"S-\") 'org-onit-goto-anchor) 50 | ;; 51 | 52 | ;;; Change Log: 53 | 54 | ;;; Code: 55 | 56 | (require 'org-clock) 57 | (require 'bookmark) 58 | 59 | (defgroup org-onit nil 60 | "Commands to toggle `org-clock-in' and `org-clock-out'." 61 | :group 'convenience) 62 | 63 | (defcustom org-onit-todo-state (or 64 | (when (functionp org-clock-in-switch-to-state) 65 | (funcall org-clock-in-switch-to-state)) 66 | org-clock-in-switch-to-state 67 | "TODO") 68 | "The default todo state." 69 | :type 'string 70 | :group 'org-onit) 71 | 72 | (defcustom org-onit-doing-tag "Doing" 73 | "Tag name to show the current task now clocking." 74 | :type 'string 75 | :group 'org-onit) 76 | 77 | (defcustom org-onit-basic-options '(:wakeup nil :nostate nil :unfold nil) 78 | "Combined options for `org-onit-toggle-doing' and `org-onit-toggle-auto'. 79 | 80 | This variable will be buffer local. 81 | 82 | Following two options can take {doing, auto, both, nil}: 83 | :wakeup If non-nil, start clocking even if the task is marked DONE. 84 | :nostate If non-nil, clock the task even if it doesn't have todo state. 85 | 86 | Following option can take {t, nil}: 87 | :unfold If non-nil, try to clock-in when unfolding a subturee. 88 | 89 | Note - :wakeup and :nonstate options are given priority over :unfold." 90 | :type 'plist 91 | :group 'org-onit) 92 | ;;;###autoload (put 'org-onit-basic-options 'safe-local-variable 'stringp) 93 | (define-obsolete-variable-alias 'org-onit-toggle-options 'org-onit-basic-options "1.2.0") 94 | 95 | (defcustom org-onit-keep-no-state t 96 | "If non-nil, do not change TODO state even when :nostate of `org-onit-basic-options' is non-nil." 97 | :type 'boolean 98 | :group 'org-onit) 99 | 100 | (defcustom org-onit-encure-clock-out-when-exit t 101 | "If non-nil, `org-clock-out' will be called when killing Emacs." 102 | :type 'boolean 103 | :group 'org-onit) 104 | 105 | (defcustom org-onit-switch-task-hook nil 106 | "Hook runs after activating new clock-in when auto clocking." 107 | :type 'hook 108 | :group 'org-onit) 109 | 110 | (defcustom org-onit-start-autoclock-hook nil 111 | "Hook runs after starting auto clock-in." 112 | :type 'hook 113 | :group 'org-onit) 114 | 115 | (defcustom org-onit-stop-autoclock-hook nil 116 | "Hook runs after stopping auto clock-in." 117 | :type 'hook 118 | :group 'org-onit) 119 | 120 | (defcustom org-onit-after-jump-hook nil 121 | "Hook runs after jumping to a bookmark." 122 | :type 'hook 123 | :group 'org-onit) 124 | 125 | (defcustom org-onit-clocking-sign-alist 126 | '("▁" "▂" "▃" "▄" "▅" "▆" "▇" "▇" "▇" "▆" "▅" "▄" "▃" "▂" "▁" "▁" "▁") 127 | "List of signs to show now clocking in a heading." 128 | :type 'list 129 | :group 'org-onit) 130 | 131 | (defcustom org-onit-bookmark "org-onit-last-clock-in" 132 | "Bookmark for the heading last clock-in." 133 | :type 'string 134 | :group 'org-onit) 135 | 136 | (defcustom org-onit-bookmark-anchor "org-onit-anchor" 137 | "Bookmark to store an anchor position." 138 | :type 'string 139 | :group 'org-onit) 140 | 141 | (defcustom org-onit-wakeup-done nil 142 | "[deprecated] If non-nil, start clocking even if the task is marked done. 143 | This flag is not utilized for `org-onit-toggle-auto'." 144 | :type 'boolean 145 | :group 'org-onit) 146 | (make-obsolete-variable 'org-onit-wakeup-done 'org-onit-basic-options "1.2.0") 147 | 148 | (defcustom org-onit-include-no-state-heading nil 149 | "[deprecated] If non-nil, clock the task even if it doesn't have todo state. 150 | This flag is utilized for `org-onit-toggle-auto'." 151 | :type 'boolean 152 | :group 'org-onit) 153 | (make-obsolete-variable 'org-onit-include-no-state-heading 'org-onit-basic-options "1.2.0") 154 | 155 | (defcustom org-onit-use-unfold-as-doing nil 156 | "[deprecated] If non-nil, clock-in when a heading is changed to unfold and not clocking." 157 | :type 'boolean 158 | :group 'org-onit) 159 | (make-obsolete-variable 'org-onit-use-unfold-as-doing 'org-onit-basic-options "1.2.0") 160 | 161 | ;;;###autoload 162 | (define-minor-mode org-onit-mode 163 | " 164 | This minor mode expands `org-clock-in', `org-clock-out' and `org-clock-goto' 165 | to support \"Doing\" functionality. When you toggle a heading, a clock for 166 | the heading is automatically activated and the heading is tagged with 167 | `org-doing-tag'. Toggling the same heading, the clock is stopped, and the tag 168 | is removed. Basically, a single heading tagged with `org-doing-tag' will 169 | appear in org buffers. 170 | 171 | The tagged heading can be easily revisited by calling the extended 172 | `org-clock-goto' command whether you are editing another heading in any 173 | org buffers. 174 | 175 | An automated `org-clock-in' capability is also provided by this package. 176 | A heading that you are currently visiting in an org buffer will be 177 | automatically clocked with executing `org-clock-in'. After you switch to 178 | other headings, the active clock will be automatically updated without any 179 | additional actions. 180 | 181 | Recommended settings for `org-clock': 182 | (setq org-clock-out-remove-zero-time-clocks t) ;; you should apply this. 183 | (setq org-clock-clocked-in-display 'frame-title) ;; or 'both 184 | (setq org-clock-frame-title-format 185 | '((:eval (format \"%s\" org-mode-line-string)))) 186 | 187 | Recommended keybindings: 188 | (global-set-key (kbd \"C-\") 'org-clock-goto) 189 | (define-key org-mode-map (kbd \"\") 'org-onit-toggle-doing) 190 | (define-key org-mode-map (kbd \"M-\") 'org-onit-toggle-auto) 191 | (define-key org-mode-map (kbd \"S-\") 'org-onit-goto-anchor) 192 | " 193 | :init-value nil 194 | :lighter (:eval (org-onit--lighter)) 195 | :require 'org-clock 196 | :group 'org-onit 197 | (if org-onit-mode 198 | (org-onit--setup) 199 | (org-onit--abort))) 200 | 201 | 202 | ;; internal functions 203 | 204 | (defun org-onit--rotate-list (list) 205 | "Rotate the provided LIST." 206 | (if (listp list) 207 | (append (cdr list) 208 | (list (car list))) 209 | list)) 210 | 211 | (defvar org-onit--auto-clocking nil) 212 | (defvar org-onit--heading nil) 213 | (defvar org-onit--state nil) 214 | (defvar org-onit--org-bookmark-heading-p (require 'org-bookmark-heading nil t)) 215 | (defvar org-onit--clock-in-last-pos nil) 216 | (defvar org-onit--anchor-last-pos nil) 217 | (defvar org-onit--frame-title-format nil) 218 | (defvar org-onit--lighter " Doing") 219 | 220 | (defun org-onit--lighter () 221 | "Lighter." 222 | org-onit--lighter) 223 | 224 | (defun org-onit--switched-p () 225 | "Return t if the current heading was changed." 226 | (if (org-before-first-heading-p) 227 | (progn 228 | (org-onit--clock-out) 229 | (setq org-onit--heading nil) 230 | (setq org-onit--state nil)) 231 | (save-excursion 232 | (save-restriction 233 | (org-back-to-heading t) 234 | (let* ((element (cadr (org-element-at-point))) 235 | (heading (plist-get element :title)) 236 | (todo (plist-get element :todo-keyword)) 237 | (switched nil)) 238 | (unless (equal org-onit--state todo) 239 | (when (or (not org-onit--state) ;; nil -> something 240 | (not ;; todo1 -> done or nil, except todo1 -> todo2 241 | (and (not (member org-onit--state org-done-keywords)) 242 | (not (member todo org-done-keywords))))) 243 | (setq switched t)) 244 | (setq org-onit--state todo)) 245 | (unless (equal org-onit--heading heading) 246 | (setq switched t) 247 | (setq org-onit--heading heading)) 248 | switched))))) 249 | 250 | (defun org-onit--auto-target-p () 251 | "Return non-nil if the heading is valid task for clock-in." 252 | (cond 253 | ((org-entry-is-done-p) 254 | (memq (plist-get org-onit-basic-options :wakeup) '(auto both))) 255 | ((not (org-get-todo-state)) 256 | (memq (plist-get org-onit-basic-options :nostate) '(auto both))) 257 | ((org-entry-is-todo-p) t) 258 | (t nil))) 259 | 260 | (defun org-onit--tagged-p () 261 | "Return t if the current heading tagged with `org-onit-doing-tag'." 262 | (member org-onit-doing-tag (org-get-tags (point) t))) 263 | 264 | (defun org-onit--bookmark-set () 265 | "Save the bookmark for the current heading." 266 | (save-excursion 267 | (save-restriction 268 | (org-back-to-heading t) 269 | (setq org-onit--clock-in-last-pos (point)) 270 | (when (bookmark-get-bookmark org-onit-bookmark 'noerror) 271 | (bookmark-delete org-onit-bookmark)) 272 | (bookmark-set org-onit-bookmark) 273 | (when (bookmark-get-bookmark org-onit-bookmark-anchor 'noerror) 274 | (setq org-onit--anchor-last-pos nil) 275 | (bookmark-delete org-onit-bookmark-anchor))))) 276 | 277 | (defun org-onit--remove-tag () 278 | "Remove `org-onit-doing-tag' tag from the current heading." 279 | (when (org-onit--tagged-p) 280 | (org-toggle-tag org-onit-doing-tag 'off))) 281 | 282 | (defun org-onit--remove-tag-not-todo () 283 | "Remove `org-onit-doing-tag' tag if the heading is done or no state." 284 | (when (or (org-entry-is-done-p) 285 | (not (org-get-todo-state))) 286 | (org-onit--clock-out))) 287 | 288 | (defun org-onit--post-action (&optional switched) 289 | "A combined action of clock-out and clock-in. 290 | If SWITCHED is non-nil, then do not check `org-onit--switched-p'." 291 | (when (and org-onit-mode 292 | (or switched 293 | (org-onit--switched-p))) 294 | (org-onit--clock-out) 295 | (when (org-onit--auto-target-p) 296 | (org-onit--clock-in) 297 | (run-hooks 'org-onit-switch-task-hook)) 298 | (org-cycle-hide-drawers 'children) 299 | (org-reveal))) 300 | 301 | (defun org-onit--bookmark-jump (bookmark) 302 | "Jump to BOOKMARK." 303 | (bookmark-jump bookmark) 304 | (when (eq major-mode 'org-mode) 305 | (unless org-onit--org-bookmark-heading-p 306 | (org-back-to-heading t)) 307 | (run-hooks 'org-onit-after-jump-hook))) 308 | 309 | (defun org-onit--clock-goto (f &optional select) 310 | "Go to the current clocking task. 311 | Even after restart of Emacs, try to restore the current task from a bookmark. 312 | F is the original `org-clock-goto'. 313 | SELECT is the optional argument of `org-clock-goto'." 314 | (let ((bm (bookmark-get-bookmark org-onit-bookmark 'noerror))) 315 | (if (eq (point) org-onit--clock-in-last-pos) 316 | (message "Already at the last clocked in.") 317 | (when (bookmark-get-bookmark org-onit-bookmark-anchor 'noerror) 318 | (bookmark-delete org-onit-bookmark-anchor)) 319 | (ignore-errors (bookmark-set org-onit-bookmark-anchor)) 320 | (if (bookmark-get-bookmark org-onit-bookmark-anchor 'noerror) 321 | (message "Anchor bookmark was recorded in a file.") 322 | (message "Anchor bookmark was not recorded for the buffer.")) 323 | (when (and (eq major-mode 'org-mode) 324 | (not (org-before-first-heading-p))) 325 | (org-back-to-heading t)) 326 | (setq org-onit--anchor-last-pos (point))) 327 | (cond 328 | ((and org-onit--org-bookmark-heading-p ;; most reliable 329 | bm) 330 | (org-onit--bookmark-jump org-onit-bookmark)) ;; call org-bookmark-jump 331 | (org-clock-history 332 | (apply f select) 333 | (org-show-children)) 334 | (bm 335 | (org-onit--bookmark-jump org-onit-bookmark)) ;; use normal bookmark 336 | (t (message "No clock is found to be shown"))))) 337 | 338 | (defun org-onit-clock-out-when-kill-emacs () 339 | "Save buffers and stop clocking when killing Emacs." 340 | (bookmark-delete org-onit-bookmark-anchor) 341 | (when (and org-onit-encure-clock-out-when-exit 342 | (org-clocking-p) 343 | (memq org-clock-persist '(history nil))) 344 | (org-onit--clock-out) 345 | (save-some-buffers t))) 346 | 347 | (defun org-onit--clock-in () 348 | "Clock-in and adding `org-onit-doing-tag' tag." 349 | (when (or (org-entry-is-done-p) 350 | (not org-onit-keep-no-state)) 351 | (org-todo org-onit-todo-state)) 352 | (org-clock-in) 353 | (org-toggle-tag org-onit-doing-tag 'on)) 354 | 355 | (defun org-onit--clock-out () 356 | "Clock-out and remove `org-onit-doing-tag' tag." 357 | (if (org-clocking-p) 358 | (org-clock-out) 359 | (org-onit--remove-tag))) 360 | 361 | (defun org-onit--backup-title-format () 362 | "Backup `the-title-format'." 363 | (setq org-onit--frame-title-format frame-title-format)) 364 | 365 | (defun org-onit--restore-title-format () 366 | "Restore the original title format." 367 | (setq frame-title-format org-onit--frame-title-format)) 368 | 369 | (defun org-onit--setup () 370 | "Setup." 371 | ;; For bookmark.el 372 | (bookmark-maybe-load-default-file) 373 | 374 | ;; For buffer-local 375 | (make-local-variable 'org-onit-basic-options) 376 | 377 | ;; This section will be removed based on the availability of `org-onit-wakeup-done' and `org-onit-include-no-state-heading' 378 | (when (and (not (plist-get org-onit-basic-options :wakeup)) 379 | (not (plist-get org-onit-basic-options :nostate)) 380 | (not (plist-get org-onit-basic-options :unfold))) 381 | (plist-put org-onit-basic-options 382 | :wakeup (when org-onit-wakeup-done 'doing)) 383 | (plist-put org-onit-basic-options 384 | :nostate (when org-onit-include-no-state-heading 'auto)) 385 | (plist-put org-onit-basic-options 386 | :unfold org-onit-use-unfold-as-doing)) 387 | 388 | (org-onit--backup-title-format) 389 | (advice-add 'org-clock-goto :around #'org-onit--clock-goto) 390 | (add-hook 'org-cycle-hook #'org-onit-clock-in-when-unfold) 391 | (add-hook 'org-after-todo-state-change-hook #'org-onit--remove-tag-not-todo) 392 | (add-hook 'kill-emacs-hook #'org-onit-clock-out-when-kill-emacs) 393 | (add-hook 'org-clock-in-hook #'org-onit--bookmark-set) 394 | (add-hook 'org-clock-out-hook #'org-onit--remove-tag) 395 | (add-hook 'org-clock-out-hook #'org-onit--restore-title-format)) 396 | 397 | (defun org-onit--abort () 398 | "Cleanup." 399 | (when (and (org-clocking-p) 400 | (memq org-clock-persist '(history nil))) 401 | (org-onit--clock-out)) 402 | (org-onit--restore-title-format) 403 | (bookmark-delete org-onit-bookmark-anchor) 404 | (setq org-onit--clock-in-last-pos nil) 405 | (setq org-onit--anchor-last-pos nil) 406 | (advice-remove 'org-clock-goto #'org-onit--clock-goto) 407 | (remove-hook 'org-cycle-hook #'org-onit-clock-in-when-unfold) 408 | (remove-hook 'org-after-todo-state-change-hook 409 | #'org-onit--remove-tag-not-todo) 410 | (remove-hook 'kill-emacs-hook #'org-onit-clock-out-when-kill-emacs) 411 | (remove-hook 'org-clock-in-hook #'org-onit--bookmark-set) 412 | (remove-hook 'org-clock-out-hook #'org-onit--remove-tag) 413 | (remove-hook 'org-clock-out-hook #'org-onit--restore-title-format)) 414 | 415 | ;; public functions 416 | 417 | (defun org-onit-goto-anchor () 418 | "Go to the anchor position if recorded." 419 | (interactive) 420 | (if (bookmark-get-bookmark org-onit-bookmark-anchor 'noerror) 421 | (progn 422 | (if (eq (point) org-onit--anchor-last-pos) 423 | (message "Already at the anchor.") 424 | (message "Jumped to the anchor.")) 425 | (org-onit--bookmark-jump org-onit-bookmark-anchor)) 426 | (message "No anchor is recorded."))) 427 | 428 | (defun org-onit-get-sign () 429 | "Return the first item of `org-onit-clocking-sign-alist'." 430 | (car (setq org-onit-clocking-sign-alist 431 | (org-onit--rotate-list 432 | org-onit-clocking-sign-alist)))) 433 | 434 | ;;;###autoload 435 | (defun org-onit-update-options (options) 436 | "Update `org-onit-basic-options' with OPTIONS. 437 | This function will update `org-onit-basic-options' with provaided properties. 438 | Unprovided property will not change the original value." 439 | (unless org-onit-mode 440 | (org-onit-mode 1)) 441 | (setq org-onit-basic-options 442 | (read 443 | (concat 444 | "(:wakeup " 445 | (format "%S" 446 | (if (plist-member options :wakeup) 447 | (plist-get options :wakeup) 448 | (plist-get org-onit-basic-options :wakeup))) 449 | " :nostate " 450 | (format "%S" 451 | (if (plist-member options :nostate) 452 | (plist-get options :nostate) 453 | (plist-get org-onit-basic-options :nostate))) 454 | " :unfold " 455 | (format "%S)" 456 | (if (plist-member options :unfold) 457 | (plist-get options :unfold) 458 | (plist-get org-onit-basic-options :unfold))))))) 459 | 460 | ;;;###autoload 461 | (defun org-onit-toggle-auto () 462 | "Toggle auto clocking." 463 | (interactive) 464 | (when (eq major-mode 'org-mode) 465 | (unless org-onit-mode 466 | (org-onit-mode 1)) 467 | (setq org-onit--auto-clocking (not org-onit--auto-clocking)) 468 | (cond (org-onit--auto-clocking 469 | (setq org-onit--lighter " Doing:auto") 470 | (add-hook 'post-command-hook #'org-onit--post-action) 471 | (unless (org-before-first-heading-p) 472 | (org-onit--post-action t)) 473 | (run-hooks 'org-onit-start-autoclock-hook)) 474 | (t 475 | (setq org-onit--lighter " Doing") 476 | (remove-hook 'post-command-hook #'org-onit--post-action) 477 | (org-onit--clock-out) 478 | (run-hooks 'org-onit-stop-autoclock-hook))) 479 | (force-mode-line-update))) 480 | 481 | ;;;###autoload 482 | (defun org-onit-toggle-doing () 483 | "Toggle `org-onit-doing-tag' tag. 484 | This command also switches `org-clock-in' and `org-clock-out'." 485 | (interactive) 486 | (when (eq major-mode 'org-mode) 487 | (unless org-onit-mode 488 | (org-onit-mode 1)) 489 | (save-excursion 490 | (save-restriction 491 | (org-back-to-heading t) 492 | (cond 493 | ((org-onit--tagged-p) 494 | (org-onit--clock-out)) 495 | ((org-entry-is-done-p) 496 | (if (memq (plist-get org-onit-basic-options :wakeup) '(doing both)) 497 | (org-onit--clock-in) 498 | (message "Prevent `org-clock-in'. And not switching to TODO."))) 499 | ((not (org-get-todo-state)) 500 | (if (memq (plist-get org-onit-basic-options :nostate) '(doing both)) 501 | (org-onit--clock-in) 502 | (message "Prevent `org-clock-in'. Heading has no todo state."))) 503 | ((org-entry-is-todo-p) 504 | (org-onit--clock-in))))) 505 | (org-cycle-hide-drawers 'children) 506 | (org-reveal))) 507 | 508 | ;;;###autoload 509 | (defun org-onit-clock-in-when-unfold (state) 510 | "Clock-in when a heading is switched to unfold and not clocking. 511 | STATE should be one of the symbols listed in the docstring of 512 | `org-cycle-hook'." 513 | (when (and (not (org-clocking-p)) 514 | (memq state '(children subtree)) 515 | (plist-get org-onit-basic-options :unfold) 516 | (or (and (org-entry-is-done-p) 517 | (memq (plist-get org-onit-basic-options :wakeup) 518 | '(doing both))) 519 | (and (not (org-get-todo-state)) 520 | (memq (plist-get org-onit-basic-options :nostate) 521 | '(doing both))) 522 | (org-entry-is-todo-p))) 523 | (unless org-onit-mode 524 | (org-onit-mode 1)) 525 | (org-onit--clock-in) 526 | (org-cycle-hide-drawers 'children) 527 | (org-reveal))) 528 | 529 | (provide 'org-onit) 530 | 531 | ;;; org-onit.el ends here 532 | -------------------------------------------------------------------------------- /tests/org-onit-test.el: -------------------------------------------------------------------------------- 1 | ;;; org-onit-test.el --- Test definitions for org-onit -*- lexical-binding: t; -*- 2 | 3 | ;; Copyright (C) 2019-2020 Takaaki ISHIKAWA 4 | 5 | ;; Author: Naoya Yamashita 6 | ;; URL: https://github.com/takaxp/org-onit 7 | 8 | ;; This program is free software: you can redistribute it and/or modify 9 | ;; it under the terms of the GNU General Public License as published by 10 | ;; the Free Software Foundation; either version 3 of the License, or 11 | ;; (at your option) any later version. 12 | 13 | ;; This program is distributed in the hope that it will be useful, 14 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | ;; GNU General Public License for more details. 17 | 18 | ;; You should have received a copy of the GNU General Public License 19 | ;; along with GNU Emacs; see the file COPYING. If not, write to the 20 | ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 21 | ;; Boston, MA 02110-1301, USA. 22 | 23 | ;;; Commentary: 24 | 25 | ;; Test definitions for `org-onit'. 26 | 27 | 28 | ;;; Code: 29 | 30 | (require 'buttercup) 31 | (require 'org-onit) 32 | 33 | (defconst org-onit-test-dir (file-name-directory 34 | (cond 35 | (load-in-progress load-file-name) 36 | ((and (boundp 'byte-compile-current-file) 37 | byte-compile-current-file) 38 | byte-compile-current-file) 39 | (:else (buffer-file-name))))) 40 | 41 | (describe "A suite" 42 | (it "contains a spec with an expectation" 43 | (expect t :to-be t))) 44 | 45 | ;; (provide 'org-onit-test) 46 | 47 | ;; Local Variables: 48 | ;; indent-tabs-mode: nil 49 | ;; End: 50 | 51 | ;;; org-onit-test.el ends here 52 | --------------------------------------------------------------------------------