// clang-format off /* * $Id: vim.c 762 2010-07-28 11:38:19Z dfishburn $ * * Copyright (c) 2000-2003, Darren Hiebert * * This source code is released for free distribution under the terms of the * GNU General Public License. * * Thanks are due to Jay Glanville for significant improvements. * * This module contains functions for generating tags for user-defined * functions for the Vim editor. */ /* * INCLUDE FILES */ #include "third_party/ctags/general.h" /* must always come first */ #include "libc/mem/alg.h" #include "libc/str/str.h" #include "libc/runtime/runtime.h" #ifdef DEBUG #include "libc/calls/calls.h" #include "libc/calls/dprintf.h" #include "libc/calls/weirdtypes.h" #include "libc/fmt/fmt.h" #include "libc/mem/fmt.h" #include "libc/stdio/stdio.h" #include "libc/stdio/temp.h" #include "third_party/musl/tempnam.h" #endif #include "third_party/ctags/parse.h" #include "third_party/ctags/read.h" #include "third_party/ctags/vstring.h" #if 0 typedef struct sLineInfo { tokenType type; keywordId keyword; vString * string; vString * scope; unsigned long lineNumber; fpos_t filePosition; } lineInfo; #endif /* * DATA DEFINITIONS */ typedef enum { K_AUGROUP, K_COMMAND, K_FUNCTION, K_MAP, K_VARIABLE } vimKind; static kindOption VimKinds [] = { { TRUE, 'a', "augroup", "autocommand groups" }, { TRUE, 'c', "command", "user-defined commands" }, { TRUE, 'f', "function", "function definitions" }, { TRUE, 'm', "map", "maps" }, { TRUE, 'v', "variable", "variable definitions" }, }; /* * DATA DECLARATIONS */ #if 0 typedef enum eException { ExceptionNone, ExceptionEOF } exception_t; #endif /* * DATA DEFINITIONS */ #if 0 static jmp_buf Exception; #endif /* * FUNCTION DEFINITIONS */ /* This function takes a char pointer, tries to find a scope separator in the * string, and if it does, returns a pointer to the character after the colon, * and the character defining the scope. * If a colon is not found, it returns the original pointer. */ static const unsigned char* skipPrefix (const unsigned char* name, int *scope) { const unsigned char* result = name; int counter; size_t length; length = strlen((const char*)name); if (scope != NULL) *scope = '\0'; if (length > 3 && name[1] == ':') { if (scope != NULL) *scope = *name; result = name + 2; } else if (length > 5 && strncasecmp ((const char*) name, "", (size_t) 5) == 0) { if (scope != NULL) *scope = *name; result = name + 5; } else { /* * Vim7 check for dictionaries or autoload function names */ counter = 0; do { switch ( name[counter] ) { case '.': /* Set the scope to d - Dictionary */ *scope = 'd'; break; case '#': /* Set the scope to a - autoload */ *scope = 'a'; break; } ++counter; } while (isalnum ((int) name[counter]) || name[counter] == '_' || name[counter] == '.' || name[counter] == '#' ); } return result; } static boolean isMap (const unsigned char* line) { /* * There are many different short cuts for specifying a map. * This routine should capture all the permutations. */ if ( strncmp ((const char*) line, "map", (size_t) 3) == 0 || strncmp ((const char*) line, "nm", (size_t) 2) == 0 || strncmp ((const char*) line, "nma", (size_t) 3) == 0 || strncmp ((const char*) line, "nmap", (size_t) 4) == 0 || strncmp ((const char*) line, "vm", (size_t) 2) == 0 || strncmp ((const char*) line, "vma", (size_t) 3) == 0 || strncmp ((const char*) line, "vmap", (size_t) 4) == 0 || strncmp ((const char*) line, "om", (size_t) 2) == 0 || strncmp ((const char*) line, "oma", (size_t) 3) == 0 || strncmp ((const char*) line, "omap", (size_t) 4) == 0 || strncmp ((const char*) line, "im", (size_t) 2) == 0 || strncmp ((const char*) line, "ima", (size_t) 3) == 0 || strncmp ((const char*) line, "imap", (size_t) 4) == 0 || strncmp ((const char*) line, "lm", (size_t) 2) == 0 || strncmp ((const char*) line, "lma", (size_t) 3) == 0 || strncmp ((const char*) line, "lmap", (size_t) 4) == 0 || strncmp ((const char*) line, "cm", (size_t) 2) == 0 || strncmp ((const char*) line, "cma", (size_t) 3) == 0 || strncmp ((const char*) line, "cmap", (size_t) 4) == 0 || strncmp ((const char*) line, "no", (size_t) 2) == 0 || strncmp ((const char*) line, "nor", (size_t) 3) == 0 || strncmp ((const char*) line, "nore", (size_t) 4) == 0 || strncmp ((const char*) line, "norem", (size_t) 5) == 0 || strncmp ((const char*) line, "norema", (size_t) 6) == 0 || strncmp ((const char*) line, "noremap", (size_t) 7) == 0 || strncmp ((const char*) line, "nno", (size_t) 3) == 0 || strncmp ((const char*) line, "nnor", (size_t) 4) == 0 || strncmp ((const char*) line, "nnore", (size_t) 5) == 0 || strncmp ((const char*) line, "nnorem", (size_t) 6) == 0 || strncmp ((const char*) line, "nnorema", (size_t) 7) == 0 || strncmp ((const char*) line, "nnoremap", (size_t) 8) == 0 || strncmp ((const char*) line, "vno", (size_t) 3) == 0 || strncmp ((const char*) line, "vnor", (size_t) 4) == 0 || strncmp ((const char*) line, "vnore", (size_t) 5) == 0 || strncmp ((const char*) line, "vnorem", (size_t) 6) == 0 || strncmp ((const char*) line, "vnorema", (size_t) 7) == 0 || strncmp ((const char*) line, "vnoremap", (size_t) 8) == 0 || strncmp ((const char*) line, "ono", (size_t) 3) == 0 || strncmp ((const char*) line, "onor", (size_t) 4) == 0 || strncmp ((const char*) line, "onore", (size_t) 5) == 0 || strncmp ((const char*) line, "onorem", (size_t) 6) == 0 || strncmp ((const char*) line, "onorema", (size_t) 7) == 0 || strncmp ((const char*) line, "onoremap", (size_t) 8) == 0 || strncmp ((const char*) line, "ino", (size_t) 3) == 0 || strncmp ((const char*) line, "inor", (size_t) 4) == 0 || strncmp ((const char*) line, "inore", (size_t) 5) == 0 || strncmp ((const char*) line, "inorem", (size_t) 6) == 0 || strncmp ((const char*) line, "inorema", (size_t) 7) == 0 || strncmp ((const char*) line, "inoremap", (size_t) 8) == 0 || strncmp ((const char*) line, "lno", (size_t) 3) == 0 || strncmp ((const char*) line, "lnor", (size_t) 4) == 0 || strncmp ((const char*) line, "lnore", (size_t) 5) == 0 || strncmp ((const char*) line, "lnorem", (size_t) 6) == 0 || strncmp ((const char*) line, "lnorema", (size_t) 7) == 0 || strncmp ((const char*) line, "lnoremap", (size_t) 8) == 0 || strncmp ((const char*) line, "cno", (size_t) 3) == 0 || strncmp ((const char*) line, "cnor", (size_t) 4) == 0 || strncmp ((const char*) line, "cnore", (size_t) 5) == 0 || strncmp ((const char*) line, "cnorem", (size_t) 6) == 0 || strncmp ((const char*) line, "cnorema", (size_t) 7) == 0 || strncmp ((const char*) line, "cnoremap", (size_t) 8) == 0 ) return TRUE; return FALSE; } static const unsigned char * readVimLine (void) { const unsigned char *line; while ((line = fileReadLine ()) != NULL) { while (isspace ((int) *line)) ++line; if ((int) *line == '"') continue; /* skip comment */ break; } return line; } static void parseFunction (const unsigned char *line) { vString *name = vStringNew (); /* boolean inFunction = FALSE; */ int scope; const unsigned char *cp = line + 1; if ((int) *++cp == 'n' && (int) *++cp == 'c' && (int) *++cp == 't' && (int) *++cp == 'i' && (int) *++cp == 'o' && (int) *++cp == 'n') ++cp; if ((int) *cp == '!') ++cp; if (isspace ((int) *cp)) { while (*cp && isspace ((int) *cp)) ++cp; if (*cp) { cp = skipPrefix (cp, &scope); if (isupper ((int) *cp) || scope == 's' || /* script scope */ scope == '<' || /* script scope */ scope == 'd' || /* dictionary */ scope == 'a') /* autoload */ { do { vStringPut (name, (int) *cp); ++cp; } while (isalnum ((int) *cp) || *cp == '_' || *cp == '.' || *cp == '#'); vStringTerminate (name); makeSimpleTag (name, VimKinds, K_FUNCTION); vStringClear (name); } } } /* TODO - update struct to indicate inside function */ while ((line = readVimLine ()) != NULL) { /* * Vim7 added the for/endfo[r] construct, so we must first * check for an "endfo", before a "endf" */ if ( (!strncmp ((const char*) line, "endfo", (size_t) 5) == 0) && (strncmp ((const char*) line, "endf", (size_t) 4) == 0) ) break; /* TODO - call parseVimLine */ } vStringDelete (name); } static void parseAutogroup (const unsigned char *line) { vString *name = vStringNew (); /* Found Autocommand Group (augroup) */ const unsigned char *cp = line + 2; if ((int) *++cp == 'r' && (int) *++cp == 'o' && (int) *++cp == 'u' && (int) *++cp == 'p') ++cp; if (isspace ((int) *cp)) { while (*cp && isspace ((int) *cp)) ++cp; if (*cp) { if (strncasecmp ((const char*) cp, "end", (size_t) 3) != 0) { do { vStringPut (name, (int) *cp); ++cp; } while (isalnum ((int) *cp) || *cp == '_'); vStringTerminate (name); makeSimpleTag (name, VimKinds, K_AUGROUP); vStringClear (name); } } } vStringDelete (name); } static boolean parseCommand (const unsigned char *line) { vString *name = vStringNew (); boolean cmdProcessed = TRUE; /* * Found a user-defined command * * They can have many options preceeded by a dash * command! -nargs=+ -complete Select :call s:DB_execSql("select " . ) * The name of the command should be the first word not preceeded by a dash * */ const unsigned char *cp = line; if ( (int) *cp == '\\' ) { /* * We are recursively calling this function is the command * has been continued on to the next line * * Vim statements can be continued onto a newline using a \ * to indicate the previous line is continuing. * * com -nargs=1 -bang -complete=customlist,EditFileComplete * \ EditFile edit * * If the following lines do not have a line continuation * the command must not be spanning multiple lines and should * be synatically incorrect. */ if ((int) *cp == '\\') ++cp; while (*cp && isspace ((int) *cp)) ++cp; } else if ( (!strncmp ((const char*) line, "comp", (size_t) 4) == 0) && (!strncmp ((const char*) line, "comc", (size_t) 4) == 0) && (strncmp ((const char*) line, "com", (size_t) 3) == 0) ) { cp += 2; if ((int) *++cp == 'm' && (int) *++cp == 'a' && (int) *++cp == 'n' && (int) *++cp == 'd') ++cp; if ((int) *cp == '!') ++cp; if ((int) *cp != ' ') { /* * :command must be followed by a space. If it is not, it is * not a valid command. * Treat the line as processed and continue. */ cmdProcessed = TRUE; goto cleanUp; } while (*cp && isspace ((int) *cp)) ++cp; } else { /* * We are recursively calling this function. If it does not start * with "com" or a line continuation character, we have moved off * the command line and should let the other routines parse this file. */ cmdProcessed = FALSE; goto cleanUp; } /* * Strip off any spaces and options which are part of the command. * These should preceed the command name. */ do { if (isspace ((int) *cp)) { ++cp; } else if (*cp == '-') { /* * Read until the next space which separates options or the name */ while (*cp && !isspace ((int) *cp)) ++cp; } else break; } while ( *cp ); if ( ! *cp ) { /* * We have reached the end of the line without finding the command name. * Read the next line and continue processing it as a command. */ line = readVimLine(); parseCommand(line); goto cleanUp; } do { vStringPut (name, (int) *cp); ++cp; } while (isalnum ((int) *cp) || *cp == '_'); vStringTerminate (name); makeSimpleTag (name, VimKinds, K_COMMAND); vStringClear (name); cleanUp: vStringDelete (name); return cmdProcessed; } static void parseLet (const unsigned char *line) { vString *name = vStringNew (); /* we've found a variable declared outside of a function!! */ const unsigned char *cp = line + 3; const unsigned char *np = line; /* get the name */ if (isspace ((int) *cp)) { while (*cp && isspace ((int) *cp)) ++cp; /* * Ignore lets which set: * & - local buffer vim settings * @ - registers * [ - Lists or Dictionaries */ if (!*cp || *cp == '&' || *cp == '@' || *cp == '[' ) goto cleanUp; /* * Ignore vim variables which are read only * v: - Vim variables. */ np = cp; ++np; if ((int) *cp == 'v' && (int) *np == ':' ) goto cleanUp; /* deal with spaces, $, @ and & */ while (*cp && *cp != '$' && !isalnum ((int) *cp)) ++cp; if (!*cp) goto cleanUp; /* cp = skipPrefix (cp, &scope); */ do { if (!*cp) break; vStringPut (name, (int) *cp); ++cp; } while (isalnum ((int) *cp) || *cp == '_' || *cp == '#' || *cp == ':' || *cp == '$'); vStringTerminate (name); makeSimpleTag (name, VimKinds, K_VARIABLE); vStringClear (name); } cleanUp: vStringDelete (name); } static boolean parseMap (const unsigned char *line) { vString *name = vStringNew (); const unsigned char *cp = line; /* Remove map */ while (*cp && isalnum ((int) *cp)) ++cp; if ((int) *cp == '!') ++cp; /* * Maps follow this basic format * map * nnoremap :Tlist * map scdt GetColumnDataType * inoremap ,,, diwi<pa>pa>kA * inoremap ( =PreviewFunctionSignature() * * The Vim help shows the various special arguments available to a map: * 1.2 SPECIAL ARGUMENTS *:map-arguments* * * *