/*
 *  GRUB  --  GRand Unified Bootloader
 *  Copyright (C) 2005,2006,2007,2008  Free Software Foundation, Inc.
 *
 *  GRUB 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 3 of the License, or
 *  (at your option) any later version.
 *
 *  GRUB 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 GRUB.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <grub/normal.h>
#include <grub/term.h>
#include <grub/misc.h>
#include <grub/mm.h>
#include <grub/loader.h>
#include <grub/command.h>
#include <grub/parser.h>

enum update_mode
  {
    NO_LINE,
    SINGLE_LINE,
    ALL_LINES
  };

struct line
{
  /* The line buffer.  */
  char *buf;
  /* The length of the line.  */
  int len;
  /* The maximum length of the line.  */
  int max_len;
};

struct screen
{
  /* The array of lines.  */
  struct line *lines;
  /* The number of lines.  */
  int num_lines;
  /* The current column.  */
  int column;
  /* The real column.  */
  int real_column;
  /* The current line.  */
  int line;
  /* The X coordinate.  */
  int x;
  /* The Y coordinate.  */
  int y;
  /* The kill buffer.  */
  char *killed_text;
  /* The flag of a completion window.  */
  int completion_shown;
};

/* Used for storing completion items temporarily.  */
static struct line completion_buffer;

/* Initialize a line.  */
static int
init_line (struct line *linep)
{
  linep->len = 0;
  linep->max_len = 80; /* XXX */
  linep->buf = grub_malloc (linep->max_len);
  if (! linep->buf)
    return 0;

  return 1;
}

/* Allocate extra space if necessary.  */
static int
ensure_space (struct line *linep, int extra)
{
  if (linep->max_len < linep->len + extra)
    {
      linep->max_len = linep->len + extra + 80; /* XXX */
      linep->buf = grub_realloc (linep->buf, linep->max_len + 1);
      if (! linep->buf)
	return 0;
    }

  return 1;
}

/* Return the number of lines occupied by this line on the screen.  */
static int
get_logical_num_lines (struct line *linep)
{
  return (linep->len / GRUB_TERM_ENTRY_WIDTH) + 1;
}

/* Print a line.  */
static void
print_line (struct line *linep, int offset, int start, int y)
{
  int i;
  char *p;

  grub_gotoxy (GRUB_TERM_LEFT_BORDER_X + GRUB_TERM_MARGIN + start + 1,
	       y + GRUB_TERM_FIRST_ENTRY_Y);

  for (p = linep->buf + offset + start, i = start;
       i < GRUB_TERM_ENTRY_WIDTH && offset + i < linep->len;
       p++, i++)
    grub_putchar (*p);

  for (; i < GRUB_TERM_ENTRY_WIDTH; i++)
    grub_putchar (' ');

  if (linep->len >= offset + GRUB_TERM_ENTRY_WIDTH)
    grub_putchar ('\\');
  else
    grub_putchar (' ');
}

/* Print an empty line.  */
static void
print_empty_line (int y)
{
  int i;

  grub_gotoxy (GRUB_TERM_LEFT_BORDER_X + GRUB_TERM_MARGIN + 1,
	       y + GRUB_TERM_FIRST_ENTRY_Y);

  for (i = 0; i < GRUB_TERM_ENTRY_WIDTH + 1; i++)
    grub_putchar (' ');
}

/* Print an up arrow.  */
static void
print_up (int flag)
{
  grub_gotoxy (GRUB_TERM_LEFT_BORDER_X + GRUB_TERM_BORDER_WIDTH,
	       GRUB_TERM_FIRST_ENTRY_Y);

  if (flag)
    grub_putcode (GRUB_TERM_DISP_UP);
  else
    grub_putchar (' ');
}

/* Print a down arrow.  */
static void
print_down (int flag)
{
  grub_gotoxy (GRUB_TERM_LEFT_BORDER_X + GRUB_TERM_BORDER_WIDTH,
	       GRUB_TERM_TOP_BORDER_Y + GRUB_TERM_NUM_ENTRIES);

  if (flag)
    grub_putcode (GRUB_TERM_DISP_DOWN);
  else
    grub_putchar (' ');
}

/* Draw the lines of the screen SCREEN.  */
static void
update_screen (struct screen *screen, int region_start, int region_column,
	       int up, int down, enum update_mode mode)
{
  int up_flag = 0;
  int down_flag = 0;
  int y;
  int i;
  struct line *linep;

  /* Check if scrolling is necessary.  */
  if (screen->y < 0 || screen->y >= GRUB_TERM_NUM_ENTRIES)
    {
      if (screen->y < 0)
	screen->y = 0;
      else
	screen->y = GRUB_TERM_NUM_ENTRIES - 1;

      region_start = 0;
      region_column = 0;
      up = 1;
      down = 1;
      mode = ALL_LINES;
    }

  if (mode != NO_LINE)
    {
      /* Draw lines. This code is tricky, because this must calculate logical
	 positions.  */
      y = screen->y - screen->column / GRUB_TERM_ENTRY_WIDTH;
      i = screen->line;
      linep = screen->lines + i;
      while (y > 0)
	{
	   i--;
	   linep--;
	   y -= get_logical_num_lines (linep);
	}

      if (y < 0 || i > 0)
	up_flag = 1;

      do
	{
	  int column;

	  for (column = 0;
	       column <= linep->len && y < GRUB_TERM_NUM_ENTRIES;
	       column += GRUB_TERM_ENTRY_WIDTH, y++)
	    {
	      if (y < 0)
		continue;

	      if (i == region_start)
		{
		  if (region_column >= column
		      && region_column < column + GRUB_TERM_ENTRY_WIDTH)
		    print_line (linep, column, region_column - column, y);
		  else if (region_column < column)
		    print_line (linep, column, 0, y);
		}
	      else if (i > region_start && mode == ALL_LINES)
		print_line (linep, column, 0, y);
	    }

	  if (y == GRUB_TERM_NUM_ENTRIES)
	    {
	      if (column <= linep->len || i + 1 < screen->num_lines)
		down_flag = 1;
	    }

	  linep++;
	  i++;

	  if (mode == ALL_LINES && i == screen->num_lines)
	    for (; y < GRUB_TERM_NUM_ENTRIES; y++)
	      print_empty_line (y);

	}
      while (y < GRUB_TERM_NUM_ENTRIES);

      /* Draw up and down arrows.  */
      if (up)
	print_up (up_flag);
      if (down)
	print_down (down_flag);
    }

  /* Place the cursor.  */
  grub_gotoxy (GRUB_TERM_LEFT_BORDER_X + GRUB_TERM_MARGIN + 1 + screen->x,
	       GRUB_TERM_FIRST_ENTRY_Y + screen->y);

  grub_refresh ();
}

/* Insert the string S into the screen SCREEN. This updates the cursor
   position and redraw the screen. Return zero if fails.  */
static int
insert_string (struct screen *screen, char *s, int update)
{
  int region_start = screen->num_lines;
  int region_column = 0;
  int down = 0;
  enum update_mode mode = NO_LINE;

  while (*s)
    {
      if (*s == '\n')
	{
	  /* LF is special because it creates a new line.  */
	  struct line *current_linep;
	  struct line *next_linep;
	  int size;

	  /* Make a new line.  */
	  screen->num_lines++;
	  screen->lines = grub_realloc (screen->lines,
					screen->num_lines
					* sizeof (struct line));
	  if (! screen->lines)
	    return 0;

	  /* Scroll down. */
	  grub_memmove (screen->lines + screen->line + 2,
			screen->lines + screen->line + 1,
			((screen->num_lines - screen->line - 2)
			 * sizeof (struct line)));

	  if (! init_line (screen->lines + screen->line + 1))
	    return 0;

	  /* Fold the line.  */
	  current_linep = screen->lines + screen->line;
	  next_linep = current_linep + 1;
	  size = current_linep->len - screen->column;

	  if (! ensure_space (next_linep, size))
	    return 0;

	  grub_memmove (next_linep->buf,
			current_linep->buf + screen->column,
			size);
	  current_linep->len = screen->column;
	  next_linep->len = size;

	  /* Update a dirty region.  */
	  if (region_start > screen->line)
	    {
	      region_start = screen->line;
	      region_column = screen->column;
	    }

	  mode = ALL_LINES;
	  down = 1; /* XXX not optimal.  */

	  /* Move the cursor.  */
	  screen->column = screen->real_column = 0;
	  screen->line++;
	  screen->x = 0;
	  screen->y++;

	  s++;
	}
      else
	{
	  /* All but LF.  */
	  char *p;
	  struct line *current_linep;
	  int size;
	  int orig_num, new_num;

	  /* Find a string delimited by LF.  */
	  p = grub_strchr (s, '\n');
	  if (! p)
	    p = s + grub_strlen (s);

	  /* Insert the string.  */
	  current_linep = screen->lines + screen->line;
	  size = p - s;
	  if (! ensure_space (current_linep, size))
	    return 0;

	  grub_memmove (current_linep->buf + screen->column + size,
			current_linep->buf + screen->column,
			current_linep->len - screen->column);
	  grub_memmove (current_linep->buf + screen->column,
			s,
			size);
	  orig_num = get_logical_num_lines (current_linep);
	  current_linep->len += size;
	  new_num = get_logical_num_lines (current_linep);

	  /* Update the dirty region.  */
	  if (region_start > screen->line)
	    {
	      region_start = screen->line;
	      region_column = screen->column;
	    }

	  if (orig_num != new_num)
	    {
	      mode = ALL_LINES;
	      down = 1; /* XXX not optimal.  */
	    }
	  else if (mode != ALL_LINES)
	    mode = SINGLE_LINE;

	  /* Move the cursor.  */
	  screen->column += size;
	  screen->real_column = screen->column;
	  screen->x += size;
	  screen->y += screen->x / GRUB_TERM_ENTRY_WIDTH;
	  screen->x %= GRUB_TERM_ENTRY_WIDTH;

	  s = p;
	}
    }

  if (update)
    update_screen (screen, region_start, region_column, 0, down, mode);

  return 1;
}

/* Release the resource allocated for SCREEN.  */
static void
destroy_screen (struct screen *screen)
{
  int i;

  if (screen->lines)
    for (i = 0; i < screen->num_lines; i++)
      {
	struct line *linep = screen->lines + i;

	if (linep)
	  grub_free (linep->buf);
      }

  grub_free (screen->killed_text);
  grub_free (screen->lines);
  grub_free (screen);
}

/* Make a new screen.  */
static struct screen *
make_screen (grub_menu_entry_t entry)
{
  struct screen *screen;

  /* Initialize the screen.  */
  screen = grub_malloc (sizeof (*screen));
  if (! screen)
    return 0;

  screen->num_lines = 1;
  screen->column = 0;
  screen->real_column = 0;
  screen->line = 0;
  screen->x = 0;
  screen->y = 0;
  screen->killed_text = 0;
  screen->completion_shown = 0;
  screen->lines = grub_malloc (sizeof (struct line));
  if (! screen->lines)
    goto fail;

  /* Initialize the first line which must be always present.  */
  if (! init_line (screen->lines))
    goto fail;

  insert_string (screen, (char *) entry->sourcecode, 0);

  /* Reset the cursor position.  */
  screen->column = 0;
  screen->real_column = 0;
  screen->line = 0;
  screen->x = 0;
  screen->y = 0;

  return screen;

 fail:
  destroy_screen (screen);
  return 0;
}

static int
forward_char (struct screen *screen, int update)
{
  struct line *linep;

  linep = screen->lines + screen->line;
  if (screen->column < linep->len)
    {
      screen->column++;
      screen->x++;
      if (screen->x == GRUB_TERM_ENTRY_WIDTH)
	{
	  screen->x = 0;
	  screen->y++;
	}
    }
  else if (screen->num_lines > screen->line + 1)
    {
      screen->column = 0;
      screen->line++;
      screen->x = 0;
      screen->y++;
    }

  screen->real_column = screen->column;

  if (update)
    update_screen (screen, screen->num_lines, 0, 0, 0, NO_LINE);
  return 1;
}

static int
backward_char (struct screen *screen, int update)
{
  if (screen->column > 0)
    {
      screen->column--;
      screen->x--;
      if (screen->x == -1)
	{
	  screen->x = GRUB_TERM_ENTRY_WIDTH - 1;
	  screen->y--;
	}
    }
  else if (screen->line > 0)
    {
      struct line *linep;

      screen->line--;
      linep = screen->lines + screen->line;
      screen->column = linep->len;
      screen->x = screen->column % GRUB_TERM_ENTRY_WIDTH;
      screen->y--;
    }

  screen->real_column = screen->column;

  if (update)
    update_screen (screen, screen->num_lines, 0, 0, 0, NO_LINE);

  return 1;
}

static int
previous_line (struct screen *screen, int update)
{
  if (screen->line > 0)
    {
      struct line *linep;
      int dy;

      /* How many physical lines from the current position
	 to the first physical line?  */
      dy = screen->column / GRUB_TERM_ENTRY_WIDTH;

      screen->line--;

      linep = screen->lines + screen->line;
      if (linep->len < screen->real_column)
	screen->column = linep->len;
      else
	screen->column = screen->real_column;

      /* How many physical lines from the current position
	 to the last physical line?  */
      dy += (linep->len / GRUB_TERM_ENTRY_WIDTH
	     - screen->column / GRUB_TERM_ENTRY_WIDTH);

      screen->y -= dy + 1;
      screen->x = screen->column % GRUB_TERM_ENTRY_WIDTH;
    }
  else
    {
      screen->y -= screen->column / GRUB_TERM_ENTRY_WIDTH;
      screen->column = 0;
      screen->x = 0;
    }

  if (update)
    update_screen (screen, screen->num_lines, 0, 0, 0, NO_LINE);

  return 1;
}

static int
next_line (struct screen *screen, int update)
{
  if (screen->line < screen->num_lines - 1)
    {
      struct line *linep;
      int dy;

      /* How many physical lines from the current position
	 to the last physical line?  */
      linep = screen->lines + screen->line;
      dy = (linep->len / GRUB_TERM_ENTRY_WIDTH
	    - screen->column / GRUB_TERM_ENTRY_WIDTH);

      screen->line++;

      linep++;
      if (linep->len < screen->real_column)
	screen->column = linep->len;
      else
	screen->column = screen->real_column;

      /* How many physical lines from the current position
	 to the first physical line?  */
      dy += screen->column / GRUB_TERM_ENTRY_WIDTH;

      screen->y += dy + 1;
      screen->x = screen->column % GRUB_TERM_ENTRY_WIDTH;
    }
  else
    {
      struct line *linep;

      linep = screen->lines + screen->line;
      screen->y += (linep->len / GRUB_TERM_ENTRY_WIDTH
		    - screen->column / GRUB_TERM_ENTRY_WIDTH);
      screen->column = linep->len;
      screen->x = screen->column % GRUB_TERM_ENTRY_WIDTH;
    }

  if (update)
    update_screen (screen, screen->num_lines, 0, 0, 0, NO_LINE);

  return 1;
}

static int
beginning_of_line (struct screen *screen, int update)
{
  screen->y -= screen->column / GRUB_TERM_ENTRY_WIDTH;
  screen->column = screen->real_column = 0;
  screen->x = 0;

  if (update)
    update_screen (screen, screen->num_lines, 0, 0, 0, NO_LINE);

  return 1;
}

static int
end_of_line (struct screen *screen, int update)
{
  struct line *linep;

  linep = screen->lines + screen->line;
  screen->y += (linep->len / GRUB_TERM_ENTRY_WIDTH
		- screen->column / GRUB_TERM_ENTRY_WIDTH);
  screen->column = screen->real_column = linep->len;
  screen->x = screen->column % GRUB_TERM_ENTRY_WIDTH;

  if (update)
    update_screen (screen, screen->num_lines, 0, 0, 0, NO_LINE);

  return 1;
}

static int
delete_char (struct screen *screen, int update)
{
  struct line *linep;
  enum update_mode mode = NO_LINE;
  int start = screen->num_lines;
  int column = 0;
  int down = 0;

  linep = screen->lines + screen->line;
  if (linep->len > screen->column)
    {
      int orig_num, new_num;

      orig_num = get_logical_num_lines (linep);

      grub_memmove (linep->buf + screen->column,
		    linep->buf + screen->column + 1,
		    linep->len - screen->column - 1);
      linep->len--;

      new_num = get_logical_num_lines (linep);

      if (orig_num != new_num)
	mode = ALL_LINES;
      else
	mode = SINGLE_LINE;

      start = screen->line;
      column = screen->column;
    }
  else if (screen->num_lines > screen->line + 1)
    {
      struct line *next_linep;

      next_linep = linep + 1;
      if (! ensure_space (linep, next_linep->len))
	return 0;

      grub_memmove (linep->buf + linep->len, next_linep->buf, next_linep->len);
      linep->len += next_linep->len;

      grub_free (next_linep->buf);
      grub_memmove (next_linep,
		    next_linep + 1,
		    (screen->num_lines - screen->line - 2)
		    * sizeof (struct line));
      screen->num_lines--;

      mode = ALL_LINES;
      start = screen->line;
      column = screen->column;
      down = 1;
    }

  screen->real_column = screen->column;

  if (update)
    update_screen (screen, start, column, 0, down, mode);

  return 1;
}

static int
backward_delete_char (struct screen *screen, int update)
{
  int saved_column;
  int saved_line;

  saved_column = screen->column;
  saved_line = screen->line;

  if (! backward_char (screen, 0))
    return 0;

  if (saved_column != screen->column || saved_line != screen->line)
    if (! delete_char (screen, update))
      return 0;

  return 1;
}

static int
kill_line (struct screen *screen, int continuous, int update)
{
  struct line *linep;
  char *p;
  int size;
  int offset;

  p = screen->killed_text;
  if (! continuous && p)
    p[0] = '\0';

  linep = screen->lines + screen->line;
  size = linep->len - screen->column;

  if (p)
    offset = grub_strlen (p);
  else
    offset = 0;

  if (size > 0)
    {
      enum update_mode mode = SINGLE_LINE;
      int down = 0;
      int orig_num, new_num;

      p = grub_realloc (p, offset + size + 1);
      if (! p)
	return 0;

      grub_memmove (p + offset, linep->buf + screen->column, size);
      p[offset + size - 1] = '\0';

      screen->killed_text = p;

      orig_num = get_logical_num_lines (linep);
      linep->len = screen->column;
      new_num = get_logical_num_lines (linep);

      if (orig_num != new_num)
	{
	  mode = ALL_LINES;
	  down = 1;
	}

      if (update)
	update_screen (screen, screen->line, screen->column, 0, down, mode);
    }
  else if (screen->line + 1 < screen->num_lines)
    {
      p = grub_realloc (p, offset + 1 + 1);
      if (! p)
	return 0;

      p[offset] = '\n';
      p[offset + 1] = '\0';

      screen->killed_text = p;

      return delete_char (screen, update);
    }

  return 1;
}

static int
yank (struct screen *screen, int update)
{
  if (screen->killed_text)
    return insert_string (screen, screen->killed_text, update);

  return 1;
}

static int
open_line (struct screen *screen, int update)
{
  int saved_y = screen->y;

  if (! insert_string (screen, "\n", 0))
    return 0;

  if (! backward_char (screen, 0))
    return 0;

  screen->y = saved_y;

  if (update)
    update_screen (screen, screen->line, screen->column, 0, 1, ALL_LINES);

  return 1;
}

/* A completion hook to print items.  */
static void
store_completion (const char *item, grub_completion_type_t type, int count)
{
  char *p;

  if (count == 0)
    {
      /* If this is the first time, print a label.  */
      const char *what;

      switch (type)
	{
	case GRUB_COMPLETION_TYPE_COMMAND:
	  what = "commands";
	  break;
	case GRUB_COMPLETION_TYPE_DEVICE:
	  what = "devices";
	  break;
	case GRUB_COMPLETION_TYPE_FILE:
	  what = "files";
	  break;
	case GRUB_COMPLETION_TYPE_PARTITION:
	  what = "partitions";
	  break;
	case GRUB_COMPLETION_TYPE_ARGUMENT:
	  what = "arguments";
	  break;
	default:
	  what = "things";
	  break;
	}

      grub_gotoxy (0, GRUB_TERM_HEIGHT - 3);
      grub_printf ("   Possible %s are:\n    ", what);
    }

  /* Make sure that the completion buffer has enough room.  */
  if (completion_buffer.max_len < (completion_buffer.len
				   + (int) grub_strlen (item) + 1 + 1))
    {
      grub_size_t new_len;

      new_len = completion_buffer.len + grub_strlen (item) + 80;
      p = grub_realloc (completion_buffer.buf, new_len);
      if (! p)
	{
	  /* Possibly not fatal.  */
	  grub_errno = GRUB_ERR_NONE;
	  return;
	}
      p[completion_buffer.len] = 0;
      completion_buffer.buf = p;
      completion_buffer.max_len = new_len;
    }

  p = completion_buffer.buf + completion_buffer.len;
  if (completion_buffer.len != 0)
    {
      *p++ = ' ';
      completion_buffer.len++;
    }
  grub_strcpy (p, item);
  completion_buffer.len += grub_strlen (item);
}

static int
complete (struct screen *screen, int continuous, int update)
{
  grub_uint16_t pos;
  char saved_char;
  struct line *linep;
  int restore;
  char *insert;
  static int count = -1;

  if (continuous)
    count++;
  else
    count = 0;

  pos = grub_getxy ();
  grub_gotoxy (0, GRUB_TERM_HEIGHT - 3);

  completion_buffer.buf = 0;
  completion_buffer.len = 0;
  completion_buffer.max_len = 0;

  linep = screen->lines + screen->line;
  saved_char = linep->buf[screen->column];
  linep->buf[screen->column] = '\0';

  insert = grub_normal_do_completion (linep->buf, &restore, store_completion);

  linep->buf[screen->column] = saved_char;

  if (restore)
    {
      char *p = completion_buffer.buf;

      screen->completion_shown = 1;

      if (p)
	{
	  int num_sections = ((completion_buffer.len + GRUB_TERM_WIDTH - 8 - 1)
			      / (GRUB_TERM_WIDTH - 8));
	  char *endp;

	  p += (count % num_sections) * (GRUB_TERM_WIDTH - 8);
	  endp = p + (GRUB_TERM_WIDTH - 8);

	  if (p != completion_buffer.buf)
	    grub_putcode (GRUB_TERM_DISP_LEFT);
	  else
	    grub_putchar (' ');

	  while (*p && p < endp)
	    grub_putchar (*p++);

	  if (*p)
	    grub_putcode (GRUB_TERM_DISP_RIGHT);
	}
    }

  grub_gotoxy (pos >> 8, pos & 0xFF);

  if (insert)
    {
      insert_string (screen, insert, update);
      count = -1;
      grub_free (insert);
    }
  else if (update)
    grub_refresh ();

  grub_free (completion_buffer.buf);
  return 1;
}

/* Clear displayed completions.  */
static void
clear_completions (void)
{
  grub_uint16_t pos;
  int i, j;

  pos = grub_getxy ();
  grub_gotoxy (0, GRUB_TERM_HEIGHT - 3);

  for (i = 0; i < 2; i++)
    {
      for (j = 0; j < GRUB_TERM_WIDTH - 1; j++)
	grub_putchar (' ');
      grub_putchar ('\n');
    }

  grub_gotoxy (pos >> 8, pos & 0xFF);
  grub_refresh ();
}

/* Execute the command list in the screen SCREEN.  */
static int
run (struct screen *screen)
{
  int currline = 0;
  char *nextline;

  auto grub_err_t editor_getline (char **line, int cont);
  grub_err_t editor_getline (char **line, int cont __attribute__ ((unused)))
    {
      struct line *linep = screen->lines + currline;
      char *p;

      if (currline > screen->num_lines)
	{
	  *line = 0;
	  return 0;
	}

      /* Trim down space characters.  */
      for (p = linep->buf + linep->len - 1;
	   p >= linep->buf && grub_isspace (*p);
	   p--)
	;
      *++p = '\0';

      linep->len = p - linep->buf;
      for (p = linep->buf; grub_isspace (*p); p++)
	;
      *line = grub_strdup (p);
      currline++;
      return 0;
    }

  grub_cls ();
  grub_printf ("  Booting a command list\n\n");


  /* Execute the script, line for line.  */
  while (currline < screen->num_lines)
    {
      editor_getline (&nextline, 0);
      if (grub_parser_get_current ()->parse_line (nextline, editor_getline))
	break;
    }

  if (grub_errno == GRUB_ERR_NONE && grub_loader_is_loaded ())
    /* Implicit execution of boot, only if something is loaded.  */
    grub_command_execute ("boot", 0, 0);

  if (grub_errno != GRUB_ERR_NONE)
    {
      grub_print_error ();
      grub_errno = GRUB_ERR_NONE;
      grub_wait_after_message ();
    }

  return 1;
}

/* Edit a menu entry with an Emacs-like interface.  */
void
grub_menu_entry_run (grub_menu_entry_t entry)
{
  struct screen *screen;
  int prev_c;

  screen = make_screen (entry);
  if (! screen)
    return;

 refresh:
  /* Draw the screen.  */
  grub_menu_init_page (0, 1);
  update_screen (screen, 0, 0, 1, 1, ALL_LINES);
  grub_setcursor (1);
  prev_c = '\0';

  while (1)
    {
      int c = GRUB_TERM_ASCII_CHAR (grub_getkey ());

      if (screen->completion_shown)
	{
	  clear_completions ();
	  screen->completion_shown = 0;
	}

      switch (c)
	{
	case 16: /* C-p */
	  if (! previous_line (screen, 1))
	    goto fail;
	  break;

	case 14: /* C-n */
	  if (! next_line (screen, 1))
	    goto fail;
	  break;

	case 6: /* C-f */
	  if (! forward_char (screen, 1))
	    goto fail;
	  break;

	case 2: /* C-b */
	  if (! backward_char (screen, 1))
	    goto fail;
	  break;

	case 1: /* C-a */
	  if (! beginning_of_line (screen, 1))
	    goto fail;
	  break;

	case 5: /* C-e */
	  if (! end_of_line (screen, 1))
	    goto fail;
	  break;

	case '\t': /* C-i */
	  if (! complete (screen, prev_c == c, 1))
	    goto fail;
	  break;

	case 4: /* C-d */
	  if (! delete_char (screen, 1))
	    goto fail;
	  break;

	case 8: /* C-h */
	  if (! backward_delete_char (screen, 1))
	    goto fail;
	  break;

	case 11: /* C-k */
	  if (! kill_line (screen, prev_c == c, 1))
	    goto fail;
	  break;

	case 21: /* C-u */
	  /* FIXME: What behavior is good for this key?  */
	  break;

	case 25: /* C-y */
	  if (! yank (screen, 1))
	    goto fail;
	  break;

	case 12: /* C-l */
	  /* FIXME: centering.  */
	  goto refresh;

	case 15: /* C-o */
	  if (! open_line (screen, 1))
	    goto fail;
	  break;

	case '\n':
	case '\r':
	  if (! insert_string (screen, "\n", 1))
	    goto fail;
	  break;

	case '\e':
	  destroy_screen (screen);
	  return;

	case 3: /* C-c */
	  grub_cmdline_run (1);
	  goto refresh;

	case 24: /* C-x */
	  if (! run (screen))
	    goto fail;
	  goto refresh;

	case 18: /* C-r */
	case 19: /* C-s */
	case 20: /* C-t */
	  /* FIXME */
	  break;

	default:
	  if (grub_isprint (c))
	    {
	      char buf[2];

	      buf[0] = c;
	      buf[1] = '\0';
	      if (! insert_string (screen, buf, 1))
		goto fail;
	    }
	  break;
	}

      prev_c = c;
    }

 fail:
  destroy_screen (screen);

  grub_cls ();
  grub_print_error ();
  grub_errno = GRUB_ERR_NONE;
  grub_printf ("\nPress any key to continue...");
  (void) grub_getkey ();
}
