twterm 2.8.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 +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
data/lib/twterm/color_manager.rb
CHANGED
@@ -1,18 +1,17 @@
|
|
1
1
|
module Twterm
|
2
2
|
class ColorManager
|
3
3
|
include Singleton
|
4
|
-
include Curses
|
5
4
|
|
6
5
|
COLORS = [:black, :white, :red, :green, :blue, :yellow, :cyan, :magenta, :transparent]
|
7
6
|
CURSES_COLORS = {
|
8
|
-
black: COLOR_BLACK,
|
9
|
-
white: COLOR_WHITE,
|
10
|
-
red: COLOR_RED,
|
11
|
-
green: COLOR_GREEN,
|
12
|
-
blue: COLOR_BLUE,
|
13
|
-
yellow: COLOR_YELLOW,
|
14
|
-
cyan: COLOR_CYAN,
|
15
|
-
magenta: COLOR_MAGENTA,
|
7
|
+
black: Curses::COLOR_BLACK,
|
8
|
+
white: Curses::COLOR_WHITE,
|
9
|
+
red: Curses::COLOR_RED,
|
10
|
+
green: Curses::COLOR_GREEN,
|
11
|
+
blue: Curses::COLOR_BLUE,
|
12
|
+
yellow: Curses::COLOR_YELLOW,
|
13
|
+
cyan: Curses::COLOR_CYAN,
|
14
|
+
magenta: Curses::COLOR_MAGENTA,
|
16
15
|
transparent: -1
|
17
16
|
}
|
18
17
|
|
data/lib/twterm/image.rb
CHANGED
@@ -7,6 +7,7 @@ require 'twterm/image/empty'
|
|
7
7
|
require 'twterm/image/horizontal_sequential_image'
|
8
8
|
require 'twterm/image/parens'
|
9
9
|
require 'twterm/image/string_image'
|
10
|
+
require 'twterm/image/underlined'
|
10
11
|
require 'twterm/image/vertical_sequential_image'
|
11
12
|
|
12
13
|
class Twterm::Image
|
@@ -14,6 +15,10 @@ class Twterm::Image
|
|
14
15
|
@column, @line = column, line
|
15
16
|
end
|
16
17
|
|
18
|
+
def _
|
19
|
+
underlined
|
20
|
+
end
|
21
|
+
|
17
22
|
def !
|
18
23
|
bold
|
19
24
|
end
|
@@ -101,6 +106,27 @@ class Twterm::Image
|
|
101
106
|
StringImage.new(str)
|
102
107
|
end
|
103
108
|
|
109
|
+
# @param items [Array<Symbol>]
|
110
|
+
# @param selected [Symbol]
|
111
|
+
#
|
112
|
+
# @return [Image]
|
113
|
+
def self.toggle_switch(items, selected)
|
114
|
+
items
|
115
|
+
.map do |item|
|
116
|
+
on = item == selected
|
117
|
+
string(item.to_s)
|
118
|
+
.bold(on)
|
119
|
+
.underlined(on)
|
120
|
+
end
|
121
|
+
.intersperse(string(' | '))
|
122
|
+
.reduce(empty) { |acc, x| acc - x }
|
123
|
+
.brackets
|
124
|
+
end
|
125
|
+
|
126
|
+
def underlined(on = true)
|
127
|
+
on ? Underlined.new(self) : self
|
128
|
+
end
|
129
|
+
|
104
130
|
def self.whitespace
|
105
131
|
string(' ')
|
106
132
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Twterm
|
2
|
+
class Image
|
3
|
+
class Underlined < Twterm::Image
|
4
|
+
def initialize(image)
|
5
|
+
@image = image
|
6
|
+
end
|
7
|
+
|
8
|
+
def height
|
9
|
+
image.height
|
10
|
+
end
|
11
|
+
|
12
|
+
def render(window)
|
13
|
+
window.attron(Curses::A_UNDERLINE)
|
14
|
+
image.at(line, column).render(window)
|
15
|
+
window.attroff(Curses::A_UNDERLINE)
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_s
|
19
|
+
"\e[4m#{image}\e[0m"
|
20
|
+
end
|
21
|
+
|
22
|
+
def width
|
23
|
+
image.width
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
attr_reader :image
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/twterm/key_mapper.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require 'toml'
|
1
|
+
require 'toml-rb'
|
2
2
|
require 'singleton'
|
3
3
|
|
4
4
|
require 'twterm/app'
|
@@ -105,14 +105,12 @@ module Twterm
|
|
105
105
|
end
|
106
106
|
|
107
107
|
def load_dict_file!
|
108
|
-
dict =
|
109
|
-
rescue
|
108
|
+
dict = TomlRB.load_file(dict_file_path, symbolize_keys: true)
|
109
|
+
rescue TomlRB::ParseError => e
|
110
110
|
first_line =
|
111
111
|
case e
|
112
|
-
when
|
113
|
-
"Your key assignments dictionary file (#{dict_file_path}) could not be parsed"
|
114
|
-
when TOML::ValueOverwriteError
|
115
|
-
"Command `#{e.key}` is declared more than once"
|
112
|
+
when TomlRB::ParseError
|
113
|
+
"Your key assignments dictionary file (#{dict_file_path}) could not be parsed:\n#{e.message}"
|
116
114
|
end
|
117
115
|
|
118
116
|
warn <<-EOS
|
@@ -132,7 +130,7 @@ Press any key to continue
|
|
132
130
|
end
|
133
131
|
|
134
132
|
def save!(mappings)
|
135
|
-
dict =
|
133
|
+
dict = TomlRB.dump(mappings).gsub("\n[", "\n\n[")
|
136
134
|
File.open(dict_file_path, 'w', 0644) { |f| f.write(dict) }
|
137
135
|
end
|
138
136
|
end
|
data/lib/twterm/list.rb
CHANGED
@@ -1,7 +1,41 @@
|
|
1
1
|
module Twterm
|
2
|
+
# A Twitter list
|
2
3
|
class List
|
3
|
-
|
4
|
+
# Unique ID of the list
|
5
|
+
#
|
6
|
+
# @return [Integer]
|
7
|
+
attr_reader :id
|
4
8
|
|
9
|
+
# @return [String]
|
10
|
+
attr_reader :name
|
11
|
+
|
12
|
+
# @return [String]
|
13
|
+
attr_reader :slug
|
14
|
+
|
15
|
+
# @return [String]
|
16
|
+
attr_reader :full_name
|
17
|
+
|
18
|
+
attr_reader :mode
|
19
|
+
|
20
|
+
# @return [String]
|
21
|
+
attr_reader :description
|
22
|
+
|
23
|
+
# The number of users that are in this list
|
24
|
+
#
|
25
|
+
# @return [Integer]
|
26
|
+
attr_reader :member_count
|
27
|
+
|
28
|
+
# The number of users that subscribe this list
|
29
|
+
#
|
30
|
+
# @return [Integer]
|
31
|
+
attr_reader :subscriber_count
|
32
|
+
|
33
|
+
# @return [String]
|
34
|
+
attr_reader :url
|
35
|
+
|
36
|
+
# @param other [List]
|
37
|
+
#
|
38
|
+
# @return [Boolean]
|
5
39
|
def ==(other)
|
6
40
|
other.is_a?(self.class) && id == other.id
|
7
41
|
end
|
@@ -11,6 +45,7 @@ module Twterm
|
|
11
45
|
update!(list)
|
12
46
|
end
|
13
47
|
|
48
|
+
# @return [self]
|
14
49
|
def update!(list)
|
15
50
|
@name = list.name
|
16
51
|
@slug = list.slug
|
@@ -5,11 +5,10 @@ require 'twterm/event/screen/resize'
|
|
5
5
|
module Twterm
|
6
6
|
class MessageWindow
|
7
7
|
include Singleton
|
8
|
-
include Curses
|
9
8
|
include Subscriber
|
10
9
|
|
11
10
|
def initialize
|
12
|
-
@window = stdscr.subwin(1, stdscr.maxx, stdscr.maxy -
|
11
|
+
@window = Curses.stdscr.subwin(1, Curses.stdscr.maxx, Curses.stdscr.maxy - 1, 0)
|
13
12
|
@queue = Queue.new
|
14
13
|
|
15
14
|
subscribe(Event::Message::AbstractMessage) do |e|
|
@@ -34,7 +33,7 @@ module Twterm
|
|
34
33
|
|
35
34
|
def show(message = nil)
|
36
35
|
loop do
|
37
|
-
break unless closed?
|
36
|
+
break unless Curses.closed?
|
38
37
|
sleep 0.5
|
39
38
|
end
|
40
39
|
|
@@ -74,8 +73,8 @@ module Twterm
|
|
74
73
|
end
|
75
74
|
|
76
75
|
def resize(_event)
|
77
|
-
@window.resize(1, stdscr.maxx)
|
78
|
-
@window.move(stdscr.maxy -
|
76
|
+
@window.resize(1, Curses.stdscr.maxx)
|
77
|
+
@window.move(Curses.stdscr.maxy - 1, 0)
|
79
78
|
end
|
80
79
|
end
|
81
80
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require 'toml'
|
1
|
+
require 'toml-rb'
|
2
2
|
|
3
3
|
module Twterm
|
4
4
|
class PersistableConfigurationProxy
|
@@ -32,16 +32,16 @@ module Twterm
|
|
32
32
|
# @param [String] filepath File path to load configuration from
|
33
33
|
# @return [Twterm::PersistableConfigurationProxy] a configuration proxy
|
34
34
|
def self.load_from_file!(klass, filepath)
|
35
|
-
config =
|
35
|
+
config = TomlRB.load_file(filepath, symbolize_keys: true)
|
36
36
|
new(klass.new(config), filepath).migrate!
|
37
37
|
rescue Errno::ENOENT
|
38
38
|
new(klass.default, filepath)
|
39
|
-
rescue
|
39
|
+
rescue TomlRB::ParseError, TomlRB::ValueOverwriteError => e
|
40
40
|
msg =
|
41
41
|
case e
|
42
|
-
when
|
42
|
+
when TomlRB::ParseError
|
43
43
|
"Your configuration file could not be parsed"
|
44
|
-
when
|
44
|
+
when TomlRB::ValueOverwriteError
|
45
45
|
"`#{e.key}` is declared more than once"
|
46
46
|
end
|
47
47
|
|
@@ -75,7 +75,7 @@ Press any key to continue
|
|
75
75
|
attr_reader :filepath, :instance
|
76
76
|
|
77
77
|
def persist!
|
78
|
-
hash =
|
78
|
+
hash = TomlRB.dump(instance.to_h).gsub("\n[", "\n\n[")
|
79
79
|
File.open(filepath, 'w', 0644) { |f| f.write(hash) }
|
80
80
|
end
|
81
81
|
end
|
data/lib/twterm/preferences.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require 'toml'
|
1
|
+
require 'toml-rb'
|
2
2
|
|
3
3
|
require 'twterm/abstract_persistable_configuration'
|
4
4
|
|
@@ -27,6 +27,9 @@ module Twterm
|
|
27
27
|
# @return [Twterm::Preferences] an instance having the default value
|
28
28
|
def self.default
|
29
29
|
new({
|
30
|
+
control: {
|
31
|
+
scroll_direction: 'traditional',
|
32
|
+
},
|
30
33
|
photo_viewer_backend: {
|
31
34
|
browser: true,
|
32
35
|
imgcat: false,
|
@@ -48,8 +51,12 @@ module Twterm
|
|
48
51
|
# @return [Hash]
|
49
52
|
def self.structure
|
50
53
|
bool = -> x { x == true || x == false }
|
54
|
+
scroll_direction = -> x { x == 'natural' || x == 'traditional' }
|
51
55
|
|
52
56
|
{
|
57
|
+
control: {
|
58
|
+
scroll_direction: scroll_direction
|
59
|
+
},
|
53
60
|
photo_viewer_backend: {
|
54
61
|
browser: bool,
|
55
62
|
imgcat: bool,
|
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
|