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.
Files changed (43) hide show
  1. checksums.yaml +5 -5
  2. data/.rspec +1 -0
  3. data/.rubocop.yml +6 -1
  4. data/.ruby-version +1 -1
  5. data/Guardfile +26 -6
  6. data/README.md +1 -0
  7. data/Rakefile +4 -6
  8. data/_config.yml +1 -0
  9. data/amun.gemspec +6 -5
  10. data/exe/amun +1 -1
  11. data/lib/amun/application.rb +11 -5
  12. data/lib/amun/behaviours/emacs.rb +14 -122
  13. data/lib/amun/behaviours/erasing.rb +67 -0
  14. data/lib/amun/behaviours/insertion.rb +22 -0
  15. data/lib/amun/behaviours/movement.rb +83 -0
  16. data/lib/amun/buffer.rb +103 -0
  17. data/lib/amun/event_manager.rb +12 -3
  18. data/lib/amun/features/echo_event.rb +4 -1
  19. data/lib/amun/features/files.rb +26 -0
  20. data/lib/amun/helpers/colors.rb +8 -7
  21. data/lib/amun/helpers/keyboard.rb +25 -8
  22. data/lib/amun/major_modes/fundamental.rb +8 -40
  23. data/lib/amun/major_modes/irb.rb +40 -0
  24. data/lib/amun/mode_line_segments/buffer_name.rb +10 -0
  25. data/lib/amun/mode_line_segments/major_mode.rb +11 -0
  26. data/lib/amun/object.rb +21 -0
  27. data/lib/amun/primitives/rect.rb +15 -0
  28. data/lib/amun/version.rb +1 -1
  29. data/lib/amun/windows/base.rb +42 -0
  30. data/lib/amun/windows/buffer_window.rb +73 -0
  31. data/lib/amun/windows/echo_area.rb +34 -0
  32. data/lib/amun/windows/frame.rb +92 -0
  33. data/lib/amun/windows/mini_buffer_window.rb +126 -0
  34. data/lib/amun/windows/mode_line.rb +50 -0
  35. data/lib/amun/windows/text_renderer.rb +41 -0
  36. metadata +56 -31
  37. data/lib/amun/ui/buffer.rb +0 -90
  38. data/lib/amun/ui/echo_area.rb +0 -42
  39. data/lib/amun/ui/mode_line.rb +0 -44
  40. data/lib/amun/ui/mode_line_segments/buffer_name.rb +0 -16
  41. data/lib/amun/ui/mode_line_segments/major_mode.rb +0 -17
  42. data/lib/amun/ui/windows/buffer_window.rb +0 -40
  43. data/lib/amun/ui/windows/frame.rb +0 -75
@@ -0,0 +1,103 @@
1
+ require 'set'
2
+ require 'amun/object'
3
+ require 'amun/event_manager'
4
+ require 'amun/major_modes/fundamental'
5
+ require 'forwardable'
6
+
7
+ module Amun
8
+ # A buffer could present any kind of IO object (File, StringIO...etc)
9
+ # also it has a major mode responsible update lines and visual lines
10
+ class Buffer < Object
11
+ extend Forwardable
12
+ attr_accessor :name, :io, :major_mode, :minor_modes
13
+
14
+ def initialize(name, io = StringIO.new)
15
+ super()
16
+ self.io = io
17
+ self.name = name
18
+ self.point = 0
19
+ self.text = ''
20
+ self.major_mode = MajorModes::Fundamental.new(self)
21
+ self.minor_modes = Set.new
22
+ end
23
+
24
+ attr_writer :point
25
+ def point
26
+ return 0 if @point.negative?
27
+ return length if @point > length
28
+ @point
29
+ end
30
+
31
+ attr_writer :mark
32
+ def mark
33
+ return nil if @mark.nil?
34
+ return 0 if @mark.negative?
35
+ return length if @mark > length
36
+ @mark
37
+ end
38
+
39
+ def trigger(event)
40
+ EventManager.join(
41
+ event,
42
+ *([events] + minor_modes.to_a + [major_mode])
43
+ )
44
+ end
45
+
46
+ # buffer should wrap the inner @text
47
+ # and expose any reading method but
48
+ # control the writing methods
49
+
50
+ def_delegators :text,
51
+ :length, :size, :[],
52
+ :count, :index, :rindex,
53
+ :empty?
54
+
55
+ def insert(index, other_str)
56
+ text.insert(index, other_str)
57
+ end
58
+
59
+ def slice!(start, length = 1)
60
+ text.slice!(start, length)
61
+ end
62
+
63
+ def <<(p1)
64
+ insert(length, p1)
65
+ end
66
+
67
+ def clear
68
+ slice!(0, length)
69
+ end
70
+
71
+ def to_s
72
+ text.dup
73
+ end
74
+
75
+ class << self
76
+ attr_writer :current, :instances
77
+
78
+ def instances
79
+ @instances ||= Set.new
80
+ end
81
+
82
+ def current
83
+ @current ||= scratch
84
+ end
85
+
86
+ def scratch
87
+ @scratch ||= new('*Scratch*')
88
+ instances << @scratch
89
+ @scratch
90
+ end
91
+
92
+ def messages
93
+ @messages ||= new('*Messages*')
94
+ instances << @messages
95
+ @messages
96
+ end
97
+ end
98
+
99
+ private
100
+
101
+ attr_accessor :text
102
+ end
103
+ end
@@ -44,8 +44,8 @@ module Amun
44
44
  raise ArgumentError, "#{method} : is not a method for #{object}"
45
45
  end
46
46
 
47
- add_chain(event)
48
- @bindings[event].unshift(object: object, method: method)
47
+ add_chain(event.to_s)
48
+ @bindings[event.to_s].unshift(object: object, method: method)
49
49
  end
50
50
 
51
51
  # remove all occurence of *method* from the *event* stack
@@ -148,6 +148,15 @@ module Amun
148
148
  end
149
149
  end
150
150
 
151
+ # clear the globally bound events
152
+ # if you need to reset this class to
153
+ # its default state, this method
154
+ # should clear all events and its associated
155
+ # objects/methods
156
+ def clear
157
+ @instance = nil
158
+ end
159
+
151
160
  private
152
161
 
153
162
  def instance
@@ -172,7 +181,7 @@ module Amun
172
181
 
173
182
  def add_chain(event)
174
183
  return unless event.to_s.include?(' ')
175
- event.to_s.split(" ").inject("") do |chain, evt|
184
+ event.to_s.split(" ")[0...-1].inject("") do |chain, evt|
176
185
  new_chain = (chain + " " + evt).strip
177
186
  @chains << new_chain
178
187
  new_chain
@@ -1,6 +1,9 @@
1
1
  require 'amun/event_manager'
2
+ require 'amun/buffer'
2
3
 
3
4
  def log(event)
4
- Amun::UI::Buffer.messages.text << "#{event}\n"
5
+ # log valid strings only no control characters
6
+ event = event.encode!('UTF-8', 'UTF-8', invalid: :replace)
7
+ Amun::Buffer.messages << "#{event}\n"
5
8
  end
6
9
  Amun::EventManager.bind_all nil, :log
@@ -0,0 +1,26 @@
1
+ require 'amun/event_manager'
2
+ require 'amun/buffer'
3
+ require 'amun/windows/mini_buffer_window'
4
+
5
+ def find_file(*)
6
+ Amun::Windows::MiniBufferWindow.new('Open file: ', Dir.pwd) do |window|
7
+ file_path = window.buffer.to_s
8
+
9
+ file_buffer = Amun::Buffer.new(file_path, File.open(file_path, 'r+'))
10
+ Amun::Buffer.instances << file_buffer
11
+ Amun::Buffer.current = file_buffer
12
+ end.attach(Amun::Application.frame)
13
+
14
+ true
15
+ end
16
+
17
+ Amun::EventManager.bind "\C-x \C-f", nil, :find_file
18
+
19
+ unless ARGV.empty?
20
+ ARGV.each do |file|
21
+ file_buffer = Amun::Buffer.new(file, File.open(file, 'r+'))
22
+ Amun::Buffer.instances << file_buffer
23
+ Amun::Buffer.current = file_buffer
24
+ Amun::Application.frame.render
25
+ end
26
+ end
@@ -2,6 +2,7 @@ require 'curses'
2
2
 
3
3
  module Amun
4
4
  module Helpers
5
+ class ColorLimitExceeded < StandardError; end
5
6
  ##
6
7
  # Colors is responsible for registering new colors
7
8
  # pairs (foreground and background)
@@ -52,8 +53,8 @@ module Amun
52
53
  # foreground(Number):: foreground color in current terminal color schema
53
54
  # background(Number):: background color in current terminal color schema
54
55
  def register(name, foreground, background)
55
- if COLORS.size >= Curses.color_pairs - 1
56
- raise "Can't register color: #{name}, max: #{Curses.color_pairs}"
56
+ if !COLORS.key?(name) && COLORS.size >= Curses.color_pairs - 1
57
+ raise ColorLimitExceeded, "Can't register color: #{name}, max: #{Curses.color_pairs}"
57
58
  end
58
59
 
59
60
  Curses.init_pair(COLORS[name], foreground, background)
@@ -79,16 +80,16 @@ module Amun
79
80
  # type(Colors::Constant):: a text style constant
80
81
  # defined in Colors, that manipulate the text style
81
82
  # (Bold, Underline, Invert colors)
82
- def use(window, name, type = NORMAL)
83
+ def use(curses_window, name, type = NORMAL)
83
84
  index = COLORS.key?(name) ? COLORS[name] : 0
84
- window.attron(Curses.color_pair(index) | type)
85
+ curses_window.attron(Curses.color_pair(index) | type)
85
86
  end
86
87
 
87
88
  # print string in Curses window in the choosen color and style
88
- def print(window, *strings)
89
+ def print(curses_window, *strings)
89
90
  strings.each do |string|
90
- use(window, string.color || DEFAULT_COLOR, string.style || NORMAL)
91
- window << string
91
+ use(curses_window, string.color || DEFAULT_COLOR, string.style || NORMAL)
92
+ curses_window << string
92
93
  end
93
94
  end
94
95
  end
@@ -2,27 +2,44 @@ require 'curses'
2
2
 
3
3
  module Amun
4
4
  module Helpers
5
+ # a module to deal with the keyboard
6
+ # a complementary module to curses
7
+ # doesn't intend to replace it
8
+ # it was created to overcome the shorcoming
9
+ # of getting a character + meta from the keyboard
10
+ # in the first place.
5
11
  module Keyboard
6
12
  module_function
7
13
 
8
14
  TIMEOUT = 100
9
15
 
16
+ # get a character from the keyboard
17
+ # and make sure you detect the meta combination
10
18
  def char
11
19
  ch = Curses.stdscr.get_char
12
- Curses.stdscr.timeout = TIMEOUT
13
- modified_char = Curses.stdscr.get_char if ch == "\e"
14
- Curses.stdscr.timeout = -1
20
+ modified_character = modified_char if ch == "\e"
15
21
 
16
- return ch if modified_char.nil?
17
- return "#{ch} #{modified_char}" if modified_char.is_a? Numeric
18
- return "#{ch} #{modified_char}" if modified_char.size > 1
22
+ return ch.to_s if modified_character.nil?
23
+ return "#{ch} #{modified_character}" if modified_character.is_a? Numeric
24
+ return "#{ch} #{modified_character}" if modified_character.length > 1
19
25
 
20
26
  begin
21
- eval "?\\M-#{modified_char}"
27
+ eval "?\\M-#{modified_character}"
22
28
  rescue SyntaxError
23
- return "#{ch} #{modified_char}"
29
+ return "#{ch} #{modified_character}"
24
30
  end
25
31
  end
32
+
33
+ private
34
+
35
+ module_function
36
+
37
+ def modified_char
38
+ Curses.stdscr.timeout = TIMEOUT
39
+ char = Curses.stdscr.get_char
40
+ Curses.stdscr.timeout = -1
41
+ char
42
+ end
26
43
  end
27
44
  end
28
45
  end
@@ -1,51 +1,19 @@
1
+ require 'amun/object'
1
2
  require 'amun/helpers/colors'
2
3
  require 'amun/behaviours/emacs'
3
4
 
4
5
  module Amun
5
6
  module MajorModes
6
- # Basic mode that show any IO
7
- class Fundamental
7
+ # Basic mode with emacs defaults
8
+ class Fundamental < Object
8
9
  include Behaviours::Emacs
9
10
 
10
11
  def initialize(buffer)
11
- @buffer = buffer
12
+ super()
13
+ self.buffer = buffer
12
14
 
13
- @events = EventManager.new
14
- @events.bind_all self, :event_handler
15
- emacs_behaviour_initialize(@events)
16
-
17
- read_io if buffer.text.nil?
18
- end
19
-
20
- def trigger(event)
21
- EventManager.join(event, @events)
22
- end
23
-
24
- def render(window)
25
- window.clear
26
- point = buffer.point
27
-
28
- window << buffer.text[0...point]
29
- window.attron(Helpers::Colors::REVERSE)
30
-
31
- at_point = buffer.text[point]
32
- window << (at_point == "\n" || at_point.nil? ? " \n" : at_point)
33
- window.attroff(Helpers::Colors::REVERSE)
34
- window << buffer.text[(point + 1)..-1]
35
- end
36
-
37
- def event_handler(event)
38
- return true unless event.is_a? String
39
- return true unless event.length == 1
40
- return true unless event.valid_encoding?
41
-
42
- case event
43
- when /[^[:print:]\n\t]/
44
- true
45
- else
46
- buffer.text.insert(buffer.point, event)
47
- buffer.point += 1
48
- end
15
+ emacs_behaviour_initialize
16
+ read_io if buffer.empty?
49
17
  end
50
18
 
51
19
  private
@@ -53,7 +21,7 @@ module Amun
53
21
  attr_accessor :buffer
54
22
 
55
23
  def read_io
56
- buffer.text = buffer.io.read
24
+ buffer << buffer.io.read
57
25
  end
58
26
  end
59
27
  end
@@ -0,0 +1,40 @@
1
+ require 'amun/object'
2
+ require 'amun/helpers/colors'
3
+ require 'amun/behaviours/emacs'
4
+
5
+ module Amun
6
+ module MajorModes
7
+ # mode that executes the last line in
8
+ # the current environment and print the output
9
+ class IRB < Object
10
+ include Behaviours::Emacs
11
+
12
+ def initialize(buffer)
13
+ super()
14
+ self.buffer = buffer
15
+
16
+ emacs_behaviour_initialize
17
+ bind "\n", self, :execute_last_line
18
+ read_io if buffer.empty?
19
+ end
20
+
21
+ def execute_last_line(*)
22
+ last_line = buffer.lines.last
23
+ result = eval(last_line)
24
+ buffer << "\n#{result}"
25
+ rescue StandardError, SyntaxError => error
26
+ buffer << "\n#{error.inspect}\n#{error.backtrace}"
27
+ ensure
28
+ buffer.point = buffer.length
29
+ end
30
+
31
+ private
32
+
33
+ attr_accessor :buffer
34
+
35
+ def read_io
36
+ buffer << buffer.io.read
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,10 @@
1
+ module Amun
2
+ module ModeLineSegments
3
+ # display buffer name in modeline
4
+ class BufferName
5
+ def render(buffer)
6
+ " #{buffer.name} ".colorize(:mode_line)
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,11 @@
1
+ module Amun
2
+ module ModeLineSegments
3
+ # display major mode name in mode line
4
+ class MajorMode
5
+ def render(buffer)
6
+ mode = buffer.major_mode.class.name.split('::').last
7
+ " (#{mode}) ".colorize(:mode_line)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,21 @@
1
+ require 'amun/event_manager'
2
+ require 'forwardable'
3
+
4
+ module Amun
5
+ # an object wrapping event manager inside it
6
+ # and exposing every method to the public
7
+ # this way you can have this object and switch
8
+ # behavior and states by switching internal event manager
9
+ # instances, for example you can have a mode that switch between
10
+ # normal and insert mode (ahem ahmed VIM style)
11
+ class Object
12
+ extend Forwardable
13
+
14
+ attr_accessor :events
15
+ def_delegators :events, :bind, :bind_all, :unbind, :unbind_all, :trigger
16
+
17
+ def initialize
18
+ @events = EventManager.new
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,15 @@
1
+ # a primitive class that incapsulate
2
+ # a rectangle on screen, with position
3
+ # (top, left) and size (width, height)
4
+ class Rect
5
+ attr_reader :top, :left, :width, :height
6
+
7
+ # create a rectangle with top, left, width and height
8
+ # opts(Hash):: a hash with keys (top, left, width, height)
9
+ def initialize(opts = {})
10
+ @top = opts[:top]
11
+ @left = opts[:left]
12
+ @width = opts[:width]
13
+ @height = opts[:height]
14
+ end
15
+ end
@@ -1,3 +1,3 @@
1
1
  module Amun
2
- VERSION = '0.1.3'.freeze
2
+ VERSION = '0.2.0'.freeze
3
3
  end