sup 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of sup might be problematic. Click here for more details.
- data/History.txt +5 -0
- data/LICENSE +280 -0
- data/Manifest.txt +52 -0
- data/README.txt +119 -0
- data/Rakefile +45 -0
- data/bin/sup +229 -0
- data/bin/sup-import +162 -0
- data/doc/FAQ.txt +38 -0
- data/doc/Philosophy.txt +59 -0
- data/doc/TODO +31 -0
- data/lib/sup.rb +141 -0
- data/lib/sup/account.rb +53 -0
- data/lib/sup/buffer.rb +391 -0
- data/lib/sup/colormap.rb +118 -0
- data/lib/sup/contact.rb +40 -0
- data/lib/sup/draft.rb +105 -0
- data/lib/sup/index.rb +353 -0
- data/lib/sup/keymap.rb +89 -0
- data/lib/sup/label.rb +41 -0
- data/lib/sup/logger.rb +42 -0
- data/lib/sup/mbox.rb +51 -0
- data/lib/sup/mbox/loader.rb +116 -0
- data/lib/sup/message.rb +302 -0
- data/lib/sup/mode.rb +79 -0
- data/lib/sup/modes/buffer-list-mode.rb +37 -0
- data/lib/sup/modes/compose-mode.rb +33 -0
- data/lib/sup/modes/contact-list-mode.rb +121 -0
- data/lib/sup/modes/edit-message-mode.rb +162 -0
- data/lib/sup/modes/forward-mode.rb +38 -0
- data/lib/sup/modes/help-mode.rb +19 -0
- data/lib/sup/modes/inbox-mode.rb +45 -0
- data/lib/sup/modes/label-list-mode.rb +89 -0
- data/lib/sup/modes/label-search-results-mode.rb +29 -0
- data/lib/sup/modes/line-cursor-mode.rb +133 -0
- data/lib/sup/modes/log-mode.rb +44 -0
- data/lib/sup/modes/person-search-results-mode.rb +29 -0
- data/lib/sup/modes/poll-mode.rb +24 -0
- data/lib/sup/modes/reply-mode.rb +136 -0
- data/lib/sup/modes/resume-mode.rb +18 -0
- data/lib/sup/modes/scroll-mode.rb +106 -0
- data/lib/sup/modes/search-results-mode.rb +31 -0
- data/lib/sup/modes/text-mode.rb +51 -0
- data/lib/sup/modes/thread-index-mode.rb +389 -0
- data/lib/sup/modes/thread-view-mode.rb +338 -0
- data/lib/sup/person.rb +120 -0
- data/lib/sup/poll.rb +80 -0
- data/lib/sup/sent.rb +46 -0
- data/lib/sup/tagger.rb +40 -0
- data/lib/sup/textfield.rb +83 -0
- data/lib/sup/thread.rb +358 -0
- data/lib/sup/update.rb +21 -0
- data/lib/sup/util.rb +260 -0
- metadata +123 -0
@@ -0,0 +1,19 @@
|
|
1
|
+
module Redwood
|
2
|
+
|
3
|
+
class HelpMode < TextMode
|
4
|
+
def initialize mode, global_keymap
|
5
|
+
title = "Help for #{mode.name}"
|
6
|
+
super <<EOS
|
7
|
+
#{title}
|
8
|
+
#{'=' * title.length}
|
9
|
+
|
10
|
+
#{mode.help_text}
|
11
|
+
Global keybindings
|
12
|
+
------------------
|
13
|
+
#{global_keymap.help_text}
|
14
|
+
EOS
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
module Redwood
|
4
|
+
|
5
|
+
class InboxMode < ThreadIndexMode
|
6
|
+
register_keymap do |k|
|
7
|
+
## overwrite toggle_archived with archive
|
8
|
+
k.add :archive, "Archive thread (remove from inbox)", 'a'
|
9
|
+
k.add :load_more_threads, "Load #{LOAD_MORE_THREAD_NUM} more threads", 'M'
|
10
|
+
k.add :reload, "Discard threads and reload", 'R'
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
super [:inbox], [:inbox]
|
15
|
+
end
|
16
|
+
|
17
|
+
def archive
|
18
|
+
remove_label_and_hide_thread cursor_thread, :inbox
|
19
|
+
regen_text
|
20
|
+
end
|
21
|
+
|
22
|
+
def multi_archive threads
|
23
|
+
threads.each { |t| remove_label_and_hide_thread t, :inbox }
|
24
|
+
regen_text
|
25
|
+
end
|
26
|
+
|
27
|
+
def is_relevant? m; m.has_label? :inbox; end
|
28
|
+
|
29
|
+
def load_more_threads n=ThreadIndexMode::LOAD_MORE_THREAD_NUM
|
30
|
+
load_n_threads_background n, :label => :inbox,
|
31
|
+
:load_killed => false,
|
32
|
+
:load_spam => false,
|
33
|
+
:when_done => lambda { |num|
|
34
|
+
BufferManager.flash "Added #{num} threads."
|
35
|
+
}
|
36
|
+
end
|
37
|
+
|
38
|
+
def reload
|
39
|
+
drop_all_threads
|
40
|
+
BufferManager.draw_screen
|
41
|
+
load_more_threads buffer.content_height
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module Redwood
|
2
|
+
|
3
|
+
class LabelListMode < LineCursorMode
|
4
|
+
register_keymap do |k|
|
5
|
+
k.add :view_results, "View messages with the selected label", :enter
|
6
|
+
k.add :reload, "Reload", "R"
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@labels = []
|
11
|
+
@text = []
|
12
|
+
super()
|
13
|
+
end
|
14
|
+
|
15
|
+
def lines; @text.length; end
|
16
|
+
def [] i; @text[i]; end
|
17
|
+
|
18
|
+
def load; regen_text; end
|
19
|
+
|
20
|
+
def load_in_background
|
21
|
+
::Thread.new do
|
22
|
+
regen_text do |i|
|
23
|
+
if i % 10 == 0
|
24
|
+
buffer.mark_dirty
|
25
|
+
BufferManager.draw_screen
|
26
|
+
sleep 0.1 # ok, dirty trick.
|
27
|
+
end
|
28
|
+
end
|
29
|
+
buffer.mark_dirty
|
30
|
+
BufferManager.draw_screen
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
protected
|
35
|
+
|
36
|
+
def reload
|
37
|
+
buffer.mark_dirty
|
38
|
+
BufferManager.draw_screen
|
39
|
+
load_in_background
|
40
|
+
end
|
41
|
+
|
42
|
+
def regen_text
|
43
|
+
@text = []
|
44
|
+
@labels = LabelManager::LISTABLE_LABELS.sort_by { |t| t.to_s } +
|
45
|
+
LabelManager.user_labels.sort_by { |t| t.to_s }
|
46
|
+
|
47
|
+
counts = @labels.map do |t|
|
48
|
+
total = Index.num_results_for :label => t
|
49
|
+
unread = Index.num_results_for :labels => [t, :unread]
|
50
|
+
[t, total, unread]
|
51
|
+
end
|
52
|
+
|
53
|
+
width = @labels.map { |t| t.to_s.length }.max
|
54
|
+
|
55
|
+
counts.map_with_index do |(t, total, unread), i|
|
56
|
+
if total == 0 && !LabelManager::LISTABLE_LABELS.include?(t)
|
57
|
+
Redwood::log "no hits for label #{t}, deleting"
|
58
|
+
LabelManager.delete t
|
59
|
+
@labels.delete t
|
60
|
+
next
|
61
|
+
end
|
62
|
+
|
63
|
+
label =
|
64
|
+
case t
|
65
|
+
when *LabelManager::LISTABLE_LABELS
|
66
|
+
t.to_s.ucfirst
|
67
|
+
else
|
68
|
+
t.to_s
|
69
|
+
end
|
70
|
+
@text << [[(unread == 0 ? :labellist_old_color : :labellist_new_color),
|
71
|
+
sprintf("%#{width + 1}s %5d %s, %5d unread", label, total, total == 1 ? " message" : "messages", unread)]]
|
72
|
+
yield i if block_given?
|
73
|
+
end.compact
|
74
|
+
end
|
75
|
+
|
76
|
+
def view_results
|
77
|
+
label = @labels[curpos]
|
78
|
+
if label == :inbox
|
79
|
+
BufferManager.raise_to_front BufferManager["inbox"]
|
80
|
+
else
|
81
|
+
b = BufferManager.spawn_unless_exists(label) do
|
82
|
+
mode = LabelSearchResultsMode.new [label]
|
83
|
+
end
|
84
|
+
b.mode.load_more_threads b.content_height
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Redwood
|
2
|
+
|
3
|
+
class LabelSearchResultsMode < ThreadIndexMode
|
4
|
+
register_keymap do |k|
|
5
|
+
k.add :load_more_threads, "Load #{LOAD_MORE_THREAD_NUM} more threads", 'M'
|
6
|
+
end
|
7
|
+
|
8
|
+
def initialize labels
|
9
|
+
@labels = labels
|
10
|
+
super
|
11
|
+
end
|
12
|
+
|
13
|
+
def is_relevant? m; @labels.all? { |l| m.has_label? l }; end
|
14
|
+
|
15
|
+
def load_more_threads n=ThreadIndexMode::LOAD_MORE_THREAD_NUM
|
16
|
+
load_n_threads_background n, :labels => @labels,
|
17
|
+
:load_killed => true,
|
18
|
+
:load_spam => false,
|
19
|
+
:when_done =>(lambda do |num|
|
20
|
+
if num > 0
|
21
|
+
BufferManager.flash "Found #{num} threads"
|
22
|
+
else
|
23
|
+
BufferManager.flash "No matches"
|
24
|
+
end
|
25
|
+
end)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
module Redwood
|
2
|
+
|
3
|
+
class LineCursorMode < ScrollMode
|
4
|
+
register_keymap do |k|
|
5
|
+
## overwrite scrollmode binding on arrow keys for cursor movement
|
6
|
+
## but j and k still scroll!
|
7
|
+
k.add :cursor_down, "Move cursor down one line", :down, 'j'
|
8
|
+
k.add :cursor_up, "Move cursor up one line", :up, 'k'
|
9
|
+
k.add :select, "Select this item", :enter
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_reader :curpos
|
13
|
+
|
14
|
+
def initialize cursor_top=0, opts={}
|
15
|
+
@cursor_top = cursor_top
|
16
|
+
@curpos = cursor_top
|
17
|
+
super opts
|
18
|
+
end
|
19
|
+
|
20
|
+
def draw
|
21
|
+
super
|
22
|
+
set_status
|
23
|
+
end
|
24
|
+
|
25
|
+
protected
|
26
|
+
|
27
|
+
def draw_line ln, opts={}
|
28
|
+
if ln == @curpos
|
29
|
+
super ln, :highlight => true, :debug => opts[:debug]
|
30
|
+
else
|
31
|
+
super
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def ensure_mode_validity
|
36
|
+
super
|
37
|
+
raise @curpos.inspect unless @curpos.is_a?(Integer)
|
38
|
+
c = @curpos.clamp topline, botline - 1
|
39
|
+
c = @cursor_top if c < @cursor_top
|
40
|
+
buffer.mark_dirty unless c == @curpos
|
41
|
+
@curpos = c
|
42
|
+
end
|
43
|
+
|
44
|
+
def set_cursor_pos p
|
45
|
+
return if @curpos == p
|
46
|
+
@curpos = p.clamp @cursor_top, lines
|
47
|
+
buffer.mark_dirty
|
48
|
+
end
|
49
|
+
|
50
|
+
def line_down # overwrite scrollmode
|
51
|
+
super
|
52
|
+
set_cursor_pos topline if @curpos < topline
|
53
|
+
end
|
54
|
+
|
55
|
+
def line_up # overwrite scrollmode
|
56
|
+
super
|
57
|
+
set_cursor_pos botline - 1 if @curpos > botline - 1
|
58
|
+
end
|
59
|
+
|
60
|
+
def cursor_down
|
61
|
+
return false unless @curpos < lines - 1
|
62
|
+
if @curpos >= botline - 1
|
63
|
+
page_down
|
64
|
+
set_cursor_pos [topline + 1, botline].min
|
65
|
+
else
|
66
|
+
@curpos += 1
|
67
|
+
unless buffer.dirty?
|
68
|
+
draw_line @curpos - 1
|
69
|
+
draw_line @curpos
|
70
|
+
set_status
|
71
|
+
buffer.commit
|
72
|
+
end
|
73
|
+
end
|
74
|
+
true
|
75
|
+
end
|
76
|
+
|
77
|
+
def cursor_up
|
78
|
+
return false unless @curpos > @cursor_top
|
79
|
+
if @curpos == topline
|
80
|
+
page_up
|
81
|
+
set_cursor_pos [botline - 2, topline].max
|
82
|
+
# raise "cursor position now #@curpos, topline #{topline} botline #{botline}"
|
83
|
+
else
|
84
|
+
@curpos -= 1
|
85
|
+
unless buffer.dirty?
|
86
|
+
draw_line @curpos + 1
|
87
|
+
draw_line @curpos
|
88
|
+
set_status
|
89
|
+
buffer.commit
|
90
|
+
end
|
91
|
+
end
|
92
|
+
true
|
93
|
+
end
|
94
|
+
|
95
|
+
def page_up # overwrite
|
96
|
+
if topline <= @cursor_top
|
97
|
+
set_cursor_pos @cursor_top
|
98
|
+
else
|
99
|
+
relpos = @curpos - topline
|
100
|
+
super
|
101
|
+
set_cursor_pos topline + relpos
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def page_down
|
106
|
+
if topline >= lines - buffer.content_height
|
107
|
+
set_cursor_pos(lines - 1)
|
108
|
+
else
|
109
|
+
relpos = @curpos - topline
|
110
|
+
super
|
111
|
+
set_cursor_pos [topline + relpos, lines - 1].min
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def jump_to_home
|
116
|
+
super
|
117
|
+
set_cursor_pos @cursor_top
|
118
|
+
end
|
119
|
+
|
120
|
+
def jump_to_end
|
121
|
+
super if topline < (lines - buffer.content_height)
|
122
|
+
set_cursor_pos(lines - 1)
|
123
|
+
end
|
124
|
+
|
125
|
+
private
|
126
|
+
|
127
|
+
def set_status
|
128
|
+
@status = "line #{@curpos + 1} of #{lines}"
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Redwood
|
2
|
+
|
3
|
+
class LogMode < TextMode
|
4
|
+
register_keymap do |k|
|
5
|
+
k.add :toggle_follow, "Toggle follow mode", 'f'
|
6
|
+
end
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@follow = true
|
10
|
+
super ""
|
11
|
+
end
|
12
|
+
|
13
|
+
def toggle_follow
|
14
|
+
@follow = !@follow
|
15
|
+
if buffer
|
16
|
+
if @follow
|
17
|
+
jump_to_line lines - buffer.content_height + 1 # leave an empty line at bottom
|
18
|
+
end
|
19
|
+
buffer.mark_dirty
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def text= t
|
24
|
+
super
|
25
|
+
if buffer && @follow
|
26
|
+
follow_top = lines - buffer.content_height + 1
|
27
|
+
jump_to_line follow_top if topline < follow_top
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def << line
|
32
|
+
super
|
33
|
+
if buffer && @follow
|
34
|
+
follow_top = lines - buffer.content_height + 1
|
35
|
+
jump_to_line follow_top if topline < follow_top
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def status
|
40
|
+
super + " (follow: #@follow)"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Redwood
|
2
|
+
|
3
|
+
class PersonSearchResultsMode < ThreadIndexMode
|
4
|
+
register_keymap do |k|
|
5
|
+
k.add :load_more_threads, "Load #{LOAD_MORE_THREAD_NUM} more threads", 'M'
|
6
|
+
end
|
7
|
+
|
8
|
+
def initialize people
|
9
|
+
@people = people
|
10
|
+
super
|
11
|
+
end
|
12
|
+
|
13
|
+
def is_relevant? m; @people.any? { |p| m.from == p }; end
|
14
|
+
|
15
|
+
def load_more_threads n=ThreadIndexMode::LOAD_MORE_THREAD_NUM
|
16
|
+
load_n_threads_background n, :participants => @people,
|
17
|
+
:load_killed => true,
|
18
|
+
:load_spam => false,
|
19
|
+
:when_done =>(lambda do |num|
|
20
|
+
if num > 0
|
21
|
+
BufferManager.flash "Found #{num} threads"
|
22
|
+
else
|
23
|
+
BufferManager.flash "No matches"
|
24
|
+
end
|
25
|
+
end)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Redwood
|
2
|
+
|
3
|
+
class PollMode < LogMode
|
4
|
+
def initialize
|
5
|
+
@new = true
|
6
|
+
super
|
7
|
+
end
|
8
|
+
|
9
|
+
def puts s=""
|
10
|
+
self << s + "\n"
|
11
|
+
# if lines % 5 == 0
|
12
|
+
BufferManager.draw_screen
|
13
|
+
# end
|
14
|
+
end
|
15
|
+
|
16
|
+
def poll
|
17
|
+
puts unless @new
|
18
|
+
@new = false
|
19
|
+
puts "poll started at #{Time.now}"
|
20
|
+
PollManager.poll { |s| puts s }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
module Redwood
|
2
|
+
|
3
|
+
class ReplyMode < EditMessageMode
|
4
|
+
REPLY_TYPES = [:sender, :list, :all, :user]
|
5
|
+
TYPE_DESCRIPTIONS = {
|
6
|
+
:sender => "Reply to sender",
|
7
|
+
:all => "Reply to all",
|
8
|
+
:list => "Reply to mailing list",
|
9
|
+
:user => "Customized reply"
|
10
|
+
}
|
11
|
+
|
12
|
+
register_keymap do |k|
|
13
|
+
k.add :move_cursor_right, "Move cursor to the right", :right
|
14
|
+
k.add :move_cursor_left, "Move cursor to the left", :left
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize message
|
18
|
+
super 2, :twiddles => false
|
19
|
+
@m = message
|
20
|
+
|
21
|
+
from =
|
22
|
+
if @m.recipient_email
|
23
|
+
AccountManager.account_for(@m.recipient_email)
|
24
|
+
else
|
25
|
+
(@m.to + @m.cc).argfind { |p| AccountManager.is_account? p }
|
26
|
+
end || AccountManager.default_account
|
27
|
+
|
28
|
+
from_email = @m.recipient_email || from.email
|
29
|
+
|
30
|
+
## ignore reply-to for list messages because it's typically set to
|
31
|
+
## the list address anyways
|
32
|
+
to = @m.is_list_message? ? @m.from : (@m.replyto || @m.from)
|
33
|
+
cc = (@m.to + @m.cc - [from, to]).uniq
|
34
|
+
|
35
|
+
@headers = {}
|
36
|
+
@headers[:sender] = {
|
37
|
+
"From" => "#{from.name} <#{from_email}>",
|
38
|
+
"To" => [to.full_address],
|
39
|
+
}
|
40
|
+
|
41
|
+
@headers[:user] = {
|
42
|
+
"From" => "#{from.name} <#{from_email}>",
|
43
|
+
"To" => "",
|
44
|
+
}
|
45
|
+
|
46
|
+
@headers[:all] = {
|
47
|
+
"From" => "#{from.name} <#{from_email}>",
|
48
|
+
"To" => [to.full_address],
|
49
|
+
"Cc" => cc.map { |p| p.full_address },
|
50
|
+
} unless cc.empty?
|
51
|
+
|
52
|
+
@headers[:list] = {
|
53
|
+
"From" => "#{from.name} <#{from_email}>",
|
54
|
+
"To" => [@m.list_address.full_address],
|
55
|
+
} if @m.is_list_message?
|
56
|
+
|
57
|
+
refs = gen_references
|
58
|
+
mid = gen_message_id
|
59
|
+
@headers.each do |k, v|
|
60
|
+
@headers[k] = v.merge({
|
61
|
+
"In-Reply-To" => "<#{@m.id}>",
|
62
|
+
"Subject" => Message.reify_subj(@m.subj),
|
63
|
+
"Message-Id" => mid,
|
64
|
+
"References" => refs,
|
65
|
+
})
|
66
|
+
end
|
67
|
+
|
68
|
+
@type_labels = REPLY_TYPES.select { |t| @headers.member?(t) }
|
69
|
+
@selected_type = @m.is_list_message? ? :list : :sender
|
70
|
+
|
71
|
+
@body = reply_body_lines(message) + sig_lines
|
72
|
+
regen_text
|
73
|
+
end
|
74
|
+
|
75
|
+
def lines; @text.length + 2; end
|
76
|
+
def [] i
|
77
|
+
case i
|
78
|
+
when 0
|
79
|
+
lame = []
|
80
|
+
@type_labels.each do |t|
|
81
|
+
lame << [(t == @selected_type ? :none_highlight : :none),
|
82
|
+
"#{TYPE_DESCRIPTIONS[t]}"]
|
83
|
+
lame << [:none, " "]
|
84
|
+
end
|
85
|
+
lame + [[:none, ""]]
|
86
|
+
when 1
|
87
|
+
""
|
88
|
+
else
|
89
|
+
@text[i - 2]
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
protected
|
94
|
+
|
95
|
+
def body; @body; end
|
96
|
+
def header; @headers[@selected_type]; end
|
97
|
+
|
98
|
+
def reply_body_lines m
|
99
|
+
lines = ["Excerpts from #{@m.from.name}'s message of #{@m.date}:"] +
|
100
|
+
m.basic_body_lines.map { |l| "> #{l}" }
|
101
|
+
lines.pop while lines.last !~ /[:alpha:]/
|
102
|
+
lines
|
103
|
+
end
|
104
|
+
|
105
|
+
def handle_new_text new_header, new_body
|
106
|
+
@body = new_body
|
107
|
+
|
108
|
+
if new_header.size != header.size ||
|
109
|
+
header.any? { |k, v| new_header[k] != v }
|
110
|
+
@selected_type = :user
|
111
|
+
@headers[:user] = new_header
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def regen_text
|
116
|
+
@text = header_lines(@headers[@selected_type] - NON_EDITABLE_HEADERS) + [""] + @body
|
117
|
+
end
|
118
|
+
|
119
|
+
def gen_references
|
120
|
+
(@m.refs + [@m.id]).map { |x| "<#{x}>" }.join(" ")
|
121
|
+
end
|
122
|
+
|
123
|
+
def move_cursor_left
|
124
|
+
i = @type_labels.index @selected_type
|
125
|
+
@selected_type = @type_labels[(i - 1) % @type_labels.length]
|
126
|
+
update
|
127
|
+
end
|
128
|
+
|
129
|
+
def move_cursor_right
|
130
|
+
i = @type_labels.index @selected_type
|
131
|
+
@selected_type = @type_labels[(i + 1) % @type_labels.length]
|
132
|
+
update
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
end
|