twterm 1.0.12 → 1.1.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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/lib/twterm.rb +15 -8
  3. data/lib/twterm/app.rb +2 -3
  4. data/lib/twterm/client.rb +218 -68
  5. data/lib/twterm/filterable_list.rb +2 -1
  6. data/lib/twterm/friendship.rb +124 -0
  7. data/lib/twterm/list.rb +5 -3
  8. data/lib/twterm/promise.rb +143 -0
  9. data/lib/twterm/screen.rb +0 -1
  10. data/lib/twterm/status.rb +15 -14
  11. data/lib/twterm/tab/base.rb +15 -1
  12. data/lib/twterm/tab/key_assignments_cheatsheet.rb +7 -19
  13. data/lib/twterm/tab/new/list.rb +9 -15
  14. data/lib/twterm/tab/new/search.rb +9 -17
  15. data/lib/twterm/tab/new/start.rb +90 -29
  16. data/lib/twterm/tab/new/user.rb +5 -7
  17. data/lib/twterm/tab/scrollable.rb +36 -4
  18. data/lib/twterm/tab/statuses/base.rb +240 -0
  19. data/lib/twterm/tab/statuses/conversation.rb +54 -0
  20. data/lib/twterm/tab/statuses/favorites.rb +45 -0
  21. data/lib/twterm/tab/statuses/home.rb +36 -0
  22. data/lib/twterm/tab/statuses/list_timeline.rb +47 -0
  23. data/lib/twterm/tab/statuses/mentions.rb +40 -0
  24. data/lib/twterm/tab/statuses/search.rb +42 -0
  25. data/lib/twterm/tab/statuses/user_timeline.rb +51 -0
  26. data/lib/twterm/tab/user_tab.rb +288 -19
  27. data/lib/twterm/tab/users/base.rb +90 -0
  28. data/lib/twterm/tab/users/followers.rb +41 -0
  29. data/lib/twterm/tab/users/friends.rb +41 -0
  30. data/lib/twterm/tab_manager.rb +13 -5
  31. data/lib/twterm/user.rb +67 -8
  32. data/lib/twterm/version.rb +1 -1
  33. data/spec/twterm/friendship_spec.rb +104 -0
  34. metadata +18 -11
  35. data/lib/twterm/tab/conversation_tab.rb +0 -49
  36. data/lib/twterm/tab/list_tab.rb +0 -44
  37. data/lib/twterm/tab/mentions_tab.rb +0 -36
  38. data/lib/twterm/tab/search_tab.rb +0 -40
  39. data/lib/twterm/tab/statuses_tab.rb +0 -251
  40. data/lib/twterm/tab/timeline_tab.rb +0 -31
  41. data/lib/twterm/user_window.rb +0 -71
@@ -12,7 +12,7 @@ module Twterm
12
12
  reset_filter
13
13
  Notifier.instance.show_error "No matches found: \"#{query}\""
14
14
  else
15
- Notifier.instance.show_message "#{total_item_count} statuses found: \"#{filter_query}\""
15
+ Notifier.instance.show_message "#{total_item_count} items found: \"#{filter_query}\""
16
16
  scroller.move_to_top
17
17
  end
18
18
 
@@ -30,6 +30,7 @@ module Twterm
30
30
  def reset_filter
31
31
  FilterQueryWindow.instance.clear
32
32
  @filter_query = ''
33
+ refresh
33
34
  end
34
35
  end
35
36
  end
@@ -0,0 +1,124 @@
1
+ module Twterm
2
+ class Friendship
3
+ STATUSES = %i(
4
+ blocking
5
+ following
6
+ following_requested
7
+ muting
8
+ ).freeze
9
+
10
+ attr_reader :status, :from, :to
11
+
12
+ @@instances = []
13
+ @@user_ids = Set.new
14
+
15
+ def initialize(status, from, to)
16
+ fail ArgumentError,
17
+ '' unless STATUSES.include? status
18
+
19
+ @status, @from, @to = status, from, to
20
+ @@instances << self
21
+ end
22
+
23
+ def blocking?
24
+ status?(:blocking)
25
+ end
26
+
27
+ def following?
28
+ status?(:following)
29
+ end
30
+
31
+ def following_requested?
32
+ status?(:following_requested)
33
+ end
34
+
35
+ def muting?
36
+ status?(:muting)
37
+ end
38
+
39
+ def self.already_looked_up?(user_id)
40
+ @@user_ids.include?(user_id)
41
+ end
42
+
43
+ def self.block(from, to)
44
+ new(:blocking, from, to)
45
+ end
46
+
47
+ def self.blocking?(from, to)
48
+ !find(:blocking, from, to).nil?
49
+ end
50
+
51
+ def self.cancel_follow_request(from, to)
52
+ new(:following_requested, from, to)
53
+ end
54
+
55
+ def self.delete(status, from, to)
56
+ cond = -> f { f.status == status && f.from == from && f.to == to }
57
+ @@instances.delete_if(&cond)
58
+ end
59
+ private_class_method :delete
60
+
61
+ def self.find(status, from, to)
62
+ cond = -> f { f.status == status && f.from == from && f.to == to }
63
+ @@instances.find(&cond)
64
+ end
65
+ private_class_method :find
66
+
67
+ def self.follow(from, to)
68
+ new(:following, from, to)
69
+ end
70
+
71
+ def self.following?(from, to)
72
+ !find(:following, from, to).nil?
73
+ end
74
+
75
+ def self.following_not_requested(from, to)
76
+ delete(:following_requested, from, to)
77
+ end
78
+
79
+ def self.following_requested(from, to)
80
+ new(:following_requested, from, to)
81
+ end
82
+
83
+ def self.following_requested?(from, to)
84
+ !find(:following_requested, from, to).nil?
85
+ end
86
+
87
+ def self.looked_up!(user_id)
88
+ @@user_ids << user_id
89
+ user_id
90
+ end
91
+
92
+ def self.mute(from, to)
93
+ new(:muting, from, to)
94
+ end
95
+
96
+ def self.muting?(from, to)
97
+ !find(:muting, from, to).nil?
98
+ end
99
+
100
+ def self.new(status, from, to)
101
+ instance = find(status, from, to)
102
+ instance.nil? ? super : instance
103
+ end
104
+ private_class_method :new
105
+
106
+ def self.unblock(from, to)
107
+ delete(:blocking, from, to)
108
+ end
109
+
110
+ def self.unfollow(from, to)
111
+ delete(:following, from, to)
112
+ end
113
+
114
+ def self.unmute(from, to)
115
+ delete(:muting, from, to)
116
+ end
117
+
118
+ private
119
+
120
+ def status?(status)
121
+ self.status == status
122
+ end
123
+ end
124
+ end
data/lib/twterm/list.rb CHANGED
@@ -41,10 +41,12 @@ module Twterm
41
41
  end
42
42
 
43
43
  def self.find_or_fetch(id)
44
- instance = find(id)
45
- (yield(instance) && return) if instance
44
+ Promise.new do |resolve, reject|
45
+ instance = find(id)
46
+ (resolve.(instance) && next) if instance
46
47
 
47
- Client.current.list(id) { |list| yield list }
48
+ Client.current.list(id).then { |list| resolve.(list) }
49
+ end
48
50
  end
49
51
 
50
52
  def self.new(list)
@@ -0,0 +1,143 @@
1
+ module Twterm
2
+ class Promise
3
+ def initialize
4
+ @state = :pending
5
+ @callbacks = []
6
+
7
+ Thread.new do
8
+ yield method(:resolve), method(:reject)
9
+ end if block_given?
10
+ end
11
+
12
+ def catch(on_rejected = nil, &block)
13
+ if on_rejected.is_a?(Proc) && block_given?
14
+ fail ArgumentError, 'both proc and block cannot be passed at the same time'
15
+ end
16
+
17
+ on_rejected ||= block
18
+ self.then(nil, on_rejected)
19
+ end
20
+
21
+ def reject(reason)
22
+ return if settled?
23
+
24
+ @state = :rejected
25
+ @reason = reason
26
+
27
+ callbacks.each do |cb|
28
+ cb.invoke_on_rejected(reason)
29
+ end
30
+ self
31
+ end
32
+
33
+ def rejected?
34
+ state.eql? :rejected
35
+ end
36
+
37
+ def resolve(value)
38
+ return if settled?
39
+
40
+ @state = :fulfilled
41
+ @value = value
42
+
43
+ callbacks.each do |cb|
44
+ cb.invoke_on_fulfilled(value)
45
+ end
46
+ self
47
+ end
48
+
49
+ def then(on_fulfilled = nil, on_rejected = nil, &block)
50
+ if (on_fulfilled.is_a?(Proc) || on_rejected.is_a?(Proc)) && block_given?
51
+ fail ArgumentError, 'both procs and block cannot be passed at the same time'
52
+ end
53
+
54
+ on_fulfilled ||= block
55
+ next_promise = Promise.new
56
+ callback = Callback.new(
57
+ self,
58
+ on_fulfilled.is_a?(Proc) ? on_fulfilled : nil,
59
+ on_rejected.is_a?(Proc) ? on_rejected : nil,
60
+ next_promise
61
+ )
62
+ @callbacks << callback
63
+
64
+ callback.invoke_on_fulfilled(@value) if fulfilled?
65
+ callback.invoke_on_rejected(@reason) if rejected?
66
+
67
+ next_promise
68
+ end
69
+
70
+ def self.resolve(value)
71
+ Promise.new { |resolve, _| resolve.(value) }
72
+ end
73
+
74
+ private
75
+
76
+ attr_reader :callbacks, :errorbacks, :next_promises, :state
77
+
78
+ def fulfilled?
79
+ state.eql? :fulfilled
80
+ end
81
+
82
+ def pending?
83
+ state.eql? :pending
84
+ end
85
+
86
+ def rejected?
87
+ state.eql? :rejected
88
+ end
89
+
90
+ def settled?
91
+ !pending?
92
+ end
93
+
94
+ class Callback
95
+ def initialize(promise, on_fulfilled, on_rejected, next_promise)
96
+ @promise = promise
97
+ @on_fulfilled, @on_rejected = on_fulfilled, on_rejected
98
+ @next_promise = next_promise
99
+ end
100
+
101
+ def has_on_fulfilled?
102
+ !on_fulfilled.nil?
103
+ end
104
+
105
+ def has_on_rejected?
106
+ !on_rejected.nil?
107
+ end
108
+
109
+ def invoke_on_fulfilled(value)
110
+ if has_on_fulfilled?
111
+ new_value = on_fulfilled.(value)
112
+ next_promise.resolve(new_value)
113
+ else
114
+ next_promise.resolve(value)
115
+ end
116
+ rescue => reason
117
+ next_promise.reject(reason)
118
+ end
119
+
120
+ def invoke_on_rejected(reason)
121
+ unless has_on_rejected?
122
+ if has_on_fulfilled?
123
+ next_promise.reject(reason)
124
+ else
125
+ raise reason unless has_on_fulfilled?
126
+ end
127
+ return
128
+ end
129
+
130
+ begin
131
+ new_value = on_rejected.(reason)
132
+ next_promise.resolve(new_value)
133
+ rescue => reason
134
+ next_promise.reject(reason)
135
+ end
136
+ end
137
+
138
+ private
139
+
140
+ attr_reader :promise, :on_fulfilled, :on_rejected, :next_promise
141
+ end
142
+ end
143
+ end
data/lib/twterm/screen.rb CHANGED
@@ -16,7 +16,6 @@ module Twterm
16
16
  def refresh
17
17
  TabManager.instance.refresh_window
18
18
  TabManager.instance.current_tab.refresh
19
- UserWindow.instance.refresh_window
20
19
  Notifier.instance.show
21
20
  end
22
21
 
data/lib/twterm/status.rb CHANGED
@@ -35,18 +35,17 @@ module Twterm
35
35
  end
36
36
 
37
37
  def in_reply_to_status(&block)
38
- if @in_reply_to_status_id.nil?
39
- block.call(nil)
40
- return
41
- end
38
+ Promise.new do |resolve, reject|
39
+ (resolve.(nil) && next) if in_reply_to_status_id.nil?
42
40
 
43
- status = Status.find(@in_reply_to_status_id)
44
- unless status.nil?
45
- block.call(status)
46
- return
47
- end
41
+ instance = Status.find(in_reply_to_status_id)
42
+ (resolve.(instance) && next) if instance
48
43
 
49
- Client.current.show_status(@in_reply_to_status_id, &block)
44
+ Client.current.show_status(in_reply_to_status_id)
45
+ .then do |status|
46
+ resolve.(status)
47
+ end
48
+ end
50
49
  end
51
50
 
52
51
  def initialize(tweet)
@@ -131,7 +130,7 @@ module Twterm
131
130
 
132
131
  def self.cleanup
133
132
  TabManager.instance.each_tab do |tab|
134
- tab.touch_statuses if tab.is_a?(Tab::StatusesTab)
133
+ tab.touch_statuses if tab.is_a?(Tab::Statuses::Base)
135
134
  end
136
135
  cond = -> (status) { status.touched_at > Time.now - MAX_CACHED_TIME }
137
136
  statuses = all.select(&cond)
@@ -144,10 +143,12 @@ module Twterm
144
143
  end
145
144
 
146
145
  def self.find_or_fetch(id)
147
- instance = find(id)
148
- (yield(instance) && return) if instance
146
+ Promise.new do |resolve, reject|
147
+ instance = find(id)
148
+ (resolve.(instance) && next) if instance
149
149
 
150
- Client.current.show_status(id) { |status| yield status }
150
+ Client.current.show_status(id) { |status| resolve.(status) }
151
+ end
151
152
  end
152
153
 
153
154
  def self.new(tweet)
@@ -15,13 +15,22 @@ module Twterm
15
15
  end
16
16
 
17
17
  def initialize
18
- @window = stdscr.subwin(stdscr.maxy - 5, stdscr.maxx - 30, 3, 0)
18
+ @window = stdscr.subwin(stdscr.maxy - 5, stdscr.maxx, 3, 0)
19
19
  end
20
20
 
21
21
  def refresh
22
22
  Thread.new do
23
23
  refresh_mutex.synchronize do
24
24
  window.clear
25
+
26
+ # avoid misalignment caused by some multibyte-characters
27
+ window.with_color(:black, :transparent) do
28
+ (0...window.maxy).each do |i|
29
+ window.setpos(i, 0)
30
+ window.addch(' ')
31
+ end
32
+ end
33
+
25
34
  update
26
35
  window.refresh
27
36
  end if refreshable?
@@ -32,6 +41,11 @@ module Twterm
32
41
  fail NotImplementedError, 'respond_to_key method must be implemented'
33
42
  end
34
43
 
44
+ def title=(title)
45
+ @title = title
46
+ TabManager.instance.refresh_window
47
+ end
48
+
35
49
  private
36
50
 
37
51
  def refresh_mutex
@@ -16,8 +16,7 @@ module Twterm
16
16
  '[j] [C-p] [DOWN]' => 'Move down',
17
17
  '[k] [C-n] [UP]' => 'Move up',
18
18
  '[u] [C-u]' => 'Scroll up',
19
- '[Q]' => 'Quit twterm',
20
- '[?]' => 'Open key assignments cheatsheet'
19
+ '[Q]' => 'Quit twterm'
21
20
  },
22
21
  'Tabs' => {
23
22
  '[h] [C-b] [LEFT]' => 'Show previous tab',
@@ -36,6 +35,10 @@ module Twterm
36
35
  '[r]' => 'Reply',
37
36
  '[R]' => 'Retweet',
38
37
  '[U]' => 'Show user'
38
+ },
39
+ 'Others' => {
40
+ '[P]' => 'View my profile',
41
+ '[?]' => 'Open key assignments cheatsheet'
39
42
  }
40
43
  }
41
44
 
@@ -49,24 +52,9 @@ module Twterm
49
52
  end
50
53
 
51
54
  def respond_to_key(key)
52
- case key
53
- when ?d, 4
54
- 10.times { scroller.move_down }
55
- when ?g
56
- scroller.move_to_top
57
- when ?G
58
- scroller.move_to_bottom
59
- when ?j, 14, Curses::Key::DOWN
60
- scroller.move_down
61
- when ?k, 16, Curses::Key::UP
62
- scroller.move_up
63
- when ?u, 21
64
- 10.times { scroller.move_up }
65
- else
66
- return false
67
- end
55
+ return true if scroller.respond_to_key(key)
68
56
 
69
- true
57
+ false
70
58
  end
71
59
 
72
60
  def title