twterm 1.0.9 → 1.0.10

Sign up to get free protection for your applications and to get access to all the features.
data/lib/twterm/list.rb CHANGED
@@ -4,6 +4,10 @@ module Twterm
4
4
 
5
5
  @@instances = {}
6
6
 
7
+ def ==(other)
8
+ other.is_a?(self.class) && id == other.id
9
+ end
10
+
7
11
  def initialize(list)
8
12
  @id = list.id
9
13
  update!(list)
@@ -24,13 +28,8 @@ module Twterm
24
28
  self
25
29
  end
26
30
 
27
- def ==(other)
28
- other.is_a?(self.class) && id == other.id
29
- end
30
-
31
- def self.new(list)
32
- instance = find(list.id)
33
- instance.nil? ? super : instance.update!(list)
31
+ def self.all
32
+ @@instances.values
34
33
  end
35
34
 
36
35
  def self.find(id)
@@ -44,8 +43,9 @@ module Twterm
44
43
  Client.current.list(id) { |list| yield list }
45
44
  end
46
45
 
47
- def self.all
48
- @@instances.values
46
+ def self.new(list)
47
+ instance = find(list.id)
48
+ instance.nil? ? super : instance.update!(list)
49
49
  end
50
50
  end
51
51
  end
@@ -4,9 +4,8 @@ module Twterm
4
4
  include Curses
5
5
 
6
6
  def initialize
7
- @window = stdscr.subwin(2, stdscr.maxx, stdscr.maxy - 2, 0)
7
+ @window = stdscr.subwin(1, stdscr.maxx, stdscr.maxy - 2, 0)
8
8
  @queue = Queue.new
9
- @help = ''
10
9
 
11
10
  Thread.new do
12
11
  while notification = @queue.pop
@@ -17,23 +16,16 @@ module Twterm
17
16
  end
18
17
  end
19
18
 
20
- def show_message(message)
21
- notification = Notification::Message.new(message)
22
- @queue.push(notification)
23
- self
24
- end
25
-
26
19
  def show_error(message)
27
20
  notification = Notification::Error.new(message)
28
21
  @queue.push(notification)
29
22
  self
30
23
  end
31
24
 
32
- def show_help(message)
33
- return if @help == message
34
-
35
- @help = message
36
- show
25
+ def show_message(message)
26
+ notification = Notification::Message.new(message)
27
+ @queue.push(notification)
28
+ self
37
29
  end
38
30
 
39
31
  def show(notification = nil)
@@ -46,22 +38,15 @@ module Twterm
46
38
 
47
39
  if notification.is_a? Notification::Base
48
40
  @window.with_color(notification.fg_color, notification.bg_color) do
49
- @window.setpos(1, 0)
41
+ @window.setpos(0, 0)
50
42
  @window.addstr(' ' * @window.maxx)
51
- @window.setpos(1, 1)
43
+ @window.setpos(0, 1)
52
44
  time = notification.time.strftime('[%H:%M:%S]')
53
45
  message = notification.show_with_width(@window.maxx)
54
46
  @window.addstr("#{time} #{message}")
55
47
  end
56
48
  end
57
49
 
58
- @window.with_color(:black, :green) do
59
- @window.setpos(0, 0)
60
- @window.addstr(' ' * @window.maxx)
61
- @window.setpos(0, 1)
62
- @window.addstr(@help)
63
- end
64
-
65
50
  @window.refresh
66
51
  end
67
52
  end
@@ -7,10 +7,7 @@ class Scheduler
7
7
  @paused = false
8
8
 
9
9
  @thread = Thread.new do
10
- loop do
11
- sleep @interval
12
- run
13
- end
10
+ loop { sleep(@interval) && run }
14
11
  end
15
12
  end
16
13
 
@@ -22,11 +19,11 @@ class Scheduler
22
19
  @paused = true
23
20
  end
24
21
 
25
- def run
26
- @block.call unless @paused
22
+ def resume
23
+ @paused = false
27
24
  end
28
25
 
29
- def unpause
30
- @paused = false
26
+ def run
27
+ @block.call unless @paused
31
28
  end
32
29
  end
data/lib/twterm/screen.rb CHANGED
@@ -13,15 +13,6 @@ module Twterm
13
13
  use_default_colors
14
14
  end
15
15
 
16
- def wait
17
- @thread = Thread.new do
18
- loop do
19
- scan
20
- end
21
- end
22
- @thread.join
23
- end
24
-
25
16
  def refresh
26
17
  TabManager.instance.refresh_window
27
18
  TabManager.instance.current_tab.refresh
@@ -29,6 +20,30 @@ module Twterm
29
20
  Notifier.instance.show
30
21
  end
31
22
 
23
+ def respond_to_key(key)
24
+ case key
25
+ when ?n
26
+ Tweetbox.instance.compose
27
+ return
28
+ when ?Q
29
+ App.instance.quit
30
+ when ??
31
+ tab = Tab::KeyAssignmentsCheatsheet.new
32
+ TabManager.instance.add_and_show tab
33
+ else
34
+ return false
35
+ end
36
+
37
+ true
38
+ end
39
+
40
+ def wait
41
+ @thread = Thread.new do
42
+ loop { scan }
43
+ end
44
+ @thread.join
45
+ end
46
+
32
47
  private
33
48
 
34
49
  def scan
@@ -38,17 +53,7 @@ module Twterm
38
53
 
39
54
  return if TabManager.instance.current_tab.respond_to_key(key)
40
55
  return if TabManager.instance.respond_to_key(key)
41
-
42
- case key
43
- when 'n'
44
- Tweetbox.instance.compose
45
- return
46
- when 'Q'
47
- App.instance.quit
48
- when '/'
49
- # filter
50
- else
51
- end
56
+ respond_to_key(key)
52
57
  end
53
58
  end
54
59
  end
data/lib/twterm/status.rb CHANGED
@@ -2,21 +2,57 @@ module Twterm
2
2
  class Status
3
3
  MAX_CACHED_TIME = 3600
4
4
 
5
- attr_reader :id, :text, :created_at, :created_at_for_sort, :retweet_count, :favorite_count, :in_reply_to_status_id, :favorited, :retweeted, :user_id, :retweeted_by_user_id, :urls, :media, :touched_at
5
+ attr_reader :appeared_at, :created_at, :favorite_count, :favorited, :id,
6
+ :in_reply_to_status_id, :media, :retweet_count, :retweeted,
7
+ :retweeted_by_user_id, :text, :touched_at, :urls, :user_id
6
8
  alias_method :favorited?, :favorited
7
9
  alias_method :retweeted?, :retweeted
8
10
 
9
11
  @@instances = {}
10
12
 
11
- def self.new(tweet)
12
- instance = find(tweet.id)
13
- instance.nil? ? super : instance.update!(tweet)
13
+ def ==(other)
14
+ other.is_a?(self.class) && id == other.id
15
+ end
16
+
17
+ def date
18
+ format = Time.now - @created_at < 86_400 ? '%H:%M:%S' : '%Y-%m-%d %H:%M:%S'
19
+ @created_at.strftime(format)
20
+ end
21
+
22
+ def expand_url!
23
+ sub = -> (x) { @text.sub!(x.url, x.display_url) }
24
+ (@media + @urls).each(&sub)
25
+ end
26
+
27
+ def favorite!
28
+ @favorite_count += 1
29
+ @favorited = true
30
+ end
31
+
32
+ def grepped_with?(query)
33
+ [text, user.screen_name, user.name]
34
+ .any? { |x| x.downcase.include?(query.downcase) }
35
+ end
36
+
37
+ def in_reply_to_status(&block)
38
+ if @in_reply_to_status_id.nil?
39
+ block.call(nil)
40
+ return
41
+ end
42
+
43
+ status = Status.find(@in_reply_to_status_id)
44
+ unless status.nil?
45
+ block.call(status)
46
+ return
47
+ end
48
+
49
+ Client.current.show_status(@in_reply_to_status_id, &block)
14
50
  end
15
51
 
16
52
  def initialize(tweet)
17
53
  unless tweet.retweeted_status.is_a? Twitter::NullObject
18
54
  @retweeted_by_user_id = tweet.user.id
19
- User.create(tweet.user)
55
+ User.new(tweet.user)
20
56
  retweeted_at = Status.parse_time(tweet.created_at)
21
57
  tweet = tweet.retweeted_status
22
58
  end
@@ -24,7 +60,7 @@ module Twterm
24
60
  @id = tweet.id
25
61
  @text = CGI.unescapeHTML(tweet.full_text.dup)
26
62
  @created_at = Status.parse_time(tweet.created_at)
27
- @created_at_for_sort = retweeted_at || @created_at
63
+ @appeared_at = retweeted_at || @created_at
28
64
  @retweet_count = tweet.retweet_count
29
65
  @favorite_count = tweet.favorite_count
30
66
  @in_reply_to_status_id = tweet.in_reply_to_status_id
@@ -36,7 +72,7 @@ module Twterm
36
72
  @urls = tweet.urls
37
73
 
38
74
  @user_id = tweet.user.id
39
- User.create(tweet.user)
75
+ User.new(tweet.user)
40
76
 
41
77
  @splitted_text = {}
42
78
 
@@ -51,32 +87,8 @@ module Twterm
51
87
  @@instances[id] = self
52
88
  end
53
89
 
54
- def update!(tweet)
55
- @retweet_count = tweet.retweet_count
56
- @favorite_count = tweet.favorite_count
57
- @retweeted = tweet.retweeted?
58
- @favorited = tweet.favorited?
59
- self
60
- end
61
-
62
- def date
63
- format = Time.now - @created_at < 86_400 ? '%H:%M:%S' : '%Y-%m-%d %H:%M:%S'
64
- @created_at.strftime(format)
65
- end
66
-
67
- def expand_url!
68
- sub = -> (x) { @text.sub!(x.url, x.display_url) }
69
- (@media + @urls).each(&sub)
70
- end
71
-
72
- def favorite!
73
- @favorite_count += 1
74
- @favorited = true
75
- end
76
-
77
- def unfavorite!
78
- @favorite_count -= 1
79
- @favorited = false
90
+ def replies
91
+ Status.all.select { |s| s.in_reply_to_status_id == id }
80
92
  end
81
93
 
82
94
  def retweet!
@@ -84,74 +96,67 @@ module Twterm
84
96
  @retweeted = true
85
97
  end
86
98
 
87
- def split(width)
88
- @splitted_text[:width] ||= @text.split_by_width(width)
99
+ def retweeted_by
100
+ User.find(@retweeted_by_user_id)
89
101
  end
90
102
 
91
- def in_reply_to_status(&block)
92
- if @in_reply_to_status_id.nil?
93
- block.call(nil)
94
- return
95
- end
96
-
97
- status = Status.find(@in_reply_to_status_id)
98
- unless status.nil?
99
- block.call(status)
100
- return
101
- end
102
-
103
- Client.current.show_status(@in_reply_to_status_id, &block)
103
+ def split(width)
104
+ @splitted_text[:width] ||= @text.split_by_width(width)
104
105
  end
105
106
 
106
- def replies
107
- Status.all.select { |s| s.in_reply_to_status_id == id }
107
+ def touch!
108
+ @touched_at = Time.now
108
109
  end
109
110
 
110
- def retweeted_by
111
- User.find(@retweeted_by_user_id)
111
+ def unfavorite!
112
+ @favorite_count -= 1
113
+ @favorited = false
112
114
  end
113
115
 
114
- def touch!
115
- @touched_at = Time.now
116
+ def update!(tweet)
117
+ @retweet_count = tweet.retweet_count
118
+ @favorite_count = tweet.favorite_count
119
+ @retweeted = tweet.retweeted?
120
+ @favorited = tweet.favorited?
121
+ self
116
122
  end
117
123
 
118
124
  def user
119
125
  User.find(user_id)
120
126
  end
121
127
 
122
- def ==(other)
123
- other.is_a?(self.class) && id == other.id
128
+ def self.all
129
+ @@instances.values
124
130
  end
125
131
 
126
- class << self
127
- def all
128
- @@instances.values
132
+ def self.cleanup
133
+ TabManager.instance.each_tab do |tab|
134
+ tab.touch_statuses if tab.is_a?(Tab::StatusesTab)
129
135
  end
136
+ cond = -> (status) { status.touched_at > Time.now - MAX_CACHED_TIME }
137
+ statuses = all.select(&cond)
138
+ status_ids = statuses.map(&:id)
139
+ @@instances = status_ids.zip(statuses).to_h
140
+ end
130
141
 
131
- def find(id)
132
- @@instances[id]
133
- end
142
+ def self.find(id)
143
+ @@instances[id]
144
+ end
134
145
 
135
- def find_or_fetch(id)
136
- instance = find(id)
137
- (yield(instance) && return) if instance
146
+ def self.find_or_fetch(id)
147
+ instance = find(id)
148
+ (yield(instance) && return) if instance
138
149
 
139
- Client.current.show_status(id) { |status| yield status }
140
- end
150
+ Client.current.show_status(id) { |status| yield status }
151
+ end
141
152
 
142
- def parse_time(time)
143
- (time.is_a?(String) ? Time.parse(time) : time.dup).localtime
144
- end
153
+ def self.new(tweet)
154
+ instance = find(tweet.id)
155
+ instance.nil? ? super : instance.update!(tweet)
156
+ end
145
157
 
146
- def cleanup
147
- TabManager.instance.each_tab do |tab|
148
- tab.touch_statuses if tab.is_a?(Tab::StatusesTab)
149
- end
150
- cond = -> (status) { status.touched_at > Time.now - MAX_CACHED_TIME }
151
- statuses = all.select(&cond)
152
- status_ids = statuses.map(&:id)
153
- @@instances = status_ids.zip(statuses).to_h
154
- end
158
+ def self.parse_time(time)
159
+ (time.is_a?(String) ? Time.parse(time) : time.dup).localtime
155
160
  end
156
161
  end
157
162
  end
@@ -3,35 +3,50 @@ module Twterm
3
3
  module Base
4
4
  include Curses
5
5
 
6
+ attr_reader :window
6
7
  attr_accessor :title
7
8
 
9
+ def ==(other)
10
+ self.equal?(other)
11
+ end
12
+
13
+ def close
14
+ window.close
15
+ end
16
+
8
17
  def initialize
9
18
  @window = stdscr.subwin(stdscr.maxy - 5, stdscr.maxx - 30, 3, 0)
10
19
  end
11
20
 
12
21
  def refresh
13
- return if @refreshing || closed? || TabManager.instance.current_tab.object_id != object_id
22
+ return unless refreshable?
14
23
 
15
- @refreshing = true
16
24
  Thread.new do
17
- update
18
- @refreshing = false
25
+ refresh_mutex.synchronize do
26
+ window.clear
27
+ update
28
+ window.refresh
29
+ end
19
30
  end
20
31
  end
21
32
 
22
- def close
23
- @window.close
24
- end
25
-
26
33
  def respond_to_key(_)
27
34
  fail NotImplementedError, 'respond_to_key method must be implemented'
28
35
  end
29
36
 
30
- def ==(other)
31
- self.equal?(other)
37
+ private
38
+
39
+ def refresh_mutex
40
+ @refresh_mutex ||= Mutex.new
32
41
  end
33
42
 
34
- private
43
+ def refreshable?
44
+ !(
45
+ refresh_mutex.locked? ||
46
+ closed? ||
47
+ TabManager.instance.current_tab.object_id != object_id
48
+ )
49
+ end
35
50
 
36
51
  def update
37
52
  fail NotImplementedError, 'update method must be implemented'
@@ -6,18 +6,8 @@ module Twterm
6
6
 
7
7
  attr_reader :status
8
8
 
9
- def initialize(status_id)
10
- @title = 'Conversation'
11
- super()
12
-
13
- Status.find_or_fetch(status_id) do |status|
14
- @status = status
15
-
16
- append(status)
17
- move_to_top
18
- Thread.new { fetch_in_reply_to_status(status) }
19
- Thread.new { fetch_replies(status) }
20
- end
9
+ def ==(other)
10
+ other.is_a?(self.class) && status == other.status
21
11
  end
22
12
 
23
13
  def fetch_in_reply_to_status(status)
@@ -37,13 +27,23 @@ module Twterm
37
27
  end
38
28
  end
39
29
 
40
- def ==(other)
41
- other.is_a?(self.class) && status == other.status
42
- end
43
-
44
30
  def dump
45
31
  @status.id
46
32
  end
33
+
34
+ def initialize(status_id)
35
+ @title = 'Conversation'
36
+ super()
37
+
38
+ Status.find_or_fetch(status_id) do |status|
39
+ @status = status
40
+
41
+ append(status)
42
+ scroll_manager.move_to_top
43
+ Thread.new { fetch_in_reply_to_status(status) }
44
+ Thread.new { fetch_replies(status) }
45
+ end
46
+ end
47
47
  end
48
48
  end
49
49
  end
@@ -0,0 +1,113 @@
1
+ module Twterm
2
+ module Tab
3
+ class KeyAssignmentsCheatsheet
4
+ include Base
5
+
6
+ def ==(other)
7
+ other.is_a?(self.class)
8
+ end
9
+
10
+ SHORTCUTS = {
11
+ 'General' => {
12
+ '[d] [C-d]' => 'Scroll down',
13
+ '[g]' => 'Move to top',
14
+ '[G]' => 'Move to bottom',
15
+ '[j] [C-p] [DOWN]' => 'Move down',
16
+ '[k] [C-n] [UP]' => 'Move up',
17
+ '[u] [C-u]' => 'Scroll up',
18
+ '[Q]' => 'Quit twterm',
19
+ '[?]' => 'Open key assignments cheatsheet'
20
+ },
21
+ 'Tabs' => {
22
+ '[h] [C-b] [LEFT]' => 'Show previous tab',
23
+ '[l] [C-f] [RIGHT]' => 'Show next tab',
24
+ '[N]' => 'Open new tab',
25
+ '[C-R]' => 'Reload',
26
+ '[w]' => 'Close tab',
27
+ '[q]' => 'Quit filtering mode',
28
+ '[/]' => 'Filter items in tab'
29
+ },
30
+ 'Tweets' => {
31
+ '[D]' => 'Delete tweet',
32
+ '[F]' => 'Add to favorite',
33
+ '[n]' => 'Compose new tweet',
34
+ '[o]' => 'Open URLs in tweet',
35
+ '[r]' => 'Reply',
36
+ '[R]' => 'Retweet',
37
+ '[U]' => 'Show user'
38
+ }
39
+ }
40
+
41
+ def respond_to_key(key)
42
+ case key
43
+ when ?d, 4
44
+ 10.times { scroll_manager.move_down }
45
+ when ?g
46
+ scroll_manager.move_to_top
47
+ when ?G
48
+ scroll_manager.move_to_bottom
49
+ when ?j, 14, Curses::Key::DOWN
50
+ scroll_manager.move_down
51
+ when ?k, 16, Curses::Key::UP
52
+ scroll_manager.move_up
53
+ when ?u, 21
54
+ 10.times { scroll_manager.move_up }
55
+ else
56
+ return false
57
+ end
58
+
59
+ true
60
+ end
61
+
62
+ def title
63
+ 'Key assignments'.freeze
64
+ end
65
+
66
+ def update
67
+ top = 2 # begin drawing from line 2
68
+ draw_cond = -> line { top <= line && line <= window.maxy - top }
69
+
70
+ current_line = top - scroll_manager.offset
71
+
72
+ window.setpos(current_line, 3)
73
+ window.bold { window.addstr('Key assignments') } if draw_cond[current_line]
74
+
75
+ SHORTCUTS.each do |category, shortcuts|
76
+ current_line += 3
77
+ window.setpos(current_line, 5)
78
+ window.bold { window.addstr("<#{category}>") } if draw_cond[current_line]
79
+ current_line += 1
80
+
81
+ shortcuts.each do |key, description|
82
+ current_line += 1
83
+ next unless draw_cond[current_line]
84
+
85
+ window.setpos(current_line, 7)
86
+ window.bold { window.addstr(key.rjust(17)) }
87
+ window.setpos(current_line, 25)
88
+ window.addstr(": #{description}")
89
+ end
90
+ end
91
+ end
92
+
93
+ private
94
+
95
+ def count
96
+ @count ||= SHORTCUTS.count * 4 + SHORTCUTS.map(&:count).reduce(0, :+) + 1
97
+ end
98
+
99
+ def offset_from_bottom
100
+ 0
101
+ end
102
+
103
+ def scroll_manager
104
+ return @scroll_manager unless @scroll_manager.nil?
105
+
106
+ @scroll_manager = ScrollManager.new
107
+ @scroll_manager.delegate = self
108
+ @scroll_manager.after_move { refresh }
109
+ @scroll_manager
110
+ end
111
+ end
112
+ end
113
+ end
@@ -13,7 +13,7 @@ module Twterm
13
13
  @list = list
14
14
  @title = @list.full_name
15
15
  TabManager.instance.refresh_window
16
- fetch { move_to_top }
16
+ fetch { scroll_manager.move_to_top }
17
17
  @auto_reloader = Scheduler.new(300) { fetch }
18
18
  end
19
19
  end
@@ -3,6 +3,18 @@ module Twterm
3
3
  class MentionsTab
4
4
  include StatusesTab
5
5
 
6
+ def close
7
+ fail NotClosableError
8
+ end
9
+
10
+ def fetch
11
+ @client.mentions do |statuses|
12
+ statuses.reverse.each(&method(:prepend))
13
+ sort
14
+ yield if block_given?
15
+ end
16
+ end
17
+
6
18
  def initialize(client)
7
19
  fail ArgumentError, 'argument must be an instance of Client class' unless client.is_a? Client
8
20
 
@@ -16,21 +28,9 @@ module Twterm
16
28
 
17
29
  @title = 'Mentions'
18
30
 
19
- fetch { move_to_top }
31
+ fetch { scroll_manager.move_to_top }
20
32
  @auto_reloader = Scheduler.new(300) { fetch }
21
33
  end
22
-
23
- def fetch
24
- @client.mentions do |statuses|
25
- statuses.reverse.each(&method(:prepend))
26
- sort
27
- yield if block_given?
28
- end
29
- end
30
-
31
- def close
32
- fail NotClosableError
33
- end
34
34
  end
35
35
  end
36
36
  end