twterm 2.5.0 → 2.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +62 -0
  3. data/.gitignore +1 -0
  4. data/Makefile +18 -0
  5. data/README.md +30 -4
  6. data/bin/twterm +1 -3
  7. data/default.nix +21 -0
  8. data/gemset.nix +5130 -0
  9. data/lib/twterm/app.rb +0 -18
  10. data/lib/twterm/client.rb +1 -6
  11. data/lib/twterm/color_manager.rb +8 -9
  12. data/lib/twterm/image.rb +26 -0
  13. data/lib/twterm/image/underlined.rb +31 -0
  14. data/lib/twterm/image_builder/user_name_image_builder.rb +1 -0
  15. data/lib/twterm/key_mapper.rb +6 -8
  16. data/lib/twterm/key_mapper/abstract_key_mapper.rb +2 -2
  17. data/lib/twterm/list.rb +36 -1
  18. data/lib/twterm/message_window.rb +4 -5
  19. data/lib/twterm/persistable_configuration_proxy.rb +6 -6
  20. data/lib/twterm/preferences.rb +8 -1
  21. data/lib/twterm/rest_client.rb +0 -33
  22. data/lib/twterm/screen.rb +54 -14
  23. data/lib/twterm/search_query_window.rb +4 -5
  24. data/lib/twterm/status.rb +10 -0
  25. data/lib/twterm/tab/abstract_tab.rb +33 -8
  26. data/lib/twterm/tab/new/index.rb +0 -10
  27. data/lib/twterm/tab/new/list.rb +3 -3
  28. data/lib/twterm/tab/new/search.rb +2 -2
  29. data/lib/twterm/tab/new/user.rb +2 -2
  30. data/lib/twterm/tab/preferences/control.rb +77 -0
  31. data/lib/twterm/tab/preferences/index.rb +6 -0
  32. data/lib/twterm/tab/status_tab.rb +10 -0
  33. data/lib/twterm/tab/statuses/abstract_statuses_tab.rb +4 -4
  34. data/lib/twterm/tab/statuses/home.rb +0 -3
  35. data/lib/twterm/tab/statuses/mentions.rb +1 -0
  36. data/lib/twterm/tab/user_tab.rb +0 -9
  37. data/lib/twterm/tab_manager.rb +77 -5
  38. data/lib/twterm/tweetbox.rb +2 -3
  39. data/lib/twterm/user.rb +1 -0
  40. data/lib/twterm/version.rb +1 -1
  41. data/nix/Gemfile +3 -0
  42. data/nix/Gemfile.lock +70 -0
  43. data/nix/gemset.nix +283 -0
  44. data/shell.nix +40 -0
  45. data/twterm.gemspec +13 -13
  46. metadata +42 -44
  47. data/.travis.yml +0 -12
  48. data/lib/twterm/direct_message.rb +0 -60
  49. data/lib/twterm/direct_message_composer.rb +0 -80
  50. data/lib/twterm/direct_message_manager.rb +0 -51
  51. data/lib/twterm/event/direct_message/fetched.rb +0 -10
  52. data/lib/twterm/event/notification/direct_message.rb +0 -30
  53. data/lib/twterm/event/status/timeline.rb +0 -10
  54. data/lib/twterm/repository/direct_message_repository.rb +0 -14
  55. data/lib/twterm/streaming_client.rb +0 -146
  56. data/lib/twterm/tab/direct_message/conversation.rb +0 -104
  57. data/lib/twterm/tab/direct_message/conversation_list.rb +0 -100
data/lib/twterm/screen.rb CHANGED
@@ -6,18 +6,18 @@ require 'twterm/subscriber'
6
6
  module Twterm
7
7
  class Screen
8
8
  include Subscriber
9
- include Curses
10
9
 
11
10
  def initialize(app, client)
12
11
  @app, @client = app, client
13
12
 
14
- @screen = init_screen
15
- noecho
16
- raw
17
- curs_set(0)
18
- stdscr.keypad(true)
19
- start_color
20
- use_default_colors
13
+ @screen = Curses.init_screen
14
+ Curses.noecho
15
+ Curses.raw
16
+ Curses.curs_set(0)
17
+ Curses.stdscr.keypad(true)
18
+ Curses.start_color
19
+ Curses.use_default_colors
20
+ Curses.mousemask(Curses::BUTTON1_CLICKED | 65536 | 2097152)
21
21
 
22
22
  subscribe(Event::Screen::Refresh) { refresh }
23
23
  subscribe(Event::Screen::Resize, :resize)
@@ -53,6 +53,42 @@ module Twterm
53
53
 
54
54
  attr_reader :app, :client
55
55
 
56
+ # @param [Integer, String] key
57
+ def handle_keyboard_event(key)
58
+ return if app.tab_manager.current_tab.respond_to_key(key)
59
+ return if app.tab_manager.respond_to_key(key)
60
+ respond_to_key(key)
61
+ end
62
+
63
+ # @param [Curses::MouseEvent] e
64
+ def handle_mouse_event(e)
65
+ x = e.x
66
+ y = e.y
67
+
68
+ case e.bstate
69
+ when Curses::BUTTON1_CLICKED
70
+ return app.tab_manager.handle_left_click(x, y) if app.tab_manager.enclose?(x, y)
71
+ when 65536
72
+ scroll_direction = app.preferences[:control, :scroll_direction]
73
+
74
+ case scroll_direction
75
+ when 'natural'
76
+ return app.tab_manager.handle_scroll_up(x, y) if app.tab_manager.enclose?(x, y)
77
+ when 'traditional'
78
+ return app.tab_manager.handle_scroll_down(x, y) if app.tab_manager.enclose?(x, y)
79
+ end
80
+ when 2097152
81
+ scroll_direction = app.preferences[:control, :scroll_direction]
82
+
83
+ case scroll_direction
84
+ when 'natural'
85
+ return app.tab_manager.handle_scroll_down(x, y) if app.tab_manager.enclose?(x, y)
86
+ when 'traditional'
87
+ return app.tab_manager.handle_scroll_up(x, y) if app.tab_manager.enclose?(x, y)
88
+ end
89
+ end
90
+ end
91
+
56
92
  def refresh
57
93
  app.tab_manager.refresh_window
58
94
  app.tab_manager.current_tab.render
@@ -60,10 +96,10 @@ module Twterm
60
96
  end
61
97
 
62
98
  def resize(event)
63
- return if closed?
99
+ return if Curses.closed?
64
100
 
65
101
  lines, cols = event.lines, event.cols
66
- resizeterm(lines, cols)
102
+ Curses.resizeterm(lines, cols)
67
103
  @screen.resize(lines, cols)
68
104
 
69
105
  refresh
@@ -72,11 +108,15 @@ module Twterm
72
108
  def scan
73
109
  app.reset_interruption_handler
74
110
 
75
- key = getch
111
+ key = Curses.getch
76
112
 
77
- return if app.tab_manager.current_tab.respond_to_key(key)
78
- return if app.tab_manager.respond_to_key(key)
79
- respond_to_key(key)
113
+ if key == Curses::Key::MOUSE
114
+ e = Curses.getmouse
115
+
116
+ handle_mouse_event(e) unless e.nil?
117
+ else
118
+ handle_keyboard_event(key)
119
+ end
80
120
  end
81
121
  end
82
122
  end
@@ -3,7 +3,6 @@ require 'twterm/subscriber'
3
3
 
4
4
  module Twterm
5
5
  class SearchQueryWindow
6
- include Curses
7
6
  include Singleton
8
7
  include Subscriber
9
8
 
@@ -12,7 +11,7 @@ module Twterm
12
11
  attr_reader :last_query
13
12
 
14
13
  def initialize
15
- @window = stdscr.subwin(1, stdscr.maxx, stdscr.maxy - 1, 0)
14
+ @window = Curses.stdscr.subwin(1, Curses.stdscr.maxx, Curses.stdscr.maxy - 1, 0)
16
15
  @searching_down = true
17
16
  @str = ''
18
17
  @last_query = ''
@@ -29,7 +28,7 @@ module Twterm
29
28
  chars = []
30
29
 
31
30
  loop do
32
- char = getch
31
+ char = Curses.getch
33
32
 
34
33
  if char.nil?
35
34
  case chars.first
@@ -117,8 +116,8 @@ module Twterm
117
116
  attr_reader :window
118
117
 
119
118
  def resize(_event)
120
- window.resize(1, stdscr.maxx)
121
- window.move(stdscr.maxy - 1, 0)
119
+ window.resize(1, Curses.stdscr.maxx)
120
+ window.move(Curses.stdscr.maxy - 1, 0)
122
121
  end
123
122
 
124
123
  def render(str)
data/lib/twterm/status.rb CHANGED
@@ -12,6 +12,7 @@ class Twitter::Tweet
12
12
  end
13
13
 
14
14
  module Twterm
15
+ # A tweet
15
16
  class Status
16
17
  attr_reader :created_at, :favorite_count, :favorited, :hashtags, :id,
17
18
  :in_reply_to_status_id, :media, :retweet_count, :retweeted,
@@ -23,11 +24,13 @@ module Twterm
23
24
  other.is_a?(self.class) && id == other.id
24
25
  end
25
26
 
27
+ # @todo This should be done in a presenter
26
28
  def date
27
29
  format = Time.now - @created_at < 86_400 ? '%H:%M:%S' : '%Y-%m-%d %H:%M:%S'
28
30
  @created_at.strftime(format)
29
31
  end
30
32
 
33
+ # @todo This can be marked as private
31
34
  def expand_url!
32
35
  sub = -> (x) { @text.sub!(x.url, x.display_url) }
33
36
  (@media + @urls).each(&sub)
@@ -69,10 +72,16 @@ module Twterm
69
72
  expand_url!
70
73
  end
71
74
 
75
+ # Is this status a quote?
76
+ #
77
+ # @return [Boolean]
72
78
  def quote?
73
79
  !quoted_status_id.nil?
74
80
  end
75
81
 
82
+ # Is this status a retweet?
83
+ #
84
+ # @return [Boolean]
76
85
  def retweet?
77
86
  !retweeted_status_id.nil?
78
87
  end
@@ -93,6 +102,7 @@ module Twterm
93
102
  @retweeted = false
94
103
  end
95
104
 
105
+ # @return [self]
96
106
  def update!(tweet, is_retweeted_status = false)
97
107
  @retweet_count = tweet.retweet_count
98
108
  @favorite_count = tweet.favorite_count
@@ -7,20 +7,31 @@ require 'twterm/subscriber'
7
7
  module Twterm
8
8
  module Tab
9
9
  class AbstractTab
10
- include Curses
11
10
  include Subscriber
12
11
 
13
- attr_reader :window, :title
12
+ # @return [String]
13
+ attr_reader :title
14
14
 
15
+ # @return [Curses::Window]
16
+ # @todo This can be (and should be) private
17
+ attr_reader :window
18
+
19
+ # @param other [Twterm::Tab::AbstractTab]
20
+ #
21
+ # @return [Boolean]
15
22
  def ==(other)
16
23
  self.equal?(other)
17
24
  end
18
25
 
26
+ # @return [void]
19
27
  def close
20
28
  unsubscribe
21
29
  window.close
22
30
  end
23
31
 
32
+ # A utility method to find a status by its ID
33
+ #
34
+ # @return [Concurrent::Promise<Twterm::Status>]
24
35
  def find_or_fetch_status(id)
25
36
  status = app.status_repository.find(id)
26
37
 
@@ -31,6 +42,9 @@ module Twterm
31
42
  end
32
43
  end
33
44
 
45
+ # A utility method to find a list by their ID
46
+ #
47
+ # @return [Concurrent::Promise<Twterm::List>]
34
48
  def find_or_fetch_list(id)
35
49
  list = app.list_repository.find(id)
36
50
 
@@ -41,6 +55,9 @@ module Twterm
41
55
  end
42
56
  end
43
57
 
58
+ # A utility method to find a user by their id
59
+ #
60
+ # @return [Concurrent::Promise<Twterm::User>]
44
61
  def find_or_fetch_user(id)
45
62
  user = app.user_repository.find(id)
46
63
 
@@ -54,7 +71,7 @@ module Twterm
54
71
  def initialize(app, client)
55
72
  @app, @client = app, client
56
73
 
57
- @window = stdscr.subwin(stdscr.maxy - 5, stdscr.maxx, 3, 0)
74
+ @window = Curses.stdscr.subwin(Curses.stdscr.maxy - 3, Curses.stdscr.maxx, 2, 0)
58
75
 
59
76
  subscribe(Event::Screen::Resize, :resize)
60
77
  end
@@ -72,7 +89,7 @@ module Twterm
72
89
  end
73
90
  end
74
91
 
75
- view.at(1, 2).render
92
+ view.render
76
93
  end if refreshable?
77
94
  end
78
95
  end
@@ -88,8 +105,13 @@ module Twterm
88
105
 
89
106
  private
90
107
 
91
- attr_reader :app, :client
108
+ # @return [Twterm::App]
109
+ attr_reader :app
110
+
111
+ # @return [Twterm::Client]
112
+ attr_reader :client
92
113
 
114
+ # @return [Twterm::Image]
93
115
  def image
94
116
  Image.string('view method is not implemented')
95
117
  end
@@ -98,19 +120,22 @@ module Twterm
98
120
  @refresh_mutex ||= Mutex.new
99
121
  end
100
122
 
123
+ # @return [Boolean]
101
124
  def refreshable?
102
125
  !(
103
126
  refresh_mutex.locked? ||
104
- closed? ||
127
+ Curses.closed? ||
105
128
  app.tab_manager.current_tab.object_id != object_id
106
129
  )
107
130
  end
108
131
 
132
+ # @return [void]
109
133
  def resize(_event)
110
- window.resize(stdscr.maxy - 5, stdscr.maxx)
111
- window.move(3, 0)
134
+ window.resize(Curses.stdscr.maxy - 3, Curses.stdscr.maxx)
135
+ window.move(2, 0)
112
136
  end
113
137
 
138
+ # @return [Twterm::View]
114
139
  def view
115
140
  View.new(window, image)
116
141
  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
@@ -33,10 +33,10 @@ module Twterm
33
33
  @@lists || []
34
34
  end
35
35
 
36
- def matches?(_list, query)
36
+ def matches?(list, query)
37
37
  [
38
- other.description,
39
- other.full_name,
38
+ list.description,
39
+ list.full_name,
40
40
  ].any? { |x| x.downcase.include?(query.downcase) }
41
41
  end
42
42
 
@@ -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