twterm 2.5.0 → 2.9.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 +18 -0
- data/README.md +30 -4
- data/bin/twterm +1 -3
- data/default.nix +21 -0
- data/gemset.nix +5130 -0
- data/lib/twterm/app.rb +0 -18
- data/lib/twterm/client.rb +1 -6
- data/lib/twterm/color_manager.rb +8 -9
- data/lib/twterm/image.rb +26 -0
- data/lib/twterm/image/underlined.rb +31 -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 -5
- 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 +54 -14
- data/lib/twterm/search_query_window.rb +4 -5
- data/lib/twterm/status.rb +10 -0
- data/lib/twterm/tab/abstract_tab.rb +33 -8
- data/lib/twterm/tab/new/index.rb +0 -10
- data/lib/twterm/tab/new/list.rb +3 -3
- 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/status_tab.rb +10 -0
- data/lib/twterm/tab/statuses/abstract_statuses_tab.rb +4 -4
- 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 -5
- 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 +70 -0
- data/nix/gemset.nix +283 -0
- data/shell.nix +40 -0
- data/twterm.gemspec +13 -13
- metadata +42 -44
- 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/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/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
|
-
|
78
|
-
|
79
|
-
|
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
|
-
|
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 -
|
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.
|
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
|
-
|
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 -
|
111
|
-
window.move(
|
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
|
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
|
data/lib/twterm/tab/new/list.rb
CHANGED
@@ -33,10 +33,10 @@ module Twterm
|
|
33
33
|
@@lists || []
|
34
34
|
end
|
35
35
|
|
36
|
-
def matches?(
|
36
|
+
def matches?(list, query)
|
37
37
|
[
|
38
|
-
|
39
|
-
|
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
|
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
|