├── .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 |
--------------------------------------------------------------------------------