twterm 2.7.0 → 2.10.2

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.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +62 -0
  3. data/.gitattributes +4 -0
  4. data/.gitignore +1 -0
  5. data/Gemfile +7 -0
  6. data/Makefile +21 -0
  7. data/README.md +52 -6
  8. data/bin/twterm +1 -3
  9. data/default.nix +21 -0
  10. data/gemset.nix +5130 -0
  11. data/lib/twterm/app.rb +13 -8
  12. data/lib/twterm/color_manager.rb +8 -9
  13. data/lib/twterm/image.rb +31 -0
  14. data/lib/twterm/image/attr.rb +42 -0
  15. data/lib/twterm/image/bold.rb +7 -21
  16. data/lib/twterm/image/color.rb +9 -15
  17. data/lib/twterm/image/dim.rb +13 -0
  18. data/lib/twterm/image/underlined.rb +17 -0
  19. data/lib/twterm/image_builder/user_name_image_builder.rb +1 -0
  20. data/lib/twterm/key_mapper.rb +6 -8
  21. data/lib/twterm/key_mapper/abstract_key_mapper.rb +2 -2
  22. data/lib/twterm/list.rb +36 -1
  23. data/lib/twterm/message_window.rb +4 -13
  24. data/lib/twterm/persistable_configuration_proxy.rb +6 -6
  25. data/lib/twterm/preferences.rb +8 -1
  26. data/lib/twterm/screen.rb +103 -25
  27. data/lib/twterm/search_query_window.rb +5 -13
  28. data/lib/twterm/status.rb +10 -0
  29. data/lib/twterm/tab/abstract_tab.rb +30 -16
  30. data/lib/twterm/tab/new/search.rb +2 -2
  31. data/lib/twterm/tab/new/user.rb +2 -2
  32. data/lib/twterm/tab/preferences/control.rb +77 -0
  33. data/lib/twterm/tab/preferences/index.rb +6 -0
  34. data/lib/twterm/tab/scrollable.rb +1 -1
  35. data/lib/twterm/tab/searchable.rb +6 -4
  36. data/lib/twterm/tab/status_tab.rb +10 -0
  37. data/lib/twterm/tab/statuses/abstract_statuses_tab.rb +11 -6
  38. data/lib/twterm/tab_manager.rb +77 -10
  39. data/lib/twterm/tweetbox.rb +2 -3
  40. data/lib/twterm/user.rb +1 -0
  41. data/lib/twterm/version.rb +1 -1
  42. data/nix/Gemfile +3 -0
  43. data/nix/Gemfile.lock +77 -0
  44. data/nix/gemset.nix +325 -0
  45. data/shell.nix +40 -0
  46. data/spec/twterm/image/bold_spec.rb +30 -0
  47. data/spec/twterm/image/color_spec.rb +16 -0
  48. data/spec/twterm/image/dim_spec.rb +30 -0
  49. data/twterm.gemspec +7 -14
  50. metadata +35 -108
  51. data/.travis.yml +0 -12
  52. data/lib/twterm/event/screen/resize.rb +0 -13
  53. data/spec/twterm/event/screen/resize_spec.rb +0 -11
data/lib/twterm/app.rb CHANGED
@@ -3,7 +3,6 @@ require 'curses'
3
3
  require 'twterm/completion_manager'
4
4
  require 'twterm/environment'
5
5
  require 'twterm/event/screen/refresh'
6
- require 'twterm/event/screen/resize'
7
6
  require 'twterm/message_window'
8
7
  require 'twterm/notification_dispatcher'
9
8
  require 'twterm/persistable_configuration_proxy'
@@ -24,6 +23,12 @@ module Twterm
24
23
 
25
24
  attr_reader :environment, :preferences, :screen
26
25
 
26
+ # return [Twterm::MessageWindow]
27
+ attr_reader :message_window
28
+
29
+ # return [Twterm::SearchQueryWindow]
30
+ attr_reader :search_query_window
31
+
27
32
  DATA_DIR = "#{ENV['HOME']}/.twterm".freeze
28
33
 
29
34
  def initialize
@@ -63,8 +68,8 @@ module Twterm
63
68
 
64
69
  @screen = Screen.new(self, client)
65
70
 
66
- SearchQueryWindow.instance
67
- MessageWindow.instance
71
+ @search_query_window = SearchQueryWindow.new(screen.search_query_window_window)
72
+ @message_window = MessageWindow.new(screen.message_window_window)
68
73
 
69
74
  @notification_dispatcher = NotificationDispatcher.new(preferences)
70
75
  @photo_viewer = PhotoViewer.new(preferences)
@@ -143,7 +148,7 @@ module Twterm
143
148
  end
144
149
 
145
150
  def tab_manager
146
- @tab_manager ||= TabManager.new(self, client)
151
+ @tab_manager ||= TabManager.new(self, client, screen.tab_manager_window)
147
152
  end
148
153
 
149
154
  def tweetbox
@@ -177,11 +182,11 @@ module Twterm
177
182
  end
178
183
 
179
184
  def on_resize
180
- return if Curses.closed?
185
+ lines, cols = `stty size`.split(' ').map(&:to_i)
186
+
187
+ Readline.set_screen_size(lines, cols)
181
188
 
182
- lines = `tput lines`.to_i
183
- cols = `tput cols`.to_i
184
- publish(Event::Screen::Resize.new(lines, cols))
189
+ screen.resize(lines, cols) unless Curses.closed?
185
190
  end
186
191
  end
187
192
  end
@@ -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
@@ -3,10 +3,12 @@ require 'twterm/image/blank_line'
3
3
  require 'twterm/image/bold'
4
4
  require 'twterm/image/brackets'
5
5
  require 'twterm/image/color'
6
+ require 'twterm/image/dim'
6
7
  require 'twterm/image/empty'
7
8
  require 'twterm/image/horizontal_sequential_image'
8
9
  require 'twterm/image/parens'
9
10
  require 'twterm/image/string_image'
11
+ require 'twterm/image/underlined'
10
12
  require 'twterm/image/vertical_sequential_image'
11
13
 
12
14
  class Twterm::Image
@@ -14,6 +16,10 @@ class Twterm::Image
14
16
  @column, @line = column, line
15
17
  end
16
18
 
19
+ def _
20
+ underlined
21
+ end
22
+
17
23
  def !
18
24
  bold
19
25
  end
@@ -44,6 +50,10 @@ class Twterm::Image
44
50
  Brackets.new(self)
45
51
  end
46
52
 
53
+ def dim(on = true)
54
+ on ? Dim.new(self) : self
55
+ end
56
+
47
57
  def self.checkbox(checked)
48
58
  string(checked ? '*' : ' ').brackets
49
59
  end
@@ -101,6 +111,27 @@ class Twterm::Image
101
111
  StringImage.new(str)
102
112
  end
103
113
 
114
+ # @param items [Array<Symbol>]
115
+ # @param selected [Symbol]
116
+ #
117
+ # @return [Image]
118
+ def self.toggle_switch(items, selected)
119
+ items
120
+ .map do |item|
121
+ on = item == selected
122
+ string(item.to_s)
123
+ .bold(on)
124
+ .underlined(on)
125
+ end
126
+ .intersperse(string(' | '))
127
+ .reduce(empty) { |acc, x| acc - x }
128
+ .brackets
129
+ end
130
+
131
+ def underlined(on = true)
132
+ on ? Underlined.new(self) : self
133
+ end
134
+
104
135
  def self.whitespace
105
136
  string(' ')
106
137
  end
@@ -0,0 +1,42 @@
1
+ require 'twterm/image'
2
+
3
+ class Twterm::Image::Attr < Twterm::Image
4
+ # @param image [Twterm::Image]
5
+ def initialize(image)
6
+ super()
7
+
8
+ @image = image
9
+ end
10
+
11
+ def height
12
+ image.height
13
+ end
14
+
15
+ def render(window)
16
+ image, attr =
17
+ if image.is_a?(self.class) # fuse attributes when possible
18
+ [image.image, self.attr | image.attr]
19
+ else
20
+ [self.image, self.attr]
21
+ end
22
+
23
+ window.attron(attr)
24
+ image.at(line, column).render(window)
25
+ window.attroff(attr)
26
+ end
27
+
28
+ def width
29
+ image.width
30
+ end
31
+
32
+ protected
33
+
34
+ attr_reader :image
35
+
36
+ # @abstract
37
+ #
38
+ # @return [Integer]
39
+ def attr
40
+ raise NotImplementedError, '`attr` must be implemented'
41
+ end
42
+ end
@@ -1,31 +1,17 @@
1
+ require 'twterm/image/attr'
2
+
1
3
  module Twterm
2
4
  class Image
3
- class Bold < 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_BOLD)
14
- image.at(line, column).render(window)
15
- window.attroff(Curses::A_BOLD)
16
- end
17
-
5
+ class Bold < Twterm::Image::Attr
18
6
  def to_s
19
7
  "\e[1m#{image}\e[0m"
20
8
  end
21
9
 
22
- def width
23
- image.width
24
- end
10
+ protected
25
11
 
26
- private
27
-
28
- attr_reader :image
12
+ def attr
13
+ Curses::A_BOLD
14
+ end
29
15
  end
30
16
  end
31
17
  end
@@ -1,18 +1,12 @@
1
+ require 'twterm/image/attr'
2
+
1
3
  module Twterm
2
4
  class Image
3
- class Color < Twterm::Image
5
+ class Color < Twterm::Image::Attr
4
6
  def initialize(image, fg, bg = :transparent)
5
- @image, @fg, @bg = image, fg, bg
6
- end
7
-
8
- def height
9
- image.height
10
- end
7
+ super(image)
11
8
 
12
- def render(window)
13
- window.attron(Curses.color_pair(color_pair_index))
14
- image.at(line, column).render(window)
15
- window.attroff(Curses.color_pair(color_pair_index))
9
+ @fg, @bg = fg, bg
16
10
  end
17
11
 
18
12
  def to_s
@@ -29,14 +23,14 @@ module Twterm
29
23
  @bg == :transparent ? str : "\e[#{bg_colors[@bg]}m#{str}"
30
24
  end
31
25
 
32
- def width
33
- image.width
26
+ protected
27
+
28
+ def attr
29
+ Curses.color_pair(color_pair_index)
34
30
  end
35
31
 
36
32
  private
37
33
 
38
- attr_reader :image
39
-
40
34
  def color_pair_index
41
35
  Twterm::ColorManager.instance.get_color_pair_index(@fg, @bg)
42
36
  end
@@ -0,0 +1,13 @@
1
+ require 'twterm/image/attr'
2
+
3
+ class Twterm::Image::Dim < Twterm::Image::Attr
4
+ def to_s
5
+ "\e[2m#{image}\e[0m"
6
+ end
7
+
8
+ protected
9
+
10
+ def attr
11
+ Curses::A_DIM
12
+ end
13
+ end
@@ -0,0 +1,17 @@
1
+ require 'twterm/image/attr'
2
+
3
+ module Twterm
4
+ class Image
5
+ class Underlined < Twterm::Image::Attr
6
+ def to_s
7
+ "\e[4m#{image}\e[0m"
8
+ end
9
+
10
+ protected
11
+
12
+ def attr
13
+ Curses::A_UNDERLINE
14
+ end
15
+ end
16
+ end
17
+ end
@@ -1,6 +1,7 @@
1
1
  require 'twterm/image'
2
2
 
3
3
  module Twterm
4
+ # @todo Rename to `Presenter`
4
5
  module ImageBuilder
5
6
  class UserNameImageBuilder
6
7
  COLORS = [:red, :blue, :green, :cyan, :yellow, :magenta].freeze
@@ -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 = TOML.load_file(dict_file_path, symbolize_keys: true)
109
- rescue TOML::ParseError, TOML::ValueOverwriteError => e
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 TOML::ParseError
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 = TOML.dump(mappings).gsub("\n[", "\n\n[")
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
@@ -35,8 +35,6 @@ module Twterm
35
35
 
36
36
  def translate(key)
37
37
  case key
38
- when '!'..'}' then key
39
- when /\A\^([A-Z]?)\Z/ then $1.ord - 'A'.ord + 1
40
38
  when 'F1' then Curses::Key::F1
41
39
  when 'F2' then Curses::Key::F2
42
40
  when 'F3' then Curses::Key::F3
@@ -49,6 +47,8 @@ module Twterm
49
47
  when 'F10' then Curses::Key::F10
50
48
  when 'F11' then Curses::Key::F11
51
49
  when 'F12' then Curses::Key::F12
50
+ when /\A\^([A-Z]?)\Z/ then $1.ord - 'A'.ord + 1
51
+ when '!'..'}' then key
52
52
  else
53
53
  raise NoSuchKey.new(key)
54
54
  end
data/lib/twterm/list.rb CHANGED
@@ -1,7 +1,41 @@
1
1
  module Twterm
2
+ # A Twitter list
2
3
  class List
3
- attr_reader :id, :name, :slug, :full_name, :mode, :description, :member_count, :subscriber_count, :url
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
@@ -1,23 +1,19 @@
1
1
  require 'twterm/subscriber'
2
2
  require 'twterm/event/message/abstract_message'
3
- require 'twterm/event/screen/resize'
4
3
 
5
4
  module Twterm
6
5
  class MessageWindow
7
- include Singleton
8
- include Curses
9
6
  include Subscriber
10
7
 
11
- def initialize
12
- @window = stdscr.subwin(1, stdscr.maxx, stdscr.maxy - 2, 0)
8
+ # @param window [Curses::Window]
9
+ def initialize(window)
10
+ @window = window
13
11
  @queue = Queue.new
14
12
 
15
13
  subscribe(Event::Message::AbstractMessage) do |e|
16
14
  queue(e)
17
15
  end
18
16
 
19
- subscribe(Event::Screen::Resize, :resize)
20
-
21
17
  Thread.new do
22
18
  while message = @queue.pop # rubocop:disable Lint/AssignmentInCondition:
23
19
  show(message)
@@ -34,7 +30,7 @@ module Twterm
34
30
 
35
31
  def show(message = nil)
36
32
  loop do
37
- break unless closed?
33
+ break unless Curses.closed?
38
34
  sleep 0.5
39
35
  end
40
36
 
@@ -72,10 +68,5 @@ module Twterm
72
68
  @queue.push(message)
73
69
  self
74
70
  end
75
-
76
- def resize(_event)
77
- @window.resize(1, stdscr.maxx)
78
- @window.move(stdscr.maxy - 2, 0)
79
- end
80
71
  end
81
72
  end