twterm 2.5.1 → 2.10.0

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 (67) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +62 -0
  3. data/.gitignore +1 -0
  4. data/Makefile +21 -0
  5. data/README.md +52 -6
  6. data/bin/twterm +1 -3
  7. data/default.nix +21 -0
  8. data/gemset.nix +4981 -0
  9. data/lib/twterm/app.rb +13 -26
  10. data/lib/twterm/client.rb +1 -6
  11. data/lib/twterm/color_manager.rb +8 -9
  12. data/lib/twterm/image.rb +31 -0
  13. data/lib/twterm/image/attr.rb +42 -0
  14. data/lib/twterm/image/bold.rb +7 -21
  15. data/lib/twterm/image/color.rb +9 -15
  16. data/lib/twterm/image/dim.rb +13 -0
  17. data/lib/twterm/image/underlined.rb +17 -0
  18. data/lib/twterm/image_builder/user_name_image_builder.rb +1 -0
  19. data/lib/twterm/key_mapper.rb +6 -8
  20. data/lib/twterm/key_mapper/abstract_key_mapper.rb +2 -2
  21. data/lib/twterm/list.rb +36 -1
  22. data/lib/twterm/message_window.rb +4 -13
  23. data/lib/twterm/persistable_configuration_proxy.rb +6 -6
  24. data/lib/twterm/preferences.rb +8 -1
  25. data/lib/twterm/rest_client.rb +0 -33
  26. data/lib/twterm/screen.rb +103 -25
  27. data/lib/twterm/search_query_window.rb +5 -13
  28. data/lib/twterm/status.rb +10 -0
  29. data/lib/twterm/tab/abstract_tab.rb +30 -16
  30. data/lib/twterm/tab/new/index.rb +0 -10
  31. data/lib/twterm/tab/new/search.rb +2 -2
  32. data/lib/twterm/tab/new/user.rb +2 -2
  33. data/lib/twterm/tab/preferences/control.rb +77 -0
  34. data/lib/twterm/tab/preferences/index.rb +6 -0
  35. data/lib/twterm/tab/scrollable.rb +1 -1
  36. data/lib/twterm/tab/searchable.rb +6 -4
  37. data/lib/twterm/tab/status_tab.rb +10 -0
  38. data/lib/twterm/tab/statuses/abstract_statuses_tab.rb +11 -6
  39. data/lib/twterm/tab/statuses/home.rb +0 -3
  40. data/lib/twterm/tab/statuses/mentions.rb +1 -0
  41. data/lib/twterm/tab/user_tab.rb +0 -9
  42. data/lib/twterm/tab_manager.rb +77 -10
  43. data/lib/twterm/tweetbox.rb +2 -3
  44. data/lib/twterm/user.rb +1 -0
  45. data/lib/twterm/version.rb +1 -1
  46. data/nix/Gemfile +3 -0
  47. data/nix/Gemfile.lock +77 -0
  48. data/nix/gemset.nix +325 -0
  49. data/shell.nix +40 -0
  50. data/spec/twterm/image/bold_spec.rb +30 -0
  51. data/spec/twterm/image/color_spec.rb +16 -0
  52. data/spec/twterm/image/dim_spec.rb +30 -0
  53. data/twterm.gemspec +13 -13
  54. metadata +46 -46
  55. data/.travis.yml +0 -12
  56. data/lib/twterm/direct_message.rb +0 -60
  57. data/lib/twterm/direct_message_composer.rb +0 -80
  58. data/lib/twterm/direct_message_manager.rb +0 -51
  59. data/lib/twterm/event/direct_message/fetched.rb +0 -10
  60. data/lib/twterm/event/notification/direct_message.rb +0 -30
  61. data/lib/twterm/event/screen/resize.rb +0 -13
  62. data/lib/twterm/event/status/timeline.rb +0 -10
  63. data/lib/twterm/repository/direct_message_repository.rb +0 -14
  64. data/lib/twterm/streaming_client.rb +0 -146
  65. data/lib/twterm/tab/direct_message/conversation.rb +0 -104
  66. data/lib/twterm/tab/direct_message/conversation_list.rb +0 -100
  67. data/spec/twterm/event/screen/resize_spec.rb +0 -11
@@ -1,26 +1,31 @@
1
1
  require 'concurrent'
2
2
 
3
- require 'twterm/event/screen/resize'
4
3
  require 'twterm/image'
5
4
  require 'twterm/subscriber'
6
5
 
7
6
  module Twterm
8
7
  module Tab
9
8
  class AbstractTab
10
- include Curses
11
9
  include Subscriber
12
10
 
13
- attr_reader :window, :title
11
+ # @return [String]
12
+ attr_reader :title
14
13
 
14
+ # @param other [Twterm::Tab::AbstractTab]
15
+ #
16
+ # @return [Boolean]
15
17
  def ==(other)
16
18
  self.equal?(other)
17
19
  end
18
20
 
21
+ # @return [void]
19
22
  def close
20
23
  unsubscribe
21
- window.close
22
24
  end
23
25
 
26
+ # A utility method to find a status by its ID
27
+ #
28
+ # @return [Concurrent::Promise<Twterm::Status>]
24
29
  def find_or_fetch_status(id)
25
30
  status = app.status_repository.find(id)
26
31
 
@@ -31,6 +36,9 @@ module Twterm
31
36
  end
32
37
  end
33
38
 
39
+ # A utility method to find a list by their ID
40
+ #
41
+ # @return [Concurrent::Promise<Twterm::List>]
34
42
  def find_or_fetch_list(id)
35
43
  list = app.list_repository.find(id)
36
44
 
@@ -41,6 +49,9 @@ module Twterm
41
49
  end
42
50
  end
43
51
 
52
+ # A utility method to find a user by their id
53
+ #
54
+ # @return [Concurrent::Promise<Twterm::User>]
44
55
  def find_or_fetch_user(id)
45
56
  user = app.user_repository.find(id)
46
57
 
@@ -53,10 +64,6 @@ module Twterm
53
64
 
54
65
  def initialize(app, client)
55
66
  @app, @client = app, client
56
-
57
- @window = stdscr.subwin(stdscr.maxy - 5, stdscr.maxx, 3, 0)
58
-
59
- subscribe(Event::Screen::Resize, :resize)
60
67
  end
61
68
 
62
69
  def render
@@ -72,7 +79,7 @@ module Twterm
72
79
  end
73
80
  end
74
81
 
75
- view.at(1, 2).render
82
+ view.render
76
83
  end if refreshable?
77
84
  end
78
85
  end
@@ -88,8 +95,13 @@ module Twterm
88
95
 
89
96
  private
90
97
 
91
- attr_reader :app, :client
98
+ # @return [Twterm::App]
99
+ attr_reader :app
92
100
 
101
+ # @return [Twterm::Client]
102
+ attr_reader :client
103
+
104
+ # @return [Twterm::Image]
93
105
  def image
94
106
  Image.string('view method is not implemented')
95
107
  end
@@ -98,22 +110,24 @@ module Twterm
98
110
  @refresh_mutex ||= Mutex.new
99
111
  end
100
112
 
113
+ # @return [Boolean]
101
114
  def refreshable?
102
115
  !(
103
116
  refresh_mutex.locked? ||
104
- closed? ||
117
+ Curses.closed? ||
105
118
  app.tab_manager.current_tab.object_id != object_id
106
119
  )
107
120
  end
108
121
 
109
- def resize(_event)
110
- window.resize(stdscr.maxy - 5, stdscr.maxx)
111
- window.move(3, 0)
112
- end
113
-
122
+ # @return [Twterm::View]
114
123
  def view
115
124
  View.new(window, image)
116
125
  end
126
+
127
+ # @todo This method is for transition. `window` should explicitly be obtained on initialization.
128
+ def window
129
+ app.screen.tab_window
130
+ end
117
131
  end
118
132
  end
119
133
  end
@@ -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
@@ -226,16 +226,16 @@ module Twterm
226
226
 
227
227
  header = [
228
228
  ImageBuilder::UserNameImageBuilder.new(user).build,
229
- Image.string(original.date.to_s).brackets,
229
+ Image.string(original.date.to_s).brackets.dim,
230
230
  (Image.whitespace.color(:black, :red) if original.favorited?),
231
231
  (Image.whitespace.color(:black, :green) if original.retweeted?),
232
- ((Image.string('retweeted by ') - !Image.string("@#{retweeted_by.screen_name}")).parens if status.retweet?),
232
+ ((Image.string('retweeted by ') - !Image.string("@#{retweeted_by.screen_name}")).parens.dim if status.retweet?),
233
233
  ((Image.number(original.favorite_count) - Image.plural(original.favorite_count, 'like')).color(:red) if original.favorite_count.positive?),
234
234
  ((Image.number(original.retweet_count) - Image.plural(original.retweet_count, 'RT')).color(:green) if original.retweet_count.positive?),
235
235
  ].compact.intersperse(Image.whitespace).reduce(Image.empty, :-)
236
236
 
237
237
  body = original
238
- .split(window.maxx - 4)
238
+ .split(window.maxx - 2)
239
239
  .map(&Image.method(:string))
240
240
  .reduce(Image.empty, :|)
241
241
 
@@ -263,6 +263,11 @@ module Twterm
263
263
  end
264
264
  end
265
265
 
266
+ # for the sake of Twterm::Tab::Searchable
267
+ def search_query_window
268
+ app.search_query_window
269
+ end
270
+
266
271
  def sort
267
272
  return if items.empty? || scroller.current_item.nil?
268
273
 
@@ -1,5 +1,4 @@
1
1
  require 'twterm/subscriber'
2
- require 'twterm/event/status/timeline'
3
2
  require 'twterm/tab/statuses/abstract_statuses_tab'
4
3
  require 'twterm/utils'
5
4
 
@@ -21,8 +20,6 @@ module Twterm
21
20
  def initialize(app, client)
22
21
  super(app, client)
23
22
 
24
- subscribe(Event::Status::Timeline) { |e| prepend(e.status) }
25
-
26
23
  reload.then do
27
24
  initially_loaded!
28
25
  scroller.move_to_top