Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 418 lines (336 sloc) 12.895 kb
fee0399e »
2012-08-08 Oops... Forgotten files :-)
1 """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
2 " "
3 " Drupal.vim - A simple plugin to help develop with Drupal "
4 " "
5 """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
6 "
dfeba6bd »
2012-08-06 Update:
7 " Description:
8 " (Actually) A very simple VIm plugin to help develop with Drupal.
9 "
10 " Maintainer: Sébastien <webastien[At]gmail.com>
fee0399e »
2012-08-08 Oops... Forgotten files :-)
11 " Last Change: 2012 August 8
12 " Version: v1.1
dfeba6bd »
2012-08-06 Update:
13 "
14 " Installation:
fee0399e »
2012-08-08 Oops... Forgotten files :-)
15 " Required:
16 " - Drop this file to your plugin directory of vim.
17 "
18 " Recommanded:
19 " - Install Drush! http://drupal.org/project/drush/
20 " /!\ "EditDrupal" command will not work without!
21 "
22 " - Add an abbreviation for "EditDrupal" command.
23 " To do it, add this to your .vimrc:
24 " cabbrev YOURABBR <c-r>= ((getcmdtype() == ':' && getcmdpos() == 1)? 'EditDrupal': 'YOURABBR')<CR>
25 " (where YOURABBR is the abbreviation you've choosed)
26 "
27 " Example: cabbrev ed <c-r>= ((getcmdtype() == ':' && getcmdpos() == 1)? 'EditDrupal': 'ed')<CR>
28 " Now, when you are in command mode, type ed<space> and 'ed' has been replaced by 'EditDrupal'.
29 "
30 " Optional:
31 " - Map a key to call the "ResetVimDrupalCache" command.
32 " To do it, add this to your .vimrc:
33 " nmap YOURKEY :ResetVimDrupalCache<CR>
34 " (where YOURKEY is the key you've choosed)
35 "
36 " Example: nmap <F10> :ResetVimDrupalCache<CR>
dfeba6bd »
2012-08-06 Update:
37 "
38 " Usage:
fee0399e »
2012-08-08 Oops... Forgotten files :-)
39 " - "ResetVimDrupalCache" command: ** args: none **
40 " Reset the internal cache.
41 "
42 " Use it if a new module/theme/file is not autocompleted.
43 "
44 "
45 " - "Hook" command: ** args: {hook_name} **
46 " Implement the named hook, where the cursor is.
dfeba6bd »
2012-08-06 Update:
47 "
48 " For convinience, the module name is guessed by the file path,
49 " when this is ambiguous or indeterminate, a prompt is displayed.
50 " A cache (renewed each time vim is opened) is used to not ask
51 " several times for the same file in which module it is associated.
52 "
fee0399e »
2012-08-08 Oops... Forgotten files :-)
53 " Note that:
54 " - If the hook implementation already exists (and found by ctags),
55 " a new function is NOT created: The cursor is placed on this instead.
56 " - If the hook contains a specific part (like hook_form_FORM_ID_alter),
57 " a dialog will ask what use for it (in this example value of FORM_ID)
58 "
59 "
60 " - "EditDrupal" command: ** args: {module / theme} {filepath} **
61 " Edit a Drupal file. /!\ Require Drush! /!\
62 "
63 " {module / theme} is the (machine) name of a module or a theme,
64 " {filepath} is the path of a file, relative to this module / theme
65 "
4b4f5c33 »
2012-08-08 Doc:
66 " You can use "/" as module argument: It will point to the Drupal root.
67 "
fee0399e »
2012-08-08 Oops... Forgotten files :-)
68 " Some file are excluded from the autocomplete list.
69 " By default: *.png, *.gif, *.jpg and *.jpeg
70 " You can change which ones by adding (and adapting) this to your .vimrc:
71 " let g:Drupal_excluded_extensions = 'png,gif,jpg,jpeg'
72 " (coma separated file extensions to exclude, or empty to allow all files)
73 "
74 "
dfeba6bd »
2012-08-06 Update:
75
fee0399e »
2012-08-08 Oops... Forgotten files :-)
76 if exists('drupal_vim_loaded') || !has('autocmd') || !exists(':filetype')
dfeba6bd »
2012-08-06 Update:
77 finish
78 endif
79
80 " Flag to indicate the plugin is already loaded
fee0399e »
2012-08-08 Oops... Forgotten files :-)
81 let drupal_vim_loaded = 1
82 " Flag to know if Drush is available
83 let s:has_drush = (system('which drush') != '')
84
85 if !exists('g:Drupal_excluded_extensions')
86 let g:Drupal_excluded_extensions = 'png,gif,jpg,jpeg'
87 endif
88
89 """"""""""""""""""""
90 " VIm commands "
91 "__________________"
dfeba6bd »
2012-08-06 Update:
92
fee0399e »
2012-08-08 Oops... Forgotten files :-)
93 " Create a custom command to manage the script cache
94 command! -nargs=0 ResetVimDrupalCache :call s:resetCache(1)
dfeba6bd »
2012-08-06 Update:
95 " Create a custom command to directly implement a Drupal hook under the cursor
96 command! -nargs=1 -complete=custom,s:HookNameAutoComplete Hook :call s:ImplementDrupalHook(<q-args>)
fee0399e »
2012-08-08 Oops... Forgotten files :-)
97 " Without Drush installed, the command 'EditDrupal' will not work
98 if s:has_drush
99 " Create a custom command to edit Drupal files without to type the complete path
100 command! -nargs=+ -complete=custom,s:EditDrupalAutoComplete EditDrupal :call s:EditDrupal(<q-args>)
101 endif
102
103 """""""""""""""""""""""
104 " Cache functions "
105 "_____________________"
106
107 " Init of reset the internal cache
108 function s:resetCache(message)
109 let s:drupal_cache = {}
110
111 if a:message
112 echohl WarningMsg | echo "The cache of Drupal VIm plugin has been reseted." | echohl None
113 endif
114 endfunction
115
116 " Get the cached value corresponding to the given infos
117 function s:cacheGet(funcname, funcargs, duration)
118 let l:key = a:funcname .'°:-:°'. join(a:funcargs, '°:-:°')
119
120 if has_key(s:drupal_cache, l:key)
121 let [l:expire, l:value] = s:drupal_cache[l:key]
122
123 if l:expire == -1 || l:expire > localtime()
124 return l:value
125 endif
126 endif
127
128 if exists('*'. a:funcname)
129 let l:value = call(a:funcname, a:funcargs)
130 call s:cacheSet(l:key, l:value, a:duration)
131
132 return l:value
133 endif
134
135 return -1
136 endfunction
137
138 " Store a cache value associated to a key and with a lifetime (in secondes)
139 function s:cacheSet(key, val, duration)
140 let s:drupal_cache[a:key] = [(a:duration == -1)? -1: localtime() + a:duration, a:val]
141 endfunction
142
143 """"""""""""""""""""""""""""""""""""""""""""""""""
144 " Functions relative to 'EditDrupal' command "
145 "________________________________________________"
146
147 " Open the requested file: 1st arg is a module/theme name, the 2nd is one of its files (path relative to it)
148 function s:EditDrupal(args)
149 let l:args = split(substitute(a:args, '\s\+', ' ', 'g'), ' ')
150 let l:path = substitute(system('drush drupal-directory '. ((l:args[0] == '/')? '': l:args[0])), "\n", '', '')
151
152 if match(l:path, '\[error\]') == -1
153 let l:args[0] = ''
154 " Two 'substitute' calls because one is required to accept filenames with spaces in it (in case of)
155 exec 'edit '. l:path .'/'. substitute(substitute(join(l:args, ' '), '^\s\+\|\s\+$', '', 'g'), ' ', '\\ ', 'g')
156 else
157 echohl WarningMsg | echo "Can't find this module, sorry." | echohl None
158 endif
159 endfunction
160
161 " Autocomplete function with 'double completion': 1st arg is a module/theme name, the 2nd is one of its files
162 function s:EditDrupalAutoComplete(ArgLead, CmdLine, CursorPos)
163 let l:project = s:cacheGet('s:dirProject', [expand('%:p:h')], 3600)
164
165 if l:project == ''
166 return ''
167 endif
168
169 let l:args = split(substitute(a:CmdLine, '\s\+', ' ', 'g'), ' ')
170
171 if strpart(a:CmdLine, len(a:CmdLine) - 1) == ' '
172 call add(l:args, ' ')
173 endif
174
175 if len(l:args) == 2
176 return s:cacheGet('s:projectModules', [l:project], 120)
177 elseif len(l:args) == 3
178 return s:cacheGet('s:moduleFiles', [l:args[1]], 60)
179 else
180 return ''
181 endif
182 endfunction
183
184 function s:dirProject(directory)
185 return substitute(system('drush status "Drupal root" --pipe'), '\n', '', '')
186 endfunction
187
188 function s:projectModules(project)
189 let l:modules = split(system('drush pml --pipe'), "\n")
190 call insert(l:modules, '/')
191
192 return join(l:modules, " \n")
193 endfunction
194
195 function s:moduleFiles(module)
196 let l:path = substitute(system('drush drupal-directory '. ((a:module == '/')? '': a:module)), "\n", '', '')
197
198 if match(l:path, '\[error\]') == -1
199 let l:files = split(system('find '. l:path .' -type f '. g:Drupal_excluded_extensions), "\n")
200 let l:start = len(l:path)
201 let l:index = 0
202
203 while l:index < len(l:files)
204 let l:files[l:index] = strpart(l:files[l:index], l:start + 1)
205 let l:index = l:index + 1
206 endwhile
207
208 return join(l:files, "\n")
209 endif
210
211 return ''
212 endfunction
213
214 """"""""""""""""""""""""""""""""""""""""""""""""
215 " Functions relative to the 'Hook' command "
216 "______________________________________________"
dfeba6bd »
2012-08-06 Update:
217
218 " Will place an implementation of the given hook under the cursor
219 function s:ImplementDrupalHook(hook)
220 if &ft != 'php'
221 echohl ErrorMsg | echo "This is not a PHP file!" | echohl None
222 return
223 endif
224
225 let l:signature = s:getHookSignature(a:hook)
226
227 if l:signature == ''
228 return
229 endif
230
231 let l:pasteValue = &paste
232 let l:currentPos = line('.')
233
234 if l:pasteValue == 0
235 exec 'set paste'
236 endif
237
238 exec "normal i\n/**\n * Implements hook_". a:hook ."().\n */\n". l:signature ." \n}\n"
239 exec 'normal '. (l:currentPos + 5) .'G$'
240
241 if l:pasteValue == 0
242 exec 'set nopaste'
243 endif
244 endfunction
245
246 " Provide autocompletion of hooks' name to the "Hook" command
247 function s:HookNameAutoComplete(ArgLead, CmdLine, CursorPos)
fee0399e »
2012-08-08 Oops... Forgotten files :-)
248 if &ft != 'php' || len(tagfiles()) != 1
dfeba6bd »
2012-08-06 Update:
249 return ''
250 endif
251
252 return s:GetCandidateHooks(a:ArgLead)
253 endfunction
254
255 " Return a list of hooks which the name start by the given expression
256 function s:GetCandidateHooks(expr)
257 let l:result = system('compgen -W "`awk "/^hook_'. a:expr .'/ { print \\$1 }" '. get(tagfiles(), 0) .'`"')
258
259 if l:result == ''
260 return ''
261 endif
262
263 let l:resultArray = s:SortUnique(split(l:result, "\n"))
264 let l:index = 0
265
266 while index < len(l:resultArray)
267 let l:resultArray[index] = strpart(l:resultArray[index], 5)
268 let index = index + 1
269 endwhile
270
271 return join(l:resultArray, "\n")
272 endfunction
273
274 " Return the signature of the given Drupal hook
275 function s:getHookSignature(hook)
276 let l:signature = ''
277
278 if len(tagfiles()) == 1
279 let l:signature = system('grep -m1 -oe "function[[:space:]+]hook_'. a:hook .'(.*{" '. get(tagfiles(), 0))
280 else
281 echohl WarningMsg | echo "Tagsfile not found!" | echohl None
282 endif
283
284 if l:signature == ''
285 echohl WarningMsg | echo "Can't find the signature of hook_". a:hook .'()' | echohl None
286
287 if inputlist(['What do you want to do?', '1. Ignore this warning and force the creation of this hook.', '2. Cancel this request.']) == 1
288 let l:signature = 'function hook_'. a:hook ."() {\n"
289 else
fee0399e »
2012-08-08 Oops... Forgotten files :-)
290 return ''
dfeba6bd »
2012-08-06 Update:
291 endif
292 endif
293
fee0399e »
2012-08-08 Oops... Forgotten files :-)
294 let l:moduleName = s:cacheGet('s:getModuleName', [expand('%:p')], -1)
dfeba6bd »
2012-08-06 Update:
295
296 if l:moduleName == ''
fee0399e »
2012-08-08 Oops... Forgotten files :-)
297 echohl WarningMsg | echo "Cancelled..." | echohl None
298 return ''
dfeba6bd »
2012-08-06 Update:
299 endif
300
fee0399e »
2012-08-08 Oops... Forgotten files :-)
301 let l:hook = a:hook
302 let l:mask = matchstr(a:hook, '\C[A-Z]\{1}[A-Z_]\+[A-Z]\{1}')
dfeba6bd »
2012-08-06 Update:
303
fee0399e »
2012-08-08 Oops... Forgotten files :-)
304 if l:mask != ''
305 let l:rplc = inputdialog('This hook contains a part that need to be specified: '. l:mask .'=')
dfeba6bd »
2012-08-06 Update:
306
fee0399e »
2012-08-08 Oops... Forgotten files :-)
307 if l:rplc == ''
308 echohl WarningMsg | echo "Cancelled..." | echohl None
309 return ''
310 endif
311
312 let l:hook = substitute(l:hook, l:mask, l:rplc, '')
dfeba6bd »
2012-08-06 Update:
313 endif
314
fee0399e »
2012-08-08 Oops... Forgotten files :-)
315 try
316 let l:currentFile = expand('%:p')
317
318 silent exec 'ts '. l:moduleName .'_'. l:hook
319 exec 'tabnew'
320 exec 'tag '. l:moduleName .'_'. l:hook
321
322 if l:currentFile == expand('%:p')
323 let l:currentLine = line('.')
324 exec 'quit'
325 exec 'norm '. l:currentLine .'G'
326 endif
327
328 exec 'norm zvzz'
329 catch
330 return substitute(l:signature, 'function\s\+hook_'. a:hook, 'function '. l:moduleName .'_'. l:hook, '')
331 endtry
332
333 return ''
334 endfunction
335
336 " (Try to) Return the name of the Drupal module currently edited
337 function s:getModuleName(path)
338 let l:fileParts = split(split(a:path, '/')[-1], '\.')
dfeba6bd »
2012-08-06 Update:
339
340 if len(l:fileParts) == 2 && l:fileParts[1] == 'module'
341 return l:fileParts[0]
342 endif
343
fee0399e »
2012-08-08 Oops... Forgotten files :-)
344 let l:directory = substitute(a:path, '/[^/]\+$', '', '')
345 let l:default = ''
dfeba6bd »
2012-08-06 Update:
346
fee0399e »
2012-08-08 Oops... Forgotten files :-)
347 while l:default == '' && isdirectory(l:directory) && !filereadable(l:directory .'/index.php')
dfeba6bd »
2012-08-06 Update:
348 if filereadable(l:directory .'/'. l:fileParts[0] .'.module')
349 return l:fileParts[0]
350 endif
351
352 let l:candidates = split(system('ls -1 '. l:directory .' | grep ".module"'), "\n")
353
354 if len(l:candidates) == 1
355 return split(l:candidates[0], '\.')[0]
356 elseif len(l:candidates) > 0
357 let l:default = split(l:candidates[0], '\.')[0]
358 endif
359
360 let l:directory .= '/..'
361 endwhile
362
363 let l:question = "Sorry, can't determine the module name, please enter it: "
364
365 if l:default != ''
366 let l:question = 'More than one module found, please confirm: '
367 endif
368
369 return inputdialog(l:question, l:default)
370 endfunction
371
fee0399e »
2012-08-08 Oops... Forgotten files :-)
372 """"""""""""""""""""""""
373 " Helper functions "
374 "______________________"
375
dfeba6bd »
2012-08-06 Update:
376 " Custom sort function for List: Remove duplicate entries in the same time
377 " Code from http://vim.wikia.com/wiki/Unique_sorting
378 function s:SortUnique(list, ...)
379 let dictionary = {}
380
381 for i in a:list
fee0399e »
2012-08-08 Oops... Forgotten files :-)
382 execute "let dictionary[ '". i ."' ] = ''"
dfeba6bd »
2012-08-06 Update:
383 endfor
384
385 let result = []
386
fee0399e »
2012-08-08 Oops... Forgotten files :-)
387 if (exists('a:1'))
dfeba6bd »
2012-08-06 Update:
388 let result = sort(keys(dictionary), a:1)
389 else
390 let result = sort(keys(dictionary))
391 endif
392
393 return result
394 endfunction
395
fee0399e »
2012-08-08 Oops... Forgotten files :-)
396 " Build the command part to exclude files from autocomplete in DrupalEdit command
397 function s:buildExcludeCommand()
398 let l:exts = split(g:Drupal_excluded_extensions, ',')
399 let l:index = 0
400
401 while l:index < len(l:exts)
402 let l:exts[l:index] = '-iname "*.'. substitute(l:exts[l:index], '^\s\+\|\s\+$', '', 'g') .'"'
403 let l:index = l:index + 1
404 endwhile
405
406 return '! \( '. join(l:exts, ' -or ') .' \)'
407 endfunction
408
409 """""""""""""""""""""""
410 " Init the module "
411 "______________________
412
413 " Build the usable exclude extensions command part
414 if g:Drupal_excluded_extensions != ''
415 let g:Drupal_excluded_extensions = s:buildExcludeCommand()
416 endif
417 " Prepare the internal cache
418 call s:resetCache(0)
419
Something went wrong with that request. Please try again.