/*
elmo - ELectronic Mail Operator
Copyright (C) 2003, 2004 rzyjontko
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; version 2.
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.
----------------------------------------------------------------------
*/
/****************************************************************************
* IMPLEMENTATION HEADERS
****************************************************************************/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <string.h>
#include <dirent.h>
#include <ctype.h>
#include "ecurses.h"
#include "clock.h"
#include "xmalloc.h"
#include "cmd.h"
#include "read.h"
#include "choose.h"
#include "file.h"
#include "abook.h"
#include "interface.h"
/****************************************************************************
* IMPLEMENTATION PRIVATE DEFINITIONS / ENUMERATIONS / SIMPLE TYPEDEFS
****************************************************************************/
#define CURSOR_AT_END() (buf.pos == buf.len)
#define CURSOR_AT_BEG() (buf.pos == 0)
#define BEEP() do { fprintf (stderr, "%c", '\a'); } while (0)
#define INITIAL_SIZE COLS
/**
* This is a 2-bit value. The higher bit is used as a boolean value
* that says if we need right dots, and the lower one says if we need
* left dots.
*/
enum dots {DOTS_NONE = 0,
DOTS_LEFT = 1,
DOTS_RIGHT = 2,
DOTS_BOTH = 3};
/****************************************************************************
* IMPLEMENTATION PRIVATE CLASS PROTOTYPES / EXTERNAL CLASS REFERENCES
****************************************************************************/
/****************************************************************************
* IMPLEMENTATION PRIVATE STRUCTURES / UTILITY CLASSES
****************************************************************************/
struct buf {
char *buf;
int size;
int len;
int pos;
};
/****************************************************************************
* IMPLEMENTATION REQUIRED EXTERNAL REFERENCES (AVOID)
****************************************************************************/
/****************************************************************************
* IMPLEMENTATION PRIVATE DATA
****************************************************************************/
/* The completeion mode is used when obtaining possible completions. */
static complete_mode_t mode = COMPLETE_NONE;
/* This controls wheter we should hide the string, and how. */
static hide_mode_t hide = HIDE_NO;
/* The length of the prompt string. */
static int prompt_len = 0;
/* Indicates if the user has cancelled or accepted the input. */
static int cancel = 0;
static int accept = 0;
/* This flag is used to check the meta-flag of the key. Meta-keys send
two characters. First one is 27 (ESC), the second is the real key.
This is why you have to press ESC twice. */
static int escape = 0;
/* Indicates if user has already pressed tab, because the completions
are shown after second tab-press. */
static int tab = 0;
/* The buffer stores the input string whith all neccessary information:
position, length and size. */
static struct buf buf;
/* These are used in completion. */
static char *compl_string = NULL;
static rstring_t *compl_items = NULL;
/****************************************************************************
* INTERFACE DATA
****************************************************************************/
/****************************************************************************
* IMPLEMENTATION PRIVATE FUNCTION PROTOTYPES
****************************************************************************/
/****************************************************************************
* BUFFER MANIPULATION FUNCTIONS
****************************************************************************/
static void
buf_init (void)
{
if (buf.size == 0){
buf.size = INITIAL_SIZE;
buf.buf = xmalloc (buf.size);
}
buf.len = 0;
buf.pos = 0;
buf.buf[0] = '\0';
}
static void
buf_destroy (void)
{
if (buf.size)
xfree (buf.buf);
buf.buf = NULL;
buf.size = 0;
}
/**
* makes buffer big enough to hold LEN more bytes
*/
static void
buf_reallocate (int len)
{
while (buf.len + len + 1 > buf.size){
buf.size *= 2;
}
buf.buf = xrealloc (buf.buf, buf.size);
}
/**
* moves all after position LEN bytes forward
*/
static void
buf_move (int len)
{
if (buf.len == buf.pos)
return;
memmove (buf.buf + buf.pos + len, buf.buf + buf.pos,
buf.len - buf.pos + 1);
}
static void
buf_put_str_len (const char *str, int len)
{
if (str == NULL || len <= 0)
return;
buf_reallocate (len);
buf_move (len);
memcpy (buf.buf + buf.pos, str, len);
buf.len += len;
buf.pos += len;
buf.buf[buf.len] = '\0';
}
static void
buf_put_str (const char *str)
{
int len;
if (str == NULL)
return;
len = strlen (str);
buf_put_str_len (str, len);
}
static void
buf_put_char (int c)
{
if (c == '\0')
return;
buf_reallocate (1);
buf_move (1);
buf.buf[buf.pos] = c;
buf.len++;
buf.pos++;
buf.buf[buf.len] = '\0';
}
static void
buf_del_chars_backward (int len)
{
if (len > buf.pos)
len = buf.pos;
memmove (buf.buf + buf.pos - len, buf.buf + buf.pos,
buf.len - buf.pos);
buf.len -= len;
buf.pos -= len;
buf.buf[buf.len] = '\0';
}
static void
buf_del_chars_forward (int len)
{
if (len > buf.len - buf.pos)
len = buf.len - buf.pos;
memmove (buf.buf + buf.pos, buf.buf + buf.pos + len,
buf.len - buf.pos - len);
buf.len -= len;
buf.buf[buf.len] = '\0';
}
/****************************************************************************
* SCREEN FUNCTIONS
****************************************************************************/
static enum dots
which_dots (int echo_size, int offset)
{
enum dots result = DOTS_NONE;
if (buf.pos > echo_size)
result |= DOTS_LEFT;
if (buf.len - offset > echo_size)
result |= DOTS_RIGHT;
return result;
}
static int
calculate_offset (int echo_size)
{
int result = 0;
while (buf.pos - result > echo_size)
result += echo_size;
return result;
}
static void
echo_input (void)
{
int x, y;
int echo_size;
int offset;
int dots_off = 0;
enum dots dots;
getmaxyx (cmd_win, y, x);
echo_size = x - prompt_len - 2 * 3;
offset = calculate_offset (echo_size);
dots = which_dots (echo_size, offset);
wmove (cmd_win, 0, prompt_len);
wclrtoeol (cmd_win);
if (hide == HIDE_EMPTY)
return;
if (dots & DOTS_LEFT){
window_addstr (cmd_win, "...");
dots_off = 3;
}
if (hide == HIDE_NO)
window_addnstr (cmd_win, buf.buf + offset, echo_size);
else
window_addnast (cmd_win, buf.buf + offset, echo_size);
if (dots & DOTS_RIGHT){
window_addstr (cmd_win, "...");
}
wmove (cmd_win, 0, prompt_len + buf.pos - offset + dots_off);
}
/****************************************************************************
* EDITING FUNCTIONS
****************************************************************************/
static void
put_char (int c)
{
if (! isprint (c)){
BEEP ();
return;
}
buf_put_char (c);
}
/****************************************************************************
* COMPLETION FUNCTIONS
****************************************************************************/
static void
change_one_item (void)
{
if (compl_items == NULL)
return;
if (compl_items->count == 1){
compl_string = xstrdup (compl_items->array[0]);
rstring_delete (compl_items);
compl_items = NULL;
}
}
static int
is_prefix (const char *a, const char *b)
{
while (*a){
if (*a != *b)
return 0;
a++;
b++;
}
return 1;
}
static int
select_fun (const struct dirent *entry)
{
int ret;
char c = buf.buf[buf.pos];
char *fname;
buf.buf[buf.pos] = '\0';
fname = strrchr (buf.buf, '/');
if (fname == NULL)
fname = buf.buf;
else
fname++;
if (*fname != '.' && *entry->d_name == '.'){
buf.buf[buf.pos] = c;
return 0;
}
ret = is_prefix (fname, entry->d_name);
buf.buf[buf.pos] = c;
return ret;
}
static void
complete_prepare_funs (void)
{
compl_items = exec_get_functions (buf.buf);
change_one_item ();
}
static void
complete_prepare_files (void)
{
char c = buf.buf[buf.pos];
char *dir;
char *ndir;
char *seek;
buf.buf[buf.pos] = '\0';
seek = strrchr (buf.buf, '/');
buf.buf[buf.pos] = c;
if (seek){
dir = xmalloc (seek - buf.buf + 2);
memcpy (dir, buf.buf, seek - buf.buf + 1);
dir[seek - buf.buf + 1] = '\0';
if (*dir == '~'){
ndir = file_expand_tilde (dir);
xfree (dir);
dir = ndir;
}
}
else {
dir = xstrdup (".");
}
compl_items = file_dir_items (dir, select_fun);
xfree (dir);
change_one_item ();
}
static void
complete_prepare_addrs (void)
{
char c = buf.buf[buf.pos];
char *seek;
buf.buf[buf.pos] = '\0';
seek = strrchr (buf.buf, ',');
buf.buf[buf.pos] = c;
if (seek){
do
seek++;
while (isspace (*seek));
}
else {
seek = buf.buf;
}
compl_items = abook_strings (seek);
change_one_item ();
}
static int
longest_common_prefix (rstring_t *vect)
{
int i;
int index = 0;
if (vect->count == 0)
return 0;
if (vect->count == 1)
return strlen (vect->array[0]);
while (vect->array[0][index]){
for (i = 1; i < vect->count; i++){
if (vect->array[0][index] != vect->array[i][index])
break;
}
if (i < vect->count)
break;
index++;
}
return index;
}
static void
replace_file (const char *str, int len)
{
char c = buf.buf[buf.pos];
char *fname;
buf.buf[buf.pos] = '\0';
fname = strrchr (buf.buf, '/');
buf.buf[buf.pos] = c;
if (fname)
fname++;
else
fname = buf.buf;
buf_del_chars_backward (buf.buf + buf.pos - fname);
buf_put_str_len (str, len);
}
static void
replace_fun (const char *str, int len)
{
buf_init ();
buf_put_str_len (str, len);
}
static void
replace_addr (const char *str, int len)
{
char c = buf.buf[buf.pos];
char *addr;
buf.buf[buf.pos] = '\0';
addr = strrchr (buf.buf, ',');
buf.buf[buf.pos] = c;
if (addr)
addr++;
else
addr = buf.buf;
buf_del_chars_backward (buf.buf + buf.pos - addr);
if (addr != buf.buf)
buf_put_char (' ');
buf_put_str_len (str, len);
}
static void
replace (const char *str, int len)
{
if (len == 0)
return;
switch (mode){
case COMPLETE_NONE:
break;
case COMPLETE_FILES:
replace_file (str, len);
break;
case COMPLETE_FUNS:
replace_fun (str, len);
break;
case COMPLETE_ADDRS:
replace_addr (str, len);
break;
}
}
static void
complete (void)
{
int len;
switch (mode){
case COMPLETE_NONE:
return;
case COMPLETE_FILES:
complete_prepare_files ();
break;
case COMPLETE_FUNS:
complete_prepare_funs ();
break;
case COMPLETE_ADDRS:
complete_prepare_addrs ();
break;
}
if (compl_string){
replace (compl_string, strlen (compl_string));
xfree (compl_string);
compl_string = NULL;
}
else if (compl_items){
fprintf (stderr, "%c", '\a');
len = longest_common_prefix (compl_items);
replace (compl_items->array[0], len);
tab = 1;
}
else {
BEEP ();
}
}
static void
choose_wrapper (void (*fun)(void))
{
int y, x;
getyx (cmd_win, y, x);
fun ();
wmove (cmd_win, 0, x);
}
static void
leave_choose (void)
{
if (tab){
tab = 0;
choose_wrapper (choose_close);
}
if (compl_string)
xfree (compl_string);
if (compl_items)
rstring_delete (compl_items);
compl_string = NULL;
compl_items = NULL;
}
/****************************************************************************
* IMPLEMENTATION PRIVATE FUNCTIONS
****************************************************************************/
static void
write_prompt (const char *prompt, const char *def)
{
int y;
window_mvaddstr (cmd_win, 0, 0, prompt);
getyx (cmd_win, y, prompt_len);
if (def)
window_addstr (cmd_win, def);
}
static void
get_string (void)
{
int meta;
int c;
while (1){
meta = 0;
c = wgetch (cmd_win);
if (c == 27){
meta = 1;
c = wgetch (cmd_win);
}
if (keymap_action (keymaps + CMD_READ, c, meta)){
leave_choose ();
put_char (c);
}
echo_input ();
if (cancel || accept)
break;
}
}
static void
clear_win (void)
{
cmd_text_color ();
wmove (cmd_win, 0, 0);
wclrtoeol (cmd_win);
wnoutrefresh (cmd_win);
}
static int
word_backward_len (void)
{
char *seek = buf.buf + buf.pos;
while (seek > buf.buf && ! isalnum (*seek))
seek--;
while (seek > buf.buf && isalnum (*seek))
seek--;
return buf.pos - (seek - buf.buf);
}
static int
word_forward_len (void)
{
char *seek = buf.buf + buf.pos;
while (seek < buf.buf + buf.len && ! isalnum (*seek))
seek++;
while (seek < buf.buf + buf.len && isalnum (*seek))
seek++;
return (seek - buf.buf) - buf.pos;
}
/****************************************************************************
* INTERFACE FUNCTIONS
****************************************************************************/
void
read_abort (void)
{
BEEP ();
if (tab){
leave_choose ();
}
else {
clear_win ();
cancel = 1;
}
}
void
read_accept (void)
{
int index;
if (tab){
index = choose_index ();
replace (compl_items->array[index],
strlen (compl_items->array[index]));
leave_choose ();
}
else {
accept = 1;
}
}
void
read_backward_char (void)
{
if (tab){
read_choose_prev ();
}
else {
leave_choose ();
if (CURSOR_AT_BEG ()){
BEEP ();
return;
}
buf.pos--;
}
}
void
read_forward_char (void)
{
if (tab){
read_choose_next ();
}
else {
leave_choose ();
if (CURSOR_AT_END ()){
BEEP ();
return;
}
buf.pos++;
}
}
void
read_backward_word (void)
{
leave_choose ();
if (CURSOR_AT_BEG ()){
return;
}
buf.pos -= word_backward_len ();
}
void
read_forward_word (void)
{
leave_choose ();
if (CURSOR_AT_END ()){
return;
}
buf.pos += word_forward_len ();
}
void
read_begin (void)
{
if (tab){
read_choose_first ();
}
else {
leave_choose ();
buf.pos = 0;
}
}
void
read_end (void)
{
if (tab){
read_choose_last ();
}
else {
leave_choose ();
buf.pos = buf.len;
}
}
void
read_del_char_fwd (void)
{
leave_choose ();
if (CURSOR_AT_END ()){
BEEP ();
return;
}
buf_del_chars_forward (1);
}
void
read_del_char_back (void)
{
leave_choose ();
if (CURSOR_AT_BEG ()){
BEEP ();
return;
}
buf_del_chars_backward (1);
}
void
read_del_word_fwd (void)
{
int len;
leave_choose ();
if (CURSOR_AT_END ()){
return;
}
len = word_forward_len ();
buf_del_chars_forward (len);
}
void
read_del_word_back (void)
{
int len;
leave_choose ();
if (CURSOR_AT_BEG ()){
return;
}
len = word_backward_len ();
buf_del_chars_backward (len);
}
void
read_kill_line (void)
{
leave_choose ();
buf_del_chars_forward (buf.len - buf.pos);
}
void
read_complete (void)
{
int y, x;
if (mode == COMPLETE_NONE){
leave_choose ();
put_char ('\t');
}
else if (tab){
getyx (cmd_win, y, x);
choose_open (compl_items->count, compl_items->array);
wmove (cmd_win, 0, x);
}
else {
complete ();
}
}
void
read_choose_first (void)
{
choose_wrapper (choose_first);
}
void
read_choose_last (void)
{
choose_wrapper (choose_last);
}
void
read_choose_next (void)
{
choose_wrapper (choose_next);
}
void
read_choose_prev (void)
{
choose_wrapper (choose_prev);
}
void
read_prepare (const char *prompt, const char *def, complete_mode_t cmode,
hide_mode_t hmode)
{
buf_init ();
buf_put_str (def);
clear_win ();
curs_set (1);
write_prompt (prompt, def);
keymap_disable_default ();
mode = cmode;
hide = hmode;
tab = 0;
cancel = 0;
accept = 0;
escape = 0;
}
void
read_restore (void)
{
mode = COMPLETE_NONE;
hide = HIDE_NO;
keymap_enable_default ();
clear_win ();
curs_set (0);
}
void
read_put_char (int c)
{
put_char (c);
}
void
read_echo_input (void)
{
echo_input ();
}
char *
read_argument (const char *prompt, const char *def, complete_mode_t cmode,
hide_mode_t hmode)
{
read_prepare (prompt, def, cmode, hmode);
get_string ();
read_restore ();
if (cancel)
return NULL;
return buf.buf;
}
void
read_free_resources (void)
{
buf_destroy ();
}
/****************************************************************************
* INTERFACE CLASS BODIES
****************************************************************************/
/****************************************************************************
*
* END MODULE read.c
*
****************************************************************************/
syntax highlighted by Code2HTML, v. 0.9.1