minder 0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.rspec +2 -0
- data/.ruby-version +1 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +75 -0
- data/LICENSE.txt +21 -0
- data/README.md +45 -0
- data/assets/done.wav +0 -0
- data/assets/start.wav +0 -0
- data/bin/minder +7 -0
- data/lib/minder/application.rb +128 -0
- data/lib/minder/break_period.rb +13 -0
- data/lib/minder/config.rb +35 -0
- data/lib/minder/frame.rb +172 -0
- data/lib/minder/idle_period.rb +25 -0
- data/lib/minder/message_frame.rb +124 -0
- data/lib/minder/period.rb +35 -0
- data/lib/minder/pomodoro_frame.rb +54 -0
- data/lib/minder/pomodoro_period.rb +13 -0
- data/lib/minder/pomodoro_runner.rb +69 -0
- data/lib/minder/quick_add_frame.rb +47 -0
- data/lib/minder/scene.rb +85 -0
- data/lib/minder/task.rb +28 -0
- data/lib/minder/task_recorder.rb +120 -0
- data/lib/minder/timer.rb +32 -0
- data/lib/minder/version.rb +3 -0
- data/lib/minder.rb +30 -0
- data/minder.gemspec +31 -0
- data/spec/minder/application_spec.rb +55 -0
- data/spec/minder/config_spec.rb +44 -0
- data/spec/minder/pomodoro_break_spec.rb +25 -0
- data/spec/minder/pomodoro_runner_spec.rb +3 -0
- data/spec/minder/pomodoro_spec.rb +25 -0
- data/spec/minder/timer_spec.rb +83 -0
- data/spec/spec_helper.rb +91 -0
- metadata +235 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: d1bd7f4b433d3f09c0b5c5c932d45f65b7e00dd6
|
4
|
+
data.tar.gz: e8c92eac92153eea880e7762c040fef0f43ffc27
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 44bbcf6c44358e936ad5d0896992a7ddb9d1815ab18bf4b5c077b346ab7651e19baba8ae5d1fbdb3c9391c97e8bc4f9d6fef108477fd29715c237fcc0d9860f3
|
7
|
+
data.tar.gz: efd8144a3790c3afe5d2848d8d392ec1ceb59bfedc6584e2b0b734e3f2215a8f775e988a2636ef2663d537f60a8490e000fae70786ff4a1bd19e4223ae160939
|
data/.rspec
ADDED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.2.2
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
minder (0.1)
|
5
|
+
curses
|
6
|
+
virtus
|
7
|
+
|
8
|
+
GEM
|
9
|
+
remote: https://rubygems.org/
|
10
|
+
specs:
|
11
|
+
axiom-types (0.1.1)
|
12
|
+
descendants_tracker (~> 0.0.4)
|
13
|
+
ice_nine (~> 0.11.0)
|
14
|
+
thread_safe (~> 0.3, >= 0.3.1)
|
15
|
+
binding_of_caller (0.7.2)
|
16
|
+
debug_inspector (>= 0.0.1)
|
17
|
+
byebug (4.0.5)
|
18
|
+
columnize (= 0.9.0)
|
19
|
+
coderay (1.1.0)
|
20
|
+
coercible (1.0.0)
|
21
|
+
descendants_tracker (~> 0.0.1)
|
22
|
+
columnize (0.9.0)
|
23
|
+
curses (1.0.1)
|
24
|
+
debug_inspector (0.0.2)
|
25
|
+
descendants_tracker (0.0.4)
|
26
|
+
thread_safe (~> 0.3, >= 0.3.1)
|
27
|
+
diff-lcs (1.2.5)
|
28
|
+
equalizer (0.0.11)
|
29
|
+
ice_nine (0.11.1)
|
30
|
+
method_source (0.8.2)
|
31
|
+
pry (0.10.1)
|
32
|
+
coderay (~> 1.1.0)
|
33
|
+
method_source (~> 0.8.1)
|
34
|
+
slop (~> 3.4)
|
35
|
+
pry-byebug (3.1.0)
|
36
|
+
byebug (~> 4.0)
|
37
|
+
pry (~> 0.10)
|
38
|
+
pry-stack_explorer (0.4.9.2)
|
39
|
+
binding_of_caller (>= 0.7)
|
40
|
+
pry (>= 0.9.11)
|
41
|
+
rake (10.4.2)
|
42
|
+
rspec (3.2.0)
|
43
|
+
rspec-core (~> 3.2.0)
|
44
|
+
rspec-expectations (~> 3.2.0)
|
45
|
+
rspec-mocks (~> 3.2.0)
|
46
|
+
rspec-core (3.2.2)
|
47
|
+
rspec-support (~> 3.2.0)
|
48
|
+
rspec-expectations (3.2.0)
|
49
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
50
|
+
rspec-support (~> 3.2.0)
|
51
|
+
rspec-mocks (3.2.1)
|
52
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
53
|
+
rspec-support (~> 3.2.0)
|
54
|
+
rspec-support (3.2.2)
|
55
|
+
slop (3.6.0)
|
56
|
+
thread_safe (0.3.5)
|
57
|
+
timecop (0.7.3)
|
58
|
+
virtus (1.0.5)
|
59
|
+
axiom-types (~> 0.1)
|
60
|
+
coercible (~> 1.0)
|
61
|
+
descendants_tracker (~> 0.0, >= 0.0.3)
|
62
|
+
equalizer (~> 0.0, >= 0.0.9)
|
63
|
+
|
64
|
+
PLATFORMS
|
65
|
+
ruby
|
66
|
+
|
67
|
+
DEPENDENCIES
|
68
|
+
bundler
|
69
|
+
minder!
|
70
|
+
pry
|
71
|
+
pry-byebug
|
72
|
+
pry-stack_explorer
|
73
|
+
rake
|
74
|
+
rspec
|
75
|
+
timecop
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 Joseph Method
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
## What is it?
|
2
|
+
|
3
|
+
This is a CLI-based tool that combines the best of several different
|
4
|
+
productivity methodologies.
|
5
|
+
|
6
|
+
At present it has these features:
|
7
|
+
|
8
|
+
- A timer to help with keeping to the [Pomodoro
|
9
|
+
Technique](http://pomodorotechnique.com/) rhythm of 4 25 minute work periods
|
10
|
+
with five minute breaks, followed by a long 15 minute break. You hear the
|
11
|
+
soothing sounds of a "pomodoro" kitchen timer at the beginning and end of a
|
12
|
+
break.
|
13
|
+
- A todo log that allows starting, unstarting and completing a task.
|
14
|
+
- Stores a simple text log of task and pomodoro activity.
|
15
|
+
|
16
|
+
Plans for the future include:
|
17
|
+
|
18
|
+
- Filtering by GTD-style labels (like @context, +project, and #tag)
|
19
|
+
- Integration with a website blacklist tool like
|
20
|
+
[SelfControl](https://github.com/SelfControlApp/selfcontrol/).
|
21
|
+
- Keeping track of the number of pomodoros performed during a day.
|
22
|
+
- Desktop notifications for Linux and MacOS
|
23
|
+
- Prompts to summarize plans for the day and day's end progress.
|
24
|
+
|
25
|
+
Audio files are from https://github.com/niftylettuce/pomodoro-timer
|
26
|
+
|
27
|
+
## Usage
|
28
|
+
|
29
|
+
There are three sections. You can switch between sections by pressing Tab. The
|
30
|
+
commands for each section only work when the section is focused.
|
31
|
+
|
32
|
+
- The Pomodoro timer section. Press space to begin the next period. Press 'e'
|
33
|
+
to edit the whole tasks list in your `$EDITOR`.
|
34
|
+
- The Tasks section. This section disappears during a Pomodoro period to avoid
|
35
|
+
distraction.
|
36
|
+
- Press 'd' to mark a task as done.
|
37
|
+
- Press 'x' to delete a task.
|
38
|
+
- Press 's' to start a task.
|
39
|
+
- Press 'u' to un-start a task.
|
40
|
+
- The Quick Add Task section. Enter text here to add a task to the tasks list
|
41
|
+
at any time.
|
42
|
+
|
43
|
+
## License
|
44
|
+
|
45
|
+
MIT License, see LICENSE.txt
|
data/assets/done.wav
ADDED
Binary file
|
data/assets/start.wav
ADDED
Binary file
|
data/bin/minder
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
require 'minder/config'
|
2
|
+
require 'minder/pomodoro_runner'
|
3
|
+
require 'minder/task_recorder'
|
4
|
+
require 'minder/scene'
|
5
|
+
|
6
|
+
require 'curses'
|
7
|
+
require 'fileutils'
|
8
|
+
|
9
|
+
module Minder
|
10
|
+
class Application
|
11
|
+
attr_accessor :config,
|
12
|
+
:scene,
|
13
|
+
:pomodoro_frame,
|
14
|
+
:message_frame,
|
15
|
+
:quick_add_frame
|
16
|
+
|
17
|
+
def initialize(config: Minder::Config.new(CONFIG_LOCATION))
|
18
|
+
self.config = config
|
19
|
+
config.load
|
20
|
+
FileUtils.mkdir_p(File.join(ENV['HOME'], '.minder'))
|
21
|
+
FileUtils.touch(DOING_FILE)
|
22
|
+
FileUtils.touch(DONE_FILE)
|
23
|
+
end
|
24
|
+
|
25
|
+
def config_location
|
26
|
+
config.location
|
27
|
+
end
|
28
|
+
|
29
|
+
def run
|
30
|
+
pomodoro_runner.add_observer(self, :handle_event)
|
31
|
+
|
32
|
+
self.scene = Scene.new
|
33
|
+
scene.setup
|
34
|
+
|
35
|
+
options = { pomodoro_runner: pomodoro_runner, task_manager: task_recorder }
|
36
|
+
|
37
|
+
self.pomodoro_frame = PomodoroFrame.new(options)
|
38
|
+
self.message_frame = MessageFrame.new(options)
|
39
|
+
self.quick_add_frame = QuickAddFrame.new(options)
|
40
|
+
quick_add_frame.focus
|
41
|
+
|
42
|
+
scene.frames << pomodoro_frame
|
43
|
+
scene.frames << message_frame
|
44
|
+
scene.frames << quick_add_frame
|
45
|
+
|
46
|
+
scene.frames.each do |frame|
|
47
|
+
frame.add_observer(self, :handle_event)
|
48
|
+
end
|
49
|
+
|
50
|
+
# TODO: Eww, gross
|
51
|
+
scene.redraw
|
52
|
+
scene.redraw
|
53
|
+
|
54
|
+
old_dimensions = [Curses.lines, Curses.cols]
|
55
|
+
loop do
|
56
|
+
scene.frames.each do |frame|
|
57
|
+
next unless frame.focused?
|
58
|
+
frame.listen
|
59
|
+
end
|
60
|
+
pomodoro_runner.tick
|
61
|
+
pomodoro_frame.refresh
|
62
|
+
scene.focused_frame.set_cursor_position
|
63
|
+
scene.focused_frame.window_refresh
|
64
|
+
|
65
|
+
new_dimensions = [Curses.lines, Curses.cols]
|
66
|
+
if new_dimensions != old_dimensions
|
67
|
+
scene.redraw
|
68
|
+
scene.redraw
|
69
|
+
old_dimensions = new_dimensions
|
70
|
+
end
|
71
|
+
|
72
|
+
sleep(0.01)
|
73
|
+
end
|
74
|
+
|
75
|
+
scene.close
|
76
|
+
end
|
77
|
+
|
78
|
+
def pomodoro_runner
|
79
|
+
@runner ||= PomodoroRunner.new(
|
80
|
+
work_duration: config.work_duration,
|
81
|
+
short_break_duration: config.short_break_duration,
|
82
|
+
long_break_duration: config.long_break_duration)
|
83
|
+
end
|
84
|
+
|
85
|
+
def task_recorder
|
86
|
+
@task_recorder ||= TaskRecorder.new
|
87
|
+
end
|
88
|
+
|
89
|
+
def handle_event(event, data = {})
|
90
|
+
return unless event
|
91
|
+
|
92
|
+
case event
|
93
|
+
when :started_work
|
94
|
+
message_frame.hide
|
95
|
+
when :completed_work
|
96
|
+
message_frame.unhide
|
97
|
+
when :continue
|
98
|
+
pomodoro_runner.continue
|
99
|
+
when :editor
|
100
|
+
`$EDITOR ~/.minder/doing.txt`
|
101
|
+
task_recorder.reload
|
102
|
+
when :add_task
|
103
|
+
task_recorder.add_task(data[:task])
|
104
|
+
when :switch_focus
|
105
|
+
scene.switch_focus
|
106
|
+
when :select_next_task
|
107
|
+
task_recorder.select_next_task
|
108
|
+
when :select_previous_task
|
109
|
+
task_recorder.select_previous_task
|
110
|
+
when :delete_task
|
111
|
+
task_recorder.delete_task
|
112
|
+
when :complete_task
|
113
|
+
task_recorder.complete_task
|
114
|
+
when :start_task
|
115
|
+
task_recorder.start_task
|
116
|
+
when :unstart_task
|
117
|
+
task_recorder.unstart_task
|
118
|
+
when :select_last_task
|
119
|
+
task_recorder.select_last_task
|
120
|
+
when :select_first_task
|
121
|
+
task_recorder.select_first_task
|
122
|
+
end
|
123
|
+
|
124
|
+
scene.redraw
|
125
|
+
scene.redraw
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module Minder
|
4
|
+
class Config
|
5
|
+
DEFAULTS = {
|
6
|
+
work_duration: 25,
|
7
|
+
short_break_duration: 5,
|
8
|
+
long_break_duration: 15
|
9
|
+
}
|
10
|
+
|
11
|
+
attr_accessor :location
|
12
|
+
attr_reader :data
|
13
|
+
|
14
|
+
def initialize(location = nil)
|
15
|
+
self.location = location
|
16
|
+
@data = {}
|
17
|
+
end
|
18
|
+
|
19
|
+
def load
|
20
|
+
if File.exists?(location.to_s)
|
21
|
+
file = File.open(location, 'r')
|
22
|
+
@data = JSON.parse(file.read, symbolize_names: true)
|
23
|
+
end
|
24
|
+
DEFAULTS.each do |key, value|
|
25
|
+
data[key] ||= DEFAULTS[key]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
DEFAULTS.keys.each do |key|
|
30
|
+
define_method(key) do
|
31
|
+
data[key]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/minder/frame.rb
ADDED
@@ -0,0 +1,172 @@
|
|
1
|
+
require 'observer'
|
2
|
+
require 'erb'
|
3
|
+
module Minder
|
4
|
+
class Frame
|
5
|
+
include Observable
|
6
|
+
|
7
|
+
attr_accessor :window,
|
8
|
+
:min_height,
|
9
|
+
:height,
|
10
|
+
:width,
|
11
|
+
:left,
|
12
|
+
:top,
|
13
|
+
:pomodoro_runner,
|
14
|
+
:task_manager,
|
15
|
+
:lines
|
16
|
+
|
17
|
+
def initialize(**options)
|
18
|
+
height = options.fetch(:height, 3)
|
19
|
+
width = options.fetch(:width, 40)
|
20
|
+
top = options.fetch(:top, 0)
|
21
|
+
left = options.fetch(:left, 0)
|
22
|
+
task_manager = options.fetch(:task_manager)
|
23
|
+
pomodoro_runner = options.fetch(:pomodoro_runner)
|
24
|
+
|
25
|
+
@focused = false
|
26
|
+
@hidden = false
|
27
|
+
@has_cursor = false
|
28
|
+
self.pomodoro_runner = pomodoro_runner
|
29
|
+
self.task_manager = task_manager
|
30
|
+
self.min_height = height
|
31
|
+
|
32
|
+
self.height = height
|
33
|
+
self.width = width
|
34
|
+
self.top = top
|
35
|
+
self.left = left
|
36
|
+
|
37
|
+
self.window = build_window
|
38
|
+
self.lines = []
|
39
|
+
end
|
40
|
+
|
41
|
+
def build_window
|
42
|
+
Curses::Window.new(min_height, width, top, left)
|
43
|
+
end
|
44
|
+
|
45
|
+
def focus
|
46
|
+
@focused = true
|
47
|
+
@has_cursor = true
|
48
|
+
end
|
49
|
+
|
50
|
+
def unfocus
|
51
|
+
@focused = false
|
52
|
+
@has_cursor = false
|
53
|
+
end
|
54
|
+
|
55
|
+
def focused?
|
56
|
+
@focused
|
57
|
+
end
|
58
|
+
|
59
|
+
def hidden?
|
60
|
+
@hidden
|
61
|
+
end
|
62
|
+
|
63
|
+
def hide
|
64
|
+
erase
|
65
|
+
@hidden = true
|
66
|
+
end
|
67
|
+
|
68
|
+
def unhide
|
69
|
+
@hidden = false
|
70
|
+
end
|
71
|
+
|
72
|
+
def template
|
73
|
+
raise NotImplementedError
|
74
|
+
end
|
75
|
+
|
76
|
+
def move(top, left)
|
77
|
+
window.move(top, left)
|
78
|
+
end
|
79
|
+
|
80
|
+
def has_cursor?
|
81
|
+
@has_cursor
|
82
|
+
end
|
83
|
+
|
84
|
+
def listen
|
85
|
+
window.timeout = 0
|
86
|
+
handle_keypress(window.getch)
|
87
|
+
end
|
88
|
+
|
89
|
+
def refresh
|
90
|
+
return if @hidden
|
91
|
+
parse_template
|
92
|
+
set_text
|
93
|
+
window_refresh
|
94
|
+
end
|
95
|
+
|
96
|
+
def window_refresh
|
97
|
+
window.refresh
|
98
|
+
end
|
99
|
+
|
100
|
+
def parse_template
|
101
|
+
b = binding
|
102
|
+
self.lines = ERB.new(template).result(b).split("\n")
|
103
|
+
end
|
104
|
+
|
105
|
+
def resize
|
106
|
+
erase
|
107
|
+
|
108
|
+
self.width = Curses.cols
|
109
|
+
if lines.length >= min_height - 2
|
110
|
+
self.height = lines.length + 2
|
111
|
+
else
|
112
|
+
self.height = min_height
|
113
|
+
end
|
114
|
+
|
115
|
+
window.resize(height, width)
|
116
|
+
window_refresh
|
117
|
+
end
|
118
|
+
|
119
|
+
def set_text
|
120
|
+
window.box(?|, ?-)
|
121
|
+
height.times do |index|
|
122
|
+
next if index >= height - 2
|
123
|
+
window.setpos(index + 1, 1)
|
124
|
+
line = lines[index]
|
125
|
+
if line
|
126
|
+
print_line(line)
|
127
|
+
else
|
128
|
+
print_line('')
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def erase
|
134
|
+
height.times do |index|
|
135
|
+
window.setpos(index, 0)
|
136
|
+
window.addstr(' ' * width )
|
137
|
+
end
|
138
|
+
window_refresh
|
139
|
+
end
|
140
|
+
|
141
|
+
def set_cursor_position
|
142
|
+
window.setpos(1, 0)
|
143
|
+
end
|
144
|
+
|
145
|
+
def print_line(text)
|
146
|
+
text = text[0,width - 2]
|
147
|
+
remainder = width - 2 - text.length
|
148
|
+
window.addstr(text + ' ' * remainder)
|
149
|
+
end
|
150
|
+
|
151
|
+
def handle_keypress(key)
|
152
|
+
return unless key
|
153
|
+
|
154
|
+
if key.is_a?(Fixnum)
|
155
|
+
if key == 9 # tab
|
156
|
+
changed
|
157
|
+
notify_observers(:switch_focus)
|
158
|
+
else
|
159
|
+
handle_non_char_keypress(key)
|
160
|
+
end
|
161
|
+
else
|
162
|
+
handle_char_keypress(key)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
def handle_non_char_keypress(key)
|
167
|
+
end
|
168
|
+
|
169
|
+
def handle_char_keypress(key)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'minder/period'
|
2
|
+
|
3
|
+
module Minder
|
4
|
+
class IdlePeriod < Period
|
5
|
+
def title
|
6
|
+
"Press space to start next period"
|
7
|
+
end
|
8
|
+
|
9
|
+
def message
|
10
|
+
nil
|
11
|
+
end
|
12
|
+
|
13
|
+
def start!
|
14
|
+
# noop
|
15
|
+
end
|
16
|
+
|
17
|
+
def elapsed?
|
18
|
+
true
|
19
|
+
end
|
20
|
+
|
21
|
+
def completed?
|
22
|
+
true
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
require 'minder/frame'
|
2
|
+
|
3
|
+
module Minder
|
4
|
+
class MessageFrame < Frame
|
5
|
+
attr_reader :current_line
|
6
|
+
|
7
|
+
def initialize(*)
|
8
|
+
super
|
9
|
+
self.height = desired_height
|
10
|
+
end
|
11
|
+
|
12
|
+
def template
|
13
|
+
if task_manager.tasks?
|
14
|
+
doing_message
|
15
|
+
else
|
16
|
+
prompt_message
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
#def build_window
|
21
|
+
#Curses::Pad.new(min_height, width)
|
22
|
+
#end
|
23
|
+
|
24
|
+
#def window_refresh
|
25
|
+
#window.refresh(scroll_offset, 0, 3, 0, height + 2, width)
|
26
|
+
#end
|
27
|
+
|
28
|
+
def prompt_message
|
29
|
+
<<-TEXT
|
30
|
+
What are you working on?
|
31
|
+
|
32
|
+
Press (e) to open editor.
|
33
|
+
TEXT
|
34
|
+
end
|
35
|
+
|
36
|
+
def header_text
|
37
|
+
<<-TEXT
|
38
|
+
Tasks (s) start (x) to delete (d) to mark as done
|
39
|
+
|
40
|
+
TEXT
|
41
|
+
end
|
42
|
+
|
43
|
+
def tasks_text
|
44
|
+
text = ''
|
45
|
+
task_manager.tasks.each do |task|
|
46
|
+
if task.started?
|
47
|
+
text += "-[*] #{task}\n"
|
48
|
+
else
|
49
|
+
text += "-[ ] #{task}\n"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
text
|
53
|
+
end
|
54
|
+
|
55
|
+
def tasks_text_lines
|
56
|
+
tasks_text.split("\n")
|
57
|
+
end
|
58
|
+
|
59
|
+
def header_text_lines
|
60
|
+
header_text.split("\n")
|
61
|
+
end
|
62
|
+
|
63
|
+
def desired_height
|
64
|
+
header_text_lines.length + tasks_text_lines.length + 3
|
65
|
+
end
|
66
|
+
|
67
|
+
def allocated_tasks_height
|
68
|
+
height - header_text_lines.length
|
69
|
+
end
|
70
|
+
|
71
|
+
def offset_tasks_text
|
72
|
+
tasks_text_lines[scroll_offset..(allocated_tasks_height + scroll_offset - 4)].join("\n")
|
73
|
+
end
|
74
|
+
|
75
|
+
def blank_line
|
76
|
+
" " * (width - 2) + "\n"
|
77
|
+
end
|
78
|
+
|
79
|
+
def doing_message
|
80
|
+
header_text +
|
81
|
+
offset_tasks_text #+
|
82
|
+
#blank_lines
|
83
|
+
end
|
84
|
+
|
85
|
+
def blank_lines
|
86
|
+
blank_line * scroll_offset
|
87
|
+
end
|
88
|
+
|
89
|
+
def set_cursor_position
|
90
|
+
window.setpos(3 + task_manager.selected_task_index - scroll_offset, 3)
|
91
|
+
end
|
92
|
+
|
93
|
+
def scroll_offset
|
94
|
+
position = task_manager.selected_task_index + 5
|
95
|
+
if height - position < 0
|
96
|
+
(height - position).abs
|
97
|
+
else
|
98
|
+
0
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def handle_char_keypress(key)
|
103
|
+
event = case key
|
104
|
+
when 'j' then :select_next_task
|
105
|
+
when 'k' then :select_previous_task
|
106
|
+
when 'd' then :complete_task
|
107
|
+
when 'x' then :delete_task
|
108
|
+
when 's' then :start_task
|
109
|
+
when 'u' then :unstart_task
|
110
|
+
when 'G' then :select_last_task
|
111
|
+
when 'g'
|
112
|
+
@keypress_memory ||= []
|
113
|
+
@keypress_memory << 'g'
|
114
|
+
if @keypress_memory == ['g', 'g']
|
115
|
+
@keypress_memory = []
|
116
|
+
:select_first_task
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
changed
|
121
|
+
notify_observers(event)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|