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
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
|