
//  ------------------------------------------------------------------
//  GoldED+
//  Copyright (C) 1990-1999 Odinn Sorensen
//  Copyright (C) 1999-2000 Alexander S. Aganichev
//  ------------------------------------------------------------------
//  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
//  ------------------------------------------------------------------
//  $Id: geedit.cpp,v 1.45 2003/03/26 16:05:36 asa Exp $
//  ------------------------------------------------------------------
//  The Internal Editor (IE), part 1.
//  ------------------------------------------------------------------

#ifdef __GNUG__
#pragma implementation "geedit.h"
#endif

//  ------------------------------------------------------------------

#include <golded.h>
#include <geedit.h>


//  ------------------------------------------------------------------
//  Globals

Line* Edit__killbuf  = NULL;
Line* Edit__pastebuf = NULL;

Path Edit__exportfilename = {""};

UndoItem** UndoItem::last_item;


//  ------------------------------------------------------------------

#ifndef NDEBUG

void IEclass::debugtest(char* __test, int __a, int __b, char* __file, int __line, int __values) {

  #if defined(GFTRK_ENABLE)
  int _tmp = __gftrk_on;
  __gftrk_on = false;
  #endif

  savefile(MODE_UPDATE);

  #if defined(GFTRK_ENABLE)
  __gftrk_on = _tmp;
  #endif

  if(__values)
    w_infof(" Range check: (%s) <%i,%i> [%s,%u] ", __test, __a, __b, __file, __line);
  else
    w_infof(" Range check: (%s) [%s,%u] ", __test, __file, __line);
  update_statusline(LNG->EscOrContinue);
  SayBibi();
  while(kbxhit())
    kbxget();
  gkey _key = kbxget();
  w_info(NULL);
  if(_key == Key_Esc) {
    LOG.errtest(__file, __line);
    LOG.printf("! An internal editor range check failed.");
    if(__values)
      LOG.printf(": Details: (%s) <%i,%i>.", __test, __a, __b);
    else {
      LOG.printf(": Details: (%s).", __test);
      LOG.printf(": Details: r%u,c%u,mr%u,mc%u,i%u,dm%u,qm%u,eqm%u.",
                 row, col, maxrow, maxcol, insert,
                 CFG->dispmargin, CFG->quotemargin, EDIT->QuoteMargin());
    }
    LOG.printf("+ Advice: Report to the Author.");
    TestErrorExit();
  }
}

#endif


//  ------------------------------------------------------------------
//  Make sure line type is correct

void IEclass::setlinetype(Line* __line) {

  _test_halt(__line == NULL);

  __line->type &= ~(GLINE_ALL|GLINE_TEAR|GLINE_ORIG|GLINE_TAGL);
  __line->type |= is_quote(__line->txt.c_str()) ? GLINE_QUOT : (__line->txt[0] == CTRL_A) ? GLINE_HIDD : 0;
}


//  ------------------------------------------------------------------
//  Insert character in string at position

//  ------------------------------------------------------------------
//  Zero-based

int IEclass::dispchar(vchar __ch, int attr) {

  if(__ch == NUL) // possible if line empty
    __ch = ' ';
  if(__ch == '\n') {
    __ch = EDIT->CharPara();
  }
  else if(__ch == ' ') {
    __ch = EDIT->CharSpace();
  }

  int atr;
  vchar chr;
  editwin.getc(crow, ccol, &atr, &chr);
  editwin.printc(crow, ccol, attr == -1 ? atr : attr, __ch);
  return atr;
}


//  ------------------------------------------------------------------

void IEclass::cursoroff() { 

  vcurhide();
}


//  ------------------------------------------------------------------

void IEclass::cursoron() { 

  vcurshow();
}


//  ------------------------------------------------------------------
//  Zero-based

void IEclass::scrollup(int __scol, int __srow, int __ecol, int __erow, int __lines) {

  editwin.scroll_box_up(__srow, __scol, __erow, __ecol, __lines);
}


//  ------------------------------------------------------------------
//  Zero-based

void IEclass::scrolldown(int __scol, int __srow, int __ecol, int __erow, int __lines) {

  editwin.scroll_box_down(__srow, __scol, __erow, __ecol, __lines);
}


//  ------------------------------------------------------------------
//  Zero-based

void IEclass::prints(int wrow, int wcol, int atr, const char* str) {

  editwin.prints(wrow, wcol, atr, str);
}


//  ------------------------------------------------------------------

Line* IEclass::findfirstline() {

  GFTRK("Editfindfirstline");

  if(not currline)
    return NULL;

  // Rewind to the first line
  Line* _firstline = currline;
  while(_firstline->prev)
    _firstline = _firstline->prev;

  GFTRK(NULL);

  return _firstline;
}


//  ------------------------------------------------------------------
//  Find out what number the current line is and put it in "thisrow"

void IEclass::getthisrow(Line* __currline) {

  GFTRK("Editgetthisrow");

  Line* _templine = findfirstline();

  thisrow = 0;
  while((_templine != __currline) and _templine->next) {
    _templine = _templine->next;
    thisrow++;
  }

  GFTRK(NULL);
}


//  ------------------------------------------------------------------
//  Zero-based

void IEclass::gotorowcol(uint __col, uint __row) {

  GFTRK("Editgotorowcol");

  _test_haltab(__col > maxcol, __col, maxcol);
  _test_haltab(__row > maxrow, __row, maxrow);

  editwin.move_cursor(__row, __col);

  ccol = __col;
  crow = __row;

  GFTRK(NULL);
}


//  ------------------------------------------------------------------

void IEclass::dispstring(const char* __string, uint __row, int attr, Line* line) {

  GFTRK("Editdispstring");

  _test_halt(__string == NULL);
  _test_haltab(__row > maxrow, __row, maxrow);

  // Get string length
  uint _length = strlen(__string);

  // String longer than window width?
  _test_haltab(_length > (maxcol+1), _length, (maxcol+1));
  _length = MinV(_length, (maxcol+1));

  // Buffer for translation to visual representation
  char _buf[EDIT_BUFLEN];

  // Space-pad and nul-terminate the buffer
  memset(_buf, ' ', maxcol+1);
  _buf[maxcol+1] = NUL;

  // Copy/translate string into buffer
  if(attr == -1) {
    char* _bufptr = _buf;
    uint _position = 0;
    const char* __str = __string;
    while(_position < _length) {
      switch(*__str) {
        case ' ':   *_bufptr = EDIT->CharSpace();  break;
        case '\n':  *_bufptr = EDIT->CharPara();   break;
        default:    *_bufptr = *__str;
      }
      _position++;
      _bufptr++;
      __str++;
    }
  }
  else {
    if(_length)
      memcpy(_buf, __string, _length);
  }

  // mark selected block
  if(line and (blockcol != -1)) {
    int selected = 0;
    for(Line *ln = findfirstline(); ln and ln != line; ln = ln->next) {
      if(ln == currline)
        selected ^= 1;
      if(ln->type & GLINE_BLOK)
        selected ^= 1;
    }
    if((line->type & GLINE_BLOK) and (line == currline)) {
      int begblock = ((col < blockcol) ? col : blockcol) - mincol;
      int endblock = ((col > blockcol) ? col : blockcol) - mincol;

      char savechar = _buf[begblock];
      _buf[begblock] = NUL;
      StyleCodeHighlight(_buf, __row, mincol, false, attr);
      _buf[begblock] = savechar;

      savechar = _buf[endblock];
      _buf[endblock] = NUL;
      bool oldusestylies = CFG->usestylies;
      bool oldhighlighturls = CFG->highlighturls;
      CFG->usestylies = false;
      CFG->highlighturls = false;
      StyleCodeHighlight(_buf+begblock, __row, mincol+begblock, false, C_READA);
      CFG->usestylies = oldusestylies;
      CFG->highlighturls = oldhighlighturls;
      _buf[endblock] = savechar;
      StyleCodeHighlight(_buf+endblock, __row, mincol+endblock, false, attr);
    }
    else if((line->type & GLINE_BLOK) or (line == currline)) {
      int blockmark = ((line->type & GLINE_BLOK) ? blockcol : col) - mincol;

      char savechar = _buf[blockmark];
      _buf[blockmark] = NUL;
      bool oldusestylies = CFG->usestylies;
      bool oldhighlighturls = CFG->highlighturls;
      if(selected) {
        CFG->usestylies = false;
        CFG->highlighturls = false;
      }
      StyleCodeHighlight(_buf, __row, mincol, false, selected ? C_READA : attr);
      CFG->usestylies = oldusestylies;
      _buf[blockmark] = savechar;
      if(not selected) {
        CFG->usestylies = false;
        CFG->highlighturls = false;
      }
      StyleCodeHighlight(_buf+blockmark, __row, mincol+blockmark, false, selected ? attr : C_READA);
      CFG->usestylies = oldusestylies;
      CFG->highlighturls = oldhighlighturls;
    }
    else {
      bool oldusestylies = CFG->usestylies;
      bool oldhighlighturls = CFG->highlighturls;
      if(selected) {
        CFG->usestylies = false;
        CFG->highlighturls = false;
      }
      StyleCodeHighlight(_buf, __row, mincol, false, selected ? C_READA : attr);
      CFG->usestylies = oldusestylies;
      CFG->highlighturls = oldhighlighturls;
    }
  }
  else
    StyleCodeHighlight(_buf, __row, mincol, false, attr);

  GFTRK(NULL);
}


//  ------------------------------------------------------------------

void IEclass::setcolor(Line* __line) {

  // Set window attribute
  if(__line->type & GLINE_HIDD)
    editwin.text_color(C_READKH);
  else if(__line->type & GLINE_KLUD)
    editwin.text_color(C_READK);
  else if(__line->type & GLINE_TAGL)
    editwin.text_color(C_READG);
  else if(__line->type & GLINE_TEAR)
    editwin.text_color(C_READT);
  else if(__line->type & GLINE_ORIG)
    editwin.text_color(C_READO);
  else if(__line->type & GLINE_QUOT)
    editwin.text_color(quotecolor(__line->txt.c_str()));
  else if(__line->type & GLINE_SIGN)
    editwin.text_color(C_READS);
  else
    editwin.text_color(C_READW);

}


//  ------------------------------------------------------------------
//  Zero-based

void IEclass::displine(Line* __line, uint __row) {

  GFTRK("Editdispline");

  _test_halt(__line == NULL);
  _test_haltab(__row > maxrow, __row, maxrow);

  // Display line
  setcolor(__line);
  dispstring(__line->txt.c_str(), __row, -1, __line);

  GFTRK(NULL);
}


//  ------------------------------------------------------------------
//  Zero-based

void IEclass::clreol(int __col, int __row) {

  GFTRK("Editclreol");

  if(__col == -1)
    __col = ccol;

  if(__row == -1)
    __row = crow;

  if((uint)__col <= maxcol)
    editwin.fill(__row, __col, __row, maxcol, ' ', C_READW);

  GFTRK(NULL);
}


//  ------------------------------------------------------------------
//  Zero-based

void IEclass::refresh(Line* __currline, uint __row) {

  GFTRK("Editrefresh");

  _test_halt(__currline == NULL);

  cursoroff();

  // Display as many lines as we can
  while(__currline and (__row <= maxrow)) {
    displine(__currline, __row++);
    __currline = __currline->next;
  }

  // If we ran out of lines, blank the rest
  if(__row <= maxrow) {
    vchar vbuf[256];
    for(int c = 0; c < maxcol+1; c++)
      vbuf[c] = _box_table(W_BREAD, 1);
    vbuf[maxcol+1] = NUL;
    wprintvs(__row++, mincol, C_READB|ACSET, vbuf);
    while(__row <= maxrow)
      dispstring("", __row++);
  }

  GFTRK(NULL);
}


//  ------------------------------------------------------------------

Line* IEclass::insertlinebelow(Line* __currline, const char* __text, long __batch_mode) {

  GFTRK("Editinsertlinebelow");

  Line* _nextline = new Line(__text ? __text : "");
  throw_xnew(_nextline);

  if(__currline) {

    _nextline->prev = __currline;
    _nextline->next = __currline->next;
    if(_nextline->next)
      _nextline->next->prev = _nextline;
    __currline->next = _nextline;
  }
  
  Undo->PushItem(EDIT_UNDO_NEW_LINE|batch_mode|__batch_mode, _nextline);

  GFTRK(NULL);

  return _nextline;
}


//  ------------------------------------------------------------------
//  Zero-based

int IEclass::downoneline(uint __row) {

  GFTRK("Editdownoneline");

  _test_haltab(__row > maxrow, __row, maxrow);

  thisrow++;

  if(__row == maxrow)
    scrollup(mincol, minrow, maxcol, maxrow);
  else
    __row++;

  gotorowcol(mincol, __row);

  GFTRK(NULL);

  return __row;
}


//  ------------------------------------------------------------------

void IEclass::GoEOL() {

  GFTRK("EditGoEOL");

  // Move cursor to the last char on the line
  col = currline->txt.length();
  if((currline->txt[col-1] == '\n') or ((col != mincol) and (currline->txt[col-1] == ' ')))
    --col;

  // String must not be longer than the window width
  _test_haltab(col > maxcol, col, maxcol);

  if(blockcol != -1)
    displine(currline, row);

  GFTRK(NULL);
}


//  ------------------------------------------------------------------

void IEclass::GoUp() {

  GFTRK("EditGoUp");

  _test_haltab(row < minrow, row, minrow);

  if(currline->prev) {

    currline = currline->prev;
    thisrow--;

    if(row == minrow) {
      scrolldown(mincol, row, maxcol, maxrow);
      if(blockcol != -1)
        displine(currline->next, row+1);
      displine(currline, row);
    }
    else {
      row--;
      if(blockcol != -1) {
        displine(currline->next, row+1);
        displine(currline, row);
      }
    }

    if((col+1) > currline->txt.length())
      GoEOL();
  }

  GFTRK(NULL);
}


//  ------------------------------------------------------------------

void IEclass::GoDown() {

  GFTRK("EditGoDown");

  _test_haltab(row > maxrow, row, maxrow);

  if(currline->next) {

    currline = currline->next;
    thisrow++;

    if(row == maxrow) {
      scrollup(mincol, minrow, maxcol, maxrow);
      if(blockcol != -1)
        displine(currline->prev, row-1);
      displine(currline,row);
    }
    else {
      row++;
      if(blockcol != -1) {
        displine(currline->prev, row-1);
        displine(currline, row);
      }
    }

    if((col+1) > currline->txt.length())
      GoEOL();
  }

  GFTRK(NULL);
}


//  ------------------------------------------------------------------

void IEclass::GoLeft() {

  GFTRK("EditGoLeft");

  _test_haltab(col < mincol, col, mincol);

  if(col == mincol) {
    if(currline->prev) {
      GoUp();
      GoEOL();
      if((col != mincol) and (currline->txt.c_str()[col] == NUL))
        col--;
    }
  }
  else
    col--;

  if(blockcol != -1)
    displine(currline, row);

  GFTRK(NULL);
}


//  ------------------------------------------------------------------

void IEclass::GoRight() {

  GFTRK("EditGoRight");

  _test_haltab(col > maxcol, col, maxcol);

  if((col == maxcol) or ((col+1) >= currline->txt.length())) {
    if(currline->next != NULL) {
      GoDown();
      col = mincol;
    }
  }
  else
    col++;

  if(blockcol != -1)
    displine(currline, row);

  GFTRK(NULL);
}


//  ------------------------------------------------------------------

Line* IEclass::wrapit(Line** __currline, uint* __curr_col, uint* __curr_row, bool __display) {

  _test_halt(__currline == NULL);
  _test_halt(*__currline == NULL);

  int scroll = 0;

  uint  _quotelen;
  char  _quotebuf[MAXQUOTELEN];
  *_quotebuf = NUL;

  uint  _curscol = *__curr_col;
  uint  _cursrow = *__curr_row;

  uint  _thisrow = *__curr_row;
  Line* _thisline = *__currline;
  Line* _lastadded = _thisline;

  bool _wrapped_above = false;

  // Start wrapping from the current line onwards
  while(_thisline) {

    // Length of this line
    uint _thislen = _thisline->txt.length();

    uint _wrapmargin = (_thisline->type & GLINE_QUOT) ? marginquotes : margintext;

    // Does this line need wrapping?
    if((_thislen > _wrapmargin) or ((_thislen == _wrapmargin) and not ((_thisline->txt[_thislen-1] == ' ') or (_thisline->txt[_thislen-1] == '\n')))) {

      // Reset quote string length
      _quotelen = 0;

      // Is this line quoted?
      if(_thisline->type & GLINE_QUOT) {

        // Get quote string and length
        GetQuotestr(_thisline->txt.c_str(), _quotebuf, &_quotelen);
      }

      // wrapmargin = 40
      //                   1111111111222222222233333333334444444444
      //         01234567890123456789012345678901234567890123456789
      //         --------------------------------------------------
      //                                                v- wrapptr
      // Case 1: this is another test with a bit of text to wrap.
      // Case 2: this is a test with a line that need wrapping.
      // Case 3: thisxisxaxtestxwithxaxlinexthatxneedxwrapping.
      // Case 4: >thisxisxaxtestxwithxaxlinexthatxneedxwrapping.

      // Point to the last char inside the margin
      int _wrappos = _wrapmargin - 1;

      // Locate the word to be wrapped

      // Did we find a space?
      if(_thisline->txt[_wrappos] == ' ') {

        // Case 1: A space was found as the last char inside the margin
        //
        // Now we must locate the first word outside the margin.
        // NOTE: Leading spaces to this word will be nulled out!

        // Begin at the first char outside the margin
        if(_wrappos <= maxcol)
          _wrappos++;
      }
      else {

        // Case 2: A non-space was found as the last char inside the margin
        //
        // Now we must locate the beginning of the word we found.

        // Keep copy of original pointer
        int _atmargin = _wrappos;

        // Search backwards until a space or the beginning of the line is found 
        while((_wrappos > _quotelen) and (_thisline->txt[_wrappos-1] != ' '))
          _wrappos--;

        // Check if we hit leading spaces
        int _spacepos = _wrappos;
        while(_spacepos > 0) {
          _spacepos--;
          if(_thisline->txt[_spacepos] != ' ')
            break;
        }

        // Did we search all the way back to the beginning of the line?
        if((_wrappos == _quotelen) or (_thisline->txt[_spacepos] == ' ')) {

          // Case 3: There are no spaces within the margin or we hit leading spaces

          // We have to break it up at the margin
          _wrappos = _atmargin;
        }
      }

      // The wrappos now points to the location to be wrapped or NUL

      // Is the line hard-terminated?
      if(_thisline->txt.find('\n', _wrappos) != _thisline->txt.npos) {

        // The line is hard-terminated.

        // Copy the quote string, if any, to the new line first
        std::string _wrapbuf = _quotebuf;

        // Copy/append the wrapped part to the new line
        _wrapbuf += _thisline->txt.substr(_wrappos);

        // The wrapped part must be placed on a new line below.
        Line* _wrapline = _lastadded = insertlinebelow(_thisline, _wrapbuf.c_str(), BATCH_MODE);

        // Saves pointer to a line where from the wrapped part was copied, its begining
        // and length. While in Undo, appends the copied part to previous line and deletes
        // it on current, moving the rest over deleted.
        Undo->PushItem(EDIT_UNDO_WRAP_TEXT|BATCH_MODE, _thisline, _quotelen, _wrapbuf.length() - _quotelen);

        _wrapline->type = _thisline->type;
        // Make sure the type of the line is correct
        setlinetype(_wrapline);

        if(__display) {

          // Is there at least one line below this one?
          if(_thisrow < maxrow) {

            // Scroll the lines below to make room for the new line
            --scroll;

            // Display the new line
            if(_wrapline->txt.length() <= (maxcol+1))
              displine(_wrapline, _thisrow+1);
          }
        }
      }
      else {

        // The line is not hard-terminated
        //
        // The wrapped part must be inserted into the next line

        // Indicate that one or more lines were wrapped in this way
        _wrapped_above = true;

        // Pointer to the next line
        Line* _nextline = _thisline->next;

        // Copy the quote string, if any, to the new line first
        std::string _wrapbuf = _quotebuf;

        // Copy/append the wrapped part to the new line
        _wrapbuf += _thisline->txt.substr(_wrappos);

        // Flag to indicate if a new line was added below
        bool _line_added_below = false;

        // Is there no next line or is the next line quoted?
        if((_nextline == NULL) or (_nextline->type & GLINE_QUOT)) {

          // The wrapped part must be placed on a new line below
          _lastadded = _nextline = insertlinebelow(_thisline, _wrapbuf.c_str(), BATCH_MODE);
          _line_added_below = true;

          Undo->PushItem(EDIT_UNDO_WRAP_TEXT|BATCH_MODE, _thisline, _quotelen, _wrapbuf.length() - _quotelen);
        }
        else {

          _nextline->txt.insert(0, _wrapbuf);
          if(_quotelen)
            Undo->PushItem(EDIT_UNDO_INS_TEXT|BATCH_MODE, _nextline, 0, _quotelen);
          Undo->PushItem(EDIT_UNDO_WRAP_TEXT|BATCH_MODE, _thisline, _quotelen, _wrapbuf.length() - _quotelen);
        }

        // Make sure the type of the line is correct
        setlinetype(_nextline);

        if(__display) {

          // Is there at least one line below this one?
          if(_line_added_below and (_thisrow < maxrow)) {

            // Scroll the lines below to make room for the new line
            --scroll;
          }

          // Display the new/wrapped line
          if((_thisrow+1) <= maxrow and _nextline->txt.length() <= (maxcol+1))
            displine(_nextline, _thisrow+1);
        }
      }

      // Was this line truncated at space?
      bool truncated_at_space = isspace(_thisline->txt[_wrappos]);

      // Truncate at the wrapping location
      _thisline->txt.erase(_wrappos);

      // Was this line quoted?
      if(_quotelen) {

        // Trim spaces off the end of the line
        int _trimpos = _wrappos - 1;
        if(isspace(_thisline->txt[_trimpos])) {
          while(_trimpos > 0 and isspace(_thisline->txt[_trimpos-1]))
            _trimpos--;
          if(_quotelen and (_trimpos < _quotelen))
            _trimpos++;
          Undo->PushItem(EDIT_UNDO_OVR_CHAR|BATCH_MODE, _thisline, _trimpos);
          _thisline->txt.erase(_trimpos);
        }
        Undo->PushItem((truncated_at_space?EDIT_UNDO_OVR_CHAR:EDIT_UNDO_INS_CHAR)|BATCH_MODE, _thisline, _trimpos+1);

        // Append a new linefeed
        _thisline->txt += "\n";
      }

      // Make sure the line type still is correct
      setlinetype(_thisline);

      if(__display) {

        // Display this line after wrapping
        if(_thisrow <= maxrow)
          displine(_thisline, _thisrow);
      }

      _thislen = _thisline->txt.length();

      // If we are on the cursor line, check if the cursor char was wrapped
      if((_thisrow == _cursrow) and (_thislen <= _curscol)) {
        _curscol = _quotelen + ((_curscol > _wrappos) ? _curscol-_wrappos : 0);
        _cursrow++;
        UndoItem* i = Undo->last_item;
        do { i = i->prev; } while(i->action & BATCH_MODE);
        if(i->col.num >= i->line->txt.length()) {
          i->action |= PREV_LINE;
          i->col.sav = i->col.num;
          i->col.num = _curscol;
        }
      }
    }

    // If this line is hard-terminated, we have finished wrapping
    // Unless the next line has grown too large
    if(_thisline->txt.find('\n') != _thisline->txt.npos) {
      if(_thisline->next == NULL)
        break;
      _wrapmargin = (_thisline->next->type & GLINE_QUOT) ? marginquotes : margintext;
      if(_thisline->next->txt.length() <= _wrapmargin)
        break;
    }
    else if(_thisline->type & GLINE_QUOT) {
      Undo->PushItem(EDIT_UNDO_INS_TEXT|BATCH_MODE, _thisline, _thisline->txt.length(), 1);
      _thisline->txt += '\n';
    }

    // Go to the next line
    _thisline = _thisline->next;
    _thisrow++;
  }

  if(__display) {

    // Display the current line after wrapping
    if(*__curr_row <= maxrow)
      displine(*__currline, *__curr_row);

    // Was the line or lines above wrapped?
    if(_wrapped_above) {

      // Display the last line in the paragraph
      if((_thisrow <= maxrow) and _thisline)
        displine(_thisline, _thisrow);
    }
  }

  // Move to the next line if the cursor was wrapped
  if(_cursrow != *__curr_row) {
    int i = *__curr_row;
    while ((i++ != _cursrow) and (__currline != NULL))
      *__currline = (*__currline)->next;
    if(_cursrow > maxrow) {
      _cursrow = maxrow;
      if(__display) {
        ++scroll;
        if(scroll > 0) {
          scrollup(mincol, minrow, maxcol, maxrow);
          scroll = 0;
        }
        displine(*__currline, *__curr_row);
      }
    }
  }

  if(__display and (scroll != 0)) {
    if((*__currline)->next != NULL)
      refresh((*__currline)->next, _cursrow+1);
    else
      refresh(*__currline, _cursrow);
  }

  // Update cursor position
  *__curr_row = _cursrow;
  *__curr_col = _curscol;

  GFTRK(NULL);

  return _lastadded;
}


//  ------------------------------------------------------------------

Line* IEclass::wrapdel(Line** __currline, uint* __curr_col, uint* __curr_row, bool __display) {

  GFTRK("Editwrapdel");

  Line *tmp = wrapit(__currline, __curr_col, __curr_row, __display);

  GFTRK(NULL);

  return tmp;
}


//  ------------------------------------------------------------------

Line* IEclass::wrapins(Line** __currline, uint* __curr_col, uint* __curr_row, bool __display) {

  GFTRK("Editwrapins");

  Line *tmp = wrapit(__currline, __curr_col, __curr_row, __display);

  GFTRK(NULL);

  return tmp;
}


//  ------------------------------------------------------------------

void IEclass::insertchar(char __ch) {

  GFTRK("Editinsertchar");

  uint _currline_len = currline->txt.length();
#ifndef NDEBUG
  _test_haltab(col > _currline_len, col, _currline_len);
#endif
  // Insert or overwrite the char, replacing the block if any
  if((selecting ? (BlockCut(true), batch_mode = BATCH_MODE) : false) or
     (col >= _currline_len) or (currline->txt[col] == '\n') or insert) {
    if(not isspace(__ch) and (col == mincol)) {
      // if previous line was wrapped on non-space character
      if(currline->prev and not currline->prev->txt.empty() and
         (currline->prev->txt.find('\n') == currline->prev->txt.npos) and
         not isspace(currline->prev->txt[currline->prev->txt.length()-1])) {
        if(not batch_mode) {
          Undo->PushItem(EDIT_UNDO_VOID);
          batch_mode = BATCH_MODE;
        }
        GoUp();
        GoEOL();
      }
    }
    Undo->PushItem(EDIT_UNDO_INS_CHAR|batch_mode);
    currline->txt.insert(col, 1, __ch);
  } else {
    Undo->PushItem(EDIT_UNDO_OVR_CHAR|batch_mode);
    currline->txt[col] = __ch;
  }
  batch_mode = BATCH_MODE;

  // Make sure the line type still is correct
  setlinetype(currline);

  // Move cursor
  col++;

  wrapins(&currline, &col, &row);

  // Adjust cursor position and display if necessary
  if(col > maxcol) {
    if(currline->next) {
      currline = currline->next;
      col = mincol;
      row++;
      if(row > maxrow) {
        row = maxrow;
        scrollup(mincol, minrow, maxcol, maxrow);
        displine(currline, row);
      }
    }
    else {
      col = maxcol;
    }
  }

  gotorowcol(col, row);

  GFTRK(NULL);
}


//  ------------------------------------------------------------------

void IEclass::DelChar() {

  GFTRK("EditDelChar");

  Line* _thisline = currline;
  Line* _nextline = currline->next;
  uint  _thislen = _thisline->txt.length();

  // Cannot delete at or beyond the nul-terminator
  if(col < _thislen) {
    Undo->PushItem(EDIT_UNDO_DEL_CHAR|batch_mode);
    _thisline->txt.erase(col, 1);
    batch_mode = BATCH_MODE;
  }
  else if(col and (col == _thislen) and _nextline) {
    GFTRK(NULL);
    if(not batch_mode) {
      Undo->PushItem(EDIT_UNDO_VOID);
      batch_mode = BATCH_MODE;
    }
    GoRight();
    DelChar();
    return;
  }

  // Did we delete the last char on the line or
  // was the cursor at or beyond the nul-terminator?
  // And is there a next line at all?
  if(((col+1) >= _thislen) and _nextline) {

    // Join the next line to this line

    // Is the next line quoted?
    // And is the cursor column non-zero?
    uint _quotelen = 0;
    if((_nextline->type & GLINE_QUOT) and col) {

      // Get quote string length
      char _quotebuf[MAXQUOTELEN];
      GetQuotestr(_nextline->txt.c_str(), _quotebuf, &_quotelen);
    }

    // Copy the next line's text to this line without quote string
    const char *_nexttext = _nextline->txt.c_str()+_quotelen;
    _thisline->txt += _nexttext + (col ? strspn(_nexttext, " ") : 0);

    Undo->PushItem(EDIT_UNDO_CUT_TEXT|batch_mode, _thisline, col);

    // Relink this line
    _thisline->next = _nextline->next;
    if(_thisline->next)
      _thisline->next->prev = _thisline;

    Undo->PushItem(EDIT_UNDO_DEL_LINE|BATCH_MODE, _nextline);
  }
  else if((_thislen > 1) and (_thisline->txt.c_str()[_thislen-2] != '\n') and _nextline and not (_nextline->type & GLINE_QUOT)) {

    _thisline->txt += _nextline->txt.c_str();

    Undo->PushItem(EDIT_UNDO_CUT_TEXT|batch_mode, _thisline, _thislen-1);

    // Relink this line
    _thisline->next = _nextline->next;
    if(_thisline->next)
      _thisline->next->prev = _thisline;

    Undo->PushItem(EDIT_UNDO_DEL_LINE|BATCH_MODE, _nextline);     
  }
  batch_mode = BATCH_MODE;

  // Make sure the line type still is correct
  setlinetype(_thisline);

  // Rewrap this line
  bool display = (row > maxrow / 2) ? false : true;
  wrapdel(&currline, &col, &row, display);
  if(display) {
    refresh(currline, row);
  }
  else {
    // Refresh the display
    Line* _topline = findtopline();
    refresh(_topline, minrow);
  }

  GFTRK(NULL);
}


//  ------------------------------------------------------------------

void IEclass::DelLeft() {

  GFTRK("EditDelLeft");

  // Cannot backspace from the first column on the first line in the msg
  if(currline->prev == NULL)
    if(col == mincol) {
      GFTRK(NULL);
      return;
    }

  // Go left(/up) and delete the character there
  if(not batch_mode) {
    if(col == mincol)
      Undo->PushItem(EDIT_UNDO_VOID|PREV_LINE, currline->prev);
    else
      Undo->PushItem(EDIT_UNDO_VOID);
    batch_mode = BATCH_MODE;
  }
  GoLeft();
  DelChar();

  GFTRK(NULL);
}


//  ------------------------------------------------------------------

void IEclass::GoWordLeft() {

  GFTRK("EditGoWordLeft");

  if(col == 0) {
    if(currline->prev) {
      GoUp();
      GoEOL();
    }
  }
  else {
 
    col--;
 
    if(not isxalnum(currline->txt[col])) {
      while(not isxalnum(currline->txt[col]) and (col > 0))
        col--;
      while(isxalnum(currline->txt[col]) and (col > 0))
        col--;
    }
    else {
      while(isxalnum(currline->txt[col]) and (col > 0))
        col--;
    }

    if(col != 0)
      col++;

    if(blockcol != -1)
      displine(currline, row);
  }

  GFTRK(NULL);
}


//  ------------------------------------------------------------------

void IEclass::GoWordRight() {

  GFTRK("EditGoWordRight");

  if((col >= currline->txt.length()) or (currline->txt[col] == '\n')) {
    if(currline->next) {
      GoDown();
      col = 0;
    }
  }
  else {
    size_t len = currline->txt.length();
    if(not isxalnum(currline->txt[col])) {
      while(not isxalnum(currline->txt[col]) and ((col+1) <= len))
        col++;
    }
    else {
      while(isxalnum(currline->txt[col]) and ((col+1) <= len))
        col++;
      while(not isxalnum(currline->txt[col]) and ((col+1) <= len))
        col++;
    }

    if(currline->txt[col-1] == '\n')
       col--;

    if(len == col) {
      if(currline->next) {
        GoDown();
        col = 0;
      }
      else
        col--;
    }

    if(blockcol != -1)
      displine(currline, row);
  }

  GFTRK(NULL);
}


//  ------------------------------------------------------------------

void IEclass::Newline() {

  GFTRK("EditNewline");

  // Pointer to the split position
  int _splitpos = col;

  // Buffer for the second part of the split line
  std::string _splitbuf;

  // If the split line was quoted, get the quotestring
  // But do not get it if the cursor points to a linefeed or is
  uint _quotelen;
  char _quotebuf[MAXQUOTELEN];
  GetQuotestr(currline->txt.c_str(), _quotebuf, &_quotelen);

  // Eliminate the quotestring if
  // - the cursor points to a linefeed or
  // - the cursor is located inside the quotestring
  if(_quotelen and ((currline->txt.length() == col) or (currline->txt[_splitpos] == '\n') or (col < _quotelen))) {
    *_quotebuf = NUL;
    _quotelen = 0;
  }

  // Append the second part to the split buffer
  _splitbuf = _quotebuf;
  _splitbuf += currline->txt.substr(_splitpos);

  Undo->PushItem(EDIT_UNDO_INS_TEXT|batch_mode, currline, col, 1);
  batch_mode = BATCH_MODE;
  
  // Copy linefeed+nul to the split position
  currline->txt.erase(_splitpos);
  currline->txt += "\n";

  // Re-type and display the split line
  setlinetype(currline);
  displine(currline, row);

  uint _quotelen1 = 0;
  char _quotebuf1[MAXQUOTELEN] = "";
  if(currline->next != NULL) {
    GetQuotestr(currline->next->txt.c_str(), _quotebuf1, &_quotelen1);
  }

  if((_splitbuf.find('\n') != _splitbuf.npos) or (currline->next == NULL) or not strieql(_quotebuf, _quotebuf1)) {
    // Insert a new line below, set the line text to the split-off part
    currline = insertlinebelow(currline, _splitbuf.c_str());

    //                  --v--
    // This line would be wrapped
    // This line would be
    // wrapped

    Undo->PushItem(EDIT_UNDO_WRAP_TEXT|BATCH_MODE, currline->prev, _quotelen, _splitbuf.length() - _quotelen);
  }
  else {
    currline = currline->next;

    currline->txt.insert(_quotelen1, _splitbuf.substr(_quotelen1));

    Undo->PushItem(EDIT_UNDO_WRAP_TEXT|BATCH_MODE, currline->prev, _quotelen1, _splitbuf.length() - _quotelen1);
  }
  setlinetype(currline);

  // Move down the cursor
  col = 0;
  row = downoneline(row);

  // Scroll the remaining lines if necessary
  if(row < maxrow)
    scrolldown(mincol, row, maxcol, maxrow);

  // Rewrap the split-off line
  wrapdel(&currline, &col, &row, true);
  refresh(currline, row);

  GFTRK(NULL);
}


//  ------------------------------------------------------------------

void IEclass::CopyAboveChar() {

  GFTRK("EditCopyAboveChar");

  char _ch = ' ';
  if(currline->prev) {
    uint _len = currline->prev->txt.length();
    if(_len and _len > col)
      _ch = currline->prev->txt[col];
    if(_ch == '\n')
      _ch = ' ';
  }
  insertchar(_ch);

  GFTRK(NULL);
}


//  ------------------------------------------------------------------

void IEclass::DupLine() {

  GFTRK("EditDupLine");

  Undo->PushItem(EDIT_UNDO_VOID);
  batch_mode = BATCH_MODE;
  
  Line* _nextline = insertlinebelow(currline, currline->txt.c_str(), batch_mode);
  _nextline->type   = currline->type & ~GLINE_BLOK;
  _nextline->color  = currline->color;
  _nextline->kludge = currline->kludge;
  refresh(currline, row);
  GoDown();

  GFTRK(NULL);
}


//  ------------------------------------------------------------------
//  PageUp behavior:
//
//  ... The top line becomes the bottom line.
//  ... Always remain at cursor row, except if we can't page up.
//  ... If we can't page up, move the cursor row to the top line.

void IEclass::GoPgUp() {

  GFTRK("EditGoPgUp");

  // Not at the first line in msg?
  if(currline->prev) {

    // Count lines
    int _count = row;

    // Move to the top line currently displayed
    Line* _topline = currline;
    while(_count and _topline->prev) {
      _topline = _topline->prev;
      _count--;
    }

    // The count must be zero at this point!
    _test_haltab(_count, _count, _count);

    // At we already fully at the top?
    if(_topline->prev == NULL) {

      // Yes, so move the cursor row to the top line
      row = minrow;
      currline = _topline;
      if(blockcol != -1)
        refresh(currline, row);
    }
    else {

      // We are not at the top, so continue with paging

      // Move a full page of lines, if possible
      _count = maxrow;
      while(_count and _topline->prev) {
        _topline = _topline->prev;
        _count--;
      }

      // Set the current line
      _count = row;
      currline = _topline;
      while(_count--)
        currline = currline->next;

      // Refresh display
      refresh(_topline, minrow);
    }
  }
  else {
    GoTopMsg();
  }

  if(col+1 > currline->txt.length())
    GoEOL();

  GFTRK(NULL);
}


//  ------------------------------------------------------------------
//  PageDown behavior:
//
//  ... The bottom line becomes the top line.
//  ... Always remain at cursor row, except if at the last line.
//  ... If at the last line, move cursor to the last window line and
//      display the lines above.
//  ... If there are too few lines to display after the page down, the
//      rest are displayed blank.

void IEclass::GoPgDn() {

  GFTRK("EditGoPgDn");

  // Not at the last line in the msg?
  if(currline->next) {

    // Go down to the last displayed line in the window
    uint _newrow = row, _oldrow = row;
    Line *oldcurrline = currline;
    while((_newrow < maxrow) and currline->next) {
      currline = currline->next;
      _newrow++;
    }

    // If there are more lines after the last line, start displaying from the top
    if(currline->next) {
      Line *_topline = currline;

      // Set current line
      _newrow = 0;
      while((_newrow < row) and currline->next) {
        currline = currline->next;
        _newrow++;
      }

      // Move cursor row if necessary
      if(_newrow < row)
        row = _newrow;

      refresh(_topline, minrow);
    }
    else {
      row = _newrow;
      refresh(oldcurrline, _oldrow);
    }
  }
  else {
    GoBotMsg();
  }

  if(col+1 > currline->txt.length())
    GoEOL();

  GFTRK(NULL);
}


//  ------------------------------------------------------------------

void IEclass::Tab() {

  GFTRK("EditTab");

  int tabsz = CFG->disptabsize ? CFG->disptabsize : 1;

  // Move to the next tab position
  do {
    if(insert)
      insertchar(' ');
    else if(currline->txt[col] != '\n')
      GoRight();
    else
      break;
  } while(col % tabsz);

  GFTRK(NULL);
}


//  ------------------------------------------------------------------

void IEclass::ReTab() {

  GFTRK("EditTabReverse")

  int tabsz = CFG->disptabsize ? CFG->disptabsize : 1;

  // Move to the next tab position
  do {
    if(not col)
      break;

    if(insert)
      DelLeft();
    else
      GoLeft();
  } while(col % tabsz);

  GFTRK(NULL);
}


//  ------------------------------------------------------------------

void IEclass::DeleteEOL() {

  GFTRK("EditDeleteEOL");

  bool _has_linefeed = (currline->txt.find('\n') != currline->txt.npos) ? true : false;
 
  Undo->PushItem(EDIT_UNDO_DEL_TEXT, currline);

  currline->txt.erase(col);

  if(_has_linefeed) {
    Undo->PushItem(EDIT_UNDO_INS_CHAR|BATCH_MODE);
    currline->txt += "\n";
  }

  clreol();

  GFTRK(NULL);
}


//  ------------------------------------------------------------------

void IEclass::DeleteSOL() {

  GFTRK("EditDeleteSOL");

  if(col == 0) {
    GFTRK(NULL);
    return;
  }

  int _oldcol = col;

  col = 0;

  Undo->PushItem(EDIT_UNDO_DEL_TEXT, currline, col, _oldcol);
  currline->txt.erase(col, _oldcol);

  wrapdel(&currline, &col, &row);

  GFTRK(NULL);
}


//  ------------------------------------------------------------------

void IEclass::deleteline(bool zapquotesbelow) {

  GFTRK("Editdeleteline");

  bool done = false;

  do {
 
    // Break if need to zap quotes, but the current line is not quote and is not empty
    if(zapquotesbelow and not ((currline->type & GLINE_QUOT) or isempty(currline)))
      break;

    // Pointer to the deleted line
    Line* _deletedline = currline;

    // If last line to be deleted delete to EOL and exit
    if(currline->next == NULL) {
      if(isempty(currline))
        break;
      insertlinebelow(currline, "", batch_mode);
      batch_mode = BATCH_MODE;
      done = true;
    }

    // Pointers to the previous and next lines
    Line* _prevline = currline->prev;
    Line* _nextline = currline->next;

    Undo->PushItem(EDIT_UNDO_PUSH_LINE|batch_mode);

    // Set the new current line to the next line (which may be NULL!)
    currline = _nextline;

    if(currline == NULL) {
      currline = _prevline;
      currline->next = NULL;
      _prevline = _prevline ? _prevline->prev : NULL;
    }

    // Link the new current line to the previous line
    currline->prev = _prevline;

    // Link the previous line to this line
    if(_prevline)
      _prevline->next = currline;

    if(_deletedline->type & GLINE_BLOK) {
      blockcol = -1;
      _deletedline->type &= ~GLINE_BLOK;
    }

    // Link the deleted line to the killbuffer
    if(Edit__killbuf) {
      Edit__killbuf->next = _deletedline;
      _deletedline->prev = Edit__killbuf;
      Edit__killbuf = _deletedline;
    }
    else {
      Edit__killbuf = _deletedline;
      Edit__killbuf->prev = NULL;
    }
    Edit__killbuf->next = NULL;

    // Move the cursor to EOL if necessary
    if(col+1 > currline->txt.length())
      GoEOL();

    if(not zapquotesbelow)
      break;

    batch_mode = BATCH_MODE;

  } while(not done);

  // Refresh display from cursor row
  refresh(currline, row);

  GFTRK(NULL);
}


//  ------------------------------------------------------------------

void IEclass::UnDelete(bool before) {

  GFTRK("EditUnDelete");

  // If there are deleted lines
  if(Edit__killbuf) {

    Line* _prevline = Edit__killbuf->prev;
    bool down = false;

    if(before) {
      Edit__killbuf->prev = currline ? currline->prev : NULL;
      Edit__killbuf->next = currline;
    }
    else {
      Edit__killbuf->prev = currline;
      Edit__killbuf->next = currline ? currline->next : NULL;
      if(row == maxrow)
        down = true;
      else if((row < maxrow) and currline)
        row++;
    }

    if(Edit__killbuf->prev)
      Edit__killbuf->prev->next = Edit__killbuf;
    if(Edit__killbuf->next)
      Edit__killbuf->next->prev = Edit__killbuf;
    currline = Edit__killbuf;
    Edit__killbuf = _prevline;
    if(Edit__killbuf)
      Edit__killbuf->next = NULL;

    Undo->PushItem(EDIT_UNDO_POP_LINE);

    // Move the cursor to EOL if necessary
    if(col+1 > currline->txt.length())
      GoEOL();

    if(down)
      GoDown();
    else
      refresh(currline, row);
  }

  GFTRK(NULL);
}


//  ------------------------------------------------------------------

void IEclass::ZapQuoteBelow() {

  GFTRK("ZapQuoteBelow");

  if(currline->prev)
    Undo->PushItem(EDIT_UNDO_VOID|PREV_LINE|batch_mode, currline->prev);
  else
    Undo->PushItem(EDIT_UNDO_VOID|batch_mode, currline);
  batch_mode = BATCH_MODE;
  UndoItem* item_to_fix = Undo->last_item;

  deleteline(true);
  if(row == 0) {
    item_to_fix->line = currline;
  }

  GFTRK(NULL);
}


//  ------------------------------------------------------------------

Line* IEclass::findtopline() {

  GFTRK("Editfindtopline");

  uint  _toprow  = row;
  Line* _topline = currline;

  while(_topline->prev and (_toprow > minrow)) {
    _topline = _topline->prev;
    _toprow--;
  }

  GFTRK(NULL);

  return _topline;
}


//  ------------------------------------------------------------------

void IEclass::savefile(int __status) {

  Subj statbak;

  GFTRK("Editsavefile");

  // Turn off cursor and put up a wait window
  int wascursoron = not vcurhidden();
  cursoroff();

  strcpy(statbak, information);
  update_statusline(LNG->Wait+1);

  // Open the save file
  const char* editorfile = AddPath(CFG->goldpath, EDIT->File());
  remove(editorfile);
  FILE* _fp = fsopen(editorfile, "wb", CFG->sharemode);
  if(_fp) {

    // Find the first line
    Line* _saveline = findfirstline();

    // First save the "unfinished" identifier
    if(__status == MODE_UPDATE) {
      fputs(unfinished, _fp);
      fputs("\r\n", _fp);
    }

    // Save as whole paragraphs
    while(_saveline) {

      // Copy the line to a buffer
      char _buf[EDIT_BUFLEN];
      strcpy(_buf, _saveline->txt.c_str());

      // If a LF was found, replace it with a CR/LF combo
      char* _lfptr = strchr(_buf, '\n');
      if(_lfptr)
        strcpy(_lfptr, "\r\n");

      // Save the line
      fputs(_buf, _fp);

      // Continue with the next line
      _saveline = _saveline->next;
    }

    // Close save file and remove wait window
    fclose(_fp);
  }

  update_statusline(statbak);

  if(wascursoron)
    cursoron();

  GFTRK(NULL);
}


//  ------------------------------------------------------------------

void IEclass::SaveFile() {

  GFTRK("EditSaveFile");

  savefile(MODE_UPDATE);

  GFTRK(NULL);
}


//  ------------------------------------------------------------------

void IEclass::SaveMsg() {

  GFTRK("EditSaveMsg");

  done = MODE_SAVE;

  GFTRK(NULL);
}


//  ------------------------------------------------------------------

int IEclass::isempty(Line* __line) {

  if(__line == NULL)
    __line = currline;

  return (__line->txt.empty() or __line->txt[0] == '\n');
}


//  ------------------------------------------------------------------

int IEclass::reflowok(char* __qstr) {

  // Stop reflow if there is no next line
  if(currline->next == NULL)
    return false;

  // Stop reflow if the next line is empty
  if(isempty(currline->next))
    return false;

  // Stop reflow if the next line is a control line
  if(currline->next->type & (GLINE_KLUDGE|GLINE_TEAR|GLINE_ORIG|GLINE_TAGL))
    return false;

  // Stop reflow if the quotestring on the next line is not the same
  uint _qlen2;
  char _qstr2[MAXQUOTELEN];
  GetQuotestr(currline->next->txt.c_str(), _qstr2, &_qlen2);
  if(not cmp_quotes(__qstr, _qstr2))
    return false;

  return true;
}


//  ------------------------------------------------------------------

void IEclass::Reflow() {

  GFTRK("EditReflow");

  // Skip empty lines
  while(isempty()) {
    if(currline->next) {
      if(not batch_mode) {
        Undo->PushItem(EDIT_UNDO_VOID);
        batch_mode = BATCH_MODE;
      }
      GoDown();
    }
    else {
      GFTRK(NULL);
      return;
    }
  }

  // Get the first quotestring
  uint _qlen1;
  char _qstr1[MAXQUOTELEN];
  GetQuotestr(currline->txt.c_str(), _qstr1, &_qlen1);
  const char* _qlenptr = currline->txt.c_str() + _qlen1;

  // Strip leading spaces from the first line
  const char* ptr = _qlenptr;
  while(*ptr and isspace(*ptr) and (*ptr != '\n')) ptr++;
  if(ptr != _qlenptr) {
    Undo->PushItem(EDIT_UNDO_DEL_TEXT|batch_mode, currline, _qlen1, ptr-_qlenptr);
    currline->txt.erase(_qlen1, ptr-_qlenptr);
  }

  // Perform the reflow
  while(reflowok(_qstr1)) {

    // Work on the current line until it is done
    Line* _thisline = currline;
    while(_thisline == currline) {

      // Stop reflow?
      if(not reflowok(_qstr1))
        break;

      // Go to the EOL, insert a space and delete the LF
      GoEOL();
      if(col+1 < maxcol) {
        insertchar(' ');
        DelChar();
      }
      else {
        GoDown();
        col = mincol;
      }
    }
  }

  // Go to the next line
  displine(currline,row);
  GoDown();

  GFTRK(NULL);
}


//  ------------------------------------------------------------------

void IEclass::ExitMsg() {

  GFTRK("EditExitMsg");

  done = MODE_QUIT;

  GFTRK(NULL);
}


//  ------------------------------------------------------------------

void IEclass::DelLine() {

  GFTRK("EditDelLine");

  cursoroff();
  deleteline();

  GFTRK(NULL);
}


//  ------------------------------------------------------------------

void IEclass::ToUpper() {

  GFTRK("EditToUpper");

  if(col < currline->txt.length()) {
    Undo->PushItem(EDIT_UNDO_OVR_CHAR);
    currline->txt[col] = toupper(currline->txt[col]);
  }

  GFTRK(NULL);
}


//  ------------------------------------------------------------------

void IEclass::ToLower() {

  GFTRK("EditToLower");

  if(col < currline->txt.length()) {
    Undo->PushItem(EDIT_UNDO_OVR_CHAR);
    currline->txt[col] = tolower(currline->txt[col]);
  }

  GFTRK(NULL);
}


//  ------------------------------------------------------------------

void IEclass::ToggleCase() {

  GFTRK("EditToggleCase");

  if(col < currline->txt.length()) {
    Undo->PushItem(EDIT_UNDO_OVR_CHAR);
    if(toupper(currline->txt[col]) == currline->txt[col])
      currline->txt[col] = tolower(currline->txt[col]);
    else
      currline->txt[col] = toupper(currline->txt[col]);
  }

  GFTRK(NULL);
}


//  ------------------------------------------------------------------

void IEclass::LookupCursor() {

  GFTRK("EditLookupCursor");

  LookupNode(msgptr, currline->txt.c_str()+col, LOOK_NAME);

  GFTRK(NULL);
}


//  ------------------------------------------------------------------

void IEclass::LookupDest() {

  GFTRK("EditLookupDest");

  LookupNode(msgptr, "", LOOK_DEST);

  GFTRK(NULL);
}


//  ------------------------------------------------------------------

void IEclass::LookupOrig() {

  GFTRK("EditLookupOrig");

  LookupNode(msgptr, "", LOOK_ORIG);

  GFTRK(NULL);
}


//  ------------------------------------------------------------------

void IEclass::Soundkill() {

  HandleGEvent(EVTT_STOPVOICE);
}


//  ------------------------------------------------------------------

void IEclass::statusline() {

  if(chartyped) {
    if(EDIT->Completion.First()) {
      do {
        const char* trig = EDIT->Completion.Trigger();
        uint tlen = strlen(trig);
        if(col >= tlen) {
          if(strneql(trig, currline->txt.c_str()+col-tlen, tlen)) {
            int saved_insert = insert;
            insert = true;
            batch_mode = BATCH_MODE;
            uint n;
            for(n=0; n<tlen; n++)
              DelLeft();
            const char* cptr = EDIT->Completion.Text();
            uint clen = strlen(cptr);
            for(n=0; n<clen; n++)
              insertchar(*cptr++);
            HandleGEvent(EVTT_EDITCOMPLETION);
            insert = saved_insert;
            break;
          }
        }
      } while(EDIT->Completion.Next());
    }
  }

  char _buf[EDIT_BUFLEN];
  *_buf = NUL;

  if(EDIT->Comment.First()) {
    do {
      const char* trig = EDIT->Comment.Trigger();
      uint tlen = strlen(trig);
      if(col >= tlen) {
        if(strnieql(trig, currline->txt.c_str()+col-tlen, tlen)) {
          strcpy(_buf, EDIT->Comment.Text());
          break;
        }
      }
    } while(EDIT->Comment.Next());
  }

  update_statuslinef(LNG->EditStatus, 1+thisrow, 1+col, _buf);
  if(*_buf and CFG->switches.get(beepcomment)) {
    HandleGEvent(EVTT_EDITCOMMENT);
  }
}


//  ------------------------------------------------------------------

int IEclass::handlekey(gkey __key) {

  int rc = true;

  switch(__key) {
    case KK_EditBlockRight:  __key = KK_EditGoRight;   break;
    case KK_EditBlockLeft:   __key = KK_EditGoLeft;    break;
    case KK_EditBlockUp:     __key = KK_EditGoUp;      break;
    case KK_EditBlockDown:   __key = KK_EditGoDown;    break;
    case KK_EditBlockHome:   __key = KK_EditGoBegLine; break;
    case KK_EditBlockEnd:    __key = KK_EditGoEOL;     break;
    case KK_EditBlockPgDn:   __key = KK_EditGoPgDn;    break;
    case KK_EditBlockPgUp:   __key = KK_EditGoPgUp;    break;

    case KK_EditCopy:
    case KK_EditCut:
    case KK_EditDelete:      goto noselecting;

    case KK_EditDelChar:
    case KK_EditDelLeft:
      if(selecting) {
        __key = KK_EditUndefine;
      }
      // fall through

    case KK_EditPaste:
    case KK_EditNewline:
      if(selecting) {
        BlockCut(true);
        batch_mode = BATCH_MODE;
      }
      goto noselecting;
      break;

    default:
      rc = PlayMacro(__key, KT_E);
      if(rc == true)
        return rc;
      rc = true;
      if(selecting) {
        Line *_line;
        selecting = NO;
        blockcol = -1;
        for(_line = findfirstline(); _line; _line = _line->next)
          _line->type &= ~GLINE_BLOK;
        // refresh screen
        int r = row;
        for(_line = currline; _line and r; _line = _line->prev)
          r--;
        refresh(_line, minrow);
      }
      goto noselecting;
  }

  if(not selecting) {
    Line *_line;
    for(_line = findfirstline(); _line; _line = _line->next)
      _line->type &= ~GLINE_BLOK;
    currline->type |= GLINE_BLOK;
    selecting = YES;
    blockcol = col;
    // refresh screen
    int r = row;
    for(_line = currline; _line and r; _line = _line->prev)
      r--;
    refresh(_line, minrow);
  }

noselecting:

  switch(__key) {
    case KK_EditAbort:            Abort();              break;
    case KK_EditAskExit:          AskExit();            break;
    case KK_EditClearDeleteBuf:   ClearDeleteBuf();     break;
    case KK_EditClearPasteBuf:    ClearPasteBuf();      break;
    case KK_EditCopyAboveChar:    CopyAboveChar();      break;
    case KK_EditDelChar:          DelChar();            break;
    case KK_EditDeleteEOL:        DeleteEOL();          break;
    case KK_EditDeleteSOL:        DeleteSOL();          break;
    case KK_EditDelLeft:          DelLeft();            break;
    case KK_EditDelLine:          DelLine();            break;
    case KK_EditDelLtWord:        DelLtWord();          break;
    case KK_EditDelRtWord:        DelRtWord();          break;
    case KK_EditDosShell:         DosShell();           break;
    case KK_EditDupLine:          DupLine();            break;
    case KK_EditExitMsg:          ExitMsg();            break;
    case KK_EditExportText:       ExportText();         break;
    case KK_EditGoBegLine:        GoBegLine();          break;
    case KK_EditGoBotLine:        GoBotLine();          break;
    case KK_EditGoBotMsg:         GoBotMsg();           break;
    case KK_EditGoDown:           GoDown();             break;
    case KK_EditGoEOL:            GoEOL();              break;
    case KK_EditGoLeft:           GoLeft();             break;
    case KK_EditGoPgDn:           GoPgDn();             break;
    case KK_EditGoPgUp:           GoPgUp();             break;
    case KK_EditGoRight:          GoRight();            break;
    case KK_EditGoTopLine:        GoTopLine();          break;
    case KK_EditGoTopMsg:         GoTopMsg();           break;
    case KK_EditGoUp:             GoUp();               break;
    case KK_EditGoWordLeft:       GoWordLeft();         break;
    case KK_EditGoWordRight:      GoWordRight();        break;
    case KK_EditHeader:           Header();             break;
    case KK_EditImportQuotebuf:   ImportQuotebuf();     break;
    case KK_EditImportText:       ImportText();         break;
    case KK_EditLoadFile:         LoadFile();           break;
    case KK_EditLookupCursor:     LookupCursor();       break;
    case KK_EditLookupDest:       LookupDest();         break;
    case KK_EditLookupOrig:       LookupOrig();         break;
    case KK_EditNewline:          Newline();            break;
    case KK_EditQuitNow:          QuitNow();            break;
    case KK_EditReflow:           Reflow();             break;
    case KK_EditSaveFile:         SaveFile();           break;
    case KK_EditSaveMsg:          SaveMsg();            break;
    case KK_EditSoundkill:        Soundkill();          break;
    case KK_EditSpellCheck:       SpellCheck();         break;
    case KK_EditTab:              Tab();                break;
    case KK_EditTabReverse:       ReTab();              break;
    case KK_EditToggleCase:       ToggleCase();         break;
    case KK_EditToggleInsert:     ToggleInsert();       break;
    case KK_EditToLower:          ToLower();            break;
    case KK_EditToUpper:          ToUpper();            break;
    case KK_EditUndefine:                               break;
    case KK_EditUnDelete:         UnDelete();           break;
    case KK_EditUndo:             Undo->PlayItem();     break;
    case KK_EditZapQuoteBelow:    ZapQuoteBelow();      break;

    // Block functions
    case KK_EditAnchor:           BlockAnchor();        break;
    case KK_EditCopy:             BlockCopy();          break;
    case KK_EditCut:              BlockCut();           break;
    case KK_EditDelete:           BlockCut(true);       break;
    case KK_EditPaste:            BlockPaste();         break;

    default:
      rc = false;
      break;
  }

  if(__key != KK_EditUndo)
    undo_ready = NO;
  
  return rc;
}


//  ------------------------------------------------------------------

int IEclass::Start(int __mode, uint* __position, GMsg* __msg) {

  GFTRK("EditStart");

  thisrow = 0;
  quitnow = NO;
  col = mincol;
  row = minrow;
  msgptr = __msg;
  msgmode = __mode;
  currline = __msg->lin;

  if(AA->isinternet() and (CFG->soupexportmargin <= CFG->dispmargin))
    margintext = CFG->soupexportmargin;
  else
    margintext = CFG->dispmargin;

  marginquotes = EDIT->QuoteMargin() + 1; // Add one for CR
  if(marginquotes > margintext)
    marginquotes = margintext;

  if(currline == NULL) {
    currline = new Line("\n");
    throw_xnew(currline);
  }

  // Check if there is an unfinished backup message
  FILE* _fp = fsopen(AddPath(CFG->goldpath, EDIT->File()), "rt", CFG->sharemode);
  if(_fp) {
    char _buf[EDIT_BUFLEN];
    fgets(_buf, sizeof(_buf), _fp);
    fclose(_fp);
    if(striinc(unfinished, _buf)) {
      w_info(LNG->UnfinishedMsg);
      update_statusline(LNG->LoadUnfinished);
      HandleGEvent(EVTT_ATTENTION);
      gkey _ch = getxch();
      w_info(NULL);
      if(_ch != Key_Esc) {
        LoadFile();
        *__position = 0;
        remove(AddPath(CFG->goldpath, EDIT->File()));
      }
    }
  }

  if(*__position) {
    for(uint _posrow=0; _posrow < *__position; _posrow++) {
      if(currline->next) {
        currline = currline->next;
        row++;
      }
    }
    thisrow = row;
  }

  done = NO;

  // If the starting line is outside the first screenful
  if(*__position >= (maxrow+1)) {
    refresh(currline->prev, minrow);
    row = 1;
  }
  else {
    refresh(findfirstline(), minrow);
  }

  gotorowcol(mincol, minrow);
  dispins();

  time_t _lasttime = time(NULL);

  while(not done) {

    statusline();

    gotorowcol(col, row);
    batch_mode = 0;

    int backattr = 0;
    if(blockcol == -1) {
      backattr = dispchar(currline->txt.c_str()[col], C_READC);
      gotorowcol(col, row);
    }

    cursoron();
    if(insert)
      vcursmall();
    else
      vcurlarge();

    gkey _ch;

    do {
      _ch = getxchtick();

      if(EDIT->AutoSave()) {
        time_t _thistime = time(NULL);
        if(_thistime >= (_lasttime+EDIT->AutoSave())) {
          _lasttime = _thistime;
          SaveFile();
        }
      }

      if(_ch == Key_Tick)
        CheckTick(KK_EditQuitNow);

    } while(_ch == Key_Tick);

    pcol = col; getthisrow(currline); prow = thisrow;

    int ismacro = false;
    gkey _kk = SearchKey(_ch, EditKey, EditKeys);
    if(_kk) {
      _ch = _kk;
    }
    else {
      ismacro = IsMacro(_ch, KT_E);
    }

    if(blockcol == -1)
      dispchar(currline->txt.c_str()[col], backattr);

    chartyped = false;
    if((_ch < KK_Commands) and (_ch & 0xFF) and not ismacro) {
      chartyped = true;
      _ch &= 0xFF;
      insertchar((char)_ch);
      undo_ready = YES;
    }
    else if(handlekey(_ch)) {
      getthisrow(currline);
    }
  }

  cursoroff();

  msgptr->lin = findfirstline();
  savefile(quitnow ? MODE_UPDATE : MODE_SAVE);

  // Prune killbuffer
  if(Edit__killbuf) {
    Line *__line = Edit__killbuf;

    int _count = EDIT->UnDelete();
    while(__line and _count--)
      __line = __line->prev;

    if(__line)
      if(__line->next)
        __line->next->prev = NULL;

    while(__line) {
      if(Undo->FixPushLine(__line)) {
        if(__line->prev) {
          __line = __line->prev;
          __line->next = NULL;
        }
        else {
          __line = NULL;
        }
      }
      else {
        if(__line->prev) {
          __line = __line->prev;
          throw_xdelete(__line->next);
        }
        else
          throw_xdelete(__line);
      }
    }
  }

  *__position = 1 + thisrow;

  GFTRK(NULL);

  return done;
}


//  ------------------------------------------------------------------

UndoStack::UndoStack(IEclass* this_editor) :

  editor(this_editor),
  row(editor->row),
  col(editor->col),
  pcol(editor->pcol),
  prow(editor->prow),
  minrow(editor->minrow),
  maxrow(editor->maxrow),
  thisrow(editor->thisrow),
  currline(editor->currline), 
  undo_ready(editor->undo_ready) {
  UndoItem::last_item = &last_item;
  last_item = NULL;
  undo_enabled = YES;
}


//  ------------------------------------------------------------------

UndoStack::~UndoStack() {

  while(last_item) {
    switch(last_item->action & EDIT_UNDO_ACTION) {
      case EDIT_UNDO_DEL_TEXT:
      case EDIT_UNDO_INS_TEXT:
      case EDIT_UNDO_WRAP_TEXT:
        throw_delete(last_item->data.text_ptr);
        break;
      case EDIT_UNDO_DEL_LINE:
        throw_xdelete(last_item->data.line_ptr);
    }
    delete last_item;
  }
}


//  ------------------------------------------------------------------

bool UndoStack::FixPushLine(Line* __line) {

  UndoItem* item = last_item;

  while(item) {
    if(((item->action & EDIT_UNDO_ACTION) == EDIT_UNDO_PUSH_LINE) and (item->data.line_ptr == __line)) {
      item->action &= ~EDIT_UNDO_ACTION;
      item->action |= EDIT_UNDO_ORPHAN_LINE;
      return true;
    }
    item = item->prev;
  }
  return false;
}


//  ------------------------------------------------------------------

void UndoStack::PushItem(uint action, Line* __line, uint __col, uint __len) {

  GFTRK("PushItem");

  if(undo_enabled) {

    throw_new(last_item = new UndoItem);
    last_item->col.num = (__col != NO_VALUE) ? __col : col;
    last_item->col.sav = 0;
    last_item->action = action;
    last_item->pcol = pcol;
    last_item->prow = prow;

    switch(action & EDIT_UNDO_ACTION) {
      case EDIT_UNDO_VOID:
      case EDIT_UNDO_INS_CHAR:
        last_item->line = __line ? __line : currline;
        last_item->data.char_int = NUL;
        break;
      case EDIT_UNDO_DEL_CHAR:
      case EDIT_UNDO_OVR_CHAR:
        last_item->line = __line ? __line : currline;
        last_item->data.char_int = last_item->line->txt[last_item->col.num];
        break;
      case EDIT_UNDO_DEL_TEXT:
        last_item->line = __line;
        __col = last_item->col.num;
        if(__len == NO_VALUE)
          __len = __line->txt.length() - __col;
        throw_new(last_item->data.text_ptr = new(__len) text_item(__col, __len));
        memcpy(last_item->data.text_ptr->text, __line->txt.c_str() + __col, __len);
        break;
      case EDIT_UNDO_CUT_TEXT:
        last_item->line = __line;
        last_item->data.text_ptr = NULL;
        break;
      case EDIT_UNDO_INS_TEXT:
      case EDIT_UNDO_WRAP_TEXT:
        last_item->line = __line;
        if(__len == NO_VALUE)
          __len = __line->txt.length() - __col;
        throw_new(last_item->data.text_ptr = new text_item(__col, __len));
        break;
      case EDIT_UNDO_NEW_LINE:
        last_item->line = last_item->data.line_ptr = __line;
        break;
      case EDIT_UNDO_DEL_LINE:
        last_item->line = __line->prev ? __line->prev : __line->next;
        last_item->data.line_ptr = __line;
        break;
      case EDIT_UNDO_PUSH_LINE:
        if(currline->next)
          last_item->line = currline->next;
        else {
          last_item->action |= LAST_LINE;
          last_item->line = currline->prev;
        }
        last_item->data.line_ptr = currline;
        break;
      case EDIT_UNDO_POP_LINE:
        last_item->line = currline;
        last_item->data.line_ptr = NULL;
        break;
    }    
  }

  GFTRK(NULL);
}


//  ------------------------------------------------------------------

void UndoStack::PlayItem() {

  GFTRK("PlayItem");

  if(last_item) {

    UndoItem* item;

    // Don't save any new items while in Undo function
    undo_enabled = NO;

    // Find first of the batch items
    for(item = last_item; item->action & BATCH_MODE; item = item->prev);

    uint curr_row_num = thisrow;
    uint curr_col_num = col;
    if((item->action & PREV_LINE) and item->line->next)
      currline = item->line->next;
    else
      currline = item->line;
    editor->getthisrow(currline);

    int _maxrow = maxrow;
    if((item->action & PREV_LINE) and not item->line->next)
      _maxrow--;

    col = item->col.num;

    if(curr_row_num != thisrow) {

      // Let user to see the position before performing Undo, unless it's a
      // neighbour line and the same column.
      undo_ready = ((abs(int(curr_row_num - thisrow)) < 2) and ((curr_col_num == col) or (col+1 > currline->txt.length())));

      // Move cursor up or down depending on where the undo line is,
      // then refresh window if the line is invisible.
      do {
        if(curr_row_num > thisrow) {
          if(row > minrow)
            curr_row_num--, row--;
          else {
            editor->refresh(currline, row);
            break;
          }
        }
        else {
          if(row < _maxrow)
            curr_row_num++, row++;
          else {
            Line* l = currline;
            for(uint r = row; r; l = l->prev, r--) {}
            editor->refresh(l, minrow);
            break;
          }
        }
      } while(curr_row_num != thisrow);
    }
    else
      undo_ready = ((abs(int(curr_col_num - col)) < 2) or (col+1 > currline->txt.length()));

    uint _pcol = item->pcol;
    uint _prow = item->prow;

    if(undo_ready) {

      if((item->action & PREV_LINE) and not item->line->next) {
        row++; thisrow++; curr_row_num++;
      }

      bool in_batch;

      // Keep undoing until item with no BATCH_MODE flag is reached.
      do {

        uint undo_type = last_item->action & EDIT_UNDO_TYPE;
        uint undo_action = last_item->action & EDIT_UNDO_ACTION;
        in_batch = last_item->action & BATCH_MODE;
        currline = last_item->line;

        if(last_item->action & PREV_LINE) {
          col = last_item->col.num = last_item->col.sav;
          if(row > minrow)
            row--;
        }

        switch(undo_type) {

          case EDIT_UNDO_CHAR:
            switch(undo_action) {
              case EDIT_UNDO_INS_CHAR:
                currline->txt.erase(last_item->col.num, 1);
                break;
              case EDIT_UNDO_DEL_CHAR:
                currline->txt.insert(last_item->col.num, 1, last_item->data.char_int);
                break;
              case EDIT_UNDO_OVR_CHAR:
                currline->txt[last_item->col.num] = last_item->data.char_int;
                break;
            }
            editor->setlinetype(currline);
            break;

          case EDIT_UNDO_TEXT: {
            text_item* text_data = last_item->data.text_ptr;
            std::string *txt = &currline->txt;
            switch(undo_action) {
              case EDIT_UNDO_DEL_TEXT:
                txt->insert(text_data->col, text_data->text, text_data->len);
                throw_delete(text_data);
                break;
              case EDIT_UNDO_CUT_TEXT:
                txt->erase(last_item->col.num);
                break;
              case EDIT_UNDO_WRAP_TEXT:
                txt->append(currline->next->txt.c_str()+text_data->col, text_data->len);
                txt = &currline->next->txt;
                // fall through...
              case EDIT_UNDO_INS_TEXT:
                txt->erase(text_data->col, text_data->len);
                throw_delete(text_data);
                break;
            }
            editor->setlinetype((undo_action == EDIT_UNDO_WRAP_TEXT) ? currline->next : currline);
            break;
          }

          case EDIT_UNDO_LINE: {
            Line* thisline = last_item->data.line_ptr;
            switch(undo_action) {
              case EDIT_UNDO_NEW_LINE:
                if(thisline->next)
                  thisline->next->prev = thisline->prev;
                if(thisline->prev) {
                  thisline->prev->next = thisline->next;
                  currline = thisline->prev;
                }
                else
                  currline = thisline->next;
                throw_xdelete(thisline);
                break;
              case EDIT_UNDO_ORPHAN_LINE:
                if(last_item->action & LAST_LINE) {
                  thisline->prev = currline;
                  thisline->next = currline ? currline->next : NULL;
                  if((row < maxrow) and currline)
                    row++;
                }
                else {
                  thisline->prev = currline ? currline->prev : NULL;
                  thisline->next = currline;
                }
                // fall through...
              case EDIT_UNDO_DEL_LINE:
                if(thisline->prev)
                  thisline->prev->next = thisline;
                if(thisline->next)
                  thisline->next->prev = thisline;
                currline = thisline;
                break;
              case EDIT_UNDO_PUSH_LINE:
                editor->UnDelete((last_item->action & LAST_LINE) ? false : true);
                break;
              case EDIT_UNDO_POP_LINE:
                editor->DelLine();
                break;
            }
          }
        }

        _pcol = last_item->pcol;
        _prow = last_item->prow;
        delete last_item;

      } while(last_item and in_batch);

      undo_enabled = YES;

      editor->getthisrow(currline);
      uint temprow = row;
      Line *templine = currline;
      Line *topline = editor->findfirstline();

      int delta = _prow-thisrow;

      if(not in_range(row+delta, minrow, maxrow)) {

        // we need to fit thisrow into the screen boundaries
        if(delta > 0) {
          for(row += delta; row > maxrow; row--) {
            if(templine) // cause refresh() issue an error since templine should never be NULL
              templine = templine->prev;
          }
          temprow = maxrow;
        }
        else {
          for(row += delta; (int)row < (int)minrow; row++) {
            if(templine) // cause refresh() issue an error since templine should never be NULL
              templine = templine->next;
          }
          temprow = minrow;
        }

        // move pointer to the top of screen so we refresh scrolled area
        while (row != minrow) {
          if(templine) // cause refresh() issue an error since templine should never be NULL
            templine = templine->prev;
          --row;
        }
      }
      else {
        if(delta < 0) {
          templine = topline;
          for(thisrow=0; thisrow < _prow; thisrow++)
            if(templine) // cause refresh() issue an error if thisrow != _prow
              templine = templine->next;
        }
        temprow = row+delta;
      }

      // refresh screen
      editor->refresh(templine, row);

      // set cursor position
      thisrow = _prow;
      col = _pcol;
      row = temprow;
      // set currline according to thisrow
      currline = topline;
      for(thisrow=0; thisrow < _prow; thisrow++)
        if(currline)
          currline = currline->next;
    }
    // Move the cursor to EOL if necessary
    else if(col+1 > currline->txt.length())
      editor->GoEOL();
    undo_ready = YES;
  }

  GFTRK(NULL);
}


//  ------------------------------------------------------------------
