twterm 2.6.0 → 2.10.1

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 (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