1 /*
2 * $Id: vim.c 485 2006-10-24 12:06:19Z dfishburn $
3 *
4 * Copyright (c) 2000-2003, Darren Hiebert
5 *
6 * This source code is released for free distribution under the terms of the
7 * GNU General Public License.
8 *
9 * Thanks are due to Jay Glanville for significant improvements.
10 *
11 * This module contains functions for generating tags for user-defined
12 * functions for the Vim editor.
13 */
14
15 /*
16 * INCLUDE FILES
17 */
18 #include "general.h" /* must always come first */
19
20 #include <string.h>
21 #include <setjmp.h>
22 #ifdef DEBUG
23 #include <stdio.h>
24 #endif
25
26
27 #include "parse.h"
28 #include "read.h"
29 #include "vstring.h"
30
31 #if 0
32 typedef struct sLineInfo {
33 tokenType type;
34 keywordId keyword;
35 vString * string;
36 vString * scope;
37 unsigned long lineNumber;
38 fpos_t filePosition;
39 } lineInfo;
40 #endif
41
42 /*
43 * DATA DEFINITIONS
44 */
45 typedef enum {
46 K_AUGROUP,
47 K_COMMAND,
48 K_FUNCTION,
49 K_MAP,
50 K_VARIABLE
51 } vimKind;
52
53 static kindOption VimKinds [] = {
54 { TRUE, 'a', "augroup", "autocommand groups" },
55 { TRUE, 'c', "command", "user-defined commands" },
56 { TRUE, 'f', "function", "function definitions" },
57 { TRUE, 'm', "map", "maps" },
58 { TRUE, 'v', "variable", "variable definitions" },
59 };
60
61 /*
62 * DATA DECLARATIONS
63 */
64
65 #if 0
66 typedef enum eException {
67 ExceptionNone, ExceptionEOF
68 } exception_t;
69 #endif
70
71 /*
72 * DATA DEFINITIONS
73 */
74
75 #if 0
76 static jmp_buf Exception;
77 #endif
78
79 /*
80 * FUNCTION DEFINITIONS
81 */
82
83 /* This function takes a char pointer, tries to find a scope separator in the
84 * string, and if it does, returns a pointer to the character after the colon,
85 * and the character defining the scope.
86 * If a colon is not found, it returns the original pointer.
87 */
skipPrefix(const unsigned char * name,int * scope)88 static const unsigned char* skipPrefix (const unsigned char* name, int *scope)
89 {
90 const unsigned char* result = name;
91 int counter;
92 size_t length;
93 length = strlen((const char*)name);
94 if (scope != NULL)
95 *scope = '\0';
96 if (length > 3 && name[1] == ':')
97 {
98 if (scope != NULL)
99 *scope = *name;
100 result = name + 2;
101 }
102 else if (length > 5 && strncasecmp ((const char*) name, "<SID>", (size_t) 5) == 0)
103 {
104 if (scope != NULL)
105 *scope = *name;
106 result = name + 5;
107 }
108 else
109 {
110 /*
111 * Vim7 check for dictionaries or autoload function names
112 */
113 counter = 0;
114 do
115 {
116 switch ( name[counter] )
117 {
118 case '.':
119 /* Set the scope to d - Dictionary */
120 *scope = 'd';
121 break;
122 case '#':
123 /* Set the scope to a - autoload */
124 *scope = 'a';
125 break;
126 }
127 ++counter;
128 } while (isalnum ((int) name[counter]) ||
129 name[counter] == '_' ||
130 name[counter] == '.' ||
131 name[counter] == '#'
132 );
133 }
134 return result;
135 }
136
isMap(const unsigned char * line)137 static boolean isMap (const unsigned char* line)
138 {
139 /*
140 * There are many different short cuts for specifying a map.
141 * This routine should capture all the permutations.
142 */
143 if (
144 strncmp ((const char*) line, "map", (size_t) 3) == 0 ||
145 strncmp ((const char*) line, "nm", (size_t) 2) == 0 ||
146 strncmp ((const char*) line, "nma", (size_t) 3) == 0 ||
147 strncmp ((const char*) line, "nmap", (size_t) 4) == 0 ||
148 strncmp ((const char*) line, "vm", (size_t) 2) == 0 ||
149 strncmp ((const char*) line, "vma", (size_t) 3) == 0 ||
150 strncmp ((const char*) line, "vmap", (size_t) 4) == 0 ||
151 strncmp ((const char*) line, "om", (size_t) 2) == 0 ||
152 strncmp ((const char*) line, "oma", (size_t) 3) == 0 ||
153 strncmp ((const char*) line, "omap", (size_t) 4) == 0 ||
154 strncmp ((const char*) line, "im", (size_t) 2) == 0 ||
155 strncmp ((const char*) line, "ima", (size_t) 3) == 0 ||
156 strncmp ((const char*) line, "imap", (size_t) 4) == 0 ||
157 strncmp ((const char*) line, "lm", (size_t) 2) == 0 ||
158 strncmp ((const char*) line, "lma", (size_t) 3) == 0 ||
159 strncmp ((const char*) line, "lmap", (size_t) 4) == 0 ||
160 strncmp ((const char*) line, "cm", (size_t) 2) == 0 ||
161 strncmp ((const char*) line, "cma", (size_t) 3) == 0 ||
162 strncmp ((const char*) line, "cmap", (size_t) 4) == 0 ||
163 strncmp ((const char*) line, "no", (size_t) 2) == 0 ||
164 strncmp ((const char*) line, "nor", (size_t) 3) == 0 ||
165 strncmp ((const char*) line, "nore", (size_t) 4) == 0 ||
166 strncmp ((const char*) line, "norem", (size_t) 5) == 0 ||
167 strncmp ((const char*) line, "norema", (size_t) 6) == 0 ||
168 strncmp ((const char*) line, "noremap", (size_t) 7) == 0 ||
169 strncmp ((const char*) line, "nno", (size_t) 3) == 0 ||
170 strncmp ((const char*) line, "nnor", (size_t) 4) == 0 ||
171 strncmp ((const char*) line, "nnore", (size_t) 5) == 0 ||
172 strncmp ((const char*) line, "nnorem", (size_t) 6) == 0 ||
173 strncmp ((const char*) line, "nnorema", (size_t) 7) == 0 ||
174 strncmp ((const char*) line, "nnoremap", (size_t) 8) == 0 ||
175 strncmp ((const char*) line, "vno", (size_t) 3) == 0 ||
176 strncmp ((const char*) line, "vnor", (size_t) 4) == 0 ||
177 strncmp ((const char*) line, "vnore", (size_t) 5) == 0 ||
178 strncmp ((const char*) line, "vnorem", (size_t) 6) == 0 ||
179 strncmp ((const char*) line, "vnorema", (size_t) 7) == 0 ||
180 strncmp ((const char*) line, "vnoremap", (size_t) 8) == 0 ||
181 strncmp ((const char*) line, "ono", (size_t) 3) == 0 ||
182 strncmp ((const char*) line, "onor", (size_t) 4) == 0 ||
183 strncmp ((const char*) line, "onore", (size_t) 5) == 0 ||
184 strncmp ((const char*) line, "onorem", (size_t) 6) == 0 ||
185 strncmp ((const char*) line, "onorema", (size_t) 7) == 0 ||
186 strncmp ((const char*) line, "onoremap", (size_t) 8) == 0 ||
187 strncmp ((const char*) line, "ino", (size_t) 3) == 0 ||
188 strncmp ((const char*) line, "inor", (size_t) 4) == 0 ||
189 strncmp ((const char*) line, "inore", (size_t) 5) == 0 ||
190 strncmp ((const char*) line, "inorem", (size_t) 6) == 0 ||
191 strncmp ((const char*) line, "inorema", (size_t) 7) == 0 ||
192 strncmp ((const char*) line, "inoremap", (size_t) 8) == 0 ||
193 strncmp ((const char*) line, "lno", (size_t) 3) == 0 ||
194 strncmp ((const char*) line, "lnor", (size_t) 4) == 0 ||
195 strncmp ((const char*) line, "lnore", (size_t) 5) == 0 ||
196 strncmp ((const char*) line, "lnorem", (size_t) 6) == 0 ||
197 strncmp ((const char*) line, "lnorema", (size_t) 7) == 0 ||
198 strncmp ((const char*) line, "lnoremap", (size_t) 8) == 0 ||
199 strncmp ((const char*) line, "cno", (size_t) 3) == 0 ||
200 strncmp ((const char*) line, "cnor", (size_t) 4) == 0 ||
201 strncmp ((const char*) line, "cnore", (size_t) 5) == 0 ||
202 strncmp ((const char*) line, "cnorem", (size_t) 6) == 0 ||
203 strncmp ((const char*) line, "cnorema", (size_t) 7) == 0 ||
204 strncmp ((const char*) line, "cnoremap", (size_t) 8) == 0
205 )
206 return TRUE;
207
208 return FALSE;
209 }
210
readVimLine(void)211 static const unsigned char * readVimLine (void)
212 {
213 const unsigned char *line;
214
215 while ((line = fileReadLine ()) != NULL)
216 {
217 while (isspace ((int) *line))
218 ++line;
219
220 if ((int) *line == '"')
221 continue; /* skip comment */
222
223 break;
224 }
225
226 return line;
227 }
228
parseFunction(const unsigned char * line)229 static void parseFunction (const unsigned char *line)
230 {
231 vString *name = vStringNew ();
232 /* boolean inFunction = FALSE; */
233 int scope;
234
235 const unsigned char *cp = line + 1;
236
237 if ((int) *++cp == 'n' && (int) *++cp == 'c' &&
238 (int) *++cp == 't' && (int) *++cp == 'i' &&
239 (int) *++cp == 'o' && (int) *++cp == 'n')
240 ++cp;
241 if ((int) *cp == '!')
242 ++cp;
243 if (isspace ((int) *cp))
244 {
245 while (*cp && isspace ((int) *cp))
246 ++cp;
247
248 if (*cp)
249 {
250 cp = skipPrefix (cp, &scope);
251 if (isupper ((int) *cp) ||
252 scope == 's' || /* script scope */
253 scope == '<' || /* script scope */
254 scope == 'd' || /* dictionary */
255 scope == 'a') /* autoload */
256 {
257 do
258 {
259 vStringPut (name, (int) *cp);
260 ++cp;
261 } while (isalnum ((int) *cp) || *cp == '_' || *cp == '.' || *cp == '#');
262 vStringTerminate (name);
263 makeSimpleTag (name, VimKinds, K_FUNCTION);
264 vStringClear (name);
265 }
266 }
267 }
268
269 /* TODO - update struct to indicate inside function */
270 while ((line = readVimLine ()) != NULL)
271 {
272 /*
273 * Vim7 added the for/endfo[r] construct, so we must first
274 * check for an "endfo", before a "endf"
275 */
276 if ( (!strncmp ((const char*) line, "endfo", (size_t) 5) == 0) &&
277 (strncmp ((const char*) line, "endf", (size_t) 4) == 0) )
278 break;
279 /* TODO - call parseVimLine */
280 }
281 vStringDelete (name);
282 }
283
parseAutogroup(const unsigned char * line)284 static void parseAutogroup (const unsigned char *line)
285 {
286 vString *name = vStringNew ();
287
288 /* Found Autocommand Group (augroup) */
289 const unsigned char *cp = line + 2;
290 if ((int) *++cp == 'r' && (int) *++cp == 'o' &&
291 (int) *++cp == 'u' && (int) *++cp == 'p')
292 ++cp;
293 if (isspace ((int) *cp))
294 {
295 while (*cp && isspace ((int) *cp))
296 ++cp;
297
298 if (*cp)
299 {
300 if (strncasecmp ((const char*) cp, "end", (size_t) 3) != 0)
301 {
302 do
303 {
304 vStringPut (name, (int) *cp);
305 ++cp;
306 } while (isalnum ((int) *cp) || *cp == '_');
307 vStringTerminate (name);
308 makeSimpleTag (name, VimKinds, K_AUGROUP);
309 vStringClear (name);
310 }
311 }
312 }
313 vStringDelete (name);
314 }
315
parseCommand(const unsigned char * line)316 static boolean parseCommand (const unsigned char *line)
317 {
318 vString *name = vStringNew ();
319 boolean cmdProcessed = TRUE;
320
321 /*
322 * Found a user-defined command
323 *
324 * They can have many options preceeded by a dash
325 * command! -nargs=+ -complete Select :call s:DB_execSql("select " . <q-args>)
326 * The name of the command should be the first word not preceeded by a dash
327 *
328 */
329 const unsigned char *cp = line;
330
331 if ( (int) *cp == '\\' )
332 {
333 /*
334 * We are recursively calling this function is the command
335 * has been continued on to the next line
336 *
337 * Vim statements can be continued onto a newline using a \
338 * to indicate the previous line is continuing.
339 *
340 * com -nargs=1 -bang -complete=customlist,EditFileComplete
341 * \ EditFile edit<bang> <args>
342 *
343 * If the following lines do not have a line continuation
344 * the command must not be spanning multiple lines and should
345 * be synatically incorrect.
346 */
347 if ((int) *cp == '\\')
348 ++cp;
349
350 while (*cp && isspace ((int) *cp))
351 ++cp;
352 }
353 else if ( (!strncmp ((const char*) line, "comp", (size_t) 4) == 0) &&
354 (!strncmp ((const char*) line, "comc", (size_t) 4) == 0) &&
355 (strncmp ((const char*) line, "com", (size_t) 3) == 0) )
356 {
357 cp += 2;
358 if ((int) *++cp == 'm' && (int) *++cp == 'a' &&
359 (int) *++cp == 'n' && (int) *++cp == 'd')
360 ++cp;
361
362 if ((int) *cp == '!')
363 ++cp;
364
365 while (*cp && isspace ((int) *cp))
366 ++cp;
367 }
368 else
369 {
370 /*
371 * We are recursively calling this function. If it does not start
372 * with "com" or a line continuation character, we have moved off
373 * the command line and should let the other routines parse this file.
374 */
375 cmdProcessed = FALSE;
376 goto cleanUp;
377 }
378
379 /*
380 * Strip off any spaces and options which are part of the command.
381 * These should preceed the command name.
382 */
383 do
384 {
385 if (isspace ((int) *cp))
386 {
387 ++cp;
388 }
389 else if (*cp == '-')
390 {
391 /*
392 * Read until the next space which sparates options or the name
393 */
394 while (*cp && !isspace ((int) *cp))
395 ++cp;
396 }
397 } while ( *cp && !isalnum ((int) *cp) );
398
399 if ( ! *cp )
400 {
401 /*
402 * We have reached the end of the line without finding the command name.
403 * Read the next line and continue processing it as a command.
404 */
405 line = readVimLine();
406 parseCommand(line);
407 goto cleanUp;
408 }
409
410 do
411 {
412 vStringPut (name, (int) *cp);
413 ++cp;
414 } while (isalnum ((int) *cp) || *cp == '_');
415
416 vStringTerminate (name);
417 makeSimpleTag (name, VimKinds, K_COMMAND);
418 vStringClear (name);
419
420 cleanUp:
421 vStringDelete (name);
422
423 return cmdProcessed;
424 }
425
parseLet(const unsigned char * line)426 static void parseLet (const unsigned char *line)
427 {
428 vString *name = vStringNew ();
429
430 /* we've found a variable declared outside of a function!! */
431 const unsigned char *cp = line + 3;
432 const unsigned char *np = line;
433 /* get the name */
434 if (isspace ((int) *cp))
435 {
436 while (*cp && isspace ((int) *cp))
437 ++cp;
438
439 /*
440 * Ignore lets which set:
441 * & - local buffer vim settings
442 * @ - registers
443 * [ - Lists or Dictionaries
444 */
445 if (!*cp || *cp == '&' || *cp == '@' || *cp == '[' )
446 goto cleanUp;
447
448 /*
449 * Ignore vim variables which are read only
450 * v: - Vim variables.
451 */
452 np = cp;
453 ++np;
454 if ((int) *cp == 'v' && (int) *np == ':' )
455 goto cleanUp;
456
457 /* deal with spaces, $, @ and & */
458 while (*cp && *cp != '$' && !isalnum ((int) *cp))
459 ++cp;
460
461 if (!*cp)
462 goto cleanUp;
463
464 /* cp = skipPrefix (cp, &scope); */
465 do
466 {
467 if (!*cp)
468 break;
469
470 vStringPut (name, (int) *cp);
471 ++cp;
472 } while (isalnum ((int) *cp) || *cp == '_' || *cp == '#' || *cp == ':' || *cp == '$');
473 vStringTerminate (name);
474 makeSimpleTag (name, VimKinds, K_VARIABLE);
475 vStringClear (name);
476 }
477
478 cleanUp:
479 vStringDelete (name);
480 }
481
parseMap(const unsigned char * line)482 static boolean parseMap (const unsigned char *line)
483 {
484 vString *name = vStringNew ();
485
486 const unsigned char *cp = line;
487
488 /* Remove map */
489 while (*cp && isalnum ((int) *cp))
490 ++cp;
491
492 if ((int) *cp == '!')
493 ++cp;
494
495 /*
496 * Maps follow this basic format
497 * map
498 * nnoremap <silent> <F8> :Tlist<CR>
499 * map <unique> <Leader>scdt <Plug>GetColumnDataType
500 * inoremap ,,, <esc>diwi<<esc>pa><cr></<esc>pa><esc>kA
501 * inoremap <buffer> ( <C-R>=PreviewFunctionSignature()<LF>
502 *
503 * The Vim help shows the various special arguments available to a map:
504 * 1.2 SPECIAL ARGUMENTS *:map-arguments*
505 * <buffer>
506 * <silent>
507 * <script>
508 * <unique>
509 * <special>
510 * <expr>
511 *
512 * Strip the special arguments from the map command, this should leave
513 * the map name which we will use as the "name".
514 */
515
516 do
517 {
518 while (*cp && isspace ((int) *cp))
519 ++cp;
520
521 if (strncmp ((const char*) cp, "<Leader>", (size_t) 8) == 0)
522 break;
523
524 if (
525 strncmp ((const char*) cp, "<buffer>", (size_t) 8) == 0 ||
526 strncmp ((const char*) cp, "<silent>", (size_t) 8) == 0 ||
527 strncmp ((const char*) cp, "<script>", (size_t) 8) == 0 ||
528 strncmp ((const char*) cp, "<unique>", (size_t) 8) == 0
529 )
530 {
531 cp += 8;
532 continue;
533 }
534
535 if (strncmp ((const char*) cp, "<expr>", (size_t) 6) == 0)
536 {
537 cp += 6;
538 continue;
539 }
540
541 if (strncmp ((const char*) cp, "<special>", (size_t) 9) == 0)
542 {
543 cp += 9;
544 continue;
545 }
546
547 break;
548 } while (*cp);
549
550 do
551 {
552 vStringPut (name, (int) *cp);
553 ++cp;
554 } while (*cp && *cp != ' ');
555
556 vStringTerminate (name);
557 makeSimpleTag (name, VimKinds, K_MAP);
558 vStringClear (name);
559
560 vStringDelete (name);
561
562 return TRUE;
563 }
564
parseVimLine(const unsigned char * line)565 static boolean parseVimLine (const unsigned char *line)
566 {
567 boolean readNextLine = TRUE;
568
569 if ( (!strncmp ((const char*) line, "comp", (size_t) 4) == 0) &&
570 (!strncmp ((const char*) line, "comc", (size_t) 4) == 0) &&
571 (strncmp ((const char*) line, "com", (size_t) 3) == 0) )
572 {
573 readNextLine = parseCommand(line);
574 /* TODO - Handle parseCommand returning FALSE */
575 }
576
577 if (isMap(line))
578 {
579 parseMap(line);
580 }
581
582 if (strncmp ((const char*) line, "fu", (size_t) 2) == 0)
583 {
584 parseFunction(line);
585 }
586
587 if (strncmp ((const char*) line, "aug", (size_t) 3) == 0)
588 {
589 parseAutogroup(line);
590 }
591
592 if ( strncmp ((const char*) line, "let", (size_t) 3) == 0 )
593 {
594 parseLet(line);
595 }
596
597 return readNextLine;
598 }
599
parseVimFile(const unsigned char * line)600 static void parseVimFile (const unsigned char *line)
601 {
602 boolean readNextLine = TRUE;
603 line = readVimLine();
604
605 while (line != NULL)
606 {
607 readNextLine = parseVimLine(line);
608
609 if ( readNextLine )
610 line = readVimLine();
611
612 }
613 }
614
findVimTags(void)615 static void findVimTags (void)
616 {
617 const unsigned char *line;
618 /* TODO - change this into a structure */
619
620 line = '\0';
621
622 parseVimFile (line);
623 }
624
VimParser(void)625 extern parserDefinition* VimParser (void)
626 {
627 static const char *const extensions [] = { "vim", NULL };
628 parserDefinition* def = parserNew ("Vim");
629 def->kinds = VimKinds;
630 def->kindCount = KIND_COUNT (VimKinds);
631 def->extensions = extensions;
632 def->parser = findVimTags;
633 return def;
634 }
635
636 /* vi:set tabstop=4 shiftwidth=4 noexpandtab: */
637