twterm 2.6.0 → 2.10.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +62 -0
  3. data/.gitignore +1 -0
  4. data/Gemfile +7 -0
  5. data/Makefile +21 -0
  6. data/README.md +52 -6
  7. data/bin/twterm +1 -3
  8. data/default.nix +21 -0
  9. data/gemset.nix +5130 -0
  10. data/lib/twterm/app.rb +13 -24
  11. data/lib/twterm/client.rb +1 -4
  12. data/lib/twterm/color_manager.rb +8 -9
  13. data/lib/twterm/image.rb +31 -0
  14. data/lib/twterm/image/attr.rb +42 -0
  15. data/lib/twterm/image/bold.rb +7 -21
  16. data/lib/twterm/image/color.rb +9 -15
  17. data/lib/twterm/image/dim.rb +13 -0
  18. data/lib/twterm/image/underlined.rb +17 -0
  19. data/lib/twterm/image_builder/user_name_image_builder.rb +1 -0
  20. data/lib/twterm/key_mapper.rb +6 -8
  21. data/lib/twterm/key_mapper/abstract_key_mapper.rb +2 -2
  22. data/lib/twterm/list.rb +36 -1
  23. data/lib/twterm/message_window.rb +4 -13
  24. data/lib/twterm/persistable_configuration_proxy.rb +6 -6
  25. data/lib/twterm/preferences.rb +8 -1
  26. data/lib/twterm/rest_client.rb +0 -33
  27. data/lib/twterm/screen.rb +103 -25
  28. data/lib/twterm/search_query_window.rb +5 -13
  29. data/lib/twterm/status.rb +10 -0
  30. data/lib/twterm/tab/abstract_tab.rb +30 -16
  31. data/lib/twterm/tab/new/index.rb +0 -10
  32. data/lib/twterm/tab/new/search.rb +2 -2
  33. data/lib/twterm/tab/new/user.rb +2 -2
  34. data/lib/twterm/tab/preferences/control.rb +77 -0
  35. data/lib/twterm/tab/preferences/index.rb +6 -0
  36. data/lib/twterm/tab/scrollable.rb +1 -1
  37. data/lib/twterm/tab/searchable.rb +6 -4
  38. data/lib/twterm/tab/status_tab.rb +10 -0
  39. data/lib/twterm/tab/statuses/abstract_statuses_tab.rb +11 -6
  40. data/lib/twterm/tab/user_tab.rb +0 -9
  41. data/lib/twterm/tab_manager.rb +77 -10
  42. data/lib/twterm/tweetbox.rb +2 -3
  43. data/lib/twterm/user.rb +1 -0
  44. data/lib/twterm/version.rb +1 -1
  45. data/nix/Gemfile +3 -0
  46. data/nix/Gemfile.lock +77 -0
  47. data/nix/gemset.nix +325 -0
  48. data/shell.nix +40 -0
  49. data/spec/twterm/image/bold_spec.rb +30 -0
  50. data/spec/twterm/image/color_spec.rb +16 -0
  51. data/spec/twterm/image/dim_spec.rb +30 -0
  52. data/twterm.gemspec +7 -14
  53. metadata +31 -113
  54. data/.travis.yml +0 -12
  55. data/lib/twterm/direct_message.rb +0 -60
  56. data/lib/twterm/direct_message_composer.rb +0 -80
  57. data/lib/twterm/direct_message_manager.rb +0 -51
  58. data/lib/twterm/event/direct_message/fetched.rb +0 -10
  59. data/lib/twterm/event/notification/direct_message.rb +0 -30
  60. data/lib/twterm/event/screen/resize.rb +0 -13
  61. data/lib/twterm/repository/direct_message_repository.rb +0 -14
  62. data/lib/twterm/tab/direct_message/conversation.rb +0 -104
  63. data/lib/twterm/tab/direct_message/conversation_list.rb +0 -100
  64. data/spec/twterm/event/screen/resize_spec.rb +0 -11
@@ -1,5 +1,4 @@
1
1
  require 'twterm/tab/abstract_tab'
2
- require 'twterm/tab/direct_message/conversation_list'
3
2
  require 'twterm/tab/rate_limit_status'
4
3
  require 'twterm/tab/preferences/index'
5
4
 
@@ -19,7 +18,6 @@ module Twterm
19
18
 
20
19
  def items
21
20
  %i(
22
- direct_messages
23
21
  list_tab
24
22
  search_tab
25
23
  user_tab
@@ -60,8 +58,6 @@ module Twterm
60
58
 
61
59
  desc =
62
60
  case item
63
- when :direct_messages
64
- 'Direct messages'
65
61
  when :list_tab
66
62
  'List tab'
67
63
  when :search_tab
@@ -82,10 +78,6 @@ module Twterm
82
78
  .reduce(Image.empty, :|)
83
79
  end
84
80
 
85
- def open_direct_messages
86
- switch(Tab::DirectMessage::ConversationList.new(app, client))
87
- end
88
-
89
81
  def open_list_tab
90
82
  switch(Tab::New::List.new(app, client))
91
83
  end
@@ -114,8 +106,6 @@ module Twterm
114
106
 
115
107
  def perform_selected_action
116
108
  case current_item
117
- when :direct_messages
118
- open_direct_messages
119
109
  when :list_tab
120
110
  open_list_tab
121
111
  when :search_tab
@@ -32,13 +32,13 @@ module Twterm
32
32
 
33
33
  def invoke_input
34
34
  resetter = proc do
35
- reset_prog_mode
35
+ Curses.reset_prog_mode
36
36
  sleep 0.1
37
37
  publish(Event::Screen::Refresh.new)
38
38
  end
39
39
 
40
40
  input_thread = Thread.new do
41
- close_screen
41
+ Curses.close_screen
42
42
  app.completion_manager.set_search_mode!
43
43
  puts "\ninput search query"
44
44
  query = (readline('> ', true) || '').strip
@@ -16,7 +16,7 @@ module Twterm
16
16
 
17
17
  def invoke_input
18
18
  resetter = proc do
19
- reset_prog_mode
19
+ Curses.reset_prog_mode
20
20
  sleep 0.1
21
21
  publish(Event::Screen::Refresh.new)
22
22
  end
@@ -24,7 +24,7 @@ module Twterm
24
24
  app.completion_manager.set_screen_name_mode!
25
25
 
26
26
  input_thread = Thread.new do
27
- close_screen
27
+ Curses.close_screen
28
28
  puts "\nSearch user"
29
29
  screen_name = (readline('> @', true) || '').strip
30
30
  resetter.call
@@ -0,0 +1,77 @@
1
+ require 'twterm/image'
2
+ require 'twterm/preferences'
3
+ require 'twterm/publisher'
4
+ require 'twterm/tab/abstract_tab'
5
+ require 'twterm/tab/scrollable'
6
+
7
+ module Twterm
8
+ module Tab
9
+ module Preferences
10
+ class Control < AbstractTab
11
+ include Scrollable
12
+ include Publisher
13
+
14
+ def drawable_item_count
15
+ 1
16
+ end
17
+
18
+ def image
19
+ drawable_items.map.with_index do |item, i|
20
+ curr = scroller.current_index?(i)
21
+ cursor = Image.cursor(2, curr)
22
+ options = Image.toggle_switch(['traditional', 'natural'], app.preferences[:control, item])
23
+ desc =
24
+ case item
25
+ when :scroll_direction
26
+ header = Image.string('Scroll direction')
27
+ body = Image.string(' ') - options
28
+ cursor - Image.whitespace - (header | body)
29
+ end
30
+ end
31
+ .intersperse(Image.blank_line)
32
+ .reduce(Image.empty) { |acc, x| acc | x }
33
+ end
34
+
35
+ def items
36
+ [
37
+ :scroll_direction,
38
+ ]
39
+ end
40
+
41
+ def respond_to_key(key)
42
+ return true if scroller.respond_to_key(key)
43
+
44
+ case key
45
+ when 10
46
+ perform_selected_action
47
+ end
48
+
49
+ false
50
+ end
51
+
52
+ def title
53
+ 'Control preferences'
54
+ end
55
+
56
+ private
57
+
58
+ def perform_selected_action
59
+ item = scroller.current_item
60
+
61
+ case item
62
+ when :scroll_direction
63
+ app.preferences[:control, :scroll_direction] =
64
+ case app.preferences[:control, :scroll_direction]
65
+ when 'natural'
66
+ 'traditional'
67
+ when 'traditional'
68
+ 'natural'
69
+ end
70
+ end
71
+
72
+ render
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -1,4 +1,5 @@
1
1
  require 'twterm/image'
2
+ require 'twterm/tab/preferences/control'
2
3
  require 'twterm/tab/preferences/notification_backend'
3
4
  require 'twterm/tab/preferences/photo_viewer_backend'
4
5
  require 'twterm/preferences'
@@ -26,6 +27,8 @@ module Twterm
26
27
  cursor = Image.cursor(1, curr)
27
28
  desc =
28
29
  case item
30
+ when :control
31
+ 'Control preferences'
29
32
  when :notification_backend
30
33
  'Notification backend preferences'
31
34
  when :photo_viewer_backend
@@ -40,6 +43,7 @@ module Twterm
40
43
 
41
44
  def items
42
45
  [
46
+ :control,
43
47
  :notification_backend,
44
48
  :photo_viewer_backend,
45
49
  ]
@@ -67,6 +71,8 @@ module Twterm
67
71
  def open
68
72
  tab =
69
73
  case scroller.current_item
74
+ when :control
75
+ Tab::Preferences::Control.new(app, client)
70
76
  when :notification_backend
71
77
  Tab::Preferences::NotificationBackend.new(app, client)
72
78
  when :photo_viewer_backend
@@ -27,7 +27,7 @@ module Twterm
27
27
  attr_reader :index, :offset
28
28
 
29
29
  attr_accessor :delegate
30
- def_delegators :delegate, :items, :total_item_count, :drawable_item_count
30
+ def_delegators :delegate, :items, :total_item_count, :drawable_item_count, :search_query_window
31
31
 
32
32
  def after_move(&block)
33
33
  add_hook(:after_move, &block)
@@ -14,6 +14,12 @@ module Twterm
14
14
  raise NotImplementedError, '`matches?` method must be implemented'
15
15
  end
16
16
 
17
+ # @abstract
18
+ # @return [Twterm::SearchQueryWindow]
19
+ def search_query_window
20
+ raise NotImplementedError, '`search_query_window` method must be implemented'
21
+ end
22
+
17
23
  class Scroller < Scrollable::Scroller
18
24
  extend Forwardable
19
25
  include Publisher
@@ -122,10 +128,6 @@ module Twterm
122
128
  end
123
129
  end
124
130
 
125
- def search_query_window
126
- SearchQueryWindow.instance
127
- end
128
-
129
131
  alias_method :count, :total_item_count
130
132
  end
131
133
  end
@@ -67,6 +67,7 @@ module Twterm
67
67
 
68
68
  def items
69
69
  [
70
+ :show_conversation,
70
71
  :reply,
71
72
  :favorite,
72
73
  :retweet,
@@ -135,6 +136,8 @@ module Twterm
135
136
  Image.string('Quote this tweet')
136
137
  when :destroy
137
138
  Image.string('Delete this tweet')
139
+ when :show_conversation
140
+ Image.string("Show conversation")
138
141
  when :show_user
139
142
  Image.string("Show user (@#{user.screen_name})")
140
143
  when :open_in_browser
@@ -172,6 +175,8 @@ module Twterm
172
175
  quote!
173
176
  when :destroy
174
177
  destroy!
178
+ when :show_conversation
179
+ show_conversation!
175
180
  when :show_user
176
181
  show_user!
177
182
  when :open_in_browser
@@ -209,6 +214,11 @@ module Twterm
209
214
  .then { render }
210
215
  end
211
216
 
217
+ def show_conversation!
218
+ tab = Tab::Statuses::Conversation.new(app, client, status_id)
219
+ app.tab_manager.add_and_show(tab)
220
+ end
221
+
212
222
  def show_user!
213
223
  user_id = status.user_id
214
224
  user_tab = Tab::UserTab.new(app, client, user_id)
@@ -27,7 +27,7 @@ module Twterm
27
27
  return if @status_ids.include?(status.id)
28
28
 
29
29
  @status_ids.push(status.id)
30
- status.split(window.maxx - 4)
30
+ status.split(window.maxx - 2)
31
31
  scroller.item_appended!
32
32
  render
33
33
  end
@@ -46,7 +46,7 @@ module Twterm
46
46
 
47
47
  def drawable_item_count
48
48
  statuses.drop(scroller.offset).lazy
49
- .map { |s| s.split(window.maxx - 4).count + 2 }
49
+ .map { |s| s.split(window.maxx - 2).count + 2 }
50
50
  .scan(0, :+)
51
51
  .each_cons(2)
52
52
  .select { |_, l| l < window.maxy }
@@ -113,7 +113,7 @@ module Twterm
113
113
  return if @status_ids.include?(status.id)
114
114
 
115
115
  @status_ids.unshift(status.id)
116
- status.split(window.maxx - 4)
116
+ status.split(window.maxx - 2)
117
117
  scroller.item_prepended!
118
118
  render
119
119
  end
@@ -177,6 +177,11 @@ module Twterm
177
177
  .then { render }
178
178
  end
179
179
 
180
+ # for the sake of Twterm::Tab::Searchable
181
+ def search_query_window
182
+ app.search_query_window
183
+ end
184
+
180
185
  def show_conversation
181
186
  status = highlighted_original_status
182
187
 
@@ -226,16 +231,16 @@ module Twterm
226
231
 
227
232
  header = [
228
233
  ImageBuilder::UserNameImageBuilder.new(user).build,
229
- Image.string(original.date.to_s).brackets,
234
+ Image.string(original.date.to_s).brackets.dim,
230
235
  (Image.whitespace.color(:black, :red) if original.favorited?),
231
236
  (Image.whitespace.color(:black, :green) if original.retweeted?),
232
- ((Image.string('retweeted by ') - !Image.string("@#{retweeted_by.screen_name}")).parens if status.retweet?),
237
+ ((Image.string('retweeted by ') - !Image.string("@#{retweeted_by.screen_name}")).parens.dim if status.retweet?),
233
238
  ((Image.number(original.favorite_count) - Image.plural(original.favorite_count, 'like')).color(:red) if original.favorite_count.positive?),
234
239
  ((Image.number(original.retweet_count) - Image.plural(original.retweet_count, 'RT')).color(:green) if original.retweet_count.positive?),
235
240
  ].compact.intersperse(Image.whitespace).reduce(Image.empty, :-)
236
241
 
237
242
  body = original
238
- .split(window.maxx - 4)
243
+ .split(window.maxx - 2)
239
244
  .map(&Image.method(:string))
240
245
  .reduce(Image.empty, :|)
241
246
 
@@ -53,7 +53,6 @@ module Twterm
53
53
  :profile_image,
54
54
  (:profile_background_image unless user.profile_background_image.nil?),
55
55
  :manage_lists,
56
- (:compose_direct_message unless myself?),
57
56
  (:open_website unless user.website.nil?),
58
57
  (:toggle_follow unless myself?),
59
58
  (:toggle_mute unless myself?),
@@ -96,10 +95,6 @@ module Twterm
96
95
  app.friendship_repository.blocking?(client.user_id, user_id)
97
96
  end
98
97
 
99
- def compose_direct_message
100
- app.direct_message_composer.compose(user)
101
- end
102
-
103
98
  def follow
104
99
  client.follow(user_id).then do |users|
105
100
  render
@@ -176,8 +171,6 @@ module Twterm
176
171
 
177
172
  def perform_selected_action
178
173
  case scroller.current_item
179
- when :compose_direct_message
180
- compose_direct_message
181
174
  when :manage_lists
182
175
  open_list_management_tab
183
176
  when :open_in_browser
@@ -294,8 +287,6 @@ module Twterm
294
287
  curr = scroller.current_index?(i)
295
288
  Image.cursor(1, curr) - Image.whitespace -
296
289
  case item
297
- when :compose_direct_message
298
- Image.string('Compose direct message')
299
290
  when :toggle_block
300
291
  if blocking?
301
292
  Image.string('Unblock this user')
@@ -1,4 +1,3 @@
1
- require 'twterm/event/screen/resize'
2
1
  require 'twterm/publisher'
3
2
  require 'twterm/subscriber'
4
3
  require 'twterm/utils'
@@ -6,7 +5,6 @@ require 'twterm/view'
6
5
 
7
6
  module Twterm
8
7
  class TabManager
9
- include Curses
10
8
  include Publisher
11
9
  include Subscriber
12
10
  include Utils
@@ -69,16 +67,70 @@ module Twterm
69
67
  end
70
68
  end
71
69
 
72
- def initialize(app, client)
70
+ # Returns if the given coordinate is enclosed by the window
71
+ #
72
+ # @param x [Integer]
73
+ # @param y [Integer]
74
+ # @return [Boolean]
75
+ def enclose?(x, y)
76
+ left = @window.begx
77
+ top = @window.begy
78
+ right = left + @window.maxx
79
+ bottom = top + @window.maxy
80
+
81
+ left <= x && x < right && top <= y && y < bottom
82
+ end
83
+
84
+ # @param app [Twterm::App]
85
+ # @param client [Twterm::Client]
86
+ # @param window [Curses::Window]
87
+ def initialize(app, client, window)
73
88
  @app, @client = app, client
74
89
 
75
90
  @tabs = []
76
91
  @index = 0
77
92
  @history = []
78
93
 
79
- @window = stdscr.subwin(3, stdscr.maxx, 0, 0)
94
+ @window = window
95
+ end
96
+
97
+ # Open the clicked tab
98
+ #
99
+ # @param x [Integer]
100
+ # @param _y [Integer]
101
+ #
102
+ # @return [nil]
103
+ def handle_left_click(x, _y)
104
+ n = find_tab_index_on_x(x)
105
+ return if n.nil?
106
+
107
+ show_nth_tab(n)
108
+
109
+ nil
110
+ end
111
+
112
+ # Open next tab
113
+ #
114
+ # @param _x [Integer]
115
+ # @param _y [Integer]
116
+ #
117
+ # @return [nil]
118
+ def handle_scroll_down(_x, _y)
119
+ show_next
80
120
 
81
- subscribe(Event::Screen::Resize, :resize)
121
+ nil
122
+ end
123
+
124
+ # Open previous tab
125
+ #
126
+ # @param _x [Integer]
127
+ # @param _y [Integer]
128
+ #
129
+ # @return [nil]
130
+ def handle_scroll_up(_x, _y)
131
+ show_previous
132
+
133
+ nil
82
134
  end
83
135
 
84
136
  def open_my_profile
@@ -120,10 +172,10 @@ module Twterm
120
172
 
121
173
  image = @tabs
122
174
  .map { |t| [t, Image.string(t.title)] }
123
- .map { |t, r| t.equal?(current_tab) ? !r : r }
175
+ .map { |t, r| t.equal?(current_tab) ? !r._ : r }
124
176
  .reduce(pipe) { |acc, x| acc - wss - x - wss - pipe }
125
177
 
126
- View.new(@window, image).at(1, 1)
178
+ View.new(@window, image)
127
179
  end
128
180
 
129
181
  def respond_to_key(key)
@@ -195,9 +247,24 @@ module Twterm
195
247
 
196
248
  attr_reader :app, :client
197
249
 
198
- def resize(_event)
199
- @window.resize(3, stdscr.maxx)
200
- @window.move(0, 0)
250
+ # @param x [Integer]
251
+ #
252
+ # @return [Integer, nil]
253
+ def find_tab_index_on_x(x)
254
+ pos = 0
255
+
256
+ @tabs.each.with_index do |tab, index|
257
+ title = tab.title
258
+ len = title.length
259
+ left = pos + 1
260
+ right = left + len + 4 # each tab has 2 whitespaces on the both side
261
+
262
+ return index if left <= x && x < right
263
+
264
+ pos = right
265
+ end
266
+
267
+ nil
201
268
  end
202
269
  end
203
270
  end