Commit 822e2f1c authored by Pavel Tvrdik's avatar Pavel Tvrdik
Browse files

BIRD Client: Improve autocomplete bahavior

Autocomplete keywords (for, where, filter, ...) and symbol names (names
of protocols, tables, ...). Client can request daemon for list of all
symbols using new cli command `refresh symbols`.

Autocomplete behavior is controlled by *.Y files using flags CLI_SF_*

Transform doc/reply_codes to c header file client/reply_codes.h
parent 4bdf1881
Loading
Loading
Loading
Loading
+4 −1
Original line number Diff line number Diff line
@@ -78,7 +78,7 @@ static int
input_complete(int arg UNUSED, int key UNUSED)
{
  static int complete_flag;
  char buf[256];
  char buf[256] = {};

  if (rl_last_func != input_complete)
    complete_flag = 0;
@@ -140,6 +140,9 @@ input_help(int arg, int key UNUSED)
void
input_init(void)
{
  retrieve_symbols();
  printf("BIRD Client " BIRD_VERSION " ready.\n");

  rl_readline_name = "birdc";
  rl_add_defun("bird-complete", input_complete, '\t');
  rl_add_defun("bird-help", input_help, '?');
+2 −0
Original line number Diff line number Diff line
@@ -136,6 +136,8 @@ input_init(void)
  if (!interactive)
    return;

  printf("BIRD Client Light " BIRD_VERSION " ready.\n");

  if (tcgetattr(0, &stored_tty) < 0)
    die("tcgetattr: %m");

+96 −9
Original line number Diff line number Diff line
@@ -34,6 +34,7 @@
#include "lib/string.h"
#include "client/client.h"
#include "sysdep/unix/unix.h"
#include "client/reply_codes.h"

#define SERVER_READ_BUF_LEN 4096

@@ -47,12 +48,15 @@ static byte server_read_buf[SERVER_READ_BUF_LEN];
static byte *server_read_pos = server_read_buf;

int init = 1;		/* During intial sequence */
int busy = 1;		/* Executing BIRD command */
int busy = 0;		/* Executing BIRD command */
int interactive;	/* Whether stdin is terminal */

static int num_lines, skip_input;
int term_lns, term_cls;

static list symbols;
static uint longest_symbol_len;


/*** Parsing of arguments ***/

@@ -94,14 +98,14 @@ parse_args(int argc, char **argv)
      for (i = optind; i < argc; i++)
	len += strlen(argv[i]) + 1;

      tmp = init_cmd = malloc(len);
      tmp = init_cmd = xmalloc(len);
      for (i = optind; i < argc; i++)
	{
	  strcpy(tmp, argv[i]);
	  tmp += strlen(tmp);
	  *tmp++ = ' ';
	}
      tmp[-1] = 0;
      tmp[-1] = 0; /* Put ending null-terminator */

      once = 1;
      interactive = 0;
@@ -126,6 +130,12 @@ handle_internal_command(char *cmd)
      puts("Press `?' for context sensitive help.");
      return 1;
    }
  if (!strncmp(cmd, REFRESH_SYMBOLS_CMD, sizeof(REFRESH_SYMBOLS_CMD)-1))
  {
    retrieve_symbols();
    return 1;
  }

  return 0;
}

@@ -166,6 +176,41 @@ submit_command(char *cmd_raw)
  free(cmd);
}

static void
add_to_symbols(int flag, const char *name)
{
  struct cli_symbol *sym = xmalloc(sizeof(struct cli_symbol));
  sym->flags = flag;

  sym->len = strlen(name);
  char *name_ = xmalloc(sym->len + 1);
  memcpy(name_, name, sym->len + 1);
  sym->name = name_;
  add_tail(&symbols, &sym->n);

  if (longest_symbol_len < sym->len)
    longest_symbol_len = sym->len;
}

void
retrieve_symbols(void)
{
  /* purge old symbols */
  list *syms = cli_get_symbol_list();
  struct cli_symbol *sym, *next;
  WALK_LIST_DELSAFE(sym, next, *syms)
  {
    rem2_node(&sym->n);
    free((char *) sym->name);
    free(sym);
  }

  add_to_symbols(CLI_SF_KW_ALL, "all");
  add_to_symbols(CLI_SF_KW_OFF, "off");

  submit_server_command(REFRESH_SYMBOLS_CMD);
}

static void
init_commands(void)
{
@@ -192,6 +237,8 @@ init_commands(void)
      exit(0);
    }

  init_list(&symbols);
  longest_symbol_len = 1; /* Be careful, it's used as denominator! */
  input_init();

  term_lns = (term_lns > 0) ? term_lns : 25;
@@ -256,13 +303,47 @@ server_connect(void)
    die("fcntl: %m");
}

list *
cli_get_symbol_list(void)
{
  return &symbols;
}

uint
cli_get_symbol_maxlen(void)
{
  return longest_symbol_len;
}

static void
server_got_symbol(int reply_code, const char *name)
{
  u32 flag = 0;

  switch (reply_code)
  {
  case RC_CONSTANT_NAME:	flag = CLI_SF_CONSTANT; break;
  case RC_VARIABLE_NAME:	flag = CLI_SF_VARIABLE; break;
  case RC_FILTER_NAME:		flag = CLI_SF_FILTER; break;
  case RC_FUNCTION_NAME:	flag = CLI_SF_FUNCTION; break;
  case RC_PROTOCOL_NAME:	flag = CLI_SF_PROTOCOL; break;
  case RC_TABLE_NAME:		flag = CLI_SF_TABLE; break;
  case RC_TEMPLATE_NAME:	flag = CLI_SF_TEMPLATE; break;
  case RC_INTERFACE_NAME:	flag = CLI_SF_INTERFACE; break;
  default:
    printf("Undefined %d: %s", reply_code, name);
    return;
  }

  add_to_symbols(flag, name);
}

#define PRINTF(LEN, PARGS...) do { if (!skip_input) len = printf(PARGS); } while(0)

static void
server_got_reply(char *x)
{
  int code;
  int code = 0;
  int len = 0;

  if (*x == '+')                        /* Async reply */
@@ -273,8 +354,14 @@ server_got_reply(char *x)
           sscanf(x, "%d", &code) == 1 && code >= 0 && code < 10000 &&
           (x[4] == ' ' || x[4] == '-'))
    {
      if (code)
      if (code >= 3000 && code < 4000)
      {
	server_got_symbol(code, x+5);
      }
      else if (code)
      {
	PRINTF(len, "%s\n", verbose ? x : x+5);
      }

      if (x[4] == ' ')
      {
+36 −0
Original line number Diff line number Diff line
@@ -6,6 +6,10 @@
 *	Can be freely distributed and used under the terms of the GNU GPL.
 */

#ifndef _BIRD_CLIENT_H_
#define _BIRD_CLIENT_H_

#define REFRESH_SYMBOLS_CMD "refresh symbols"

extern int init, busy, interactive;
extern int term_lns, term_cls;
@@ -33,4 +37,36 @@ char *cmd_expand(char *cmd);

/* client.c */

/* Client Symbol Flags: Types */
#define CLI_SF_CONSTANT		(1 << 0)
#define CLI_SF_VARIABLE		(1 << 1)
#define CLI_SF_FILTER		(1 << 2)
#define CLI_SF_FUNCTION		(1 << 3)
#define CLI_SF_PROTOCOL		(1 << 4)
#define CLI_SF_TABLE		(1 << 5)
#define CLI_SF_TEMPLATE		(1 << 6)
#define CLI_SF_INTERFACE	(1 << 7)

#define CLI_SF_OPTIONAL		(1 << 8) /* This node is optional not mandatory */
#define CLI_SF_PARAMETER	(1 << 9) /* A parameter/word will follow after this node */

/* Client Symbol Flags: Keywords */
#define CLI_SF_KW_ALL		(1 << 10)
#define CLI_SF_KW_OFF 		(1 << 11)


struct cli_symbol
{
  node n;
  const char *name;
  uint len;
  u32 flags;			/* CLI_SF_* */
};

void submit_command(char *cmd_raw);
void retrieve_symbols(void);
void add_keywords_to_symbols(void);
list *cli_get_symbol_list(void);
uint cli_get_symbol_maxlen(void);

#endif
+108 −15
Original line number Diff line number Diff line
@@ -15,10 +15,14 @@
#include "client/client.h"

struct cmd_info {
  /* use for build tree and command cli */
  char *command;
  char *args;
  char *help;

  /* only for build tree */
  int is_real_cmd;
  u32 flags;			/* Mask of (CLI_SF_*) */
};

static struct cmd_info command_table[] = {
@@ -26,11 +30,15 @@ static struct cmd_info command_table[] = {
};

struct cmd_node {
  struct cmd_node *sibling, *son, **plastson;
  struct cmd_info *cmd, *help;
  int len;
  signed char prio;
  char token[1];
  struct cmd_node *sibling;
  struct cmd_node *son;
  struct cmd_node **plastson;	/* Helping pointer to son */
  struct cmd_info *cmd;		/* Short info */
  struct cmd_info *help;	/* Detailed info */
  signed char prio; 		/* Priority */
  int len; 			/* Length of string in token */
  u32 flags;			/* Mask of (CLI_SF_*) */
  char token[1];		/* Name of command */
};

static struct cmd_node cmd_root;
@@ -77,6 +85,7 @@ cmd_build_tree(void)
	old->cmd = cmd;
      else
	old->help = cmd;
      old->flags |= cmd->flags;
    }
}

@@ -102,6 +111,8 @@ static struct cmd_node *
cmd_find_abbrev(struct cmd_node *root, char *cmd, int len, int *pambiguous)
{
  struct cmd_node *m, *best = NULL, *best2 = NULL;
  list *l_syms = cli_get_symbol_list();
  struct cli_symbol *sym;

  *pambiguous = 0;
  for(m=root->son; m; m=m->sibling)
@@ -133,6 +144,14 @@ cmd_list_ambiguous(struct cmd_node *root, char *cmd, int len)
  for(m=root->son; m; m=m->sibling)
    if (m->len > len && !memcmp(m->token, cmd, len))	
      cmd_display_help(m->help, m->cmd);

  struct cli_symbol *sym;
  list *syms = cli_get_symbol_list();
  WALK_LIST(sym, *syms)
  {
    if ((sym->flags & root->flags) && sym->len > len && memcmp(sym->name, cmd, len) == 0)
      printf("%s\n", sym->name);
  }
}

void
@@ -169,11 +188,17 @@ cmd_help(char *cmd, int len)
    cmd_display_help(m->help, m->cmd);
}

/*
 * Return length of common prefix of all matches,
 * Write count of all matches into pcount,
 * Write common prefix string into buf
 */
static int
cmd_find_common_match(struct cmd_node *root, char *cmd, int len, int *pcount, char *buf)
{
  struct cmd_node *m;
  int best, best_prio, i;
  int best, /* len of common prefix */
      best_prio, i;

  *pcount = 0;
  best = -1;
@@ -195,6 +220,7 @@ cmd_find_common_match(struct cmd_node *root, char *cmd, int len, int *pcount, ch
      (*pcount)++;
      if (best < 0)
	{
	  /* For a case that we'll have exactly one match */
	  strcpy(buf, m->token + len);
	  best = m->len - len;
	  best_prio = m->prio;
@@ -207,6 +233,33 @@ cmd_find_common_match(struct cmd_node *root, char *cmd, int len, int *pcount, ch
	  best = i;
	}
    }

  list *syms = cli_get_symbol_list();
  struct cli_symbol *sym;
  WALK_LIST(sym, *syms)
  {
    if (!(sym->flags & root->flags))
      continue;

    if (sym->len < len || memcmp(sym->name, cmd, len))
      continue;

    (*pcount)++;

    if (best < 0)
    {
      strcpy(buf, sym->name + len);
      best = sym->len - len;		/* for a case that we'll have only one match */
    }
    else
    {
      i = 0;
      while (i < best && i < sym->len - len && buf[i] == sym->name[len+i])
	i++;
      best = i;
    }
  }

  return best;
}

@@ -216,7 +269,7 @@ cmd_complete(char *cmd, int len, char *buf, int again)
  char *start = cmd;
  char *end = cmd + len;
  char *fin;
  struct cmd_node *n, *m;
  struct cmd_node *n, *m = NULL;
  char *z;
  int ambig, cnt = 0, common;

@@ -226,7 +279,7 @@ cmd_complete(char *cmd, int len, char *buf, int again)

  /* Find the context */
  n = &cmd_root;
  while (cmd < fin && n->son)
  while (cmd < fin)
    {
      if (isspace(*cmd))
	{
@@ -248,12 +301,39 @@ cmd_complete(char *cmd, int len, char *buf, int again)
	}
      if (!m)
	return -1;

      /* Try skip a parameter/word */
      if (m->flags & CLI_SF_PARAMETER)
      {
	z = cmd;

	/* Skip spaces before parameter */
	while (cmd < fin && isspace(*cmd))
	  cmd++;

	/* Skip one parameter/word */
	while (cmd < fin && !isspace(*cmd))
	  cmd++;

	/* Check ending of parameter */
	if (isspace(*cmd))
	{
	  if (m->flags & CLI_SF_OPTIONAL)
	    m = n;
	  continue;
	}
	else
	  cmd = z;
      }

      /* Do not enter to optional command nodes */
      if (!(m->flags & CLI_SF_OPTIONAL))
	n = m;
    }

  /* Completion of parameters is not yet supported */
  if (!n->son)
    return -1;
  /* Enter to the last command node */
  if (m && (m->flags & CLI_SF_PARAMETER))
    n = m;

  /* We know the context, let's try to complete */
  common = cmd_find_common_match(n, fin, end-fin, &cnt, buf);
@@ -281,8 +361,8 @@ cmd_complete(char *cmd, int len, char *buf, int again)
char *
cmd_expand(char *cmd)
{
  struct cmd_node *n, *m;
  char *c, *b, *args;
  struct cmd_node *n, *m, *last_real_cmd = NULL;
  char *c, *b, *args, *lrc_args = NULL;
  int ambig;

  args = c = cmd;
@@ -306,14 +386,27 @@ cmd_expand(char *cmd)
	  cmd_list_ambiguous(n, b, c-b);
	  return NULL;
	}

      args = c;
      n = m;

      if (m->cmd)
      {
	last_real_cmd = m;
	lrc_args = c;
      }
    }
  if (!n->cmd)

  if (!n->cmd && !last_real_cmd)
    {
      puts("No such command. Press `?' for help.");
      return NULL;
    }
  if (last_real_cmd && last_real_cmd != n)
  {
    n = last_real_cmd;
    args = lrc_args;
  }
  b = xmalloc(strlen(n->cmd->command) + strlen(args) + 1);
  sprintf(b, "%s%s", n->cmd->command, args);
  return b;
Loading