Skip to content


Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP


Support for custom paths in Sass, Less and Coffee #58

wants to merge 4 commits into from

5 participants

Leonid Shevtsov Michał Lipski Tim Pope Joshua Davey bpo217
Leonid Shevtsov

Hi Tim,

I replaced hardcoded paths to stylesheets and scripts, like public/stylesheets/sass, with some fancy path detection.

The detection depends on the corresponding gem being declared in Gemfile/environment.rb, then proceeds to look for files in a list of possible directories (like app/stylesheets, which seems pretty popular), then falls back to retrieving the path from the app itself (slow but sure, and only in rare cases).

Works with Sass, Less, and Coffeescript; I only tested it on Sass since that's what was available.

It's my first experience with vimscript - I tried to be nice. :)


Michał Lipski

Great idea! For example I have CoffeeScript files inside app/coffeescripts.

Tim Pope

How does Rails 3.1 asset handling affect this?

The sheer volume of code in this pull request made me squirm, so I've kind of let it sit.

Leonid Shevtsov

If you mean that introducing a standard convention for .sass and .coffee locations will deprecate this code - well, I do hope it will.

But in my opinion, existing 3.0 and 2.3 applications won't migrate to sprockets, even if they upgrade to 3.1, so for them the problem of locating the correct path remains.

Other than that, the only thing that 3.1 changes is that app/javascripts should be added to the list of .coffee default locations.

Leonid Shevtsov

I've gave the matter more thought, and it looks like the "asset pipeline" makes opening the correct file from Vim a much more complex process.

So, I'll look more into the logic and probably submit another patch. Rails 3.1 will probably make the current asset-related commands useless.

Tim Pope

What's the current thinking on this? Now that the asset pipeline has started to catch on, I'd like to simplify a bit, perhaps dropping app/scripts and app/stylesheets (which, if memory serves, weren't supported back when this pull request was introduced).

In theory, the new config/editor.json projections should allow for people stuck on nonstandard setups to override :Rjavascript and :Rstylesheet.

Joshua Davey

@tpope Could projections also be used to switch the order of preference :Rjavascript and :Rstylesheet for new files? That is, could a projection be used to put new files into app/assets/javascripts?

Also, at this point in time, should we invert the above? Default to app/assets and let a projection handle forcing public?

Tim Pope

The problem with sticking new files under app/assets is that we don't know what extension to give them. .js? Rails further complicates this with its infuriatingly mushy file name handling, allowing such bastardizations as .coffee and .coffee.erb (and maybe even a naked .erb for all I know). And if that wasn't bad enough, CSS adds both SCSS and Sass to the mix, so we can't just look at what gems are installed.

What does rails generate create? Is it customizable? Can we detect that and use it to guide our default?

Joshua Davey

Crazy idea... what about looking at the surrounding files as a guideline? For example, if there are 2 files in app/assets/javascripts, but only .js file, we go with the extension. The same would work for stylesheets because presumably people either prefer .sass or .scss, but not both.

Basically, rather than deciding a default based on Rails "conventions" or generators, we let the existing project decide.

Crazy? Or craziest?


@tpope You can of course make your own rails generator; however, I am unsure whether you can customize the built in ones.

I really believe this feature should be: if they input 1 or more file extensions explicitly, then that should be the file name (don't add anything); if they don't then it should default to the core language extension for that particular context. The core extension for a stylesheet being simply .css. The core extension for a view being simply .html. Since these extensions are the "base" extensions for a website, this fallback won't break for a long time. So you have to enter .html.erb if you want the erb... Big deal. It's better than it breaking for the other guy who uses .html.haml.

This method seems like it would take a lot less code than any other route, and yet solve the problem. Would this be difficult to implement?

Tim Pope

File creation is now correctly handled based on configured generators. I'm thinking that should address all remaining concerns here.

Tim Pope tpope closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Dec 3, 2010
  1. Leonid Shevtsov

    1) Support Sass paths other than public/stylesheets/sass (practically…

    leonid-shevtsov authored
    … any path will work) 2) add completion for Sass partials to Rstylesheet
  2. Leonid Shevtsov

    Merge branch 'master' of

    leonid-shevtsov authored
  3. Leonid Shevtsov
Commits on Jan 23, 2011
  1. Leonid Shevtsov
This page is out of date. Refresh to see the latest.
Showing with 90 additions and 24 deletions.
  1. +90 −24 autoload/rails.vim
114 autoload/rails.vim
@@ -787,22 +787,88 @@ function! s:app_default_locale() dict abort
return self.cache.get('default_locale')
+function s:app_gemfile_path() dict
+ if self.cache.needs('gemfile_path')
+ if findfile('Gemfile', self.path())!=''
+ call self.cache.set('gemfile_path', self.path()+'/Gemfile')
+ else
+ call self.cache.set('gemfile_path', self.path()+'/config/environment.rb')
+ endif
+ end
+ return self.cache.get('gemfile_path')
+" yep, this is quite heuristic
+function! s:app_has_gem(gem) dict
+ return system('grep -P "^[^#]*gem\s+[''\"]'.a:gem.'[''\"]" '.self.gemfile_path())!=''
+function! s:app_module_path(name, gem_name, guess_directories, guess_file_mask, application_query) dict
+ if self.cache.needs(a:name.'_path')
+ call self.cache.set(a:name.'_path', '')
+ if self.has_gem(a:gem_name)
+ for guess_dir in a:guess_directories
+ if glob(self.path(guess_dir.a:guess_file_mask))!=''
+ call self.cache.set(a:name.'_path', guess_dir)
+ return guess_dir
+ endif
+ endfor
+ if self.cache.get(a:name.'_path')=='' && a:application_query!=''
+ " Query the app - it knows the path for sure. This is slow, hence the guesswork above
+ call self.cache.set(a:name.'_path', system(self.path('script/runner').' '''.a:application_query.''''))
+ end
+ endif
+ end
+ return self.cache.get(a:name.'_path')
+function! s:app_sass_path() dict
+ return self.module_path(
+ \ 'sass',
+ \ 'haml',
+ \ ['app/stylesheets/', 'public/stylesheets/sass/', 'public/stylesheets/'],
+ \ '*.s[ac]ss',
+ \ 'print defined?(Sass) ? Sass::Plugin.template_location_array[0][0].gsub(/^#{Rails.root}\//,"")+"/" : ""'
+ \ )
+function! s:app_lesscss_path() dict
+ return self.module_path(
+ \ 'lesscss',
+ \ 'more',
+ \ ['app/stylesheets/', 'public/stylesheets/'],
+ \ '*.less',
+ \ 'print defined?(Less) ? Less::More.source_path.gsub(/^#{Rails.root}\//,"")+"/" : ""'
+ \ )
+function! s:app_coffee_path() dict
+ return self.module_path(
+ \ 'coffee',
+ \ '(barista|bistro_car)',
+ \ ['app/scripts/', 'app/coffeescripts/', 'public/javascripts/'],
+ \ '*.coffee',
+ \ ''
+ \ )
function! s:app_has(feature) dict
let map = {
- \'test': 'test/',
- \'spec': 'spec/',
+ \'test': 'test/',
+ \'spec': 'spec/',
\'cucumber': 'features/',
- \'sass': 'public/stylesheets/sass/',
- \'lesscss': 'app/stylesheets/',
- \'coffee': 'app/scripts/'}
+ \'sass': rails#app().sass_path(),
+ \'lesscss': rails#app().lesscss_path(),
+ \'coffee': rails#app().coffee_path()
+ \ }
if self.cache.needs('features')
call self.cache.set('features',{})
let features = self.cache.get('features')
if !has_key(features,a:feature)
let path = get(map,a:feature,a:feature.'/')
- let features[a:feature] = isdirectory(rails#app().path(path))
+ let features[a:feature] = path!='/' && isdirectory(rails#app().path(path))
return features[a:feature]
@@ -812,7 +878,7 @@ function! s:app_test_suites() dict
return filter(['test','spec','cucumber'],'self.has(v:val)')
-call s:add_methods('app',['default_locale','environments','file','has','test_suites'])
+call s:add_methods('app',['default_locale','environments','file','gemfile_path', 'has_gem', 'module_path', 'sass_path', 'lesscss_path', 'coffee_path', 'has','test_suites'])
call s:add_methods('file',['path','name','lines','getline'])
call s:add_methods('buffer',['app','number','path','name','lines','getline','type_name'])
call s:add_methods('readable',['app','calculate_file_type','type_name','line_count'])
@@ -1382,14 +1448,14 @@ function! s:readable_preview_urls(lnum) dict abort
if has_key(self,'getvar') && self.getvar('rails_preview') != ''
let url += [self.getvar('rails_preview')]
- if =~ '^public/stylesheets/sass/'
- let urls = urls + [s:sub(s:sub(,'^public/stylesheets/sass/','/stylesheets/'),'\.s[ac]ss$','.css')]
+ if rails#app().has('sass') && =~ '^'.rails#app().sass_path()
+ let urls = urls + [s:sub(s:sub(,'^'.rails#app().sass_path(),'/stylesheets/'),'\.s[ac]ss$','.css')]
elseif =~ '^public/'
let urls = urls + [s:sub(,'^public','')]
- elseif =~ '^app/stylesheets/'
- let urls = urls + [s:sub(s:sub(,'^app/stylesheets/','/stylesheets/'),'\.less$','.css')]
- elseif =~ '^app/scripts/'
- let urls = urls + [s:sub(s:sub(,'^app/scripts/','/javascripts/'),'\.coffee$','.js')]
+ elseif rails#app().has('lesscss') && =~ '^'.rails#app().lesscss_path()
+ let urls = urls + [s:sub(s:sub(,'^'.rails#app().lesscss_path(),'/stylesheets/'),'\.less$','.css')]
+ elseif rails#app().has('coffee') && =~ '^'.rails#app().coffee_path()
+ let urls = urls + [s:sub(s:sub(,'^'.rails#app().coffee_path(),'/javascripts/'),'\.coffee$','.js')]
elseif self.controller_name() != '' && self.controller_name() != 'application'
if self.type_name('controller') && self.last_method(a:lnum) != ''
let urls += ['/'.self.controller_name().'/'.self.last_method(a:lnum).'/']
@@ -2243,7 +2309,7 @@ endfunction
function! s:stylesheetList(A,L,P)
let list = rails#app().relglob('public/stylesheets/','**/*','.css')
if rails#app().has('sass')
- call extend(list,rails#app().relglob('public/stylesheets/sass/','**/*','.s?ss'))
+ call extend(list,rails#app().relglob(rails#app().sass_path(),'**/*','.s?ss'))
call s:uniq(list)
return s:completion_filter(list,a:A)
@@ -2662,12 +2728,12 @@ endfunction
function! s:stylesheetEdit(cmd,...)
let name = a:0 ? a:1 : s:controller(1)
- if rails#app().has('sass') && rails#app().has_file('public/stylesheets/sass/'.name.'.sass')
- return s:EditSimpleRb(a:cmd,"stylesheet",name,"public/stylesheets/sass/",".sass",1)
- elseif rails#app().has('sass') && rails#app().has_file('public/stylesheets/sass/'.name.'.scss')
- return s:EditSimpleRb(a:cmd,"stylesheet",name,"public/stylesheets/sass/",".scss",1)
- elseif rails#app().has('lesscss') && rails#app().has_file('app/stylesheets/'.name.'.less')
- return s:EditSimpleRb(a:cmd,"stylesheet",name,"app/stylesheets/",".less",1)
+ if rails#app().has('sass') && rails#app().has_file(rails#app().sass_path().name.'.sass')
+ return s:EditSimpleRb(a:cmd,"stylesheet",name,rails#app().sass_path(),".sass",1)
+ elseif rails#app().has('sass') && rails#app().has_file(rails#app().sass_path().name.'.scss')
+ return s:EditSimpleRb(a:cmd,"stylesheet",name,rails#app().sass_path(),".scss",1)
+ elseif rails#app().has('lesscss') && rails#app().has_file(rails#app().lesscss_path().name.'.less')
+ return s:EditSimpleRb(a:cmd,"stylesheet",name,rails#app().lesscss_path(),".less",1)
return s:EditSimpleRb(a:cmd,"stylesheet",name,"public/stylesheets/",".css",1)
@@ -2675,10 +2741,10 @@ endfunction
function! s:javascriptEdit(cmd,...)
let name = a:0 ? a:1 : s:controller(1)
- if rails#app().has('coffee') && rails#app().has_file('app/scripts/'.name.'.coffee')
- return s:EditSimpleRb(a:cmd,'javascript',name,'app/scripts/','.coffee',1)
- elseif rails#app().has('coffee') && rails#app().has_file('app/scripts/'.name.'.js')
- return s:EditSimpleRb(a:cmd,'javascript',name,'app/scripts/','.js',1)
+ if rails#app().has('coffee') && rails#app().has_file(rails#app().coffee_path().name.'.coffee')
+ return s:EditSimpleRb(a:cmd,'javascript',name,rails#app().coffee_path(),'.coffee',1)
+ elseif rails#app().has('coffee') && rails#app().has_file(rails#app().coffee_path().name.'.js')
+ return s:EditSimpleRb(a:cmd,'javascript',name,rails#app().coffee_path(),'.js',1)
return s:EditSimpleRb(a:cmd,'javascript',name,'public/javascripts/','.js',1)
Something went wrong with that request. Please try again.