twterm 2.5.1 → 2.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.circleci/config.yml +62 -0
- data/.gitignore +1 -0
- data/Makefile +21 -0
- data/README.md +52 -6
- data/bin/twterm +1 -3
- data/default.nix +21 -0
- data/gemset.nix +4981 -0
- data/lib/twterm/app.rb +13 -26
- data/lib/twterm/client.rb +1 -6
- data/lib/twterm/color_manager.rb +8 -9
- data/lib/twterm/image.rb +31 -0
- data/lib/twterm/image/attr.rb +42 -0
- data/lib/twterm/image/bold.rb +7 -21
- data/lib/twterm/image/color.rb +9 -15
- data/lib/twterm/image/dim.rb +13 -0
- data/lib/twterm/image/underlined.rb +17 -0
- data/lib/twterm/image_builder/user_name_image_builder.rb +1 -0
- data/lib/twterm/key_mapper.rb +6 -8
- data/lib/twterm/key_mapper/abstract_key_mapper.rb +2 -2
- data/lib/twterm/list.rb +36 -1
- data/lib/twterm/message_window.rb +4 -13
- data/lib/twterm/persistable_configuration_proxy.rb +6 -6
- data/lib/twterm/preferences.rb +8 -1
- data/lib/twterm/rest_client.rb +0 -33
- data/lib/twterm/screen.rb +103 -25
- data/lib/twterm/search_query_window.rb +5 -13
- data/lib/twterm/status.rb +10 -0
- data/lib/twterm/tab/abstract_tab.rb +30 -16
- data/lib/twterm/tab/new/index.rb +0 -10
- data/lib/twterm/tab/new/search.rb +2 -2
- data/lib/twterm/tab/new/user.rb +2 -2
- data/lib/twterm/tab/preferences/control.rb +77 -0
- data/lib/twterm/tab/preferences/index.rb +6 -0
- data/lib/twterm/tab/scrollable.rb +1 -1
- data/lib/twterm/tab/searchable.rb +6 -4
- data/lib/twterm/tab/status_tab.rb +10 -0
- data/lib/twterm/tab/statuses/abstract_statuses_tab.rb +11 -6
- data/lib/twterm/tab/statuses/home.rb +0 -3
- data/lib/twterm/tab/statuses/mentions.rb +1 -0
- data/lib/twterm/tab/user_tab.rb +0 -9
- data/lib/twterm/tab_manager.rb +77 -10
- data/lib/twterm/tweetbox.rb +2 -3
- data/lib/twterm/user.rb +1 -0
- data/lib/twterm/version.rb +1 -1
- data/nix/Gemfile +3 -0
- data/nix/Gemfile.lock +77 -0
- data/nix/gemset.nix +325 -0
- data/shell.nix +40 -0
- data/spec/twterm/image/bold_spec.rb +30 -0
- data/spec/twterm/image/color_spec.rb +16 -0
- data/spec/twterm/image/dim_spec.rb +30 -0
- data/twterm.gemspec +13 -13
- metadata +46 -46
- data/.travis.yml +0 -12
- data/lib/twterm/direct_message.rb +0 -60
- data/lib/twterm/direct_message_composer.rb +0 -80
- data/lib/twterm/direct_message_manager.rb +0 -51
- data/lib/twterm/event/direct_message/fetched.rb +0 -10
- data/lib/twterm/event/notification/direct_message.rb +0 -30
- data/lib/twterm/event/screen/resize.rb +0 -13
- data/lib/twterm/event/status/timeline.rb +0 -10
- data/lib/twterm/repository/direct_message_repository.rb +0 -14
- data/lib/twterm/streaming_client.rb +0 -146
- data/lib/twterm/tab/direct_message/conversation.rb +0 -104
- data/lib/twterm/tab/direct_message/conversation_list.rb +0 -100
- 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
|
-
|
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.
|
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
|
-
|
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
|
-
|
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
|
data/lib/twterm/tab/new/index.rb
CHANGED
@@ -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
|
data/lib/twterm/tab/new/user.rb
CHANGED
@@ -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 -
|
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 -
|
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 -
|
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 -
|
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
|