diff --git a/ftdetect/drupal.vim b/ftdetect/drupal.vim
index 541645d..76c4c2f 100644
--- a/ftdetect/drupal.vim
+++ b/ftdetect/drupal.vim
@@ -1,11 +1,11 @@
 " Set filetype for Drupal PHP files.
-autocmd BufRead,BufNewFile *.module set filetype=php.drupal
-autocmd BufRead,BufNewFile *.php set filetype=php.drupal
-autocmd BufRead,BufNewFile *.install set filetype=php.drupal
-autocmd BufRead,BufNewFile *.inc set filetype=php.drupal
-autocmd BufRead,BufNewFile *.profile set filetype=php.drupal
-autocmd BufRead,BufNewFile *.theme set filetype=php.drupal
-autocmd BufRead,BufNewFile *.engine set filetype=php.drupal
-autocmd BufRead,BufNewFile *.test set filetype=php.drupal
+autocmd BufRead,BufNewFile *.module set filetype=php
+autocmd BufRead,BufNewFile *.php set filetype=php
+autocmd BufRead,BufNewFile *.install set filetype=php
+autocmd BufRead,BufNewFile *.inc set filetype=php
+autocmd BufRead,BufNewFile *.profile set filetype=php
+autocmd BufRead,BufNewFile *.theme set filetype=php
+autocmd BufRead,BufNewFile *.engine set filetype=php
+autocmd BufRead,BufNewFile *.test set filetype=php
 " info and make files use INI syntax.
 autocmd BufRead,BufNewFile *.{info,make,build}	set filetype=dosini
diff --git a/ftplugin/drupal.vim b/ftplugin/drupal.vim
index b95be7e..db269fe 100644
--- a/ftplugin/drupal.vim
+++ b/ftplugin/drupal.vim
@@ -1,5 +1,5 @@
-" This file contains common Drupal-related settings.  It is :source'd by the
-" *_drupal.vim files in this directory; there is no drupal filetype.
+" We never :set ft=drupal.  This filetype is always added to another, as in
+" :set ft=php.drupal or :set ft=css.drupal.
 
 setl nojoinspaces            "No second space when joining lines that end in "."
 setl autoindent              "Auto indent based on previous line
@@ -21,45 +21,49 @@ setl formatoptions+=croql
 " Custom SVN blame
 vmap <buffer> gl :<C-U>!svn blame <C-R>=expand("%:P") <CR> \| sed -n <C-R>=line("'<") <CR>,<C-R>=line("'>") <CR>p <CR>
 
-" Return a string that can be used to open URL's (and other things).
-" Usage:
-" let open = s:OpenCommand()
-" if strlen(open) | execute '!' . open . ' http://example.com' | endif
-" See http://www.dwheeler.com/essays/open-files-urls.html
-if !exists('*s:OpenCommand')
+" {{{ @var $DRUPAL_ROOT
+" plugin/drupal.vim defines several useful strings in b:Drupal_info.
+" There is no such thing as buffer-local environment variables, so call this
+" each time you enter a Drupal buffer.
+augroup Drupal
+  autocmd BufEnter <buffer> if strlen(b:Drupal_info.DRUPAL_ROOT) |
+	\ let $DRUPAL_ROOT = b:Drupal_info.DRUPAL_ROOT | endif
+augroup END
+do BufEnter <buffer>
+" }}}
 
-function s:OpenCommand()
-if has('macunix') && executable('open')
-  return 'open'
+" The tags file can be used for PHP omnicompletion even if $DRUPAL_ROOT == ''.
+" If $DRUPAL_ROOT is set correctly, then the tags file can be used for tag
+" searches.
+" TODO:  If we do not know which version of Drupal core, add no tags file or
+" all?
+if strlen(b:Drupal_info.CORE)
+  let &l:tags .= ',' . expand('<sfile>:p:h:h') . '/drupal' . b:Drupal_info.CORE . '.tags'
 endif
-if has('win32unix') && executable('cygstart')
-  return 'cygstart'
-endif
-if has('unix') && executable('xdg-open')
-  return 'xdg-open'
-endif
-if (has('win32') || has('win64')) && executable('cmd')
-  return 'cmd /c start'
-endif
-  return ''
-endfun
+
+if !exists('*s:OpenURL')
 
 function s:OpenURL(base)
-  let open = s:OpenCommand()
+  let open = b:Drupal_info.OPEN_COMMAND
   if open == ''
     return
   endif
   let func =  shellescape(expand('<cword>'))
   if a:base == 'api.d.o'
-    execute '!' . open . ' http://api.drupal.org/' . func
+    if strlen(b:Drupal_info.CORE)
+      execute '!' . open . ' http://api.drupal.org/api/search/' .
+	    \ b:Drupal_info.CORE . '/' . func
+    else
+      execute '!' . open . ' http://api.drupal.org/' . func
+    endif
   else
     execute '!' . open . ' ' . a:base . func
   endif
 endfun
 
-endif " !exists('*s:OpenCommand')
+endif " !exists('*s:OpenURL')
 
-if strlen(s:OpenCommand())
+if strlen(b:Drupal_info.OPEN_COMMAND)
   " Lookup the API docs for a drupal function under cursor.
   nnoremap <buffer> <LocalLeader>da :silent call <SID>OpenURL('api.d.o')<CR><C-L>
 
@@ -69,3 +73,16 @@ endif
 
 " Get the value of the drupal variable under cursor.
 nnoremap <buffer> <LocalLeader>dv :execute "!drush vget ".shellescape(expand("<cword>"), 1)<CR>
+
+" PHP specific settings.
+" In ftdetect/drupal.vim we set ft=php.drupal.  This means that the settings
+" here will come after those set by the PHP ftplugins.  In particular, we can
+" override the 'comments' setting.
+
+if &ft =~ '\<php\>'
+  setl ignorecase              "Ignore case in search
+  setl smartcase               "Only ignore case when all letters are lowercase
+  setl comments=sr:/**,m:*\ ,ex:*/,://
+  "  Format comment blocks.  Just type / on a new line to close.
+  "  Recognize // (but not #) style comments.
+endif
diff --git a/ftplugin/javascript_drupal.vim b/ftplugin/javascript_drupal.vim
deleted file mode 100644
index 17afa0b..0000000
--- a/ftplugin/javascript_drupal.vim
+++ /dev/null
@@ -1,13 +0,0 @@
-" Source the default ftplugin so that it does not change our settings.
-" Uncomment the next line if it is needed.
-" runtime! ftplugin/javascript.vim
-
-" Get common settings and mappings for Drupal.
-source <sfile>:h/drupal.vim
-
-" Settings specific to javascript/Drupal go here.
-
-" Is javascript a case-insensitive language?
-
-" setl ignorecase              "Ignore case in search
-" setl smartcase               "Only ignore case when all letters are lowercase
diff --git a/ftplugin/php_drupal.vim b/ftplugin/php_drupal.vim
deleted file mode 100644
index 08276b4..0000000
--- a/ftplugin/php_drupal.vim
+++ /dev/null
@@ -1,12 +0,0 @@
-" Source the default ftplugin so that it does not change our settings.
-" Specifically, the 'comments' option gets clobbered by html.vim.
-runtime! ftplugin/php.vim
-
-" Get common settings and mappings for Drupal.
-source <sfile>:h/drupal.vim
-
-setl ignorecase              "Ignore case in search
-setl smartcase               "Only ignore case when all letters are lowercase
-setl comments=sr:/**,m:*\ ,ex:*/,://
-"  Format comment blocks.  Just type / on a new line to close.
-"  Recognize // (but not #) style comments.
diff --git a/plugin/drupal.vim b/plugin/drupal.vim
index 656afce..32f3fca 100644
--- a/plugin/drupal.vim
+++ b/plugin/drupal.vim
@@ -48,33 +48,39 @@ map <S-u> :redo<cr>
 map <C-n> :tabn<cr>
 map <C-p> :tabp<cr>
 
+" {{{
+" Everything from here on assumes that autocommands are available.
+" }}}
+if !has("autocmd")
+  finish
+endif
+
 " Uncomment the following to have Vim jump to the last position when
 " reopening a file
-if has("autocmd")
- au BufReadPost * if line("'\"") > 0 && line("'\"") <= line("$")
- \| exe "normal! g'\"" | endif
-endif
+au BufReadPost * if line("'\"") > 0 && line("'\"") <= line("$")
+      \| exe "normal! g'\"" | endif
 
-" Highlight long comments and trailing whitespace.
-" It is not good practice to include syntax items in a plugin file, but we do
-" it here, with Syntax autocommands, because syntax files do not follow the
-" same naming conventions as ftplugin files, so we cannot use (for example)
-" syntax/php_drupal.vim .
-" Adapted from http://vim.wikia.com/wiki/Highlight_unwanted_spaces
-highlight default link drupalExtraWhitespace Error
-highlight default link drupalOverLength Error
 augroup Drupal
   " Remove ALL autocommands for the Drupal group.
   autocmd!
-  autocmd Syntax php syn match drupalOverLength "\%81v.*" containedin=phpComment contained
-  autocmd Syntax css syn match drupalOverLength "\%81v.*" containedin=cssComment contained
+  " s:DrupalInit() will create the buffer-local Dictionary b:Drupal_info
+  " containing useful information for ftplugins and syntax files.  It will
+  " also add drupal as a secondary filetype.  This will load the ftplugins and
+  " syntax files drupal.vim after the usual ones.
+  autocmd FileType php,css,javascript,dosini call s:DrupalInit()
+
+  " Highlight trailing whitespace.
   autocmd BufWinEnter * call s:ToggleWhitespaceMatch('BufWinEnter')
   autocmd BufWinLeave * call s:ToggleWhitespaceMatch('BufWinLeave')
   autocmd InsertEnter * call s:ToggleWhitespaceMatch('InsertEnter')
   autocmd InsertLeave * call s:ToggleWhitespaceMatch('InsertLeave')
 augroup END
+highlight default link drupalExtraWhitespace Error
+
+" Adapted from http://vim.wikia.com/wiki/Highlight_unwanted_spaces
 function! s:ToggleWhitespaceMatch(event)
-  if &ft != 'php' && &ft != 'css'
+  " Bail out unless the filetype is php.drupal, css.drupal, ...
+  if &ft !~ '\<drupal\>'
     return
   endif
   if a:event == 'BufWinEnter'
@@ -93,3 +99,171 @@ function! s:ToggleWhitespaceMatch(event)
     call matchadd('drupalExtraWhitespace', '\s\+$', 10, w:whitespace_match_number)
   endif
 endfunction
+
+" Borrowed from autoload/pathogen.vim:
+let s:slash = !exists("+shellslash") || &shellslash ? '/' : '\'
+
+" {{{ @function s:DrupalInit()
+" Save some information in the buffer-local Dictionary b:Drupal_info for use
+" by ftplugin and syntax scripts.  The keys are
+" - DRUPAL_ROOT
+"   path to the Drupal root
+" - INFO_FILE
+"   path to the .info file of the containing module, theme, etc.
+" - TYPE
+"   'module' or 'theme' or 'make'
+" - OPEN_COMMAND
+"   'open' or 'xdg-open' or 'cmd /c start', depending on the OS
+" In all cases, the values will be '' if we cannot make a reasonable guess.
+" {{{
+function! s:DrupalInit()
+  " Expect something like /var/www/drupal-7.9/sites/all/modules/ctools
+  let path = expand('%:p')
+  let directory = fnamemodify(path, ':h')
+  let info = {'DRUPAL_ROOT': s:DrupalRoot(directory),
+	\ 'INFO_FILE': s:InfoPath(directory)}
+  let info.CORE = s:CoreVersion(info.INFO_FILE)
+  let info.TYPE = s:IniType(info.INFO_FILE)
+  let info.OPEN_COMMAND = s:OpenCommand()
+
+  " @var b:Drupal_info
+  let b:Drupal_info = info
+  " TODO:  If we are not inside a Drupal directory, maybe skip this.  Wait
+  " until someone complains that we are munging his non-Drupal php files.
+  set ft+=.drupal
+endfun
+" }}} }}}
+
+" {{{ @function s:IniType()
+" Try to guess which part of the path is the Drupal root directory.
+"
+" @param path
+"   A string representing a system path.
+"
+" @return
+"   A string representing the Drupal root, '' if not found.
+" {{{
+function! s:DrupalRoot(path)
+  let markers = ['index.php', 'cron.php', 'modules', 'themes', 'sites']
+  " On *nix, start with '', but on Windows typically start with 'C:'.
+  let droot = matchstr(a:path, '[^\' . s:slash . ']*')
+  for part in split(matchstr(a:path, '\' . s:slash . '.*'), s:slash)
+    let droot .= s:slash . part
+    " This would be easier if it did not have to work on Windows.
+    let ls = glob(droot . s:slash . '*')
+    let is_drupal_root = 1
+    for marker in markers
+      if match(ls, '\' . s:slash . marker . "\<C-J>") == -1
+	let is_drupal_root = 0
+	break
+      endif
+    endfor
+    " If all the markers are there, then this looks like a Drupal root.
+    if is_drupal_root
+      return droot
+    endif
+  endfor
+  return ''
+endfun
+" }}} }}}
+
+" {{{ @function s:IniType()
+" Try to find the .info file of the module, theme, etc. containing a path.
+"
+" @param path
+"   A string representing a system path.
+"
+" @return
+"   A string representing the path of the .info file, '' if not found.
+" {{{
+function! s:InfoPath(path)
+  let dir = a:path
+  while dir =~ '\' . s:slash
+    let infopath = glob(dir . s:slash . '*.{info,make,build}')
+    if strlen(infopath)
+      return infopath
+    endif
+    " No luck yet, so go up one directory.
+    let dir = substitute(dir, '\' . s:slash . '[^\' . s:slash . ']*$', '', '')
+  endwhile
+  return ''
+endfun
+" }}} }}}
+
+" Return a string that can be used to open URL's (and other things).
+" Usage:
+" let open = s:OpenCommand()
+" if strlen(open) | execute '!' . open . ' http://example.com' | endif
+" See http://www.dwheeler.com/essays/open-files-urls.html
+function! s:OpenCommand()
+if has('macunix') && executable('open')
+  return 'open'
+endif
+if has('win32unix') && executable('cygstart')
+  return 'cygstart'
+endif
+if has('unix') && executable('xdg-open')
+  return 'xdg-open'
+endif
+if (has('win32') || has('win64')) && executable('cmd')
+  return 'cmd /c start'
+endif
+  return ''
+endfun
+
+" {{{ @function s:CoreVersion(info_path)
+" Find the version of Drupal core by parsing the .info file.
+"
+" @param info_path
+"   A string representing the path to the .info file.
+"
+" @return
+"   A numeric string representing the Drupal core version.
+" {{{
+function! s:CoreVersion(info_path)
+  " Find the Drupal core version.
+  if a:info_path == '' || !filereadable(a:info_path)
+    return ''
+  endif
+  let lines = readfile(a:info_path, '', 500)
+  let core_re = '^\s*core\s*=\s*\zs\d\+\ze\.x\s*$'
+  let core_line = matchstr(lines, core_re)
+  return matchstr(core_line, core_re)
+endfun
+" }}} }}}
+
+" {{{ @function s:IniType(info_path)
+" Find the type (module, theme, make) by parsing the path.
+"
+" @param info_path
+"   A string representing the path to the .info file.
+"
+" @return
+"   A string:  module, theme, make
+" {{{
+" TODO:  How do we recognize a Profiler .info file?
+function! s:IniType(info_path)
+  let ext = fnamemodify(a:info_path, ':e')
+  if ext == 'make' || ext == 'build'
+    return 'make'
+  else
+    " If the extension is not 'info' at this point, I do not know how we got
+    " here.
+    let m_index = strridx(a:info_path, s:slash . 'modules' . s:slash)
+    let t_index = strridx(a:info_path, s:slash . 'themes' . s:slash)
+    " If neither matches, try a case-insensitive search.
+    if m_index == -1 && t_index == -1
+      let m_index = matchend(a:info_path, '\c.*\' . s:slash . 'modules\' . s:slash)
+      let t_index = matchend(a:info_path, '\c.*\' . s:slash . 'themes\' . s:slash)
+    endif
+    if m_index > t_index
+      return 'module'
+    elseif m_index < t_index
+      return 'theme'
+    endif
+    " We are not inside a themes/ directory, nor a modules/ directory.  Do not
+    " guess.
+    return ''
+  endif
+endfun
+" }}} }}}
diff --git a/syntax/drupal.vim b/syntax/drupal.vim
new file mode 100644
index 0000000..14ef848
--- /dev/null
+++ b/syntax/drupal.vim
@@ -0,0 +1,13 @@
+" We never :set ft=drupal.  This filetype is always added to another, as in
+" :set ft=php.drupal or :set ft=css.drupal.
+
+" Highlight long comments.  The order of these two commands matters!
+syn match drupalOverLength "\%>80v[^*/].*" containedin=@drupalComment contained
+syn match drupalOverLength "\%81v.*\*\/"me=e-2 containedin=@drupalComment contained
+" Add <syntax>Comment.* to the @drupalComment cluster for all applicable
+" syntax types.
+let s:syntax = substitute(&syntax, '\.drupal\>', '', 'g')
+execute 'syn cluster drupalComment contains=' .
+      \ substitute(s:syntax, '\.\|$', 'Comment.*,', 'g')
+
+highlight default link drupalOverLength Error
