raindown 0.5.1 → 0.7.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/lib/raindown/chart/chart.rb +12 -1
- data/lib/raindown/cli.rb +2 -2
- data/lib/raindown/document.rb +16 -9
- data/lib/raindown/endpoint/curl.rb +19 -0
- data/lib/raindown/node_blocks.rb +9 -6
- data/lib/raindown/nodes.rb +1 -0
- data/lib/raindown/projects.rb +8 -4
- data/lib/raindown/stat.rb +53 -0
- data/lib/raindown/tui/card.rb +56 -0
- data/lib/raindown/tui/endpoint.rb +16 -0
- data/lib/raindown/tui/endpoints.rb +40 -0
- data/lib/raindown/tui/events.rb +50 -0
- data/lib/raindown/tui/feelings/batch.rb +47 -0
- data/lib/raindown/tui/feelings/bins.rb +67 -0
- data/lib/raindown/tui/feelings/celebration.rb +25 -0
- data/lib/raindown/tui/feelings/code_box.rb +129 -0
- data/lib/raindown/tui/feelings/code_methods.rb +134 -0
- data/lib/raindown/tui/feelings/feelings_log.rb +133 -0
- data/lib/raindown/tui/feelings/feelings_renderer.rb +184 -0
- data/lib/raindown/tui/feelings/feelings_view.rb +161 -0
- data/lib/raindown/tui/feelings/fireworks.rb +75 -0
- data/lib/raindown/tui/feelings/rain.rb +66 -0
- data/lib/raindown/tui/feelings/scene.rb +37 -0
- data/lib/raindown/tui/feelings/waffle.rb +43 -0
- data/lib/raindown/tui/feelings/weekly_batch.rb +32 -0
- data/lib/raindown/tui/file_tree.rb +55 -12
- data/lib/raindown/tui/highlighter.rb +45 -0
- data/lib/raindown/tui/layout.rb +66 -77
- data/lib/raindown/tui/menu_overlay.rb +59 -0
- data/lib/raindown/tui/navigator/commands.rb +97 -0
- data/lib/raindown/tui/navigator/focus.rb +25 -0
- data/lib/raindown/tui/navigator/navigator.rb +160 -0
- data/lib/raindown/tui/navigator/project_menu.rb +61 -0
- data/lib/raindown/tui/navigator/search.rb +126 -0
- data/lib/raindown/tui/navigator/views.rb +52 -0
- data/lib/raindown/tui/renderers/dashboard_renderer.rb +102 -0
- data/lib/raindown/tui/renderers/endpoint_renderer.rb +81 -0
- data/lib/raindown/tui/renderers/list_renderer.rb +15 -0
- data/lib/raindown/tui/renderers/outline_renderer.rb +21 -0
- data/lib/raindown/tui/renderers/renderer.rb +18 -0
- data/lib/raindown/tui/row.rb +12 -3
- data/lib/raindown/tui/rows.rb +58 -0
- data/lib/raindown/tui/search_overlay.rb +73 -0
- data/lib/raindown/tui/terminal/input.rb +91 -0
- data/lib/raindown/tui/terminal/ncurses_shim.rb +46 -0
- data/lib/raindown/tui/terminal/quiet.rb +32 -0
- data/lib/raindown/tui/{screen.rb → terminal/screen.rb} +42 -24
- data/lib/raindown/tui/terminal/styles.rb +154 -0
- data/lib/raindown/tui/text.rb +110 -0
- data/lib/raindown/tui/title.rb +29 -0
- data/lib/raindown/tui/views/dashboard_view.rb +147 -0
- data/lib/raindown/tui/views/endpoint_view.rb +77 -0
- data/lib/raindown/tui/{pane.rb → views/list_view.rb} +11 -8
- data/lib/raindown/tui/{outline.rb → views/outline_view.rb} +48 -20
- data/lib/raindown/tui/workers/project.rb +122 -0
- data/lib/raindown/tui/workers/worker.rb +124 -0
- data/lib/raindown/tui/workers/workers.rb +53 -0
- data/lib/raindown/tui.rb +10 -5
- data/lib/version.rb +1 -1
- metadata +63 -9
- data/lib/raindown/tui/commands.rb +0 -70
- data/lib/raindown/tui/input.rb +0 -55
- data/lib/raindown/tui/navigator.rb +0 -123
- data/lib/raindown/tui/styles.rb +0 -36
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative '../actions'
|
|
4
|
-
|
|
5
|
-
module Raindown
|
|
6
|
-
module TUI
|
|
7
|
-
# What a Navigator does to the open file: run an endpoint, open the editor, write back and read it again.
|
|
8
|
-
# Each sets the status, so the Screen shows how it went, and returns what the Screen should do next, if anything.
|
|
9
|
-
module Commands
|
|
10
|
-
private
|
|
11
|
-
|
|
12
|
-
def execute
|
|
13
|
-
directive = selected(:outline)&.target if @focus == :outline
|
|
14
|
-
return error('Select an endpoint to run') unless directive.is_a?(Document::Directive) && directive.endpoint?
|
|
15
|
-
|
|
16
|
-
[:wait, "Running the endpoint on line #{directive.line}...", -> { run_endpoint(directive) }]
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def run_endpoint(directive)
|
|
20
|
-
changed = Actions.execute(path, directive)
|
|
21
|
-
@outline.read
|
|
22
|
-
info(changed ? "Ran the endpoint on line #{directive.line}" : 'The response is the same')
|
|
23
|
-
rescue StandardError => e
|
|
24
|
-
error(e.message)
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
# In the files pane, the selected file opens at its top, and in the outline, the open file at the selected line.
|
|
28
|
-
def edit
|
|
29
|
-
row = selected
|
|
30
|
-
show(row.target) if @focus == :files && row&.kind == :file
|
|
31
|
-
return error('Open a file to edit') unless path
|
|
32
|
-
|
|
33
|
-
line = @focus == :outline && row ? row.line : 1
|
|
34
|
-
[:run, Actions.edit_command(path, line:)]
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
def write_back
|
|
38
|
-
return error('Open a file to write back') unless path
|
|
39
|
-
|
|
40
|
-
changed = Actions.write_back(path)
|
|
41
|
-
@outline.read
|
|
42
|
-
info(changed ? "Updated #{relative(path)}" : "#{relative(path)} is up to date")
|
|
43
|
-
rescue StandardError => e
|
|
44
|
-
error(e.message)
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
def reload
|
|
48
|
-
@files.load
|
|
49
|
-
@outline&.read if path && File.exist?(path)
|
|
50
|
-
info('Reloaded')
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
# Reads the file again when it's changed, like when it's saved in an editor or a watcher writes back to it.
|
|
54
|
-
def refresh
|
|
55
|
-
@outline.read if @outline&.changed?
|
|
56
|
-
nil
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
def info(message)
|
|
60
|
-
@status = [:info, message]
|
|
61
|
-
nil
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
def error(message)
|
|
65
|
-
@status = [:error, message]
|
|
66
|
-
nil
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
end
|
|
70
|
-
end
|
data/lib/raindown/tui/input.rb
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'curses'
|
|
4
|
-
require 'unicode/display_width'
|
|
5
|
-
|
|
6
|
-
module Raindown
|
|
7
|
-
module TUI
|
|
8
|
-
# Turns curses' keys and clicks into a Navigator's events.
|
|
9
|
-
module Input
|
|
10
|
-
KEYS = {
|
|
11
|
-
Curses::KEY_UP => :up, 'k' => :up, Curses::KEY_DOWN => :down, 'j' => :down,
|
|
12
|
-
Curses::KEY_LEFT => :left, 'h' => :left, Curses::KEY_RIGHT => :right, 'l' => :right,
|
|
13
|
-
"\t" => :tab, Curses::KEY_BTAB => :tab, "\r" => :enter, "\n" => :enter, Curses::KEY_ENTER => :enter,
|
|
14
|
-
' ' => :toggle, 'x' => :execute, 'e' => :edit, 'w' => :write_back, 'r' => :reload, 'q' => :quit
|
|
15
|
-
}.freeze
|
|
16
|
-
|
|
17
|
-
module_function
|
|
18
|
-
|
|
19
|
-
# A key from getch, which is a string for a printable character and otherwise a number.
|
|
20
|
-
def key_event(key)
|
|
21
|
-
key = key.chr if key.is_a?(Integer) && key < 256
|
|
22
|
-
KEYS[key]
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
# A scroll of the wheel moves the cursor, and a click sends the event of what's under it.
|
|
26
|
-
def mouse_event(event, zones)
|
|
27
|
-
wheel.find { |button, _| event.bstate.anybits?(button) }&.last || click(zones, column: event.x, row: event.y)
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
# Older ncurses has no fifth button, which is the wheel scrolling down.
|
|
31
|
-
def wheel
|
|
32
|
-
{ Curses::BUTTON4_PRESSED => :up }.tap do |buttons|
|
|
33
|
-
buttons[Curses::BUTTON5_PRESSED] = :down if Curses.const_defined?(:BUTTON5_PRESSED)
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
# Where each of a Layout's segments with an event is on the screen, as [row, columns, event].
|
|
38
|
-
def zones(lines)
|
|
39
|
-
lines.each_with_index.flat_map do |segments, row|
|
|
40
|
-
column = 0
|
|
41
|
-
segments.filter_map do |segment|
|
|
42
|
-
width = Unicode::DisplayWidth.of(segment.text)
|
|
43
|
-
zone = [row, column...(column + width), segment.event] if segment.event
|
|
44
|
-
column += width
|
|
45
|
-
zone
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def click(zones, column:, row:)
|
|
51
|
-
zones.find { |zone_row, columns, _event| zone_row == row && columns.cover?(column) }&.last
|
|
52
|
-
end
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
end
|
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative 'file_tree'
|
|
4
|
-
require_relative 'commands'
|
|
5
|
-
require_relative 'outline'
|
|
6
|
-
require_relative 'pane'
|
|
7
|
-
|
|
8
|
-
module Raindown
|
|
9
|
-
module TUI
|
|
10
|
-
# What the terminal UI shows and does, in plain Ruby, with no terminal: a FileTree of the project's Markdown files,
|
|
11
|
-
# beside the open file's Outline. The Screen draws it and sends it events, like :down or
|
|
12
|
-
# [:press, :outline, 3, :execute], and handle returns what the Screen should do next:
|
|
13
|
-
# nil draw again
|
|
14
|
-
# :quit stop
|
|
15
|
-
# [:run, command] leave the screen to run a command, like the editor, then call ran
|
|
16
|
-
# [:wait, message, task] show the message while the task runs, like an endpoint's request
|
|
17
|
-
class Navigator
|
|
18
|
-
include Commands
|
|
19
|
-
|
|
20
|
-
# Keys of the events that move the cursor or the focus, and those that act on what's selected.
|
|
21
|
-
MOVES = { up: -1, down: 1 }.freeze
|
|
22
|
-
PANES = { tab: nil, left: :files, right: :outline }.freeze
|
|
23
|
-
ACTIONS = { execute: :execute, edit: :edit, write_back: :write_back, reload: :reload, tick: :refresh }.freeze
|
|
24
|
-
|
|
25
|
-
attr_reader :focus, :status
|
|
26
|
-
|
|
27
|
-
def initialize(root:, path: nil)
|
|
28
|
-
@files = FileTree.new(root:)
|
|
29
|
-
@focus = :files
|
|
30
|
-
@panes = { files: Pane.new { @files.rows }, outline: Pane.new { @outline ? @outline.rows : [] } }
|
|
31
|
-
@status = [:info, 'Tab switches panes, Enter opens and folds, q quits']
|
|
32
|
-
first = @files.rows.find { it.kind == :file }&.target
|
|
33
|
-
show(path || first) if path || first
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def handle(event)
|
|
37
|
-
name, *args = event
|
|
38
|
-
return :quit if name == :quit
|
|
39
|
-
return move(MOVES[name]) if MOVES.key?(name)
|
|
40
|
-
return switch(PANES[name] || other_pane) if PANES.key?(name)
|
|
41
|
-
return select(*args) if name == :select
|
|
42
|
-
return press(*args) if name == :press
|
|
43
|
-
return activate(name) if %i[enter toggle].include?(name)
|
|
44
|
-
|
|
45
|
-
send(ACTIONS[name]) if ACTIONS.key?(name)
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
# After the editor closes, the file is written back, like a save while it's watched, and read again.
|
|
49
|
-
def ran
|
|
50
|
-
write_back
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
def root = @files.root
|
|
54
|
-
def path = @outline&.path
|
|
55
|
-
def relative(path) = @files.relative(path)
|
|
56
|
-
|
|
57
|
-
def rows(pane) = @panes[pane].rows
|
|
58
|
-
def cursor(pane) = @panes[pane].cursor
|
|
59
|
-
def visible(pane, height) = @panes[pane].visible(height)
|
|
60
|
-
|
|
61
|
-
private
|
|
62
|
-
|
|
63
|
-
def show(path)
|
|
64
|
-
@outline = Outline.new(File.expand_path(path))
|
|
65
|
-
@panes[:outline].reset
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
def other_pane
|
|
69
|
-
@focus == :files ? :outline : :files
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
def move(by)
|
|
73
|
-
@panes[@focus].move(by)
|
|
74
|
-
nil
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
def switch(pane)
|
|
78
|
-
@focus = pane
|
|
79
|
-
nil
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
def select(pane, index)
|
|
83
|
-
@focus = pane
|
|
84
|
-
@panes[pane].cursor = index
|
|
85
|
-
nil
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
def press(pane, index, event)
|
|
89
|
-
select(pane, index)
|
|
90
|
-
handle(event)
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
def selected(pane = @focus)
|
|
94
|
-
@panes[pane].selected
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
# Enter opens what's selected, or folds it, and Space only folds.
|
|
98
|
-
# A line of output, or a directive without any, opens in the editor.
|
|
99
|
-
def activate(name)
|
|
100
|
-
row = selected
|
|
101
|
-
return edit if name == :enter && editable?(row)
|
|
102
|
-
|
|
103
|
-
case row&.kind
|
|
104
|
-
when :folder then @files.toggle(row.target)
|
|
105
|
-
when :file then open_file(row.target) if name == :enter
|
|
106
|
-
when :heading then @outline.fold(row.target)
|
|
107
|
-
when :directive then @outline.expand(row.target) if row.target.output
|
|
108
|
-
end
|
|
109
|
-
nil
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
def editable?(row)
|
|
113
|
-
row&.kind == :output || (row&.kind == :directive && !row.target.output)
|
|
114
|
-
end
|
|
115
|
-
|
|
116
|
-
def open_file(path)
|
|
117
|
-
show(path)
|
|
118
|
-
@focus = :outline
|
|
119
|
-
info("Opened #{relative(path)}")
|
|
120
|
-
end
|
|
121
|
-
end
|
|
122
|
-
end
|
|
123
|
-
end
|
data/lib/raindown/tui/styles.rb
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'curses'
|
|
4
|
-
|
|
5
|
-
module Raindown
|
|
6
|
-
module TUI
|
|
7
|
-
# The curses attributes of a Layout's styles, in colour on the terminal's own background when it has colours.
|
|
8
|
-
module Styles
|
|
9
|
-
COLORS = { directive: Curses::COLOR_CYAN, button: Curses::COLOR_YELLOW, error: Curses::COLOR_RED,
|
|
10
|
-
folder: Curses::COLOR_BLUE }.freeze
|
|
11
|
-
|
|
12
|
-
module_function
|
|
13
|
-
|
|
14
|
-
# Sets up the colours, once the screen has started, and returns the attribute of each style.
|
|
15
|
-
def start
|
|
16
|
-
colors = Curses.has_colors?
|
|
17
|
-
start_colors if colors
|
|
18
|
-
color = ->(name) { colors ? Curses.color_pair(COLORS.keys.index(name) + 1) : Curses::A_NORMAL }
|
|
19
|
-
|
|
20
|
-
{
|
|
21
|
-
title: Curses::A_REVERSE | Curses::A_BOLD, selected: Curses::A_REVERSE, cursor: Curses::A_UNDERLINE,
|
|
22
|
-
heading: Curses::A_BOLD, directive: color[:directive], output: Curses::A_DIM,
|
|
23
|
-
error: color[:error] | Curses::A_BOLD, button: color[:button] | Curses::A_BOLD,
|
|
24
|
-
selected_button: color[:button] | Curses::A_REVERSE, folder: color[:folder] | Curses::A_BOLD,
|
|
25
|
-
key: Curses::A_REVERSE, border: Curses::A_DIM
|
|
26
|
-
}
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def start_colors
|
|
30
|
-
Curses.start_color
|
|
31
|
-
Curses.use_default_colors
|
|
32
|
-
COLORS.each_value.with_index(1) { |color, pair| Curses.init_pair(pair, color, -1) }
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
end
|