amun 0.1.3 → 0.2.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 +5 -5
- data/.rspec +1 -0
- data/.rubocop.yml +6 -1
- data/.ruby-version +1 -1
- data/Guardfile +26 -6
- data/README.md +1 -0
- data/Rakefile +4 -6
- data/_config.yml +1 -0
- data/amun.gemspec +6 -5
- data/exe/amun +1 -1
- data/lib/amun/application.rb +11 -5
- data/lib/amun/behaviours/emacs.rb +14 -122
- data/lib/amun/behaviours/erasing.rb +67 -0
- data/lib/amun/behaviours/insertion.rb +22 -0
- data/lib/amun/behaviours/movement.rb +83 -0
- data/lib/amun/buffer.rb +103 -0
- data/lib/amun/event_manager.rb +12 -3
- data/lib/amun/features/echo_event.rb +4 -1
- data/lib/amun/features/files.rb +26 -0
- data/lib/amun/helpers/colors.rb +8 -7
- data/lib/amun/helpers/keyboard.rb +25 -8
- data/lib/amun/major_modes/fundamental.rb +8 -40
- data/lib/amun/major_modes/irb.rb +40 -0
- data/lib/amun/mode_line_segments/buffer_name.rb +10 -0
- data/lib/amun/mode_line_segments/major_mode.rb +11 -0
- data/lib/amun/object.rb +21 -0
- data/lib/amun/primitives/rect.rb +15 -0
- data/lib/amun/version.rb +1 -1
- data/lib/amun/windows/base.rb +42 -0
- data/lib/amun/windows/buffer_window.rb +73 -0
- data/lib/amun/windows/echo_area.rb +34 -0
- data/lib/amun/windows/frame.rb +92 -0
- data/lib/amun/windows/mini_buffer_window.rb +126 -0
- data/lib/amun/windows/mode_line.rb +50 -0
- data/lib/amun/windows/text_renderer.rb +41 -0
- metadata +56 -31
- data/lib/amun/ui/buffer.rb +0 -90
- data/lib/amun/ui/echo_area.rb +0 -42
- data/lib/amun/ui/mode_line.rb +0 -44
- data/lib/amun/ui/mode_line_segments/buffer_name.rb +0 -16
- data/lib/amun/ui/mode_line_segments/major_mode.rb +0 -17
- data/lib/amun/ui/windows/buffer_window.rb +0 -40
- data/lib/amun/ui/windows/frame.rb +0 -75
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
require 'amun/object'
|
|
2
|
+
require 'forwardable'
|
|
3
|
+
require 'curses'
|
|
4
|
+
|
|
5
|
+
module Amun
|
|
6
|
+
module Windows
|
|
7
|
+
# based on amun object, means it has event manager inside
|
|
8
|
+
# and respond to all event manager methods (bind, undind, trigger...etc)
|
|
9
|
+
# and has a size (Rect instance), it also expose the methods of the size
|
|
10
|
+
# to the public, it also creates a subwindow from the curses standard screen
|
|
11
|
+
# and resizes it whenever you set a new (size) value
|
|
12
|
+
class Base < Object
|
|
13
|
+
extend Forwardable
|
|
14
|
+
|
|
15
|
+
attr_reader :size
|
|
16
|
+
def_delegators :size, :top, :left, :width, :height
|
|
17
|
+
|
|
18
|
+
def initialize(size)
|
|
19
|
+
super()
|
|
20
|
+
@size = size
|
|
21
|
+
@curses_window = Curses.stdscr.subwin(height, width, top, left)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# change the object size
|
|
25
|
+
# the internal curses window will be
|
|
26
|
+
# resized and moved along with it
|
|
27
|
+
def size=(size)
|
|
28
|
+
@size = size
|
|
29
|
+
resize
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
attr_accessor :curses_window
|
|
35
|
+
|
|
36
|
+
def resize
|
|
37
|
+
curses_window.resize(height, width)
|
|
38
|
+
curses_window.move(top, left)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
require 'amun/buffer'
|
|
2
|
+
require 'amun/windows/base'
|
|
3
|
+
require 'amun/windows/text_renderer'
|
|
4
|
+
require 'amun/windows/mode_line'
|
|
5
|
+
require 'amun/primitives/rect'
|
|
6
|
+
require 'forwardable'
|
|
7
|
+
|
|
8
|
+
module Amun
|
|
9
|
+
module Windows
|
|
10
|
+
# a window to display any buffer
|
|
11
|
+
# or the current buffer
|
|
12
|
+
class BufferWindow < Base
|
|
13
|
+
extend Forwardable
|
|
14
|
+
|
|
15
|
+
def_delegator :buffer, :trigger
|
|
16
|
+
|
|
17
|
+
attr_accessor :mode_line
|
|
18
|
+
|
|
19
|
+
def initialize(size, buffer = nil)
|
|
20
|
+
super(size)
|
|
21
|
+
@buffer = buffer
|
|
22
|
+
@mode_line = ModeLine.new(mode_line_size)
|
|
23
|
+
@text_renderer = TextRenderer.new(text_renderer_size)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def render
|
|
27
|
+
@text_renderer.render(buffer)
|
|
28
|
+
@mode_line.render(buffer)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# set a specific buffer to be displayed in this window
|
|
32
|
+
def display_buffer(buffer)
|
|
33
|
+
@buffer = buffer
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# render current buffer from the Buffer class
|
|
37
|
+
def display_current_buffer
|
|
38
|
+
@buffer = nil
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# get current buffer that this window is rendering
|
|
42
|
+
def buffer
|
|
43
|
+
@buffer || Buffer.current
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def resize
|
|
49
|
+
super
|
|
50
|
+
@mode_line.size = mode_line_size
|
|
51
|
+
@text_renderer.size = text_renderer_size
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def text_renderer_size
|
|
55
|
+
Rect.new(
|
|
56
|
+
top: top,
|
|
57
|
+
left: left,
|
|
58
|
+
width: width,
|
|
59
|
+
height: height - 1
|
|
60
|
+
)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def mode_line_size
|
|
64
|
+
Rect.new(
|
|
65
|
+
top: top + height - 1,
|
|
66
|
+
left: left,
|
|
67
|
+
width: width,
|
|
68
|
+
height: 1
|
|
69
|
+
)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
require 'amun/windows/base'
|
|
2
|
+
require 'amun/buffer'
|
|
3
|
+
|
|
4
|
+
module Amun
|
|
5
|
+
module Windows
|
|
6
|
+
# a line that is rendered by default at the end on the screen
|
|
7
|
+
# takes the whole width of screen
|
|
8
|
+
# should be linked to \*messages\* memory buffer and display new messages
|
|
9
|
+
# in the buffer text
|
|
10
|
+
class EchoArea < Base
|
|
11
|
+
def initialize(size)
|
|
12
|
+
super(size)
|
|
13
|
+
@last_messages_size = 0
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def render
|
|
17
|
+
curses_window.erase
|
|
18
|
+
curses_window << message
|
|
19
|
+
curses_window.refresh
|
|
20
|
+
update_last_messages_size
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def message
|
|
26
|
+
Buffer.messages[@last_messages_size..-1].strip.lines.last
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def update_last_messages_size
|
|
30
|
+
@last_messages_size = Buffer.messages.length
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
require 'curses'
|
|
2
|
+
require 'amun/event_manager'
|
|
3
|
+
require 'amun/buffer'
|
|
4
|
+
require 'amun/windows/base'
|
|
5
|
+
require 'amun/windows/buffer_window'
|
|
6
|
+
require 'amun/windows/echo_area'
|
|
7
|
+
|
|
8
|
+
module Amun
|
|
9
|
+
module Windows
|
|
10
|
+
# a Frame fills all the space in terminal
|
|
11
|
+
# renders an echo area or mini buffer if it exists
|
|
12
|
+
# and an object that
|
|
13
|
+
# respond to #render and #trigger, like buffer,
|
|
14
|
+
# or another window or so
|
|
15
|
+
class Frame < Base
|
|
16
|
+
attr_accessor :mini_buffer, :window, :echo_area
|
|
17
|
+
attr_writer :screen
|
|
18
|
+
|
|
19
|
+
def initialize(size = default_size)
|
|
20
|
+
super(size)
|
|
21
|
+
@window = BufferWindow.new(top_window_size)
|
|
22
|
+
@echo_area = EchoArea.new(bottom_window_size)
|
|
23
|
+
bind(Curses::KEY_RESIZE, self, :set_size_to_terminal)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def trigger(event)
|
|
27
|
+
EventManager.join(event, self.events, echo_area, mini_buffer || window, EventManager)
|
|
28
|
+
rescue StandardError => error
|
|
29
|
+
handle_exception(error)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def render
|
|
33
|
+
render_window(window)
|
|
34
|
+
render_window(bottom_area)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def set_size_to_terminal(*)
|
|
38
|
+
self.size = default_size
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def resize
|
|
44
|
+
super
|
|
45
|
+
window.size = top_window_size
|
|
46
|
+
echo_area.size = bottom_window_size
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def bottom_area
|
|
50
|
+
mini_buffer || echo_area
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def default_size
|
|
54
|
+
Rect.new(
|
|
55
|
+
top: 0,
|
|
56
|
+
left: 0,
|
|
57
|
+
width: Curses.stdscr.maxx,
|
|
58
|
+
height: Curses.stdscr.maxy
|
|
59
|
+
)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def top_window_size
|
|
63
|
+
Rect.new(
|
|
64
|
+
top: top,
|
|
65
|
+
left: left,
|
|
66
|
+
width: width,
|
|
67
|
+
height: height - 1
|
|
68
|
+
)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def bottom_window_size
|
|
72
|
+
Rect.new(
|
|
73
|
+
top: top + height - 1,
|
|
74
|
+
left: left,
|
|
75
|
+
width: width,
|
|
76
|
+
height: 1
|
|
77
|
+
)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def render_window(window)
|
|
81
|
+
window.render
|
|
82
|
+
mini_buffer.render if mini_buffer
|
|
83
|
+
rescue StandardError => error
|
|
84
|
+
handle_exception(error)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def handle_exception(e)
|
|
88
|
+
Buffer.messages << "#{e.message} (#{e.backtrace.first})\n"
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
require 'amun/windows/base'
|
|
2
|
+
require 'amun/buffer'
|
|
3
|
+
require 'amun/windows/text_renderer'
|
|
4
|
+
require 'amun/primitives/rect'
|
|
5
|
+
|
|
6
|
+
module Amun
|
|
7
|
+
module Windows
|
|
8
|
+
# a minibuffer that when attached will replace the frame
|
|
9
|
+
# echo area and display one line with a label (buffer name)
|
|
10
|
+
# and the value typed by the user (buffer content)
|
|
11
|
+
# it will fire 2 events, done and cancel, you can listen
|
|
12
|
+
# on them to do stuff with the input data,
|
|
13
|
+
# also after both events the buffer will be cleared
|
|
14
|
+
# to allow you to reattach the same window again and reuse it,
|
|
15
|
+
# also it allow passing a block to the initializer to execute it
|
|
16
|
+
# when the user press enter (done event) so you can have a fast usage as such
|
|
17
|
+
# MiniBufferWindow.new("Are you sure?[Y/N]", "Y") do |window|
|
|
18
|
+
# exit if window.buffer.to_s.downcase == 'y'
|
|
19
|
+
# end.attach(Amun::Application.frame)
|
|
20
|
+
# that will create a minibuffer and attach it to current active frame
|
|
21
|
+
# also will have a label asking the user "Are you sure?" and a default
|
|
22
|
+
# value "Y" so user can just press enter, or clear it and press any other character
|
|
23
|
+
# when user press enter the block will be executed with the window itself as a parameter
|
|
24
|
+
# so the block will exit amun if the answer to the question is "y"
|
|
25
|
+
class MiniBufferWindow < Base
|
|
26
|
+
attr_reader :buffer
|
|
27
|
+
|
|
28
|
+
def initialize(name, default_value = '', &block)
|
|
29
|
+
super(default_size)
|
|
30
|
+
|
|
31
|
+
@buffer = Buffer.new(name)
|
|
32
|
+
@buffer << default_value
|
|
33
|
+
@buffer.point = default_value.length
|
|
34
|
+
@done_block = block if block_given?
|
|
35
|
+
@text_renderer = TextRenderer.new(text_renderer_size)
|
|
36
|
+
|
|
37
|
+
bind "\e", self, :cancel
|
|
38
|
+
bind "\C-g", self, :cancel
|
|
39
|
+
bind "\n", self, :done
|
|
40
|
+
bind "done", self, :exec_done_block
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# attach the mini buffer to a frame of your choice,
|
|
44
|
+
# that will make it replace the echo are in this frame
|
|
45
|
+
def attach(frame)
|
|
46
|
+
detach if attached?
|
|
47
|
+
self.size = Rect.new(
|
|
48
|
+
top: frame.top + frame.height - 1,
|
|
49
|
+
left: frame.left,
|
|
50
|
+
width: frame.width,
|
|
51
|
+
height: 1
|
|
52
|
+
)
|
|
53
|
+
@frame = frame
|
|
54
|
+
@frame.mini_buffer = self
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# deatach the mini buffer from its frame
|
|
58
|
+
def detach
|
|
59
|
+
@frame.mini_buffer = nil
|
|
60
|
+
@frame = nil
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# is the mini buffer currently attached to any frame?
|
|
64
|
+
def attached?
|
|
65
|
+
@frame && @frame.mini_buffer == self
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def render
|
|
69
|
+
curses_window.erase
|
|
70
|
+
curses_window << buffer.name
|
|
71
|
+
curses_window.refresh
|
|
72
|
+
@text_renderer.render(buffer)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def trigger(event)
|
|
76
|
+
EventManager.join(
|
|
77
|
+
event,
|
|
78
|
+
events,
|
|
79
|
+
buffer
|
|
80
|
+
)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def cancel(*)
|
|
84
|
+
detach
|
|
85
|
+
trigger("cancel")
|
|
86
|
+
buffer.clear
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def done(*)
|
|
90
|
+
detach
|
|
91
|
+
trigger("done")
|
|
92
|
+
buffer.clear
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def exec_done_block(*)
|
|
96
|
+
return unless @done_block
|
|
97
|
+
@done_block.call(self)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
private
|
|
101
|
+
|
|
102
|
+
def resize
|
|
103
|
+
super
|
|
104
|
+
@text_renderer.size = text_renderer_size
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def text_renderer_size
|
|
108
|
+
Rect.new(
|
|
109
|
+
top: top,
|
|
110
|
+
left: left + buffer.name.length,
|
|
111
|
+
width: width - buffer.name.length,
|
|
112
|
+
height: height
|
|
113
|
+
)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def default_size
|
|
117
|
+
Rect.new(
|
|
118
|
+
top: 0,
|
|
119
|
+
left: 0,
|
|
120
|
+
width: Curses.stdscr.maxx,
|
|
121
|
+
height: Curses.stdscr.maxy
|
|
122
|
+
)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
require 'amun/windows/base'
|
|
2
|
+
require 'amun/helpers/colors'
|
|
3
|
+
require 'amun/mode_line_segments/major_mode'
|
|
4
|
+
require 'amun/mode_line_segments/buffer_name'
|
|
5
|
+
|
|
6
|
+
module Amun
|
|
7
|
+
module Windows
|
|
8
|
+
# a line of small segments that display
|
|
9
|
+
# information about the current window,
|
|
10
|
+
# like mode name, line number, buffer name...etc
|
|
11
|
+
class ModeLine < Base
|
|
12
|
+
attr_reader :left_segments, :right_segments
|
|
13
|
+
|
|
14
|
+
def initialize(size)
|
|
15
|
+
super(size)
|
|
16
|
+
@right_segments = []
|
|
17
|
+
@left_segments = [
|
|
18
|
+
ModeLineSegments::BufferName.new,
|
|
19
|
+
ModeLineSegments::MajorMode.new
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
Helpers::Colors.register_default(:mode_line, 0, 255)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def render(buffer)
|
|
26
|
+
right_output = render_segments(right_segments, buffer)
|
|
27
|
+
left_output = render_segments(left_segments, buffer)
|
|
28
|
+
filler = empty_space(right_output, left_output)
|
|
29
|
+
|
|
30
|
+
curses_window.erase
|
|
31
|
+
Helpers::Colors.print(curses_window, *left_output, filler, *right_output)
|
|
32
|
+
curses_window.refresh
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def empty_space(right_output, left_output)
|
|
38
|
+
text_size = (right_output + left_output).map(&:size).inject(0, :+)
|
|
39
|
+
empty_space = [0, width - text_size].max
|
|
40
|
+
(' ' * empty_space).colorize(:mode_line)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def render_segments(segments, buffer)
|
|
44
|
+
segments.map do |segment|
|
|
45
|
+
segment.render(buffer)
|
|
46
|
+
end.flatten
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
require 'amun/windows/base'
|
|
2
|
+
|
|
3
|
+
module Amun
|
|
4
|
+
module Windows
|
|
5
|
+
# this class renders the buffer in a curses window
|
|
6
|
+
# makes sure the current character under point is highlighted
|
|
7
|
+
# highlight the space between point and mark
|
|
8
|
+
# and make sure to color text and other stuff
|
|
9
|
+
# consider it the rendering engine of the buffer
|
|
10
|
+
class TextRenderer < Base
|
|
11
|
+
def render(buffer)
|
|
12
|
+
curses_window.erase
|
|
13
|
+
curses_window.scrollok(true)
|
|
14
|
+
render_text(buffer)
|
|
15
|
+
curses_window.refresh
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def render_text(buffer)
|
|
21
|
+
point = buffer.point
|
|
22
|
+
curses_window << buffer[0...point]
|
|
23
|
+
render_point(buffer)
|
|
24
|
+
curses_window << buffer[(point + 1)..-1]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def render_point(buffer)
|
|
28
|
+
curses_window.attron(Helpers::Colors::REVERSE)
|
|
29
|
+
curses_window << case buffer[buffer.point]
|
|
30
|
+
when "\n"
|
|
31
|
+
" \n"
|
|
32
|
+
when nil
|
|
33
|
+
" "
|
|
34
|
+
else
|
|
35
|
+
buffer[buffer.point]
|
|
36
|
+
end
|
|
37
|
+
curses_window.attroff(Helpers::Colors::REVERSE)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|