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.
Files changed (101) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +14 -0
  3. data/README.md +321 -0
  4. data/lib/termgui.rb +1 -0
  5. data/src/action.rb +58 -0
  6. data/src/box.rb +90 -0
  7. data/src/color.rb +174 -0
  8. data/src/cursor.rb +69 -0
  9. data/src/editor/editor_base.rb +152 -0
  10. data/src/editor/editor_base_handlers.rb +116 -0
  11. data/src/element.rb +61 -0
  12. data/src/element_bounds.rb +111 -0
  13. data/src/element_box.rb +64 -0
  14. data/src/element_render.rb +102 -0
  15. data/src/element_style.rb +51 -0
  16. data/src/emitter.rb +102 -0
  17. data/src/emitter_state.rb +19 -0
  18. data/src/enterable.rb +93 -0
  19. data/src/event.rb +92 -0
  20. data/src/focus.rb +102 -0
  21. data/src/geometry.rb +53 -0
  22. data/src/image.rb +60 -0
  23. data/src/input.rb +85 -0
  24. data/src/input_grab.rb +17 -0
  25. data/src/input_time.rb +97 -0
  26. data/src/key.rb +114 -0
  27. data/src/log.rb +24 -0
  28. data/src/node.rb +117 -0
  29. data/src/node_attributes.rb +27 -0
  30. data/src/node_visit.rb +52 -0
  31. data/src/renderer.rb +119 -0
  32. data/src/renderer_cursor.rb +18 -0
  33. data/src/renderer_draw.rb +28 -0
  34. data/src/renderer_image.rb +31 -0
  35. data/src/renderer_print.rb +40 -0
  36. data/src/screen.rb +96 -0
  37. data/src/screen_element.rb +59 -0
  38. data/src/screen_input.rb +43 -0
  39. data/src/screen_renderer.rb +53 -0
  40. data/src/style.rb +149 -0
  41. data/src/tco/colouring.rb +248 -0
  42. data/src/tco/config.rb +57 -0
  43. data/src/tco/palette.rb +603 -0
  44. data/src/tco/tco_termgui.rb +30 -0
  45. data/src/termgui.rb +29 -0
  46. data/src/util/css.rb +98 -0
  47. data/src/util/css_query.rb +23 -0
  48. data/src/util/easing.rb +364 -0
  49. data/src/util/hash_object.rb +131 -0
  50. data/src/util/imagemagick.rb +27 -0
  51. data/src/util/justify.rb +20 -0
  52. data/src/util/unicode-categories.rb +572 -0
  53. data/src/util/wrap.rb +102 -0
  54. data/src/util.rb +110 -0
  55. data/src/widget/button.rb +33 -0
  56. data/src/widget/checkbox.rb +47 -0
  57. data/src/widget/col.rb +30 -0
  58. data/src/widget/image.rb +106 -0
  59. data/src/widget/inline.rb +40 -0
  60. data/src/widget/input_number.rb +73 -0
  61. data/src/widget/inputbox.rb +85 -0
  62. data/src/widget/label.rb +33 -0
  63. data/src/widget/modal.rb +69 -0
  64. data/src/widget/row.rb +26 -0
  65. data/src/widget/selectbox.rb +100 -0
  66. data/src/widget/textarea.rb +54 -0
  67. data/src/xml/xml.rb +80 -0
  68. data/test/action_test.rb +34 -0
  69. data/test/box_test.rb +15 -0
  70. data/test/css_test.rb +39 -0
  71. data/test/editor/editor_base_test.rb +201 -0
  72. data/test/element_bounds_test.rb +77 -0
  73. data/test/element_box_test.rb +8 -0
  74. data/test/element_render_test.rb +124 -0
  75. data/test/element_style_test.rb +85 -0
  76. data/test/element_test.rb +10 -0
  77. data/test/emitter_test.rb +108 -0
  78. data/test/event_test.rb +19 -0
  79. data/test/focus_test.rb +37 -0
  80. data/test/geometry_test.rb +12 -0
  81. data/test/input_test.rb +47 -0
  82. data/test/key_test.rb +14 -0
  83. data/test/log_test.rb +21 -0
  84. data/test/node_test.rb +105 -0
  85. data/test/performance/performance1.rb +48 -0
  86. data/test/renderer_test.rb +74 -0
  87. data/test/renderer_test_rect.rb +4 -0
  88. data/test/screen_test.rb +58 -0
  89. data/test/style_test.rb +18 -0
  90. data/test/termgui_test.rb +10 -0
  91. data/test/test_all.rb +30 -0
  92. data/test/util_hash_object_test.rb +93 -0
  93. data/test/util_test.rb +26 -0
  94. data/test/widget/checkbox_test.rb +99 -0
  95. data/test/widget/col_test.rb +87 -0
  96. data/test/widget/inline_test.rb +40 -0
  97. data/test/widget/label_test.rb +94 -0
  98. data/test/widget/row_test.rb +40 -0
  99. data/test/wrap_test.rb +11 -0
  100. data/test/xml_test.rb +77 -0
  101. metadata +148 -0
@@ -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
@@ -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