twterm 1.0.9 → 1.0.10

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.
@@ -2,7 +2,73 @@ module Twterm
2
2
  module Tab
3
3
  module StatusesTab
4
4
  include Base
5
- include Scrollable
5
+
6
+ def append(status)
7
+ fail ArgumentError,
8
+ 'argument must be an instance of Status class' unless status.is_a? Status
9
+
10
+ return if @status_ids.include?(status.id)
11
+
12
+ @status_ids.unshift(status.id)
13
+ status.split(window.maxx - 4)
14
+ status.touch!
15
+ scroll_manager.item_appended!
16
+ refresh
17
+ end
18
+
19
+ def count
20
+ grep_query.empty? ? @status_ids.count : statuses.count
21
+ end
22
+
23
+ def delete(status_id)
24
+ @status_ids.delete(status_id)
25
+ refresh
26
+ end
27
+
28
+ def destroy_status
29
+ status = highlighted_status
30
+
31
+ Client.current.destroy_status(status) do
32
+ delete(status.id)
33
+ refresh
34
+ end
35
+ end
36
+
37
+ def favorite
38
+ return if highlighted_status.nil?
39
+
40
+ method_name = highlighted_status.favorited ? :unfavorite : :favorite
41
+ Client.current.method(method_name).call(highlighted_status) { refresh }
42
+ end
43
+
44
+ def fetch
45
+ fail NotImplementedError, 'fetch method must be implemented'
46
+ end
47
+
48
+ def grep
49
+ reset_grep
50
+
51
+ Curses.echo
52
+ Curses.setpos(stdscr.maxy - 1, 0)
53
+ Curses.stdscr.addch '/'
54
+ @grep_query = Curses.getstr.chomp
55
+ Curses.noecho
56
+
57
+ if grep_query.empty?
58
+ reset_grep
59
+ elsif count == 0
60
+ Notifier.instance.show_error "No matches found: \"#{grep_query}\""
61
+ reset_grep
62
+ else
63
+ Notifier.instance.show_message "#{count} statuses found: \"#{grep_query}\""
64
+ scroll_manager.move_to_top
65
+ refresh
66
+ end
67
+ end
68
+
69
+ def grep_query
70
+ @grep_query || ''
71
+ end
6
72
 
7
73
  def initialize
8
74
  super
@@ -10,10 +76,12 @@ module Twterm
10
76
  @status_ids = []
11
77
  end
12
78
 
13
- def statuses
14
- statuses = @status_ids.map { |id| Status.find(id) }.reject(&:nil?)
15
- @status_ids = statuses.map(&:id)
16
- statuses
79
+ def open_link
80
+ return if highlighted_status.nil?
81
+
82
+ status = highlighted_status
83
+ urls = status.urls.map(&:expanded_url) + status.media.map(&:expanded_url)
84
+ urls.each(&Launchy.method(:open))
17
85
  end
18
86
 
19
87
  def prepend(status)
@@ -22,21 +90,9 @@ module Twterm
22
90
  return if @status_ids.include?(status.id)
23
91
 
24
92
  @status_ids << status.id
25
- status.split(@window.maxx - 4)
26
- status.touch!
27
- item_prepended
28
- refresh
29
- end
30
-
31
- def append(status)
32
- fail ArgumentError, 'argument must be an instance of Status class' unless status.is_a? Status
33
-
34
- return if @status_ids.include?(status.id)
35
-
36
- @status_ids.unshift(status.id)
37
- status.split(@window.maxx - 4)
93
+ status.split(window.maxx - 4)
38
94
  status.touch!
39
- item_appended
95
+ scroll_manager.item_prepended!
40
96
  refresh
41
97
  end
42
98
 
@@ -45,17 +101,53 @@ module Twterm
45
101
  Tweetbox.instance.compose(highlighted_status)
46
102
  end
47
103
 
48
- def favorite
49
- return if highlighted_status.nil?
50
- if highlighted_status.favorited?
51
- Client.current.unfavorite(highlighted_status) do
52
- refresh
53
- end
104
+ def reset_grep
105
+ # TODO: replace with more general way (issue #170)
106
+ stdscr.clear
107
+ Screen.instance.refresh
108
+
109
+ @grep_query = ''
110
+ refresh
111
+ end
112
+
113
+ def respond_to_key(key)
114
+ case key
115
+ when ?c
116
+ show_conversation
117
+ when ?d, 4
118
+ 10.times { scroll_manager.move_down }
119
+ when ?D
120
+ destroy_status
121
+ when ?F
122
+ favorite
123
+ when ?g
124
+ scroll_manager.move_to_top
125
+ when ?G
126
+ scroll_manager.move_to_bottom
127
+ when ?j, 14, Curses::Key::DOWN
128
+ scroll_manager.move_down
129
+ when ?k, 16, Curses::Key::UP
130
+ scroll_manager.move_up
131
+ when ?o
132
+ open_link
133
+ when ?r
134
+ reply
135
+ when ?R
136
+ retweet
137
+ when 18
138
+ fetch
139
+ when ?u, 21
140
+ 10.times { scroll_manager.move_up }
141
+ when ?U
142
+ show_user
143
+ when ?/
144
+ grep
145
+ when ?q
146
+ reset_grep
54
147
  else
55
- Client.current.favorite(highlighted_status) do
56
- refresh
57
- end
148
+ return false
58
149
  end
150
+ true
59
151
  end
60
152
 
61
153
  def retweet
@@ -65,18 +157,10 @@ module Twterm
65
157
  end
66
158
  end
67
159
 
68
- def destroy_status
69
- status = highlighted_status
70
-
71
- Client.current.destroy_status(status) do
72
- delete(status.id)
73
- refresh
74
- end
75
- end
76
-
77
- def delete(status_id)
78
- @status_ids.delete(status_id)
79
- refresh
160
+ def show_conversation
161
+ return if highlighted_status.nil?
162
+ tab = Tab::ConversationTab.new(highlighted_status.id)
163
+ TabManager.instance.add_and_show(tab)
80
164
  end
81
165
 
82
166
  def show_user
@@ -86,21 +170,15 @@ module Twterm
86
170
  TabManager.instance.add_and_show(user_tab)
87
171
  end
88
172
 
89
- def open_link
90
- return if highlighted_status.nil?
91
- status = highlighted_status
92
- urls = status.urls.map(&:expanded_url) + status.media.map(&:expanded_url)
93
- urls.each(&Launchy.method(:open))
94
- end
95
-
96
- def show_conversation
97
- return if highlighted_status.nil?
98
- tab = Tab::ConversationTab.new(highlighted_status.id)
99
- TabManager.instance.add_and_show(tab)
100
- end
173
+ def statuses
174
+ statuses = @status_ids.map { |id| Status.find(id) }.reject(&:nil?)
175
+ @status_ids = statuses.map(&:id)
101
176
 
102
- def fetch
103
- fail NotImplementedError, 'fetch method must be implemented'
177
+ if grep_query.empty?
178
+ statuses
179
+ else
180
+ statuses.select { |s| s.grepped_with?(grep_query) }
181
+ end
104
182
  end
105
183
 
106
184
  def touch_statuses
@@ -110,136 +188,102 @@ module Twterm
110
188
  def update
111
189
  current_line = 0
112
190
 
113
- @window.clear
191
+ offset = scroll_manager.offset
192
+ index = scroll_manager.index
114
193
 
115
194
  return if offset < 0
116
195
 
117
196
  statuses.reverse.drop(offset).each.with_index(offset) do |status, i|
118
- formatted_lines = status.split(@window.maxx - 4).count
119
- if current_line + formatted_lines + 2 > @window.maxy
120
- @scrollable_last = i
197
+ formatted_lines = status.split(window.maxx - 4).count
198
+ if current_line + formatted_lines + 2 > window.maxy
199
+ scroll_manager.last = i
121
200
  break
122
201
  end
123
202
 
124
203
  posy = current_line
125
204
 
126
205
  if index == i
127
- @window.with_color(:black, :magenta) do
206
+ window.with_color(:black, :magenta) do
128
207
  (formatted_lines + 1).times do |j|
129
- @window.setpos(posy + j, 0)
130
- @window.addch(' ')
208
+ window.setpos(posy + j, 0)
209
+ window.addch(' ')
131
210
  end
132
211
  end
133
212
  end
134
213
 
135
- @window.setpos(current_line, 2)
214
+ window.setpos(current_line, 2)
136
215
 
137
- @window.bold do
138
- @window.with_color(status.user.color) do
139
- @window.addstr(status.user.name)
216
+ window.bold do
217
+ window.with_color(status.user.color) do
218
+ window.addstr(status.user.name)
140
219
  end
141
220
  end
142
221
 
143
- @window.addstr(" (@#{status.user.screen_name}) [#{status.date}] ")
222
+ window.addstr(" (@#{status.user.screen_name}) [#{status.date}] ")
144
223
 
145
224
  unless status.retweeted_by.nil?
146
- @window.addstr('(retweeted by ')
147
- @window.bold do
148
- @window.addstr("@#{status.retweeted_by.screen_name}")
225
+ window.addstr('(retweeted by ')
226
+ window.bold do
227
+ window.addstr("@#{status.retweeted_by.screen_name}")
149
228
  end
150
- @window.addstr(') ')
229
+ window.addstr(') ')
151
230
  end
152
231
 
153
232
  if status.favorited?
154
- @window.with_color(:black, :yellow) do
155
- @window.addch(' ')
233
+ window.with_color(:black, :yellow) do
234
+ window.addch(' ')
156
235
  end
157
236
 
158
- @window.addch(' ')
237
+ window.addch(' ')
159
238
  end
160
239
 
161
240
  if status.retweeted?
162
- @window.with_color(:black, :green) do
163
- @window.addch(' ')
241
+ window.with_color(:black, :green) do
242
+ window.addch(' ')
164
243
  end
165
- @window.addch(' ')
244
+ window.addch(' ')
166
245
  end
167
246
 
168
247
  if status.favorite_count > 0
169
- @window.with_color(:yellow) do
170
- @window.addstr("#{status.favorite_count}fav#{status.favorite_count > 1 ? 's' : ''}")
248
+ window.with_color(:yellow) do
249
+ window.addstr("#{status.favorite_count}fav#{status.favorite_count > 1 ? 's' : ''}")
171
250
  end
172
- @window.addch(' ')
251
+ window.addch(' ')
173
252
  end
174
253
 
175
254
  if status.retweet_count > 0
176
- @window.with_color(:green) do
177
- @window.addstr("#{status.retweet_count}RT#{status.retweet_count > 1 ? 's' : ''}")
255
+ window.with_color(:green) do
256
+ window.addstr("#{status.retweet_count}RT#{status.retweet_count > 1 ? 's' : ''}")
178
257
  end
179
- @window.addch(' ')
258
+ window.addch(' ')
180
259
  end
181
260
 
182
- status.split(@window.maxx - 4).each do |line|
261
+ status.split(window.maxx - 4).each do |line|
183
262
  current_line += 1
184
- @window.setpos(current_line, 2)
185
- @window.addstr(line)
263
+ window.setpos(current_line, 2)
264
+ window.addstr(line)
186
265
  end
187
266
 
188
267
  current_line += 2
189
268
  end
190
269
 
191
- draw_scroll_bar
192
-
193
- @window.refresh
194
-
195
270
  UserWindow.instance.update(highlighted_status.user) unless highlighted_status.nil?
196
- show_help
197
- end
198
-
199
- def respond_to_key(key)
200
- return true if super
201
-
202
- case key
203
- when 'c'
204
- show_conversation
205
- when 'D'
206
- destroy_status
207
- when 'F'
208
- favorite
209
- when 'o'
210
- open_link
211
- when 'r'
212
- reply
213
- when 'R'
214
- retweet
215
- when 18
216
- fetch
217
- when 'U'
218
- show_user
219
- else
220
- return false
221
- end
222
- true
223
271
  end
224
272
 
225
273
  private
226
274
 
227
275
  def highlighted_status
228
- id = @status_ids[count - index - 1]
276
+ id = @status_ids[scroll_manager.count - scroll_manager.index - 1]
229
277
  Status.find(id)
230
278
  end
231
279
 
232
- def count
233
- @status_ids.count
234
- end
235
-
236
280
  def offset_from_bottom
237
281
  return @offset_from_bottom unless @offset_from_bottom.nil?
238
282
 
239
283
  height = 0
240
284
  statuses.each.with_index(-1) do |status, i|
241
- height += status.split(@window.maxx - 4).count + 2
242
- if height >= @window.maxy
285
+ height += status.split(window.maxx - 4).count + 2
286
+ if height >= window.maxy
243
287
  @offset_from_bottom = i
244
288
  return i
245
289
  end
@@ -247,13 +291,18 @@ module Twterm
247
291
  count
248
292
  end
249
293
 
250
- def sort
251
- @status_ids &= Status.all.map(&:id)
252
- @status_ids.sort_by! { |id| Status.find(id).created_at_for_sort }
294
+ def scroll_manager
295
+ return @scroll_manager unless @scroll_manager.nil?
296
+
297
+ @scroll_manager = ScrollManager.new
298
+ @scroll_manager.delegate = self
299
+ @scroll_manager.after_move { refresh }
300
+ @scroll_manager
253
301
  end
254
302
 
255
- def show_help
256
- Notifier.instance.show_help '[n] Compose [r] Reply [F] Favorite [R] Retweet [U] Show user [w] Close tab [Q] Quit'
303
+ def sort
304
+ @status_ids &= Status.all.map(&:id)
305
+ @status_ids.sort_by! { |id| Status.find(id).appeared_at }
257
306
  end
258
307
  end
259
308
  end
@@ -3,6 +3,18 @@ module Twterm
3
3
  class TimelineTab
4
4
  include StatusesTab
5
5
 
6
+ def close
7
+ fail NotClosableError
8
+ end
9
+
10
+ def fetch
11
+ @client.home_timeline do |statuses|
12
+ statuses.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
 
@@ -11,23 +23,9 @@ module Twterm
11
23
  @client.on_timeline_status(&method(:prepend))
12
24
  @title = 'Timeline'
13
25
 
14
- fetch { move_to_top }
26
+ fetch { scroll_manager.move_to_top }
15
27
  @auto_reloader = Scheduler.new(180) { fetch }
16
28
  end
17
-
18
- def fetch
19
- Thread.new do
20
- @client.home_timeline do |statuses|
21
- statuses.each(&method(:prepend))
22
- sort
23
- yield if block_given?
24
- end
25
- end
26
- end
27
-
28
- def close
29
- fail NotClosableError
30
- end
31
29
  end
32
30
  end
33
31
  end
@@ -6,17 +6,17 @@ module Twterm
6
6
 
7
7
  attr_reader :user
8
8
 
9
- def initialize(user_id)
10
- super()
9
+ def ==(other)
10
+ other.is_a?(self.class) && user == other.user
11
+ end
11
12
 
12
- User.find_or_fetch(user_id) do |user|
13
- @user = user
14
- @title = "@#{@user.screen_name}"
15
- TabManager.instance.refresh_window
13
+ def close
14
+ @auto_reloader.kill if @auto_reloader
15
+ super
16
+ end
16
17
 
17
- fetch { move_to_top }
18
- @auto_reloader = Scheduler.new(120) { fetch }
19
- end
18
+ def dump
19
+ @user.id
20
20
  end
21
21
 
22
22
  def fetch
@@ -27,17 +27,17 @@ module Twterm
27
27
  end
28
28
  end
29
29
 
30
- def close
31
- @auto_reloader.kill if @auto_reloader
32
- super
33
- end
30
+ def initialize(user_id)
31
+ super()
34
32
 
35
- def ==(other)
36
- other.is_a?(self.class) && user == other.user
37
- end
33
+ User.find_or_fetch(user_id) do |user|
34
+ @user = user
35
+ @title = "@#{@user.screen_name}"
36
+ TabManager.instance.refresh_window
38
37
 
39
- def dump
40
- @user.id
38
+ fetch { scroll_manager.move_to_top }
39
+ @auto_reloader = Scheduler.new(120) { fetch }
40
+ end
41
41
  end
42
42
  end
43
43
  end
@@ -5,16 +5,9 @@ module Twterm
5
5
 
6
6
  DUMPED_TABS_FILE = "#{ENV['HOME']}/.twterm/dumped_tabs"
7
7
 
8
- def initialize
9
- @tabs = []
10
- @index = 0
11
- @history = []
12
-
13
- @window = stdscr.subwin(3, stdscr.maxx - 30, 0, 0)
14
- end
15
-
16
8
  def add(tab_to_add)
17
9
  fail ArgumentError, 'argument must be an instance of Tab::Base' unless tab_to_add.is_a? Tab::Base
10
+
18
11
  @tabs.each.with_index do |tab, i|
19
12
  next unless tab == tab_to_add
20
13
  @index = i
@@ -35,28 +28,6 @@ module Twterm
35
28
  result
36
29
  end
37
30
 
38
- def current_tab
39
- @history.unshift(@index).uniq!
40
- @tabs[@index]
41
- end
42
-
43
- def show_next
44
- @index = (@index + 1) % @tabs.count
45
- current_tab.refresh
46
- refresh_window
47
- end
48
-
49
- def show_previous
50
- @index = (@index - 1) % @tabs.count
51
- current_tab.refresh
52
- refresh_window
53
- end
54
-
55
- def open_new
56
- tab = Tab::New::Start.new
57
- add_and_show(tab)
58
- end
59
-
60
31
  def close
61
32
  current_tab.close
62
33
  @tabs.delete_at(@index)
@@ -69,9 +40,19 @@ module Twterm
69
40
  Notifier.instance.show_error 'This tab cannot be closed'
70
41
  end
71
42
 
72
- def switch(tab)
73
- close
74
- add_and_show(tab)
43
+ def current_tab
44
+ @history.unshift(@index).uniq!
45
+ @tabs[@index]
46
+ end
47
+
48
+ def dump_tabs
49
+ data = @tabs.each_with_object([]) do |tab, arr|
50
+ next unless tab.is_a? Tab::Dumpable
51
+ arr << [tab.class, tab.title, tab.dump]
52
+ end
53
+ File.open(DUMPED_TABS_FILE, 'w', 0600) do |f|
54
+ f.write data.to_yaml
55
+ end
75
56
  end
76
57
 
77
58
  def each_tab(&block)
@@ -80,8 +61,25 @@ module Twterm
80
61
  end
81
62
  end
82
63
 
64
+ def initialize
65
+ @tabs = []
66
+ @index = 0
67
+ @history = []
68
+
69
+ @window = stdscr.subwin(3, stdscr.maxx - 30, 0, 0)
70
+ end
71
+
72
+ def open_new
73
+ tab = Tab::New::Start.new
74
+ add_and_show(tab)
75
+ end
76
+
83
77
  def recover_tabs
84
- return unless File.exist? DUMPED_TABS_FILE
78
+ unless File.exist? DUMPED_TABS_FILE
79
+ tab = Tab::KeyAssignmentsCheatsheet.new
80
+ add(tab)
81
+ return
82
+ end
85
83
 
86
84
  data = YAML.load(File.read(DUMPED_TABS_FILE))
87
85
  data.each do |klass, title, arg|
@@ -92,16 +90,6 @@ module Twterm
92
90
  Notifier.instance.show_error 'Failed to recover tabs'
93
91
  end
94
92
 
95
- def dump_tabs
96
- data = @tabs.each_with_object([]) do |tab, arr|
97
- next unless tab.is_a? Tab::Dumpable
98
- arr << [tab.class, tab.title, tab.dump]
99
- end
100
- File.open(DUMPED_TABS_FILE, 'w', 0600) do |f|
101
- f.write data.to_yaml
102
- end
103
- end
104
-
105
93
  def refresh_window
106
94
  @window.clear
107
95
  current_tab_id = current_tab.object_id
@@ -137,5 +125,22 @@ module Twterm
137
125
  end
138
126
  true
139
127
  end
128
+
129
+ def show_next
130
+ @index = (@index + 1) % @tabs.count
131
+ current_tab.refresh
132
+ refresh_window
133
+ end
134
+
135
+ def show_previous
136
+ @index = (@index - 1) % @tabs.count
137
+ current_tab.refresh
138
+ refresh_window
139
+ end
140
+
141
+ def switch(tab)
142
+ close
143
+ add_and_show(tab)
144
+ end
140
145
  end
141
146
  end