From dd11dc96a741a15a61398a1a3ef4d6ee4b94ba3b Mon Sep 17 00:00:00 2001 From: Tim Pope Date: Wed, 31 May 2006 01:23:22 +0000 Subject: [PATCH] Syntax highlighting! --- doc/rails.txt | 38 +++++--- plugin/rails.vim | 225 +++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 222 insertions(+), 41 deletions(-) diff --git a/doc/rails.txt b/doc/rails.txt index c9853d43..2a13a6ae 100644 --- a/doc/rails.txt +++ b/doc/rails.txt @@ -23,10 +23,10 @@ following features for Ruby on Rails application development. 1. Automatically detects buffers containing files from Rails applications, and applies settings to those buffers (and only those buffers). You can - use an |autocommand| to apply your own custom settings as well. + use an autocommand to apply your own custom settings as well. |rails-configuration| -2. Unintrusive. It should only affect files in a Rails application; regular +2. Unintrusive. Only files in a Rails application should be affected; regular Ruby scripts are left untouched. Even when enabled, the plugin should keep out of your way if you're not using its features. (If you find a situation where this is not a case, contact the |rails-plugin-author|.) @@ -70,23 +70,33 @@ Little configuration should need to be done; this plugin automatically detects and adjusts Vim sensibly and subtly. If you would like to set your own custom Vim settings when ever a Rails file is loaded, you can use an autocmd like the following in your vimrc: > - autocmd User rails map ra RailsAlternate + autocmd User Rails* map ra RailsAlternate +Note the use of an asterisk; this is required to allow for future expansion. A few global variables control the behavior of this plugin. In general, they -can be enabled with lines line the following in your vimrc: > +can be enabled by setting them to 1 in your vimrc, and disabled by setting +them to 0. > let g:rails_some_option=1 + let g:rails_some_option=0 < - *g:rails_loaded* -Do not load the plugin. For emergency use only. > + *g:rails_loaded* > let g:rails_loaded=1 -< - *g:rails_no_isfname* -Disables the changes to the 'isfname' option. See |rails-'isfname'|. +Do not load the plugin. For emergency use only. + + *g:rails_isfname* > + let g:rails_isfname=1 +Enables changes to the 'isfname' option. Default is enabled. See +|rails-'isfname'|. - *g:rails_statusline* -Give a clue in the statusline when this plugin is enabled: > + *g:rails_statusline* > let g:rails_statusline=1 -This requires that the current filetype is in your existing 'statusline'. +Give a clue in the statusline when this plugin is enabled. This requires that +the current filetype is in your existing 'statusline'. + + *g:rails_syntax* > + let g:rails_syntax=1 +When enabled, this tweaks the syntax highlighting to be more Rails friendly. +Default is disabled, which will most likely change in a future version. ============================================================================== MANAGED VIM OPTIONS *rails-options* @@ -118,7 +128,7 @@ in |rails-gf|. The |rails-gf| feature is further enhanced by changing 'isfname'. Unfortunately, this is a global option. To compensate, the plugin attempts to restore this option upon changing buffers. If this causes problems for you, -enable the |g:rails_no_isfname| setting. Also, please explain your problem to +disable the |g:rails_isfname| setting. Also, please explain your problem to the |rails-plugin-author|, so that it might be addressed in a future release. *rails-'makeprg'* *rails-'mp'* @@ -189,7 +199,7 @@ There are no default mappings. *RailsAlternate* RailsAlternate Switch to the "alternate" file. Typically this just adds or removes "_test" from the filename , but try it - in lots of places, you may be surprised. > + in lots of places; you may be surprised. > :map ra RailsAlternate ============================================================================== diff --git a/plugin/rails.vim b/plugin/rails.vim index d6158a62..a830e064 100644 --- a/plugin/rails.vim +++ b/plugin/rails.vim @@ -10,7 +10,7 @@ " Exit quickly when: " - this plugin was already loaded (or disabled) " - when 'compatible' is set -if exists("g:loaded_rails") || &cp +if exists("g:loaded_rails") && g:loaded_rails || &cp finish endif let g:loaded_rails = 1 @@ -18,6 +18,23 @@ let g:loaded_rails = 1 let cpo_save = &cpo set cpo&vim +function s:RubyEval(ruby,...) + if a:0 > 0 + let def = a:1 + else + let def = "" + endif + if !executable("ruby") + return def + endif + let results = system('ruby -e '.s:qq().'require %{rubygems} rescue nil; require %{active_support} rescue nil; '.a:ruby.s:qq()) + if results =~ '-e:\d' + return def + else + return results + endif +endfunction + function! s:InitPlugin() if has("autocmd") augroup railsDetect @@ -27,7 +44,7 @@ function! s:InitPlugin() autocmd BufLeave * call s:ClearGlobals() augroup END endif - if exists("g:rails_statusline") + if exists("g:rails_statusline") && g:rails_statusline call s:InitStatusline() endif endfunction @@ -40,6 +57,78 @@ function! RailsAppPath() endif endfunction +function! RailsFilePath() + if !exists("b:rails_app_path") + return "" + elseif exists("b:rails_file_path") + return b:rails_file_path + endif + let f = substitute(expand("%:p"),'\\ \@!','/','g') + if substitute(b:rails_app_path,'\\ \@!','/','g') == strpart(f,0,strlen(b:rails_app_path)) + return strpart(f,strlen(b:rails_app_path)+1) + else + return f + endif +endfunction + +function! RailsFileType() + if !exists("b:rails_app_path") + return "" + elseif exists("b:rails_file_type") + return b:rails_file_type + endif + let f = RailsFilePath() + let e = fnamemodify(RailsFilePath(),':e') + let r = "" + let top = getline(1).getline(2).getline(3) + if f == "" + let r = f + elseif f =~ '_controller\.rb$' + if top =~ '\' + let r = "controller-api" + else + let r = "controller" + endif + elseif f =~ '_api\.rb' + let r = "api" + elseif f =~ '\' + let r = "model-ar" + elseif top =~ '<\s*ActionMailer::Base\>' + let r = "model-am" + else + let r = "model" + endif + elseif f =~ '\' + let r = "view-layout-" . e + elseif f =~ '\' + let r = "view-" . e + elseif f =~ '\' + if e == "yml" + let r = "fixtures-yaml" + else + let r = "fixtures-" . e + endif + elseif f =~ '\' + let r = "migration" + elseif e == "css" || e == "js" || e == "html" + let r = e + endif + return r +endfunction + function! s:qq() " Quote character if &shellxquote == "'" @@ -68,9 +157,11 @@ function! s:Detect(filename) endfunction function! s:SetGlobals() - if exists("b:rails_app_path") && !exists("g:rails_no_isfname") - let b:rails_restore_isfname=&isfname - set isfname=@,48-57,/,-,_,\",',: + if exists("b:rails_app_path") + if !exists("g:rails_isfname") || g:rails_isfname + let b:rails_restore_isfname=&isfname + set isfname=@,48-57,/,-,_,\",',: + endif endif endfunction @@ -92,6 +183,7 @@ function! s:InitBuffer(path) setlocal filetype=ruby endif call s:Commands() + call s:Syntax() silent! compiler rubyunit let &l:makeprg='rake -f '.rp.'/Rakefile' call s:SetRubyBasePath() @@ -102,10 +194,10 @@ function! s:InitBuffer(path) " It would be nice if we could do this without pulling in half of Rails " set include=\\<\\zs\\u\\f*\\l\\f*\\ze\\>\\\|^\\s*\\(require\\\|load\\)\\s\\+['\"]\\zs\\f\\+\\ze set include=\\<\\zsAct\\f*::Base\\ze\\>\\\|^\\s*\\(require\\\|load\\)\\s\\+['\"]\\zs\\f\\+\\ze - setlocal includeexpr=RailsFilename() + setlocal includeexpr=RailsIncludeexpr() else " Does this cause problems in any filetypes? - setlocal includeexpr=RailsFilename() + setlocal includeexpr=RailsIncludeexpr() setlocal suffixesadd=.rb,.rhtml,.rxml,.rjs,.css,.js,.yml,.csv,.rake,.sql,.html endif if &filetype == "ruby" @@ -121,8 +213,8 @@ function! s:InitBuffer(path) let &l:path = rp."/app/views,".&l:path.",".rp."/public" endif " Since so many generated files are malformed... - set eol - silent doautocmd User rails + set endofline + silent doautocmd User Rails* if filereadable(b:rails_app_path."/config/rails.vim") sandbox exe "source ".rp."/config/rails.vim" endif @@ -149,6 +241,67 @@ function s:Commands() endif endfunction +function! s:Syntax() + if exists("g:rails_syntax") && g:rails_syntax && (exists("g:syntax_on") || exists("g:syntax_manual")) + let t = RailsFileType() + if !exists("s:rails_view_helpers") + let s:rails_view_helpers = s:RubyEval('require %{action_view}; puts ActionView::Helpers.constants.select {|c| c =~ /Helper$/}.collect {|c|ActionView::Helpers.const_get c}.collect {|c| c.public_instance_methods(false)}.flatten.sort.uniq.reject {|m| m =~ /[=?]$/}.join(%{ })',"form_tag end_form_tag") + endif +" let g:rails_view_helpers = s:rails_view_helpers + let rails_view_helpers = '+\.\@+' + if &syntax == 'ruby' + if t =~ '^model$' || t =~ '^model-ar\>' + syn keyword railsRubyModelActsMethod acts_as_list acts_as_nested_set acts_as_tree + syn keyword railsRubyModelAssociationMethod belongs_to has_one has_many has_and_belongs_to_many + syn keyword railsRubyModelClassMethod attr_accessible attr_protected establish_connection set_inheritance_column set_locking_column set_primary_key set_sequence_name set_table_name + syn keyword railsRubyModelValidationMethod validate validate_on_create validate_on_update validates_acceptance_of validates_associated validates_confirmation_of validates_each validates_exclusion_of validates_format_of validates_inclusion_of validates_length_of validates_numericality_of validates_presence_of validates_size_of validates_uniqueness_of + hi def link railsRubyModelActsMethod railsRubyModelMethod + hi def link railsRubyModelAssociationMethod railsRubyModelMethod + hi def link railsRubyModelClassMethod railsRubyModelMethod + hi def link railsRubyModelValidationMethod railsRubyModelMethod + hi def link railsRubyModelMethod railsRubyMethod + endif + if t =~ '^controller\>' || t =~ '^view\>' || t=~ '^helper\>' + syn match railsRubyMethod '\<\%(params\|request\|response\|session\|headers\|template\|cookies\|flash\)\>' + syn match railsRubyError '@\%(params\|request\|response\|session\|headers\|template\|cookies\|flash\)\>' + endif + if t =~ '^helper\>' || t=~ '^view\>' + exe "syn match railsRubyHelperMethod ".rails_view_helpers + hi def link railsRubyHelperMethod railsRubyMethod + endif + hi def link railsRubyError rubyError + hi def link railsRubyMethod railsMethod + hi def link railsMethod rubyFunction + elseif &syntax == "eruby" && t =~ '^view\>' + exe "syn match railsErubyHelperMethod ".rails_view_helpers." containedin=@erubyRegions" + syn match railsErubyMethod '\<\%(params\|request\|response\|session\|headers\|template\|cookies\|flash\)\>' containedin=@erubyRegions + syn match railsErubyMethod '\.\@' containedin=@erubyRegions + syn match railsRubyError '@\%(params\|request\|response\|session\|headers\|template\|cookies\|flash\)\>' containedin=@erubyRegions + hi def link railsRubyError rubyError + hi def link railsErubyHelperMethod railsErubyMethod + hi def link railsErubyMethod railsMethod + hi def link railsMethod rubyFunction + elseif &syntax == "yaml" + " Modeled after syntax/eruby.vim + unlet b:current_syntax + let g:main_syntax = 'eruby' + syn include @rubyTop syntax/ruby.vim + unlet g:main_syntax + syn cluster railsYamlRegions contains=railsYamlOneLiner,railsYamlBlock,railsYamlExpression,railsYamlComment + syn region railsYamlOneLiner matchgroup=railsYamlDelimiter start="^%%\@!" end="$" contains=@railsRubyTop containedin=ALLBUT,@railsYamlRegions keepend oneline + syn region railsYamlBlock matchgroup=railsYamlDelimiter start="<%%\@!" end="%>" contains=@rubyTop containedin=ALLBUT,@railsYamlRegions + syn region railsYamlExpression matchgroup=railsYamlDelimiter start="<%=" end="%>" contains=@rubyTop containedin=ALLBUT,@railsYamlRegions + syn region railsYamlComment matchgroup=railsYamlDelimiter start="<%#" end="%>" contains=rubyTodo,@Spell containedin=ALLBUT,@railsYamlRegions keepend + syn match railsYamlMethod '\.\@' containedin=@erubyRegions + hi def link railsYamlDelimiter Delimiter + hi def link railsYamlMethod railsMethod + hi def link railsMethod rubyFunction + hi def link railsYamlComment Comment + let b:current_syntax = "yaml" + endif + endif +endfunction + function! s:EscapePath(p) return substitute(a:p,' ','\\ ','g') endfunction @@ -167,7 +320,7 @@ function! s:InitRuby() endif endfunction -function! RailsFilename() +function! RailsIncludeexpr() " Is this foolproof? if mode() =~ '[iR]' || expand("") != v:fname return s:RailsUnderscore(v:fname) @@ -206,7 +359,7 @@ function! s:RailsUnderscore(str,...) let str = substitute(str,'\(\u\+\)\(\u\l\)','\1_\2','g') let str = substitute(str,'\(\l\|\d\)\(\u\)','\1_\2','g') let str = substitute(str,'-','_','g') - let str = substitute(str,'.*','\L&','') + let str = tolower(str) let fpat = '\(\s*\%("\f*"\|:\f*\|'."'\\f*'".'\)\s*,\s*\)*' if a:str =~ '\u' " Classes should always be in .rb's @@ -313,34 +466,42 @@ function s:MakePartial(bang,...) range abort endfunction function s:FindAlternate() - if expand("%:t") == "database.yml" + let f = RailsFilePath() + let t = RailsFileType() + if expand("%:t") == "database.yml" || f =~ '\' + if t =~ '\' + let dest = fnamemodify(f,':r:s?/layouts\>??').'/layout' + echo dest + else + let dest = f + endif " Go to the helper, controller, or model - let helper = s:EscapePath(expand("%:p:h:s?.*[\/]app[\/]views[\/]?app/helpers/?")."_helper.rb") - let controller = s:EscapePath(expand("%:p:h:s?.*[\/]app[\/]views[\/]?app/controllers/?")."_controller.rb") - let model = s:EscapePath(expand("%:p:h:s?.*[\/]app[\/]views[\/]?app/models/?").".rb") + let helper = fnamemodify(dest,":h:s?/views/?/helpers/?")."_helper.rb" + let controller = fnamemodify(dest,":h:s?/views/?/controllers/?")."_controller.rb" + let model = fnamemodify(dest,":h:s?/views/?/models/?").".rb" if filereadable(b:rails_app_path."/".helper) " Would it be better to skip the helper and go straight to the " controller? - exe "find ".helper + exe "find ".s:EscapePath(helper) elseif filereadable(b:rails_app_path."/".controller) - exe "find ".controller + exe "find ".s:EscapePath(controller) elseif filereadable(b:rails_app_path."/".model) - exe "find ".model + exe "find ".s:EscapePath(model) else - exe "find ".controller + exe "find ".s:EscapePath(controller) endif - elseif expand("%:p") =~ '/app/helpers/.*_helper\.rb$' - let controller = s:EscapePath(expand("%:p:s?.*[\/]app[\/]helpers[\/]?app/controllers/?:s?_helper.rb$?_controller.rb?")) - exe "find ".controller - elseif expand("%:e") == "csv" || expand("%:e") == "yml" + elseif t =~ '^helper\>' + let controller = substitute(substitute(f,'/helpers/','/controllers/',''),'_helper\.rb$','_controller.rb','') + exe "find ".s:EscapePath(controller) + elseif t =~ '\' let file = s:RailsSingularize(expand("%:t:r")).'_test' exe "find ".s:EscapePath(file) else - let file = expand("%:t:r") + let file = fnamemodify(f,":t:r") if file =~ '_test$' exe "find ".s:EscapePath(substitute(file,'_test$','','')) else @@ -359,7 +520,12 @@ endfunction function! RailsStatusline() if exists("b:rails_app_path") - return "[Rails]" + let t = RailsFileType() + if t != "" + return "[Rails-".t."]" + else + return "[Rails]" + endif else return "" endif @@ -367,7 +533,12 @@ endfunction function! RailsSTATUSLINE() if exists("b:rails_app_path") - return ",RAILS" + let t = RailsFileType() + if t != "" + return ",RAILS-".toupper(t) + else + return ",RAILS" + endif else return "" endif