/* 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 #endif #include #include #include #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 * ****************************************************************************/