twterm 1.3.0 → 2.0.0.beta1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +14 -18
  3. data/bin/twterm +1 -1
  4. data/lib/twterm/app.rb +120 -30
  5. data/lib/twterm/client.rb +10 -13
  6. data/lib/twterm/completion_mamanger.rb +11 -6
  7. data/lib/twterm/direct_message.rb +6 -28
  8. data/lib/twterm/direct_message_composer.rb +10 -5
  9. data/lib/twterm/direct_message_manager.rb +5 -6
  10. data/lib/twterm/event/notification/abstract_notification.rb +27 -0
  11. data/lib/twterm/event/notification/error.rb +13 -0
  12. data/lib/twterm/event/notification/info.rb +13 -0
  13. data/lib/twterm/event/notification/success.rb +13 -0
  14. data/lib/twterm/event/notification/warning.rb +13 -0
  15. data/lib/twterm/event_dispatcher.rb +1 -1
  16. data/lib/twterm/extensions/array.rb +5 -0
  17. data/lib/twterm/extensions/enumerator/lazy.rb +3 -0
  18. data/lib/twterm/extensions/string.rb +0 -4
  19. data/lib/twterm/friendship.rb +1 -85
  20. data/lib/twterm/image/between.rb +31 -0
  21. data/lib/twterm/image/blank_line.rb +21 -0
  22. data/lib/twterm/image/bold.rb +31 -0
  23. data/lib/twterm/image/brackets.rb +21 -0
  24. data/lib/twterm/image/color.rb +45 -0
  25. data/lib/twterm/image/empty.rb +21 -0
  26. data/lib/twterm/image/horizontal_sequential_image.rb +48 -0
  27. data/lib/twterm/image/parens.rb +21 -0
  28. data/lib/twterm/image/string_image.rb +38 -0
  29. data/lib/twterm/image/vertical_sequential_image.rb +43 -0
  30. data/lib/twterm/image.rb +107 -0
  31. data/lib/twterm/key_mapper/abstract_key_mapper.rb +51 -0
  32. data/lib/twterm/key_mapper/app_key_mapper.rb +13 -0
  33. data/lib/twterm/key_mapper/cursor_key_mapper.rb +13 -0
  34. data/lib/twterm/key_mapper/general_key_mapper.rb +18 -0
  35. data/lib/twterm/key_mapper/no_such_command.rb +20 -0
  36. data/lib/twterm/key_mapper/no_such_key.rb +16 -0
  37. data/lib/twterm/key_mapper/status_key_mapper.rb +18 -0
  38. data/lib/twterm/key_mapper/tab_key_mapper.rb +31 -0
  39. data/lib/twterm/key_mapper.rb +127 -0
  40. data/lib/twterm/list.rb +0 -31
  41. data/lib/twterm/notifier.rb +7 -7
  42. data/lib/twterm/repository/abstract_entity_repository.rb +41 -0
  43. data/lib/twterm/repository/abstract_expirable_entity_repository.rb +35 -0
  44. data/lib/twterm/repository/abstract_repository.rb +64 -0
  45. data/lib/twterm/repository/direct_message_repository.rb +14 -0
  46. data/lib/twterm/repository/friendship_repository.rb +108 -0
  47. data/lib/twterm/repository/hashtag_repository.rb +39 -0
  48. data/lib/twterm/repository/list_repository.rb +14 -0
  49. data/lib/twterm/repository/status_repository.rb +36 -0
  50. data/lib/twterm/repository/user_repository.rb +22 -0
  51. data/lib/twterm/rest_client.rb +107 -63
  52. data/lib/twterm/screen.rb +21 -15
  53. data/lib/twterm/search_query_window.rb +139 -0
  54. data/lib/twterm/status.rb +14 -108
  55. data/lib/twterm/streaming_client.rb +13 -12
  56. data/lib/twterm/tab/base.rb +48 -8
  57. data/lib/twterm/tab/direct_message/conversation.rb +53 -52
  58. data/lib/twterm/tab/direct_message/conversation_list.rb +46 -45
  59. data/lib/twterm/tab/dumpable.rb +3 -3
  60. data/lib/twterm/tab/key_assignments_cheatsheet.rb +58 -57
  61. data/lib/twterm/tab/loadable.rb +20 -0
  62. data/lib/twterm/tab/new/list.rb +32 -43
  63. data/lib/twterm/tab/new/search.rb +31 -44
  64. data/lib/twterm/tab/new/start.rb +44 -55
  65. data/lib/twterm/tab/new/user.rb +15 -12
  66. data/lib/twterm/tab/rate_limit_status.rb +84 -0
  67. data/lib/twterm/tab/scrollable.rb +39 -19
  68. data/lib/twterm/tab/searchable.rb +133 -0
  69. data/lib/twterm/tab/statuses/base.rb +139 -129
  70. data/lib/twterm/tab/statuses/conversation.rb +26 -15
  71. data/lib/twterm/tab/statuses/favorites.rb +10 -8
  72. data/lib/twterm/tab/statuses/home.rb +10 -9
  73. data/lib/twterm/tab/statuses/list_timeline.rb +12 -8
  74. data/lib/twterm/tab/statuses/mentions.rb +17 -11
  75. data/lib/twterm/tab/statuses/search.rb +8 -5
  76. data/lib/twterm/tab/statuses/user_timeline.rb +11 -8
  77. data/lib/twterm/tab/user_list_management.rb +109 -0
  78. data/lib/twterm/tab/user_tab.rb +125 -126
  79. data/lib/twterm/tab/users/base.rb +39 -41
  80. data/lib/twterm/tab/users/followers.rb +9 -6
  81. data/lib/twterm/tab/users/friends.rb +9 -6
  82. data/lib/twterm/tab_manager.rb +64 -40
  83. data/lib/twterm/tweetbox.rb +18 -13
  84. data/lib/twterm/uri_opener.rb +2 -1
  85. data/lib/twterm/user.rb +2 -110
  86. data/lib/twterm/version.rb +1 -1
  87. data/lib/twterm/view.rb +30 -0
  88. data/lib/twterm.rb +3 -9
  89. data/spec/fixtures/status.json +107 -0
  90. data/spec/fixtures/user.json +102 -0
  91. data/spec/spec_helper.rb +7 -0
  92. data/spec/supports/shared_examples/abstract_key_mapper.rb +17 -0
  93. data/spec/twterm/extension/enumerator/lazy_spec.rb +11 -0
  94. data/spec/twterm/friendship_spec.rb +0 -102
  95. data/spec/twterm/image/blank_line_spec.rb +11 -0
  96. data/spec/twterm/image/brackets_spec.rb +12 -0
  97. data/spec/twterm/image/color_spec.rb +22 -0
  98. data/spec/twterm/image/empry_spec.rb +11 -0
  99. data/spec/twterm/image/horizontal_sequential_image_spec.rb +15 -0
  100. data/spec/twterm/image/parens_spec.rb +12 -0
  101. data/spec/twterm/image/string_image_spec.rb +12 -0
  102. data/spec/twterm/image/vertical_sequential_image_spec.rb +14 -0
  103. data/spec/twterm/image_spec.rb +65 -0
  104. data/spec/twterm/key_mapper/abstract_key_mapper_spec.rb +21 -0
  105. data/spec/twterm/key_mapper/app_key_mapper_spec.rb +7 -0
  106. data/spec/twterm/key_mapper/status_key_mapper_spec.rb +7 -0
  107. data/spec/twterm/key_mapper/tab_key_mapper_spec.rb +7 -0
  108. data/spec/twterm/repository/friendship_repository_spec.rb +108 -0
  109. data/spec/twterm/status_spec.rb +58 -0
  110. data/spec/twterm/user_spec.rb +94 -0
  111. data/twterm.gemspec +13 -10
  112. metadata +129 -35
  113. data/lib/twterm/event/notification.rb +0 -33
  114. data/lib/twterm/extensions/integer.rb +0 -5
  115. data/lib/twterm/filter_query_window.rb +0 -91
  116. data/lib/twterm/filterable_list.rb +0 -41
  117. data/lib/twterm/history/base.rb +0 -21
  118. data/lib/twterm/history/hashtag.rb +0 -13
  119. data/lib/twterm/history/savable.rb +0 -37
  120. data/lib/twterm/history/screen_name.rb +0 -11
  121. data/lib/twterm/promise.rb +0 -143
@@ -1,7 +1,8 @@
1
+ require 'concurrent'
1
2
  require 'twterm/direct_message'
2
3
  require 'twterm/direct_message_manager'
3
4
  require 'twterm/publisher'
4
- require 'twterm/event/notification'
5
+ require 'twterm/event/notification/success'
5
6
 
6
7
  module Twterm
7
8
  module RESTClient
@@ -10,12 +11,16 @@ module Twterm
10
11
  CONSUMER_KEY = 'vLNSVFgXclBJQJRZ7VLMxL9lA'.freeze
11
12
  CONSUMER_SECRET = 'OFLKzrepRG2p1hq0nUB9j2S9ndFQoNTPheTpmOY0GYw55jGgS5'.freeze
12
13
 
14
+ def add_list_member(list_id, user_id)
15
+ send_request { rest_client.add_list_member(list_id, user_id) }
16
+ end
17
+
13
18
  def block(*user_ids)
14
19
  send_request do
15
20
  rest_client.block(*user_ids)
16
21
  end.then do |users|
17
22
  users.each do |user|
18
- Friendship.block(self.user_id, user.id)
23
+ friendship_repository.block(self.user_id, user.id)
19
24
  end
20
25
  end
21
26
  end
@@ -24,10 +29,10 @@ module Twterm
24
29
  send_request do
25
30
  rest_client.create_direct_message(recipient.id, text)
26
31
  end.then do |message|
27
- msg = DirectMessage.new(message)
32
+ msg = direct_message_repository.create(message)
28
33
  direct_message_manager.add(msg.recipient, msg)
29
34
  publish(Event::DirectMessage::Fetched.new)
30
- publish(Event::Notification.new(:message, 'Your message to @%s has been sent' % msg.recipient.screen_name))
35
+ publish(Event::Notification::Success.new('Your message to @%s has been sent' % msg.recipient.screen_name))
31
36
  end
32
37
  end
33
38
 
@@ -37,13 +42,13 @@ module Twterm
37
42
 
38
43
  def direct_messages_received
39
44
  send_request do
40
- rest_client.direct_messages(count: 200).map(&DirectMessage.method(:new))
45
+ rest_client.direct_messages(count: 200).map { |dm| direct_message_repository.create(dm) }
41
46
  end
42
47
  end
43
48
 
44
49
  def direct_messages_sent
45
50
  send_request do
46
- rest_client.direct_messages_sent(count: 200).map(&DirectMessage.method(:new))
51
+ rest_client.direct_messages_sent(count: 200).map { |dm| direct_message_repository.create(dm) }
47
52
  end
48
53
  end
49
54
 
@@ -51,11 +56,11 @@ module Twterm
51
56
  send_request_without_catch do
52
57
  rest_client.destroy_status(status.id)
53
58
  publish(Event::Status::Delete.new(status.id))
54
- publish(Event::Notification.new(:message, 'Your tweet has been deleted'))
59
+ publish(Event::Notification::Success.new('Your tweet has been deleted'))
55
60
  end.catch do |reason|
56
61
  case reason
57
62
  when Twitter::Error::NotFound, Twitter::Error::Forbidden
58
- publish(Event::Notification.new(:error, 'You cannot destroy that status'))
63
+ publish(Event::Notification::Error.new('You cannot destroy that status'))
59
64
  else
60
65
  raise reason
61
66
  end
@@ -67,10 +72,11 @@ module Twterm
67
72
 
68
73
  send_request do
69
74
  rest_client.favorite(status.id)
70
- end.then do
71
- status.favorite!
72
- publish(Event::Notification.new(:message, 'Successfully liked: @%s "%s"' % [
73
- status.user.screen_name, status.text
75
+ end.then do |tweet, *_|
76
+ status_repository.create(tweet)
77
+
78
+ publish(Event::Notification::Success.new('Successfully liked: @%s "%s"' % [
79
+ tweet.user.screen_name, status.text
74
80
  ]))
75
81
  end
76
82
  end
@@ -81,7 +87,7 @@ module Twterm
81
87
  send_request do
82
88
  rest_client.favorites(user_id, count: 200)
83
89
  end.then do |tweets|
84
- tweets.map(&Status.method(:new))
90
+ tweets.map { |tweet| status_repository.create(tweet) }
85
91
  end
86
92
  end
87
93
 
@@ -97,9 +103,9 @@ module Twterm
97
103
  end.then do |users|
98
104
  users.each do |user|
99
105
  if user.protected?
100
- Friendship.following_requested(self.user_id, user.id)
106
+ friendship_repository.following_requested(self.user_id, user.id)
101
107
  else
102
- Friendship.follow(self.user_id, user.id)
108
+ friendship_repository.follow(self.user_id, user.id)
103
109
  end
104
110
  end
105
111
  end
@@ -113,9 +119,9 @@ module Twterm
113
119
  send_request do
114
120
  rest_client.follower_ids(user_id).each_slice(100) do |user_ids|
115
121
  m.synchronize do
116
- users = rest_client.users(*user_ids).map(& -> u { User.new(u) })
122
+ users = rest_client.users(*user_ids).map { |u| user_repository.create(u) }
117
123
  users.each do |user|
118
- Friendship.follow(user.id, self.user_id)
124
+ friendship_repository.follow(user.id, self.user_id)
119
125
  end if user_id == self.user_id
120
126
  yield users
121
127
  end
@@ -131,7 +137,7 @@ module Twterm
131
137
  send_request do
132
138
  rest_client.friend_ids(user_id).each_slice(100) do |user_ids|
133
139
  m.synchronize do
134
- yield rest_client.users(*user_ids).map(& -> u { User.new(u) })
140
+ yield rest_client.users(*user_ids).map { |u| user_repository.create(u) }
135
141
  end
136
142
  end
137
143
  end
@@ -140,10 +146,10 @@ module Twterm
140
146
  def home_timeline
141
147
  send_request do
142
148
  rest_client.home_timeline(count: 200)
143
- end.then do |statuses|
144
- statuses
149
+ end.then do |tweets|
150
+ tweets
145
151
  .select(&@mute_filter)
146
- .map(&Status.method(:new))
152
+ .map { |tweet| status_repository.create(tweet) }
147
153
  end
148
154
  end
149
155
 
@@ -151,7 +157,7 @@ module Twterm
151
157
  send_request do
152
158
  rest_client.list(list_id)
153
159
  end.then do |list|
154
- List.new(list)
160
+ list_repository.create(list)
155
161
  end
156
162
  end
157
163
 
@@ -163,7 +169,7 @@ module Twterm
163
169
  end.then do |statuses|
164
170
  statuses
165
171
  .select(&@mute_filter)
166
- .map(&Status.method(:new))
172
+ .map { |tweet| status_repository.create(tweet) }
167
173
  end
168
174
  end
169
175
 
@@ -171,12 +177,13 @@ module Twterm
171
177
  send_request do
172
178
  rest_client.lists
173
179
  end.then do |lists|
174
- lists.map { |list| List.new(list) }
180
+ lists.map { |list| list_repository.create(list) }
175
181
  end
176
182
  end
177
183
 
178
184
  def lookup_friendships
179
- user_ids = User.ids.reject { |id| Friendship.already_looked_up?(id) }
185
+ repo = friendship_repository
186
+ user_ids = user_repository.ids.reject { |id| repo.already_looked_up?(id) }
180
187
  send_request_without_catch do
181
188
  user_ids.each_slice(100) do |chunked_user_ids|
182
189
  friendships = rest_client.friendships(*chunked_user_ids)
@@ -185,13 +192,13 @@ module Twterm
185
192
  client_id = user_id
186
193
 
187
194
  conn = friendship.connections
188
- conn.include?('blocking') ? Friendship.block(client_id, id) : Friendship.unblock(client_id, id)
189
- conn.include?('following') ? Friendship.follow(client_id, id) : Friendship.unfollow(client_id, id)
190
- conn.include?('following_requested') ? Friendship.following_requested(client_id, id) : Friendship.following_not_requested(client_id, id)
191
- conn.include?('followed_by') ? Friendship.follow(id, client_id) : Friendship.unfollow(id, client_id)
192
- conn.include?('muting') ? Friendship.mute(client_id, id) : Friendship.unmute(client_id, id)
195
+ conn.include?('blocking') ? repo.block(client_id, id) : repo.unblock(client_id, id)
196
+ conn.include?('following') ? repo.follow(client_id, id) : repo.unfollow(client_id, id)
197
+ conn.include?('following_requested') ? repo.following_requested(client_id, id) : repo.following_not_requested(client_id, id)
198
+ conn.include?('followed_by') ? repo.follow(id, client_id) : repo.unfollow(id, client_id)
199
+ conn.include?('muting') ? repo.mute(client_id, id) : repo.unmute(client_id, id)
193
200
 
194
- Friendship.looked_up!(id)
201
+ repo.looked_up!(id)
195
202
  end
196
203
  end
197
204
  end.catch do |e|
@@ -204,13 +211,21 @@ module Twterm
204
211
  end.catch(&show_error)
205
212
  end
206
213
 
214
+ def memberships(user_id, options = {})
215
+ send_request do
216
+ user_id.nil? ? rest_client.memberships(options) : rest_client.memberships(user_id, options)
217
+ end.then do |cursor|
218
+ cursor.map { |list| list_repository.create(list) }
219
+ end
220
+ end
221
+
207
222
  def mentions
208
223
  send_request do
209
224
  rest_client.mentions(count: 200)
210
225
  end.then do |statuses|
211
226
  statuses
212
227
  .select(&@mute_filter)
213
- .map(&Status.method(:new))
228
+ .map { |tweet| status_repository.create(tweet) }
214
229
  end
215
230
  end
216
231
 
@@ -219,11 +234,19 @@ module Twterm
219
234
  rest_client.mute(*user_ids)
220
235
  end.then do |users|
221
236
  users.each do |user|
222
- Friendship.mute(self.user_id, user.id)
237
+ friendship_repository.mute(self.user_id, user.id)
223
238
  end
224
239
  end
225
240
  end
226
241
 
242
+ def owned_lists
243
+ send_request do
244
+ rest_client.owned_lists
245
+ end.then do |lists|
246
+ lists.map { |list| list_repository.create(list) }
247
+ end
248
+ end
249
+
227
250
  def post(text, in_reply_to = nil)
228
251
  send_request do
229
252
  if in_reply_to.is_a? Status
@@ -232,20 +255,29 @@ module Twterm
232
255
  else
233
256
  rest_client.update(text)
234
257
  end
235
- publish(Event::Notification.new(:message, 'Your tweet has been posted'))
258
+ publish(Event::Notification::Success.new('Your tweet has been posted'))
236
259
  end
237
260
  end
238
261
 
262
+ def rate_limit_status
263
+ send_request { Twitter::REST::Request.new(rest_client, :get, '/1.1/application/rate_limit_status.json').perform }
264
+ end
265
+
266
+ def remove_list_member(list_id, user_id)
267
+ send_request { rest_client.remove_list_member(list_id, user_id) }
268
+ end
269
+
239
270
  def retweet(status)
240
271
  fail ArgumentError,
241
272
  'argument must be an instance of Status class' unless status.is_a? Status
242
273
 
243
274
  send_request_without_catch do
244
275
  rest_client.retweet!(status.id)
245
- end.then do
246
- status.retweet!
247
- publish(Event::Notification.new(:message, 'Successfully retweeted: @%s "%s"' % [
248
- status.user.screen_name, status.text
276
+ end.then do |tweet, *_|
277
+ status_repository.create(tweet)
278
+
279
+ publish(Event::Notification::Success.new('Successfully retweeted: @%s "%s"' % [
280
+ tweet.user.screen_name, status.text
249
281
  ]))
250
282
  end.catch do |reason|
251
283
  message =
@@ -255,15 +287,11 @@ module Twterm
255
287
  when Twitter::Error::NotFound
256
288
  'The status is not found'
257
289
  when Twitter::Error::Forbidden
258
- if status.user.id == user_id # when the status is mine
259
- 'You cannot retweet your own status'
260
- else # when the status is not mine
261
- 'The status is protected'
262
- end
290
+ 'The status is protected'
263
291
  else
264
292
  raise e
265
293
  end
266
- publish(Event::Notification.new(:error, "Retweet attempt failed: #{message}"))
294
+ publish(Event::Notification::Error.new("Retweet attempt failed: #{message}"))
267
295
  end.catch(&show_error)
268
296
  end
269
297
 
@@ -279,7 +307,7 @@ module Twterm
279
307
  end.then do |statuses|
280
308
  statuses
281
309
  .map(&Twitter::Tweet.method(:new))
282
- .map(&Status.method(:new))
310
+ .map { |tweet| status_repository.create(tweet) }
283
311
  end
284
312
  end
285
313
 
@@ -287,7 +315,7 @@ module Twterm
287
315
  send_request do
288
316
  rest_client.status(status_id)
289
317
  end.then do |status|
290
- Status.new(status)
318
+ status_repository.create(status)
291
319
  end
292
320
  end
293
321
 
@@ -302,7 +330,7 @@ module Twterm
302
330
  raise reason
303
331
  end
304
332
  end.catch(&show_error).then do |user|
305
- user.nil? ? nil : User.new(user)
333
+ user.nil? ? nil : user_repository.create(user)
306
334
  end
307
335
  end
308
336
 
@@ -311,7 +339,7 @@ module Twterm
311
339
  rest_client.unblock(*user_ids)
312
340
  end.then do |users|
313
341
  users.each do |user|
314
- Friendship.unblock(self.user_id, user.id)
342
+ friendship_repository.unblock(self.user_id, user.id)
315
343
  end
316
344
  end
317
345
  end
@@ -322,10 +350,11 @@ module Twterm
322
350
 
323
351
  send_request do
324
352
  rest_client.unfavorite(status.id)
325
- end.then do
326
- status.unfavorite!
327
- publish(Event::Notification.new(:message, 'Successfully unliked: @%s "%s"' % [
328
- status.user.screen_name, status.text
353
+ end.then do |tweet, *_|
354
+ status_repository.create(tweet)
355
+
356
+ publish(Event::Notification::Success.new('Successfully unliked: @%s "%s"' % [
357
+ tweet.user.screen_name, status.text
329
358
  ]))
330
359
  end
331
360
  end
@@ -335,7 +364,7 @@ module Twterm
335
364
  rest_client.unfollow(*user_ids)
336
365
  end.then do |users|
337
366
  users.each do |user|
338
- Friendship.unfollow(self.user_id, user.id)
367
+ friendship_repository.unfollow(self.user_id, user.id)
339
368
  end
340
369
  end
341
370
  end
@@ -345,18 +374,39 @@ module Twterm
345
374
  rest_client.unmute(*user_ids)
346
375
  end.then do |users|
347
376
  users.each do |user|
348
- Friendship.unmute(self.user_id, user.id)
377
+ friendship_repository.unmute(self.user_id, user.id)
349
378
  end
350
379
  end
351
380
  end
352
381
 
382
+ def unretweet(status)
383
+ send_request do
384
+ Twitter::REST::Request.new(rest_client, :post, "/1.1/statuses/unretweet/#{status.id}.json").perform
385
+ end
386
+ .then do |json, *_|
387
+ json[:retweet_count] -= 1
388
+ json[:retweeted] = false
389
+
390
+ Twitter::Tweet.new(json)
391
+ end
392
+ .then do |tweet|
393
+ status = status_repository.create(tweet)
394
+
395
+ publish(Event::Notification::Success.new('Successfully unretweeted: @%s "%s"' % [
396
+ tweet.user.screen_name, status.text
397
+ ]))
398
+
399
+ status
400
+ end
401
+ end
402
+
353
403
  def user_timeline(user_id)
354
404
  send_request do
355
405
  rest_client.user_timeline(user_id, count: 200)
356
406
  end.then do |statuses|
357
407
  statuses
358
408
  .select(&@mute_filter)
359
- .map(&Status.method(:new))
409
+ .map { |tweet| status_repository.create(tweet) }
360
410
  end
361
411
  end
362
412
 
@@ -374,13 +424,7 @@ module Twterm
374
424
  end
375
425
 
376
426
  def send_request_without_catch(&block)
377
- Promise.new do |resolve, reject|
378
- begin
379
- resolve.(block.call)
380
- rescue Twitter::Error => reason
381
- reject.(reason)
382
- end
383
- end
427
+ Concurrent::Promise.execute { block.call }
384
428
  end
385
429
 
386
430
  private
@@ -393,7 +437,7 @@ module Twterm
393
437
  proc do |e|
394
438
  case e
395
439
  when Twitter::Error
396
- publish(Event::Notification.new(:error, "Failed to send request: #{e.message}"))
440
+ publish(Event::Notification::Error.new("Failed to send request: #{e.message}"))
397
441
  else
398
442
  raise e
399
443
  end
data/lib/twterm/screen.rb CHANGED
@@ -1,16 +1,18 @@
1
1
  require 'twterm/event/screen/resize'
2
+ require 'twterm/key_mapper'
2
3
  require 'twterm/subscriber'
3
4
 
4
5
  module Twterm
5
6
  class Screen
6
- include Singleton
7
7
  include Subscriber
8
8
  include Curses
9
9
 
10
- def initialize
10
+ def initialize(app, client)
11
+ @app, @client = app, client
12
+
11
13
  @screen = init_screen
12
14
  noecho
13
- cbreak
15
+ raw
14
16
  curs_set(0)
15
17
  stdscr.keypad(true)
16
18
  start_color
@@ -20,21 +22,23 @@ module Twterm
20
22
  end
21
23
 
22
24
  def refresh
23
- TabManager.instance.refresh_window
24
- TabManager.instance.current_tab.refresh
25
+ app.tab_manager.refresh_window
26
+ app.tab_manager.current_tab.render
25
27
  Notifier.instance.show
26
28
  end
27
29
 
28
30
  def respond_to_key(key)
31
+ k = KeyMapper.instance
32
+
29
33
  case key
30
- when ?n
31
- Tweetbox.instance.compose
34
+ when k[:status, :compose]
35
+ app.tweetbox.compose
32
36
  return
33
- when ?Q
34
- App.instance.quit
35
- when ??
36
- tab = Tab::KeyAssignmentsCheatsheet.new
37
- TabManager.instance.add_and_show tab
37
+ when k[:app, :quit], 3
38
+ app.quit
39
+ when k[:app, :cheatsheet]
40
+ tab = Tab::KeyAssignmentsCheatsheet.new(app, client)
41
+ app.tab_manager.add_and_show tab
38
42
  else
39
43
  return false
40
44
  end
@@ -51,6 +55,8 @@ module Twterm
51
55
 
52
56
  private
53
57
 
58
+ attr_reader :app, :client
59
+
54
60
  def resize(event)
55
61
  return if closed?
56
62
 
@@ -62,12 +68,12 @@ module Twterm
62
68
  end
63
69
 
64
70
  def scan
65
- App.instance.reset_interruption_handler
71
+ app.reset_interruption_handler
66
72
 
67
73
  key = getch
68
74
 
69
- return if TabManager.instance.current_tab.respond_to_key(key)
70
- return if TabManager.instance.respond_to_key(key)
75
+ return if app.tab_manager.current_tab.respond_to_key(key)
76
+ return if app.tab_manager.respond_to_key(key)
71
77
  respond_to_key(key)
72
78
  end
73
79
  end
@@ -0,0 +1,139 @@
1
+ require 'twterm/event/screen/resize'
2
+ require 'twterm/subscriber'
3
+
4
+ module Twterm
5
+ class SearchQueryWindow
6
+ include Curses
7
+ include Singleton
8
+ include Subscriber
9
+
10
+ class CancelInput < StandardError; end
11
+
12
+ attr_reader :last_query
13
+
14
+ def initialize
15
+ @window = stdscr.subwin(1, stdscr.maxx, stdscr.maxy - 1, 0)
16
+ @searching_down = true
17
+ @str = ''
18
+ @last_query = ''
19
+
20
+ subscribe(Event::Screen::Resize, :resize)
21
+ end
22
+
23
+ def input
24
+ @str = ''
25
+ render_current_string
26
+
27
+ Curses.timeout = 10
28
+
29
+ chars = []
30
+
31
+ loop do
32
+ char = getch
33
+
34
+ if char.nil?
35
+ case chars.first
36
+ when 3, 27 # cancel with <C-c> / Esc
37
+ raise CancelInput
38
+ when 4 # cancel with <C-d> when query is empty
39
+ raise CancelInput if @str.empty?
40
+ when 10 # submit with <C-j>
41
+ @str = last_query.to_s if @str.empty?
42
+ break
43
+ when 127 # backspace
44
+ raise CancelInput if @str.empty?
45
+
46
+ @str.chop!
47
+ render_current_string
48
+ when 0..31
49
+ # ignore control codes (\x00 - \x1f)
50
+ else
51
+ next if chars.empty?
52
+ @str << chars
53
+ .map { |x| x.is_a?(String) ? x.ord : x }
54
+ .pack('c*')
55
+ .force_encoding('utf-8')
56
+ render_current_string
57
+ end
58
+
59
+ chars = []
60
+ else
61
+ chars << char
62
+ end
63
+ end
64
+
65
+ @last_query = @str unless @str.empty?
66
+ last_query
67
+ rescue CancelInput
68
+ @str = ''
69
+ clear
70
+ ensure
71
+ Curses.timeout = -1
72
+ end
73
+
74
+ def clear
75
+ window.clear
76
+ window.refresh
77
+ end
78
+
79
+ def render_last_query
80
+ render(last_query) unless last_query.empty?
81
+ end
82
+
83
+ def searching_backward!
84
+ @searching_forward = false
85
+ end
86
+
87
+ def searching_backward?
88
+ !@searching_forward
89
+ end
90
+
91
+ def searching_down!
92
+ @searching_down = true
93
+ end
94
+
95
+ def searching_down?
96
+ @searching_down
97
+ end
98
+
99
+ def searching_forward!
100
+ @searching_forward = true
101
+ end
102
+
103
+ def searching_forward?
104
+ @searching_forward
105
+ end
106
+
107
+ def searching_up!
108
+ @searching_down = false
109
+ end
110
+
111
+ def searching_up?
112
+ !@searching_down
113
+ end
114
+
115
+ private
116
+
117
+ attr_reader :window
118
+
119
+ def resize(event)
120
+ window.resize(1, stdscr.maxx)
121
+ window.move(stdscr.maxy - 1, 0)
122
+ end
123
+
124
+ def render(str)
125
+ window.clear
126
+ window.setpos(0, 0)
127
+ window.addstr("#{symbol}#{str}")
128
+ window.refresh
129
+ end
130
+
131
+ def render_current_string
132
+ render(@str)
133
+ end
134
+
135
+ def symbol
136
+ searching_down? ^ searching_forward? ? '?' : '/'
137
+ end
138
+ end
139
+ end