twterm 2.8.0 → 2.9.0
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.
- checksums.yaml +4 -4
- data/.circleci/config.yml +16 -9
- 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/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/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/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/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_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 +40 -31
@@ -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
|
@@ -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
|
@@ -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
|
@@ -235,7 +235,7 @@ module Twterm
|
|
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
|
|
data/lib/twterm/tab_manager.rb
CHANGED
@@ -6,7 +6,6 @@ require 'twterm/view'
|
|
6
6
|
|
7
7
|
module Twterm
|
8
8
|
class TabManager
|
9
|
-
include Curses
|
10
9
|
include Publisher
|
11
10
|
include Subscriber
|
12
11
|
include Utils
|
@@ -69,6 +68,20 @@ module Twterm
|
|
69
68
|
end
|
70
69
|
end
|
71
70
|
|
71
|
+
# Returns if the given coordinate is enclosed by the window
|
72
|
+
#
|
73
|
+
# @param x [Integer]
|
74
|
+
# @param y [Integer]
|
75
|
+
# @return [Boolean]
|
76
|
+
def enclose?(x, y)
|
77
|
+
left = @window.begx
|
78
|
+
top = @window.begy
|
79
|
+
right = left + @window.maxx
|
80
|
+
bottom = top + @window.maxy
|
81
|
+
|
82
|
+
left <= x && x < right && top <= y && y < bottom
|
83
|
+
end
|
84
|
+
|
72
85
|
def initialize(app, client)
|
73
86
|
@app, @client = app, client
|
74
87
|
|
@@ -76,11 +89,50 @@ module Twterm
|
|
76
89
|
@index = 0
|
77
90
|
@history = []
|
78
91
|
|
79
|
-
@window = stdscr.subwin(
|
92
|
+
@window = Curses.stdscr.subwin(1, Curses.stdscr.maxx, 0, 0)
|
80
93
|
|
81
94
|
subscribe(Event::Screen::Resize, :resize)
|
82
95
|
end
|
83
96
|
|
97
|
+
# Open the clicked tab
|
98
|
+
#
|
99
|
+
# @param x [Integer]
|
100
|
+
# @param _y [Integer]
|
101
|
+
#
|
102
|
+
# @return [nil]
|
103
|
+
def handle_left_click(x, _y)
|
104
|
+
n = find_tab_index_on_x(x)
|
105
|
+
return if n.nil?
|
106
|
+
|
107
|
+
show_nth_tab(n)
|
108
|
+
|
109
|
+
nil
|
110
|
+
end
|
111
|
+
|
112
|
+
# Open next tab
|
113
|
+
#
|
114
|
+
# @param _x [Integer]
|
115
|
+
# @param _y [Integer]
|
116
|
+
#
|
117
|
+
# @return [nil]
|
118
|
+
def handle_scroll_down(_x, _y)
|
119
|
+
show_next
|
120
|
+
|
121
|
+
nil
|
122
|
+
end
|
123
|
+
|
124
|
+
# Open previous tab
|
125
|
+
#
|
126
|
+
# @param _x [Integer]
|
127
|
+
# @param _y [Integer]
|
128
|
+
#
|
129
|
+
# @return [nil]
|
130
|
+
def handle_scroll_up(_x, _y)
|
131
|
+
show_previous
|
132
|
+
|
133
|
+
nil
|
134
|
+
end
|
135
|
+
|
84
136
|
def open_my_profile
|
85
137
|
current_user_id = client.user_id
|
86
138
|
tab = Tab::UserTab.new(app, client, current_user_id)
|
@@ -120,10 +172,10 @@ module Twterm
|
|
120
172
|
|
121
173
|
image = @tabs
|
122
174
|
.map { |t| [t, Image.string(t.title)] }
|
123
|
-
.map { |t, r| t.equal?(current_tab) ? !r : r }
|
175
|
+
.map { |t, r| t.equal?(current_tab) ? !r._ : r }
|
124
176
|
.reduce(pipe) { |acc, x| acc - wss - x - wss - pipe }
|
125
177
|
|
126
|
-
View.new(@window, image)
|
178
|
+
View.new(@window, image)
|
127
179
|
end
|
128
180
|
|
129
181
|
def respond_to_key(key)
|
@@ -195,8 +247,28 @@ module Twterm
|
|
195
247
|
|
196
248
|
attr_reader :app, :client
|
197
249
|
|
250
|
+
# @param x [Integer]
|
251
|
+
#
|
252
|
+
# @return [Integer, nil]
|
253
|
+
def find_tab_index_on_x(x)
|
254
|
+
pos = 0
|
255
|
+
|
256
|
+
@tabs.each.with_index do |tab, index|
|
257
|
+
title = tab.title
|
258
|
+
len = title.length
|
259
|
+
left = pos + 1
|
260
|
+
right = left + len + 4 # each tab has 2 whitespaces on the both side
|
261
|
+
|
262
|
+
return index if left <= x && x < right
|
263
|
+
|
264
|
+
pos = right
|
265
|
+
end
|
266
|
+
|
267
|
+
nil
|
268
|
+
end
|
269
|
+
|
198
270
|
def resize(_event)
|
199
|
-
@window.resize(
|
271
|
+
@window.resize(1, Curses.stdscr.maxx)
|
200
272
|
@window.move(0, 0)
|
201
273
|
end
|
202
274
|
end
|