/* -*- linux-c -*-
*
* Copyright (c) 2005 by Intel Corp.
*
* 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. This
* file and program are licensed under a BSD style license. See
* the Copying file included with the OpenHPI distribution for
* full licensing terms.
*
* Author(s):
* Kouzmich < Mikhail.V.Kouzmich@intel.com >
*
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <ctype.h>
#ifdef _WIN32
// TODO
#else
#include <fcntl.h>
#include <termios.h>
#endif
#include "hpi_cmd.h"
#define CTRL_A_KEY 0x01
#define CTRL_B_KEY 0x02
#define CTRL_D_KEY 0x04
#define CTRL_E_KEY 0x05
#define CTRL_F_KEY 0x06
#define CTRL_G_KEY 0x07
#define BELL_KEY 0x07
#define CTRL_H_KEY 0x08
#define TAB_KEY 0x09
#define NL_KEY 0x0A
#define CTRL_K_KEY 0x0B
#define CTRL_L_KEY 0x0C
#define CTRL_N_KEY 0x0E
#define CTRL_R_KEY 0x12
#define CTRL_S_KEY 0x13
#define CTRL1_KEY 0x1B
#define CTRL2_KEY 0x5B
#define BACKSP_KEY 0x7F
#define INSERT_KEY 0x32
#define DELETE_KEY 0x33
#define PGUP_KEY 0x35
#define PGDOWN_KEY 0x36
#define UP_KEY 0x41
#define DOWN_KEY 0x42
#define RIGHT_KEY 0x43
#define LEFT_KEY 0x44
#define END_KEY 0x46
#define HOME_KEY 0x48
#define HISTORY_DELTA 5
typedef struct {
int n_items;
int comp_len;
char **items;
} compl_t;
compl_t complition_struct;
int termfd = -1;
static char clear_buf[READ_BUF_SIZE];
static int no_stty = 1;
#ifdef _WIN32
// TODO
#else
static struct termios saved_termio;
#endif
static int is_insert_key = 0;
static char **History;
static int hist_ind = 0;
static int hist_size = 0;
static int current_hist_ind = -1;
void init_history(void)
{
History = (char **)malloc(sizeof(char *) * HISTORY_DELTA);
hist_size = HISTORY_DELTA;
memset(History, 0, sizeof(char *) * HISTORY_DELTA);
current_hist_ind = -1;
hist_ind = 0;
complition_struct.n_items = 0;
}
static void get_history_new(int new_cmd)
{
char **tmp;
if (current_hist_ind < 0) new_cmd = 1;
if ((current_hist_ind >= 0) &&
(*(History[current_hist_ind]) == 0)) {
hist_ind = current_hist_ind;
return;
};
if (new_cmd) current_hist_ind++;
else return;
if (current_hist_ind >= hist_size) {
hist_size += HISTORY_DELTA;
tmp = (char **)malloc(sizeof(char *) * hist_size);
memset(tmp, 0, sizeof(char *) * hist_size);
if (current_hist_ind > 1) {
memcpy(tmp, History,
sizeof(char *) * current_hist_ind);
free(History);
};
History = tmp;
};
hist_ind = current_hist_ind;
History[current_hist_ind] = (char *)malloc(1);
*(History[current_hist_ind]) = 0;
}
static void add_to_history(char *line, int index)
{
if (line == (char *)NULL) return;
if (strlen(line) == 0) return;
if (index > current_hist_ind) return;
if(strcmp(line, History[index]) == 0) return;
free(History[index]);
History[index] = strdup(line);
}
static char *get_history_next(char *str)
{
add_to_history(str, hist_ind);
if (current_hist_ind > hist_ind) hist_ind++;
else printf("%c", BELL_KEY);
return(History[hist_ind]);
}
static char *get_history_prev(char *str)
{
add_to_history(str, hist_ind);
hist_ind--;
if (hist_ind < 0) {
hist_ind = 0;
printf("%c", BELL_KEY);
};
return(History[hist_ind]);
}
static void go_to_begin(int index)
{
while (index-- > 0) printf("%c", '\b');
}
static int print_str_by_index(char *buf, int index, int cursor_pos)
// index - current cursor position
// cursor_pos - new cursor position,
// if cursor_pos == -1 set to the end of the buf
// return value: new cursor position
{
int n;
n = strlen(buf) - index;
if (n > 0)
printf("%s", buf + index);
if ((cursor_pos == -1) || (cursor_pos > strlen(buf)))
cursor_pos = strlen(buf);
n = strlen(buf) - cursor_pos;
go_to_begin(n);
return(cursor_pos);
}
static int clear_line(int index, int length)
{
go_to_begin(index);
memset(clear_buf, ' ', length);
clear_buf[length] = 0;
return(print_str_by_index(clear_buf, 0, 0));
}
static int add_char(char *buf, int length, int c, int index)
// return value : new corsor position
{
int i;
if (index >= length) {
buf[length++] = c;
return(length);
};
if ( ! is_insert_key)
for (i = length; i > index; i--) buf[i] = buf[i - 1];
buf[index] = c;
i = (is_insert_key) ? length - 1 : length;
print_str_by_index(buf, index, index + 1);
return(index + 1);
}
static int delete_char(char *buf, int length, int index, int as)
// as = 0 - backspace key, 1 - delete key
// return value : new corsor position
{
int n, ind;
if (index < 0) return(0);
if ((index == length) && as) return(length);
ind = (as) ? index : index - 1;
memcpy(buf + ind, buf + ind + 1, length - ind - 1);
buf[length - 1] = ' ';
if (as == 0) printf("%c", '\b');
n = print_str_by_index(buf, ind, ind);
buf[length - 1] = 0;
return(n);
}
static int find_cmd_by_text(char *text, int current_index, int forward)
{
int i, len, is_cmp = 0;
len = strlen(text);
if (len == 0) return(-1);
for (i = current_index; (i >= 0) && (i <= current_hist_ind);) {
if (strncmp(History[i], text, len) == 0) {
is_cmp = 1;
break;
};
if (forward) i++;
else i--;
};
if (is_cmp) return(i);
return(-1);
}
static int find_command(char *line, int curr_index, int forward)
{
int res, cmd_ind, len, ind, c, line_size;
char str[READ_BUF_SIZE];
char text[READ_BUF_SIZE];
len = strlen(line) + strlen(Title);
clear_line(len, len);
if (forward) {
if (curr_index == current_hist_ind) {
printf("%c", BELL_KEY);
return(curr_index);
}
} else {
if (curr_index <= 0) {
printf("%c", BELL_KEY);
return(0);
}
};
memset(text, 0, READ_BUF_SIZE);
len = 0;
ind = 0;
cmd_ind = curr_index;
for (;;) {
line_size = ind + strlen(History[cmd_ind]);
if (forward)
res = find_cmd_by_text(text, cmd_ind, 1);
else
res = find_cmd_by_text(text, cmd_ind, 0);
if (res != -1)
cmd_ind = res;
else
printf("%c", BELL_KEY);
if (forward)
snprintf(str, READ_BUF_SIZE,
"(i-search)`%s': ", text);
else
snprintf(str, READ_BUF_SIZE,
"(revers-i-search)`%s': ", text);
clear_line(ind, line_size);
ind = print_str_by_index(str, 0, -1);
print_str_by_index(History[cmd_ind], 0, 0);
c = getchar();
if (c == BACKSP_KEY) {
len--;
if (len < 0) len = 0;
text[len] = 0;
} else if ((c < ' ') || (c > 'z')) {
ungetc(c, stdin);
break;
};
text[len++] = c;
if (len >= READ_BUF_SIZE) break;
};
res = ind + strlen(History[cmd_ind]);
clear_line(ind, res);
return(cmd_ind);
}
static void check_compl(compl_t *compl_def)
{
int i, j, len;
char *str;
if (compl_def->n_items == 0) {
compl_def->comp_len = 0;
return;
};
if (compl_def->n_items == 1) {
compl_def->comp_len = strlen(compl_def->items[0]);
return;
};
str = compl_def->items[0];
len = strlen(str);
for (i = 1; i < compl_def->n_items; i++) {
for (j = len; j > 0; j--) {
if (strncmp(str, compl_def->items[i], j) == 0)
break;
};
if (j == 0) {
compl_def->comp_len = 0;
return;
};
len = j;
};
compl_def->comp_len = len;
}
static void add_to_compl(char *text, compl_t *compl_def)
{
char **tmp;
int n;
n = compl_def->n_items + 1;
tmp = (char **)malloc(sizeof(char *) * n);
if (compl_def->n_items > 0) {
memcpy(tmp, compl_def->items,
sizeof(char *) * compl_def->n_items);
free(compl_def->items);
};
compl_def->items = tmp;
tmp[compl_def->n_items] = strdup(text);
compl_def->n_items = n;
}
static int completion_func(int compl_type, char *text, compl_t *compl_def)
{
int i, len;
command_def_t *cmd = NULL;
if (compl_def == (compl_t *)NULL) return(0);
if (compl_def->n_items > 0) {
for (i = 0; i < compl_def->n_items; i++)
free(compl_def->items[i]);
free(compl_def->items);
compl_def->n_items = 0;
};
compl_def->comp_len = 0;
len = strlen(text);
switch (compl_type) {
case COMPL_CMD:
for (cmd = commands; cmd->cmd != NULL; cmd++) {
if ((cmd->type != MAIN_COM) &&
(cmd->type != block_type) &&
(cmd->type != UNDEF_COM))
continue;
if (strncmp(text, cmd->cmd, len) == 0)
add_to_compl(cmd->cmd, compl_def);
};
break;
case COMPL_NULL:
return(0);
};
check_compl(compl_def);
return(compl_def->n_items);
}
static int set_term_flags(void)
{
#ifdef _WIN32
// TODO
return 0;
#else
int res, c;
char name[1024];
struct termios termio;
if (no_stty == 0) return(0);
ctermid(name);
termfd = open(name, O_RDWR);
if (termfd < 0) {
printf("Can not open terminal\n");
return(1);
};
c = tcgetattr(termfd, &saved_termio);
if (c != 0) {
printf("Can not read terminal attrs\n");
return(1);
};
termio = saved_termio;
c = ICANON | ECHO | ECHOCTL;
c = ~c;
termio.c_lflag &= c;
termio.c_cc[VMIN] = 1;
termio.c_cc[VTIME] = 0;
res = tcsetattr(termfd, TCSANOW, &termio);
no_stty = 0;
return(res);
#endif
}
void restore_term_flags(void)
{
#ifdef _WIN32
// TODO
#else
if (no_stty) return;
tcsetattr(termfd, TCSANOW, &saved_termio);
no_stty = 1;
#endif
}
char *get_command_line(int new_cmd, int comp_type)
{
int c, ind = 0, len = 0, res;
char input_buf[READ_BUF_SIZE];
char *str;
if (set_term_flags() != 0)
exit(1);
get_history_new(new_cmd);
memset(input_buf, 0, READ_BUF_SIZE);
for (;;) {
c = getchar();
len = strlen(input_buf);
if (len >= (READ_BUF_SIZE - 1)) c = NL_KEY;
switch (c) {
case CTRL_A_KEY:
go_to_begin(ind);
ind = print_str_by_index(input_buf, 0, 0);
break;
case CTRL_B_KEY:
printf("%c", '\b');
ind--;
if (ind < 0) {
ind = 0;
printf("%c", ' ');
};
break;
case CTRL_D_KEY:
if (ind == len) break;
ind = delete_char(input_buf, len, ind, 1);
break;
case CTRL_E_KEY:
ind = print_str_by_index(input_buf, ind, len);
break;
case CTRL_G_KEY:
case CTRL_L_KEY:
printf("%c", c);
break;
case CTRL_F_KEY:
ind = print_str_by_index(input_buf, ind, ind + 1);
break;
case TAB_KEY:
res = completion_func(comp_type, input_buf,
&complition_struct);
if (res == 0) break;
if (res == 1) {
strcpy(input_buf, complition_struct.items[0]);
strcat(input_buf, " ");
ind = print_str_by_index(input_buf, ind, -1);
break;
};
memset(input_buf, 0, READ_BUF_SIZE);
strncpy(input_buf, complition_struct.items[0],
complition_struct.comp_len);
ind = print_str_by_index(input_buf, ind, -1);
break;
case NL_KEY:
printf("%c", c);
if (current_hist_ind != hist_ind)
add_to_history(input_buf, hist_ind);
if (strlen(input_buf) == 0) return("");
add_to_history(input_buf, current_hist_ind);
return(History[current_hist_ind]);
case CTRL_K_KEY:
clear_line(ind, len);
memset(input_buf + ind, 0, len - ind);
print_str_by_index(input_buf, 0, ind);
break;
case CTRL_N_KEY:
ungetc(DOWN_KEY, stdin);
ungetc(CTRL2_KEY, stdin);
ungetc(CTRL1_KEY, stdin);
break;
case CTRL_R_KEY:
case CTRL_S_KEY:
res = find_command(input_buf, hist_ind,
(c == CTRL_S_KEY));
if (res != hist_ind) {
hist_ind = res;
memset(input_buf, 0, READ_BUF_SIZE);
strcpy(input_buf, History[hist_ind]);
};
print_str_by_index(Title, 0, -1);
ind = print_str_by_index(input_buf, 0, -1);
break;
case CTRL1_KEY:
c = getchar();
if (c != CTRL2_KEY) break;
c = getchar();
switch (c) {
case INSERT_KEY:
getchar();
is_insert_key = (is_insert_key) ? 0 : 1;
break;
case DELETE_KEY:
getchar();
if (ind == len) break;
ind = delete_char(input_buf, len, ind, 1);
break;
case LEFT_KEY:
printf("%c", '\b');
ind--;
if (ind < 0) {
ind = 0;
printf("%c", ' ');
};
break;
case RIGHT_KEY:
ind++;
if (ind > len) ind = len;
else print_str_by_index(input_buf, ind - 1, ind);
break;
case UP_KEY:
case DOWN_KEY:
clear_line(ind, len);
if (c == UP_KEY)
str = get_history_prev(input_buf);
else
str = get_history_next(input_buf);
memset(input_buf, 0, READ_BUF_SIZE);
strcpy(input_buf, str);
len = strlen(input_buf);
ind = print_str_by_index(input_buf, 0, len);
break;
case PGUP_KEY:
case PGDOWN_KEY:
getchar();
add_to_history(input_buf, hist_ind);
clear_line(ind, len);
hist_ind = (c == PGUP_KEY) ? 0 : current_hist_ind;
str = History[hist_ind];
memset(input_buf, 0, READ_BUF_SIZE);
strcpy(input_buf, str);
len = strlen(input_buf);
ind = print_str_by_index(input_buf, 0, len);
break;
case HOME_KEY:
go_to_begin(ind);
ind = print_str_by_index(input_buf, 0, 0);
break;
case END_KEY:
ind = print_str_by_index(input_buf, ind, len);
break;
};
break;
case CTRL_H_KEY:
case BACKSP_KEY:
ind = (ind <= 0) ? 0 : delete_char(input_buf, len, ind, 0);
break;
default:
if (ind == len) {
input_buf[ind++] = c;
printf("%c", c);
break;
};
ind = add_char(input_buf, len, c, ind);
break;
};
if (ind >= (READ_BUF_SIZE - 1)) {
if (current_hist_ind != hist_ind)
add_to_history(input_buf, hist_ind);
add_to_history(input_buf, current_hist_ind);
return(History[current_hist_ind]);
}
};
return((char *)NULL);
}
void set_current_history(char *line)
{
if (line == (char *)NULL) return;
if (strlen(line) == 0) return;
if (strcmp(History[current_hist_ind], line) == 0) return;
free(History[current_hist_ind]);
History[current_hist_ind] = strdup(line);
}
char *get_last_history(void)
{
if (current_hist_ind > 0)
return(History[current_hist_ind - 1]);
return((char *)NULL);
}
char *get_def_history(char *text, int *count)
{
int ind, i, res;
char *tmp, c;
tmp = text;
i = *count;
while((*tmp != ' ') && (*tmp != 0)) {
tmp++;
i++;
};
c = *tmp;
*tmp = 0;
if (isdigit(*text)) {
ind = atoi(text);
*tmp = c;
if ((ind < 0) || (ind > current_hist_ind))
return((char *)NULL);
*count = i;
return(History[ind]);
};
res = find_cmd_by_text(text, current_hist_ind - 1, 0);
*tmp = c;
if (res == -1) return((char *)NULL);
*count = i;
return(History[res]);
}
ret_code_t history_cmd(void)
{
int i;
for (i = 0; i <= current_hist_ind; i++) {
printf("[ %d ] %s\n", i, History[i]);
};
return(HPI_SHELL_OK);
}