termgui 0.0.4
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 +7 -0
- data/Gemfile +14 -0
- data/README.md +321 -0
- data/lib/termgui.rb +1 -0
- data/src/action.rb +58 -0
- data/src/box.rb +90 -0
- data/src/color.rb +174 -0
- data/src/cursor.rb +69 -0
- data/src/editor/editor_base.rb +152 -0
- data/src/editor/editor_base_handlers.rb +116 -0
- data/src/element.rb +61 -0
- data/src/element_bounds.rb +111 -0
- data/src/element_box.rb +64 -0
- data/src/element_render.rb +102 -0
- data/src/element_style.rb +51 -0
- data/src/emitter.rb +102 -0
- data/src/emitter_state.rb +19 -0
- data/src/enterable.rb +93 -0
- data/src/event.rb +92 -0
- data/src/focus.rb +102 -0
- data/src/geometry.rb +53 -0
- data/src/image.rb +60 -0
- data/src/input.rb +85 -0
- data/src/input_grab.rb +17 -0
- data/src/input_time.rb +97 -0
- data/src/key.rb +114 -0
- data/src/log.rb +24 -0
- data/src/node.rb +117 -0
- data/src/node_attributes.rb +27 -0
- data/src/node_visit.rb +52 -0
- data/src/renderer.rb +119 -0
- data/src/renderer_cursor.rb +18 -0
- data/src/renderer_draw.rb +28 -0
- data/src/renderer_image.rb +31 -0
- data/src/renderer_print.rb +40 -0
- data/src/screen.rb +96 -0
- data/src/screen_element.rb +59 -0
- data/src/screen_input.rb +43 -0
- data/src/screen_renderer.rb +53 -0
- data/src/style.rb +149 -0
- data/src/tco/colouring.rb +248 -0
- data/src/tco/config.rb +57 -0
- data/src/tco/palette.rb +603 -0
- data/src/tco/tco_termgui.rb +30 -0
- data/src/termgui.rb +29 -0
- data/src/util/css.rb +98 -0
- data/src/util/css_query.rb +23 -0
- data/src/util/easing.rb +364 -0
- data/src/util/hash_object.rb +131 -0
- data/src/util/imagemagick.rb +27 -0
- data/src/util/justify.rb +20 -0
- data/src/util/unicode-categories.rb +572 -0
- data/src/util/wrap.rb +102 -0
- data/src/util.rb +110 -0
- data/src/widget/button.rb +33 -0
- data/src/widget/checkbox.rb +47 -0
- data/src/widget/col.rb +30 -0
- data/src/widget/image.rb +106 -0
- data/src/widget/inline.rb +40 -0
- data/src/widget/input_number.rb +73 -0
- data/src/widget/inputbox.rb +85 -0
- data/src/widget/label.rb +33 -0
- data/src/widget/modal.rb +69 -0
- data/src/widget/row.rb +26 -0
- data/src/widget/selectbox.rb +100 -0
- data/src/widget/textarea.rb +54 -0
- data/src/xml/xml.rb +80 -0
- data/test/action_test.rb +34 -0
- data/test/box_test.rb +15 -0
- data/test/css_test.rb +39 -0
- data/test/editor/editor_base_test.rb +201 -0
- data/test/element_bounds_test.rb +77 -0
- data/test/element_box_test.rb +8 -0
- data/test/element_render_test.rb +124 -0
- data/test/element_style_test.rb +85 -0
- data/test/element_test.rb +10 -0
- data/test/emitter_test.rb +108 -0
- data/test/event_test.rb +19 -0
- data/test/focus_test.rb +37 -0
- data/test/geometry_test.rb +12 -0
- data/test/input_test.rb +47 -0
- data/test/key_test.rb +14 -0
- data/test/log_test.rb +21 -0
- data/test/node_test.rb +105 -0
- data/test/performance/performance1.rb +48 -0
- data/test/renderer_test.rb +74 -0
- data/test/renderer_test_rect.rb +4 -0
- data/test/screen_test.rb +58 -0
- data/test/style_test.rb +18 -0
- data/test/termgui_test.rb +10 -0
- data/test/test_all.rb +30 -0
- data/test/util_hash_object_test.rb +93 -0
- data/test/util_test.rb +26 -0
- data/test/widget/checkbox_test.rb +99 -0
- data/test/widget/col_test.rb +87 -0
- data/test/widget/inline_test.rb +40 -0
- data/test/widget/label_test.rb +94 -0
- data/test/widget/row_test.rb +40 -0
- data/test/wrap_test.rb +11 -0
- data/test/xml_test.rb +77 -0
- metadata +148 -0
data/src/widget/modal.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
# temporary modal window solution.
|
2
|
+
# TODO: reimplement this using actual screen children elements and remove it for closing
|
3
|
+
|
4
|
+
require_relative '../screen'
|
5
|
+
require_relative '../util'
|
6
|
+
require_relative '../element'
|
7
|
+
require_relative '../style'
|
8
|
+
require_relative '../log'
|
9
|
+
require_relative '../widget/button'
|
10
|
+
require_relative '../widget/col'
|
11
|
+
require_relative '../widget/label'
|
12
|
+
|
13
|
+
def open_modal(
|
14
|
+
screen: nil,
|
15
|
+
title: Label.new(text: 'Modal'),
|
16
|
+
content: Label.new(text: 'Content'),
|
17
|
+
on_close: nil
|
18
|
+
)
|
19
|
+
modal = screen.query_one_by_attribute('modal-widget', true)
|
20
|
+
return if modal
|
21
|
+
|
22
|
+
screen.set_timeout(0.3) do
|
23
|
+
focused = screen.focus.focused
|
24
|
+
focusables = screen.focus.focusables
|
25
|
+
focusables.each { |e| e.set_attribute('focusable', false) }
|
26
|
+
title = title.is_a?(String) ? Label.new(text: title, style: { bold: true, fg: '#001119', underline: true }, x: 1, y: 1) : title
|
27
|
+
content = content.is_a?(String) ? Label.new(text: content, style: { wrap: true, fg: '#77aa33' }, x: 1, y: 3, height: 0.8, width: 0.9) : content
|
28
|
+
close = Button.new(text: 'close', y: 1, x: 0.9, action: proc {
|
29
|
+
modal.remove
|
30
|
+
screen.clear
|
31
|
+
screen.render
|
32
|
+
on_close&.call
|
33
|
+
focusables.each { |e| e.set_attribute('focusable', true) }
|
34
|
+
focused&.focus
|
35
|
+
})
|
36
|
+
modal = screen.append_child Element.new(
|
37
|
+
x: 0.2, y: 0.1,
|
38
|
+
height: 0.9,
|
39
|
+
width: 0.6,
|
40
|
+
attributes: { 'modal-widget': true },
|
41
|
+
padding: Bounds.new(top: 1, left: 2),
|
42
|
+
style: { bg: 'white', fg: 'blue', wrap: true, border: Border.new(bg: 'red', style: 'double') },
|
43
|
+
children: [
|
44
|
+
title,
|
45
|
+
close,
|
46
|
+
content
|
47
|
+
]
|
48
|
+
)
|
49
|
+
close.focus
|
50
|
+
modal.render
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def clode_modal(screen)
|
55
|
+
modals = screen.query_by_attribute('modal-widget', true)
|
56
|
+
# return unless modals.length>0
|
57
|
+
modals.each{|modal|modal.remove}
|
58
|
+
screen.render if modals.length>0
|
59
|
+
end
|
60
|
+
|
61
|
+
module TermGui
|
62
|
+
module Widget
|
63
|
+
class Modal
|
64
|
+
def self.open(**args)
|
65
|
+
open_modal(args)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
data/src/widget/row.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require_relative '../element'
|
2
|
+
module TermGui
|
3
|
+
module Widget
|
4
|
+
# Row container. A row child is rendered at the right of the previous child - all of them in one row.
|
5
|
+
# By default it will have width==0.999
|
6
|
+
class Row < Element
|
7
|
+
attr_accessor :gap
|
8
|
+
def initialize(**args)
|
9
|
+
super({ width: 0.9999999 }.merge(args))
|
10
|
+
@name = 'row'
|
11
|
+
@gap = args[:gap]||0
|
12
|
+
end
|
13
|
+
|
14
|
+
def layout
|
15
|
+
# init_x = abs_content_x
|
16
|
+
last_x = abs_content_x
|
17
|
+
@children.each do |c|
|
18
|
+
c.abs_x = last_x
|
19
|
+
last_x += c.abs_width + @gap
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
Row = TermGui::Widget::Row
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require_relative 'button'
|
2
|
+
require_relative '../enterable'
|
3
|
+
require_relative '../log'
|
4
|
+
require_relative 'checkbox'
|
5
|
+
|
6
|
+
module TermGui
|
7
|
+
module Widget
|
8
|
+
class SelectOption < CheckBox
|
9
|
+
def initialize(**args)
|
10
|
+
super
|
11
|
+
@name = 'select-option'
|
12
|
+
set_attribute('focusable', false)
|
13
|
+
end
|
14
|
+
# def default_style
|
15
|
+
# Style.new(fg: 'white', bg: 'yellow')
|
16
|
+
# end
|
17
|
+
end
|
18
|
+
|
19
|
+
# One line text input box, analog to HTMLInputElement
|
20
|
+
class SelectBox < Button
|
21
|
+
include Enterable
|
22
|
+
def initialize(**args)
|
23
|
+
super
|
24
|
+
@name = 'select'
|
25
|
+
set_attribute('escape-on-blur', get_attribute('escape-on-blur') == nil ? true : get_attribute('escape-on-blur'))
|
26
|
+
set_attribute('action-on-focus', get_attribute('action-on-focus') == nil ? true : get_attribute('action-on-focus'))
|
27
|
+
@options = (args[:options] || [])
|
28
|
+
@option_elements = @options.map.with_index do |o, i|
|
29
|
+
SelectOption.new(
|
30
|
+
text: o[:text] || o[:value],
|
31
|
+
value: o[:selected] || false,
|
32
|
+
parent: self,
|
33
|
+
y: i + 1,
|
34
|
+
x: 0
|
35
|
+
)
|
36
|
+
end
|
37
|
+
render_options 0
|
38
|
+
end
|
39
|
+
|
40
|
+
def render_options(c = nil)
|
41
|
+
@current = c unless c == nil
|
42
|
+
@option_elements.each_with_index do |e, i|
|
43
|
+
e.style.bg = 'red' if i == @current
|
44
|
+
e.style.bg = 'black' if i != @current
|
45
|
+
e.render
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def value
|
50
|
+
@option_elements.map.with_index { |e, i| e.value ? @options[i][:value] : nil }.reject { |v| v == nil }
|
51
|
+
end
|
52
|
+
|
53
|
+
def value=(v)
|
54
|
+
# todo
|
55
|
+
# throw 'todo'
|
56
|
+
end
|
57
|
+
def handle_key(event)
|
58
|
+
if !super(event)
|
59
|
+
if event.key == 'up'
|
60
|
+
render_options [@current - 1, 0].max
|
61
|
+
true
|
62
|
+
elsif event.key == 'down'
|
63
|
+
render_options [@current + 1, @option_elements.length - 1].min
|
64
|
+
true
|
65
|
+
elsif ['space'].include? event.key
|
66
|
+
@option_elements[@current].value = !@option_elements[@current].value
|
67
|
+
on_input value, event
|
68
|
+
true
|
69
|
+
else
|
70
|
+
false
|
71
|
+
end
|
72
|
+
else
|
73
|
+
true
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
SelectBox = TermGui::Widget::SelectBox
|
81
|
+
|
82
|
+
# sb=nil
|
83
|
+
# s = Screen.new(children: [
|
84
|
+
# Button.new(text: 'hello', x: 0.7, y: 0.6, action: proc {|e|
|
85
|
+
# e.target.text = sb.value.join(', ')
|
86
|
+
# e.target.render
|
87
|
+
# }),
|
88
|
+
# (sb = SelectBox.new(x: 2, y: 1, width: 0.5, height: 0.5, options:
|
89
|
+
# [
|
90
|
+
# {
|
91
|
+
# text: '1', value: 1, selected: true
|
92
|
+
# }, {
|
93
|
+
# text: '2', value: 2
|
94
|
+
# }, {
|
95
|
+
# text: '3', value: 3
|
96
|
+
# }
|
97
|
+
# ]))
|
98
|
+
# ])
|
99
|
+
|
100
|
+
# s.start
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# require_relative 'button'
|
2
|
+
# require_relative '../event'
|
3
|
+
|
4
|
+
# module TermGui
|
5
|
+
# module Widget
|
6
|
+
# # One line text input box, analog to HTMLInputElement
|
7
|
+
# class TextArea < Button
|
8
|
+
# attr_accessor :value
|
9
|
+
|
10
|
+
# def initialize(**args)
|
11
|
+
# super
|
12
|
+
# @value = args[:value] || ''
|
13
|
+
# @key_listener = nil
|
14
|
+
# install(:input)
|
15
|
+
# install(:change)
|
16
|
+
# install(:escape)
|
17
|
+
# on(:action) do
|
18
|
+
# @key_listener = proc { |e| on_key e }
|
19
|
+
# root_screen.event.add_any_key_listener @key_listener
|
20
|
+
# on('change', args[:change]) if args[:change]
|
21
|
+
# on('input', args[:input]) if args[:input]
|
22
|
+
# on('escape', args[:escape]) if args[:escape]
|
23
|
+
# end
|
24
|
+
# end
|
25
|
+
|
26
|
+
# def on_key(event)
|
27
|
+
# if !get_attribute('focused')
|
28
|
+
# trigger('change', ChangeEvent.new(self, @value, event))
|
29
|
+
# root_screen.event.remove_any_key_listener @key_listener
|
30
|
+
# elsif event.key == 'escape'
|
31
|
+
# trigger('escape', EscapeEvent.new(self, event))
|
32
|
+
# root_screen.event.remove_any_key_listener @key_listener
|
33
|
+
# elsif event.key == 'backspace'
|
34
|
+
# on_input @value.slice(0, @value.length - 1), event
|
35
|
+
# elsif alphanumeric? event.key
|
36
|
+
# on_input @value + event.key, event
|
37
|
+
# end
|
38
|
+
# end
|
39
|
+
|
40
|
+
# protected
|
41
|
+
|
42
|
+
# def on_input(value, event = nil)
|
43
|
+
# @value = value
|
44
|
+
# self.text = @value
|
45
|
+
# update_width
|
46
|
+
# root_screen.clear
|
47
|
+
# root_screen.render
|
48
|
+
# trigger('input', ChangeEvent.new(self, @value, event))
|
49
|
+
# end
|
50
|
+
# end
|
51
|
+
# end
|
52
|
+
# end
|
53
|
+
|
54
|
+
# InputBox = TermGui::Widget::InputBox
|
data/src/xml/xml.rb
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'rexml/document'
|
2
|
+
require 'erb'
|
3
|
+
require_relative '../element'
|
4
|
+
require_relative '../screen'
|
5
|
+
require_relative '../log'
|
6
|
+
require_relative '../util'
|
7
|
+
require_relative '../style'
|
8
|
+
require_relative '../widget/col'
|
9
|
+
require_relative '../widget/button'
|
10
|
+
require_relative '../widget/label'
|
11
|
+
|
12
|
+
def process_attrs(e, b, attrs = default_attrs)
|
13
|
+
a = {}
|
14
|
+
e.attributes.each_attribute do |attr|
|
15
|
+
value = attr.value
|
16
|
+
name = attr.expanded_name
|
17
|
+
if attrs[:number].include? name
|
18
|
+
a[name.to_sym] = is_percent(value.to_f) ? value.to_f : value.to_i
|
19
|
+
elsif attrs[:code].include? name
|
20
|
+
a[name.to_sym] = eval(value, b)
|
21
|
+
elsif name == 'style'
|
22
|
+
a[name.to_sym] = Style.from_hash(eval(value))
|
23
|
+
else
|
24
|
+
a[name.to_sym] = value
|
25
|
+
end
|
26
|
+
end
|
27
|
+
a
|
28
|
+
end
|
29
|
+
|
30
|
+
def percent_bounds_hack(result, parent)
|
31
|
+
result.set_attribute('xml', true)
|
32
|
+
if parent && !parent.get_attribute('xml')
|
33
|
+
result.set_attribute('x', 0)
|
34
|
+
result.set_attribute('y', 0)
|
35
|
+
result.set_attribute('width', parent.width)
|
36
|
+
result.set_attribute('height', parent.height)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def process_node(node, parent = nil, bindings = nil, builders = default_builders, attrs = default_attrs)
|
41
|
+
if node.is_a?(REXML::Text)
|
42
|
+
parent.text += node.to_s.strip if parent
|
43
|
+
else
|
44
|
+
c = (builders[node.name.to_sym] || Element)
|
45
|
+
a = process_attrs(node, bindings, attrs).merge(parent: parent)
|
46
|
+
result = c.new(a.merge(builders[node.name.to_sym] ? {} : { name: node.name }))
|
47
|
+
percent_bounds_hack(result, parent)
|
48
|
+
node.each do |e|
|
49
|
+
c = process_node(e, result, bindings, builders, attrs)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
result
|
53
|
+
end
|
54
|
+
|
55
|
+
def default_attrs
|
56
|
+
{
|
57
|
+
number: %w[width height x y],
|
58
|
+
code: ['action']
|
59
|
+
}
|
60
|
+
end
|
61
|
+
|
62
|
+
def default_builders
|
63
|
+
{
|
64
|
+
col: Col,
|
65
|
+
button: Button,
|
66
|
+
label: Label
|
67
|
+
}
|
68
|
+
end
|
69
|
+
|
70
|
+
def render_xml(xml: nil, parent: nil, binding: nil, custom_builders: {}, custom_attrs: {})
|
71
|
+
builders = default_builders.merge(custom_builders)
|
72
|
+
attrs = default_attrs.merge(custom_attrs)
|
73
|
+
doc = REXML::Document.new xml
|
74
|
+
process_node(doc, parent, binding, builders, attrs)
|
75
|
+
end
|
76
|
+
|
77
|
+
def render_erb(template: nil, parent: nil, binding: nil, erb_binding: binding, custom_builders: {}, custom_attrs: {})
|
78
|
+
xml = ERB.new(template).result(erb_binding).strip
|
79
|
+
render_xml(xml: xml, parent: parent, binding: binding, custom_builders: custom_builders, custom_attrs: custom_attrs)
|
80
|
+
end
|
data/test/action_test.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
include Test::Unit::Assertions
|
3
|
+
require_relative '../src/element'
|
4
|
+
require_relative '../src/screen'
|
5
|
+
require_relative '../src/log'
|
6
|
+
|
7
|
+
class ActionTest < Test::Unit::TestCase
|
8
|
+
def test_global_action
|
9
|
+
screen = Screen.new_for_testing width: 5, height: 6
|
10
|
+
s = ''
|
11
|
+
global = ''
|
12
|
+
e1 = screen.append_child Element.new(text: '1', attributes: { focusable: true })
|
13
|
+
screen.append_child Element.new(text: '2', attributes: { focusable: true, action: proc { |_e| s = 'e2' } })
|
14
|
+
e1.set_attribute('action', proc { s = 'e1' })
|
15
|
+
screen.action.on('action') do |e|
|
16
|
+
global = 'e' + e.target.text
|
17
|
+
end
|
18
|
+
screen.input.emit_key 'enter'
|
19
|
+
assert_equal '', s
|
20
|
+
assert_equal '', global
|
21
|
+
screen.focus.focus_next
|
22
|
+
assert_equal '', s
|
23
|
+
assert_equal '', global
|
24
|
+
screen.input.emit_key 'enter'
|
25
|
+
assert_equal 'e2', s
|
26
|
+
assert_equal 'e2', global
|
27
|
+
screen.focus.focus_next
|
28
|
+
assert_equal 'e2', s
|
29
|
+
assert_equal 'e2', global
|
30
|
+
screen.input.emit_key 'enter'
|
31
|
+
assert_equal 'e1', s
|
32
|
+
assert_equal 'e1', global
|
33
|
+
end
|
34
|
+
end
|
data/test/box_test.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
include Test::Unit::Assertions
|
3
|
+
require_relative '../src/box'
|
4
|
+
|
5
|
+
class BoxTest < Test::Unit::TestCase
|
6
|
+
def test_simple
|
7
|
+
b = draw_box(width: 8, height: 3)
|
8
|
+
assert_equal "box\n#{b.join('\n')}\nbox",
|
9
|
+
"box\n" \
|
10
|
+
'┌──────┐\\n' \
|
11
|
+
'│ │\\n' \
|
12
|
+
"└──────┘\n" \
|
13
|
+
'box'
|
14
|
+
end
|
15
|
+
end
|
data/test/css_test.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
include Test::Unit::Assertions
|
3
|
+
require_relative '../src/util/css'
|
4
|
+
|
5
|
+
class CssTest < Test::Unit::TestCase
|
6
|
+
def test_parse_rules
|
7
|
+
p = CSSParser.new
|
8
|
+
expected = '[{:selector=>"foo", :body=>"bg: red; padding-top: 3"}, {:selector=>".bar .primary", :body=>"border: double black"}, {:selector=>"; .sidebar .container", :body=>"padding: 1"}]'
|
9
|
+
actual = p.parse_rules('foo {bg: red; padding-top: 3} .bar .primary {border: double black}; .sidebar .container { padding: 1}').to_s
|
10
|
+
assert_equal expected, actual
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_parse_rules_properties
|
14
|
+
p = CSSParser.new
|
15
|
+
expected = '[{:selector=>"foo", :properties=>[{:name=>"bg", :value=>"red"}, {:name=>"padding-top", :value=>"3"}]}, {:selector=>".bar .primary", :properties=>[{:name=>"border", :value=>"double black"}]}, {:selector=>"; .sidebar .container", :properties=>[{:name=>"padding", :value=>"1"}]}]'
|
16
|
+
actual = p.parse_rules_properties(p.parse_rules('foo {bg: red; padding-top: 3} .bar .primary {border: double black}; .sidebar .container { padding: 1}')).to_s
|
17
|
+
assert_equal expected, actual
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_parse_rules_selectors
|
21
|
+
p = CSSParser.new
|
22
|
+
expected = '[{:selectors=>[[{:name=>"foo", :operator=>nil}]], :properies=>[{:name=>"bg", :value=>"red"}, {:name=>"padding-top", :value=>"3"}]}, {:selectors=>[[{:name=>".bar", :operator=>" "}, {:name=>".primary", :operator=>nil}]], :properies=>[{:name=>"border", :value=>"double black"}]}, {:selectors=>[[{:name=>";", :operator=>" "}, {:name=>".sidebar", :operator=>" "}, {:name=>".container", :operator=>nil}]], :properies=>[{:name=>"padding", :value=>"1"}]}]'
|
23
|
+
rules = p.parse_rules_properties(p.parse_rules('foo {bg: red; padding-top: 3} .bar .primary {border: double black}; .sidebar .container { padding: 1}'))
|
24
|
+
actual = p.parse_rules_selectors(rules).to_s
|
25
|
+
assert_equal expected, actual
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_parse
|
29
|
+
p = CSSParser.new
|
30
|
+
expected = '[{:selectors=>[[{:name=>"foo", :operator=>nil}]], :properies=>[{:name=>"bg", :value=>"red"}, {:name=>"padding-top", :value=>"3"}]}, {:selectors=>[[{:name=>".bar", :operator=>" "}, {:name=>".primary", :operator=>nil}]], :properies=>[{:name=>"border", :value=>"double black"}]}, {:selectors=>[[{:name=>";", :operator=>" "}, {:name=>".sidebar", :operator=>" "}, {:name=>".container", :operator=>nil}]], :properies=>[{:name=>"padding", :value=>"1"}]}]'
|
31
|
+
actual = p.parse('foo {bg: red; padding-top: 3} .bar .primary {border: double black}; .sidebar .container { padding: 1}').to_s
|
32
|
+
assert_equal expected, actual
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_parse_selector
|
36
|
+
expected = '[{:name=>"a", :operator=>">"}, {:name=>"b", :operator=>" "}, {:name=>"c", :operator=>">"}, {:name=>"d", :operator=>" "}, {:name=>"f", :operator=>nil}]'
|
37
|
+
assert_equal expected, CSSParser.new.parse_selector('a> b c>d f').to_s
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,201 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
include Test::Unit::Assertions
|
3
|
+
require_relative '../../src/editor/editor_base'
|
4
|
+
require_relative '../../src/log'
|
5
|
+
require_relative '../../src/screen'
|
6
|
+
require_relative '../../src/event'
|
7
|
+
|
8
|
+
class EditorBaseTest < Test::Unit::TestCase
|
9
|
+
def test_arrows_enter_cursor_backspace
|
10
|
+
screen = Screen.new_for_testing(width: 24, height: 6)
|
11
|
+
ed = EditorBase.new(text: 'hello world\nhow are you?', screen: screen, cursor_x: 0, cursor_y: 0)
|
12
|
+
ed.enable
|
13
|
+
screen.set_timeout do
|
14
|
+
assert_equal 'hello world\nhow are you?', ed.text
|
15
|
+
assert_equal [0, 0], [ed.cursor_x, ed.cursor_y]
|
16
|
+
|
17
|
+
screen.clear
|
18
|
+
ed.render
|
19
|
+
assert_equal(
|
20
|
+
'hello world \n' \
|
21
|
+
'how are you? \n' \
|
22
|
+
' \n' \
|
23
|
+
' \n' \
|
24
|
+
' \n' \
|
25
|
+
' \n', screen.renderer.print
|
26
|
+
)
|
27
|
+
assert_equal [0, 0], [ed.cursor_x, ed.cursor_y]
|
28
|
+
|
29
|
+
screen.event.handle_key(KeyEvent.new('right'))
|
30
|
+
assert_equal [1, 0], [ed.cursor_x, ed.cursor_y]
|
31
|
+
|
32
|
+
screen.event.handle_key(KeyEvent.new('enter'))
|
33
|
+
screen.clear
|
34
|
+
ed.render
|
35
|
+
assert_equal(
|
36
|
+
'h \n' \
|
37
|
+
'ello world \n' \
|
38
|
+
'how are you? \n' \
|
39
|
+
' \n' \
|
40
|
+
' \n' \
|
41
|
+
' \n', screen.renderer.print
|
42
|
+
)
|
43
|
+
|
44
|
+
screen.event.handle_key(KeyEvent.new('backspace'))
|
45
|
+
screen.clear
|
46
|
+
ed.render
|
47
|
+
assert_equal(
|
48
|
+
'hello world \n' \
|
49
|
+
'how are you? \n' \
|
50
|
+
' \n' \
|
51
|
+
' \n' \
|
52
|
+
' \n' \
|
53
|
+
' \n', screen.renderer.print
|
54
|
+
)
|
55
|
+
assert_equal [1, 0], [ed.cursor_x, ed.cursor_y]
|
56
|
+
|
57
|
+
6.times { screen.event.handle_key(KeyEvent.new('right')) }
|
58
|
+
screen.event.handle_key(KeyEvent.new('left'))
|
59
|
+
screen.event.handle_key(KeyEvent.new('down'))
|
60
|
+
assert_equal [6, 1], [ed.cursor_x, ed.cursor_y]
|
61
|
+
|
62
|
+
screen.event.handle_key(KeyEvent.new('enter'))
|
63
|
+
screen.clear
|
64
|
+
ed.render
|
65
|
+
assert_equal(
|
66
|
+
'hello world \n' \
|
67
|
+
'how ar \n' \
|
68
|
+
'e you? \n' \
|
69
|
+
' \n' \
|
70
|
+
' \n' \
|
71
|
+
' \n', screen.renderer.print
|
72
|
+
)
|
73
|
+
assert_equal [0, 2], [ed.cursor_x, ed.cursor_y]
|
74
|
+
|
75
|
+
screen.event.handle_key(KeyEvent.new('backspace'))
|
76
|
+
screen.clear
|
77
|
+
ed.render
|
78
|
+
assert_equal(
|
79
|
+
'hello world \n' \
|
80
|
+
'how are you? \n' \
|
81
|
+
' \n' \
|
82
|
+
' \n' \
|
83
|
+
' \n' \
|
84
|
+
' \n', screen.renderer.print
|
85
|
+
)
|
86
|
+
assert_equal [6, 1], [ed.cursor_x, ed.cursor_y]
|
87
|
+
|
88
|
+
6.times { screen.event.handle_key(KeyEvent.new('left')) }
|
89
|
+
assert_equal [0, 1], [ed.cursor_x, ed.cursor_y]
|
90
|
+
screen.event.handle_key(KeyEvent.new('backspace'))
|
91
|
+
screen.clear
|
92
|
+
ed.render
|
93
|
+
assert_equal(
|
94
|
+
'hello worldhow are you? \n' \
|
95
|
+
' \n' \
|
96
|
+
' \n' \
|
97
|
+
' \n' \
|
98
|
+
' \n' \
|
99
|
+
' \n', screen.renderer.print
|
100
|
+
)
|
101
|
+
assert_equal [11, 0], [ed.cursor_x, ed.cursor_y]
|
102
|
+
|
103
|
+
3.times { screen.event.handle_key(KeyEvent.new('space')) }
|
104
|
+
screen.clear
|
105
|
+
ed.render
|
106
|
+
assert_equal(
|
107
|
+
'hello world how are yo\n' \
|
108
|
+
' \n' \
|
109
|
+
' \n' \
|
110
|
+
' \n' \
|
111
|
+
' \n' \
|
112
|
+
' \n', screen.renderer.print
|
113
|
+
)
|
114
|
+
assert_equal [14, 0], [ed.cursor_x, ed.cursor_y]
|
115
|
+
|
116
|
+
screen.event.handle_key(KeyEvent.new('X'))
|
117
|
+
screen.event.handle_key(KeyEvent.new('enter'))
|
118
|
+
screen.clear
|
119
|
+
ed.render
|
120
|
+
assert_equal(
|
121
|
+
'hello world X \n' \
|
122
|
+
'how are you? \n' \
|
123
|
+
' \n' \
|
124
|
+
' \n' \
|
125
|
+
' \n' \
|
126
|
+
' \n', screen.renderer.print
|
127
|
+
)
|
128
|
+
assert_equal [0, 1], [ed.cursor_x, ed.cursor_y]
|
129
|
+
|
130
|
+
screen.destroy
|
131
|
+
end
|
132
|
+
screen.start
|
133
|
+
end
|
134
|
+
|
135
|
+
def test_coords
|
136
|
+
screen = Screen.new_for_testing(width: 24, height: 6)
|
137
|
+
ed = EditorBase.new(text: 'Welcome to this\nhumble editor', screen: screen, cursor_x: 0, cursor_y: 0, x: 5, y: 2)
|
138
|
+
ed.enable
|
139
|
+
screen.set_timeout do
|
140
|
+
screen.clear
|
141
|
+
ed.render
|
142
|
+
assert_equal(
|
143
|
+
' \n' \
|
144
|
+
' \n' \
|
145
|
+
' Welcome to this \n' \
|
146
|
+
' humble editor \n' \
|
147
|
+
' \n' \
|
148
|
+
' \n', screen.renderer.print
|
149
|
+
)
|
150
|
+
assert_equal [0, 0], [ed.cursor_x, ed.cursor_y]
|
151
|
+
screen.event.handle_key(KeyEvent.new('down'))
|
152
|
+
assert_equal [0, 1], [ed.cursor_x, ed.cursor_y]
|
153
|
+
screen.event.handle_key(KeyEvent.new('right'))
|
154
|
+
assert_equal [1, 1], [ed.cursor_x, ed.cursor_y]
|
155
|
+
screen.event.handle_key(KeyEvent.new('enter'))
|
156
|
+
screen.clear
|
157
|
+
ed.render
|
158
|
+
assert_equal(
|
159
|
+
' \n' \
|
160
|
+
' \n' \
|
161
|
+
' Welcome to this \n' \
|
162
|
+
' h \n' \
|
163
|
+
' umble editor \n' \
|
164
|
+
' \n', screen.renderer.print
|
165
|
+
)
|
166
|
+
assert_equal [0, 2], [ed.cursor_x, ed.cursor_y]
|
167
|
+
|
168
|
+
screen.event.handle_key(KeyEvent.new('backspace'))
|
169
|
+
assert_equal [1, 1], [ed.cursor_x, ed.cursor_y]
|
170
|
+
screen.clear
|
171
|
+
ed.render
|
172
|
+
assert_equal(
|
173
|
+
' \n' \
|
174
|
+
' \n' \
|
175
|
+
' Welcome to this \n' \
|
176
|
+
' humble editor \n' \
|
177
|
+
' \n' \
|
178
|
+
' \n', screen.renderer.print
|
179
|
+
)
|
180
|
+
assert_equal [1, 1], [ed.cursor_x, ed.cursor_y]
|
181
|
+
|
182
|
+
screen.event.handle_key(KeyEvent.new('A'))
|
183
|
+
screen.clear
|
184
|
+
ed.render
|
185
|
+
assert_equal(
|
186
|
+
' \n' \
|
187
|
+
' \n' \
|
188
|
+
' Welcome to this \n' \
|
189
|
+
' hAumble editor \n' \
|
190
|
+
' \n' \
|
191
|
+
' \n', screen.renderer.print
|
192
|
+
)
|
193
|
+
assert_equal [2, 1], [ed.cursor_x, ed.cursor_y]
|
194
|
+
|
195
|
+
# screen.renderer.print_dev_stdout
|
196
|
+
|
197
|
+
screen.destroy
|
198
|
+
end
|
199
|
+
screen.start
|
200
|
+
end
|
201
|
+
end
|