2741 lines
68 KiB
C++
2741 lines
68 KiB
C++
|
|
// ------------------------------------------------------------------
|
|
// 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$
|
|
// ------------------------------------------------------------------
|
|
// The Internal Editor (IE), part 1.
|
|
// ------------------------------------------------------------------
|
|
|
|
#ifdef __GNUG__
|
|
#pragma implementation "geedit.h"
|
|
#endif
|
|
|
|
// ------------------------------------------------------------------
|
|
|
|
#include <golded.h>
|
|
#include <geedit.h>
|
|
|
|
|
|
// ------------------------------------------------------------------
|
|
// Globals
|
|
|
|
int CFG__editquotewrap = YES;
|
|
|
|
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);
|
|
_test_halt(__line->text == NULL);
|
|
|
|
__line->type &= ~GLINE_ALL;
|
|
__line->type |= is_quote(__line->text) ? GLINE_QUOT : (*__line->text == CTRL_A) ? GLINE_HIDD : 0;
|
|
}
|
|
|
|
|
|
// ------------------------------------------------------------------
|
|
// Insert character in string at position
|
|
|
|
void IEclass::strinschr(char*& __string, char __ch, uint __position) {
|
|
|
|
_test_halt(__string == NULL);
|
|
|
|
// 0123456789 _position = 2
|
|
// 1234567890 length = 4
|
|
// helo< < = nul
|
|
// he lo<
|
|
// hello<
|
|
|
|
// Get string length
|
|
uint _length = strlen(__string);
|
|
|
|
// Is position beyond the nul-terminator in the string?
|
|
_test_haltab(__position > _length, __position, _length);
|
|
|
|
// Reallocate string to make room for the char (3=nul+newchar+possible space)
|
|
__string = (char*)throw_realloc(__string, _length+3);
|
|
|
|
// Make room for the char to insert
|
|
memmove(__string+__position+1, __string+__position, _length-__position+1);
|
|
THROW_CHECKPTR(__string);
|
|
|
|
// Put in the char
|
|
__string[__position] = __ch;
|
|
THROW_CHECKPTR(__string);
|
|
if((__string[__position+1] == NUL) and (__ch != ' ') and (__ch != '\n')) {
|
|
__string[__position+1] = ' ';
|
|
__string[__position+2] = NUL;
|
|
THROW_CHECKPTR(__string);
|
|
}
|
|
}
|
|
|
|
|
|
// ------------------------------------------------------------------
|
|
// Delete character in string at position
|
|
|
|
void IEclass::strdelchr(char* __string, uint __position) {
|
|
|
|
_test_halt(__string == NULL);
|
|
|
|
// 0123456789 _position = 1
|
|
// 1234567890 length = 5
|
|
// hello< < = nul
|
|
// hllo<
|
|
|
|
uint _length = strlen(__string);
|
|
|
|
// Is position at or beyond the nul-terminator in the string?
|
|
_test_haltab(__position >= _length, __position, _length);
|
|
|
|
// Delete the character
|
|
memmove(__string+__position, __string+__position+1, _length-__position);
|
|
THROW_CHECKPTR(__string);
|
|
}
|
|
|
|
|
|
// ------------------------------------------------------------------
|
|
// Zero-based
|
|
|
|
int IEclass::dispchar(vchar __ch, int attr) {
|
|
|
|
if(__ch != '\n') {
|
|
if(__ch == ' ')
|
|
__ch = EDIT->CharSpace();
|
|
}
|
|
else {
|
|
__ch = EDIT->CharPara();
|
|
}
|
|
|
|
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, 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(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;
|
|
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 {
|
|
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;
|
|
StyleCodeHighlight(_buf+begblock, __row, mincol+begblock, false, C_READA);
|
|
_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;
|
|
StyleCodeHighlight(_buf, __row, mincol, false, selected ? C_READA : attr);
|
|
_buf[blockmark] = savechar;
|
|
StyleCodeHighlight(_buf+blockmark, __row, mincol+blockmark, false, selected ? attr : C_READA);
|
|
}
|
|
else
|
|
StyleCodeHighlight(_buf, __row, mincol, false, selected ? C_READA : attr);
|
|
}
|
|
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->text));
|
|
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_halt(__line->text == NULL);
|
|
_test_haltab(__row > maxrow, __row, maxrow);
|
|
|
|
// Display line
|
|
setcolor(__line);
|
|
dispstring(__line->text, __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, char* __text, long __batch_mode) {
|
|
|
|
GFTRK("Editinsertlinebelow");
|
|
|
|
Line* _nextline = (Line*)throw_xcalloc(1, sizeof(Line));
|
|
_nextline->text = __text ? throw_strdup(__text) : (char*)NULL;
|
|
|
|
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 = strlen(currline->text);
|
|
if(col)
|
|
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) > strlen(currline->text))
|
|
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) > strlen(currline->text))
|
|
GoEOL();
|
|
}
|
|
|
|
GFTRK(NULL);
|
|
}
|
|
|
|
|
|
// ------------------------------------------------------------------
|
|
|
|
void IEclass::GoLeft() {
|
|
|
|
GFTRK("EditGoLeft");
|
|
|
|
_test_haltab(col < mincol, col, mincol);
|
|
|
|
if(col == mincol) {
|
|
if(currline->prev) {
|
|
GoUp();
|
|
GoEOL();
|
|
}
|
|
}
|
|
else
|
|
col--;
|
|
|
|
if(blockcol != -1)
|
|
displine(currline, row);
|
|
|
|
GFTRK(NULL);
|
|
}
|
|
|
|
|
|
// ------------------------------------------------------------------
|
|
|
|
void IEclass::GoRight() {
|
|
|
|
GFTRK("EditGoRight");
|
|
|
|
_test_haltab(col > maxcol, col, maxcol);
|
|
|
|
char _cursorchar = currline->text[col];
|
|
|
|
if((col == maxcol) or (_cursorchar == '\n') or (_cursorchar == NUL)) {
|
|
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, int __display) {
|
|
|
|
_test_halt(__currline == NULL);
|
|
_test_halt(*__currline == NULL);
|
|
|
|
uint _quotelen;
|
|
char _quotebuf[100];
|
|
|
|
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 = strlen(_thisline->text);
|
|
|
|
uint _wrapmargin = (_thisline->type & GLINE_QUOT) ? marginquotes : margintext;
|
|
|
|
// Does this line need wrapping?
|
|
if(_thislen > _wrapmargin or (_thislen == _wrapmargin and not isspace(_thisline->text[_thislen-1]))) {
|
|
|
|
// Reset quote string length
|
|
_quotelen = 0;
|
|
|
|
// Is this line quoted?
|
|
if((_thisline->type & GLINE_QUOT) and CFG__editquotewrap) {
|
|
|
|
// Get quote string and length
|
|
GetQuotestr(_thisline->text, _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
|
|
char* _wrapptr = _thisline->text + _wrapmargin - 1;
|
|
|
|
// Locate the word to be wrapped
|
|
|
|
// Did we find a space?
|
|
if(*_wrapptr == ' ') {
|
|
|
|
// 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
|
|
_wrapptr++;
|
|
}
|
|
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
|
|
char* _atmargin = _wrapptr;
|
|
|
|
// Search backwards until a space or the beginning of the line is found
|
|
while((_wrapptr > _thisline->text) and (*(_wrapptr-1) != ' '))
|
|
_wrapptr--;
|
|
|
|
// Check if we hit leading spaces
|
|
char* _spaceptr = _wrapptr;
|
|
if(_spaceptr > _thisline->text) {
|
|
_spaceptr--;
|
|
while((_spaceptr > _thisline->text) and (*_spaceptr == ' '))
|
|
_spaceptr--;
|
|
}
|
|
|
|
// Did we search all the way back to the beginning of the line?
|
|
if((_wrapptr == _thisline->text) or (_wrapptr == (_thisline->text+_quotelen)) or (*_spaceptr == ' ')) {
|
|
|
|
// Case 3: There are no spaces within the margin or we hit leading spaces
|
|
|
|
// We have to break it up at the margin
|
|
_wrapptr = _atmargin;
|
|
}
|
|
}
|
|
|
|
// The wrapptr now points to the location to be wrapped or NUL
|
|
|
|
// Get length of the wrapped part
|
|
uint _wraplen = strlen(_wrapptr);
|
|
|
|
// Is the line hard-terminated?
|
|
if(strchr(_wrapptr, '\n')) {
|
|
|
|
// The line is hard-terminated.
|
|
//
|
|
// The wrapped part must be placed on a new line below.
|
|
|
|
Line* _wrapline = _lastadded = insertlinebelow(_thisline, NULL, BATCH_MODE);
|
|
// Allocate space for the new line
|
|
_wrapline->text = (char*)throw_malloc(_quotelen + _wraplen + 1);
|
|
|
|
// Copy the quote string, if any, to the new line first
|
|
if(_quotelen) {
|
|
strcpy(_wrapline->text, _quotebuf);
|
|
THROW_CHECKPTR(_wrapline->text);
|
|
}
|
|
else {
|
|
*_wrapline->text = NUL;
|
|
THROW_CHECKPTR(_wrapline->text);
|
|
}
|
|
|
|
// Copy/append the wrapped part to the new line
|
|
strcat(_wrapline->text, _wrapptr);
|
|
THROW_CHECKPTR(_wrapline->text);
|
|
|
|
// 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, _wrapline, _quotelen, _wraplen);
|
|
|
|
_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
|
|
scrolldown(mincol, _thisrow+1, maxcol, maxrow);
|
|
|
|
// Display the new line
|
|
if(strlen(_wrapline->text) <= (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;
|
|
|
|
// 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, "", BATCH_MODE);
|
|
_line_added_below = true;
|
|
}
|
|
|
|
// Get length of the text of next line
|
|
uint _nextlen = _nextline->text ? strlen(_nextline->text) : 0;
|
|
|
|
// Reallocate next line's text to make room for the wrapped part
|
|
_nextline->text = (char*)throw_realloc(_nextline->text, _quotelen+_wraplen+_nextlen+1);
|
|
|
|
// Move the next line's text to make room for the wrapped part and the quote string, if any
|
|
memmove(_nextline->text+_quotelen+_wraplen, _nextline->text, _nextlen+1);
|
|
THROW_CHECKPTR(_nextline->text);
|
|
|
|
// Copy the wrapped part
|
|
memmove(_nextline->text+_quotelen, _wrapptr, _wraplen);
|
|
THROW_CHECKPTR(_nextline->text);
|
|
|
|
// Was this line quoted?
|
|
if(_quotelen) {
|
|
|
|
// Copy the quote string
|
|
memmove(_nextline->text, _quotebuf, _quotelen);
|
|
THROW_CHECKPTR(_nextline->text);
|
|
|
|
Undo->PushItem(EDIT_UNDO_INS_TEXT|BATCH_MODE, _nextline, 0, _quotelen);
|
|
}
|
|
|
|
Undo->PushItem(EDIT_UNDO_WRAP_TEXT|BATCH_MODE, _nextline, _quotelen, _wraplen);
|
|
|
|
// 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
|
|
scrolldown(mincol, _thisrow+1, maxcol, maxrow);
|
|
}
|
|
|
|
// Display the new/wrapped line
|
|
if((_thisrow+1) <= maxrow and strlen(_nextline->text) <= (maxcol+1))
|
|
displine(_nextline, _thisrow+1);
|
|
}
|
|
}
|
|
|
|
// Nul-terminate at the wrapping location
|
|
*_wrapptr = NUL;
|
|
THROW_CHECKPTR(_thisline->text);
|
|
|
|
// Was this line quoted?
|
|
if(_quotelen) {
|
|
|
|
// Trim spaces off the end of the line
|
|
char* _trimptr = _wrapptr - 1;
|
|
if(*_trimptr == ' ') {
|
|
while(*(_trimptr-1) == ' ')
|
|
_trimptr--;
|
|
if(_quotelen and _trimptr-_thisline->text < _quotelen)
|
|
_trimptr++;
|
|
Undo->PushItem(EDIT_UNDO_OVR_CHAR|BATCH_MODE, _thisline, _trimptr - _thisline->text);
|
|
Undo->PushItem(EDIT_UNDO_OVR_CHAR|BATCH_MODE, _thisline, _trimptr - _thisline->text + 1);
|
|
*_trimptr = NUL;
|
|
}
|
|
else
|
|
Undo->PushItem(EDIT_UNDO_OVR_CHAR|BATCH_MODE, _thisline, _wrapptr - _thisline->text);
|
|
|
|
// Append a new linefeed
|
|
strcat(_thisline->text, "\n");
|
|
THROW_CHECKPTR(_thisline->text);
|
|
}
|
|
|
|
// Make sure the line type still is correct
|
|
setlinetype(_thisline);
|
|
|
|
if(__display) {
|
|
|
|
// Display this line after wrapping
|
|
if(_thisrow <= maxrow)
|
|
displine(_thisline, _thisrow);
|
|
}
|
|
|
|
// If we are on the cursor line, check if the cursor char was wrapped
|
|
if(_thisrow == *__curr_row and strlen(_thisline->text) <= *__curr_col) {
|
|
char* _colptr = _thisline->text + *__curr_col;
|
|
_curscol = _quotelen + ((_colptr > _wrapptr) ? _colptr - _wrapptr : 0);
|
|
_cursrow++, thisrow++;
|
|
UndoItem* i = Undo->last_item;
|
|
do { i = i->prev; } while(i->action & BATCH_MODE);
|
|
if(i->col.num >= strlen(i->line->text)) {
|
|
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(strchr(_thisline->text, '\n')) {
|
|
if(_thisline->next == NULL)
|
|
break;
|
|
_wrapmargin = (_thisline->next->type & GLINE_QUOT) ? marginquotes : margintext;
|
|
if(strlen(_thisline->next->text) <= _wrapmargin)
|
|
break;
|
|
}
|
|
|
|
// 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) {
|
|
*__currline = (*__currline)->next;
|
|
if(_cursrow > maxrow) {
|
|
_cursrow = maxrow;
|
|
scrollup(mincol, minrow, maxcol, maxrow);
|
|
displine(*__currline, row);
|
|
}
|
|
}
|
|
|
|
// Update cursor position
|
|
*__curr_row = _cursrow;
|
|
*__curr_col = _curscol;
|
|
//thisrow = _cursrow;
|
|
|
|
GFTRK(NULL);
|
|
|
|
return _lastadded;
|
|
}
|
|
|
|
|
|
// ------------------------------------------------------------------
|
|
|
|
Line* IEclass::wrapdel(Line** __currline, uint* __curr_col, uint* __curr_row, int __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, int __display) {
|
|
|
|
GFTRK("Editwrapins");
|
|
|
|
Line *tmp = wrapit(__currline, __curr_col, __curr_row, __display);
|
|
|
|
GFTRK(NULL);
|
|
|
|
return tmp;
|
|
}
|
|
|
|
|
|
// ------------------------------------------------------------------
|
|
|
|
void IEclass::insertchar(char __ch) {
|
|
|
|
GFTRK("Editinsertchar");
|
|
|
|
// Insert or overwrite the char, replacing the block if any
|
|
if((selecting ? (BlockCut(true), batch_mode = BATCH_MODE) : false) or
|
|
(currline->text[col] == '\n') or (currline->text[col] == NUL) or insert) {
|
|
Undo->PushItem(EDIT_UNDO_INS_CHAR|batch_mode);
|
|
strinschr(currline->text, __ch, col);
|
|
} else {
|
|
#ifndef NDEBUG
|
|
uint _currline_len = strlen(currline->text);
|
|
_test_haltab(col > _currline_len, col, _currline_len);
|
|
#endif
|
|
Undo->PushItem(EDIT_UNDO_OVR_CHAR|batch_mode);
|
|
currline->text[col] = __ch;
|
|
THROW_CHECKPTR(currline->text);
|
|
}
|
|
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 = strlen(_thisline->text);
|
|
|
|
// Cannot delete at or beyond the nul-terminator
|
|
if(col < _thislen) {
|
|
Undo->PushItem(EDIT_UNDO_DEL_CHAR|batch_mode);
|
|
strdelchr(_thisline->text, col);
|
|
batch_mode = BATCH_MODE;
|
|
}
|
|
|
|
// 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 CFG__editquotewrap and col) {
|
|
|
|
// Get quote string length
|
|
char _dummybuf[100];
|
|
GetQuotestr(_nextline->text, _dummybuf, &_quotelen);
|
|
}
|
|
|
|
// Reallocate this line's text to make room for the next
|
|
_thisline->text = (char*)throw_realloc(_thisline->text, _thislen + strlen(_nextline->text) - _quotelen + 1);
|
|
|
|
// Copy the next line's text to this line
|
|
// Skip past the next line's quote string and blanks, if any
|
|
char* _nextptr = _nextline->text+_quotelen;
|
|
if(not ((_nextline->type & GLINE_QUOT) and (col == 0))) {
|
|
while(*_nextptr == ' ')
|
|
_nextptr++;
|
|
}
|
|
strcat(_thisline->text, _nextptr);
|
|
THROW_CHECKPTR(_thisline->text);
|
|
|
|
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);
|
|
}
|
|
batch_mode = BATCH_MODE;
|
|
|
|
// Make sure the line type still is correct
|
|
setlinetype(_thisline);
|
|
|
|
// Rewrap this line
|
|
wrapdel(&currline, &col, &row);
|
|
|
|
refresh(currline, row);
|
|
|
|
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
|
|
GoLeft();
|
|
DelChar();
|
|
|
|
GFTRK(NULL);
|
|
}
|
|
|
|
|
|
// ------------------------------------------------------------------
|
|
|
|
void IEclass::GoWordLeft() {
|
|
|
|
GFTRK("EditGoWordLeft");
|
|
|
|
if(col == 0) {
|
|
if(currline->prev) {
|
|
GoUp();
|
|
GoEOL();
|
|
}
|
|
}
|
|
else {
|
|
|
|
col--;
|
|
|
|
if(not isxalnum(currline->text[col])) {
|
|
while(not isxalnum(currline->text[col]) and (col > 0))
|
|
col--;
|
|
while(isxalnum(currline->text[col]) and (col > 0))
|
|
col--;
|
|
}
|
|
else {
|
|
while(isxalnum(currline->text[col]) and (col > 0))
|
|
col--;
|
|
}
|
|
|
|
if(col != 0)
|
|
col++;
|
|
|
|
if(blockcol != -1)
|
|
displine(currline, row);
|
|
}
|
|
|
|
GFTRK(NULL);
|
|
}
|
|
|
|
|
|
// ------------------------------------------------------------------
|
|
|
|
void IEclass::GoWordRight() {
|
|
|
|
GFTRK("EditGoWordRight");
|
|
|
|
if((currline->text[col] == NUL) or (currline->text[col] == '\n')) {
|
|
if(currline->next) {
|
|
GoDown();
|
|
col = 0;
|
|
}
|
|
}
|
|
else {
|
|
if(not isxalnum(currline->text[col])) {
|
|
while(not isxalnum(currline->text[col]) and ((col+1) <= strlen(currline->text)))
|
|
col++;
|
|
}
|
|
else {
|
|
while(isxalnum(currline->text[col]) and ((col+1) <= strlen(currline->text)))
|
|
col++;
|
|
while(not isxalnum(currline->text[col]) and ((col+1) <= strlen(currline->text)))
|
|
col++;
|
|
}
|
|
|
|
if(currline->text[col-1] == '\n')
|
|
col--;
|
|
|
|
if(currline->text[col] == NUL) {
|
|
if(currline->next) {
|
|
GoDown();
|
|
col = 0;
|
|
}
|
|
else
|
|
col--;
|
|
}
|
|
|
|
if(blockcol != -1)
|
|
displine(currline, row);
|
|
}
|
|
|
|
GFTRK(NULL);
|
|
}
|
|
|
|
|
|
// ------------------------------------------------------------------
|
|
|
|
void IEclass::Newline() {
|
|
|
|
GFTRK("EditNewline");
|
|
|
|
char* _text = currline->text;
|
|
|
|
// If the line is not hard-terminated, make room for it
|
|
if(not strchr(_text, '\n') and (_text[col] == NUL))
|
|
_text = currline->text = (char*)throw_realloc(_text, strlen(_text)+3);
|
|
|
|
// Pointer to the split position
|
|
char* _splitptr = _text + col;
|
|
|
|
// Buffer for the second part of the split line
|
|
char* _splitbuf = (char*)throw_malloc(EDIT_BUFLEN);
|
|
*_splitbuf = NUL;
|
|
|
|
// 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 = 0;
|
|
if(is_quote(_text)) {
|
|
GetQuotestr(_text, _splitbuf, &_quotelen);
|
|
THROW_CHECKPTR(_splitbuf);
|
|
}
|
|
|
|
// Eliminate the quotestring if
|
|
// - the cursor points to a linefeed or
|
|
// - the cursor is located inside the quotestring
|
|
if(_quotelen and ((*_splitptr == '\n') or (*_splitptr == NUL) or (col < _quotelen)))
|
|
*_splitbuf = _quotelen = 0;
|
|
|
|
// Append the second part to the split buffer
|
|
strcat(_splitbuf, _splitptr);
|
|
THROW_CHECKPTR(_splitbuf);
|
|
|
|
Undo->PushItem(EDIT_UNDO_INS_TEXT|batch_mode, currline, col, 1);
|
|
batch_mode = BATCH_MODE;
|
|
|
|
// Copy linefeed+nul to the split position
|
|
strcpy(_splitptr, "\n");
|
|
THROW_CHECKPTR(currline->text);
|
|
|
|
// Re-type and display the split line
|
|
setlinetype(currline);
|
|
displine(currline, row);
|
|
|
|
// Insert a new line below, set the line text to the split-off part
|
|
currline = insertlinebelow(currline, _splitbuf);
|
|
|
|
// --v--
|
|
// This line would be wrapped
|
|
// This line would be
|
|
// wrapped
|
|
|
|
Undo->PushItem(EDIT_UNDO_WRAP_TEXT|BATCH_MODE, currline, _quotelen, strlen(_splitbuf) - _quotelen);
|
|
|
|
setlinetype(currline);
|
|
throw_free(_splitbuf);
|
|
|
|
// 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);
|
|
|
|
GFTRK(NULL);
|
|
}
|
|
|
|
|
|
// ------------------------------------------------------------------
|
|
|
|
void IEclass::CopyAboveChar() {
|
|
|
|
GFTRK("EditCopyAboveChar");
|
|
|
|
char _ch = ' ';
|
|
if(currline->prev) {
|
|
char* _ptr = currline->prev->text;
|
|
uint _len = strlen(_ptr);
|
|
if(_len and _len > col)
|
|
_ch = _ptr[col];
|
|
if(_ch == '\n')
|
|
_ch = ' ';
|
|
}
|
|
insertchar(_ch);
|
|
|
|
GFTRK(NULL);
|
|
}
|
|
|
|
|
|
// ------------------------------------------------------------------
|
|
|
|
void IEclass::DupLine() {
|
|
|
|
GFTRK("EditDupLine");
|
|
|
|
Undo->PushItem(EDIT_UNDO_VOID);
|
|
|
|
Line* _nextline = insertlinebelow(currline, currline->text, 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) > strlen(currline->text))
|
|
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) > strlen(currline->text))
|
|
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->text[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 = strchr(currline->text, '\n') ? true : false;
|
|
|
|
Undo->PushItem(EDIT_UNDO_DEL_TEXT, currline);
|
|
|
|
currline->text[col] = NUL;
|
|
THROW_CHECKPTR(currline->text);
|
|
|
|
if(_has_linefeed) {
|
|
strcat(currline->text, "\n");
|
|
THROW_CHECKPTR(currline->text);
|
|
}
|
|
|
|
clreol();
|
|
|
|
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 strblank(currline->text)))
|
|
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(currline->text[0] == NUL)
|
|
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) > strlen(currline->text))
|
|
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) > strlen(currline->text))
|
|
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;
|
|
|
|
deleteline(true);
|
|
if(row) {
|
|
GoUp();
|
|
GoEOL();
|
|
Newline();
|
|
}
|
|
else {
|
|
UndoItem* i = Undo->last_item;
|
|
do { i = i->prev; } while(i->action & BATCH_MODE);
|
|
i->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->text);
|
|
|
|
// 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->text == '\n') or (*__line->text == NUL);
|
|
}
|
|
|
|
|
|
// ------------------------------------------------------------------
|
|
|
|
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[100];
|
|
GetQuotestr(currline->next->text, _qstr2, &_qlen2);
|
|
if(not strieql(__qstr, _qstr2))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
// ------------------------------------------------------------------
|
|
|
|
void IEclass::Reflow() {
|
|
|
|
GFTRK("EditReflow");
|
|
|
|
// Skip empty lines
|
|
while(isempty()) {
|
|
if(currline->next)
|
|
GoDown();
|
|
else {
|
|
GFTRK(NULL);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Get the first quotestring
|
|
uint _qlen1;
|
|
char _qstr1[100];
|
|
GetQuotestr(currline->text, _qstr1, &_qlen1);
|
|
char* _qlenptr = currline->text + _qlen1;
|
|
|
|
// Strip leading spaces from the first line
|
|
char* ptr = strskip_wht(_qlenptr);
|
|
if(ptr != _qlenptr) {
|
|
Undo->PushItem(EDIT_UNDO_DEL_TEXT, currline, _qlen1, ptr-_qlenptr);
|
|
memmove(_qlenptr, ptr, strlen(ptr) + 1);
|
|
}
|
|
|
|
// 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");
|
|
|
|
Undo->PushItem(EDIT_UNDO_OVR_CHAR);
|
|
currline->text[col] = toupper(currline->text[col]);
|
|
|
|
GFTRK(NULL);
|
|
}
|
|
|
|
|
|
// ------------------------------------------------------------------
|
|
|
|
void IEclass::ToLower() {
|
|
|
|
GFTRK("EditToLower");
|
|
|
|
Undo->PushItem(EDIT_UNDO_OVR_CHAR);
|
|
currline->text[col] = tolower(currline->text[col]);
|
|
|
|
GFTRK(NULL);
|
|
}
|
|
|
|
|
|
// ------------------------------------------------------------------
|
|
|
|
void IEclass::ToggleCase() {
|
|
|
|
GFTRK("EditToggleCase");
|
|
|
|
Undo->PushItem(EDIT_UNDO_OVR_CHAR);
|
|
if(toupper(currline->text[col]) == currline->text[col])
|
|
currline->text[col] = tolower(currline->text[col]);
|
|
else
|
|
currline->text[col] = toupper(currline->text[col]);
|
|
|
|
GFTRK(NULL);
|
|
}
|
|
|
|
|
|
// ------------------------------------------------------------------
|
|
|
|
void IEclass::LookupCursor() {
|
|
|
|
GFTRK("EditLookupCursor");
|
|
|
|
LookupNode(msgptr, currline->text+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->text+col-tlen, tlen)) {
|
|
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);
|
|
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->text+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 = YES;
|
|
|
|
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:
|
|
case KK_EditPaste:
|
|
if(selecting) {
|
|
BlockCut(true);
|
|
batch_mode = BATCH_MODE;
|
|
if(__key != KK_EditPaste)
|
|
__key = KK_EditUndefine;
|
|
}
|
|
goto noselecting;
|
|
break;
|
|
|
|
default:
|
|
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_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 = PlayMacro(__key, KT_E);
|
|
}
|
|
|
|
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();
|
|
if(marginquotes > margintext)
|
|
marginquotes = margintext;
|
|
|
|
whelppcat(H_Editor);
|
|
|
|
if(currline == NULL) {
|
|
currline = (Line*)throw_xcalloc(1, sizeof(Line));
|
|
currline->text = throw_strdup("\n");
|
|
}
|
|
|
|
// 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->text[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->text[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);
|
|
whelpop();
|
|
|
|
// 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 {
|
|
throw_release(__line->text);
|
|
if(__line->prev) {
|
|
__line = __line->prev;
|
|
throw_xrelease(__line->next);
|
|
}
|
|
else {
|
|
throw_xrelease(__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_WRAP_TEXT:
|
|
delete last_item->data.text_ptr;
|
|
break;
|
|
case EDIT_UNDO_DEL_LINE:
|
|
if(last_item->data.line_ptr->isallocated())
|
|
throw_release(last_item->data.line_ptr->text);
|
|
throw_xfree(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) {
|
|
|
|
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;
|
|
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->text[last_item->col.num];
|
|
break;
|
|
case EDIT_UNDO_DEL_TEXT:
|
|
last_item->line = __line;
|
|
if(__len == NO_VALUE)
|
|
__len = strlen(__line->text + __col) + 1;
|
|
throw_new(last_item->data.text_ptr = new(__len) text_item(__col, __len));
|
|
memcpy(last_item->data.text_ptr->text, __line->text + __col, __len);
|
|
break;
|
|
case EDIT_UNDO_CUT_TEXT:
|
|
last_item->line = __line;
|
|
break;
|
|
case EDIT_UNDO_INS_TEXT:
|
|
last_item->line = __line;
|
|
goto save_item;
|
|
case EDIT_UNDO_WRAP_TEXT:
|
|
last_item->line = __line->prev;
|
|
save_item:
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// ------------------------------------------------------------------
|
|
|
|
void UndoStack::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;
|
|
currline = (item->action & PREV_LINE) ? item->line->next : item->line;
|
|
editor->getthisrow(currline);
|
|
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 > strlen(currline->text)));
|
|
|
|
// 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 > strlen(currline->text));
|
|
|
|
uint _pcol = item->pcol;
|
|
uint _prow = item->prow;
|
|
|
|
if(undo_ready) {
|
|
|
|
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:
|
|
editor->strdelchr(currline->text, last_item->col.num);
|
|
break;
|
|
case EDIT_UNDO_DEL_CHAR:
|
|
editor->strinschr(currline->text, last_item->data.char_int, last_item->col.num);
|
|
break;
|
|
case EDIT_UNDO_OVR_CHAR:
|
|
currline->text[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;
|
|
char *dest_ptr, *from_ptr, *thistext = currline->text;
|
|
switch(undo_action) {
|
|
case EDIT_UNDO_DEL_TEXT:
|
|
thistext = currline->text = (char*)throw_realloc(thistext, strlen(thistext) + text_data->len + 1);
|
|
memmove(thistext + text_data->col + text_data->len, thistext + text_data->col, strlen(thistext + text_data->col) + 1);
|
|
memcpy(thistext + text_data->col, text_data->text, text_data->len);
|
|
delete text_data;
|
|
break;
|
|
case EDIT_UNDO_CUT_TEXT:
|
|
thistext[last_item->col.num] = NUL;
|
|
break;
|
|
case EDIT_UNDO_WRAP_TEXT:
|
|
thistext = currline->text = (char*)throw_realloc(thistext, strlen(thistext) + text_data->len + 1);
|
|
strncat(thistext, currline->next->text + text_data->col, text_data->len);
|
|
thistext = currline->next->text;
|
|
// fall through...
|
|
case EDIT_UNDO_INS_TEXT:
|
|
dest_ptr = thistext + text_data->col;
|
|
from_ptr = dest_ptr + text_data->len;
|
|
memmove(dest_ptr, from_ptr, strlen(from_ptr) + 1);
|
|
delete text_data;
|
|
break;
|
|
}
|
|
editor->setlinetype(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_release(thisline->text);
|
|
throw_xfree(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)
|
|
down = true;
|
|
else*/ 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);
|
|
|
|
editor->refresh(currline, row);
|
|
undo_enabled = YES;
|
|
editor->getthisrow(currline);
|
|
uint temprow = thisrow, posrow;
|
|
|
|
Line *templine = editor->findfirstline();
|
|
if(templine) {
|
|
for(posrow=0; posrow < _prow; posrow++)
|
|
if(templine->next)
|
|
templine = templine->next;
|
|
thisrow = posrow; col = _pcol;
|
|
currline = templine;
|
|
|
|
if(not in_range(thisrow-temprow, minrow, maxrow)) {
|
|
do {
|
|
if(thisrow > temprow) {
|
|
if(row > minrow)
|
|
temprow--, row--;
|
|
else {
|
|
editor->refresh(templine, row);
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
if(row < maxrow)
|
|
temprow++, row++;
|
|
else {
|
|
editor->refresh(templine, row = minrow);
|
|
break;
|
|
}
|
|
}
|
|
} while(temprow != thisrow);
|
|
}
|
|
else {
|
|
row += thisrow-temprow;
|
|
editor->refresh(templine, row);
|
|
}
|
|
}
|
|
}
|
|
// Move the cursor to EOL if necessary
|
|
else if((col+1) > strlen(currline->text))
|
|
editor->GoEOL();
|
|
undo_ready = YES;
|
|
}
|
|
}
|
|
|
|
|
|
// ------------------------------------------------------------------
|