/*
 * Copyright (c) 2002-2006 Samit Basu
 *
 * 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; either version 2 of the License, or
 * (at your option) any later version.
 *
 * 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
 *
 */

/*
 * To-add: copy/paste/select - typeahead
 */

#include <algorithm>
#include "KeyManager.hpp"
#include "Interpreter.hpp"
#include "Common.hpp"
#include "Context.hpp"
#include <qapplication.h>
#include <QtCore>
#include <iostream>

#define TAB_WIDTH 8
/*
 * The following macro returns non-zero if a character is
 * a control character.
 */
#define IS_CTRL_CHAR(c) ((unsigned char)(c) < ' ' || (unsigned char)(c)=='\177')
/*
 * Given a binary control character, return the character that
 * had to be pressed at the same time as the control key.
 */
#define CTRL_TO_CHAR(c) (toupper((unsigned char)(c) | 0x40))

#define GL_WORD_CHARS "_*\?\\[]"

// Maximum allowed number of columns in the text window
#define MAXCOLS 256

#define LINELEN 65536

// ----------------------------------------------------------------------------
// KeyManager
// ----------------------------------------------------------------------------

KeyManager::KeyManager()  {
  keypresswait = false;
  cutbuf = "";
  linelen = 1000;
  ntotal = 0;
  buff_curpos = 0;
  term_curpos = 0;
  keyseq_count = 0;
  last_search = -1;
  ncolumn = 80;
  nline = 24;
  insert = true;
  //  history.push_back("");
  enteredLinesEmpty = true;
  ReplacePrompt("");
  loopactive = 0;
  lineData = "";
  ResetLineBuffer();
  context = NULL;
  QSettings settings("FreeMat", "FreeMat");
  QStringList historyList = settings.value("interpreter/history").toStringList();
  if (!historyList.size())
    history.push_back("");
  for (int i=0;i<historyList.size();i++) 
    history.push_back(historyList[i].toStdString());
}

Context* KeyManager::GetCompletionContext() {
  return context;
}

void KeyManager::SetCompletionContext(Context* ctxt) {
  context = ctxt;
}

int KeyManager::getTerminalWidth() {
  return ncolumn;
}

void KeyManager::ClearHistory() {
  history.clear();
}

void KeyManager::SetTermWidth(int w) {
  emit UpdateTermWidth(w);
  ncolumn = w;
  Redisplay();
}

void KeyManager::SetTermCurpos(int n) {
  TerminalMove(n - term_curpos);
}

void KeyManager::PlaceCursor(int n) {
  /*
   * Don't allow the cursor position to go out of the bounds of the input
   * line.
   */
  if(n >= ntotal)
    n = ntotal;
  if(n < 0)
    n = 0;
  /*
   * Record the new buffer position.
   */
  buff_curpos = n;
  /*
   * Move the terminal cursor to the corresponding character.
   */
  int tmpi = BuffCurposToTermCurpos(n);
  SetTermCurpos(tmpi);
}

int KeyManager::DisplayedCharWidth(char c, int aterm_curpos) {
  if(c=='\t')
    return TAB_WIDTH - ((aterm_curpos % ncolumn) % TAB_WIDTH);
  if(IS_CTRL_CHAR(c))
    return 2;
  if(!isprint((int)(unsigned char) c)) {
    char string[TAB_WIDTH + 4];
    sprintf(string, "\\%o", (int)(unsigned char)c);
    return strlen(string);
  };
  return 1;
}

// Return the number of terminal characters needed to display a
// given substring.
int KeyManager::DisplayedStringWidth(string s, int nc, int aterm_curpos) {
  int slen=0;   /* The displayed number of characters */
  int i;
  /*
   * How many characters are to be measured?
   */
  if(nc < 0)
    nc = s.length();
  /*
   * Add up the length of the displayed string.
   */
  for(i=0; i<nc; i++)
    slen += DisplayedCharWidth(s[i], aterm_curpos + slen);
  return slen;
}

void KeyManager::InsertString(int pos, string s) {
  lineData.insert(pos,s);
}

void KeyManager::InsertCharacter(int pos, char c) {
  lineData.insert(pos,1,c);
}

void KeyManager::EraseCharacters(int pos, int cnt) {
  lineData.erase(pos,cnt);
}

void KeyManager::SetCharacter(int pos, char c) {
  if (pos < lineData.size())
    lineData[pos] = c;
  else {
    int topad = (pos-lineData.size()+1);
    lineData.append(topad,' ');
    lineData[pos] = c;
  }
}

int KeyManager::BuffCurposToTermCurpos(int n) {
  return prompt_len + DisplayedStringWidth(lineData, n, prompt_len);
}

void KeyManager::Redisplay() {
  /*
   * Keep a record of the current cursor position.
   */
  int sbuff_curpos = buff_curpos;
  /*
   * Move the cursor to the start of the terminal line, and clear from there
   * to the end of the display.
   */
  SetTermCurpos(0);
  emit ClearEOD();
  /*
   * Nothing is displayed yet.
   */
  term_len = 0;
  /*
   * Display the current prompt.
   */
  DisplayPrompt();
  /*
   * Render the part of the line that the user has typed in so far.
   */
  OutputString(lineData,'\0');
  /*
   * Restore the cursor position.
   */
  PlaceCursor(sbuff_curpos);
}

void KeyManager::setTerminalWidth(int w) {
  //  cout << "Set terminal width " << w << "\n";
  ncolumn = w; 
}

void KeyManager::ReplacePrompt(string aprompt) {
  prompt = aprompt;
  prompt_len = DisplayedStringWidth(aprompt,-1,0);
}

void KeyManager::TerminalMove(int n) {
  int cur_row, cur_col; /* The current terminal row and column index of */
                        /*  the cursor wrt the start of the input line. */
  int new_row, new_col; /* The target terminal row and column index of */
                        /*  the cursor wrt the start of the input line. */
  /*
   * How far can we move left?
   */
  if(term_curpos + n < 0)
    n = term_curpos;
  /*
   * Break down the current and target cursor locations into rows and columns.
   */
  cur_row = term_curpos / ncolumn;
  cur_col = term_curpos % ncolumn;
  new_row = (term_curpos + n) / ncolumn;
  new_col = (term_curpos + n) % ncolumn;
  /*
   * Move down to the next line.
   */
  for(; cur_row < new_row; cur_row++) 
    emit MoveDown();
  /*
   * Move up to the previous line.
   */
  for(; cur_row > new_row; cur_row--)
    emit MoveUp();
  /*
   * Move to the right within the target line?
   */
  if(cur_col < new_col) {
    {
      for(; cur_col < new_col; cur_col++)
	emit MoveRight();
    };
    /*
     * Move to the left within the target line?
     */
  } else if(cur_col > new_col) {
    {
      for(; cur_col > new_col; cur_col--)
	emit MoveLeft();
    };
  }
  /*
   * Update the recorded position of the terminal cursor.
   */
  term_curpos += n;  
}

void KeyManager::TruncateDisplay() {
  /*
   * Keep a record of the current terminal cursor position.
   */
  int aterm_curpos = term_curpos;
  /*
   * First clear from the cursor to the end of the current input line.
   */
  emit ClearEOL();
  /*
   * If there is more than one line displayed, go to the start of the
   * next line and clear from there to the end of the display. Note that
   * we can't use clear_eod to do the whole job of clearing from the
   * current cursor position to the end of the terminal because
   * clear_eod is only defined when used at the start of a terminal line
   * (eg. with gnome terminals, clear_eod clears from the start of the
   * current terminal line, rather than from the current cursor
   * position).
   */
  if(term_len / ncolumn > term_curpos / ncolumn) {
    emit MoveDown();
    emit MoveBOL();
    emit ClearEOD();
    /*
     * Where is the cursor now?
     */
    term_curpos = ncolumn * (aterm_curpos / ncolumn + 1);
    /*
     * Restore the cursor position.
     */
    SetTermCurpos(aterm_curpos);
  };
  /*
   * Update the recorded position of the final character.
   */
  term_len = term_curpos;  
}

void KeyManager::AddCharToLine(char c) {
  /*
   * Keep a record of the current cursor position.
   */
  int sbuff_curpos = buff_curpos;
  int sterm_curpos = term_curpos;
  /*
   * Work out the displayed width of the new character.
   */
  int width = DisplayedCharWidth(c, sterm_curpos);
  /*
   * If we are in insert mode, or at the end of the line,
   * check that we can accomodate a new character in the buffer.
   * If not, simply return, leaving it up to the calling program
   * to check for the absence of a newline character.
   */
  if((insert || sbuff_curpos >= ntotal) && ntotal >= linelen)
    return;
  /*
   * Are we adding characters to the line (ie. inserting or appending)?
   */
  if(insert || sbuff_curpos >= ntotal) {
    /*
     * If inserting, make room for the new character.
     */
    if(sbuff_curpos < ntotal) {
      InsertCharacter(sbuff_curpos,' ');
    };    
    /*
     * Copy the character into the buffer.
     */
    SetCharacter(sbuff_curpos,c);
    buff_curpos++;
    /*
     * If the line was extended, update the record of the string length
     * and terminate the extended string.
     */
    ntotal++;
    /*
     * Redraw the line from the cursor position to the end of the line,
     * and move the cursor to just after the added character.
     */
    OutputString(string(lineData,sbuff_curpos), '\0');
    SetTermCurpos(sterm_curpos + width);
    /*
     * Are we overwriting an existing character?
     */
  } else {
    /*
     * Get the widths of the character to be overwritten and the character
     * that is going to replace it.
     */
    int old_width = DisplayedCharWidth(lineData[sbuff_curpos],
				       sterm_curpos);
    /*
     * Overwrite the character in the buffer.
     */
    SetCharacter(sbuff_curpos,c);
    /*
     * If we are replacing with a narrower character, we need to
     * redraw the terminal string to the end of the line, then
     * overwrite the trailing old_width - width characters
     * with spaces.
     */
    if(old_width > width) {
      OutputString(string(lineData,sbuff_curpos), '\0');
      /*
       * Clear to the end of the terminal.
       */
      TruncateDisplay();
      /*
       * Move the cursor to the end of the new character.
       */
      SetTermCurpos(sterm_curpos + width);
      buff_curpos++;
      /*
       * If we are replacing with a wider character, then we will be
       * inserting new characters, and thus extending the line.
       */
    } else if(width > old_width) {
      /*
       * Redraw the line from the cursor position to the end of the line,
       * and move the cursor to just after the added character.
       */
      OutputString(string(lineData,sbuff_curpos), '\0');
      SetTermCurpos(sterm_curpos + width);
      buff_curpos++;
      /*
       * The original and replacement characters have the same width,
       * so simply overwrite.
       */
    } else {
      /*
       * Copy the character into the buffer.
       */
      SetCharacter(sbuff_curpos,c);
      buff_curpos++;
      /*
       * Overwrite the original character.
       */
      OutputChar(c, lineData[buff_curpos]);
    };
  };
}

int KeyManager::DisplayPrompt() {
  term_curpos = 0;
  emit MoveBOL();
  emit ClearEOL();
  OutputString(prompt, '\0');
  return 0;
}

/*.......................................................................
 * Write a character to the terminal after expanding tabs and control
 * characters to their multi-character representations.
 *
 * Input:
 *  gl    GetLine *   The resource object of this program.
 *  c        char     The character to be output.
 *  pad      char     Many terminals have the irritating feature that
 *                    when one writes a character in the last column of
 *                    of the terminal, the cursor isn't wrapped to the
 *                    start of the next line until one more character
 *                    is written. Some terminals don't do this, so
 *                    after such a write, we don't know where the
 *                    terminal is unless we output an extra character.
 *                    This argument specifies the character to write.
 *                    If at the end of the input line send '\0' or a
 *                    space, and a space will be written. Otherwise,
 *                    pass the next character in the input line
 *                    following the one being written.
 * Output:
 *  return    int     0 - OK.
 */ 
void KeyManager::OutputChar(char c, char pad) {
  char string[TAB_WIDTH + 4]; /* A work area for composing compound strings */
  int nchar;                  /* The number of terminal characters */
  int i;
  /*
   * Check for special characters.
   */
  if(c == '\t') {
    /*
     * How many spaces do we need to represent a tab at the current terminal
     * column?
     */
    nchar = DisplayedCharWidth('\t', term_curpos);
    /*
     * Compose the tab string.
     */
    for(i=0; i<nchar; i++)
      string[i] = ' ';
  } else if(IS_CTRL_CHAR(c)) {
    string[0] = '^';
    string[1] = CTRL_TO_CHAR(c);
    nchar = 2;
  } else if(!isprint((int)(unsigned char) c)) {
    sprintf(string, "\\%o", (int)(unsigned char)c);
    nchar = strlen(string);
  } else {
    string[0] = c;
    nchar = 1;
  };
  /*
   * Terminate the string.
   */
  string[nchar] = '\0';
  /*
   * Write the string to the terminal.
   */
  emit OutputRawString(string);
  /*
   * Except for one exception to be described in a moment, the cursor should
   * now have been positioned after the character that was just output.
   */
  term_curpos += nchar;
  /*
   * Keep a record of the number of characters in the terminal version
   * of the input line.
   */
  if(term_curpos > term_len)
    term_len = term_curpos;
  /*
   * If the new character ended exactly at the end of a line,
   * most terminals won't move the cursor onto the next line until we
   * have written a character on the next line, so append an extra
   * space then move the cursor back.
   */
  if(term_curpos % ncolumn == 0) {
    int sterm_curpos = term_curpos;
    OutputChar(pad ? pad : ' ', ' ');
    SetTermCurpos(sterm_curpos);
  };
}

void KeyManager::OutputString(string st, char pad) {
  if (st.size() == 0) return;
  for(unsigned int i=0;i<st.size()-1;i++)
    OutputChar(st[i],st[i+1]);
  OutputChar(st[st.size()-1],pad);
}

KeyManager::~KeyManager() {
}


void KeyManager::ResetLineBuffer() {
  //  line.clear();
  ntotal = 0;
  buff_curpos = 0;
  term_curpos = 0;
  term_len = 0;
  insert_curpos = 0;
  lineData = "";
}

void KeyManager::NewLine() {
  AddHistory(lineData);
  PlaceCursor(ntotal);
  emit OutputRawString("\r\n");
  emit ExecuteLine(lineData + "\n");
  ReplacePrompt("");
  ResetLineBuffer();
  DisplayPrompt();
}

void KeyManager::Ready() {
  ReplacePrompt("--> ");
  Redisplay();
}

void KeyManager::CursorLeft() {
  PlaceCursor(buff_curpos-1);
}

void KeyManager::CursorRight() {
  PlaceCursor(buff_curpos+1);
}

void KeyManager::BeginningOfLine() {
  PlaceCursor(0);
}
void KeyManager::BackwardDeleteChar() {
  if (1 > buff_curpos - insert_curpos)
    return;
  CursorLeft();
  DeleteChars(1,0);
}

void KeyManager::ForwardDeleteChar() {
  DeleteChars(1,0);
}

// Delete nc characters starting from the one under the cursor.
// Optionally copy the deleted characters to the cut buffer.
void KeyManager::DeleteChars(int nc, int cut) {
  /*
   * If there are fewer than nc characters following the cursor, limit
   * nc to the number available.
   */
  if(buff_curpos + nc > ntotal)
    nc = ntotal - buff_curpos;
  /*
   * Copy the about to be deleted region to the cut buffer.
   */
  if(cut) {
    // Fixme
    //    cutbuf = string(line,buff_curpos,nc);
  }
  /*
   * Nothing to delete?
   */
  if(nc <= 0)
    return;
  /*
   * Copy the remaining part of the line back over the deleted characters.
   */
  EraseCharacters(buff_curpos,nc);

  ntotal -= nc;

  /*
   * Redraw the remaining characters following the cursor.
   */
  OutputString(string(lineData,buff_curpos), '\0');
  /*
   * Clear to the end of the terminal.
   */
  TruncateDisplay();
  /*
   * Place the cursor at the start of where the deletion was performed.
   */
  PlaceCursor(buff_curpos);
}

void KeyManager::EndOfLine() {
  PlaceCursor(ntotal);
}

void KeyManager::ClearCurrentLine() {
  PlaceCursor(0);
  ntotal = 0;
  lineData.clear();
  TruncateDisplay();
}

void KeyManager::KillLine() {
  cutbuf = string(lineData,buff_curpos);
  ntotal = buff_curpos;
  lineData.erase(ntotal);
  TruncateDisplay();
  PlaceCursor(buff_curpos);
}

void KeyManager::HistorySearchBackward() {
  if (last_search != keyseq_count-1) {
    SearchPrefix(string(lineData),buff_curpos);
    startsearch = history.size();
  }
  last_search = keyseq_count;
  HistoryFindBackwards();
  ntotal = lineData.size();
  buff_curpos = ntotal;
  Redisplay();
}

void KeyManager::HistorySearchForward() {
  if (last_search != keyseq_count-1)
    SearchPrefix(string(lineData),buff_curpos);
  last_search = keyseq_count;
  HistoryFindForwards();
  ntotal = lineData.size();
  buff_curpos = ntotal;
  Redisplay();
}

void KeyManager::SearchPrefix(string aline, int aprefix_len) {
  prefix = string(aline,0,aprefix_len);
  prefix_len = aprefix_len;
  startsearch = history.size();
}

void KeyManager::AddHistory(string mline) {
  prefix = "";
  prefix_len = 0;
  if (mline.size() > 0) {
    if (history.back() != mline)
      history.push_back(mline);
    while (history.size() > 1000)
      history.pop_front();
  }
  emit SendCommand(QString::fromStdString(mline));
  return;
}

void KeyManager::HistoryFindForwards() {
  unsigned int i;
  bool found;
  if (startsearch >= (history.size()-1)) {
    ResetLineBuffer();
    AddStringToLine(prefix);
    startsearch = history.size();
    return;
  }
  i = startsearch+1;
  found = false;
  while (i<history.size() && !found) {
    found = (prefix_len == 0) || 
      (history[i].compare(0,prefix_len,prefix) == 0);
    if (!found) i++;
  }
  if (!found && (i >= history.size())) {
    startsearch = history.size();
    ResetLineBuffer();
    AddStringToLine(prefix);
    return;
  }
  lineData = history[i];
  startsearch = i;
}

void KeyManager::HistoryFindBackwards() {
  int i;
  bool found;
  if (startsearch == 0) return;
  i = startsearch-1;
  found = false;
  while (i>=0 && !found) {
    found = (history[i].compare(0,prefix_len,prefix) == 0);
    if (!found) i--;
  }
  if (!found) return;
  lineData = history[i];
  startsearch = i;
}



/*.......................................................................
 * Insert/append a string to the line buffer and terminal at the current
 * cursor position.
 *
 * Input:
 *  gl   GetLine *   The resource object of this library.
 *  s       char *   The string to be added.
 * Output:
 *  return   int     0 - OK.
 *                   1 - Insufficient room.
 */
void KeyManager::AddStringToLine(string s) {
  int buff_slen;   /* The length of the string being added to line[] */
  int term_slen;   /* The length of the string being written to the terminal */
  int sbuff_curpos; /* The original value of gl->buff_curpos */
  int sterm_curpos; /* The original value of gl->term_curpos */
  /*
   * Keep a record of the current cursor position.
   */
  sbuff_curpos = buff_curpos;
  sterm_curpos = term_curpos;
  /*
   * How long is the string to be added?
   */
  buff_slen = s.length();
  term_slen = DisplayedStringWidth(s, buff_slen, sterm_curpos);
  /*
   * Check that we can accomodate the string in the buffer.
   * If not, simply return, leaving it up to the calling program
   * to check for the absence of a newline character.
   */
  if(ntotal + buff_slen > linelen)
    return;
  /*
   * Move the characters that follow the cursor in the buffer by
   * buff_slen characters to the right.
   */
  InsertString(buff_curpos,s);
  /*
   * Copy the string into the buffer.
   */
  ntotal += buff_slen;
  buff_curpos += buff_slen;
  /*
   * Maintain the buffer properly terminated.
   */
  /*
   * Write the modified part of the line to the terminal, then move
   * the terminal cursor to the end of the displayed input string.
   */
  OutputString(string(lineData,sbuff_curpos), '\0');
  SetTermCurpos(sterm_curpos + term_slen);
}

void KeyManager::Yank() {
  buff_mark = buff_curpos;
  if (cutbuf.empty())
    return;
  AddStringToLine(cutbuf);
}

void KeyManager::ListCompletions(vector<string> completions) {
  int maxlen;    /* The length of the longest matching string */
  int width;     /* The width of a column */
  int ncol;      /* The number of columns to list */
  int nrow;      /* The number of rows needed to list all of the matches */
  int row,col;   /* The row and column being written to */
  unsigned int i;
  /*
   * Not enough space to list anything?
   */
  if(ncolumn < 1)
    return;
  /*
   * Work out the maximum length of the matching strings.
   */
  maxlen = 0;
  for(i=0; i<completions.size(); i++) {
    int len = completions[i].length();
    if(len > maxlen)
      maxlen = len;
  };
  /*
   * Nothing to list?
   */
  if(maxlen == 0)
    return;
  /*
   * Split the available terminal width into columns of maxlen + 2 characters.
   */
  width = maxlen + 2;
  ncol = ncolumn / width;
  /*
   * If the column width is greater than the terminal width, the matches will
   * just have to overlap onto the next line.
   */
  if(ncol < 1)
    ncol = 1;
  /*
   * How many rows will be needed?
   */
  nrow = (completions.size() + ncol - 1) / ncol;
  /*
   * Print the matches out in ncol columns, sorted in row order within each
   * column.
   */
  for(row=0; row < nrow; row++) {
    for(col=0; col < ncol; col++) {
      unsigned int m = col*nrow + row;
      if(m < completions.size()) {
	char buffer[4096];
	sprintf(buffer, "%s%-*s%s", completions[m].c_str(),
		(int) (ncol > 1 ? maxlen - completions[m].length():0),
		"", col<ncol-1 ? "  " : "\r\n");
	emit OutputRawString(buffer);
      } else {
	emit OutputRawString("\r\n");
	break;
      };
    };
  };
}

string GetCommonPrefix(vector<string> matches,
			    string tempstring) {
  unsigned int minlength;
  unsigned int prefixlength;
  bool allmatch;
  string templ;
  unsigned int i, j;

  minlength = matches[0].size();
  for (i=0;i<matches.size();i++)
    minlength = (minlength < matches[i].size()) ? 
      minlength : matches[i].size();
  prefixlength = minlength;
  templ = matches[0];
  for (i=0;i<matches.size();i++) {
    j = 0;
    allmatch = true;
    while (allmatch && (j<prefixlength)) {
      string mtch(matches[i]);
      allmatch = (mtch[j] == templ[j]);
      if (allmatch) j++;
    }
    prefixlength = (j < prefixlength) ? j : prefixlength;
  }
  if (prefixlength <= tempstring.length())
    return (string(""));
  else
    return(templ.substr(tempstring.length(),prefixlength-tempstring.length()));
}


void KeyManager::CompleteWord() {
  int redisplay=0;        /* True if the whole line needs to be redrawn */
  int suffix_len;         /* The length of the completion extension */
  int cont_len;           /* The length of any continuation suffix */
  int nextra;             /* The number of characters being added to the */
                          /*  total length of the line. */
  int buff_pos;           /* The buffer index at which the completion is */
                          /*  to be inserted. */
  vector<string> matches;
  redisplay = 1;
  /*
   * Get the cursor position at which the completion is to be inserted.
   */
  buff_pos = buff_curpos;
  /*
   * Perform the completion.
   */
  string tempstring;
  matches = GetCompletions(lineData, buff_curpos, tempstring);
  if(matches.size() == 0) {
    emit OutputRawString("\r\n");
    term_curpos = 0;
    redisplay = 1;
    /*
     * Are there any completions?
     */
  } else if(matches.size() >= 1) {
    /*
     * If there any ambiguous matches, report them, starting on a new line.
     */
    if(matches.size() > 1) {
      emit OutputRawString("\r\n");
      ListCompletions(matches);
      redisplay = 1;
    };
    /*
     * Find the common prefix
     */
    string prefix;
    prefix = GetCommonPrefix(matches, tempstring);
    /*
     * Get the length of the suffix and any continuation suffix to add to it.
     */
    suffix_len = prefix.length(); // This is supposed to be the length of the filename extension...
    cont_len = 0;
    /*
     * Work out the number of characters that are to be added.
     */
    nextra = suffix_len + cont_len;
    /*
     * Is there anything to be added?
     */
    if(nextra) {
      /*
       * Will there be space for the expansion in the line buffer?
       */
      if(ntotal + nextra < linelen) {
	/*
	 * Make room to insert the filename extension.
	 */
	InsertString(buff_curpos,string(prefix,0,nextra));
	/*
	 * Record the increased length of the line.
	 */
	ntotal += nextra;
	/*
	 * Place the cursor position at the end of the completion.
	 */
	buff_curpos += nextra;
	/*
	 * If we don't have to redisplay the whole line, redisplay the part
	 * of the line which follows the original cursor position, and place
	 * the cursor at the end of the completion.
	 */
	if(!redisplay) {
	  TruncateDisplay();
	  OutputString(string(lineData,buff_pos), '\0');
	  PlaceCursor(buff_curpos);
	  return;
	};
      } else {
	redisplay = 1;
      };
    };
  };
  /*
   * Redisplay the whole line?
   */
  if(redisplay) {
    term_curpos = 0;
    Redisplay();
    return;
  };
  return;
}

void KeyManager::getKeyPress() {
  keypresswait = true;
  while (keypresswait) 
    qApp->processEvents();
}

void KeyManager::OnChar( int c ) {
  if (keypresswait) {
    keypresswait = false;
    return;
  }
  keyseq_count++;
  switch(c) {
  case KM_BACKSPACE:
  case KM_BACKSPACEALT:
    BackwardDeleteChar();
    break;
  case KM_LEFT:
    CursorLeft();
    break;
  case KM_RIGHT:
    CursorRight();
    break;
  case KM_DELETE:
    ForwardDeleteChar();
    break;
  case KM_INSERT:
    insert = !insert;
    break;
  case KM_HOME:
    BeginningOfLine();
    break;
  case KM_END:
    EndOfLine();
    break;
  case KM_UP:
    HistorySearchBackward();
    break;
  case KM_DOWN:
    HistorySearchForward();
    break;
  case KM_CTRLA:
    BeginningOfLine();
    break;
  case KM_CTRLB:
    emit RegisterInterrupt();
    break;
  case KM_CTRLE:
    EndOfLine();
    break;
  case KM_CTRLD:
    ForwardDeleteChar();
    break;
  case KM_TAB:
    if ((buff_curpos != 0) && (lineData[buff_curpos-1] != ' ') &&
 	(lineData[buff_curpos-1] != '\t'))
      CompleteWord();
    else
      AddCharToLine(c);
    break;
  case KM_CTRLY:
    Yank();
    break;
  case KM_CTRLW:
    ClearCurrentLine();
    break;
  case KM_CTRLK:
    KillLine();
    break;
  case KM_NEWLINE:
  case 10:
    NewLine();
    break;
  default:
    AddCharToLine(c);
  }
}

void KeyManager::QueueString(QString t) {
  string g(t.toStdString());
  AddStringToLine(g);
}

void KeyManager::QueueMultiString(QString t) {
  t.replace(QChar(8233),"\n");
  t.replace("\r\n","\n");
  t.replace("\r","\n"); 
  if (t.indexOf("\n") < 0) {
    QueueString(t);
    return;
  }
  QStringList tlist(t.split("\n"));
  for (int i=0;i<tlist.size()-1;i++) {
    string t(tlist[i].toStdString());
    emit OutputRawString(t+"\r\n");
    emit ExecuteLine(t+"\n");
    AddHistory(t);
  }
  if (t.endsWith('\n')) {
    string t(tlist.back().toStdString());
    emit OutputRawString(t+"\r\n");
    emit ExecuteLine(t+"\n");
    AddHistory(t);
  }  else {
    QueueString(tlist.back());
  }
  return;
}

void KeyManager::QueueCommand(QString t) {
  QueueString(t);
  AddHistory(t.toStdString());
  emit OutputRawString("\r\n");
  emit ExecuteLine(lineData+"\n");
  ResetLineBuffer();
  DisplayPrompt();
}

void KeyManager::QueueSilent(QString t) {
  emit ExecuteLine(t.toStdString()+"\n");
}

//char* KeyManager::getLine(string aprompt) {
//  emit UpdateVariables();
//  ReplacePrompt(aprompt);
//  DisplayPrompt();
//  while (enteredLinesEmpty) {
//    loopactive++;
//    m_loop->exec();
//  }
//  string theline(enteredLines.front());
//  enteredLines.pop_front();
//  enteredLinesEmpty = (enteredLines.empty());
//  char *cp;
//  cp = strdup(theline.c_str());
//  return cp;
//}

void KeyManager::RegisterTerm(QObject* term) {
  connect(this,SIGNAL(MoveDown()),term,SLOT(MoveDown()));
  connect(this,SIGNAL(MoveUp()),term,SLOT(MoveUp()));
  connect(this,SIGNAL(MoveRight()),term,SLOT(MoveRight()));
  connect(this,SIGNAL(MoveLeft()),term,SLOT(MoveLeft()));
  connect(this,SIGNAL(ClearEOL()),term,SLOT(ClearEOL()));
  connect(this,SIGNAL(ClearEOD()),term,SLOT(ClearEOD()));
  connect(this,SIGNAL(MoveBOL()),term,SLOT(MoveBOL()));
  connect(this,SIGNAL(OutputRawString(string)),term,
	  SLOT(OutputRawString(string)));
  connect(this,SIGNAL(ClearDisplay()),term,SLOT(ClearDisplay()));
  connect(term,SIGNAL(OnChar(int)),this,SLOT(OnChar(int)));
  connect(term,SIGNAL(SetTextWidth(int)),this,SLOT(SetTermWidth(int)));  
}

void KeyManager::ClearDisplayCommand() {
  emit ClearDisplay();
}

void KeyManager::ContinueAction() {
  emit ExecuteLine("return\n");
}

void KeyManager::StopAction() {
  emit ExecuteLine("retall\n");  
}

void KeyManager::DbStepAction() {
  emit ExecuteLine("dbstep\n");
}

void KeyManager::DbTraceAction() {
  emit ExecuteLine("dbtrace\n");
}

void KeyManager::SetPrompt(string txt) {
  ReplacePrompt(txt);
  Redisplay();
  emit UpdateVariables();
}

/*.......................................................................
 * Search backwards for the potential start of a filename. This
 * looks backwards from the specified index in a given string,
 * stopping at the first unescaped space or the start of the line.
 *
 * Input:
 *  string  const char *  The string to search backwards in.
 *  back_from      int    The index of the first character in string[]
 *                        that follows the pathname.
 * Output:
 *  return        char *  The pointer to the first character of
 *                        the potential pathname, or NULL on error.
 */
static char *start_of_path(const char *string, int back_from)
{
  int i, j;
  /*
   * Search backwards from the specified index.
   */
  for(i=back_from-1; i>=0; i--) {
    int c = string[i];
    /*
     * Stop on unescaped spaces.
     */
    if(isspace((int)(unsigned char)c)) {
      /*
       * The space can't be escaped if we are at the start of the line.
       */
      if(i==0)
	break;
      /*
       * Find the extent of the escape characters which precedes the space.
       */
      for(j=i-1; j>=0 && string[j]=='\\'; j--)
	;
      /*
       * If there isn't an odd number of escape characters before the space,
       * then the space isn't escaped.
       */
      if((i - 1 - j) % 2 == 0)
	break;
    } 
    else if (!isalpha(c) && !isdigit(c) && (c != '_') && (c != '.') && (c != '\\') && (c != '/'))
      break;
  };
  return (char *)string + i + 1;
}

vector<string> KeyManager::GetCompletions(string line, 
					  int word_end, 
					  string &matchString) {
  vector<string> completions;
  if (!context->getMutex()->tryLock()) return completions;
  QMutexLocker lock(context->getMutex());
  context->getMutex()->unlock();
  /*
   * Find the start of the filename prefix to be completed, searching
   * backwards for the first unescaped space, or the start of the line.
   */
  char *start = start_of_path(line.c_str(), word_end);
  char *tmp;
  int mtchlen;
  mtchlen = word_end - (start-line.c_str());
  tmp = (char*) malloc(mtchlen+1);
  memcpy(tmp,start,mtchlen);
  tmp[mtchlen] = 0;
  matchString = string(tmp);
  
  /*
   *  the preceeding character was not a ' (quote), then
   * do a command expansion, otherwise, do a filename expansion.
   */
  if (!context) return completions;
  if (start[-1] != '\'') {
    vector<string> local_completions(context->getCompletions(string(start)));
    for (int i=0;i<local_completions.size();i++) 
      if (local_completions[i].find("private:") == local_completions[i].npos)
	completions.push_back(local_completions[i]);
  }
  stringVector comp(GetCompletionList(tmp));
  for (int i=0;i<comp.size();i++) 
    if (comp[i].find("private:") == comp[i].npos)
      completions.push_back(comp[i]);
  sort(completions.begin(),completions.end());
  return completions;
}

void KeyManager::WriteHistory() {
  QSettings settings("FreeMat","FreeMat");
  QStringList historyList;
  for (int i=0;i<history.size();i++) 
    historyList << QString::fromStdString(history[i]);
  settings.setValue("interpreter/history",historyList);
}


syntax highlighted by Code2HTML, v. 0.9.1