amun 0.1.3 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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