hilfer 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +6 -0
- data/README.txt +49 -0
- data/bin/ec +9 -0
- data/bin/hilfer +260 -0
- data/bin/hilfer-icon.png +0 -0
- data/bin/sd +17 -0
- data/lib/hilfer/rails_locator.rb +150 -0
- data/lib/hilfer/scite_editor.rb +182 -0
- data/lib/hilfer/svn_colours.rb +58 -0
- data/lib/hilfer/tree_viewer.rb +805 -0
- metadata +75 -0
data/History.txt
ADDED
data/README.txt
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
= hilfer
|
2
|
+
|
3
|
+
* http://www.semiosix.com/hilfer
|
4
|
+
|
5
|
+
== DESCRIPTION:
|
6
|
+
|
7
|
+
Directory browser with plenty of keyboard shortcuts and integration with scite
|
8
|
+
rails and subversion
|
9
|
+
|
10
|
+
== FEATURES/PROBLEMS:
|
11
|
+
|
12
|
+
See TODO list
|
13
|
+
|
14
|
+
== SYNOPSIS:
|
15
|
+
|
16
|
+
hilfer [directory]
|
17
|
+
|
18
|
+
== REQUIREMENTS:
|
19
|
+
|
20
|
+
ruby-gtk2
|
21
|
+
|
22
|
+
== INSTALL:
|
23
|
+
|
24
|
+
sudo gem install
|
25
|
+
|
26
|
+
== LICENSE:
|
27
|
+
|
28
|
+
(The MIT License)
|
29
|
+
|
30
|
+
Copyright (c) 2008 FIX
|
31
|
+
|
32
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
33
|
+
a copy of this software and associated documentation files (the
|
34
|
+
'Software'), to deal in the Software without restriction, including
|
35
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
36
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
37
|
+
permit persons to whom the Software is furnished to do so, subject to
|
38
|
+
the following conditions:
|
39
|
+
|
40
|
+
The above copyright notice and this permission notice shall be
|
41
|
+
included in all copies or substantial portions of the Software.
|
42
|
+
|
43
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
44
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
45
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
46
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
47
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
48
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
49
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/bin/ec
ADDED
data/bin/hilfer
ADDED
@@ -0,0 +1,260 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
require 'pathname'
|
5
|
+
|
6
|
+
require 'hilfer/tree_viewer.rb'
|
7
|
+
|
8
|
+
require 'optparse'
|
9
|
+
|
10
|
+
# defaults
|
11
|
+
$options = { :debug => false, :hidden => false, :auto_sync => false, :quit_editor => false }
|
12
|
+
|
13
|
+
oparser = OptionParser.new
|
14
|
+
oparser.on( '-y', '--auto-sync', 'track editor current file changes') { |o| $options[:host] = true }
|
15
|
+
oparser.on( '-s', '--show-hidden', 'Show hidden files' ) { |o| $options[:user] = true }
|
16
|
+
oparser.on( '-D', '-d', '-v', '--debug' ) { |o| $options[:debug] = true }
|
17
|
+
oparser.on( '-q', '--quit-editor', 'close editor on shutdown' ) { |o| puts "o: #{o.inspect}"; $options[:quit_editor] = true }
|
18
|
+
|
19
|
+
oparser.on( '-h', '-?', '--help' ) do |o|
|
20
|
+
puts oparser.to_s
|
21
|
+
exit( 0 )
|
22
|
+
end
|
23
|
+
|
24
|
+
# remove parsed options
|
25
|
+
ARGV = args = oparser.parse( ARGV )
|
26
|
+
|
27
|
+
# this must come after options parsing or it tries to eat the options
|
28
|
+
require 'gtk2'
|
29
|
+
|
30
|
+
# NOTE no space after comma, trailing comma is so
|
31
|
+
# that bold, italic etc and font size can be specified
|
32
|
+
FIXED_FONT = "Lucida Console,Courier New,"
|
33
|
+
CONFIG_FILE = File.expand_path( '~/.hilfer' )
|
34
|
+
|
35
|
+
=begin rdoc
|
36
|
+
HilferItems are stored in the TreeModel
|
37
|
+
- item is the display name (ie filename and extension)
|
38
|
+
- path is the full pathname, expanded, unqualified etc etc
|
39
|
+
- last_child_used is the GtkTreeView path of the last child of a directory
|
40
|
+
node that had selection
|
41
|
+
- status is either :expanded or :collapsed
|
42
|
+
- populated is whether or not this directory node has been populated
|
43
|
+
from the filesystem. To aid in recursively populating the tree
|
44
|
+
=end
|
45
|
+
class HilferItem
|
46
|
+
attr_accessor :item, :path, :last_child_used, :status, :svn_status, :colour
|
47
|
+
attr_writer :populated
|
48
|
+
|
49
|
+
def initialize( item, path )
|
50
|
+
@item = item
|
51
|
+
@path = path
|
52
|
+
@dir = File.directory? path
|
53
|
+
@populated = false
|
54
|
+
@status = :collapsed
|
55
|
+
@colour = '#000'
|
56
|
+
end
|
57
|
+
|
58
|
+
def dir?
|
59
|
+
@dir
|
60
|
+
end
|
61
|
+
|
62
|
+
def to_s
|
63
|
+
@item
|
64
|
+
end
|
65
|
+
|
66
|
+
# always return true for file nodes
|
67
|
+
def populated?
|
68
|
+
if !dir?
|
69
|
+
true
|
70
|
+
else
|
71
|
+
@populated
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# always return false for file nodes
|
76
|
+
def expanded?
|
77
|
+
if !dir?
|
78
|
+
false
|
79
|
+
else
|
80
|
+
@status == :expanded
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
=begin rdoc
|
87
|
+
Knows how to lauch gnome-terminal with mutiple tabs,
|
88
|
+
each opened on a different directory
|
89
|
+
=end
|
90
|
+
class GnomeTerminal
|
91
|
+
def launch( dirs )
|
92
|
+
# construct arguments and launch
|
93
|
+
args = [ "gnome-terminal" ]
|
94
|
+
dirs.uniq.each do |x|
|
95
|
+
# first one will be a window, subsequent ones are tabs
|
96
|
+
args << ( args.size == 1 ? '--window' : '--tab' )
|
97
|
+
args << "--working-directory=#{x}"
|
98
|
+
end
|
99
|
+
system( *args )
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
=begin rdoc
|
104
|
+
Just launches xterm. I couldn't figure out how to open it in different
|
105
|
+
directories.
|
106
|
+
|
107
|
+
use it by saying
|
108
|
+
|
109
|
+
$options[:terminal] = Xterm.new
|
110
|
+
|
111
|
+
before the call to TreeViewer.new
|
112
|
+
=end
|
113
|
+
class Xterm
|
114
|
+
def launch( dirs )
|
115
|
+
system( 'xterm &' )
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# yes, this actually does catch method calls polymorphically
|
120
|
+
# but you can't change the TreePath or TreeIter that are returned
|
121
|
+
class HilferModel < Gtk::TreeStore
|
122
|
+
def initialize( *args )
|
123
|
+
super
|
124
|
+
end
|
125
|
+
|
126
|
+
def ancestor?( iter, descendant )
|
127
|
+
print "testing #{iter[0]} against #{descendant}\n" if $options[:debug]
|
128
|
+
super
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
=begin rdoc
|
133
|
+
Read and write configuration to ~/.hilfer in yaml.
|
134
|
+
Currently only writes window position
|
135
|
+
=end
|
136
|
+
class HilferConfig
|
137
|
+
|
138
|
+
def initialize( file, window, count )
|
139
|
+
@file, @window = file, window
|
140
|
+
|
141
|
+
if File.file? CONFIG_FILE
|
142
|
+
config = YAML.load_file CONFIG_FILE
|
143
|
+
@window.set_default_size( *config[:size] )
|
144
|
+
@window.show_all
|
145
|
+
# move must be after show_all otherwise it
|
146
|
+
# gets overridden
|
147
|
+
@window.move( *config[:position] ) if count == 1
|
148
|
+
else
|
149
|
+
@window.set_default_size( 300,700 ).show_all
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# save config
|
154
|
+
def save
|
155
|
+
config = {
|
156
|
+
:path => @window.title,
|
157
|
+
:position => @window.position,
|
158
|
+
:size => @window.size
|
159
|
+
}
|
160
|
+
File.open( CONFIG_FILE, 'w' ) { |file| file.print config.to_yaml }
|
161
|
+
end
|
162
|
+
|
163
|
+
end
|
164
|
+
|
165
|
+
=begin rdoc
|
166
|
+
Layout of the Tree Viewer Window.
|
167
|
+
=end
|
168
|
+
class TreeViewerWindow
|
169
|
+
@@window_count = 0
|
170
|
+
|
171
|
+
def initialize( root_fs_path, editor )
|
172
|
+
@window = Gtk::Window.new
|
173
|
+
|
174
|
+
# initialize viewer with a path
|
175
|
+
@editor = editor
|
176
|
+
|
177
|
+
# location bar
|
178
|
+
@location = Gtk::Entry.new
|
179
|
+
|
180
|
+
@tv = TreeViewer.new( root_fs_path, editor, @window, @location )
|
181
|
+
|
182
|
+
# add scrollbars
|
183
|
+
@scroll = Gtk::ScrolledWindow.new( nil, nil )
|
184
|
+
@scroll.set_policy( Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC )
|
185
|
+
@scroll.add( @tv.view )
|
186
|
+
|
187
|
+
# expand and fill
|
188
|
+
@vbox = Gtk::VBox.new( false )
|
189
|
+
@vbox.pack_start( @location, false, false )
|
190
|
+
@vbox.pack_start( @scroll, true, true )
|
191
|
+
@vbox.focus_chain = [@scroll, @location]
|
192
|
+
|
193
|
+
icon_path = find_icon_path
|
194
|
+
if icon_path != nil
|
195
|
+
@window.icon = Gdk::Pixbuf.new( icon_path )
|
196
|
+
end
|
197
|
+
@window.title = 'Hilfer ' + @tv.root_item.path
|
198
|
+
@window.add( @vbox )
|
199
|
+
|
200
|
+
# read config. OK, the last parameter is a hack
|
201
|
+
# but we need it so that new windows aren't show in
|
202
|
+
# exactly the same position and it looks like nothing
|
203
|
+
# has happened
|
204
|
+
@@window_count += 1
|
205
|
+
@hc = HilferConfig.new( CONFIG_FILE, @window, @@window_count )
|
206
|
+
|
207
|
+
# set first line selected
|
208
|
+
@tv.select_first
|
209
|
+
|
210
|
+
# The program will directly end upon 'delete_event', ie window close
|
211
|
+
@window.signal_connect( 'delete_event' ) do
|
212
|
+
# unregister view from editor
|
213
|
+
@editor.unregister_view( @tv )
|
214
|
+
# finish
|
215
|
+
@@window_count -= 1
|
216
|
+
if @@window_count == 0
|
217
|
+
@hc.save
|
218
|
+
# this MUST come before cleanup, otherwise
|
219
|
+
# there will be no pipes to write the command to
|
220
|
+
@editor.quit
|
221
|
+
# delete command pipe files
|
222
|
+
@editor.cleanup
|
223
|
+
Gtk.main_quit
|
224
|
+
false
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
# to allow the sd command to find the correct editor instance
|
229
|
+
@window.signal_connect 'focus-in-event' do |widget,event|
|
230
|
+
@editor.write_pipe_name
|
231
|
+
false
|
232
|
+
end
|
233
|
+
|
234
|
+
# to allow the sd command to find the correct editor instance
|
235
|
+
@window.signal_connect 'focus-out-event' do |widget,event|
|
236
|
+
@editor.write_pipe_name
|
237
|
+
false
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
def find_icon_path
|
242
|
+
$LOAD_PATH.grep( /hilfer/ ).each do |path|
|
243
|
+
png_file = Pathname.new( path ) + 'hilfer-icon.png'
|
244
|
+
return png_file.to_s if png_file.exist?
|
245
|
+
end
|
246
|
+
nil
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
|
251
|
+
################
|
252
|
+
# Main script
|
253
|
+
|
254
|
+
tvw = TreeViewerWindow.new(
|
255
|
+
File.expand_path( ARGV[0] || '.' ),
|
256
|
+
SciteEditor.new
|
257
|
+
)
|
258
|
+
|
259
|
+
# and off we go...
|
260
|
+
Gtk.main
|
data/bin/hilfer-icon.png
ADDED
Binary file
|
data/bin/sd
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
#! /bin/bash
|
2
|
+
pipe=`cat /tmp/$USER.hilfer.scite`
|
3
|
+
director=/tmp/$USER.scite.director
|
4
|
+
wd=`pwd`
|
5
|
+
if [ -e $pipe ]; then
|
6
|
+
for name in $@; do
|
7
|
+
echo open:$wd/$name >$pipe;
|
8
|
+
done
|
9
|
+
else
|
10
|
+
test -e $pipe && /bin/rm $pipe
|
11
|
+
test -e $director && /bin/rm $director
|
12
|
+
/usr/bin/mkfifo $director
|
13
|
+
/usr/bin/scite \
|
14
|
+
-ipc.director.name=$director \
|
15
|
+
-ipc.scite.name=$pipe \
|
16
|
+
$@ &
|
17
|
+
fi
|
@@ -0,0 +1,150 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
|
3
|
+
=begin
|
4
|
+
This handles various keypresses that jump to specific
|
5
|
+
locations in a Rails project. If a specific file isn't
|
6
|
+
selected, the jump will go to the directory. Otherwise
|
7
|
+
it will go to a specific file.
|
8
|
+
|
9
|
+
For example, if user_controller.rb is currently selected
|
10
|
+
and Ctrl-Shift-T is pressed, user_controller_test.rb will
|
11
|
+
be selected.
|
12
|
+
|
13
|
+
TODO fix multiple selections
|
14
|
+
=end
|
15
|
+
class RailsLocator
|
16
|
+
attr_accessor :treeviewer
|
17
|
+
|
18
|
+
def initialize( treeviewer )
|
19
|
+
@treeviewer = treeviewer
|
20
|
+
|
21
|
+
@controller_re = %r|^(.*?)/app/controllers/(.*?)/?(.*?)_controller.rb$|
|
22
|
+
@view_re = %r|^(.*?)/app/views/(.*?)/?([^/.]*?\..*?)?$|
|
23
|
+
@model_re = %r|^(.*?)/app/models/(.*?/?)([^/.]*?).rb$|
|
24
|
+
@unit_test_re = %r|^(.*?)/test/unit/(.*?/?)(.*?)_test.rb$|
|
25
|
+
@functional_test_re = %r|^(.*?)/test/functional/(.*?/?)(.*?)_controller_test.rb$|
|
26
|
+
@fixture_re = %r|^(.*?)/test/fixtures/(.*?/?)(.*?).yml$|
|
27
|
+
end
|
28
|
+
|
29
|
+
# this does the common code for each keypress
|
30
|
+
# the block should return a related path for
|
31
|
+
# the particular keypress on the currently selected
|
32
|
+
# item
|
33
|
+
def select_related_paths( widget, event, &block )
|
34
|
+
items = []
|
35
|
+
paths_to_select = []
|
36
|
+
widget.selection.selected_each do
|
37
|
+
|model, path, iter|
|
38
|
+
item = model.get_value( iter, 0 )
|
39
|
+
puts "item: #{item.path}" if $options[:debug]
|
40
|
+
new_rails_path = yield( item )
|
41
|
+
puts "new_rails_path #{new_rails_path}" if $options[:debug]
|
42
|
+
paths_to_select << rails_root + new_rails_path
|
43
|
+
end
|
44
|
+
|
45
|
+
treeviewer.select_fs_paths( paths_to_select, true )
|
46
|
+
end
|
47
|
+
|
48
|
+
def rails_root
|
49
|
+
treeviewer.root_item.path
|
50
|
+
end
|
51
|
+
|
52
|
+
def goto_rails_path( fs_path )
|
53
|
+
treeviewer.select_fs_path( rails_root + fs_path, true )
|
54
|
+
end
|
55
|
+
|
56
|
+
def handle_keypress( widget, event )
|
57
|
+
retval = true
|
58
|
+
case
|
59
|
+
# Shift-Ctrl-V - go to view, or view dir
|
60
|
+
when event.hardware_keycode == 55 && event.state.control_mask? && event.state.shift_mask?
|
61
|
+
select_related_paths( widget, event ) do |item|
|
62
|
+
case
|
63
|
+
when @controller_re.match( item.path )
|
64
|
+
'/app/views/' + $2 + $3
|
65
|
+
when @functional_test_re.match( item.path )
|
66
|
+
'/app/views/' + $2 + $3
|
67
|
+
else
|
68
|
+
'/app/views'
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Shift-Ctrl-C - go to controller, or controller dir
|
73
|
+
when event.hardware_keycode == 54 && event.state.control_mask? && event.state.shift_mask?
|
74
|
+
select_related_paths( widget, event ) do |item|
|
75
|
+
case
|
76
|
+
when md = @view_re.match( item.path )
|
77
|
+
'/app/controllers/' + $2 + '_controller.rb'
|
78
|
+
when md = @functional_test_re.match( item.path )
|
79
|
+
'/app/controllers/' + $2 + $3 + '_controller.rb'
|
80
|
+
else
|
81
|
+
'/app/controllers'
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Shift-Ctrl-M - go to model, or model dir
|
86
|
+
when event.hardware_keycode == 58 && event.state.control_mask? && event.state.shift_mask?
|
87
|
+
select_related_paths( widget, event ) do |item|
|
88
|
+
case
|
89
|
+
when md = @unit_test_re.match( item.path )
|
90
|
+
'/app/models' + $2 + '/' + $3 + '.rb'
|
91
|
+
when md = @fixture_re.match( item.path )
|
92
|
+
'/app/models' + $2 + '/' + $3.singularize + '.rb'
|
93
|
+
else
|
94
|
+
'/app/models'
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# Shift-Ctrl-T - go to test, or test dir
|
99
|
+
when event.hardware_keycode == 28 && event.state.control_mask? && event.state.shift_mask?
|
100
|
+
select_related_paths( widget, event ) do |item|
|
101
|
+
case
|
102
|
+
when md = @model_re.match( item.path )
|
103
|
+
'/test/unit/' + $2 + $3 + '_test.rb'
|
104
|
+
when md = @view_re.match( item.path )
|
105
|
+
'/test/functional/' + $2 + '_controller_test.rb'
|
106
|
+
when md = @controller_re.match( item.path )
|
107
|
+
'/test/functional/' + $2 + $3 + '_controller_test.rb'
|
108
|
+
when md = @fixture_re.match( item.path )
|
109
|
+
'/test/unit/' + $2 + $3.singularize + '_test.rb'
|
110
|
+
else
|
111
|
+
'/test'
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# Shift-Ctrl-H - go to helpers dir
|
116
|
+
when event.hardware_keycode == 43 && event.state.control_mask? && event.state.shift_mask?
|
117
|
+
goto_rails_path '/app/helpers'
|
118
|
+
|
119
|
+
# Shift-Ctrl-L - go to layouts dir
|
120
|
+
when event.hardware_keycode == 46 && event.state.control_mask? && event.state.shift_mask?
|
121
|
+
goto_rails_path '/app/views/layouts'
|
122
|
+
|
123
|
+
# Shift-Ctrl-I - go to migrations
|
124
|
+
when event.hardware_keycode == 31 && event.state.control_mask? && event.state.shift_mask?
|
125
|
+
goto_rails_path '/db/migrate'
|
126
|
+
|
127
|
+
# Shift-Ctrl-S - go to stylesheets
|
128
|
+
when event.hardware_keycode == 39 && event.state.control_mask? && event.state.shift_mask?
|
129
|
+
goto_rails_path '/public/stylesheets'
|
130
|
+
|
131
|
+
# Shift-Ctrl-F - go to fixtures
|
132
|
+
when event.hardware_keycode == 41 && event.state.control_mask? && event.state.shift_mask?
|
133
|
+
select_related_paths( widget, event ) do |item|
|
134
|
+
case
|
135
|
+
when md = @model_re.match( item.path )
|
136
|
+
'/test/fixtures' + $2 + '/' + $3.pluralize + '.yml'
|
137
|
+
when md = @unit_test_re.match( item.path )
|
138
|
+
'/test/fixtures' + $2 + '/' + $3.pluralize + '.yml'
|
139
|
+
else
|
140
|
+
'/test/fixtures'
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
else
|
145
|
+
retval = false
|
146
|
+
end
|
147
|
+
retval
|
148
|
+
end
|
149
|
+
|
150
|
+
end
|
@@ -0,0 +1,182 @@
|
|
1
|
+
# handle director interface to scite
|
2
|
+
class SciteEditor
|
3
|
+
|
4
|
+
# options can contain :debug
|
5
|
+
# view is a GtkTreeView
|
6
|
+
def initialize( options = {} )
|
7
|
+
@scite_pipe_name = "/tmp/#{ENV['USER']}.#{Process.pid}.scite"
|
8
|
+
@director_pipe_name = "/tmp/#{ENV['USER']}.#{Process.pid}.director"
|
9
|
+
|
10
|
+
# delete these if they already exist because we
|
11
|
+
# need to use their existence to determine if scite
|
12
|
+
# is running
|
13
|
+
File.exists?( @scite_pipe_name ) && File.unlink( @scite_pipe_name )
|
14
|
+
File.exists?( @director_pipe_name ) && File.unlink( @director_pipe_name )
|
15
|
+
|
16
|
+
# this is an array of TreeViewer objects
|
17
|
+
@views = []
|
18
|
+
# the command-line options
|
19
|
+
@options = options
|
20
|
+
end
|
21
|
+
|
22
|
+
def cleanup
|
23
|
+
FileUtils.rm @scite_pipe_name if File.exist? @scite_pipe_name
|
24
|
+
FileUtils.rm @director_pipe_name if File.exist? @director_pipe_name
|
25
|
+
FileUtils.rm pipe_name_file if File.exist? pipe_name_file
|
26
|
+
end
|
27
|
+
|
28
|
+
# send some command
|
29
|
+
def send( cmd, arg = '' )
|
30
|
+
launch
|
31
|
+
File.open( @scite_pipe_name, 'a' ) do |file|
|
32
|
+
file.print( cmd + ":" + arg.to_s, "\n" )
|
33
|
+
puts "sending: #{cmd}:#{arg.to_s}" if $options[:debug]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# open files in scite
|
38
|
+
def open_action( files )
|
39
|
+
files.each { |x| send "open", x.path }
|
40
|
+
send 'identity',0
|
41
|
+
end
|
42
|
+
|
43
|
+
def dump
|
44
|
+
%w{dyn local user base embed}.each {|x| send 'enumproperties', x}
|
45
|
+
#~ send 'askproperty','dyn:CurrentWord'
|
46
|
+
end
|
47
|
+
|
48
|
+
# fetch the current file in scite
|
49
|
+
def synchronize_path
|
50
|
+
send 'askfilename'
|
51
|
+
end
|
52
|
+
|
53
|
+
# shut down editor
|
54
|
+
def quit
|
55
|
+
send 'quit'
|
56
|
+
end
|
57
|
+
|
58
|
+
# insert text to editor, at current caret, or overwriting the current selection
|
59
|
+
def insert( arg )
|
60
|
+
value =
|
61
|
+
case
|
62
|
+
# note use of single quote - SciTE wants escaped characters
|
63
|
+
when arg.respond_to?( :join ); arg.join( '\\n' ) + '\\n'
|
64
|
+
else; arg.to_s
|
65
|
+
end
|
66
|
+
send 'insert', value
|
67
|
+
end
|
68
|
+
|
69
|
+
# send selection to all registered views
|
70
|
+
def set_selection ( fs_path )
|
71
|
+
@views.each { |v| v.synchronise_editor_path( fs_path ) }
|
72
|
+
end
|
73
|
+
|
74
|
+
def register_view( view )
|
75
|
+
@views << view
|
76
|
+
end
|
77
|
+
|
78
|
+
def unregister_view( view )
|
79
|
+
@views.delete( view )
|
80
|
+
end
|
81
|
+
|
82
|
+
def pipe_name_file
|
83
|
+
@pipe_name_file ||= "/tmp/#{ENV['USER']}.hilfer.scite"
|
84
|
+
end
|
85
|
+
|
86
|
+
def write_pipe_name
|
87
|
+
File.open( pipe_name_file, 'w' ) do |f|
|
88
|
+
f.write @scite_pipe_name
|
89
|
+
f.write "\n"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
protected
|
94
|
+
|
95
|
+
# start up the editor if there isn't already one
|
96
|
+
# calling it when the editor is already open does nothing
|
97
|
+
def launch
|
98
|
+
# create the director pipe if it isn't there already
|
99
|
+
if !File.exists?( @director_pipe_name )
|
100
|
+
system( "mkfifo #{@director_pipe_name}" )
|
101
|
+
end
|
102
|
+
|
103
|
+
# scite already open, so don't launch another instance
|
104
|
+
return if File.exists?( @scite_pipe_name )
|
105
|
+
|
106
|
+
cmd = <<EOF
|
107
|
+
/usr/bin/scite
|
108
|
+
-ipc.director.name=#{@director_pipe_name}
|
109
|
+
-ipc.scite.name=#{@scite_pipe_name}
|
110
|
+
EOF
|
111
|
+
oneline = cmd.gsub( "\n", " ")
|
112
|
+
child_pid = fork
|
113
|
+
if child_pid.nil?
|
114
|
+
system( oneline )
|
115
|
+
|
116
|
+
# scite ended, so delete the pipes
|
117
|
+
File.unlink @director_pipe_name if File.exist? @director_pipe_name
|
118
|
+
File.unlink @scite_pipe_name if File.exist? @scite_pipe_name
|
119
|
+
|
120
|
+
# unfork, don't trigger a SystemException
|
121
|
+
exit! true
|
122
|
+
else
|
123
|
+
# start the listener thread
|
124
|
+
start_listener
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def start_listener
|
129
|
+
|
130
|
+
sleep 0.1 while !File.exists? @scite_pipe_name
|
131
|
+
|
132
|
+
Thread.new( self ) do |arg|
|
133
|
+
begin
|
134
|
+
arg.listen
|
135
|
+
rescue Errno::ENOENT
|
136
|
+
break
|
137
|
+
rescue Exception => e
|
138
|
+
print "listener thread ended: ", e.inspect, "\n"
|
139
|
+
e.backtrace.each { |x| print x, "\n" }
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
end
|
144
|
+
|
145
|
+
# respond to strings from scite
|
146
|
+
def listen
|
147
|
+
File.open( @director_pipe_name ).each( "\x0" ) do |line|
|
148
|
+
begin
|
149
|
+
line = line.slice(0...-1) if line[-1] = 0
|
150
|
+
print "heard #{line}\n" if $options[:debug]
|
151
|
+
case line
|
152
|
+
# scite sends one of these for each file opened
|
153
|
+
when /^opened:(.*)$/
|
154
|
+
set_selection( $1 )
|
155
|
+
|
156
|
+
# scite sends one of these each time the buffer is switched
|
157
|
+
when /^switched:(.*)$/
|
158
|
+
set_selection( $1 )
|
159
|
+
|
160
|
+
# response to askfilename, as sent by synchronize_path
|
161
|
+
when /^filename:(.*)$/
|
162
|
+
set_selection( $1 )
|
163
|
+
|
164
|
+
# the specified file has just been saved. Do nothing.
|
165
|
+
# TODO could check that it exists and add it if not.
|
166
|
+
when /^saved:(.*)$/
|
167
|
+
|
168
|
+
when /^closed$/
|
169
|
+
puts "SciTE closing"
|
170
|
+
break
|
171
|
+
|
172
|
+
# print it out
|
173
|
+
else
|
174
|
+
print "unknown: ", line, "\n" if $options[:debug]
|
175
|
+
end
|
176
|
+
rescue Exception => e
|
177
|
+
print "caught Exception in listen: ", e.inspect, "\n"
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
end
|