/* -*- show-trailing-whitespace: t; indent-tabs: t -*-
* Copyright (c) 2003,2004,2005,2006 David Lichteblau
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <curses.h>
#include <term.h>
#include "common.h"
#include <readline/readline.h>
#include <readline/history.h>
int
carray_cmp(GArray *a, GArray *b)
{
int d = memcmp(a->data, b->data, MIN(a->len, b->len));
if (d) return d;
if (a->len < b->len)
return -1;
else if (a->len == b->len)
return 0;
else
return 1;
}
int
carray_ptr_cmp(const void *aa, const void *bb)
{
GArray *a = *((GArray **) aa);
GArray *b = *((GArray **) bb);
return carray_cmp(a ,b);
}
void
fdcp(int fdsrc, int fddst)
{
int n;
char buf[4096];
do {
if ( (n = read(fdsrc, buf, sizeof(buf))) == -1) syserr();
if (write(fddst, buf, n) != n) syserr();
} while (n);
}
void
cp(char *src, char *dst, off_t skip, int append)
{
int fdsrc, fddst;
int flags = append ? O_WRONLY | O_APPEND : O_CREAT | O_EXCL | O_WRONLY;
if ( (fdsrc = open(src, O_RDONLY)) == -1) syserr();
if (lseek(fdsrc, skip, SEEK_SET) == -1) syserr();
if ( (fddst = open(dst, flags, 0600)) == -1) syserr();
fdcp(fdsrc, fddst);
if (close(fdsrc) == -1) syserr();
if (close(fddst) == -1) syserr();
}
void
fcopy(FILE *src, FILE *dst)
{
int n;
char buf[4096];
for (;;) {
if ( (n = fread(buf, 1, sizeof(buf), src)) == 0) {
if (feof(src)) break;
syserr();
}
if (fwrite(buf, 1, n, dst) != n) syserr();
}
}
static void
print_charbag(char *charbag)
{
int i;
putchar('[');
for (i = 0; charbag[i]; i++) {
char c = charbag[i];
if (c > 32)
putchar(c);
}
putchar(']');
}
char
choose(char *prompt, char *charbag, char *help)
{
struct termios term;
int c;
if (tcgetattr(0, &term) == -1) syserr();
term.c_lflag &= ~ICANON;
term.c_cc[VMIN] = 1;
term.c_cc[VTIME] = 0;
for (;;) {
if (tcsetattr(0, TCSANOW, &term) == -1) syserr();
fputs(prompt, stdout);
putchar(' ');
print_charbag(charbag);
putchar(' ');
if (strchr(charbag, c = getchar()))
break;
fputs("\nPlease enter one of ", stdout);
print_charbag(charbag);
putchar('\n');
if (help) printf(" %s", help);
putchar('\n');
}
term.c_lflag |= ICANON;
if (tcsetattr(0, TCSANOW, &term) == -1) syserr();
putchar('\n');
return c;
}
static long
line_number(char *pathname, long pos)
{
FILE *f;
long line = 1;
int c;
if ( !(f = fopen(pathname, "r+"))) syserr();
while (pos > 0) {
switch ( c = getc_unlocked(f)) {
case EOF:
goto done;
case '\n':
if ( (c = getc_unlocked(f)) != EOF) {
ungetc(c, f);
line++;
}
/* fall through */
default:
pos--;
}
}
done:
if (fclose(f) == EOF) syserr();
return line;
}
void
edit(char *pathname, long line)
{
int childpid;
int status;
char *vi;
vi = getenv("VISUAL");
if (!vi) vi = getenv("EDITOR");
if (!vi) vi = "vi";
switch ( (childpid = fork())) {
case -1:
syserr();
case 0:
if (line > 0) {
char buf[20];
snprintf(buf, 20, "+%ld", line);
execlp(vi, vi, buf, pathname, 0);
} else
execlp(vi, vi, pathname, 0);
syserr();
}
if (waitpid(childpid, &status, 0) == -1) syserr();
if (!WIFEXITED(status) || WEXITSTATUS(status))
yourfault("editor died");
}
void
edit_pos(char *pathname, long pos)
{
edit(pathname, pos > 0 ? line_number(pathname, pos) : -1);
}
static int
invalidp(char *ti)
{
return ti == 0 || ti == (char *) -1;
}
void
view(char *pathname)
{
int childpid;
int status;
char *pg;
char *clear = tigetstr("clear");
pg = getenv("PAGER");
if (!pg) pg = "less";
if (!invalidp(clear))
putp(clear);
switch ( (childpid = fork())) {
case -1:
syserr();
case 0:
execlp(pg, pg, pathname, 0);
syserr();
}
if (waitpid(childpid, &status, 0) == -1) syserr();
if (!WIFEXITED(status) || WEXITSTATUS(status))
puts("pager died");
}
int
pipeview(int *fd)
{
int childpid;
char *pg;
char *clear = tigetstr("clear");
int fds[2];
pg = getenv("PAGER");
if (!pg) pg = "less";
if (!invalidp(clear))
putp(clear);
if (pipe(fds) == -1) syserr();
switch ( (childpid = fork())) {
case -1:
syserr();
case 0:
close(fds[1]);
dup2(fds[0], 0);
close(fds[0]);
execlp(pg, pg, 0);
syserr();
}
close(fds[0]);
*fd = fds[1];
return childpid;
}
void
pipeview_wait(int childpid)
{
int status;
if (waitpid(childpid, &status, 0) == -1) syserr();
if (!WIFEXITED(status) || WEXITSTATUS(status))
puts("pager died");
}
char *
home_filename(char *name)
{
char *home = getenv("HOME");
int n;
char *result;
if (!home) {
fputs("Warning: You don't have a $HOME.\n", stderr);
return 0;
}
n = strlen(home);
result = xalloc(n + 1 + strlen(name) + 1);
strcpy(result, home);
result[n] = '/';
strcpy(result + n + 1, name);
return result;
}
static char *
history_filename()
{
return home_filename(".ldapvi_history");
}
void
read_ldapvi_history()
{
char *filename = history_filename();
using_history();
if (!filename)
return;
if (read_history(filename) && errno != ENOENT)
perror("Oops, couldn't read history");
free(filename);
}
void
write_ldapvi_history()
{
char *filename = history_filename();
if (!filename)
return;
if (write_history(filename))
perror("Oops, couldn't write history");
free(filename);
}
char *
getline(char *prompt, char *value)
{
tdialog d;
init_dialog(&d, DIALOG_DEFAULT, prompt, value);
dialog(0, &d, 1, 0);
return d.value ? d.value : xdup("");
}
char *
get_password()
{
tdialog d;
init_dialog(&d, DIALOG_PASSWORD, "Password: ", "");
dialog(0, &d, 1, 0);
return d.value ? d.value : xdup("");
}
static char *readline_default;
static int
cb_set_readline_default()
{
rl_insert_text(readline_default);
return 0;
}
void
display_password(void)
{
int i;
char *backup = xalloc(rl_end + 1);
strncpy(backup, rl_line_buffer, rl_end);
for (i = 0; i < rl_end; i++)
rl_line_buffer[i] = '*';
rl_redisplay();
strncpy(rl_line_buffer, backup, rl_end);
}
static char *
getline2(char *prompt, char *value, int password, int history)
{
char *str;
if (password)
rl_redisplay_function = display_password;
readline_default = value;
rl_startup_hook = cb_set_readline_default;
str = readline(prompt);
rl_startup_hook = 0;
if (password)
rl_redisplay_function = rl_redisplay;
if (str && *str && history)
add_history(str);
return str;
}
void
init_dialog(tdialog *d, enum dialog_mode mode, char *prompt, char *value)
{
d->mode = mode;
d->prompt = prompt;
d->value = value;
}
char *
append(char *a, char *b)
{
int k = strlen(a);
char *result = xalloc(k + strlen(b) + 1);
strcpy(result, a);
strcpy(result + k, b);
return result;
}
void *
xalloc(size_t size)
{
void *result = malloc(size);
if (!result) {
write(2, "\nmalloc error\n", sizeof("\nmalloc error\n") - 1);
_exit(2);
}
return result;
}
char *
xdup(char *str)
{
char *result;
if (!str)
return str;
if (!(result = strdup(str))) {
write(2, "\nstrdup error\n", sizeof("\nstrdup error\n") - 1);
_exit(2);
}
return result;
}
int
adjoin_str(GPtrArray *strs, char *str)
{
int i;
for (i = 0; i < strs->len; i++)
if (!strcmp(str, g_ptr_array_index(strs, i)))
return -1;
g_ptr_array_add(strs, str);
return i;
}
int
adjoin_ptr(GPtrArray *a, void *p)
{
int i;
for (i = 0; i < a->len; i++)
if (g_ptr_array_index(a, i) == p)
return -1;
g_ptr_array_add(a, p);
return i;
}
void
dumb_dialog(tdialog *d, int n)
{
GString *prompt = g_string_new("");
int i;
for (i = 0; i < n; i++) {
g_string_assign(prompt, d[i].prompt);
g_string_append(prompt, ": ");
switch (d[i].mode) {
case DIALOG_DEFAULT:
d[i].value = getline2(prompt->str, d[i].value, 0, 1);
break;
case DIALOG_PASSWORD:
d[i].value = getline2(prompt->str, d[i].value, 1, 0);
break;
case DIALOG_CHALLENGE:
printf("%s: %s\n", prompt->str, d[i].value);
break;
}
}
g_string_free(prompt, 1);
}
enum dialog_rc {
dialog_continue, dialog_done, dialog_goto, dialog_relative,
dialog_help, dialog_clear
};
static Keymap dialog_keymap = 0;
static Keymap dialog_empty_keymap = 0;
static enum dialog_rc dialog_action;
static int dialog_next;
static int
cb_view_pre_input()
{
rl_done = 1;
return 0;
}
static int
cb_dialog_done(int a, int b)
{
rl_done = 1;
dialog_action = dialog_done;
return 42;
}
static int
cb_dialog_goto(int a, int b)
{
rl_done = 1;
dialog_action = dialog_goto;
dialog_next = a - 1;
return 42;
}
static int
cb_dialog_prev(int a, int b)
{
rl_done = 1;
dialog_action = dialog_relative;
dialog_next = - 1;
return 42;
}
static int
cb_dialog_next(int a, int b)
{
rl_done = 1;
dialog_action = dialog_relative;
dialog_next = 1;
return 42;
}
static int
cb_dialog_help(int a, int b)
{
rl_done = 1;
dialog_action = dialog_help;
return 42;
}
static int
cb_dialog_clear(int a, int b)
{
rl_done = 1;
dialog_action = dialog_clear;
return 42;
}
#define DIALOG_HELP \
"\nEdit the lines above using standard readline commands.\n" \
"Use RET to edit each line in turn.\n" \
"\n" \
"Special keys:\n" \
" M-RET Finish the dialog immediately.\n" \
" C-p Go back to the previous line.\n" \
" C-n Go to the next line (alias for RET).\n" \
" M-g With numeric prefix, go to the specified line.\n" \
"\n" \
"Non-password lines are saved in the history. Standard readline\n" \
"bindings for history access include:\n" \
" C-r Incremental search through history.\n" \
" <up>/<down> Previous/next history entry.\n"
static void
dialog_rebuild(char *up, char *clreos,
char *header, char **prompts, tdialog *d, int n,
int target, int help)
{
int i;
putp(clreos);
if (header) {
putchar('\n');
fputs(header, stdout);
putchar('\n');
fputs("Type M-h for help on key bindings.", stdout);
putchar('\n');
putchar('\n');
}
rl_pre_input_hook = cb_view_pre_input;
for (i = 0; i < n; i++) {
int passwordp = d[i].mode == DIALOG_PASSWORD;
free(getline2(prompts[i], d[i].value, passwordp, 0));
putchar('\n');
}
rl_pre_input_hook = 0;
if (help) {
fputs(DIALOG_HELP, stdout);
for (i = 0; DIALOG_HELP[i]; i++)
if (DIALOG_HELP[i] == '\n')
putp(up);
}
for (i = 0; i < n - target; i++)
putp(up);
}
static Keymap
set_meta_keymap(Keymap keymap, Keymap meta_keymap)
{
if (!meta_keymap)
meta_keymap = rl_copy_keymap((Keymap) keymap[27].function);
keymap[27].type = ISKMAP;
keymap[27].function = (rl_command_func_t *) meta_keymap;
}
static void
init_dialog_keymap(Keymap keymap)
{
Keymap meta_keymap = (Keymap) keymap[27].function;
rl_bind_key_in_map('L' - '@', cb_dialog_clear, keymap);
rl_bind_key_in_map('P' - '@', cb_dialog_prev, keymap);
rl_bind_key_in_map('N' - '@', cb_dialog_next, keymap);
rl_bind_key_in_map('\r', cb_dialog_done, meta_keymap);
rl_bind_key_in_map('g', cb_dialog_goto, meta_keymap);
rl_bind_key_in_map('h', cb_dialog_help, meta_keymap);
}
void
dialog(char *header, tdialog *d, int n, int start)
{
int i;
char *up = tigetstr("cuu1");
char *clreos = tigetstr("ed");
char *clear = tigetstr("clear");
#if 0
char *hsm = rl_variable_value("horizontal-scroll-mode");
#endif
char *hsm = "off";
Keymap original_keymap = rl_get_keymap();
int max = 0;
char **prompts;
if (n == 0)
return;
if (invalidp(up) || invalidp(clreos) || invalidp(clear)) {
puts("Dumb terminal. Using fallback dialog.");
dumb_dialog(d, n);
return;
}
if (!dialog_keymap) {
rl_initialize();
dialog_keymap = rl_copy_keymap(original_keymap);
dialog_empty_keymap = rl_make_bare_keymap();
set_meta_keymap(dialog_keymap, 0);
set_meta_keymap(dialog_empty_keymap, rl_make_bare_keymap());
init_dialog_keymap(dialog_keymap);
init_dialog_keymap(dialog_empty_keymap);
}
rl_variable_bind("horizontal-scroll-mode", "on");
rl_inhibit_completion = 1; /* fixme */
for (i = 0; i < n; i++)
max = MAX(max, strlen(d[i].prompt));
prompts = xalloc(sizeof(char *) * n);
for (i = 0; i < n; i++) {
char *prompt = d[i].prompt;
int len = strlen(prompt);
char *str = xalloc(max + 3);
memset(str, ' ', max);
strcpy(str + max - len, prompt);
strcpy(str + max, ": ");
prompts[i] = str;
if (d[i].value)
d[i].value = xdup(d[i].value);
}
dialog_rebuild(up, clreos, header, prompts, d, n, start, 0);
i = start;
for (;;) {
char *orig = d[i].value;
int passwordp = d[i].mode == DIALOG_PASSWORD;
dialog_action = dialog_continue;
if (d[i].mode == DIALOG_CHALLENGE)
rl_set_keymap(dialog_empty_keymap);
else
rl_set_keymap(dialog_keymap);
d[i].value = getline2(prompts[i], orig, passwordp, !passwordp);
if (orig)
free(orig);
switch (dialog_action) {
case dialog_continue:
dialog_next = i + 1;
break;
case dialog_clear: /* fall through */
case dialog_help:
dialog_next = i;
break;
case dialog_relative:
dialog_next += i;
/* fall through */
case dialog_goto:
if (dialog_next < 0 || dialog_next >= n)
dialog_next = i;
break;
case dialog_done:
dialog_next = n;
break;
}
if (dialog_action == dialog_clear)
putp(clear);
else {
if (header)
i += 4;
if (dialog_action != dialog_continue)
i--;
do putp(up); while (i--);
}
dialog_rebuild(up, clreos, header, prompts, d, n, dialog_next,
dialog_action == dialog_help);
if (dialog_next >= n)
break;
i = dialog_next;
}
for (i = 0; i < n; i++)
free(prompts[i]);
free(prompts);
rl_set_keymap(original_keymap);
rl_variable_bind("horizontal-scroll-mode", hsm);
rl_inhibit_completion = 0;
}
syntax highlighted by Code2HTML, v. 0.9.1