*
* \b arg1 | \b arg2 | \b string |
* \b Description |
*
* 0 \e (optional) | - | Tags file name |
* Load tags file given in \a string.
* It can be either in \e etags or \e ctags format. |
*
* 1 | \e optional (default: 0) | Symbol name |
* Find tag entry whose symbol matches \a string,
* skipping \a arg2 matching entries |
*
* 2 | - | - |
* Check whether a tags file has been loaded |
*
* 3 | - | - |
* Insert current tag entry's target file name into the edit buffer |
*
* 4 | - | - |
* Insert current tag entry's search string into the edit buffer |
*
* 5 | - | - |
* Insert current tag entry's line number into the edit buffer |
*
* 6 | - | - |
* Dump entire tag database into the edit buffer |
*
*
*
* \param uct Command Token
* \param arg_count Number of arguments: 0 \e none, 1 \a arg1 given,
* 2 \a arg1 and \a arg2 given
* \param arg1 Operation identifier
* \param arg2 Operation parameter if required, depending on \a arg1
* \param string Tags file name or symbol depending on \a arg1
*
* \return Status code
* \retval SUCCESS Operation was successful
* \retval FAIL Operation failed, an error message has been displayed
*/
int
cmd_tags(
struct cmd_token *uct,
int arg_count,
int arg1,
int arg2,
char *string )
{
register struct undo_token *ut;
register struct tags *old_tags;
register struct tags *new_tags;
register struct tagent *tep = NULL;
int hashval,skip_cnt;
PREAMBLE();
if(arg_count == 0) arg1 = 0;
if(arg_count < 2) arg2 = 0;
if(arg1 < TAGS_MIN_OPTION || arg1 > TAGS_MAX_OPTION){
error_message("FT option selector out of range");
return(FAIL);
}/* End IF */
switch(arg1){
case TAGS_LOAD_TAGS_FILE:
old_tags = current_tags;
new_tags = tag_load_file(string);
if(new_tags == NULL) return(FAIL);
current_tags = new_tags;
ut = allocate_undo_token(uct);
if(ut == NULL) return(FAIL);
ut->opcode = UNDO_C_LOAD_TAGS;
ut->carg1 = (char *)old_tags;
break;
case TAGS_SEARCH_TAGS_FILE:
if(current_tags == NULL) return(FAIL);
hashval = tag_calc_hash(string);
tep = current_tags->tagents[hashval];
skip_cnt = arg2;
while(tep){
if(strcmp(string,tep->te_symbol) == 0){
if(skip_cnt-- <= 0){
if(current_tags->current_entry != tep){
ut = allocate_undo_token(uct);
if(ut == NULL) return(FAIL);
ut->opcode = UNDO_C_SELECT_TAGS;
ut->carg1 = (char *)current_tags->current_entry;
}/* End IF */
current_tags->current_entry = tep;
return(SUCCESS);
}/* End IF */
}/* End IF */
tep = tep->te_next;
}/* End While */
return(FAIL);
case TAGS_TEST_FOR_LOADED_TAGS:
if(current_tags){
return(SUCCESS);
}/* End IF */
return(FAIL);
case TAGS_INSERT_TARGET_FILE:
if(current_tags->current_entry){
if(current_tags->current_entry->te_filename){
buff_insert_with_undo(
uct,
curbuf,
curbuf->dot,
current_tags->current_entry->te_filename,
strlen(current_tags->current_entry->te_filename)
);
return(SUCCESS);
}/* End IF */
}/* End IF */
return(FAIL);
case TAGS_INSERT_SEARCH_STRING:
if(current_tags->current_entry){
if(current_tags->current_entry->te_search_string){
buff_insert_with_undo(
uct,
curbuf,
curbuf->dot,
current_tags->current_entry->te_search_string,
strlen(current_tags->current_entry->te_search_string)
);
return(SUCCESS);
}/* End IF */
}/* End IF */
return(FAIL);
case TAGS_INSERT_LINENO:
if(current_tags->current_entry){
if(current_tags->current_entry->te_lineno){
buff_insert_with_undo(
uct,
curbuf,
curbuf->dot,
current_tags->current_entry->te_lineno,
strlen(current_tags->current_entry->te_lineno)
);
return(SUCCESS);
}/* End IF */
}/* End IF */
return(FAIL);
case TAGS_INSERT_ALL:
if(current_tags){
tag_dump_database(current_tags,uct);
return(SUCCESS);
}/* End IF */
return(FAIL);
}/* End Switch */
return(SUCCESS);
}/* End Routine */
/**
* \brief Read in a tags file
*
* This is a huge monolithic nasty routine which reads either VI (\e ctags) or
* EMACS (\e etags) tags files, and builds an internal representation.
*
* \param string File name of tags file to load
*
* \return Internal representation of tags database
* \retval NULL An error occurred, an error message has been displayed
*/
struct tags *
tag_load_file( char *string )
{
FILE *fd = NULL;
register struct tags *tp = NULL;
register struct tagent *tep = NULL;
register char *cp;
char *outcp = NULL;
char *filename_cp = NULL;
int state = 0;
int hashval;
int c;
char emacs_flag = NO;
char comma_seen = 0;
char tmp_buffer[LINE_BUFFER_SIZE];
char tmp_search_string[LINE_BUFFER_SIZE];
char tmp_filename[LINE_BUFFER_SIZE];
char tmp_symbol[LINE_BUFFER_SIZE];
char tmp_number[LINE_BUFFER_SIZE];
char tmp_message[LINE_BUFFER_SIZE];
int line = 0;
fd = fopen(string,"r");
if(fd == NULL){
sprintf(
tmp_message,
"Could not open TAGS file <%s>: %s",
string,
error_text(errno)
);
error_message(tmp_message);
return(NULL);
}/* End IF */
tp = (struct tags *)tec_alloc(TYPE_C_TAGS,sizeof(struct tags));
if(tp == NULL) return(NULL);
bzero((char *)tp,sizeof(struct tags));
/*
* Loop reading tag entries
*/
while(1){
cp = tmp_buffer;
bzero(cp,sizeof(tmp_buffer));
if(fgets(cp,sizeof(tmp_buffer),fd) == NULL) break;
line++;
/*
* Ok, we have the start of an entry. Check for continuation.
*/
while(cp[0]){
if(cp[0] == '\n'){
cp[1] = '\0';
break;
}/* End IF */
if(cp[0] == '\\' && cp[1] == '\n'){
if(fgets(cp,sizeof(tmp_buffer)-(cp-tmp_buffer),fd) == NULL){
cp[0] = '\n'; cp[1] = '\0';
}/* End IF */
line++;
}/* End IF */
cp++;
}/* End While */
/*
* First step is to get the name of the symbol so that we can obtain the
* hash value
*/
cp = tmp_buffer;
if(emacs_flag == NO) state = 0;
while(*cp){
if(state < 0) break;
switch(state){
/*
* Skip over symbol name until we hit whitespace. Then calculate the hash
* value for that symbol, allocate the tag entry structure, and copy the
* symbol name into it.
*/
case 0:
if(cp[0] == '\f' && cp[1] == '\n'){
emacs_flag = YES;
state = 100;
continue;
}/* End IF */
if(isspace((int)*cp)){
c = *cp;
*cp = '\0';
hashval = tag_calc_hash(tmp_buffer);
tep = (struct tagent *)tec_alloc(
TYPE_C_TAGENT,
sizeof(struct tagent)
);
if(tep == NULL) goto err;
bzero((char *)tep,sizeof(*tep));
tep->te_next = tp->tagents[hashval];
tp->tagents[hashval] = tep;
tep->te_symbol = (char *)tec_alloc(
TYPE_C_TAGSTR,
strlen(tmp_buffer) + 1
);
if(tep->te_symbol == NULL) goto err;
strcpy(tep->te_symbol,tmp_buffer);
*cp = c;
state++;
}/* End IF */
cp++;
continue;
/*
* Skip any amount of whitespace which seperates the symbol from the
* filename.
*/
case 1:
if(!isspace((int)*cp)){
filename_cp = cp;
state++;
continue;
}/* End IF */
cp++;
continue;
/*
* Find the length of the filename portion and copy it to the tagent
*/
case 2:
if(isspace((int)*cp)){
c = *cp;
*cp = '\0';
tep->te_filename = (char *)tec_alloc(
TYPE_C_TAGSTR,
strlen(filename_cp) + 1
);
if(tep->te_filename == NULL) goto err;
strcpy(tep->te_filename,filename_cp);
*cp = c;
state++;
}/* End IF */
cp++;
continue;
/*
* Skip any amount of whitespace which seperates the filename from the
* search string
*/
case 3:
if(!isspace((int)*cp)){
state++;
continue;
}/* End IF */
cp++;
continue;
/*
* The next character should be a slash to begin the search string
*/
case 4:
if(*cp == '/'){
outcp = tmp_search_string;
*outcp = '\0';
state++;
}
cp++;
continue;
/*
* Find the length of the search string portion and copy it to the tagent
*/
case 5:
if(*cp == '/'){
*outcp = '\0';
tep->te_search_string = (char *)tec_alloc(
TYPE_C_TAGSTR,
strlen(tmp_search_string) + 1
);
if(tep->te_search_string == NULL) goto err;
strcpy(tep->te_search_string,tmp_search_string);
state = -1;
continue;
}/* End IF */
if(*cp == '\\'){
state = state + 1;
cp++;
continue;
}/* End IF */
*outcp++ = *cp++;
continue;
/*
* Here on a backquote character. Just skip it and pass through the quoted
* character without testing it for termination of the search string.
*/
case 6:
*outcp++ = *cp++;
state = state - 1;
continue;
/*
* Here if we encounter a form-feed. This is a giveaway that we have an
* emacs style tags file, so we have to parse it differently.
*/
case 100:
if(cp[0] != '\f' || cp[1] != '\n'){
cp++;
continue;
}/* End IF */
cp += 2;
state = 101;
outcp = tmp_filename;
comma_seen = 0;
continue;
/*
* Here we get the name of the source file
*/
case 101:
if(*cp == '\n'){
if(comma_seen == 0){
state = 100;
continue;
}/* End IF */
outcp--;
while(*outcp != ',') outcp--;
*outcp = '\0';
cp++;
state = 102;
outcp = tmp_symbol;
*outcp++ = 127;
continue;
}/* End IF */
if(*cp == ',') comma_seen = 1;
*outcp++ = *cp++;
continue;
/*
* Now we get symbol names
*/
case 102:
if(*cp == '\f'){
state = 100;
continue;
}/* End IF */
if(*cp != 127){
*outcp++ = *cp++;
continue;
}/* End IF */
/*
* Here on a 127 code which terminates the symbol name. First eat back to
* the first symbol character.
*/
while((c = outcp[-1])){
if(c == 127) break;
if(
isalnum(c) ||
isdigit(c) ||
c == '_' ||
c == '$' ||
c == '.'
){
*outcp = '\0';
break;
}/* End IF */
outcp--;
}/* End While */
/*
* Now go back until we find the first non-symbol character.
*/
while((c = outcp[-1])){
if(c == 127) break;
if(
isalnum(c) ||
isdigit(c) ||
c == '_' ||
c == '$' ||
c == '.'
){
outcp--;
continue;
}/* End IF */
break;
}/* End While */
hashval = tag_calc_hash(outcp);
tep = (struct tagent *)tec_alloc(
TYPE_C_TAGENT,
sizeof(struct tagent)
);
if(tep == NULL) goto err;
bzero((char *)tep,sizeof(*tep));
tep->te_next = tp->tagents[hashval];
tp->tagents[hashval] = tep;
tep->te_symbol = (char *)tec_alloc(
TYPE_C_TAGSTR,
strlen(outcp) + 1
);
if(tep->te_symbol == NULL) goto err;
strcpy(tep->te_symbol,outcp);
tep->te_filename = (char *)tec_alloc(
TYPE_C_TAGSTR,
strlen(tmp_filename) + 1
);
if(tep->te_filename == NULL) goto err;
strcpy(tep->te_filename,tmp_filename);
state = 103;
continue;
/*
* Eat until we reach a number, which is the line number
*/
case 103:
if(isdigit((int)*cp)){
state = 104;
outcp = tmp_number;
continue;
}/* End IF */
if(*cp == '\f'){
state = 100;
continue;
}/* End IF */
cp++;
continue;
/*
* Read in the line number
*/
case 104:
if(isdigit((int)*cp)){
*outcp++ = *cp++;
continue;
}/* End IF */
*outcp++ = '\0';
tep->te_lineno = (char *)tec_alloc(
TYPE_C_TAGSTR,
strlen(tmp_number) + 1
);
if(tep->te_lineno == NULL) goto err;
strcpy(tep->te_lineno,tmp_number);
state = 105;
continue;
/*
* Eat until end of line
*/
case 105:
if(*cp == '\n'){
cp++;
state = 102;
outcp = tmp_symbol;
continue;
}/* End IF */
if(*cp == '\f'){
state = 100;
continue;
}/* End IF */
cp++;
continue;
}/* End Switch */
}/* End While */
}/* End While */
fclose(fd);
return(tp);
err:
tag_free_struct(tp);
if(fd) fclose(fd);
return(NULL);
}/* End Routine */
/**
* \brief Release memory associated with a tags database
*
* \param tp Tags database
*/
void
tag_free_struct( struct tags *tp )
{
register struct tagent **tepp;
register struct tagent *tep;
register int i;
if(tp == NULL) return;
for(i = 0, tepp = &tp->tagents[0]; (unsigned)i < ELEMENTS(tp->tagents); i++,tepp++){
while((tep = *tepp)){
*tepp = tep->te_next;
if(tep->te_symbol){
tec_release(TYPE_C_TAGSTR,tep->te_symbol);
}/* End IF */
if(tep->te_filename){
tec_release(TYPE_C_TAGSTR,tep->te_filename);
}/* End IF */
if(tep->te_search_string){
tec_release(TYPE_C_TAGSTR,tep->te_search_string);
}/* End IF */
tec_release(TYPE_C_TAGENT,(char *)tep);
}/* End While */
}/* End FOR */
tec_release(TYPE_C_TAGS,(char *)tp);
}/* End Routine */
/**
* \brief Hash function for tag entries of a tags database
*
* \param string Tag entry symbol name
*
* \return Hash value
*/
int
tag_calc_hash( char *string )
{
register int hash = 0;
register int c;
register int shift = 0;
while((c = *string++)){
shift = shift == 8 ? 0 : shift + 1;
hash += c << shift;
}/* End While */
return(hash % TAG_HASH_ENTRIES);
}/* End Routine */
/**
* \brief Dump tags database into edit buffer
*
* \param tp Tags database
* \param uct Command Token
*/
void
tag_dump_database( struct tags *tp, struct cmd_token *uct )
{
register struct tagent **tepp;
register struct tagent *tep;
register int i;
char tmp_output[LINE_BUFFER_SIZE];
if(tp == NULL) return;
for(i = 0, tepp = &tp->tagents[0]; (unsigned)i < ELEMENTS(tp->tagents); i++,tepp++){
tep = *tepp;
while(tep){
sprintf(
tmp_output,
"sym <%s> hash %d file <%s> line <%s> search <%s>\n",
tep->te_symbol ? tep->te_symbol : "",
tep->te_symbol ? tag_calc_hash(tep->te_symbol) : 0,
tep->te_filename ? tep->te_filename : "",
tep->te_lineno ? tep->te_lineno : "",
tep->te_search_string ? tep->te_search_string : ""
);
buff_insert_with_undo(
uct,
curbuf,
curbuf->dot,
tmp_output,
strlen(tmp_output)
);
tep = tep->te_next;
}/* End While */
}/* End FOR */
return;
}/* End Routine */