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