twterm 1.3.0 → 2.0.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +14 -18
- data/bin/twterm +1 -1
- data/lib/twterm/app.rb +120 -30
- data/lib/twterm/client.rb +10 -13
- data/lib/twterm/completion_mamanger.rb +11 -6
- data/lib/twterm/direct_message.rb +6 -28
- data/lib/twterm/direct_message_composer.rb +10 -5
- data/lib/twterm/direct_message_manager.rb +5 -6
- data/lib/twterm/event/notification/abstract_notification.rb +27 -0
- data/lib/twterm/event/notification/error.rb +13 -0
- data/lib/twterm/event/notification/info.rb +13 -0
- data/lib/twterm/event/notification/success.rb +13 -0
- data/lib/twterm/event/notification/warning.rb +13 -0
- data/lib/twterm/event_dispatcher.rb +1 -1
- data/lib/twterm/extensions/array.rb +5 -0
- data/lib/twterm/extensions/enumerator/lazy.rb +3 -0
- data/lib/twterm/extensions/string.rb +0 -4
- data/lib/twterm/friendship.rb +1 -85
- data/lib/twterm/image/between.rb +31 -0
- data/lib/twterm/image/blank_line.rb +21 -0
- data/lib/twterm/image/bold.rb +31 -0
- data/lib/twterm/image/brackets.rb +21 -0
- data/lib/twterm/image/color.rb +45 -0
- data/lib/twterm/image/empty.rb +21 -0
- data/lib/twterm/image/horizontal_sequential_image.rb +48 -0
- data/lib/twterm/image/parens.rb +21 -0
- data/lib/twterm/image/string_image.rb +38 -0
- data/lib/twterm/image/vertical_sequential_image.rb +43 -0
- data/lib/twterm/image.rb +107 -0
- data/lib/twterm/key_mapper/abstract_key_mapper.rb +51 -0
- data/lib/twterm/key_mapper/app_key_mapper.rb +13 -0
- data/lib/twterm/key_mapper/cursor_key_mapper.rb +13 -0
- data/lib/twterm/key_mapper/general_key_mapper.rb +18 -0
- data/lib/twterm/key_mapper/no_such_command.rb +20 -0
- data/lib/twterm/key_mapper/no_such_key.rb +16 -0
- data/lib/twterm/key_mapper/status_key_mapper.rb +18 -0
- data/lib/twterm/key_mapper/tab_key_mapper.rb +31 -0
- data/lib/twterm/key_mapper.rb +127 -0
- data/lib/twterm/list.rb +0 -31
- data/lib/twterm/notifier.rb +7 -7
- data/lib/twterm/repository/abstract_entity_repository.rb +41 -0
- data/lib/twterm/repository/abstract_expirable_entity_repository.rb +35 -0
- data/lib/twterm/repository/abstract_repository.rb +64 -0
- data/lib/twterm/repository/direct_message_repository.rb +14 -0
- data/lib/twterm/repository/friendship_repository.rb +108 -0
- data/lib/twterm/repository/hashtag_repository.rb +39 -0
- data/lib/twterm/repository/list_repository.rb +14 -0
- data/lib/twterm/repository/status_repository.rb +36 -0
- data/lib/twterm/repository/user_repository.rb +22 -0
- data/lib/twterm/rest_client.rb +107 -63
- data/lib/twterm/screen.rb +21 -15
- data/lib/twterm/search_query_window.rb +139 -0
- data/lib/twterm/status.rb +14 -108
- data/lib/twterm/streaming_client.rb +13 -12
- data/lib/twterm/tab/base.rb +48 -8
- data/lib/twterm/tab/direct_message/conversation.rb +53 -52
- data/lib/twterm/tab/direct_message/conversation_list.rb +46 -45
- data/lib/twterm/tab/dumpable.rb +3 -3
- data/lib/twterm/tab/key_assignments_cheatsheet.rb +58 -57
- data/lib/twterm/tab/loadable.rb +20 -0
- data/lib/twterm/tab/new/list.rb +32 -43
- data/lib/twterm/tab/new/search.rb +31 -44
- data/lib/twterm/tab/new/start.rb +44 -55
- data/lib/twterm/tab/new/user.rb +15 -12
- data/lib/twterm/tab/rate_limit_status.rb +84 -0
- data/lib/twterm/tab/scrollable.rb +39 -19
- data/lib/twterm/tab/searchable.rb +133 -0
- data/lib/twterm/tab/statuses/base.rb +139 -129
- data/lib/twterm/tab/statuses/conversation.rb +26 -15
- data/lib/twterm/tab/statuses/favorites.rb +10 -8
- data/lib/twterm/tab/statuses/home.rb +10 -9
- data/lib/twterm/tab/statuses/list_timeline.rb +12 -8
- data/lib/twterm/tab/statuses/mentions.rb +17 -11
- data/lib/twterm/tab/statuses/search.rb +8 -5
- data/lib/twterm/tab/statuses/user_timeline.rb +11 -8
- data/lib/twterm/tab/user_list_management.rb +109 -0
- data/lib/twterm/tab/user_tab.rb +125 -126
- data/lib/twterm/tab/users/base.rb +39 -41
- data/lib/twterm/tab/users/followers.rb +9 -6
- data/lib/twterm/tab/users/friends.rb +9 -6
- data/lib/twterm/tab_manager.rb +64 -40
- data/lib/twterm/tweetbox.rb +18 -13
- data/lib/twterm/uri_opener.rb +2 -1
- data/lib/twterm/user.rb +2 -110
- data/lib/twterm/version.rb +1 -1
- data/lib/twterm/view.rb +30 -0
- data/lib/twterm.rb +3 -9
- data/spec/fixtures/status.json +107 -0
- data/spec/fixtures/user.json +102 -0
- data/spec/spec_helper.rb +7 -0
- data/spec/supports/shared_examples/abstract_key_mapper.rb +17 -0
- data/spec/twterm/extension/enumerator/lazy_spec.rb +11 -0
- data/spec/twterm/friendship_spec.rb +0 -102
- data/spec/twterm/image/blank_line_spec.rb +11 -0
- data/spec/twterm/image/brackets_spec.rb +12 -0
- data/spec/twterm/image/color_spec.rb +22 -0
- data/spec/twterm/image/empry_spec.rb +11 -0
- data/spec/twterm/image/horizontal_sequential_image_spec.rb +15 -0
- data/spec/twterm/image/parens_spec.rb +12 -0
- data/spec/twterm/image/string_image_spec.rb +12 -0
- data/spec/twterm/image/vertical_sequential_image_spec.rb +14 -0
- data/spec/twterm/image_spec.rb +65 -0
- data/spec/twterm/key_mapper/abstract_key_mapper_spec.rb +21 -0
- data/spec/twterm/key_mapper/app_key_mapper_spec.rb +7 -0
- data/spec/twterm/key_mapper/status_key_mapper_spec.rb +7 -0
- data/spec/twterm/key_mapper/tab_key_mapper_spec.rb +7 -0
- data/spec/twterm/repository/friendship_repository_spec.rb +108 -0
- data/spec/twterm/status_spec.rb +58 -0
- data/spec/twterm/user_spec.rb +94 -0
- data/twterm.gemspec +13 -10
- metadata +129 -35
- data/lib/twterm/event/notification.rb +0 -33
- data/lib/twterm/extensions/integer.rb +0 -5
- data/lib/twterm/filter_query_window.rb +0 -91
- data/lib/twterm/filterable_list.rb +0 -41
- data/lib/twterm/history/base.rb +0 -21
- data/lib/twterm/history/hashtag.rb +0 -13
- data/lib/twterm/history/savable.rb +0 -37
- data/lib/twterm/history/screen_name.rb +0 -11
- data/lib/twterm/promise.rb +0 -143
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'twterm/tab/loadable'
|
2
|
+
|
3
|
+
module Twterm
|
4
|
+
module Tab
|
5
|
+
class RateLimitStatus < Tab::Base
|
6
|
+
include Loadable
|
7
|
+
include Scrollable
|
8
|
+
|
9
|
+
@@mutex = Mutex.new
|
10
|
+
@@status = nil
|
11
|
+
|
12
|
+
def initialize(app, client)
|
13
|
+
super(app, client)
|
14
|
+
|
15
|
+
scroller.set_no_cursor_mode!
|
16
|
+
|
17
|
+
@ticker = Scheduler.new(1) { render }
|
18
|
+
|
19
|
+
@scheduler = Scheduler.new(10) { fetch }
|
20
|
+
|
21
|
+
fetch.then { initially_loaded! }
|
22
|
+
end
|
23
|
+
|
24
|
+
def close
|
25
|
+
@scheduler.kill
|
26
|
+
@ticker.kill
|
27
|
+
super
|
28
|
+
end
|
29
|
+
|
30
|
+
def drawable_item_count
|
31
|
+
window.maxy - 2
|
32
|
+
end
|
33
|
+
|
34
|
+
def image
|
35
|
+
return Image.string('Loading...') unless initially_loaded?
|
36
|
+
|
37
|
+
items
|
38
|
+
.drop(scroller.offset)
|
39
|
+
.take(drawable_item_count)
|
40
|
+
.reduce(Image.empty, :|)
|
41
|
+
end
|
42
|
+
|
43
|
+
def items
|
44
|
+
return [] unless initially_loaded?
|
45
|
+
|
46
|
+
@@status[:resources].flat_map do |category, limits|
|
47
|
+
[
|
48
|
+
!Image.string(category.to_s.gsub('_', ' ')).color(:green),
|
49
|
+
Image.blank_line,
|
50
|
+
*limits.flat_map do |endpoint, data|
|
51
|
+
limit, remaining = data[:limit], data[:remaining]
|
52
|
+
t = Time.at(data[:reset])
|
53
|
+
diff = (t - Time.now).round
|
54
|
+
|
55
|
+
[
|
56
|
+
Image.string(" #{endpoint}").color(:cyan) - (
|
57
|
+
limit == remaining \
|
58
|
+
? Image.empty
|
59
|
+
: Image.string(" (Resets #{diff.positive? ? "in #{diff} seconds" : 'soon...'})")),
|
60
|
+
Image.string(' ') - Image.remaining_resource(remaining, limit, 50) - Image.string(" #{remaining}/#{limit}"),
|
61
|
+
Image.blank_line,
|
62
|
+
]
|
63
|
+
end,
|
64
|
+
Image.blank_line,
|
65
|
+
]
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def respond_to_key(key)
|
70
|
+
scroller.respond_to_key(key)
|
71
|
+
end
|
72
|
+
|
73
|
+
def title
|
74
|
+
'Rate Limit Status'.freeze
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def fetch
|
80
|
+
client.rate_limit_status.then { |status| @@mutex.synchronize { @@status = status } }
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -9,9 +9,9 @@ module Twterm
|
|
9
9
|
def scroller
|
10
10
|
return @scroller unless @scroller.nil?
|
11
11
|
|
12
|
-
@scroller = Scroller.new
|
12
|
+
@scroller = self.class::Scroller.new(self)
|
13
13
|
@scroller.delegate = self
|
14
|
-
@scroller.after_move {
|
14
|
+
@scroller.after_move { render }
|
15
15
|
@scroller
|
16
16
|
end
|
17
17
|
|
@@ -21,10 +21,9 @@ module Twterm
|
|
21
21
|
items.count
|
22
22
|
end
|
23
23
|
|
24
|
-
private
|
25
|
-
|
26
24
|
class Scroller
|
27
25
|
extend Forwardable
|
26
|
+
include Publisher
|
28
27
|
|
29
28
|
attr_reader :index, :offset
|
30
29
|
|
@@ -35,19 +34,21 @@ module Twterm
|
|
35
34
|
add_hook(:after_move, &block)
|
36
35
|
end
|
37
36
|
|
38
|
-
def
|
39
|
-
|
37
|
+
def current_index?(i)
|
38
|
+
index == offset + i
|
40
39
|
end
|
41
40
|
|
42
|
-
def current_item
|
43
|
-
index
|
41
|
+
def current_item
|
42
|
+
items[index]
|
44
43
|
end
|
45
44
|
|
46
45
|
def no_cursor_mode?
|
47
46
|
!!@no_cursor_mode
|
48
47
|
end
|
49
48
|
|
50
|
-
def initialize
|
49
|
+
def initialize(tab)
|
50
|
+
@tab = tab
|
51
|
+
|
51
52
|
@index = 0
|
52
53
|
@offset = 0
|
53
54
|
@no_cursor_mode = false
|
@@ -58,7 +59,6 @@ module Twterm
|
|
58
59
|
end
|
59
60
|
|
60
61
|
def item_appended!
|
61
|
-
@index -= 1
|
62
62
|
@offset -= 1 if @offset > 0
|
63
63
|
end
|
64
64
|
|
@@ -69,7 +69,7 @@ module Twterm
|
|
69
69
|
end
|
70
70
|
|
71
71
|
def move_down
|
72
|
-
return if count == 0 || index == count - 1
|
72
|
+
return if count == 0 || index == count - 1 || (no_cursor_mode? && offset + drawable_item_count >= count)
|
73
73
|
# return when there are no items or cursor is at the bottom
|
74
74
|
|
75
75
|
@index += 1
|
@@ -81,7 +81,7 @@ module Twterm
|
|
81
81
|
def move_to_bottom
|
82
82
|
return if count == 0 || index == count - 1
|
83
83
|
|
84
|
-
@index = count - 1
|
84
|
+
@index = no_cursor_mode? ? count - drawable_item_count : count - 1
|
85
85
|
@offset = [count - drawable_item_count + 1, 0].max
|
86
86
|
|
87
87
|
@offset += 1 until last_item_shown?
|
@@ -89,6 +89,16 @@ module Twterm
|
|
89
89
|
hook :after_move
|
90
90
|
end
|
91
91
|
|
92
|
+
def move_to(n)
|
93
|
+
if nth_item_drawable?(n)
|
94
|
+
@index = n
|
95
|
+
else
|
96
|
+
@index = @offset = n
|
97
|
+
end
|
98
|
+
|
99
|
+
hook :after_move
|
100
|
+
end
|
101
|
+
|
92
102
|
def move_to_top
|
93
103
|
return if count.zero? || index.zero?
|
94
104
|
|
@@ -108,23 +118,31 @@ module Twterm
|
|
108
118
|
end
|
109
119
|
|
110
120
|
def nth_item_drawable?(n)
|
111
|
-
n.between?(offset, offset + drawable_item_count)
|
121
|
+
n.between?(offset, offset + drawable_item_count - 1)
|
112
122
|
end
|
113
123
|
|
114
124
|
def respond_to_key(key)
|
125
|
+
k = KeyMapper.instance
|
126
|
+
|
115
127
|
case key
|
116
|
-
when
|
128
|
+
when k[:general, :page_down]
|
117
129
|
10.times { move_down }
|
118
|
-
when
|
130
|
+
when k[:general, :top]
|
119
131
|
move_to_top
|
120
|
-
when
|
132
|
+
when k[:general, :bottom]
|
121
133
|
move_to_bottom
|
122
|
-
when
|
134
|
+
when k[:general, :down], Curses::Key::DOWN
|
123
135
|
move_down
|
124
|
-
when
|
136
|
+
when k[:general, :up], Curses::Key::UP
|
125
137
|
move_up
|
126
|
-
when
|
138
|
+
when k[:general, :page_up]
|
127
139
|
10.times { move_up }
|
140
|
+
when k[:cursor, :top_of_window]
|
141
|
+
move_to(offset)
|
142
|
+
when k[:cursor, :middle_of_window]
|
143
|
+
move_to((2 * offset + [drawable_item_count, total_item_count - offset].min - 1) / 2)
|
144
|
+
when k[:cursor, :bottom_of_window]
|
145
|
+
move_to(offset + [drawable_item_count, total_item_count - offset].min - 1)
|
128
146
|
else
|
129
147
|
return false
|
130
148
|
end
|
@@ -138,6 +156,8 @@ module Twterm
|
|
138
156
|
|
139
157
|
private
|
140
158
|
|
159
|
+
attr_reader :tab
|
160
|
+
|
141
161
|
def add_hook(name, &block)
|
142
162
|
@hooks ||= {}
|
143
163
|
@hooks[name] = block
|
@@ -0,0 +1,133 @@
|
|
1
|
+
require 'twterm/event/notification/info'
|
2
|
+
require 'twterm/event/notification/warning'
|
3
|
+
require 'twterm/search_query_window'
|
4
|
+
|
5
|
+
module Twterm
|
6
|
+
module Tab
|
7
|
+
module Searchable
|
8
|
+
include Scrollable
|
9
|
+
extend Forwardable
|
10
|
+
|
11
|
+
def_delegators :scroller, :search_query
|
12
|
+
|
13
|
+
def matches?(item, query)
|
14
|
+
raise NotImplementedError, '`matches?` method must be implemented'
|
15
|
+
end
|
16
|
+
|
17
|
+
class Scroller < Scrollable::Scroller
|
18
|
+
extend Forwardable
|
19
|
+
include Publisher
|
20
|
+
|
21
|
+
attr_reader :search_query
|
22
|
+
|
23
|
+
def_delegators :search_query_window,
|
24
|
+
:searching_backward!, :searching_backward?,
|
25
|
+
:searching_down!, :searching_down?,
|
26
|
+
:searching_forward!, :searching_forward?,
|
27
|
+
:searching_up!, :searching_up?
|
28
|
+
|
29
|
+
def initialize(*)
|
30
|
+
super
|
31
|
+
@search_query = ''
|
32
|
+
end
|
33
|
+
|
34
|
+
def find_next
|
35
|
+
searching_forward!
|
36
|
+
|
37
|
+
search_query_window.render_last_query
|
38
|
+
search
|
39
|
+
end
|
40
|
+
|
41
|
+
def find_previous
|
42
|
+
searching_backward!
|
43
|
+
|
44
|
+
search_query_window.render_last_query
|
45
|
+
search
|
46
|
+
end
|
47
|
+
|
48
|
+
def respond_to_key(key)
|
49
|
+
k = KeyMapper.instance
|
50
|
+
|
51
|
+
return if super
|
52
|
+
|
53
|
+
case key
|
54
|
+
when k[:tab, :find_next]
|
55
|
+
find_next
|
56
|
+
when k[:tab, :find_previous]
|
57
|
+
find_previous
|
58
|
+
when k[:tab, :search_down]
|
59
|
+
search_down
|
60
|
+
when k[:tab, :search_up]
|
61
|
+
search_up
|
62
|
+
else
|
63
|
+
return false
|
64
|
+
end
|
65
|
+
|
66
|
+
true
|
67
|
+
end
|
68
|
+
|
69
|
+
def search_up
|
70
|
+
searching_up!
|
71
|
+
searching_forward!
|
72
|
+
ask
|
73
|
+
|
74
|
+
return if search_query.empty?
|
75
|
+
|
76
|
+
find_next
|
77
|
+
end
|
78
|
+
|
79
|
+
def search_down
|
80
|
+
searching_down!
|
81
|
+
searching_forward!
|
82
|
+
ask
|
83
|
+
|
84
|
+
return if search_query.empty?
|
85
|
+
|
86
|
+
find_next
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
def ask
|
92
|
+
@search_query = search_query_window.input || ''
|
93
|
+
end
|
94
|
+
|
95
|
+
def search
|
96
|
+
@search_query = search_query_window.last_query if search_query.empty?
|
97
|
+
|
98
|
+
if @search_query.empty?
|
99
|
+
event = Event::Notification::Info.new("search query is empty. Press '/' or '?' to start searching.")
|
100
|
+
publish(event)
|
101
|
+
return
|
102
|
+
end
|
103
|
+
|
104
|
+
f = searching_down? ^ searching_forward? ? -> xs { xs.reverse } : -> xs { xs }
|
105
|
+
previous_index = index
|
106
|
+
|
107
|
+
xs = [
|
108
|
+
*f.(items.each_with_index.drop(index.succ) + items.each_with_index.take(index)),
|
109
|
+
[current_item, index]
|
110
|
+
]
|
111
|
+
_, index = xs.find { |x, _| tab.matches?(x, search_query) }
|
112
|
+
|
113
|
+
if index.nil?
|
114
|
+
publish(Event::Notification::Warning.new("No matches found: \"#{search_query}\""))
|
115
|
+
else
|
116
|
+
hit_bottom = Event::Notification::Info.new('search hit BOTTOM, continuing at TOP')
|
117
|
+
hit_top = Event::Notification::Info.new('search hit TOP, continuing at BOTTOM')
|
118
|
+
|
119
|
+
publish(hit_bottom) if (searching_down? ^ searching_backward?) && index <= previous_index
|
120
|
+
publish(hit_top) if (searching_up? ^ searching_backward?) && index >= previous_index
|
121
|
+
move_to(index)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def search_query_window
|
126
|
+
SearchQueryWindow.instance
|
127
|
+
end
|
128
|
+
|
129
|
+
alias_method :count, :total_item_count
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|