Posted by Jim Morris
Sat, 25 Nov 2006 03:05:00 GMT
I imported the rest of the Textmate ruby and rails snippets I had to combine them
into the one ruby file though.
I have added about 80 new ones to the existing ones by Scott Becker
that I found here
The list of snippets I now have is at the end of this post, note I did change the
abbreviation for some of the assertions they all now start with as so
I can search for them faster with my select_superabbrevs macro
described in the previous post.
download the new snippets from here
unzip and replace the ruby file in the .jedit/SuperAbbrevs directory with the one in
this zip archive.
You need the Beta version of SuperAbbrevs to use this from here
Super Abbreviations for the ruby mode
--------------------------------------- : --------------------------------------
:${1:key} => ${2:"value"}$end
-------------------------------------- all -------------------------------------
all? { |${1:e}| $end }
-------------------------------------- am --------------------------------------
alias_method :${1:new_name}, :${2:old_name}
-------------------------------------- any -------------------------------------
any? { |${1:e}| $end }
-------------------------------------- app -------------------------------------
if __FILE__ == \$PROGRAM_NAME
$end
end
-------------------------------------- ase -------------------------------------
assert_equal ${1:value}, @${2:thing}.${3:attr}$end
------------------------------------- asid -------------------------------------
assert_in_delta(${1:expected_float}, ${2:actual_float}, ${3:2 ** -20})
------------------------------------- asio -------------------------------------
assert_instance_of(${1:ExpectedClass}, ${2:actual_instance})$end
------------------------------------- asko -------------------------------------
assert_kind_of ${1:Class}, @${2:thing}$end
-------------------------------------- asm -------------------------------------
assert_match(/${1:expected_pattern}/, ${2:actual_string})
-------------------------------------- asn -------------------------------------
assert_nil(${1:instance})$end
------------------------------------- asne -------------------------------------
assert_not_equal(${1:unexpected}, ${2:actual})
------------------------------------- asnm -------------------------------------
assert_no_match(/${1:unexpected_pattern}/, ${2:actual_string})
------------------------------------- asnn -------------------------------------
assert_not_nil ${1:true}$end
------------------------------------- asnr -------------------------------------
assert_nothing_raised(${1:Exception}) { $end }
------------------------------------- asns -------------------------------------
assert_not_same(${1:unexpected}, ${2:actual})$end
------------------------------------- asnt -------------------------------------
assert_nothing_thrown { $end }
-------------------------------------- aso -------------------------------------
assert_operator(${1:left}, :${2:operator}, ${3:right})
-------------------------------------- asr -------------------------------------
assert_raise(${1:ActiveRecord::RecordNotFound}) { ${2:Class}.find(@${3:thing}.${4:id}) }$end
------------------------------------- asrar ------------------------------------
assert_raise(${1:ActiveRecord::RecordNotFound}) { ${2:Class}.find(@${3:thing}.${4:id}) }
------------------------------------- asre -------------------------------------
assert_response :${1:success}$end
------------------------------------- asrt -------------------------------------
assert_redirected_to :action => "${1:index}"$end
-------------------------------------- ass -------------------------------------
assert(${1:test}, "${2:Failure message.}")
------------------------------------- assm -------------------------------------
assert_same(${1:expected}, ${2:actual})$end
-------------------------------------- ast -------------------------------------
assert_throws(:${1:expected}) { $end }
-------------------------------------- bt --------------------------------------
belongs_to :${1:object}, :class_name => "${2:ClassName}", :foreign_key => "${3:foreign_key}_id"$end
------------------------------------- case -------------------------------------
case ${1:object}
when ${2:condition}
$end
end
-------------------------------------- cl --------------------------------------
classify { |${1:e}| $end }
------------------------------------- clafn ------------------------------------
split("::").inject(Object) { |par, const| par.const_get(const) }
------------------------------------- class ------------------------------------
class ${1:ClassName}
$end
end
-------------------------------------- col -------------------------------------
collect { |${1:e}| $end }
------------------------------------ collect -----------------------------------
collect { |${1:element}| ${1:element}.$2 }$end
------------------------------------- Comp -------------------------------------
include Comparable
def <=>(other)
$end
end
-------------------------------------- dee -------------------------------------
Marshal.load(Marshal.dump(${1:obj_to_copy}))
-------------------------------------- def -------------------------------------
def ${1:method_name}
$end
end
------------------------------------- defd -------------------------------------
def_delegator :${1:@del_obj}, :${2:del_meth}, :${3:new_name}
------------------------------------- defds ------------------------------------
def_delegators :${1:@del_obj}, :${2:del_methods}
------------------------------------- defs -------------------------------------
def self.${1:class_method_name}
$end
end
------------------------------------- deft -------------------------------------
def test_${1:case_name}
$end
end
-------------------------------------- det -------------------------------------
detect { |${1:e}| $end }
-------------------------------------- do --------------------------------------
do
$end
end
-------------------------------------- doo -------------------------------------
do |${1:object}|
$end
end
-------------------------------------- ea --------------------------------------
each { |${1:e}| $end }
-------------------------------------- eab -------------------------------------
each_byte { |${1:byte}| $end }
-------------------------------------- eac -------------------------------------
each_char { |${1:chr}| $end }
------------------------------------- each -------------------------------------
each { |${1:element}| ${1:element}.$end }
-------------------------------- each_with_index -------------------------------
each_with_index { |${1:element}, ${2:idx}| ${1:element}.$end }
-------------------------------------- eai -------------------------------------
each_index { |${1:i}| $end }
-------------------------------------- eak -------------------------------------
each_key { |${1:key}| $end }
-------------------------------------- eal -------------------------------------
each_line$1 { |${2:line}| $end }
-------------------------------------- eap -------------------------------------
each_pair { |${1:name}, ${2:val}| $end }
------------------------------------- easl -------------------------------------
each_slice(${1:2}) { |${2:group}| $end }
-------------------------------------- eav -------------------------------------
each_value { |${1:val}| $end }
------------------------------------- eawi -------------------------------------
each_with_index { |${1:e}, ${2:i}| $end }
------------------------------------- Enum -------------------------------------
include Enumerable
def each(&block)
$end
end
-------------------------------------- fin -------------------------------------
find { |${1:e}| $end }
------------------------------------- fina -------------------------------------
find_all { |${1:e}| $end }
-------------------------------------- fl --------------------------------------
flunk("${1:Failure message.}")
------------------------------------- flao -------------------------------------
inject(Array.new) { |${1:arr}, ${2:a}| ${1:arr}.push(*${2:a}) }
------------------------------------- flash ------------------------------------
flash[:${1:notice}] = "${2:Successfully created...}"$end
------------------------------------- forin ------------------------------------
for ${1:element} in ${2:collection}
${1:element}.$3
end$end
------------------------------------- Forw -------------------------------------
extend Forwardable
-------------------------------------- gre -------------------------------------
grep(${1:/${2:pattern}/}) { |${3:match}| $end }
------------------------------------- Hash -------------------------------------
Hash.new { |${1:hash}, ${2:key}| ${1:hash}[${2:key}] = $end }
-------------------------------------- hm --------------------------------------
has_many :${1:objects}, :class_name => "${2:ClassName}", :foreign_key => "${3:foreign_key}_id"$end
-------------------------------------- ho --------------------------------------
has_one :${1:object}, :class_name => "${2:ClassName}", :foreign_key => "${3:foreign_key}_id"$end
-------------------------------------- if --------------------------------------
if ${1:condition}
$end
end
-------------------------------------- ife -------------------------------------
if ${1:condition}
$2
else
$3
end
-------------------------------------- inj -------------------------------------
inject(${1:init}) { |${2:mem}, ${3:var}| $end }
------------------------------------ inject ------------------------------------
inject(${1:object}) { |${2:injection}, ${3:element}| $4 }$end
------------------------------------- logi -------------------------------------
logger.info "${1:Current value is...}"$end
-------------------------------------- mac -------------------------------------
add_column :${1:table_name}, :${2:column_name}, :${3:string}$end
-------------------------------------- mai -------------------------------------
add_index :${1:table_name},[:${2:column_name}], :name => "${2:column_name}_index"$end
-------------------------------------- map -------------------------------------
map { |${1:e}| $end }
------------------------------------- mapwi ------------------------------------
enum_with_index.map { |${1:e}, ${2:i}| $end }
-------------------------------------- max -------------------------------------
max { |a, b| $end }
-------------------------------------- mcc -------------------------------------
change_column :${1:table_name}, :${2:column_name}, :${3:string}, ${4:default => 1}$end
-------------------------------------- mct -------------------------------------
create_table :${1:table_name} do |t|
$2
end
-------------------------------------- Md --------------------------------------
File.open(${1:"${2:path/to/file}.dump"}, "w") { |${3:file}| Marshal.dump(${4:obj}, ${3:file}) }
-------------------------------------- mdt -------------------------------------
drop_table :${1:table_name}$end
-------------------------------------- mex -------------------------------------
execute "$1"$end
-------------------------------------- min -------------------------------------
min { |a, b| $end }
-------------------------------------- mm --------------------------------------
def method_missing(meth, *args, &block)
$end
end
-------------------------------------- mnc -------------------------------------
rename_column :${1:table_name}, :${2:column_name}, :${3:new_column}$end
-------------------------------------- mod -------------------------------------
module ${1:ModuleName}
module_function
$end
end
-------------------------------------- mrc -------------------------------------
remove_column :${1:table_name}, :${2:column_name}$end
-------------------------------------- mri -------------------------------------
remove_index :${1:table_name}, :${2:column_name}$end
-------------------------------------- mtc -------------------------------------
t.column :${1:column_name}, :${2:string}$end
--------------------------------------- p --------------------------------------
params[:${1:id}]$end
-------------------------------------- par -------------------------------------
partition { |${1:e}| $end }
--------------------------------------- r --------------------------------------
attr_reader :${1:attr_names}
-------------------------------------- ra --------------------------------------
render :action => "${1:action}"$end
-------------------------------------- ral -------------------------------------
render :action => "${1:action}", :layout => "${2:layoutname}"$end
-------------------------------------- ran -------------------------------------
sort_by { rand }
-------------------------------------- rb --------------------------------------
#!/usr/bin/env ruby -w
------------------------------------- rcea -------------------------------------
render_component :action => "${1:index}"$end
------------------------------------- rcec -------------------------------------
render_component :controller => "${1:items}"$end
------------------------------------- rceca ------------------------------------
render_component :controller => "${1:items}", :action => "${2:index}"$end
-------------------------------------- rea -------------------------------------
redirect_to :action => "${1:index}"$end
------------------------------------- reai -------------------------------------
redirect_to :action => "${1:show}", :id => ${2:@item}$end
-------------------------------------- rec -------------------------------------
redirect_to :controller => "${1:items}"$end
------------------------------------- reca -------------------------------------
redirect_to :controller => "${1:items}", :action => "${2:list}"$end
------------------------------------- recai ------------------------------------
redirect_to :controller => "${1:items}", :action => "${2:show}", :id => ${3:@item}$end
-------------------------------------- rej -------------------------------------
reject { |${1:e}| $end }
------------------------------------ reject ------------------------------------
reject { |${1:element}| ${1:element}.$end }
-------------------------------------- req -------------------------------------
require "$end"
------------------------------------- reve -------------------------------------
reverse_each { |${1:e}| $end }
-------------------------------------- rf --------------------------------------
render :file => "${1:filepath}"$end
-------------------------------------- rfu -------------------------------------
render :file => "${1:filepath}", :use_full_path => ${2:false}$end
-------------------------------------- ri --------------------------------------
render :inline => "${1:<%= 'hello' %>}"$end
-------------------------------------- ril -------------------------------------
render :inline => "${1:<%= 'hello' %>}", :locals => { ${2::name} => "${3:value}"$4 }$end
-------------------------------------- rit -------------------------------------
render :inline => "${1:<%= 'hello' %>}", :type => ${2::rxml}$end
-------------------------------------- rl --------------------------------------
render :layout => "${1:layoutname}"$end
-------------------------------------- rn --------------------------------------
render :nothing => ${1:true}$end
-------------------------------------- rns -------------------------------------
render :nothing => ${1:true}, :status => ${2:401}$end
-------------------------------------- rp --------------------------------------
render :partial => "${1:item}"$end
-------------------------------------- rpc -------------------------------------
render :partial => "${1:item}", :collection => ${2:items}$end
-------------------------------------- rpl -------------------------------------
render :partial => "${1:item}", :locals => { :${2:name} => "${3:value}"$4 }$end
-------------------------------------- rpo -------------------------------------
render :partial => "${1:item}", :object => ${2:object}$end
-------------------------------------- rps -------------------------------------
render :partial => "${1:item}", :status => ${2:500}$end
-------------------------------------- rt --------------------------------------
render :text => "${1:text to render...}"$end
-------------------------------------- rtl -------------------------------------
render :text => "${1:text to render...}", :layout => "${2:layoutname}"$end
------------------------------------- rtlt -------------------------------------
render :text => "${1:text to render...}", :layout => ${2:true}$end
-------------------------------------- rts -------------------------------------
render :text => "${1:text to render...}", :status => ${2:401}$end
-------------------------------------- rw --------------------------------------
attr_accessor :${1:attr_names}
--------------------------------------- s --------------------------------------
session[:${1:user}]$end
-------------------------------------- sca -------------------------------------
scan(/${1:pattern}/) { |${2:match}| $end }
-------------------------------------- sel -------------------------------------
select { |${1:e}| $end }
------------------------------------ select ------------------------------------
select { |${1:element}| ${1:element}.$2 }$end
-------------------------------------- sor -------------------------------------
sort { |a, b| $end }
------------------------------------- sorb -------------------------------------
sort_by { |${1:e}| $end }
-------------------------------------- tc --------------------------------------
require "test/unit"
require "${1:library_file_name}"
deli delete_if { |${1:e}| $end }
-------------------------------------- ts --------------------------------------
require "test/unit"
require "tc_${1:test_case_file}"
require "tc_${2:test_case_file}"
-------------------------------------- uni -------------------------------------
ARGF.each_line$1 do |${2:line}|
$end
end
------------------------------------ unless ------------------------------------
unless ${1:condition}
$end
end
------------------------------------- usai -------------------------------------
if ARGV.$1
puts "Usage: #{\$PROGRAM_NAME} ${2:ARGS_GO_HERE}"
exit
end
------------------------------------- usau -------------------------------------
unless ARGV.$1
puts "Usage: #{\$PROGRAM_NAME} ${2:ARGS_GO_HERE}"
exit
end
-------------------------------------- va --------------------------------------
validates_associated :${1:attribute}, :on => "${2:create}"$end
------------------------------------- vaif -------------------------------------
validates_associated :${1:attribute}, :on => "${2:create}", :if => proc { |obj| ${3:obj.condition?} }$end
-------------------------------------- vc --------------------------------------
validates_confirmation_of :${1:attribute}, :on => "${2:create}", :message => "${3:should match confirmation}"$end
------------------------------------- vcif -------------------------------------
validates_confirmation_of :${1:attribute}, :on => "${2:create}", :message => "${3:should match confirmation}", :if => proc { |obj| ${4:obj.condition?} }$end
-------------------------------------- ve --------------------------------------
validates_exclusion_of :${1:attribute}, :in => ${2:enumerable}, :on => "${3:create}", :message => "${4:is not allowed}"$end
------------------------------------- veif -------------------------------------
validates_exclusion_of :${1:attribute}, :in => ${2:enumerable}, :on => "${3:create}", :message => "${4:is not allowed}", :if => proc { |obj| ${5:obj.condition?} }$end
------------------------------------ verify ------------------------------------
verify :only => [:$1], :method => :post, :render => {:status => 500, :text => "use HTTP-POST"}$end
------------------------------------ verifyr -----------------------------------
verify :only => [:$1], :session => :user, :params => :id, :redirect_to => {:action => '${2:index}'}$end
------------------------------------- vlen -------------------------------------
validates_length_of ${1::name}, :maximum=>${2:10}, :message=>"${3:less than %d if you don't mind}"
-------------------------------------- vp --------------------------------------
validates_presence_of :${1:attribute}, :on => "${2:create}", :message => "${3:must be present}"$end
------------------------------------- vpif -------------------------------------
validates_presence_of :${1:attribute}, :on => "${2:create}", :message => "${3:must be present}", :if => proc { |obj| ${4:obj.condition?} }$end
-------------------------------------- vu --------------------------------------
validates_uniqueness_of :${1:attribute}, :on => "${2:create}", :message => "${3:must be unique}"$end
------------------------------------- vuif -------------------------------------
validates_uniqueness_of :${1:attribute}, :on => "${2:create}", :message => "${3:must be unique}", :if => proc { |obj| ${4:obj.condition?} }$end
--------------------------------------- w --------------------------------------
attr_writer :${1:attr_names}
------------------------------------- when -------------------------------------
when ${1:condition}
$end
------------------------------------- ydump ------------------------------------
File.open(${1:"${2:path/to/file}.yaml"}, "w") { |${3:file}| YAML.dump(${4:obj}, ${3:file}) }
------------------------------------- yload ------------------------------------
File.open(${1:"${2:path/to/file}.yaml"}) { |${3:file}| YAML.load(${3:file}) }
-------------------------------------- zip -------------------------------------
zip(${1:enums}) { |${2:row}| $end }
Posted in Rails, Ruby, JEdit | Tags jedit, rails, ruby, snippets, superabbrevs | 8 comments | no trackbacks
Posted by Jim Morris
Wed, 22 Nov 2006 07:20:07 GMT
OK after
expounding
on how much I like Epsilon as an editor, I finally hit a wall with it.
I wanted tabs of the currently open files, and it simply does not
provide that facility yet. (I know all modern editors do!). On X11 (IE
Linux and OS/X) Epsilon is not really a graphical app, it does open an
X11 window, but within it most dialogs and lists (like buffer lists
etc) are text, a little like a curses app. The advantage is it opens
really fast, and loads huge files pretty fast. The downside is you
don't get all those nice windowing facilities like movable pop-up
windows, dialogs, trees and tabs!
So after hearing so much about JEdit, I bit the bullet and gave it a
whirl. Following the instructions from a number of blogs (too numerous
to mention) I installed it on my KUbuntu Linux box, with all the
plugins people recommend, and of course the Ruby plugin and
SupperAbbrevs (The beta version), so I have my snippets!
Being used to an Emacs like editor I had to fuss around remapping all
my keys (or at least some of them), then map the new plugins to
reasonable key-bindings (called shortcuts in JEdit lingo).
I have to say I am pretty impressed, it does everything I want (or
almost). It has a plugin for tabs, it has a project manager, and
HTML/XML modes. The Ruby mode is pretty good, modeled after Textmate.
So the things that were missing that I had become accustomed to were.
- Expanding #{} in a string when you type # - Wrote a macro to do that
- Being able to select a snippet (called abbreviation in the lingo)
from a list rather than having to remember the abbreviation. - wrote a macro to do it
- Some Emacs like behavior like control-K etc. - found it in the macros manager
- Toggling between view and controller (and model) in a rails project - Wrote one, but found a better one in macros
- Being able to use snippets (OK abbreviations!) from a different
language mode. eg I'm in rhtml but I want an HTML abbreviation, or
vice versa. - wrote a macro to do it
- Running ruby scripts into a local buffer - the console plugin solved this
- Running Focused Ruby Unit tests - wrote a macro to do it
So at first I thought I would have to hack the various plugins, but
then I discovered the beanShell macros are extremely powerful,
basically they are java syntax, but have full access to the JEdit
internals and most of the plugins internals too. As Java is my second
favorite language, this was a nice surprise. (You should guess by now
Ruby is my current favorite language).
So I went about writing macros to fill in the gaps, to see if I could
switch to JEdit.
As a side note I will not buy a Mac so I can use Textmate! as awesome
as Textmate is, it is not enough to get me to switch, so I will get
JEdit as close to it as I can, maybe even further. (I worked on a
MacBook Pro Intel for 3+ months, and was not sold).
Yes JEdit actually has intellisense-like completion for Ruby. A
previous blog entry
of mine explained how I tried Komodo to get this, but it was not very
good, however the JEdit version found in the latest RubyPlugin, works
pretty well, I think it uses parsing from the JRuby project, so no
surprise it works.
In addition it also has a pretty good ruby and rails help, that is
linked to the autocomplete, it is missing a few rails entries, but is
very handy. Features of this clever plugin are found
here.
All this and JEdit is free!
Back to the macro writing.
The first hack, er I mean Macro, was to expand # to #{} when it is in
a string. This was relatively easy, as shown below. I just has to make
sure I was actually in a string, and take into account escaped quotes
and escaped #. Anyway the macro works quite well, I think it would be
better off in the RubyPlugin, but never mind.
It took me a while to think of beanshell as Java, I kept wanting to
leave off ; and other scripting language things, but it really is
Java, just scripted, very impressive language, I have no idea how they
do it, if they compile on the fly or interpret, but it works, and you
have full access to Swing, and as I have written many thousands of
lines of Java/Swing code over the years, I was back in business. (It
did take me a while to remember that stuff though as my Swing Feng was
a few years old).
// Expand # to #{} if in quotes
boolean escaped() {
pos= textArea.getCaretPosition();
str= textArea.getText(pos-1, 1);
return(str.equals("\\"));
}
boolean inQuotes() {
cl= textArea.getCaretLine();
str= textArea.getLineText(cl);
pos= (textArea.getCaretPosition() - textArea.getLineStartOffset(cl))-1;
cnt= 0;
do{
off= str.lastIndexOf("\"", pos);
if(off < 0)
break;
else if(off == 0 || str.charAt(off-1) != '\\')
cnt++;
pos= off-1;
}while(pos >= 0);
return (cnt & 1) != 0;
}
if((buffer.getMode().getName().equals("ruby") || buffer.getMode().getName().equals("rhtml")) && inQuotes() && !escaped()){
textArea.setSelectedText("#{}");
textArea.goToPrevCharacter(false);
}else{
textArea.setSelectedText("#");
}
The next one was recreating my Epsilon Hack that toggles between view
and controller. I found a great starting macro which toggled between
model and controller, so I just modified that a bit, using the same
basic technique, and within a few hours I had my view/controller
toggle. It may not be as robust as my Epsilon version, as it does a
pretty naive search to find what method it is in, in the controller,
but it seems to work. Of course, as with Textmates version, it only works
if the rails naming conventions are followed.
Download from here
UPDATE I have found a much better implementation with all the bells
and whistles of the Textmate version
here.
The big one was getting a list up to show the SupperAbbrevs snippets
for any given mode, and selecting the one you want. This took a good
part of a day, I had to study the SupperAbbrevs code, and I found a
good starting point with a macro that lists abbreviations from the
built-in JEdit abbreviations. I use a good part of that, and just
changed it so you can select one of the abbreviations and use that to
insert the snippet. I ran into a few quirks with JTable and selection,
but that was because I was pretty rusty, I have solved those problems
before, I had just forgotten how to.
So now I can type a portion or just one letter of an abbreviation, hit
Alt-Space and I get a pop-up with all the abbreviations of the current
mode, and the first one that matches what I have typed so far already
selected. Hitting enter will insert that one, or I can select the one
I want, and hit enter or click Expand, and it does its magic. I need
this as I can never remember all those abbreviations, for all the
rails and ruby snippets. (Not even Textmate can do that!) Of course I
can still type in the whole abbreviation and hit TAB to get the quick
expansion as before.
Now something that Textmate does that I like is you can click on an
icon and you get a list of all the modes then you can select a snippet
from any mode and insert it. I don't think macros will let you do
exactly that, (I think that would require a plugin), but the next best
thing is hitting Alt-Space with nothing in front of it, and I get a
pop up. I can select the mode I want then the snippet from the table,
and click insert, and it does the same thing. Just a few extra mouse
clicks than Textmate, but close enough for me.
Now I just have to figure out how to contribute these macros to the
Textmate community, the Wiki is closed to registered users only, and I
got no reply when I tried to register.
I'll put all my macros here for now, until they register me. Then you
will be able to download these macros from the nice MacroManager
plugin they have in JEdit. So far there seems to be a plugin or macro
for almost everything. The other macros are all available via the
macroManager plugin, including the toggle between model and controller
one. The Emacs macros are also available from the same utility.
I should say I am using the 4.2stable version of JEdit, I played with
4.3pre8 but it was throwing a lot of exceptions on the screen,
although none fatal, but it did seem to be a little slower, I'll wait
for it become more stable before trying again.
The RubyPlugin is available from the plugin manager, the latest
version of superAbbrevs is not however, I got it from this
link explained
here
These were the most useful howtos I found
ruby-rails-jedit,
and jedit-for-ruby-rails-development.
but there are literally hundreds around.
Another idea taken from Textmate is a macro that runs the current Ruby
Unit Test case, download from here.
It will run a focused test only on the testcase the cursor is currently in.
Here is the download link
for the biggest Macro I have done to date, it is the
one that allows you to select a SuperAbbrev snippet.
Posted in Rails, Ruby, JEdit | Tags jedit, rails, ruby, textmate | 5 comments | no trackbacks
Posted by Jim Morris
Sun, 01 Oct 2006 00:20:00 GMT
I use Lugarus excellent Epsilon Editor for most of my
programming editing needs, on Win32 and Linux.
(An exception is for Java programming where I use Eclipse).
I have spent some time writing extensions to Epsilon to handle Ruby
and Rails programming, inspired mostly by Textmate on Mac/OSX, and
Eclipse for Java. I tried using the Eclipse for Ruby, but I was very
disappointed, also the developers have made some design decisions I
can't live with (like no auto indent after opening braces etc).
I have tried Komodo Pro as described
here.
OK the Epsilon editor is not free, it costs about $250 ($99 for an
upgrade) which includes all platforms, and the license lets you use it
on your Laptop and Desktop, in fact you can use any version of Epsilon
on up to four computers you own. The result is a professional, solid,
stable Editor, that is what you get when you pay for something. (This
editor has been around at least 20 years, which is when I started
using it on DOS!). In addition to running as an X Windows program and
a console program on windows and Linux it also runs on Mac OS X,
FreeBSD, OS/2 and DOS.
The Editor is an Emacs clone out of the box, it also has CUA and Brief
emulations (well key bindings). The best feature IMHO is the fact you
get most of the source code for the editor which is written in its own
c-like language called eel. This makes it much easier to write
extensions and customizations for the editor if you are familiar with
C. (I never could wrap my brain around Lisp which is why I don't use
GNU Emacs). It also runs in console mode as well as windows mode,
which is useful if you have to login via ssh etc to edit files.
Seeing how every programmer has their own ideas of what an editor
should do and its look and feel, easy customization is crucial.
The extensions I have written for Epsilon are all freely available, as
are extensions written by other users. (Various language modes,
template extensions, SCM extensions etc).
In the past I wrote a java help extension that tried to do context
sensitive help, this worked OK but not as well as Eclipse.
Recently I wrote a bunch of extensions for Ruby and Rails, thanks to
the ruby mode extension written by Timothy Byrd (available on Lugaru's
download page) I was able to get syntax highlighting and formatting
already done. I added some simple help extensions, a snippet facility
(ala Textmate), and some convenience actions for Rails development. I
also extended Timothy's ruby mode extension with something that
completes the #{} when # is typed in a string. (I first saw this in
Textmate and hated it, but it grew on me, until I had to have it on
Linux). I have avoided the temptation to also do the automatic closing
of { ( " etc that you find in Textmate because I still hate those, but
they are easy to do using the same technique I used for #{}.
I've also added the ability to run the current buffer through the ruby
interpreter and show the results in a pop up window, also to run a
specific unit test if the file is a Ruby test case.
The key strokes any command uses is easily modified, as well as the
colors used for syntax highlighting.
The version of ruby_mode.e on Lugarus site does not currently have the
latest changes I have made, so it can be downloaded from the link below...
The other extensions can also be downloaded from my site...
These can be installed by copying them to your ~/.epsilon folder, and
adding a load line to your einit.ecm file, see the comments in the
source file. The snippets should be un-tarred into the ~/.epsilon
folder. The README explains how to load them.
eg add this to your einit.ecm file:
(load-eel-from-path "ruby_mode.e" 2)
(load-eel-from-path "rubyhelp.e" 2)
(load-eel-from-path "snippets.e" 2)
(load-eel-from-path "rename_in_place.e" 2)
Also here are the color codes I use for ruby_mode, these are designed
by Timothy: (Change the window-black to whatever color set you are using)
&window-black color class for ruby-brace: [0x725CEB on 0x0]
&window-black color class for ruby-class: [0xD9D240 on 0x0]
&window-black color class for ruby-comment: [0xC0C0C0 on 0x0]
&window-black color class for ruby-global: [0xFFB737 on 0x0]
&window-black color class for ruby-keyword: [0xFF8000 on 0x0]
&window-black color class for ruby-number: [0xFF9090 on 0x0]
&window-black color class for ruby-punctuation: yellow on black
&window-black color class for ruby-regexp: [0x007FFF on 0x0]
&window-black color class for ruby-shell-cmd: [0x8FFF2F on 0x0]
&window-black color class for ruby-shell-subst: [0x8FFF8F on 0x0]
&window-black color class for ruby-str-subst: [0xFFC0C8 on 0x0]
&window-black color class for ruby-string: cyan on black
&window-black color class for ruby-perl-var: red on black
Although you can browse for files that epsilon has currently open and
switch between these buffers, the method is fairly crude by todays
standards of tabbed windows etc, so I wrote a little graphical helper
called
project_browser.rb
that uses the fox window toolkit. It just shows a tree of the
directory it was given on the command line, and if you click on any of
the files they open in the epsilon window. This is a lot like the
project browser windows you find in Textmate, Eclipse and others. You
need to install fox version 1.4 and the fox14 gem too to use this. It
also allows you to exclude files and directories from display in the
tree, by putting a YAML file called .proj_exclude.yaml in the
project directory, I'll document this further if there is any interest
in it (Leave a comment if you are interested). It allows multiple
project directories to be open and shows them in tabs at the top. I'm
also working on integrating subversion into it. It could also be
adapted to work with virtually any editor that allows files to be sent
to the editor by a separate process.

Posted in Rails, Ruby, Linux | Tags editor, epsilon, ide, rails, ruby | no comments | no trackbacks
Posted by Jim Morris
Mon, 04 Sep 2006 18:51:35 GMT
If you Google around for information or even some documentation on
the ruby SVN bindings you will find plenty of comments that it simply
is not documented, so when I wanted to add an SVN status check to a UI
I was working on (a project browser window for Rails), I had to "Use
the source Luke". However given the bindings are actually mostly
automatically generated by SWIG, and the actual details are hidden in
a goo of swig generated c code, even that was a challenge.
Eventually I realized that the API was almost identical to the c level
subversion API, not surprisingly, and for the most part it works the
way you would expect. However I could not find any examples of the
client status call, so here it is for anyone else struggling with this
issue.
require "svn/core"
require "svn/client"
require "svn/wc"
require "svn/repos"
NONE = 1
UNVERSIONED = 2
NORMAL = 3
ADDED = 4
MISSING = 5
DELETED = 6
REPLACED = 7
MODIFIED = 8
MERGED = 9
CONFLICTED = 10
IGNORED = 11
OBSTRUCTED = 12
EXTERNAL = 13
INCOMPLETE = 14
UPDATED = 15
MODIFIED_NEWER = 16
NEWFILE = 17
$modes= {
NONE => "None",
UNVERSIONED => "unversioned",
NORMAL => "normal",
ADDED => "file added",
MISSING => "missing",
DELETED => "file removed",
REPLACED => "deleted and then re-added",
MODIFIED => "modified",
MERGED => "received repos mods",
CONFLICTED => "file modified and in conflict",
IGNORED => "ignored",
OBSTRUCTED => "unversioned resource is in the way of the versioned resource",
EXTERNAL => "unversioned path populated by an svn:external property",
INCOMPLETE => "directory doesn't contain a complete entries list",
UPDATED => "newer version in repository",
MODIFIED_NEWER => "modified and newer version in repository",
NEWFILE => "new file in repository",
nil => "Unknown status"
}
def compute_status(status)
return MODIFIED_NEWER if status.repos_text_status == MODIFIED && status.text_status != NORMAL
return NEWFILE if status.repos_text_status == ADDED && status.text_status == NONE
return status.text_status if status.text_status != NORMAL
return UPDATED if status.repos_text_status == MODIFIED
NORMAL
end
ctx = Svn::Client::Context.new()
rev = ctx.status("/home/user/project/myproject", "HEAD", true, true) do |path, status|
astat= compute_status(status)
puts "#{path}: #{status.text_status},#{status.repos_text_status} = #{$modes[astat]}"
unless status.entry.nil?
puts(".....name: #{status.entry.name}")
puts(".....url: #{status.entry.url}")
puts(".....repos: #{status.entry.repos }")
puts(".....revision: #{status.entry.revision}")
puts(".....kind: #{status.entry.kind}")
puts(".....schedule: #{status.entry.schedule}")
puts(".....deleted: #{status.entry.deleted}")
puts(".....absent: #{status.entry.absent}")
puts(".....incomplete: #{status.entry.incomplete}")
puts(".....cmt_date: #{status.entry.cmt_date}")
puts(".....cmt_rev: #{status.entry.cmt_rev}")
puts(".....cmt_author: #{status.entry.cmt_author}")
puts(".....prop_time: #{status.entry.prop_time}")
puts(".....text_time: #{status.entry.text_time}")
end
end
This is a sample, it shows how to setup the client context, call the
status call, and parse the results. I also added some sugar by
defining the numeric status codes and adding a hash to translate them
to english. I also compute an amalgamated status code from the local
status and status in the repository, to get the extra status codes I
added above.
The status parameter returned by the status() call turns out to be a
standard svn status structure, containing various fields, the most
interesting one as far as status is concerned are text_status and
repos_text_status fields which are numeric fields which specifies the
SVN status of the file locally and in the repository, I have created
in the example above a bunch of constants to define each of the
states, and a hash to turn them into english.
Note the compute_status() method which looks at both the local status
and repository status to see what the relative state of any file is
wrt the repository.
The other interesting field is the entry field, which contains bunch
of data as shown above, and in the structure definition below.
There are more parameters that can be passed to the status
call, but the last two I show should be set to true, as that allows
recursion into sub directories and shows all files, by default
recursion is true but all_files is false which only show files not
under svn control.
The developers have made some attempt to make this relatively easy to
use, you can forget about the apr pools and stuff, and the second
parameter is usually a structure that tells it the version or revision
to search for, this has been simplified so you can pass in a string
describing the revision ("HEAD", "TAIL", etc) or a number denoting the
revision number.
The status and entry structures have also had the types converted into
convenient ruby types, but for reference I show the c struct
definitions from the various svn header files...
Here is the full status structure from svn_wc.h
typedef struct svn_wc_status2_t
{
/** Can be @c NULL if not under version control. */
svn_wc_entry_t *entry;
/** The status of the entries text. */
enum svn_wc_status_kind text_status;
/** The status of the entries properties. */
enum svn_wc_status_kind prop_status;
/** a directory can be 'locked' if a working copy update was interrupted. */
svn_boolean_t locked;
/** a file or directory can be 'copied' if it's scheduled for
* addition-with-history (or part of a subtree that is scheduled as such.).
*/
svn_boolean_t copied;
/** a file or directory can be 'switched' if the switch command has been
* used.
*/
svn_boolean_t switched;
/** The entry's text status in the repository. */
enum svn_wc_status_kind repos_text_status;
/** The entry's property status in the repository. */
enum svn_wc_status_kind repos_prop_status;
/** The entry's lock in the repository, if any. */
svn_lock_t *repos_lock;
}
and the entry structure
typedef struct svn_wc_entry_t
{
/* IMPORTANT: If you extend this structure, check svn_wc_entry_dup() to see
if you need to extend that as well. */
/* General Attributes */
/** entry's name */
const char *name;
/** base revision */
svn_revnum_t revision;
/** url in repository */
const char *url;
/** canonical repository URL or NULL if not known */
const char *repos;
/** repository uuid */
const char *uuid;
/** node kind (file, dir, ...) */
svn_node_kind_t kind;
/* State information */
/** scheduling (add, delete, replace ...) */
svn_wc_schedule_t schedule;
/** in a copied state */
svn_boolean_t copied;
/** deleted, but parent rev lags behind */
svn_boolean_t deleted;
/** absent -- we know an entry of this name exists, but that's all
(usually this happens because of authz restrictions) */
svn_boolean_t absent;
/** for THIS_DIR entry, implies whole entries file is incomplete */
svn_boolean_t incomplete;
/** copyfrom location */
const char *copyfrom_url;
/** copyfrom revision */
svn_revnum_t copyfrom_rev;
/** old version of conflicted file */
const char *conflict_old;
/** new version of conflicted file */
const char *conflict_new;
/** working version of conflicted file */
const char *conflict_wrk;
/** property reject file */
const char *prejfile;
/** last up-to-date time for text contents (0 means no information available)
*/
apr_time_t text_time;
/** last up-to-date time for properties (0 means no information available) */
apr_time_t prop_time;
/** base64-encoded checksum for the untranslated text base file,
* can be @c NULL for backwards compatibility.
*/
const char *checksum;
/* "Entry props" */
/** last revision this was changed */
svn_revnum_t cmt_rev;
/** last date this was changed */
apr_time_t cmt_date;
/** last commit author of this item */
const char *cmt_author;
/** lock token or NULL if path not locked in this WC
* @since New in 1.2.
*/
const char *lock_token;
/** lock owner, or NULL if not locked in this WC
* @since New in 1.2.
*/
const char *lock_owner;
/** lock comment or NULL if not locked in this WC or no comment
* @since New in 1.2.
*/
const char *lock_comment;
/** Lock creation date or 0 if not locked in this WC
* @since New in 1.2.
*/
apr_time_t lock_creation_date;
/* IMPORTANT: If you extend this structure, check svn_wc_entry_dup() to see
if you need to extend that as well. */
} svn_wc_entry_t;
For the most part you can simply reference the fields in the structure
and get a relatively understandable result.
The status call is defined in ruby as this
def status(path, rev=nil, recurse=true, get_all=false,
update=true, no_ignore=false,
ignore_externals=false, &status_func)
The first parameter is the path on the local file system to get the
status of, the second is the revision to get, something like "HEAD",
or 2345 can be passed in, the next parameter is whether to recurse
into subdirectories, the next tells it to return the status of all
files if set to true, default of false and only returns "interesting
files" ie local mods and/or out-of-date or not versioned. Update is
set to true it will contact the repository to get more information wrt
to the version specified in the first parameter (which is ignored
otherwise). I couldn't find any documentation in the c stuff about
the no_ignore parameter, but the ignore_externals tells it to return
status on externals or not. See the header file svn_client.h and the
call svn_client_status2 for more documentation.
ruby svn
Posted in Ruby, svn | Tags ruby, rubysvn, subversion, svn | 3 comments | no trackbacks