belajar 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +16 -0
- data/.travis.yml +6 -0
- data/Gemfile +4 -0
- data/Guardfile +5 -0
- data/README.md +5 -0
- data/Rakefile +5 -0
- data/belajar.gemspec +38 -0
- data/bin/belajar +12 -0
- data/lib/belajar.rb +24 -0
- data/lib/belajar/chapter.rb +23 -0
- data/lib/belajar/configuration.rb +89 -0
- data/lib/belajar/congratulator.rb +13 -0
- data/lib/belajar/course.rb +80 -0
- data/lib/belajar/exceptions.rb +19 -0
- data/lib/belajar/generator.rb +60 -0
- data/lib/belajar/github_client.rb +30 -0
- data/lib/belajar/loadable.rb +23 -0
- data/lib/belajar/loading/chapters.rb +9 -0
- data/lib/belajar/loading/courses.rb +9 -0
- data/lib/belajar/loading/units.rb +9 -0
- data/lib/belajar/reference_solution.rb +18 -0
- data/lib/belajar/solution.rb +53 -0
- data/lib/belajar/storeable.rb +32 -0
- data/lib/belajar/task.rb +10 -0
- data/lib/belajar/terminal.rb +12 -0
- data/lib/belajar/terminal/cli.rb +59 -0
- data/lib/belajar/terminal/courses.rb +179 -0
- data/lib/belajar/terminal/output.rb +78 -0
- data/lib/belajar/terminal/setup.rb +115 -0
- data/lib/belajar/terminal/solutions.rb +46 -0
- data/lib/belajar/terminal/texts/about.txt +19 -0
- data/lib/belajar/terminal/texts/congratulations.txt +12 -0
- data/lib/belajar/terminal/texts/courses_empty.txt +3 -0
- data/lib/belajar/terminal/texts/hint_course_download.txt +13 -0
- data/lib/belajar/terminal/texts/welcome.txt +12 -0
- data/lib/belajar/terminal/welcome.rb +98 -0
- data/lib/belajar/test.rb +46 -0
- data/lib/belajar/test_result.rb +86 -0
- data/lib/belajar/unit.rb +28 -0
- data/lib/belajar/version.rb +3 -0
- data/lib/belajar/views.rb +51 -0
- data/lib/belajar/views/chapters_menu.rb +60 -0
- data/lib/belajar/views/courses_menu.rb +52 -0
- data/lib/belajar/views/main_menu.rb +37 -0
- data/lib/belajar/views/menu.rb +89 -0
- data/lib/belajar/views/splash.rb +57 -0
- data/lib/belajar/views/task_view.rb +236 -0
- data/lib/belajar/views/top_bar.rb +40 -0
- data/lib/belajar/views/units_menu.rb +61 -0
- data/lib/belajar/window.rb +215 -0
- data/spec/belajar/chapter_spec.rb +76 -0
- data/spec/belajar/configuration_spec.rb +161 -0
- data/spec/belajar/congratulator_spec.rb +24 -0
- data/spec/belajar/course_spec.rb +201 -0
- data/spec/belajar/generator_spec.rb +82 -0
- data/spec/belajar/github_client_spec.rb +53 -0
- data/spec/belajar/loading/chapters_spec.rb +16 -0
- data/spec/belajar/loading/courses_spec.rb +16 -0
- data/spec/belajar/loading/units_spec.rb +21 -0
- data/spec/belajar/reference_solution_spec.rb +41 -0
- data/spec/belajar/solution_spec.rb +86 -0
- data/spec/belajar/storeable_spec.rb +35 -0
- data/spec/belajar/task_spec.rb +23 -0
- data/spec/belajar/terminal/cli_spec.rb +51 -0
- data/spec/belajar/terminal/courses_spec.rb +293 -0
- data/spec/belajar/terminal/output_spec.rb +151 -0
- data/spec/belajar/terminal/setup_spec.rb +10 -0
- data/spec/belajar/terminal/solutions_spec.rb +8 -0
- data/spec/belajar/terminal/welcome_spec.rb +12 -0
- data/spec/belajar/terminal_spec.rb +24 -0
- data/spec/belajar/test_example_spec.rb +54 -0
- data/spec/belajar/test_result_spec.rb +91 -0
- data/spec/belajar/test_spec.rb +48 -0
- data/spec/belajar/unit_spec.rb +85 -0
- data/spec/belajar/views/chapters_menu_spec.rb +6 -0
- data/spec/belajar/views/courses_menu_spec.rb +6 -0
- data/spec/belajar/views/menu_spec.rb +19 -0
- data/spec/belajar/views/task_view_spec.rb +7 -0
- data/spec/belajar/views/units_menu_spec.rb +6 -0
- data/spec/belajar/views_spec.rb +21 -0
- data/spec/belajar_spec.rb +51 -0
- data/spec/path_helpers_spec.rb +60 -0
- data/spec/resource_helpers_spec.rb +33 -0
- data/spec/spec_helper.rb +28 -0
- data/spec/support/macros/content_helpers.rb +129 -0
- data/spec/support/macros/mock_helpers.rb +25 -0
- data/spec/support/macros/path_helpers.rb +139 -0
- data/spec/support/macros/resource_helpers.rb +157 -0
- data/spec/support/macros/test_helpers.rb +6 -0
- metadata +385 -0
data/lib/belajar/unit.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
module Belajar
|
2
|
+
class Unit
|
3
|
+
|
4
|
+
attr_reader :title, :task
|
5
|
+
|
6
|
+
def initialize(path)
|
7
|
+
@path = path
|
8
|
+
@title = File.basename(path).gsub(/\_+/, ' ')
|
9
|
+
end
|
10
|
+
|
11
|
+
def task
|
12
|
+
@task ||= Task.new(@path)
|
13
|
+
end
|
14
|
+
|
15
|
+
def reference_solution
|
16
|
+
@reference_solution ||= ReferenceSolution.new(@path)
|
17
|
+
end
|
18
|
+
|
19
|
+
def solution
|
20
|
+
@solution = Solution.new(@path)
|
21
|
+
end
|
22
|
+
|
23
|
+
def mastered?
|
24
|
+
solution.verified?
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'curses'
|
2
|
+
require 'active_support/concern'
|
3
|
+
require 'belajar/views/top_bar'
|
4
|
+
|
5
|
+
module Belajar
|
6
|
+
module Views
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
included do
|
10
|
+
include Curses
|
11
|
+
|
12
|
+
def reset_menu_position
|
13
|
+
@position = 0
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def default_window(height = nil, width = nil, top = 0, left = 0)
|
19
|
+
init_screen
|
20
|
+
|
21
|
+
noecho
|
22
|
+
crmode
|
23
|
+
curs_set(0) # invisible cursor
|
24
|
+
|
25
|
+
height ||= lines
|
26
|
+
width ||= cols + 1
|
27
|
+
|
28
|
+
window = Belajar::Window.new(height, width, top, left)
|
29
|
+
|
30
|
+
Curses.lines.times do |line|
|
31
|
+
window.setpos(line, 0)
|
32
|
+
window.clear_line
|
33
|
+
end
|
34
|
+
|
35
|
+
window.keypad(true)
|
36
|
+
window.scrollok(true)
|
37
|
+
window.refresh
|
38
|
+
window
|
39
|
+
end
|
40
|
+
|
41
|
+
def sub_window_below_top_bar(window, top_bar)
|
42
|
+
top_bar.show
|
43
|
+
top = top_bar.height
|
44
|
+
sub_window = window.subwin(window.maxy - top, window.maxx, top, 0)
|
45
|
+
sub_window.keypad(true)
|
46
|
+
sub_window
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'belajar/views/menu'
|
2
|
+
|
3
|
+
module Belajar
|
4
|
+
module Views
|
5
|
+
|
6
|
+
class ChaptersMenu < Menu
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def before_enter(*args)
|
11
|
+
@course = args[0]
|
12
|
+
end
|
13
|
+
|
14
|
+
def before_reenter(*args)
|
15
|
+
@course = args[0]
|
16
|
+
@chapter = args[1]
|
17
|
+
@position = @course.chapters.find_index(@chapter)
|
18
|
+
end
|
19
|
+
|
20
|
+
def header_text
|
21
|
+
"*#{@course.title}* - available chapters:"
|
22
|
+
end
|
23
|
+
|
24
|
+
def interact_with(window)
|
25
|
+
while char = window.getch
|
26
|
+
case char
|
27
|
+
when KEY_UP
|
28
|
+
@position -= 1
|
29
|
+
broadcast(:reset_menu_position)
|
30
|
+
when KEY_DOWN
|
31
|
+
@position += 1
|
32
|
+
broadcast(:reset_menu_position)
|
33
|
+
when 10 # Enter
|
34
|
+
broadcast(:enter, @course, models[@position])
|
35
|
+
return
|
36
|
+
when 263 # Backspace
|
37
|
+
broadcast(:reenter, @course)
|
38
|
+
return
|
39
|
+
when 27 # ESC
|
40
|
+
exit
|
41
|
+
end
|
42
|
+
|
43
|
+
@position = items.length - 1 if @position < 0
|
44
|
+
@position = 0 if @position >= items.length
|
45
|
+
draw(window, @position)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def models
|
50
|
+
@course.chapters
|
51
|
+
end
|
52
|
+
|
53
|
+
def items
|
54
|
+
models.map(&:title)
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'belajar/views/menu'
|
2
|
+
|
3
|
+
module Belajar
|
4
|
+
module Views
|
5
|
+
|
6
|
+
class CoursesMenu < Menu
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def header_text
|
11
|
+
'Available belajar courses:'
|
12
|
+
end
|
13
|
+
|
14
|
+
def interact_with(window)
|
15
|
+
while char = window.getch
|
16
|
+
case char
|
17
|
+
when KEY_UP
|
18
|
+
@position -= 1
|
19
|
+
broadcast(:reset_menu_position)
|
20
|
+
when KEY_DOWN
|
21
|
+
@position += 1
|
22
|
+
broadcast(:reset_menu_position)
|
23
|
+
when 10 # Enter
|
24
|
+
broadcast(:enter, models[@position])
|
25
|
+
return
|
26
|
+
when 27 # ESC
|
27
|
+
exit
|
28
|
+
end
|
29
|
+
|
30
|
+
@position = items.length - 1 if @position < 0
|
31
|
+
@position = 0 if @position >= items.length
|
32
|
+
draw(window, @position)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def models
|
37
|
+
Loading::Courses.load(Belajar.config.courses_path)
|
38
|
+
end
|
39
|
+
|
40
|
+
def items
|
41
|
+
non_empty_courses = models.select { |course| !course.chapters.empty? }
|
42
|
+
|
43
|
+
non_empty_courses.map do |course|
|
44
|
+
line = "#{course.title}"
|
45
|
+
self.items_info <<= [(course.author ? "(by #{course.author})" : '')]
|
46
|
+
line
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Belajar
|
2
|
+
module Views
|
3
|
+
|
4
|
+
class MainMenu
|
5
|
+
include Views
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
courses_menu = Views::CoursesMenu.new
|
9
|
+
chapters_menu = Views::ChaptersMenu.new
|
10
|
+
units_menu = Views::UnitsMenu.new
|
11
|
+
task_view = Views::TaskView.new
|
12
|
+
|
13
|
+
# Subscription: `first.subscribe(second)` means
|
14
|
+
# first subscribes second on the first's broadcast.
|
15
|
+
# second has to have method that is broadcasted.
|
16
|
+
|
17
|
+
# top down navigation
|
18
|
+
courses_menu.subscribe(chapters_menu, on: :enter)
|
19
|
+
chapters_menu.subscribe(units_menu, on: :enter)
|
20
|
+
units_menu.subscribe(task_view, on: :enter)
|
21
|
+
|
22
|
+
# bottom up navigation
|
23
|
+
chapters_menu.subscribe(courses_menu, on: :reenter)
|
24
|
+
units_menu.subscribe(chapters_menu, on: :reenter)
|
25
|
+
task_view.subscribe(units_menu, on: :reenter)
|
26
|
+
|
27
|
+
# position reset
|
28
|
+
courses_menu.subscribe(chapters_menu, on: :reset_menu_position)
|
29
|
+
courses_menu.subscribe(units_menu, on: :reset_menu_position)
|
30
|
+
chapters_menu.subscribe(units_menu, on: :reset_menu_position)
|
31
|
+
|
32
|
+
courses_menu.enter
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'wisper'
|
2
|
+
|
3
|
+
module Belajar
|
4
|
+
module Views
|
5
|
+
|
6
|
+
class Menu
|
7
|
+
include Views
|
8
|
+
include Wisper::Publisher
|
9
|
+
|
10
|
+
TOP_BAR_TEXT = [
|
11
|
+
'Use *UP KEY* and *DOWN KEY* for menu navigation',
|
12
|
+
'Enter menu with *RETURN*',
|
13
|
+
'Go back with *BACKSPACE*',
|
14
|
+
'Exit with *ESC*'
|
15
|
+
].join(' | ')
|
16
|
+
|
17
|
+
def initialize
|
18
|
+
@position = 0
|
19
|
+
end
|
20
|
+
|
21
|
+
def enter(*args)
|
22
|
+
if self.class.private_method_defined?(:before_enter)
|
23
|
+
before_enter(*args)
|
24
|
+
end
|
25
|
+
|
26
|
+
@window = default_window
|
27
|
+
top_bar = TopBar.new(@window, TOP_BAR_TEXT)
|
28
|
+
show sub_window_below_top_bar(@window, top_bar)
|
29
|
+
end
|
30
|
+
|
31
|
+
def reenter(*args)
|
32
|
+
if self.class.private_method_defined?(:before_reenter)
|
33
|
+
before_reenter(*args)
|
34
|
+
end
|
35
|
+
|
36
|
+
enter(*args)
|
37
|
+
end
|
38
|
+
|
39
|
+
protected
|
40
|
+
|
41
|
+
def show(window)
|
42
|
+
draw(window, @position)
|
43
|
+
interact_with(window)
|
44
|
+
end
|
45
|
+
|
46
|
+
def draw(window, active_index = 0)
|
47
|
+
window.attrset(A_NORMAL)
|
48
|
+
window.setpos(0, 1)
|
49
|
+
window.print_markdown header_text
|
50
|
+
|
51
|
+
items.each_with_index do |item, index|
|
52
|
+
window.setpos(index + 2, 1)
|
53
|
+
window.print_indicator(models[index])
|
54
|
+
window.attrset(index == active_index ? A_STANDOUT : A_NORMAL)
|
55
|
+
window.write " #{item.to_s} "
|
56
|
+
window.attrset(A_NORMAL)
|
57
|
+
window.write " #{items_info[index].try(:join, ' ')}"
|
58
|
+
end
|
59
|
+
|
60
|
+
window.refresh
|
61
|
+
end
|
62
|
+
|
63
|
+
def interact_with(window)
|
64
|
+
raise "Please implement the method #interact_with!"
|
65
|
+
end
|
66
|
+
|
67
|
+
def models
|
68
|
+
raise "Please implement the method #models!"
|
69
|
+
end
|
70
|
+
|
71
|
+
def items
|
72
|
+
raise "Please implement the method #items!"
|
73
|
+
end
|
74
|
+
|
75
|
+
def header_text
|
76
|
+
raise "Please implement the method #header_text!"
|
77
|
+
end
|
78
|
+
|
79
|
+
def items_info
|
80
|
+
@items_info || []
|
81
|
+
end
|
82
|
+
|
83
|
+
def items_info=(items_info)
|
84
|
+
@items_info = items_info
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Belajar
|
2
|
+
module Views
|
3
|
+
|
4
|
+
class Splash
|
5
|
+
include Views
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
title = "BELAJAR"
|
9
|
+
subtitle = "Learning the Ruby programming language dead easy."
|
10
|
+
|
11
|
+
panel = default_window
|
12
|
+
|
13
|
+
lines.times do |line|
|
14
|
+
panel.setpos(line, 0)
|
15
|
+
panel.red(' ' * cols, Curses::A_STANDOUT)
|
16
|
+
end
|
17
|
+
|
18
|
+
panel.setpos((lines / 4), (cols - title.length) / 2)
|
19
|
+
panel.red(title, Curses::A_STANDOUT)
|
20
|
+
panel.refresh
|
21
|
+
|
22
|
+
sleep 0.5
|
23
|
+
|
24
|
+
ruby_ascii_art.each_with_index do |line, index|
|
25
|
+
panel.setpos(lines / 4 + 2 + index, (cols - line.length) / 2)
|
26
|
+
panel.red(line, Curses::A_STANDOUT)
|
27
|
+
sleep 0.06
|
28
|
+
panel.refresh
|
29
|
+
end
|
30
|
+
|
31
|
+
panel.setpos(lines / 4 + 11, (cols - subtitle.length) / 2)
|
32
|
+
|
33
|
+
subtitle.chars do |char|
|
34
|
+
panel.red(char, Curses::A_STANDOUT)
|
35
|
+
panel.refresh
|
36
|
+
sleep 0.02
|
37
|
+
end
|
38
|
+
|
39
|
+
sleep 2.5
|
40
|
+
|
41
|
+
close_screen
|
42
|
+
end
|
43
|
+
|
44
|
+
def ruby_ascii_art
|
45
|
+
[
|
46
|
+
" ___________ ",
|
47
|
+
" /.\\ /.\\ /.\\ ",
|
48
|
+
"/___\\/___\\/___\\",
|
49
|
+
" \\ \\ . / . / ",
|
50
|
+
" \\ \\ ./ ./ ",
|
51
|
+
" \\\\ / / ",
|
52
|
+
" \\./ "
|
53
|
+
]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,236 @@
|
|
1
|
+
module Belajar
|
2
|
+
module Views
|
3
|
+
require 'wisper'
|
4
|
+
require 'os'
|
5
|
+
|
6
|
+
class TaskView
|
7
|
+
include Views
|
8
|
+
include Wisper::Publisher
|
9
|
+
|
10
|
+
TOP_BAR_TEXT = [
|
11
|
+
'Scroll with *UP KEY* and *DOWN KEY*',
|
12
|
+
'Verify solution with *v*',
|
13
|
+
'Open solution file with *o*',
|
14
|
+
'Clear validation with *c*',
|
15
|
+
'Exit with *ESC*'
|
16
|
+
].join(' | ')
|
17
|
+
|
18
|
+
def initialize
|
19
|
+
@lines = []
|
20
|
+
@top = nil
|
21
|
+
end
|
22
|
+
|
23
|
+
def enter(course, chapter, unit)
|
24
|
+
@course = course
|
25
|
+
@chapter = chapter
|
26
|
+
@unit = unit
|
27
|
+
|
28
|
+
@test_result_lines = nil
|
29
|
+
@lines = @unit.task.markdown.lines
|
30
|
+
@top_bar_height = 4
|
31
|
+
@head_height = 2
|
32
|
+
|
33
|
+
initialize_window(@lines.count + @top_bar_height + @head_height)
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def set_head(window)
|
39
|
+
window.setpos(0, 1)
|
40
|
+
window.clear_line
|
41
|
+
|
42
|
+
text = "*#{@course.title}* > *#{@chapter.title}* > *#{@unit.title}*:"
|
43
|
+
window.print_markdown(text)
|
44
|
+
|
45
|
+
window.setpos(1, 1)
|
46
|
+
window.clear_line
|
47
|
+
end
|
48
|
+
|
49
|
+
def show(window)
|
50
|
+
window.scrollok(true)
|
51
|
+
draw(window)
|
52
|
+
interact_with(window)
|
53
|
+
end
|
54
|
+
|
55
|
+
def draw(window)
|
56
|
+
@top = 0
|
57
|
+
window.attrset(A_NORMAL)
|
58
|
+
set_head(window)
|
59
|
+
|
60
|
+
@lines.each_with_index do |line, index|
|
61
|
+
window.setpos(index + 2, 1)
|
62
|
+
print_line(window, line, index)
|
63
|
+
end
|
64
|
+
|
65
|
+
window.setpos(0, 1)
|
66
|
+
window.refresh
|
67
|
+
end
|
68
|
+
|
69
|
+
def scroll_up(window)
|
70
|
+
if @top > 0
|
71
|
+
window.scrl(-1)
|
72
|
+
set_head(window)
|
73
|
+
|
74
|
+
@top -= 1
|
75
|
+
line = @lines[@top]
|
76
|
+
|
77
|
+
if line
|
78
|
+
window.setpos(2, 1)
|
79
|
+
print_line(window, line, @top)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def scroll_down(window)
|
85
|
+
if @top + Curses.lines <= @lines.count + @top_bar_height + @head_height
|
86
|
+
window.scrl(1)
|
87
|
+
set_head(window)
|
88
|
+
|
89
|
+
@top += 1
|
90
|
+
line = @lines[@top + window.maxy - 1]
|
91
|
+
window.setpos(window.maxy - 1, 1)
|
92
|
+
|
93
|
+
if line
|
94
|
+
print_line(window, line, @top)
|
95
|
+
else
|
96
|
+
window.clear_line
|
97
|
+
end
|
98
|
+
|
99
|
+
return true
|
100
|
+
else
|
101
|
+
return false
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def print_line(window, line, index)
|
106
|
+
if @test_result_lines && index.between?(0, @test_result_lines.count + 1)
|
107
|
+
if @unit.mastered?
|
108
|
+
window.green(line, A_STANDOUT, full_line: true)
|
109
|
+
else
|
110
|
+
example_index = example_index(@example_heights, index)
|
111
|
+
|
112
|
+
if @examples[example_index].passed?
|
113
|
+
window.green(line, A_STANDOUT, full_line: true)
|
114
|
+
else
|
115
|
+
window.red(line, A_STANDOUT, full_line: true)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
else
|
119
|
+
window.print_markdown(line.strip)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def interact_with(window)
|
124
|
+
while char = window.getch
|
125
|
+
scrollable = true
|
126
|
+
|
127
|
+
case char
|
128
|
+
when 'v' # verify
|
129
|
+
print_test_results(window)
|
130
|
+
return
|
131
|
+
when 'c' # clear
|
132
|
+
reset_screen(window)
|
133
|
+
return
|
134
|
+
when 'o' # open solution file
|
135
|
+
open_editor(@unit.solution.path)
|
136
|
+
when Curses::KEY_DOWN, Curses::KEY_CTRL_N
|
137
|
+
scrollable = scroll_down(window)
|
138
|
+
when Curses::KEY_UP, Curses::KEY_CTRL_P
|
139
|
+
scrollable = scroll_up(window)
|
140
|
+
when Curses::KEY_NPAGE, ?\s # white space
|
141
|
+
0.upto(window.maxy - 2) do |n|
|
142
|
+
scrolled = scroll_down(window)
|
143
|
+
|
144
|
+
unless scrolled
|
145
|
+
scrollable = false if n == 0
|
146
|
+
break
|
147
|
+
end
|
148
|
+
end
|
149
|
+
when Curses::KEY_PPAGE
|
150
|
+
0.upto(window.maxy - 2) do |n|
|
151
|
+
scrolled = scroll_up(window)
|
152
|
+
|
153
|
+
unless scrolled
|
154
|
+
scrollable = false if n == 0
|
155
|
+
break
|
156
|
+
end
|
157
|
+
end
|
158
|
+
when Curses::KEY_LEFT, Curses::KEY_CTRL_T
|
159
|
+
while scroll_up(window)
|
160
|
+
end
|
161
|
+
when Curses::KEY_RIGHT, Curses::KEY_CTRL_B
|
162
|
+
while scroll_down(window)
|
163
|
+
end
|
164
|
+
when Curses::KEY_BACKSPACE
|
165
|
+
broadcast(:reenter, @course, @chapter, @unit)
|
166
|
+
return
|
167
|
+
when 27 # ESC
|
168
|
+
exit
|
169
|
+
end
|
170
|
+
|
171
|
+
Curses.beep unless scrollable
|
172
|
+
window.setpos(0, 1)
|
173
|
+
window.refresh
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def open_editor(file_path)
|
178
|
+
if OS.windows?
|
179
|
+
system "start '' '#{file_path}'"
|
180
|
+
elsif OS.mac?
|
181
|
+
system "open '#{file_path}'"
|
182
|
+
elsif OS.linux?
|
183
|
+
system "xdg-open '#{file_path}'"
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
def print_test_results(window)
|
188
|
+
result = @unit.solution.verify!
|
189
|
+
@test_result_lines = result.summary_lines
|
190
|
+
|
191
|
+
if result.passed?
|
192
|
+
code_lines = @unit.reference_solution.code_lines
|
193
|
+
|
194
|
+
unless code_lines.empty?
|
195
|
+
code_lines.map! { |line| " #{line}" }
|
196
|
+
code_lines.unshift('', "Reference code:", '')
|
197
|
+
end
|
198
|
+
|
199
|
+
@test_result_lines += code_lines
|
200
|
+
end
|
201
|
+
|
202
|
+
@lines = [''] + @test_result_lines + ['', ''] + @unit.task.markdown.lines
|
203
|
+
@examples = result.examples
|
204
|
+
|
205
|
+
@example_heights = @examples.reduce({}) do |hash, example|
|
206
|
+
start = hash.values.reduce(0) { |sum, r| sum += r.count }
|
207
|
+
range = (start..(start + example.message.split("\n").count) + 2)
|
208
|
+
hash[hash.keys.count] = range
|
209
|
+
hash
|
210
|
+
end
|
211
|
+
|
212
|
+
height = [@lines.count + @top_bar_height + @head_height, Curses.lines].max
|
213
|
+
initialize_window(height)
|
214
|
+
end
|
215
|
+
|
216
|
+
def reset_screen(window)
|
217
|
+
@test_result_lines = nil
|
218
|
+
@lines = @unit.task.markdown.lines
|
219
|
+
height = [@lines.count + @top_bar_height + @head_height, Curses.lines].max
|
220
|
+
initialize_window(height)
|
221
|
+
end
|
222
|
+
|
223
|
+
def initialize_window(height)
|
224
|
+
@window = default_window(height)
|
225
|
+
|
226
|
+
top_bar = TopBar.new(@window, TOP_BAR_TEXT)
|
227
|
+
show sub_window_below_top_bar(@window, top_bar)
|
228
|
+
end
|
229
|
+
|
230
|
+
def example_index(heights, index)
|
231
|
+
heights.values.index { |range| range.include?(index) }
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
end
|
236
|
+
end
|