merrol 0.0.2
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.
- data/.autotest +7 -0
- data/.gitignore +4 -0
- data/.rspec +2 -0
- data/LICENSE +23 -0
- data/README.md +58 -0
- data/ROADMAP.md +22 -0
- data/Rakefile +63 -0
- data/autotest/discover.rb +2 -0
- data/bin/m +9 -0
- data/config/commands.yml +150 -0
- data/config/language.yml +47 -0
- data/config/messages.yml +3 -0
- data/config/settings.yml +3 -0
- data/config/snippets/rails.yml +22 -0
- data/config/snippets/rspec.yml +35 -0
- data/config/snippets/ruby.yml +97 -0
- data/config/styles/Railscasts.xml +121 -0
- data/config/templates/rails.yml +21 -0
- data/config/templates/rspec.yml +6 -0
- data/data/images/merrol.svg +412 -0
- data/data/images/merrol2.svg +531 -0
- data/data/images/modified.svg +156 -0
- data/data/images/saved.svg +156 -0
- data/lib/merrol/controllers/controller.rb +26 -0
- data/lib/merrol/controllers/edit_controller.rb +24 -0
- data/lib/merrol/controllers/file_controller.rb +61 -0
- data/lib/merrol/controllers/help_controller.rb +6 -0
- data/lib/merrol/controllers/main_controller.rb +26 -0
- data/lib/merrol/controllers/search_controller.rb +5 -0
- data/lib/merrol/controllers/tools_controller.rb +28 -0
- data/lib/merrol/gtk/bin.rb +11 -0
- data/lib/merrol/gtk/image.rb +8 -0
- data/lib/merrol/gtk/list_view.rb +43 -0
- data/lib/merrol/gtk/source_view.rb +22 -0
- data/lib/merrol/gtk/widget.rb +12 -0
- data/lib/merrol/gtk/window.rb +18 -0
- data/lib/merrol/keyboard_map.rb +136 -0
- data/lib/merrol/lib/application.rb +29 -0
- data/lib/merrol/lib/commands.rb +40 -0
- data/lib/merrol/lib/file.rb +6 -0
- data/lib/merrol/lib/shortcut.rb +35 -0
- data/lib/merrol/lib/widget_builder.rb +49 -0
- data/lib/merrol/lib/yaml.rb +10 -0
- data/lib/merrol/models/source_model.rb +44 -0
- data/lib/merrol/views/edit.yml +50 -0
- data/lib/merrol/views/file_list.yml +11 -0
- data/lib/merrol/views/file_path.yml +8 -0
- data/lib/merrol/views/file_status.yml +6 -0
- data/lib/merrol/views/hbox.yml +6 -0
- data/lib/merrol/views/main.yml +9 -0
- data/lib/merrol/views/scroll_bars.yml +15 -0
- data/lib/merrol/views/status_bar.yml +7 -0
- data/lib/merrol.rb +16 -0
- data/lib/prerequisites.rb +54 -0
- data/merrol.gemspec +24 -0
- data/spec/controllers/controller_spec.rb +27 -0
- data/spec/controllers/edit_controller_spec.rb +38 -0
- data/spec/controllers/file_controller_spec.rb +209 -0
- data/spec/controllers/main_controller_spec.rb +20 -0
- data/spec/gtk/bin_spec.rb +13 -0
- data/spec/gtk/list_view_spec.rb +114 -0
- data/spec/gtk/source_view_spec.rb +41 -0
- data/spec/gtk/widget_spec.rb +37 -0
- data/spec/gtk/window_spec.rb +28 -0
- data/spec/integration/000_startup_spec.rb +14 -0
- data/spec/integration/001_load_from_commandline_spec.rb +25 -0
- data/spec/integration/002_loading_and_saving_spec.rb +35 -0
- data/spec/integration/003_tab_file_switching_spec.rb +49 -0
- data/spec/integration/ideas_spec.rb +23 -0
- data/spec/integration_helper.rb +11 -0
- data/spec/lib/application_spec.rb +57 -0
- data/spec/lib/commands_spec.rb +63 -0
- data/spec/lib/file_spec.rb +9 -0
- data/spec/lib/shortcut_spec.rb +59 -0
- data/spec/lib/widget_builder_spec.rb +77 -0
- data/spec/lib/yaml_spec.rb +26 -0
- data/spec/models/source_model_spec.rb +80 -0
- data/spec/spec_helper.rb +18 -0
- data/spec/support/actions.rb +28 -0
- data/spec/support/expectations.rb +46 -0
- data/spec/support/setup.rb +32 -0
- metadata +224 -0
@@ -0,0 +1,136 @@
|
|
1
|
+
require 'gtk2'
|
2
|
+
require 'gtksourceview2'
|
3
|
+
require 'yaml'
|
4
|
+
require File.expand_path(File.dirname(__FILE__) + '/yaml.rb')
|
5
|
+
require File.expand_path(File.dirname(__FILE__) + '/file.rb')
|
6
|
+
require_relative 'command_dispatcher'
|
7
|
+
include Merrol::CommandDispatcher
|
8
|
+
load_commands
|
9
|
+
|
10
|
+
window = Gtk::Window.new "Keyboard Shortcuts"
|
11
|
+
window.set_default_size(300, 200)
|
12
|
+
window.signal_connect("destroy") do |w|
|
13
|
+
Gtk.main_quit
|
14
|
+
end
|
15
|
+
window.signal_connect('key_press_event') do |w, e|
|
16
|
+
if !toggle_button(shortcut(e), true)
|
17
|
+
shortcut = shortcut(e)
|
18
|
+
help = command_for(key_from_event(e)).last
|
19
|
+
$help.buffer.text = help if help
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
window.signal_connect('key_release_event') do |w, e|
|
24
|
+
toggle_button shortcut(e), false
|
25
|
+
end
|
26
|
+
|
27
|
+
def toggle_button modifier, active
|
28
|
+
if %w(CTRL ALT SHIFT).include?(modifier)
|
29
|
+
$modifiers.each do |button|
|
30
|
+
button.active = active if button.children.first.text.split("\n").first == modifier
|
31
|
+
end
|
32
|
+
render_keys
|
33
|
+
true
|
34
|
+
else
|
35
|
+
false
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def render_keys
|
40
|
+
$rows.children[0..-2].each do |row|
|
41
|
+
row.children.each do |button|
|
42
|
+
next if button.is_a?(Gtk::Label)
|
43
|
+
label = button.children.first
|
44
|
+
key = label.text.split("\n").first
|
45
|
+
command = command_for(key).first
|
46
|
+
button.sensitive = !command.nil? unless %w(CTRL SHIFT ALT).include?(key)
|
47
|
+
label.markup = "<small><b>#{key}</b>\n#{command}</small>"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def shortcut shortcut
|
53
|
+
keys = []
|
54
|
+
key = key_from_event(shortcut)
|
55
|
+
return key if %w(CTRL SHIFT ALT).include?(key)
|
56
|
+
keys << "CTRL" if shortcut.state.control_mask?
|
57
|
+
keys << "ALT" if shortcut.state.mod1_mask?
|
58
|
+
keys << "SHIFT" if shortcut.state.shift_mask?
|
59
|
+
keys << key
|
60
|
+
keys.join('+')
|
61
|
+
end
|
62
|
+
|
63
|
+
def key_from_event e
|
64
|
+
unmodified_keyval = Gdk::Keymap.default.lookup_key(e.hardware_keycode, 0, 0)
|
65
|
+
key = Gdk::Keyval.to_name(unmodified_keyval).upcase
|
66
|
+
{'RETURN' => 'ENTER', 'ESCAPE' => 'ESC',
|
67
|
+
'CONTROL_L' => 'CTRL', 'ALT_L' => 'ALT', 'SHIFT_L' => 'SHIFT',
|
68
|
+
'CONTROL_R' => 'CTRL', 'ALT_R' => 'ALT', 'SHIFT_R' => 'SHIFT'
|
69
|
+
}[key] || key
|
70
|
+
end
|
71
|
+
|
72
|
+
keys = [
|
73
|
+
%w(ESC F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12),
|
74
|
+
%w(` 1 2 3 4 5 6 7 8 9 0 - = BACK),
|
75
|
+
%w(TAB Q W E R T Y U I O P [ ] ENTER),
|
76
|
+
%w(CAPS A S D F G H J K L ; ' #),
|
77
|
+
%w(SHIFT \\ Z X C V B N M , . / SHIFT),
|
78
|
+
%w(CTRL WIN ALT SPACE ALT FN CTRL)
|
79
|
+
]
|
80
|
+
|
81
|
+
$modifiers = []
|
82
|
+
$rows = Gtk::VBox.new
|
83
|
+
|
84
|
+
keys.each do |row|
|
85
|
+
hbox = Gtk::HBox.new
|
86
|
+
$rows.pack_start(hbox, false, false, 1)
|
87
|
+
row.each do |key|
|
88
|
+
command = @commands[key].first if @commands[key]
|
89
|
+
if %w(CTRL SHIFT ALT).include?(key)
|
90
|
+
button = Gtk::ToggleButton.new
|
91
|
+
$modifiers << button
|
92
|
+
button.signal_connect('toggled') do |w, e|
|
93
|
+
modifier = w.children.first.text.split("\n").first
|
94
|
+
toggle_button modifier, w.active?
|
95
|
+
end
|
96
|
+
|
97
|
+
else
|
98
|
+
button = Gtk::Button.new
|
99
|
+
button.signal_connect('clicked') do |w, e|
|
100
|
+
key = w.children.first.text.split("\n").first
|
101
|
+
$help.buffer.text = command_for(key).last
|
102
|
+
end
|
103
|
+
button.sensitive = false unless command
|
104
|
+
end
|
105
|
+
label = Gtk::Label.new
|
106
|
+
label.markup = "<small><b>#{key}</b>\n#{command}</small>"
|
107
|
+
button.add(label)
|
108
|
+
button.width_request = key.length * 10 + 40
|
109
|
+
button.width_request = 300 if key == 'SPACE'
|
110
|
+
hbox.pack_start(button, true, true, 1)
|
111
|
+
|
112
|
+
hbox.pack_start(Gtk::Label.new.set_width_request(50), true, true, 1) if key == '#'
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
$help = Gtk::TextView.new
|
117
|
+
$help.buffer.text = "Use the keyboard or mouse to press (or click) on modifiers and keys to see more information."
|
118
|
+
$help.height_request = 60
|
119
|
+
$help.wrap_mode = Gtk::TextTag::WRAP_WORD
|
120
|
+
$help.sensitive = false
|
121
|
+
$help.modify_font Pango::FontDescription.new('FreeSans Normal 11')
|
122
|
+
$rows.pack_start(Gtk::Frame.new.add($help), true, true, 2)
|
123
|
+
window.add($rows)
|
124
|
+
|
125
|
+
def command_for key
|
126
|
+
modifiers = ''
|
127
|
+
modifiers << 'CTRL+' if $modifiers[2].active?
|
128
|
+
modifiers << 'ALT+' if $modifiers[3].active?
|
129
|
+
modifiers << 'SHIFT+' if $modifiers[0].active?
|
130
|
+
@commands[modifiers + key] || []
|
131
|
+
end
|
132
|
+
|
133
|
+
window.show_all
|
134
|
+
|
135
|
+
Gtk.main
|
136
|
+
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Merrol
|
2
|
+
class Application
|
3
|
+
attr_reader :views, :controllers
|
4
|
+
|
5
|
+
def initialize working_dir, paths
|
6
|
+
@views = WidgetBuilder.build :main, :status_bar, :hbox, :file_list, :scroll_bars, :edit, :file_status, :file_path
|
7
|
+
main_view = @views[:main]
|
8
|
+
commands = Commands.new main_view
|
9
|
+
|
10
|
+
@controllers = {}
|
11
|
+
controller_paths = Dir[File.app_relative('lib/merrol/controllers') + '/*_controller.rb']
|
12
|
+
controller_paths.each do |controller|
|
13
|
+
controller = File.basename(controller, '.rb')
|
14
|
+
name = controller.gsub(/_controller/, '')
|
15
|
+
@controllers[name] = eval(controller.classify).new commands, @views
|
16
|
+
end
|
17
|
+
|
18
|
+
@controllers['main'].working_dir = working_dir
|
19
|
+
@controllers['file'].load_all paths
|
20
|
+
|
21
|
+
# TODO: move both to a controller
|
22
|
+
@views[:file_path].text = paths.last if paths.last
|
23
|
+
@views[:edit].grab_focus
|
24
|
+
|
25
|
+
main_view.show_all
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Merrol
|
2
|
+
class Commands
|
3
|
+
def initialize(window)
|
4
|
+
@accel_group = Gtk::AccelGroup.new
|
5
|
+
window.add_accel_group @accel_group
|
6
|
+
load
|
7
|
+
end
|
8
|
+
|
9
|
+
def register controller
|
10
|
+
@commands[controller.name.to_s].each do |command, detail|
|
11
|
+
if detail['key'] =~ /TAB/
|
12
|
+
Gtk::Window.toplevels.first.signal_connect('key_press_event') do |widget, event|
|
13
|
+
if detail['key'] == Shortcut.from(event)
|
14
|
+
controller.send(command)
|
15
|
+
else
|
16
|
+
false
|
17
|
+
end
|
18
|
+
end
|
19
|
+
else
|
20
|
+
@accel_group.connect "<#{APP_NAME}>/#{controller.name}/#{command}" do
|
21
|
+
controller.send(command)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def load
|
30
|
+
@commands = YAML.load_config 'commands'
|
31
|
+
@commands.each do |category, commands|
|
32
|
+
commands.each do |command, detail|
|
33
|
+
event = Shortcut.to_event(detail['key'])
|
34
|
+
Gtk::AccelMap.add_entry("<#{APP_NAME}>/#{category}/#{command}", event.keyval, event.state)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Merrol
|
2
|
+
class Shortcut
|
3
|
+
def self.from event
|
4
|
+
shortcut = Gdk::Keyval.to_name(event.keyval).upcase
|
5
|
+
shortcut = shortcut.gsub(/(_L|_R)$/, '').gsub('CONTROL', 'CTRL').gsub('ESCAPE', 'ESC').gsub('RETURN', 'ENTER')
|
6
|
+
|
7
|
+
if !%w(CTRL ALT SHIFT).include? shortcut
|
8
|
+
keys = []
|
9
|
+
keys << "CTRL" if event.state.control_mask?
|
10
|
+
keys << "ALT" if event.state.mod1_mask?
|
11
|
+
keys << "SHIFT" if event.state.shift_mask?
|
12
|
+
keys << shortcut
|
13
|
+
shortcut = keys.join('+')
|
14
|
+
end
|
15
|
+
shortcut
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.to_event key, release = nil
|
19
|
+
event = Gdk::EventKey.new(release ? Gdk::Event::KEY_RELEASE : Gdk::Event::KEY_PRESS)
|
20
|
+
key = key.gsub(/ESC/, 'ESCAPE').gsub(/ENTER/, 'RETURN')
|
21
|
+
|
22
|
+
if %w(CTRL ALT SHIFT).include? key
|
23
|
+
key = key.gsub('CTRL', 'CONTROL').capitalize + '_L'
|
24
|
+
else
|
25
|
+
event.state |= Gdk::Window::CONTROL_MASK if key =~ /CTRL+/
|
26
|
+
event.state |= Gdk::Window::MOD1_MASK if key =~ /ALT+/
|
27
|
+
event.state |= Gdk::Window::SHIFT_MASK if key =~ /SHIFT+/
|
28
|
+
key = key.split('+').last.capitalize
|
29
|
+
end
|
30
|
+
event.keyval = Gdk::Keyval.from_name(key)
|
31
|
+
event
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Merrol
|
2
|
+
class WidgetBuilder
|
3
|
+
def self.build *view_names
|
4
|
+
widgets = {}
|
5
|
+
view_names.each do |view_name|
|
6
|
+
yaml = YAML.load_view(view_name.to_s)
|
7
|
+
widget = nil
|
8
|
+
add_to = nil
|
9
|
+
pack = {:expand => false, :fill => false, :padding => 0}
|
10
|
+
yaml['layout'].merge(yaml['options'] || {}).each do |key, value|
|
11
|
+
if key == 'type'
|
12
|
+
widget = eval('Gtk::' + value).new
|
13
|
+
elsif value.is_a?(Hash)
|
14
|
+
widget.send("#{key}=", constants_from(value))
|
15
|
+
elsif key == 'add_to'
|
16
|
+
add_to = widgets[value.to_sym]
|
17
|
+
elsif %w(expand fill padding).include?(key)
|
18
|
+
pack[key.to_sym] = value
|
19
|
+
else
|
20
|
+
widget.send("#{key}=", value)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
if add_to
|
25
|
+
container = add_to.container if add_to.respond_to?(:container)
|
26
|
+
container ||= add_to
|
27
|
+
if container.is_a?(Gtk::Bin)
|
28
|
+
container.add(widget)
|
29
|
+
else
|
30
|
+
container.pack_start(widget, pack[:expand], pack[:fill], pack[:padding])
|
31
|
+
end
|
32
|
+
end
|
33
|
+
widgets[view_name] = widget
|
34
|
+
end
|
35
|
+
widgets
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def self.constants_from value
|
41
|
+
type = value['type']
|
42
|
+
value = value['value']
|
43
|
+
value = [value] unless value.is_a?(Array)
|
44
|
+
|
45
|
+
eval(value.map{ |v| ['Gtk', type, v].compact.join('::') }.join(' | '))
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module YAML
|
2
|
+
LOCATIONS = {'config' => 'config', 'view' => 'lib/merrol/views'}
|
3
|
+
|
4
|
+
def self.method_missing(meth, *args, &block)
|
5
|
+
dir = LOCATIONS[meth.to_s.gsub(/load_/, '')]
|
6
|
+
super unless dir
|
7
|
+
self.load File.open(File.app_relative("#{dir}/#{args[0]}.yml"))
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Merrol
|
2
|
+
class SourceModel < Gtk::SourceBuffer
|
3
|
+
|
4
|
+
def initialize path = nil
|
5
|
+
super(nil)
|
6
|
+
@path = path
|
7
|
+
load
|
8
|
+
language_from_filetype
|
9
|
+
end
|
10
|
+
|
11
|
+
def save
|
12
|
+
File.open(@path, 'w') {|f| f.write(self.text) }
|
13
|
+
self.modified = false
|
14
|
+
end
|
15
|
+
|
16
|
+
def load
|
17
|
+
if @path && File.exist?(@path)
|
18
|
+
begin_not_undoable_action { self.text = File.read(@path) }
|
19
|
+
self.modified = false
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def language_from_filetype
|
24
|
+
yaml = YAML.load_config('language')
|
25
|
+
yaml.each do |name, hash|
|
26
|
+
hash['pattern'].split(' ').each do |pattern|
|
27
|
+
if pattern[0..0] == '.' && @path[0..0] != '.'
|
28
|
+
name_or_ext = File.extname(@path)
|
29
|
+
else
|
30
|
+
name_or_ext = File.basename(@path)
|
31
|
+
end
|
32
|
+
|
33
|
+
bang_line = "#!/usr/bin/env #{hash['bang']}"
|
34
|
+
file_exists = hash['exists'] ? File.exist?(hash['exists']) : true
|
35
|
+
if (pattern == name_or_ext || self.text[0..(bang_line.length - 1)] == bang_line) && file_exists
|
36
|
+
self.language = Gtk::SourceLanguageManager.new.get_language name
|
37
|
+
return
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
@@ -0,0 +1,50 @@
|
|
1
|
+
layout:
|
2
|
+
type: SourceView
|
3
|
+
add_to: scroll_bars
|
4
|
+
|
5
|
+
options:
|
6
|
+
auto_indent: true
|
7
|
+
theme: config/styles/Railscasts
|
8
|
+
font: monospace 9
|
9
|
+
highlight_current_line: true
|
10
|
+
indent_on_tab: true
|
11
|
+
indent_width: 2
|
12
|
+
insert_spaces_instead_of_tabs: true
|
13
|
+
right_margin_position: 120
|
14
|
+
show_line_marks: false
|
15
|
+
show_line_numbers: true
|
16
|
+
show_right_margin: true
|
17
|
+
tab_width: 2
|
18
|
+
|
19
|
+
#TODO: Provide support for buffer preferences
|
20
|
+
#buffer:
|
21
|
+
highlight_matching_brackets: true
|
22
|
+
# bracket_match_style:
|
23
|
+
# type: SourceStyle
|
24
|
+
# background: nil
|
25
|
+
# line_background: nil
|
26
|
+
# foreground: nil
|
27
|
+
# bold: false
|
28
|
+
# italic: false
|
29
|
+
# mask: false
|
30
|
+
# strikethrough: false
|
31
|
+
# underline: false
|
32
|
+
|
33
|
+
# value can be one or more of the following as an array or singular
|
34
|
+
# DRAW_SPACES_SPACE, DRAW_SPACES_TAB, DRAW_SPACES_NEWLINE
|
35
|
+
# DRAW_SPACES_NBSP, DRAW_SPACES_LEADING, DRAW_SPACES_TEXT
|
36
|
+
# DRAW_SPACES_TRAILING, or just DRAW_SPACES_ALL
|
37
|
+
# draw_spaces:
|
38
|
+
# type: SourceView
|
39
|
+
# value: [DRAW_SPACES_SPACE, DRAW_SPACES_NEWLINE]
|
40
|
+
|
41
|
+
# SMART_HOME_END_BEFORE, SMART_HOME_END_BEFORE, SMART_HOME_END_BEFORE
|
42
|
+
smart_home_end:
|
43
|
+
type: SourceView
|
44
|
+
value: SMART_HOME_END_BEFORE
|
45
|
+
|
46
|
+
# WRAP_NONE, WRAP_CHAR, WRAP_WORD, WRAP_WORD_CHAR
|
47
|
+
wrap_mode:
|
48
|
+
type: TextTag
|
49
|
+
value: WRAP_WORD
|
50
|
+
|
@@ -0,0 +1,15 @@
|
|
1
|
+
layout:
|
2
|
+
type: ScrolledWindow
|
3
|
+
add_to: hbox
|
4
|
+
expand: true
|
5
|
+
fill: true
|
6
|
+
padding: 1
|
7
|
+
|
8
|
+
options:
|
9
|
+
hscrollbar_policy:
|
10
|
+
type:
|
11
|
+
value: POLICY_AUTOMATIC # show horizontal scrollbar: POLICY_NEVER, POLICY_AUTOMATIC, POLICY_ALWAYS
|
12
|
+
vscrollbar_policy:
|
13
|
+
type:
|
14
|
+
value: POLICY_AUTOMATIC # show vertical scrollbar: POLICY_NEVER, POLICY_AUTOMATIC, POLICY_ALWAYS
|
15
|
+
|
data/lib/merrol.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require_relative 'prerequisites'
|
2
|
+
|
3
|
+
require 'gtk2'
|
4
|
+
require 'gtksourceview2'
|
5
|
+
require 'yaml'
|
6
|
+
require 'active_support/inflector'
|
7
|
+
require 'observer'
|
8
|
+
|
9
|
+
APP_NAME = "Merrol"
|
10
|
+
WORKING_DIR = Dir.getwd
|
11
|
+
|
12
|
+
require "#{File.dirname(__FILE__)}/merrol/controllers/controller"
|
13
|
+
merrol_path = "#{File.dirname(__FILE__)}/merrol/**/*.rb"
|
14
|
+
to_include = Dir[merrol_path]
|
15
|
+
to_include.each { |file| require file unless file.include?('keyboard_map') }
|
16
|
+
|
@@ -0,0 +1,54 @@
|
|
1
|
+
class LibNotFoundError < StandardError; end
|
2
|
+
|
3
|
+
def check_packages
|
4
|
+
selections = `dpkg --get-selections`
|
5
|
+
libs = %w(libglib2.0-dev libatk1.0-dev libpango1.0-dev libgtk2.0-dev libgtksourceview2.0-dev)
|
6
|
+
libs.each do |lib|
|
7
|
+
raise LibNotFoundError unless selections =~ Regexp.new(lib)
|
8
|
+
end
|
9
|
+
rescue LibNotFoundError => e
|
10
|
+
puts 'Some packages needed for installation are missing'
|
11
|
+
puts 'To install the rquired packages please run:'
|
12
|
+
puts ' sudo apt-get install libglib2.0-dev libatk1.0-dev libpango1.0-dev libgtk2.0-dev libgtksourceview2.0-dev'
|
13
|
+
puts
|
14
|
+
exit
|
15
|
+
end
|
16
|
+
|
17
|
+
def install_ruby_gnome2
|
18
|
+
trap("INT") { puts "\rAborted."; exit }
|
19
|
+
|
20
|
+
puts "Some ruby libraries need to be compiled and installed to run merrol (gtk2 and gtksourceview2)"
|
21
|
+
puts "Press ENTER to install or CTRL+C to abort"
|
22
|
+
gets
|
23
|
+
|
24
|
+
logfile = '/tmp/ruby-gnome2-install.log'
|
25
|
+
|
26
|
+
package = 'ruby-gnome2-all-0.90.4'
|
27
|
+
system "gem install pkg-config > #{logfile}"
|
28
|
+
puts 'pkg-config installed'
|
29
|
+
|
30
|
+
puts 'Downloading sources...'
|
31
|
+
system "cd /tmp && wget http://downloads.sourceforge.net/ruby-gnome2/#{package}.tar.gz >> #{logfile}"
|
32
|
+
|
33
|
+
puts 'Extracting sources...'
|
34
|
+
system "cd /tmp && tar xf #{package}.tar.gz"
|
35
|
+
|
36
|
+
puts 'Configuring ...'
|
37
|
+
system "cd /tmp/#{package} && ruby extconf.rb >> #{logfile}"
|
38
|
+
|
39
|
+
puts 'Building ...'
|
40
|
+
system "cd /tmp/#{package} && sudo make >> #{logfile}"
|
41
|
+
|
42
|
+
puts 'Installing ...'
|
43
|
+
system "cd /tmp/#{package} && sudo make install >> #{logfile}"
|
44
|
+
|
45
|
+
puts 'Done.'
|
46
|
+
end
|
47
|
+
|
48
|
+
begin
|
49
|
+
check_packages
|
50
|
+
require 'gtksourceview2'
|
51
|
+
rescue LoadError
|
52
|
+
install_ruby_gnome2
|
53
|
+
end
|
54
|
+
|
data/merrol.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = "merrol"
|
3
|
+
s.version = "0.0.2"
|
4
|
+
s.authors = "Phil Thompson"
|
5
|
+
s.email = "phil@electricvisions.com"
|
6
|
+
s.homepage = "http://merrol.com"
|
7
|
+
s.summary = "MERROL - Minimalist Editor for Ruby, Rails and Other Languages"
|
8
|
+
s.description = "An editor for Ruby, Rails and supporting langauges, written in pure Ruby with an open design to allow customization and improvement."
|
9
|
+
s.required_rubygems_version = ">= 1.3.6"
|
10
|
+
s.rubyforge_project = "merrol"
|
11
|
+
|
12
|
+
s.add_dependency "activesupport"
|
13
|
+
|
14
|
+
s.add_development_dependency "autotest"
|
15
|
+
s.add_development_dependency "simplecov"
|
16
|
+
s.add_development_dependency "rspec", '2.0.0'
|
17
|
+
|
18
|
+
s.files = `git ls-files`.split("\n")
|
19
|
+
s.test_files = `git ls-files -- spec/*`.split("\n")
|
20
|
+
|
21
|
+
s.executables = ['m']
|
22
|
+
s.require_path = 'lib'
|
23
|
+
end
|
24
|
+
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Controller do
|
4
|
+
it 'gets the name without controller suffix' do
|
5
|
+
mock_view = mock Gtk::Window, :signal_connect => nil
|
6
|
+
MainController.new(mock(Commands, :register => nil), {:main => mock_view}).name.should == :main
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'access views using <name>_view method' do
|
10
|
+
mock_view = mock Gtk::Window, :signal_connect => nil
|
11
|
+
controller = Controller.new(mock(Commands, :register => nil), {:main => mock_view})
|
12
|
+
controller.main_view.should == mock_view
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'raises method missing error when view not found' do
|
16
|
+
mock_view = mock Gtk::Window, :signal_connect => nil
|
17
|
+
controller = Controller.new(mock(Commands, :register => nil), {:main => mock_view})
|
18
|
+
lambda {controller.non_existent_view}.should raise_error NoMethodError
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'raises method missing error when not a view method' do
|
22
|
+
mock_view = mock Gtk::Window, :signal_connect => nil
|
23
|
+
controller = Controller.new(mock(Commands, :register => nil), {:main => mock_view})
|
24
|
+
lambda {controller.non_existent_method}.should raise_error NoMethodError
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe EditController do
|
4
|
+
before(:each) do
|
5
|
+
@mock_commands = mock Commands, :register => nil
|
6
|
+
@mock_view = mock(Gtk::SourceView)
|
7
|
+
@edit = EditController.new(@mock_commands, {:edit => @mock_view})
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'selects all text' do
|
11
|
+
@mock_view.should_receive(:select_all).with true
|
12
|
+
@edit.select_all
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'deselects all text' do
|
16
|
+
@mock_view.should_receive(:select_all).with false
|
17
|
+
@edit.select_none
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'calls redo on view' do
|
21
|
+
@mock_view.should_receive(:redo)
|
22
|
+
@edit.redo
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'calls undo on view' do
|
26
|
+
@mock_view.should_receive(:undo)
|
27
|
+
@edit.undo
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'calls delete on view' do
|
31
|
+
@mock_buffer = mock SourceModel
|
32
|
+
@mock_view.stub(:buffer).and_return @mock_buffer
|
33
|
+
@mock_buffer.should_receive(:delete_selection).with(true, true)
|
34
|
+
@edit.delete
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|