556 lines
14 KiB
C
556 lines
14 KiB
C
/*===========================================================================
|
|
Copyright (c) 1998-2000, The Santa Cruz Operation
|
|
All rights reserved.
|
|
|
|
Redistribution and use in source and binary forms, with or without
|
|
modification, are permitted provided that the following conditions are met:
|
|
|
|
*Redistributions of source code must retain the above copyright notice,
|
|
this list of conditions and the following disclaimer.
|
|
|
|
*Redistributions in binary form must reproduce the above copyright notice,
|
|
this list of conditions and the following disclaimer in the documentation
|
|
and/or other materials provided with the distribution.
|
|
|
|
*Neither name of The Santa Cruz Operation nor the names of its contributors
|
|
may be used to endorse or promote products derived from this software
|
|
without specific prior written permission.
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS
|
|
IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT falseT LIMITED TO,
|
|
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
|
|
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
CONSEQUENTIAL DAMAGES (INCLUDING, BUT falseT LIMITED TO, PROCUREMENT OF
|
|
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
INTERRUPTION)
|
|
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
|
DAMAGE.
|
|
=========================================================================*/
|
|
|
|
/* cscope - interactive C symbol cross-reference
|
|
*
|
|
* terminal input functions
|
|
*/
|
|
|
|
#include "global.h"
|
|
#include <ncurses.h>
|
|
#include <setjmp.h> /* jmp_buf */
|
|
#include <stdlib.h>
|
|
#include <errno.h>
|
|
#if HAVE_SYS_TERMIOS_H
|
|
# include <sys/termios.h>
|
|
#endif
|
|
|
|
#include "keys.h"
|
|
|
|
extern const void *const winput;
|
|
extern const void *const wmode;
|
|
extern const void *const wresult;
|
|
extern const void *const *const current_window;
|
|
|
|
bool do_press_any_key = false;
|
|
|
|
static jmp_buf env; /* setjmp/longjmp buffer */
|
|
static int prevchar; /* previous, ungotten character */
|
|
|
|
/* Internal prototypes: */
|
|
static void catchint(int sig);
|
|
|
|
/* catch the interrupt signal */
|
|
|
|
static void catchint(int sig) {
|
|
UNUSED(sig);
|
|
|
|
signal(SIGINT, catchint);
|
|
longjmp(env, 1);
|
|
}
|
|
|
|
static inline bool rebuild_reference() {
|
|
if(isuptodate == true) {
|
|
postmsg("The -d option prevents rebuilding the symbol database");
|
|
return false;
|
|
}
|
|
exitcurses();
|
|
freefilelist(); /* remake the source file list */
|
|
makefilelist();
|
|
rebuild();
|
|
if(errorsfound == true) {
|
|
errorsfound = false;
|
|
askforreturn();
|
|
}
|
|
entercurses();
|
|
postmsg(""); /* clear any previous message */
|
|
totallines = 0;
|
|
disprefs = 0;
|
|
return true;
|
|
}
|
|
|
|
/* unget a character */
|
|
void myungetch(int c) {
|
|
prevchar = c;
|
|
}
|
|
|
|
/* ask user to enter a character after reading the message */
|
|
void askforchar(void) {
|
|
addstr("Type any character to continue: ");
|
|
getch();
|
|
}
|
|
|
|
/* ask user to press the RETURN key after reading the message */
|
|
void askforreturn(void) {
|
|
fprintf(stderr, "Press the RETURN key to continue: ");
|
|
getchar();
|
|
/* HBB 20060419: message probably messed up the screen --- redraw */
|
|
if(incurses == true) { redrawwin(curscr); }
|
|
}
|
|
|
|
/* expand the ~ and $ shell meta characters in a path */
|
|
void shellpath(char *out, int limit, char *in) {
|
|
char *lastchar;
|
|
char *s, *v;
|
|
|
|
/* skip leading white space */
|
|
while(isspace((unsigned char)*in)) {
|
|
++in;
|
|
}
|
|
lastchar = out + limit - 1;
|
|
|
|
/* a tilde (~) by itself represents $HOME; followed by a name it
|
|
represents the $LOGDIR of that login name */
|
|
if(*in == '~') {
|
|
*out++ = *in++; /* copy the ~ because it may not be expanded */
|
|
|
|
/* get the login name */
|
|
s = out;
|
|
while(s < lastchar && *in != '/' && *in != '\0' && !isspace((unsigned char)*in)) {
|
|
*s++ = *in++;
|
|
}
|
|
*s = '\0';
|
|
|
|
/* if the login name is null, then use $HOME */
|
|
if(*out == '\0') {
|
|
v = getenv("HOME");
|
|
} else { /* get the home directory of the login name */
|
|
v = logdir(out);
|
|
}
|
|
/* copy the directory name if it isn't too big */
|
|
if(v != NULL && strlen(v) < (lastchar - out)) {
|
|
strcpy(out - 1, v);
|
|
out += strlen(v) - 1;
|
|
} else {
|
|
/* login not found, so ~ must be part of the file name */
|
|
out += strlen(out);
|
|
}
|
|
}
|
|
/* get the rest of the path */
|
|
while(out < lastchar && *in != '\0' && !isspace((unsigned char)*in)) {
|
|
|
|
/* look for an environment variable */
|
|
if(*in == '$') {
|
|
*out++ = *in++; /* copy the $ because it may not be expanded */
|
|
|
|
/* get the variable name */
|
|
s = out;
|
|
while(s < lastchar && *in != '/' && *in != '\0' &&
|
|
!isspace((unsigned char)*in)) {
|
|
*s++ = *in++;
|
|
}
|
|
*s = '\0';
|
|
|
|
/* get its value, but only it isn't too big */
|
|
if((v = getenv(out)) != NULL && strlen(v) < (lastchar - out)) {
|
|
strcpy(out - 1, v);
|
|
out += strlen(v) - 1;
|
|
} else {
|
|
/* var not found, or too big, so assume $ must be part of the
|
|
* file name */
|
|
out += strlen(out);
|
|
}
|
|
} else { /* ordinary character */
|
|
*out++ = *in++;
|
|
}
|
|
}
|
|
*out = '\0';
|
|
}
|
|
|
|
static int wmode_input(const int c) {
|
|
switch(c) {
|
|
case KEY_ENTER:
|
|
case '\r':
|
|
case '\n':
|
|
case ctrl('N'): /* go to next input field */
|
|
case KEY_DOWN:
|
|
case KEY_RIGHT:
|
|
field = (field + 1) % FIELDS;
|
|
break;
|
|
case ctrl('P'): /* go to previous input field */
|
|
case KEY_UP:
|
|
case KEY_LEFT:
|
|
field = (field + (FIELDS - 1)) % FIELDS;
|
|
break;
|
|
case KEY_HOME: /* go to first input field */
|
|
field = 0;
|
|
break;
|
|
case KEY_LL: /* go to last input field */
|
|
curdispline = disprefs;
|
|
break;
|
|
default:
|
|
return 0;
|
|
}
|
|
|
|
window_change |= CH_MODE;
|
|
return 1;
|
|
}
|
|
|
|
static int wresult_input(const int c) {
|
|
switch(c) {
|
|
case KEY_ENTER: /* open for editing */
|
|
case '\r':
|
|
case '\n':
|
|
editref(curdispline);
|
|
window_change = CH_ALL;
|
|
break;
|
|
case ctrl('N'):
|
|
case KEY_DOWN:
|
|
case KEY_RIGHT:
|
|
if((curdispline + 1) < disprefs) { ++curdispline; }
|
|
break;
|
|
case ctrl('P'):
|
|
case KEY_UP:
|
|
case KEY_LEFT:
|
|
if(curdispline) { --curdispline; }
|
|
break;
|
|
case KEY_HOME:
|
|
curdispline = 0;
|
|
break;
|
|
case KEY_LL:
|
|
field = FIELDS - 1;
|
|
break;
|
|
default:
|
|
if(c > mdisprefs) { goto noredisp; }
|
|
const int pos = dispchar2int(c);
|
|
if(pos > -1) { editref(pos); }
|
|
goto noredisp;
|
|
}
|
|
|
|
window_change |= CH_RESULT;
|
|
noredisp:
|
|
return 1;
|
|
}
|
|
|
|
static int global_input(const int c) {
|
|
switch(c) {
|
|
case '\t':
|
|
horswp_window();
|
|
break;
|
|
case '%':
|
|
verswp_window();
|
|
break;
|
|
case ctrl('R'):
|
|
rebuild_reference();
|
|
break;
|
|
case ctrl('K'):
|
|
field = (field + (FIELDS - 1)) % FIELDS;
|
|
window_change |= CH_MODE;
|
|
break;
|
|
case ctrl('J'):
|
|
field = (field + 1) % FIELDS;
|
|
window_change |= CH_MODE;
|
|
break;
|
|
case ctrl('H'): /* display previous page */
|
|
case '-':
|
|
case KEY_PPAGE:
|
|
if(totallines == 0) { return 0; } /* don't redisplay if there are no lines */
|
|
curdispline = 0;
|
|
if(current_page > 0) {
|
|
--current_page;
|
|
window_change |= CH_RESULT;
|
|
}
|
|
break;
|
|
case '+':
|
|
case KEY_NPAGE:
|
|
if(totallines == 0) { return 0; } /* don't redisplay if there are no lines */
|
|
curdispline = 0;
|
|
++current_page;
|
|
window_change |= CH_RESULT;
|
|
break;
|
|
case '>': /* write or append the lines to a file */
|
|
if (totallines == 0) {
|
|
postmsg("There are no lines to write to a file");
|
|
break;
|
|
}
|
|
input_mode = INPUT_APPEND;
|
|
window_change |= CH_INPUT;
|
|
force_window();
|
|
break;
|
|
case '<': /* read lines from a file */
|
|
input_mode = INPUT_READ;
|
|
window_change |= CH_INPUT;
|
|
force_window();
|
|
break;
|
|
case '|': /* pipe the lines to a shell command */
|
|
case '^':
|
|
break; // XXX fix
|
|
if(totallines == 0) {
|
|
postmsg("There are no lines to pipe to a shell command");
|
|
return 0;
|
|
}
|
|
/* get the shell command */
|
|
// move(PRLINE, 0);
|
|
// addstr(pipeprompt);
|
|
// if (mygetline("", newpat, COLS - sizeof(pipeprompt), '\0', NO) == 0) {
|
|
// clearprompt();
|
|
// return(NO);
|
|
// }
|
|
///* if the ^ command, redirect output to a temp file */
|
|
// if (commandc == '^') {
|
|
// strcat(strcat(newpat, " >"), temp2);
|
|
// /* HBB 20020708: somebody might have even
|
|
// * their non-interactive default shells
|
|
// * complain about clobbering
|
|
// * redirections... --> delete before
|
|
// * overwriting */
|
|
// remove(temp2);
|
|
// }
|
|
// exitcurses();
|
|
// if ((file = mypopen(newpat, "w")) == NULL) {
|
|
// fprintf(stderr, "cscope: cannot open pipe to shell command: %s\n",
|
|
// newpat);
|
|
// } else {
|
|
// seekline(1);
|
|
// while ((c = getc(refsfound)) != EOF) {
|
|
// putc(c, file);
|
|
// }
|
|
// seekline(topline);
|
|
// mypclose(file);
|
|
// }
|
|
// if (commandc == '^') {
|
|
// if (readrefs(temp2) == NO) {
|
|
// postmsg("Ignoring empty output of ^ command");
|
|
// }
|
|
// }
|
|
// askforreturn();
|
|
// entercurses();
|
|
break;
|
|
case '!': /* shell escape */
|
|
execute(shell, shell, NULL);
|
|
current_page = 0;
|
|
break;
|
|
case ctrl('U'): /* redraw screen */
|
|
case KEY_CLEAR:
|
|
window_change = CH_ALL;
|
|
break;
|
|
case '?': /* help */
|
|
window_change = CH_HELP;
|
|
break;
|
|
case ctrl('E'): /* edit all lines */
|
|
editall();
|
|
break;
|
|
case ctrl('S'): // toggle caseless
|
|
caseless = !caseless;
|
|
egrepcaseless(caseless);
|
|
window_change |= CH_CASE;
|
|
break;
|
|
case EOF:
|
|
myexit(0);
|
|
break;
|
|
default:
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
int change_input(const int c) {
|
|
MOUSE *p; /* mouse data */
|
|
|
|
switch(c) {
|
|
case '*': /* invert page */
|
|
for(unsigned i = 0; i < (nextline-1); i++) {
|
|
change[topref + i] = !change[topref + i];
|
|
}
|
|
window_change |= CH_RESULT;
|
|
break;
|
|
case ctrl('A'): /* invert all lines */
|
|
for(unsigned i = 0; i < totallines; i++) {
|
|
change[i] = !change[i];
|
|
}
|
|
window_change |= CH_RESULT;
|
|
break;
|
|
case ctrl('X'): /* mouse selection */
|
|
if((p = getmouseaction(DUMMYCHAR)) == NULL) {
|
|
break; /* unknown control sequence */
|
|
}
|
|
/* if the button number is a scrollbar tag */
|
|
if(p->button == '0') {
|
|
// scrollbar(p);
|
|
break;
|
|
}
|
|
/* find the selected line */
|
|
/* NOTE: the selection is forced into range */
|
|
{
|
|
int i;
|
|
for(i = disprefs - 1; i > 0; --i) {
|
|
if(p->y1 >= displine[i]) { break; }
|
|
}
|
|
change[i] = !change[i];
|
|
}
|
|
break;
|
|
case ctrl('D'):
|
|
changestring(input_line, newpat, change, totallines);
|
|
free(change);
|
|
change = NULL;
|
|
input_mode = INPUT_NORMAL;
|
|
horswp_window();
|
|
rebuild_reference();
|
|
search(newpat);
|
|
break;
|
|
default:
|
|
{
|
|
/* if a line was selected */
|
|
const int cc = dispchar2int(c);
|
|
if(cc != -1 && cc < totallines) {
|
|
change[cc] = !change[cc];
|
|
window_change |= CH_RESULT;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// XXX: move this
|
|
int changestring(const char *from, const char *to, const bool *const change, const int change_len) {
|
|
char newfile[PATHLEN + 1]; /* new file name */
|
|
char oldfile[PATHLEN + 1]; /* old file name */
|
|
char linenum[NUMLEN + 1]; /* file line number */
|
|
char msg[MSGLEN + 1]; /* message */
|
|
FILE *script; /* shell script file */
|
|
|
|
|
|
// Return early
|
|
bool anymarked = false; /* any line marked */
|
|
for(int i = 0; i < change_len; i++) {
|
|
if(change[i]) {
|
|
anymarked = true;
|
|
break;
|
|
}
|
|
}
|
|
if(!anymarked) { return false; }
|
|
|
|
/* open the temporary file */
|
|
if((script = myfopen(temp2, "w")) == NULL) {
|
|
cannotopen(temp2);
|
|
return (false);
|
|
}
|
|
|
|
/* for each line containing the old text */
|
|
fprintf(script, "ed - <<\\!\n");
|
|
*oldfile = '\0';
|
|
fseek(refsfound, 0, SEEK_SET);
|
|
for(int i = 0; fscanf(refsfound,
|
|
"%" PATHLEN_STR "s%*s%" NUMLEN_STR "s%*[^\n]",
|
|
newfile,
|
|
linenum) == 2;
|
|
++i) {
|
|
/* see if the line is to be changed */
|
|
if(!change[i]) { break; }
|
|
|
|
/* if this is a new file */
|
|
if(strcmp(newfile, oldfile) != 0) {
|
|
|
|
/* make sure it can be changed */
|
|
if(access(newfile, WRITE) != 0) {
|
|
snprintf(msg, sizeof(msg), "Cannot write to file %s", newfile);
|
|
postmsg(msg);
|
|
goto end;
|
|
}
|
|
/* if there was an old file */
|
|
if(*oldfile != '\0') { fprintf(script, "w\n"); /* save it */ }
|
|
/* edit the new file */
|
|
strcpy(oldfile, newfile);
|
|
fprintf(script, "e %s\n", oldfile);
|
|
}
|
|
/* output substitute command */
|
|
fprintf(script, "%ss/", linenum); /* change */
|
|
for(const char *s = from; *s != '\0'; ++s) {
|
|
/* old text */
|
|
if(strchr("/\\[.^*", *s) != NULL) { putc('\\', script); }
|
|
if(caseless == true && isalpha((unsigned char)*s)) {
|
|
putc('[', script);
|
|
if(islower((unsigned char)*s)) {
|
|
putc(toupper((unsigned char)*s), script);
|
|
putc(*s, script);
|
|
} else {
|
|
putc(*s, script);
|
|
putc(tolower((unsigned char)*s), script);
|
|
}
|
|
putc(']', script);
|
|
} else {
|
|
putc(*s, script);
|
|
}
|
|
}
|
|
putc('/', script); /* to */
|
|
for(const char *s = to; *s != '\0'; ++s) { /* new text */
|
|
if(strchr("/\\&", *s) != NULL) { putc('\\', script); }
|
|
putc(*s, script);
|
|
}
|
|
fprintf(script, "/gp\n"); /* and print */
|
|
}
|
|
fprintf(script, "w\nq\n!\n"); /* write and quit */
|
|
fflush(script);
|
|
|
|
/* edit the files */
|
|
execute("sh", "sh", temp2, NULL); // XXX: this should not echo
|
|
end:
|
|
fclose(script);
|
|
return true;
|
|
}
|
|
|
|
int handle_input(const int c) {
|
|
/* - was wating for any input - */
|
|
if(do_press_any_key) {
|
|
do_press_any_key = false;
|
|
return 0;
|
|
}
|
|
/* - Resize - */
|
|
/* it's treated specially because curses treat it specially:
|
|
+ its valid without keypad()
|
|
+ as far as i can tell this is the only key that does not
|
|
flush after itself
|
|
*/
|
|
if(c == KEY_RESIZE) {
|
|
redisplay();
|
|
flushinp();
|
|
return 0;
|
|
}
|
|
|
|
/* --- global --- */
|
|
const int r = global_input(c);
|
|
if(r) { return 0; }
|
|
/* --- mode specific --- */
|
|
switch(input_mode) {
|
|
case INPUT_NORMAL:
|
|
if(*current_window == winput) {
|
|
return interpret(c);
|
|
} else if(*current_window == wmode) {
|
|
return wmode_input(c);
|
|
} else if(*current_window == wresult) {
|
|
return wresult_input(c);
|
|
}
|
|
assert("'current_window' dangling.");
|
|
break; /* NOTREACHED */
|
|
case INPUT_CHANGE_TO:
|
|
case INPUT_APPEND:
|
|
case INPUT_READ:
|
|
return interpret(c);
|
|
case INPUT_CHANGE:
|
|
return change_input(c);
|
|
}
|
|
|
|
return 0;
|
|
}
|