/*--------------------------------*-C-*---------------------------------* * File: activetags.c * * Copyright 1997 Nat Friedman, Massachusetts Institute of Technology * * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * * *----------------------------------------------------------------------*/ /* This file contains all of the basic active tags functionality. These are the generalized routines which can be plugged into just about anything. If I've designed everything properly, which I believe I have, you should not have to change anything in this file in order to plug active tags into an application. See activeeterm.c for the routines which interface these functions with Eterm */ static const char cvs_ident[] = "$Id$"; #include "feature.h" #ifdef USE_ACTIVE_TAGS #include #include #include #include #include #include #include "activetags.h" #include "activeeterm.h" /* ============================ Global Variables =========================== */ /* This is for run-time enabling and disabling of tags. It is 1 if tags are enabled at 0 otherwise. */ int active_tags_enabled = 1; /* This is the global array of tag configurations. Each of the tags in the configuration file corresponds to a tag in this global array. These are allocated statically just to reduce the complexity of the code. Increase MAX_TAGS in activetags.h if you run out. */ struct active_tag tag[MAX_TAGS]; int num_tags = 0; /* The tag environment (e.g. "X", "console", ... */ char *tag_env; /* The data regarding the last tag highlighted on the screen. NB: this model limits the number of highlighted tags on the screen to one. */ int last_h_tag_index = -1, last_h_tag_begin_row = -1, last_h_tag_end_row = -1, last_h_tag_begin_col = -1, last_h_tag_end_col = -1; tag_highlight_t old_highlighting[MAX_SEARCH_LINES][MAX_SEARCH_COLS]; char *tag_config_file = NULL; static char *thingy = "ActiveTags 1.0b4 -- Copyright 1997,1998 Nat Friedman -- DINGUS UBER ALLES"; /* ============================== Tag Routines ============================= */ /* This is the tag intialization routine. It needs to be called upon startup. */ void initialize_tags(void) { /* Parse the config file */ parse_tag_config(tag_config_file); } void disable_tags(void) { fprintf(stderr, "Active tags are disabled.\n"); active_tags_enabled = 0; } /* check_tag_mode returns true if the mode parameter is one of the modes for which the tag indexed by tag_index is active. Otherwise, it returns false. */ int check_tag_mode(int tag_index, char *mode) { int i; /* If no modes are listed for a particular tag, that tag is always active. */ if (tag[tag_index].num_modes == 0) return 1; for (i = 0; i < tag[tag_index].num_modes; i++) if (!strcmp(mode, tag[tag_index].mode[i])) return 1; return 0; } int check_tag_env(int tag_index) { int i; if (!tag[tag_index].num_envs) return 1; for (i = 0; i < tag[tag_index].num_envs; i++) if (!strcasecmp(tag_env, tag[tag_index].env[i])) return 1; return 0; } /* Check the position specified by (row,col) for a tag. If there is a tag there, set tag_begin and tag_end to the proper offsets into screen.text and screen.rend, and set tag_index to the index of the tag that was identified. If no tag is found, return 0. Otherwise, return 1. If binding_mask is set, then only search tags whose binding mask matches the binding_mask passed to the function. Tag searching begins at the specified index, tag_begin_index. */ int find_tag(int row, int col, int *tag_begin_row, int *tag_begin_col, int *tag_end_row, int *tag_end_col, int *tag_index, unsigned int binding_mask, int tag_begin_index) { char *curr_row; static char mode[1024]; static int mode_check_count = 0; char compare_region[MAX_SEARCH_CHARS]; int compare_offset, compare_region_pointer_position; int done; unsigned int region_size; unsigned int last_region_size = 0; int start_row, end_row, i, dest_offset; char *start_match_p, *end_match_p; #ifndef ACTIVE_TAGS_SPENCER_REGEXP regmatch_t regmatch[5]; #endif D_TAGS(("==> find_tag(row=%d, col=%d, ..., binding=%d, begin=%d)\n", row, col, binding_mask, tag_begin_index)); if (!mode_check_count) get_tag_mode(mode); mode_check_count++; if (mode_check_count == MAX_MODE_CHECK_COUNT) mode_check_count = 0; for (*tag_index = tag_begin_index; *tag_index < num_tags; (*tag_index)++) { D_TAGS((" ==> tag: %d (sl=%d)\n", *tag_index, tag[*tag_index].search_lines)); if (((binding_mask == 0) && (!tag[*tag_index].latent)) || (binding_mask && (binding_mask == tag[*tag_index].binding_mask))) if (check_tag_mode(*tag_index, mode) && (check_tag_env(*tag_index))) { start_row = row - tag[*tag_index].search_lines / 2; end_row = row + tag[*tag_index].search_lines / 2; if (start_row < tag_min_row()) start_row = tag_min_row(); if (end_row > tag_max_row()) end_row = tag_max_row(); compare_region_pointer_position = ((row - start_row) * row_width()) + col; region_size = (row_width()) * (end_row - start_row + 1); if (region_size > MAX_SEARCH_CHARS) { fprintf(stderr, "search region too large: reduce number of " "search lines.\n"); fprintf(stderr, "row_width: %d end_row: %d start_row: %d\n", row_width(), end_row, start_row); break; } if (region_size != last_region_size) { D_TAGS((" ==> region_size == %d\tlast_region_size == %d\n", region_size, last_region_size)); i = start_row; dest_offset = 0; while (i <= end_row) { tag_get_row(i, &curr_row); D_TAGS(("Memcpying row into place...\n")); memcpy(compare_region + dest_offset, curr_row, row_width()); D_TAGS(("Done\n")); dest_offset += row_width(); i++; } compare_region[dest_offset + 1] = '\0'; } last_region_size = region_size; done = 0; compare_offset = 0; while (!done) { #ifdef ACTIVE_TAGS_SPENCER_REGEXP if (regexec(tag[*tag_index].rx, compare_region + compare_offset)) #else if (!regexec(&tag[*tag_index].rx, compare_region + compare_offset, 4, regmatch, REG_NOTBOL | REG_NOTEOL)) #endif { #ifdef ACTIVE_TAGS_SPENCER_REGEXP start_match_p = tag[*tag_index].rx->startp[0]; end_match_p = tag[*tag_index].rx->endp[0]; #else start_match_p = compare_region + compare_offset + regmatch[0].rm_so; end_match_p = compare_region + compare_offset + regmatch[0].rm_eo; #endif if ((start_match_p <= (compare_region + compare_region_pointer_position)) && (end_match_p > (compare_region + compare_region_pointer_position))) { *tag_begin_row = ((start_match_p - compare_region) / row_width()) + start_row; *tag_begin_col = (start_match_p - compare_region) - ((start_match_p - compare_region) / row_width()) * row_width(); *tag_end_row = ((end_match_p - compare_region) / row_width()) + start_row; *tag_end_col = (end_match_p - compare_region) - ((end_match_p - compare_region) / row_width()) * row_width(); D_TAGS(("Found tag: begin_row: %d begin_col: %d\nend_row : %d end_col : %d\n", *tag_begin_row, *tag_begin_col, *tag_end_row, *tag_end_col)); return 1; } else compare_offset = (end_match_p - compare_region); } else done = 1; } } } return 0; } /* tag_scroll -- This is to notify the tag functionality that the screen has scrolled nlines lines (positive means scrolled up, negative means scrolled down) from start_row to end_row (inclusive) */ int tag_screen_scroll(int nlines, int start_row, int end_row) { D_TAGS(("tag_scroll(%d, %d, %d)\n", nlines, start_row, end_row)); D_TAGS(("\tlast_brow: %d last_erow: %d\n", last_h_tag_begin_row, last_h_tag_end_row)); if (!nlines) return 0; if (last_h_tag_index == -1) return 0; /* If the last highlighted tag is not part of the region that scrolled, we don't need to do anything. */ if (last_h_tag_begin_row > end_row) return 0; if (last_h_tag_end_row < start_row) return 0; /* Otherwise, update the position of the tag last highlighted */ last_h_tag_begin_row += nlines; last_h_tag_end_row += nlines; /* Erase the tag */ (void) show_tag(last_h_tag_begin_row - nlines, last_h_tag_begin_col + 1); return 1; } /* This function restores the rendering information for the currently highlighted tag to its status before the tag was highlighted. */ void erase_tag_highlighting(void) { int row, col; int final_highlight_col; int start_highlight_col; if (last_h_tag_index != -1) { for (row = last_h_tag_begin_row; row <= last_h_tag_end_row; row++) { final_highlight_col = (row == last_h_tag_end_row) ? last_h_tag_end_col : row_width(); start_highlight_col = (row == last_h_tag_begin_row) ? last_h_tag_begin_col : 0; for (col = start_highlight_col; col < final_highlight_col; col++) { set_tag_highlight(row, col, old_highlighting[row - last_h_tag_begin_row][col]); } } } /* We don't need to keep erasing now that nothing is highlighted */ last_h_tag_index = -1; } /* Highlight a tag if one exists at the location specified in pixels. If no tag exists there and a tag is currently highlighted, we need to erase that tag's highlighting. */ int show_tag(int row, int col) { unsigned int tag_begin_col, tag_end_col, tag_begin_row, tag_end_row, tag_index; int final_highlight_col; int start_highlight_col; tag_highlight_t highlight; D_TAGS(("==> show_tag(%d,%d)\n", row, col)); /* If there's no tag there and a tag is currently highlighted, we need to erase its highlighting. */ if (!find_tag(row, col, &tag_begin_row, &tag_begin_col, &tag_end_row, &tag_end_col, &tag_index, 0, 0)) { D_TAGS((" ==> no tag, erasing highlighting and leaving.\n")); /* Erase the old highlighting */ tag_hide(); return 0; } /* If we've come this far, then we are on a tag, and it needs to be highlighted. */ /* If we're on the same tag as last time, there's no need to do anything. */ if ((tag_index == last_h_tag_index) && (tag_begin_row == last_h_tag_begin_row) && (tag_end_row == last_h_tag_end_row) && (tag_begin_col == last_h_tag_begin_col) && (tag_end_col == last_h_tag_end_col)) return 1; /* Erase the old highlighting */ tag_hide(); /* Add the new highlighting */ for (row = tag_begin_row; row <= tag_end_row; row++) { final_highlight_col = (row == tag_end_row) ? tag_end_col : row_width(); start_highlight_col = (row == tag_begin_row) ? tag_begin_col : 0; for (col = start_highlight_col; col < final_highlight_col; col++) { get_tag_highlight(row, col, &highlight); memcpy((void *) &old_highlighting[row - tag_begin_row][col], (void *) &highlight, sizeof(tag_highlight_t)); set_tag_highlight(row, col, tag[tag_index].highlight); } } /* Store the old values to erase later */ last_h_tag_index = tag_index; last_h_tag_begin_row = tag_begin_row; last_h_tag_end_row = tag_end_row; last_h_tag_begin_col = tag_begin_col; last_h_tag_end_col = tag_end_col; return 1; } /* Check to see if there's a tag at the location specified by (x,y) (in pixels). If so, execute the corresponding action. Return 0 if there's no tag there, otherwise return 1. */ int tag_activate(int row, int col, unsigned int binding_mask) { int tag_begin_row, tag_end_row, tag_index, tag_begin_col, tag_end_col; D_TAGS(("tag_activate(row==%d, col==%d, ...)\n", row, col)); /* If there is no tag to be activated here, return. */ if (!find_tag(row, col, &tag_begin_row, &tag_begin_col, &tag_end_row, &tag_end_col, &tag_index, binding_mask, 0)) return 0; /* Otherwise, activate the tag. */ execute_tag(tag_begin_row, tag_begin_col, tag_end_row, tag_end_col, tag_index); return 1; } /* Execute the tag specified by tag_index and contained between the indices specified by tag_begin and tag_end. */ void execute_tag(int tag_begin_row, int tag_begin_col, int tag_end_row, int tag_end_col, int tag_index) { char tagstr[MAX_SEARCH_CHARS]; char cmd[MAX_TAG_COMMAND_LENGTH]; char *p, *q; pid_t pid; int dest_offset; int i, start_column, end_column; char *curr_row; printf("==> Activating tag %d:\n ==> Action: [%s]\n Env: [%s] ==> Output: %s\n", tag_index, tag[tag_index].action, tag_env, (tag[tag_index].output_type == TAG_OUTPUT_NULL) ? "NULL" : ((tag[tag_index].output_type == TAG_OUTPUT_POPUP) ? "POPUP" : ((tag[tag_index].output_type == TAG_OUTPUT_LOOP) ? "LOOP" : ((tag[tag_index].output_type == TAG_OUTPUT_REPL) ? "REPL" : "UNKNOWN")))); /* If the tag's action is TAG_ACTION_RELOAD, then we simply relaod the tag config file. */ if (!strcmp(tag[tag_index].action, TAG_ACTION_RELOAD)) { for (i = 0; i < num_tags; i++) #ifdef ACTIVE_TAGS_SPENCER_REGEXP regfree(tag[i].rx); #else regfree(&tag[i].rx); #endif num_tags = 0; parse_tag_config(tag_config_file); return; } if (!strcmp(tag[tag_index].action, TAG_ACTION_DISABLE)) { disable_tags(); return; } /* For debugging */ if (!strcmp(tag[tag_index].action, TAG_ACTION_MODE)) { char mode[1024]; get_tag_mode(mode); fprintf(stderr, "Mode: %s\n", mode); return; } /* Fork off a separate process to execute the new tag. */ pid = fork(); if (pid == 0) { /* child */ D_TAGS(("Child\n")); i = tag_begin_row; dest_offset = 0; while (i <= tag_end_row) { start_column = i == tag_begin_row ? tag_begin_col : 0; end_column = i == tag_end_row ? tag_end_col : row_width(); D_TAGS(("row: %d Start col: %d end_col: %d\n", i, start_column, end_column)); tag_get_row(i, &curr_row); memcpy(tagstr + dest_offset, curr_row + start_column, end_column - start_column); dest_offset += end_column - start_column; i++; } tagstr[dest_offset] = '\0'; D_TAGS(("\t==> tag string: {%s}\n", tagstr)); /* Initialize the command string */ *cmd = '\0'; /* Build the command string from the action string by replacing all occurences of ${} in the action string with the tag string. */ q = p = tag[tag_index].action; while ((p = strstr(q, "${}")) != NULL) { *p = '\0'; strcat(cmd, q); strcat(cmd, tagstr); /* Step over the remaining characters of the ${} */ q = p + 3; } strcat(cmd + strlen(cmd), q); if (!set_tag_pwd()) fprintf(stderr, "Active Tags: couldn't set the pwd!\n"); /* Set the UID appropriately so we don't act as the wrong user */ if (!set_tag_uid()) { fprintf(stderr, "Active Tags: tag action: Couldn't set the uid!\n"); exit(1); } /* For a loop action, we connect stdout on the tag process to stdin on the terminal's executing process */ if (tag[tag_index].output_type == TAG_OUTPUT_LOOP) /* I wonder if screwing around with Eterm's stdin is a good idea >:) * -vendu */ if (!set_tag_stdout()) { fprintf(stderr, "Active Tags: tag action: Couldn't set stdout for " "a loop action!\n"); exit(1); } system(cmd); #if 0 exit(1); /* This might be a bad idea :) Makes Eterm exit, at * least if run from another Eterm. -vendu */ #endif return; } } #endif /* USE_ACTIVE_TAGS */