/*
 * recenteditor.c
 *
 * A programmers editor
 *
 * Author: Dario Rodriguez dario@softhome.net
 * This program is licensed under the terms of GNU GPL v2.1+
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <limits.h>
#include <signal.h>
#include <time.h>

#include "re_data.h"
#include "re_plugin_unsaved.h"
#include "re_plugin_highlighter.h"
#include "re_plugin_prototypes.h"
#include "re_ui.h"
#include "ext/socklib.h"

#define LINEFORCESCROLL 1
#define COLFORCESCROLL 5

#define IDLETIMEOUTSECONDS 10

#define COMMANDBUFSIZE 1024
#define DEFAULTFONTHEIGHT 14
#define SELECTBUFBLOCK 16384

#define COMMAND_WARNING "(!)"
#define COMMAND_INFO "(i)"
#define COMMAND_GOTOLINE "Go to line:"
#define COMMAND_SEARCHFORWARD "Search:"
#define COMMAND_REPLACEWHAT "Search for:"
#define COMMAND_REPLACEWITH "Replace with:"
#define COMMAND_REPLACEHOW "Replace options (Igncase Selected Backwards Entire All Noconfirm):"
#define COMMAND_CONFIRMEXIT "There is unsaved data. Please confirm exit with y+ENTER:"
#define COMMAND_QUESTION "(?)"
#define COMMAND_EXIT "Exit"

/* "forest" theme */
#define COLOR_STATUSBG "\x14\x3a\xaf\xff"
#define COLOR_STATUSFG "\xe6\xdc\x5d\xff"
#define COLOR_STATUSFGLIGHT "\x6f\x73\xa3\xff"

#define COLOR_QUERYBG "\xad\x92\x5e\xff"
#define COLOR_QUERYFG "\xd0\xef\x4f\xff"

#define COLOR_WARNINGBG "\xba\x07\x07\xff"
#define COLOR_WARNINGFG "\xe6\xdc\x5d\xff"

#define COLOR_INFOBG "\x4e\x8a\x4e\xff"
#define COLOR_INFOFG "\xee\xee\x46\xff"
#define COLOR_INFOFGLIGHT "\x84\xa4\x4c\xff"

typedef struct re_t {
        redata_t *data;
        reui_t *ui;
        int flag_newfile;
        char filename[PATH_MAX];
        int x, y, w, h; // contents rect
        int fontheightpercent;
        int originline,origincol;
        int curline,curcol;
        int maxrow,maxcol;
        int headerdirty;
        int contentsdirty;
        int command_first_key;
        int selactive;
        int sellinefrom,sellineto;
        int selcolfrom,selcolto;
        long sizeselectbuf;
        long usedselectbuf;
        char *selectbuf;
        char *command;
        char commandbuf[COMMANDBUFSIZE];
        char cachelastsearch[COMMANDBUFSIZE];
        char cachelastreplacewith[COMMANDBUFSIZE];
        question_t *question;
        int showingwarning;
        int ignorenkeys;
} re_t;

volatile int flag_sigint;
volatile int flag_sigpipe;


static int setsignal(int num, void (*sighandler)(int));
static void sighandler_sigint(int num);
static void sighandler_sigpipe(int num);
static int mystricmp(const char *s1, const char *s2);


re_t *re_init(void);
void re_free(re_t *re);
int re_setuidata(re_t *re);

int re_setfilename(re_t *re, char *filename);
int re_fixorigin(re_t *re);
int re_fixorigin_center(re_t *re);
int re_processkey_editing(re_t *re, SDL_Event *event);
int re_processkey_commandwait(re_t *re, SDL_Event *event);
int re_processkey_commanddata(re_t *re, SDL_Event *event);
int re_processcommand(re_t *re);
int re_moveupdown(re_t *re, int totalinc);
int re_moveleftright(re_t *re, int totalinc);
int re_changefontsize(re_t *re, int direction);
int re_sel_setstart(re_t *re, int line, int col);
int re_sel_setend(re_t *re, int line, int col);
int re_sel_resize(re_t *re,int oldcol,int oldline,int direction);
int re_sel_toggle(re_t *re);
int re_sel_lincolisinside(re_t *re, int line, int col);
int re_sel_lincolisbefore(re_t *re, int line, int col);
int re_sel_lincolisafter(re_t *re, int line, int col);
int re_sel_lincolisend(re_t *re, int line, int col);
int re_selectbuf_resize(re_t *re,long size);
int re_selectbuf_fill(re_t *re,long frompos,long size, int nadditionalspaces);
int re_selectbuf_replace(re_t *re,char *newdata);
int re_rtrim(re_t *re, long curpos, int *trimmed);
long re_getmatchingbracket(re_t *re,long posini, char originalchar, char *matchingchar);
int re_drawheader_editing(re_t *re);
int re_drawheader_command(re_t *re);
int re_drawcontents(re_t *re);

int
main(int argc, char *argv[])
{
        re_t *re;
        int do_exit;
        SDL_Event event;
        sselect *ssel;
        int flag_had_events;
        time_t lastidle,now;
        int load_pending;
        if(argc!=2 || strcmp(argv[argc-1],"--help")==0) {
                fprintf(stderr,"Syntax: %s filename\n",argv[0]);
                return(1);
        }
        if((re=re_init())==NULL) {
                fprintf(stderr,"ERROR: couldn't init structs.\n");
                return(2);
        }
        if((ssel=sselect_init())==NULL) {
                fprintf(stderr,"ERROR: couln't init internal data.\n");
                re_free(re),re=NULL;
                return(2);
        }
        if(re_setfilename(re,argv[argc-1])!=0) {
                fprintf(stderr,"ERROR: filename too long.\n");
                re_free(re),re=NULL;
                return(2);
        }
        reui_title(re->ui,re->filename);
        flag_sigint=flag_sigpipe=0;
        setsignal(SIGINT,sighandler_sigint);
        setsignal(SIGPIPE,sighandler_sigpipe);
        do_exit=0;
        reui_scr2renderer(re->ui,0,0,re->ui->w,re->ui->h);
        re_setuidata(re);
        re->headerdirty=1;
        re->contentsdirty=1;
        flag_had_events=0;
        SDL_StartTextInput();
        lastidle=0;
        load_pending=1;
        redata_loadquestions_setup(re->data, re->filename);
        while(do_exit==0 && flag_sigint==0) {
                /* load file (or ask pre-load questions), if pending */
                if(load_pending && re->command==NULL) {
                        question_t *question;
                        if((question=redata_loadquestions_getnext(re->data))!=NULL) {
                                re->command=COMMAND_QUESTION;
                                re->question=question;
#if 0
fprintf(stderr,"QUESTION INIT: %s: %s\n",re->question->title,re->question->body);
#endif
                        } else {
                                long startpos;
                                char *startptr;
                                int len;
                                if(redata_load(re->data,re->filename,NULL)==-1)
                                        re->flag_newfile=1;
                                else
                                        re->flag_newfile=0;
                                re->originline=re->origincol=0;
                                re->curline=re->curcol=0;
                                load_pending=0;
                                re->headerdirty=1;
                                re->contentsdirty=1;
                                /* check if last character of file is '\n', if not, add it */
                                if(redata_getused(re->data)==0
                                  || (redata_line_rawinfo(re->data,redata_getused(re->data)-1,&startpos,&startptr,&len,NULL)!=-1
                                    && len>0 && startptr[len-1]!='\n')) {
                                        redata_op_add(re->data,redata_getused(re->data),"\n",1,NULL);
                                        re->command=COMMAND_WARNING;
                                        snprintf(re->commandbuf,sizeof(re->commandbuf),"Added missing \\n at end of file");
                                        re->commandbuf[sizeof(re->commandbuf)-1]='\0';
                                }
                        }
                }
                if(re->headerdirty) {
                        if(re->command==NULL) {
#if 0
fprintf(stderr,"REDRAW Header (editing)\n");
#endif
                                re_drawheader_editing(re);
                                re->showingwarning=0;
                        } else {
#if 0
fprintf(stderr,"REDRAW Header (command)\n");
#endif
                                re_drawheader_command(re);
                                if(strcmp(re->command,COMMAND_WARNING)==0 || strcmp(re->command,COMMAND_INFO)==0) {
                                        /* the warnings/info only get shown once, remove it */
                                        re->command=NULL;
                                        re->commandbuf[0]='\0';
                                        re->showingwarning=1;
                                } else {
                                        re->showingwarning=0;
                                }
                        }
                }
                if(re->contentsdirty)
                        re_drawcontents(re);
                if(re->ui->rendererdirty) {
#if 0
fprintf(stderr,"RENDER\n");
#endif
                        reui_present(re->ui);
                }
                sselect_wait(ssel,(flag_had_events)?10:100);
                flag_had_events=(flag_had_events>0)?flag_had_events-1:0;
                SDL_PumpEvents();
                while(SDL_PeepEvents(&event,1,SDL_GETEVENT,SDL_FIRSTEVENT,SDL_LASTEVENT)>0) {
                        flag_had_events=10;
                        switch(event.type) {
                                case SDL_QUIT:
                                        if(redata_needs_saving(re->data)) {
                                                re->command=COMMAND_CONFIRMEXIT;
                                                re->commandbuf[0]='\0';
                                                re->headerdirty=1;
                                        } else {
                                                do_exit=1;
                                        }
                                        break;
                                case SDL_KEYDOWN:
                                case SDL_TEXTINPUT:
                                case SDL_TEXTEDITING:
                                        if(re->command==NULL || strcmp(re->command,COMMAND_WARNING)==0 || strcmp(re->command,COMMAND_INFO)==0)
                                                re_processkey_editing(re,&event);
                                        else if(re->command[0]=='\0')
                                                re_processkey_commandwait(re,&event);
                                        else
                                                re_processkey_commanddata(re,&event);
                                        break;
                                case SDL_WINDOWEVENT:
#if 0
fprintf(stderr,"WINDOWEVENT: %s(%li) data1:%li data2:%li\n",
(event.window.event==SDL_WINDOWEVENT_NONE)?"NONE":
(event.window.event==SDL_WINDOWEVENT_SHOWN)?"SHOWN":
(event.window.event==SDL_WINDOWEVENT_HIDDEN)?"HIDDEN":
(event.window.event==SDL_WINDOWEVENT_EXPOSED)?"EXPOSED":
(event.window.event==SDL_WINDOWEVENT_MOVED)?"MOVED":
(event.window.event==SDL_WINDOWEVENT_RESIZED)?"RESIZED":
(event.window.event==SDL_WINDOWEVENT_SIZE_CHANGED)?"SIZE_CHANGED":
(event.window.event==SDL_WINDOWEVENT_MINIMIZED)?"MINIMIZED":
(event.window.event==SDL_WINDOWEVENT_MAXIMIZED)?"MAXIMIZED":
(event.window.event==SDL_WINDOWEVENT_RESTORED)?"RESTORED":
(event.window.event==SDL_WINDOWEVENT_FOCUS_GAINED)?"FOCUS_GAINED":
(event.window.event==SDL_WINDOWEVENT_FOCUS_LOST)?"FOCUS_LOST":
(event.window.event==SDL_WINDOWEVENT_CLOSE)?"CLOSE":
(event.window.event==SDL_WINDOWEVENT_TAKE_FOCUS)?"TAKE_FOCUS":
"UNKNOWN",
(long) event.window.event,(long)event.window.data1,(long)event.window.data2);
#endif
                                        if(event.window.event==SDL_WINDOWEVENT_SHOWN
                                          || event.window.event==SDL_WINDOWEVENT_EXPOSED) {
                                                if(!re->showingwarning)
                                                        re->headerdirty=1;
                                                re->contentsdirty=1;
                                        } else if((event.window.event==SDL_WINDOWEVENT_RESIZED
                                          || event.window.event==SDL_WINDOWEVENT_SIZE_CHANGED)
                                          && (event.window.data1!=re->ui->w || event.window.data2!=re->ui->h)) {
#if 0
fprintf(stderr,"Resizing from %ix%i to %ix%i...\n",re->ui->w,re->ui->h,(int)event.window.data1,(int)event.window.data2);
#endif
                                                reui_resize(re->ui,event.window.data1,event.window.data2);

                                                re_setuidata(re);
                                                re->headerdirty=1;
                                                re->contentsdirty=1;
                                        }
                                        break;
                                default:
                                        break;
                        }
                }
                /* additional processing of global commands */
                if(re->command!=NULL && strcmp(re->command,COMMAND_EXIT)==0)
                        do_exit=1;
                /* timeouts */
                now=time(NULL);
                if((lastidle+IDLETIMEOUTSECONDS)<now || (lastidle-IDLETIMEOUTSECONDS)>now) {
                        lastidle=now;
                        redata_idleproc(re->data,re->filename);
                }
        }
        SDL_StopTextInput();
        sselect_free(ssel),ssel=NULL;
        re_free(re),re=NULL;
        return(0);
}

static int
setsignal(int num, void (*sighandler)(int))
{
        struct sigaction sa;
        sa.sa_handler=sighandler;
        sigemptyset(&sa.sa_mask);
        sa.sa_flags=0;
        return(sigaction(num,&sa,0));
}

static void
sighandler_sigint(int num)
{
        flag_sigint=1;
}

static void
sighandler_sigpipe(int num)
{
        flag_sigpipe=1;
}

static int
mystricmp(const char *s1, const char *s2)
{
        int c1,c2;
        for(;*s1!='\0' && *s2!='\0';s1++,s2++) {
                if(*s1==*s2)
                        continue;
                c1=*((unsigned char *)s1);
                c2=*((unsigned char *)s2);
                c1=(c1>='A' && c1<='Z')?c1-'A'+'a':c1;
                c2=(c2>='A' && c2<='Z')?c2-'A'+'a':c2;
                if(c1==c2)
                        continue;
                return(c1-c2);
        }
        return(((int) (*((unsigned char *)s1)))-((int) (*((unsigned char *)s2))));
}

re_t *
re_init(void)
{
        re_t *re;
        if((re=malloc(sizeof(re_t)))==NULL)
                return(NULL); /* insuf. mem. */
        memset(re,0,sizeof(re_t));
        if((re->data=redata_init(
          redata_unsaved_register,
          redata_highlighter_register,
          redata_prototypes_register,
          NULL))==NULL) {
                re_free(re),re=NULL;
                return(NULL); /* insuf. mem. */
        }
        re->fontheightpercent=100;
        if((re->ui=reui_init(DEFAULTFONTHEIGHT*re->fontheightpercent/100))==NULL) {
                re_free(re),re=NULL;
                return(NULL); /* video init error */
        }
        return(re);
}

void
re_free(re_t *re)
{
        if(re==NULL)
                return; /* all done */
        if(re->ui!=NULL)
                reui_free(re->ui),re->ui=NULL;
        if(re->data!=NULL)
                redata_free(re->data),re->data=NULL;
        if(re->selectbuf!=NULL)
                free(re->selectbuf),re->selectbuf=NULL,re->usedselectbuf=re->sizeselectbuf=0;
        free(re),re=NULL;
        return;
}

int
re_setuidata(re_t *re)
{
        if(re==NULL)
                return(-1);
        re->x=0,re->y=re->ui->fontheight,re->w=re->ui->w,re->h=re->ui->h-re->y;
        re->maxrow=re->h/re->ui->fontheight-1;
        re->maxcol=re->w/re->ui->fontwidth-1;
        re_fixorigin(re);
        return(0);
}


int
re_setfilename(re_t *re, char *filename)
{
        if(re==NULL || filename==NULL)
                return(-1);
        strncpy(re->filename,filename,sizeof(re->filename));
        re->filename[sizeof(re->filename)-1]='\0';
        if(strcmp(filename,re->filename)!=0)
                return(-1); /* filename too long */
        return(0);
}

int
re_fixorigin(re_t *re)
{
        if(re==NULL)
                return(-1);
        if(re->curline<(re->originline+LINEFORCESCROLL)) {
                re->originline=re->curline-LINEFORCESCROLL;
                re->originline=(re->originline<0)?0:re->originline;
        }
        if(re->curline>(re->originline+re->maxrow-LINEFORCESCROLL)) {
                re->originline=re->curline+LINEFORCESCROLL-re->maxrow;
                re->originline=(re->originline<0)?0:(re->originline>re->curline)?re->curline:re->originline;
        }
        if(re->curcol<re->origincol) {
                re->origincol=re->curcol;
                re->origincol=(re->origincol<0)?0:re->origincol;
        }
        if(re->curcol>=(re->origincol+re->maxcol-COLFORCESCROLL)) {
                int col;
                col=re->curcol-(re->curcol%COLFORCESCROLL);
                re->origincol=col+COLFORCESCROLL-re->maxcol;
                re->origincol=(re->origincol<0)?0:re->origincol;

        }
        return(0);
}

int
re_fixorigin_center(re_t *re)
{
        if(re==NULL)
                return(-1);
        if(re->curline<re->originline || re->curline>=(re->originline+re->maxrow)) {
                re->originline=re->curline-(re->maxrow/2);
                re->originline=(re->originline<0)?0:re->originline;
        }
        re->origincol=(re->curcol>(re->maxcol-COLFORCESCROLL))?(re->curcol-(re->maxcol-COLFORCESCROLL)):0;
        return(0);
}


int
re_processkey_editing(re_t *re, SDL_Event *event)
{
        SDL_Event fakeevent;
        if(re==NULL || event==NULL)
                return(-1); /* sanity check failed */
        /* convert RETURN KEYDOWN to TEXTINPUT */
        if(event->type==SDL_KEYDOWN && event->key.keysym.sym==SDLK_RETURN) {
                memset(&fakeevent,0,sizeof(SDL_Event));
                event=&fakeevent;
                event->type=SDL_TEXTINPUT;
                strncpy(event->text.text,"\n",sizeof(event->text.text));
                event->text.text[sizeof(event->text.text)-1]='\0';
        } else if(event->type==SDL_KEYDOWN && event->key.keysym.sym==SDLK_TAB) {
                static char spaces[8]={"        "};
                memset(&fakeevent,0,sizeof(SDL_Event));
                event=&fakeevent;
                event->type=SDL_TEXTINPUT;
                strncpy(event->text.text,spaces+(re->curcol%8),sizeof(event->text.text));
                event->text.text[sizeof(event->text.text)-1]='\0';
        } else if(event->type==SDL_KEYDOWN && event->key.keysym.sym==SDLK_DELETE) {
#if 0
fprintf(stderr,"SDL_KEYDOWN: DELETE\n");
#endif
                long realend;
                int at_end;
                long cursorpos;
                if(redata_linecol2pos(re->data,re->curline,re->curcol,&cursorpos,NULL)!=0
                  || cursorpos==redata_getused(re->data)) {
                        return(0); /* already at end, nothing to delete */
                }
                if(redata_line_realend(re->data,cursorpos,&realend)==-1)
                        return(-1); /* couldn't get current line info */
                at_end=(cursorpos==realend)?1:0;
                if(at_end && (cursorpos+1)==redata_getused(re->data))
                        return(-1); /* we shouldn't erase the final '\n' */
                memset(&fakeevent,0,sizeof(SDL_Event));
                event=&fakeevent;
                event->type=SDL_KEYDOWN;
                event->key.keysym.sym=SDLK_BACKSPACE;
                redata_undo_groupinit(re->data,NULL);
                if(at_end) {
                        int col;
                        if(redata_pos2linecol(re->data,cursorpos,NULL,&col)==-1)
                                return(-1); /* couldn't get line info */
                        if(redata_op_addn(re->data,cursorpos,' ',re->curcol-col,NULL)!=0)
                                return(-1); /* couldn't add spaces up to current displayed pos */
                        re->curline++;
                        re->curcol=0;
                } else {
                        re_moveleftright(re,1);
                }
#if 0
fprintf(stderr,"SDL_KEYDOWN: fake setup ok\n");
#endif
        }
        /* special case: text editing event */
        if(event->type==SDL_TEXTINPUT) {
                int len;
                long realend;
                int at_end;
                int nspaces;
                long cursorpos;
                if(re->ignorenkeys>0) {
                        re->ignorenkeys--;
                        return(0); /* this is an already processed key, ignore it */
                }
#if 0
fprintf(stderr,"SDL_TEXTINPUT:\"%s\"\n",event->text.text);
#endif
                if(redata_linecol2pos(re->data,re->curline,re->curcol,&cursorpos,NULL)!=0
                  || redata_line_realend(re->data,cursorpos,&realend)==-1) {
                        return(-1); /* couldn't get current line info */
                }
                at_end=(cursorpos==realend)?1:0;
                for(nspaces=0;event->text.text[nspaces]==' ';nspaces++)
                        ;
                if(nspaces>0 && event->text.text[nspaces]=='\0' && at_end) {
                        /* instead of adding spaces at the end of the line, we move the cursor to the right */
                        re_moveleftright(re,nspaces);
                        return(0); /* no need to add spaces at the end of the line */
                }
                redata_undo_groupinit(re->data,NULL);
                if(!(event->text.text[0]=='\n' && event->text.text[1]=='\0') && at_end) {
                        int col;
                        if(redata_pos2linecol(re->data,cursorpos,NULL,&col)==-1)
                                return(-1); /* couldn't get line info */
                        if(redata_op_addn(re->data,cursorpos,' ',re->curcol-col,NULL)!=0)
                                return(-1); /* couldn't add spaces up to current displayed pos */
                        /* increment cursorpos; spaces are 1 byte, so the number of columns advanced is the number of bytes advanced */
                        cursorpos+=re->curcol-col;
                }
                len=strlen(event->text.text);
                if(redata_op_add(re->data,cursorpos,event->text.text,len,NULL)!=0)
                        return(-1); /* couldn't add requested text */
                cursorpos+=len;
                if(event->text.text[0]=='\n' && event->text.text[1]=='\0') {
                        int trimmed;
                        if(re_rtrim(re,cursorpos-len,&trimmed))
                                cursorpos-=trimmed;
#if 1
#if 0
fprintf(stderr,"SDL_TEXTINPUT: Insering newline\n");
#endif
#endif
                        re->curline++;
                        re->curcol=0;
                } else {
                        re->curcol+=redata_generic_utf8len(event->text.text,len);
                }
                redata_undo_groupcommit(re->data,NULL);
                re_fixorigin(re);
                re->headerdirty=1;
                re->contentsdirty=1;
        }
#if 0
        else if(event->type==SDL_TEXTEDITING) {
                /* NOTE: on window enter events we cam also receive this event (!) */
#warning TODO: CJK support
fprintf(stderr,"SDL_TEXTEDITING: composition:\"%s\" start:%i len:%i\n",event->edit.text,event->edit.start,event->edit.length);
        }
#endif
        /* default case: keydown event */
        if(event->type!=SDL_KEYDOWN)
                return(0); /* Ignore other possible events */
#if 0
fprintf(stderr,"SDL_KEYDOWN: sym:%i\n",event->key.keysym.sym);
#endif
        if(event->key.keysym.sym==SDLK_ESCAPE) {
                re->headerdirty=1;
        } else if((SDL_GetModState()&KMOD_ALT)!=0 && re->selactive && (event->key.keysym.sym==SDLK_LEFT || event->key.keysym.sym==SDLK_RIGHT)) {
                int l;
                int is_del;
                int tolinefix;
                long pos,pos2;
                is_del=(event->key.keysym.sym==SDLK_LEFT)?1:0;
                redata_undo_groupinit(re->data,NULL);
                tolinefix=(re->selcolto==0)?1:0;
                for(l=re->sellinefrom;l<=(re->sellineto-tolinefix);l++) {
                        if(redata_linecol2pos(re->data,l,0,&pos,NULL)!=0)
                                continue;
                        if(is_del==1 && redata_linecol2pos(re->data,l,1,&pos2,NULL)!=0)
                                continue;
                        if(!is_del)
                                redata_op_add(re->data,pos," ",1,NULL);
                        else if(pos!=pos2)
                                redata_op_del(re->data,pos,pos2-pos,NULL);
                }
                redata_undo_groupcommit(re->data,NULL);
                re->headerdirty=1;
                re->contentsdirty=1;
        } else if(event->key.keysym.sym==SDLK_DOWN || event->key.keysym.sym==SDLK_UP) {
                int oldcol=re->curcol,oldline=re->curline;
                if(re_moveupdown(re,(event->key.keysym.sym==SDLK_UP)?-1:1)==0 && (SDL_GetModState()&KMOD_SHIFT)!=0 && (re->curcol!=oldcol || re->curline!=oldline)) {
                        re_sel_resize(re,oldcol,oldline,(event->key.keysym.sym==SDLK_UP)?-1:1);
                }
        } else if(event->key.keysym.sym==SDLK_LEFT || event->key.keysym.sym==SDLK_RIGHT) {
                int oldcol=re->curcol,oldline=re->curline;
                if(re_moveleftright(re,(event->key.keysym.sym==SDLK_LEFT)?-1:1)==0 && (SDL_GetModState()&KMOD_SHIFT)!=0 && (re->curcol!=oldcol || re->curline!=oldline)) {
                        re_sel_resize(re,oldcol,oldline,(event->key.keysym.sym==SDLK_LEFT)?-1:1);
                }
        } else if((SDL_GetModState()&KMOD_CTRL)!=0 && (event->key.keysym.sym==SDLK_PAGEDOWN || event->key.keysym.sym==SDLK_PAGEUP)) {
                long cursorpos;
                cursorpos=(event->key.keysym.sym==SDLK_PAGEDOWN)?(redata_getused(re->data)-1):0;
                cursorpos=(cursorpos<0)?0:cursorpos;
                redata_pos2linecol(re->data,cursorpos,&(re->curline),&(re->curcol));
                re_fixorigin_center(re);
                re->headerdirty=1;
                re->contentsdirty=1;
        } else if(event->key.keysym.sym==SDLK_PAGEDOWN || event->key.keysym.sym==SDLK_PAGEUP) {
                re_moveupdown(re,(event->key.keysym.sym==SDLK_PAGEUP)?-(re->maxrow):re->maxrow);
        } else if(event->key.keysym.sym==SDLK_HOME || event->key.keysym.sym==SDLK_END) {
                long newpos;
                long cursorpos;
                if(redata_linecol2pos(re->data,re->curline,re->curcol,&cursorpos,NULL)!=0)
                        return(0); /* error obtaining position */
                if((event->key.keysym.sym==SDLK_HOME && redata_line_realstart(re->data,cursorpos,&newpos)==-1)
                  || (event->key.keysym.sym==SDLK_END && redata_line_realend(re->data,cursorpos,&newpos)==-1)) {
                        return(-1); /* couldn't get destination pos data */
                }
                cursorpos=newpos;
                if(redata_pos2linecol(re->data,cursorpos,NULL,&(re->curcol))==-1)
                        return(-1); /* couldn't get col of current pos */;
                re_fixorigin(re);
                re->headerdirty=1;
                re->contentsdirty=1;
        } else if(event->key.keysym.sym==SDLK_BACKSPACE && (re->curline>0 || re->curcol>0)) {
                int line,col;
                long startpos,newpos,realstart,start;
                char *ptr;
                int len;
                int trimmed;
                int doneinc;
                long cursorpos;
                if(redata_linecol2pos(re->data,re->curline,re->curcol,&cursorpos,NULL)!=0)
#if 0
fprintf(stderr,"SDL_KEYDOWN: BACKSPACE%s\n",(event==&fakeevent)?" (fake)":"");
#endif
                        return(0); /* error obtaining position */
                if(redata_pos2linecol(re->data,cursorpos,&line,&col)==-1)
                        return(-1); /* couldn't get current line data */
                if(col<re->curcol) {
                        re_moveleftright(re,-1);
                        return(0); /* were hovering to the right of last char, only had to move left */
                }
                if(event!=&fakeevent)
                        redata_undo_groupinit(re->data,NULL);
                /* delete the last character */
                if(col>0) {
                        if(redata_line_realstart(re->data,cursorpos,&realstart)==-1)
                                return(-1); /* couldn't get line info */
                        /* get the start of the part to delete */
                        doneinc=0;
                        for(ptr=NULL,len=0,start=0,newpos=cursorpos;doneinc!=1;) {
                                if((newpos-1)<realstart)
                                        break;
                                newpos--;
                                if(ptr==NULL || (start+len)<=newpos || newpos<start) {
                                        if(redata_line_rawinfo(re->data,newpos,&start,&ptr,&len,NULL)==-1)
                                                return(-1); /* couldn't get line data */
                                }
                                if(redata_generic_utf8isstartbyte(ptr[newpos-start]))
                                        doneinc++;
                        }
                        /* delete */
                        redata_op_del(re->data,newpos,cursorpos-newpos,NULL);
                        cursorpos=newpos;
                } else {
                        long delpos;
                        /* to make the code trivial, we're being lazy on '\n' deletion: we call ...rawinfo() for each byte in the multibyte utf-8 sequence */
                        for(delpos=cursorpos-1
                          ;delpos>0
                          && redata_line_rawinfo(re->data,delpos,&startpos,&ptr,&len,NULL)==0;) {
                                if(redata_generic_utf8isstartbyte(ptr[delpos-startpos]))
                                        break;
                        }
                        redata_op_del(re->data,delpos,cursorpos-delpos,NULL);
                        cursorpos=delpos;
                }
                redata_pos2linecol(re->data,cursorpos,&(re->curline),&(re->curcol));
                /* if cursor at end of line and there are trailing spaces at the end, delete them too */
                if(re_rtrim(re,cursorpos,&trimmed))
                        cursorpos-=trimmed;
                /* end of deletion */
                redata_undo_groupcommit(re->data,NULL);
                re_fixorigin(re);
                re->headerdirty=1;
                re->contentsdirty=1;
        } else if(event->key.keysym.sym==SDLK_q && (SDL_GetModState()&KMOD_CTRL)!=0) {
                re->command="";
                re->command_first_key='q';
                re->headerdirty=1;
        } else if(event->key.keysym.sym==SDLK_F2 || (event->key.keysym.sym==SDLK_s && (SDL_GetModState()&KMOD_CTRL)!=0)) {
                char *errormsg=NULL;
                if(redata_save(re->data,re->filename,&errormsg)!=-1)
                        errormsg="File saved";
                re->command=COMMAND_INFO;
                snprintf(re->commandbuf,sizeof(re->commandbuf),errormsg);
                re->commandbuf[sizeof(re->commandbuf)-1]='\0';
                re->headerdirty=1;
        } else if(event->key.keysym.sym==SDLK_k && (SDL_GetModState()&KMOD_CTRL)!=0) {
                re->command="";
                re->command_first_key='k';
                re->headerdirty=1;
        } else if(event->key.keysym.sym==SDLK_z && (SDL_GetModState()&KMOD_CTRL)!=0) {
                long newpos;
                if(redata_op_undo(re->data,&newpos))
                        return(-1);
                redata_pos2linecol(re->data,newpos,&(re->curline),&(re->curcol));
                re_fixorigin(re);
                re->headerdirty=1;
                re->contentsdirty=1;
        } else if(event->key.keysym.sym==SDLK_l && (SDL_GetModState()&KMOD_CTRL)!=0 && re->cachelastsearch[0]!='\0') {
                long newpos;
                long cursorpos;
                if(redata_linecol2pos(re->data,re->curline,re->curcol,&cursorpos,NULL)!=0)
                        return(-1); /* error obtaining position */
                /* search next (forward) */
                if((newpos=redata_searchforward(re->data,cursorpos+1,re->cachelastsearch,strlen(re->cachelastsearch)))==-1) {
                        re->command=COMMAND_WARNING;
                        snprintf(re->commandbuf,sizeof(re->commandbuf),"String not found");
                        re->commandbuf[sizeof(re->commandbuf)-1]='\0';
                        re->headerdirty=1;
                        return(-1);
                }
                cursorpos=newpos;
                redata_pos2linecol(re->data,cursorpos,&(re->curline),&(re->curcol));
                re_fixorigin_center(re);
                re->headerdirty=1;
                re->contentsdirty=1;
        } else if(event->key.keysym.sym==SDLK_PLUS && (SDL_GetModState()&KMOD_CTRL)!=0) {
#warning XXX TODO: Control+shift+'+'/'-' iterates between the color permutations of the current theme in rgb, 6 in total (Control+shift+'0' resets to the default permutation)
                re_changefontsize(re, 1);
                re->ignorenkeys++;
        } else if(event->key.keysym.sym==SDLK_MINUS && (SDL_GetModState()&KMOD_CTRL)!=0) {
                re_changefontsize(re, -1);
                re->ignorenkeys++;
        } else if(event->key.keysym.sym==SDLK_0 && (SDL_GetModState()&KMOD_CTRL)!=0) {
                re_changefontsize(re, 0);
                re->ignorenkeys++;
        } else if((event->key.keysym.sym==SDLK_c || event->key.keysym.sym==SDLK_x) && (SDL_GetModState()&KMOD_CTRL)!=0) {
                long frompos,topos;
                int coldone;
                int is_cut=(event->key.keysym.sym==SDLK_x)?1:0;
                /* Mac-HIG/OpenLook-style copy (ctrl+c) / cut (ctrl+x) */
                if(re->selactive
                  && redata_linecol2pos(re->data,re->sellinefrom,re->selcolfrom,&frompos,NULL)==0
                  && redata_linecol2pos(re->data,re->sellineto,re->selcolto,&topos,&coldone)==0) {
                        re_selectbuf_fill(re,frompos,topos-frompos,re->selcolto-coldone);
                        SDL_SetClipboardText(re->selectbuf);
                        if(is_cut) {
                                redata_undo_groupinit(re->data,NULL);
                                redata_op_del(re->data,frompos,topos-frompos,NULL);
                                redata_undo_groupcommit(re->data,NULL);
                                redata_pos2linecol(re->data,frompos,&(re->curline),&(re->curcol));
                                re_fixorigin(re);
                                re->headerdirty=1;
                        }
                        re_sel_toggle(re);
                        re->contentsdirty=1;
                }
                re->ignorenkeys++;
        } else if(event->key.keysym.sym==SDLK_v && (SDL_GetModState()&KMOD_CTRL)!=0) {
                long cursorpos;
                /* Mac-HIG/OpenLook-style paste (ctrl+v)*/
                if(redata_linecol2pos(re->data,re->curline,re->curcol,&cursorpos,NULL)!=0)
                        return(-1); /* error obtaining position */
                if(re->selactive)
                        re_sel_toggle(re);
                if(SDL_HasClipboardText())
                        re_selectbuf_replace(re,SDL_GetClipboardText());
                if(re->usedselectbuf>0) {
                        redata_undo_groupinit(re->data,NULL);
                        redata_op_add(re->data,cursorpos,re->selectbuf,re->usedselectbuf,NULL);
                        redata_undo_groupcommit(re->data,NULL);
                        cursorpos+=re->usedselectbuf;
                        redata_pos2linecol(re->data,cursorpos,&(re->curline),&(re->curcol));
                        re_fixorigin(re);
                        re->headerdirty=1;
                        re->contentsdirty=1;
                }
                re->ignorenkeys++;
        }
        return(0);
}


int
re_processkey_commandwait(re_t *re, SDL_Event *event)
{
        if(re==NULL || event==NULL)
                return(-1); /* sanity check failed */
        /* default case: keydown event */
        if(event->type!=SDL_KEYDOWN)
                return(0); /* Ignore other possible events */
        if(re->command_first_key=='q' && event->key.keysym.sym==SDLK_l) {
                re->command=COMMAND_GOTOLINE;
                re->commandbuf[0]='\0';
                re->headerdirty=1;
        } else if(re->command_first_key=='q' && event->key.keysym.sym==SDLK_f) {
                re->command=COMMAND_SEARCHFORWARD;
                strncpy(re->commandbuf,re->cachelastsearch,sizeof(re->commandbuf));
                re->commandbuf[sizeof(re->commandbuf)-1]='\0';
                re->headerdirty=1;
        } else if(re->command_first_key=='q' && event->key.keysym.sym==SDLK_a) {
                re->command=COMMAND_REPLACEWHAT;
                strncpy(re->commandbuf,re->cachelastsearch,sizeof(re->commandbuf));
                re->commandbuf[sizeof(re->commandbuf)-1]='\0';
                re->headerdirty=1;
        } else if(re->command_first_key=='k' && event->key.keysym.sym==SDLK_q) {
                if(redata_needs_saving(re->data)) {
                        re->command=COMMAND_CONFIRMEXIT;
                        re->commandbuf[0]='\0';
                } else {
                        re->command=COMMAND_EXIT;
                        re->commandbuf[0]='\0';
                }
                re->headerdirty=1;
        } else if(re->command_first_key=='k' && event->key.keysym.sym==SDLK_h) {
                re_sel_toggle(re);
                re->command=NULL;
                re->headerdirty=1;
        } else if(re->command_first_key=='k' && event->key.keysym.sym==SDLK_b) {
                re_sel_setstart(re,re->curline,re->curcol);
                re->command=NULL;
                re->headerdirty=1;
        } else if(re->command_first_key=='k' && event->key.keysym.sym==SDLK_k) {
                re_sel_setend(re,re->curline,re->curcol);
                re->command=NULL;
                re->headerdirty=1;
        } else if(re->command_first_key=='k' && event->key.keysym.sym==SDLK_y) {
                long frompos,topos;
                int coldone;
                long cursorpos;
                /* wordstar-style blockdel (ctrl+k+y) */
                if(redata_linecol2pos(re->data,re->curline,re->curcol,&cursorpos,NULL)!=0)
                        return(-1); /* error obtaining position */
                if(re->selactive
                  && redata_linecol2pos(re->data,re->sellinefrom,re->selcolfrom,&frompos,NULL)==0
                  && redata_linecol2pos(re->data,re->sellineto,re->selcolto,&topos,&coldone)==0) {
                        re_selectbuf_fill(re,frompos,topos-frompos,re->selcolto-coldone);
                        redata_undo_groupinit(re->data,NULL);
                        redata_op_del(re->data,frompos,topos-frompos,NULL);
                        redata_undo_groupcommit(re->data,NULL);
                        if(cursorpos>frompos) {
                                cursorpos=frompos;
                                redata_pos2linecol(re->data,cursorpos,&(re->curline),&(re->curcol));
                                re_fixorigin(re);
                        }
                        re->headerdirty=1;
                        re->contentsdirty=1;
                }
                if(re->selactive)
                        re_sel_toggle(re);
                re->command=NULL;
                re->headerdirty=1;
        } else if(re->command_first_key=='k' && (event->key.keysym.sym==SDLK_c || event->key.keysym.sym==SDLK_v)) {
                long frompos,topos,insertpos;
                int tocoldone,coldone;
                /* wordstar-style blockcopy (ctrl+k+c) or move (ctrl+k+v)*/
                int is_move=(event->key.keysym.sym==SDLK_v)?1:0;
                if(re->selactive
                  && redata_linecol2pos(re->data,re->sellinefrom,re->selcolfrom,&frompos,NULL)==0
                  && redata_linecol2pos(re->data,re->sellineto,re->selcolto,&topos,&tocoldone)==0
                  && redata_linecol2pos(re->data,re->curline,re->curcol,&insertpos,&coldone)==0
                  && re_selectbuf_fill(re,frompos,topos-frompos,re->selcolto-tocoldone)==0
                  && redata_preallocate(re->data,redata_getused(re->data)+(re->curcol-coldone)+(is_move?0:re->usedselectbuf))==0) {
                        if(is_move && insertpos>=frompos && insertpos<topos) {
                                re->curline=re->sellinefrom;
                                re->curcol=re->selcolfrom;
                                redata_linecol2pos(re->data,re->curline,re->curcol,&insertpos,&coldone);
                        }
                        redata_undo_groupinit(re->data,NULL);
                        if(coldone<re->curcol
                          && redata_op_addn(re->data,insertpos,' ',re->curcol-coldone,NULL)==0) {
                                insertpos+=re->curcol-coldone;
                        }
                        if(is_move) {
                                long cursorpos;
                                redata_op_del(re->data,frompos,re->usedselectbuf,NULL);
                                redata_op_add(re->data,insertpos-((insertpos>frompos)?re->usedselectbuf:0),re->selectbuf,re->usedselectbuf,NULL);
                                cursorpos=insertpos-((insertpos>frompos)?re->usedselectbuf:0)+re->usedselectbuf;
                                redata_pos2linecol(re->data,cursorpos,&(re->curline),&(re->curcol));
                                redata_pos2linecol(re->data,cursorpos-re->usedselectbuf,&(re->sellinefrom),&(re->selcolfrom));
                                re->sellineto=re->curline;
                                re->selcolto=re->curcol;
                        } else {
                                long cursorpos;
                                redata_op_add(re->data,insertpos,re->selectbuf,re->usedselectbuf,NULL);
                                cursorpos=insertpos+re->usedselectbuf;
                                redata_pos2linecol(re->data,cursorpos,&(re->curline),&(re->curcol));
                                redata_pos2linecol(re->data,insertpos,&(re->sellinefrom),&(re->selcolfrom));
                                re->sellineto=re->curline;
                                re->selcolto=re->curcol;
                        }
                        redata_undo_groupcommit(re->data,NULL);
                        re_fixorigin(re);
                        re->headerdirty=1;
                        re->contentsdirty=1;
                }
                re->command=NULL;
                re->headerdirty=1;
        } else {
                re->command=NULL;
                re->headerdirty=1;
        }
        if(!(SDL_GetModState()&KMOD_CTRL))
                re->ignorenkeys=1; /* control was not pressed, we will receive again this key as SDL_TEXTINPUT */
        return(0);
}

int
re_processkey_commanddata(re_t *re, SDL_Event *event)
{
        if(re==NULL || event==NULL)
                return(-1); /* sanity check failed */
        /* special case: text editing event */
        if(event->type==SDL_TEXTINPUT) {
                int len;
                if(re->ignorenkeys>0) {
                        re->ignorenkeys--;
                        return(0); /* this is an already processed key, ignore it */
                }
                len=strlen(event->text.text);
                if((strlen(re->commandbuf)+len+1)>=sizeof(re->commandbuf))
                        return(-1); /* No space for text */
                memcpy(re->commandbuf+strlen(re->commandbuf),event->text.text,len+1);
                re->commandbuf[sizeof(re->commandbuf)-1]='\0';
                re->headerdirty=1;
        }
        /* default case: keydown event */
        if(event->type!=SDL_KEYDOWN)
                return(0); /* Ignore other possible events */
        if(event->key.keysym.sym==SDLK_ESCAPE) {
                re->command=NULL;
                re->commandbuf[0]='\0';
                re->headerdirty=1;
#warning XXX: TODO: re->question: Make cursor change option, make numbers change option
        } else if(event->key.keysym.sym==SDLK_BACKSPACE && re->commandbuf[0]!='\0') {
                int nchar;
                char *ptr;
                if((nchar=redata_generic_utf8len(re->commandbuf,strlen(re->commandbuf)))<=0)
                        return(-1); /* error parsing commandbuf */
                if((ptr=redata_generic_utf8col(re->commandbuf,strlen(re->commandbuf),nchar-1))==NULL)
                        return(-1); /* error positioning in commandbuf */
                *ptr='\0';
                re->headerdirty=1;
        } else if(event->key.keysym.sym==SDLK_RETURN) {
                re_processcommand(re);
        }
        return(0);
}

int
re_processcommand(re_t *re)
{
        if(re==NULL || re->command==NULL)
                return(-1);
        re->commandbuf[sizeof(re->commandbuf)-1]='\0';
        if(strcmp(re->command,COMMAND_CONFIRMEXIT)==0) {
                if(strcmp(re->commandbuf,"y")==0) {
                        re->command=COMMAND_EXIT;
                        re->commandbuf[0]='\0';
                        re->headerdirty=1;
                        return(0);
                }
                re->headerdirty=1;
        } else if(strcmp(re->command,COMMAND_GOTOLINE)==0) {
                int line;
                long pos;
                line=atoi(re->commandbuf)-1;
                line=(line<0)?0:line;
                if(redata_linecol2pos(re->data,line,re->curcol,&pos,NULL)==-1) {
                        re->command=COMMAND_WARNING;
                        snprintf(re->commandbuf,sizeof(re->commandbuf),"Unknown line");
                        re->commandbuf[sizeof(re->commandbuf)-1]='\0';
                        re->headerdirty=1;
                        return(-1);
                }
                re->curline=line;
                /* position the line in the center of viewport */
                re->originline=line-(re->maxrow/2);
                re->originline=(re->originline<0)?0:re->originline;
                re->headerdirty=1;
                re->contentsdirty=1;
        } else if(strcmp(re->command,COMMAND_SEARCHFORWARD)==0) {
                long newpos;
                long cursorpos;
                if(redata_linecol2pos(re->data,re->curline,re->curcol,&cursorpos,NULL)!=0)
                        return(-1); /* error obtaining position */
                if((newpos=redata_searchforward(re->data,cursorpos,re->commandbuf,strlen(re->commandbuf)))==-1) {
                        re->command=COMMAND_WARNING;
                        snprintf(re->commandbuf,sizeof(re->commandbuf),"String not found");
                        re->commandbuf[sizeof(re->commandbuf)-1]='\0';
                        re->headerdirty=1;
                        return(-1);
                }
                strncpy(re->cachelastsearch,re->commandbuf,sizeof(re->cachelastsearch));
                re->cachelastsearch[sizeof(re->cachelastsearch)-1]='\0';
                cursorpos=newpos;
                redata_pos2linecol(re->data,cursorpos,&(re->curline),&(re->curcol));
                re_fixorigin_center(re);
                re->headerdirty=1;
                re->contentsdirty=1;
        } else if(strcmp(re->command,COMMAND_REPLACEWHAT)==0) {
                re->command=COMMAND_REPLACEWITH;
                strncpy(re->cachelastsearch,re->commandbuf,sizeof(re->cachelastsearch));
                re->cachelastsearch[sizeof(re->cachelastsearch)-1]='\0';
                strncpy(re->commandbuf,re->cachelastreplacewith,sizeof(re->commandbuf));
                re->commandbuf[sizeof(re->commandbuf)-1]='\0';
                re->headerdirty=1;
                return(0);
        } else if(strcmp(re->command,COMMAND_REPLACEWITH)==0) {
                re->command=COMMAND_REPLACEHOW;
                strncpy(re->cachelastreplacewith,re->commandbuf,sizeof(re->cachelastreplacewith));
                re->cachelastreplacewith[sizeof(re->cachelastreplacewith)-1]='\0';
                re->commandbuf[0]='\0';
                re->headerdirty=1;
                return(0);
        } else if(strcmp(re->command,COMMAND_REPLACEHOW)==0) {
                int is_all;
                int slen,rlen;
                long oldpos,newpos,endpos;
                long total;
                is_all=(mystricmp(re->commandbuf,"ean")==0)?1:(mystricmp(re->commandbuf,"san")==0)?0:-1;
                if(is_all==-1) {
                        re->command=COMMAND_WARNING;
                        snprintf(re->commandbuf,sizeof(re->commandbuf),"Sorry, unimplemented. For now use only EAN or SAN options.");
                        re->commandbuf[sizeof(re->commandbuf)-1]='\0';
                        re->headerdirty=1;
                        return(-1);
                }
                if(is_all==0 && !re->selactive) {
                        re->command=COMMAND_WARNING;
                        snprintf(re->commandbuf,sizeof(re->commandbuf),"No active selection, nothing changed.");
                        re->commandbuf[sizeof(re->commandbuf)-1]='\0';
                        re->headerdirty=1;
                        return(-1);
                }
                oldpos=0;
                endpos=redata_getused(re->data);
                if(!is_all
                  && (redata_linecol2pos(re->data,re->sellinefrom,re->selcolfrom,&oldpos,NULL)!=0
                  || redata_linecol2pos(re->data,re->sellineto,re->selcolto,&endpos,NULL)!=0)) {
                        re->command=COMMAND_WARNING;
                        snprintf(re->commandbuf,sizeof(re->commandbuf),"Invalid selection.");
                        re->commandbuf[sizeof(re->commandbuf)-1]='\0';
                        re->headerdirty=1;
                        return(-1);
                }
                slen=strlen(re->cachelastsearch);
                rlen=strlen(re->cachelastreplacewith);
                redata_undo_groupinit(re->data,NULL);
                for(total=0
                  ;(oldpos+slen)<endpos && (newpos=redata_searchforward(re->data,oldpos,re->cachelastsearch,slen))!=-1
                    && (newpos+slen)<=endpos
                  ;total++,oldpos=newpos+rlen) {
                        redata_op_del(re->data,newpos,slen,NULL);
                        redata_op_add(re->data,newpos,re->cachelastreplacewith,rlen,NULL);
                }
                redata_undo_groupcommit(re->data,NULL);
                redata_pos2linecol(re->data,oldpos,&(re->curline),&(re->curcol));
                re_fixorigin_center(re);
                re->headerdirty=1;
                re->contentsdirty=1;
                re->command=COMMAND_INFO;
                snprintf(re->commandbuf,sizeof(re->commandbuf),"%li subst",total);
                re->commandbuf[sizeof(re->commandbuf)-1]='\0';
                re->headerdirty=1;
                return(0);
        } else if(strcmp(re->command,COMMAND_QUESTION)==0) {
                /* validate reply */
                if(!(atoi(re->commandbuf)>0 && atoi(re->commandbuf)<=re->question->nopts)) {
                        /* invalid reply: reset buf */
                        re->commandbuf[0]=0;
                        re->headerdirty=1;
                        return(-1);
                }
                /* send reply */
                redata_loadquestions_reply(re->data,re->question,atoi(re->commandbuf)-1);
                re->headerdirty=1;
        }
        re->command=NULL;
        re->commandbuf[0]='\0';
        return(0);
}

int
re_moveupdown(re_t *re, int totalinc)
{
        long newpos,newpos2;
        int inc,doneinc;
        long realstart;
        int needs_inccol;
        long cursorpos;
        if(re==NULL)
                return(-1); /* sanity check failed */
        if(totalinc==0)
                return(0); /* nothing to do */
        inc=(totalinc<0)?-1:1;
        if(redata_linecol2pos(re->data,re->curline,re->curcol,&cursorpos,NULL)!=0)
                return(-1); /* error obtaining position */
        newpos=cursorpos; /* get rid of compiler warning (will be overwitten in the loop as totalinc never is 0) */
        needs_inccol=0;
        for(doneinc=0;doneinc!=totalinc;doneinc+=inc) {
#if 0
fprintf(stderr,"MOVING from cursorpos:%li line:%i col:%i\n",cursorpos,re->curline,re->curcol);
#endif
                if((inc==-1 && redata_line_prevrealstart(re->data,cursorpos,&realstart)==-1)
                   || (inc==1 && redata_line_nextrealstart(re->data,cursorpos,&realstart)==-1)) {
                        break; /* couldn't get current line data, we are at start/end */
                }
                cursorpos=realstart;
                needs_inccol=1;
                re->curline+=inc;
#if 0
fprintf(stderr,"MOVING   to cursorpos:%li line:%i col:%i\n",cursorpos,re->curline,re->curcol);
#endif
        }
        if(!needs_inccol)
                return(-1);
        if(redata_line_inccol(re->data,cursorpos,re->curcol,&newpos2,NULL)==-1) {
                /* error advancing cursor, "emergency" repositioning */
#if 0
fprintf(stderr,"SETTING COLUMN ERROR\n");
#endif
                re->curcol=0;
                newpos2=newpos;
        }
#if 0
fprintf(stderr,"COLUMN from cursorpos:%li line:%i col:%i\n",cursorpos,re->curline,re->curcol);
#endif
        cursorpos=newpos2;
#if 0
fprintf(stderr,"COLUMN   to cursorpos:%li line:%i col:%i\n",cursorpos,re->curline,re->curcol);
#endif
        re_fixorigin(re);
        re->contentsdirty=1;
        re->headerdirty=1;
        return(0);
}

int
re_moveleftright(re_t *re, int totalinc)
{
        long newpos,realstart;
        int inc,doneinc;
        char *ptr;
        int len;
        long start;
        int tmpcol,oldcol;
        long cursorpos;
        if(re==NULL)
                return(-1); /* sanity check failed */
        if(totalinc==0)
                return(0); /* nothing to do */
        if(redata_linecol2pos(re->data,re->curline,re->curcol,&cursorpos,NULL)!=0)
                return(-1); /* error obtaining position */
        oldcol=re->curcol;
        if(totalinc<0 && (re->curcol+totalinc)<=0) {
                /* we'll land on the start of the line -- do it trivially */
                if(redata_line_realstart(re->data,cursorpos,&realstart)==-1)
                        return(-1); /* couldn't get current pos */
                re->curcol=0;
                cursorpos=realstart;
        } else {
                /* move a char at a time */
                if(redata_line_realstart(re->data,cursorpos,&realstart)==-1)
                        return(-1); /* couldn't get current pos */
                inc=(totalinc<0)?-1:1;
                doneinc=0;
                if(redata_pos2linecol(re->data,cursorpos,NULL,&tmpcol)==-1)
                        return(-1); /* couldn't get current pos */
                /* special case: we're just over the '\n' going right, we have to move 1 to enable the generic case to work */
                if(tmpcol==re->curcol && inc>0) {
                        if(redata_line_rawinfo(re->data,cursorpos,&start,&ptr,&len,NULL)==-1)
                                return(-1); /* couldn't get line data */
                        if(ptr[cursorpos-start]=='\n') {
                                tmpcol++;
                                totalinc--;
                                re->curcol++;
                        }
                }
                /* generic case: cursor after the \n ("floating") */
                if(tmpcol<re->curcol) {
                        int avail;
                        if(inc>0) {
                                doneinc=totalinc;
                        } else {
                                avail=re->curcol-tmpcol;
                                doneinc=(avail>=totalinc)?totalinc:avail;
                        }
                }
                /* generic case: move one char at a time */
                for(ptr=NULL,len=0,start=0,newpos=cursorpos;doneinc!=totalinc;) {
                        if((newpos+inc)<realstart)
                                break;
                        newpos+=inc;
                        if(ptr==NULL || (start+len)<=newpos || newpos<start) {
                                if(redata_line_rawinfo(re->data,newpos,&start,&ptr,&len,NULL)==-1)
                                        return(-1); /* couldn't get line data */
                        }
                        if(redata_generic_utf8isstartbyte(ptr[newpos-start]))
                                doneinc+=inc;
                        if(ptr[newpos-start]=='\n')
                                break;
                }
                cursorpos=newpos;
                re->curcol+=doneinc;
                if(inc>0 && doneinc<totalinc && ptr!=NULL && ptr[newpos-start]=='\n')
                        re->curcol+=(totalinc-doneinc);
        }
        if(re->curcol!=oldcol) {
                re->contentsdirty=1;
                re->headerdirty=1;
                if(re->curcol<(re->origincol+COLFORCESCROLL) && re->origincol>0) {
                        re->origincol=(re->curcol-COLFORCESCROLL)/COLFORCESCROLL;
                        re->origincol=re->origincol*COLFORCESCROLL;
                        re->origincol=(re->origincol<0)?0:re->origincol;
                }
                if(re->curcol>(re->origincol+re->maxcol-COLFORCESCROLL)) {
                        re->origincol=re->curcol+COLFORCESCROLL-re->maxcol-(re->curcol%COLFORCESCROLL);
                        re->origincol=(re->origincol<0)?0:re->origincol;
                }
        }
        return(0);
}

int
re_changefontsize(re_t *re, int direction)
{
        int validpercent[]={50,67,90,100,110,120,133,150,170,200,240,300};
        int newpercent;
        int i;
        if(re==NULL)
                return(-1); /* sanity check failed */
        if(direction<0) {
                newpercent=validpercent[0];
                for(i=0;i<(sizeof(validpercent)/sizeof(validpercent[0]));i++) {
                        if(validpercent[i]<re->fontheightpercent)
                                newpercent=validpercent[i];
                        else
                                break;
                }
        } else if(direction>0) {
                newpercent=validpercent[(sizeof(validpercent)/sizeof(validpercent[0]))-1];
                for(i=(sizeof(validpercent)/sizeof(validpercent[0]))-1;i>=0;i--) {
                        if(validpercent[i]>re->fontheightpercent)
                                newpercent=validpercent[i];
                        else
                                break;
                }
        } else {
                newpercent=100;
        }
        if(reui_setfontheight(re->ui,DEFAULTFONTHEIGHT*newpercent/100)==-1)
                return(-1); /* couldn't setup new font size */
        re->fontheightpercent=newpercent;
        re_setuidata(re);
        re_fixorigin(re);
        re->contentsdirty=1;
        re->headerdirty=1;
        return(0);
}

int
re_sel_setstart(re_t *re, int line, int col)
{
        if(re==NULL || line<0 || col<0)
                return(-1);
        re->sellinefrom=line;
        re->selcolfrom=col;
        if(re->selactive==0
          || (re->sellineto<re->sellinefrom)
          || (re->sellineto==re->sellinefrom && re->selcolto<re->selcolfrom)) {
              re->sellineto=re->sellinefrom;
              re->selcolto=re->selcolfrom;
        }
        re->selactive=1;
        re->contentsdirty=1;
        return(0);
}

int
re_sel_setend(re_t *re, int line, int col)
{
        if(re==NULL || line<0 || col<0)
                return(-1);
        re->sellineto=line;
        re->selcolto=col;
        if(re->selactive==0
          || (re->sellineto<re->sellinefrom)
          || (re->sellineto==re->sellinefrom && re->selcolto<re->selcolfrom)) {
              re->sellinefrom=re->sellineto;
              re->selcolfrom=re->selcolto;
        }
        re->selactive=1;
        re->contentsdirty=1;
        return(0);
}

int
re_sel_resize(re_t *re,int oldcol,int oldline,int direction)
{
        if(re==NULL || oldcol<0 || oldline<0 || (direction!=-1 && direction!=1))
                return(-1);
        if(direction==-1) {
                if(re->selactive==0) {
                        re_sel_setstart(re,re->curline,re->curcol);
                        re_sel_setend(re,oldline,oldcol);
                } else if(re_sel_lincolisafter(re,re->curline,re->curcol) || re_sel_lincolisinside(re,re->curline,re->curcol)) {
                        re_sel_setend(re,re->curline,re->curcol);
                } else {
                        re_sel_setstart(re,re->curline,re->curcol);
                }
        } else {
                if(re->selactive==0 || re_sel_lincolisbefore(re,oldline,oldcol)) {
                        re_sel_setstart(re,oldline,oldcol);
                        re_sel_setend(re,re->curline,re->curcol);
                } else if(re_sel_lincolisinside(re,oldline,oldcol)) {
                        re_sel_setstart(re,re->curline,re->curcol);
                } else {
                        re_sel_setend(re,re->curline,re->curcol);
                }
        }
        if(re->sellinefrom==re->sellineto && re->selcolfrom==re->selcolto)
                re->selactive=0;
        return(0);
}

int
re_sel_toggle(re_t *re)
{
        if(re==NULL)
                return(-1);
        re->selactive=1-re->selactive;
        re->contentsdirty=1;
        return(0);
}

int
re_sel_lincolisinside(re_t *re, int line, int col)
{
        if(re==NULL || line<0 || col<0)
                return(0);
        if(line==re->sellinefrom && line==re->sellineto) {
                if(col>=re->selcolfrom && col<re->selcolto)
                        return(1);
                return(0);
        } else if(line==re->sellinefrom) {
                if(col>=re->selcolfrom)
                        return(1);
                return(0);
        } else if(line>re->sellinefrom && line<re->sellineto) {
                return(1);
        } else if(line==re->sellineto) {
                if(col<re->selcolto)
                        return(1);
                return(0);
        }
        return(0);
}

int
re_sel_lincolisbefore(re_t *re, int line, int col)
{
        if(re==NULL || line<0 || col<0)
                return(0);
        if(line<re->sellinefrom || (line==re->sellinefrom && col<re->selcolfrom))
                return(1);
        return(0);
}

int
re_sel_lincolisafter(re_t *re, int line, int col)
{
        if(re==NULL || line<0 || col<0)
                return(0);
        if(line>re->sellineto || (line==re->sellineto && col>=re->selcolto))
                return(1);
        return(0);
}

int
re_sel_lincolisend(re_t *re, int line, int col)
{
        if(re==NULL || line<0 || col<0)
                return(0);
        if(line==re->sellineto && col==re->selcolto)
                return(1);
        return(0);
}


int
re_selectbuf_resize(re_t *re,long size)
{
        long newsize;
        char *newptr;
        if(re==NULL || size<0)
                return(-1); /* sanity check failed */
        if(size==0)
                return(0); /* nothing to do */
        newsize=(size+SELECTBUFBLOCK-1)/SELECTBUFBLOCK;
        newsize*=SELECTBUFBLOCK;
        if((newptr=realloc(re->selectbuf,newsize))==NULL)
                return(-1);
        re->selectbuf=newptr;
        re->sizeselectbuf=newsize;
        return(0);
}

int
re_selectbuf_fill(re_t *re,long frompos,long size, int nadditionalspaces)
{
        int nchunk,off,avail;
        long n;
        if(re==NULL || size<0 || nadditionalspaces<0 || (frompos+size)>redata_getused(re->data) || redata_getposptr(re->data,frompos,&nchunk,&off)!=0)
                return(-1); /* sanity check failed */
        re->usedselectbuf=0;
        if((size+nadditionalspaces+1)>re->sizeselectbuf
          && re_selectbuf_resize(re,size+nadditionalspaces+1)!=0) {
                return(-1); /* insuf. mem. */
        }
        for(n=0;n<size && nchunk<re->data->sizechunks;nchunk++,off=0) {
                avail=re->data->chunks[nchunk]->useddata-off;
                if(avail<=0)
                        continue;
                if(avail>(size-n))
                        avail=size-n;
                memcpy(re->selectbuf+n,re->data->chunks[nchunk]->data+off,avail);
                n+=avail;
        }
        re->usedselectbuf=n;
        if(nadditionalspaces>0) {
                memset(re->selectbuf+re->usedselectbuf,' ',nadditionalspaces);
                re->usedselectbuf+=nadditionalspaces;
        }
        re->selectbuf[re->usedselectbuf]='\0';
        return(0);
}

int
re_selectbuf_replace(re_t *re,char *newdata)
{
        long size;
        if(re==NULL || newdata==NULL)
                return(-1);
        size=strlen(newdata);
        if((size+1)>re->sizeselectbuf
          && re_selectbuf_resize(re,size+1)!=0) {
                return(-1); /* insuf. mem. */
        }
        memcpy(re->selectbuf,newdata,size);
        re->usedselectbuf=size;
        re->selectbuf[re->usedselectbuf]='\0';
        return(0);
}


int
re_rtrim(re_t *re, long curpos, int *trimmed)
{
        long startpos;
        char *start;
        int len;
        int n;
        if(re==NULL || curpos<0 || curpos>redata_getused(re->data))
                return(-1);
        if(trimmed!=NULL)
                *trimmed=0;
        if(redata_line_rawinfo(re->data,curpos,&startpos,&start,&len,NULL)==0 &&
           len>1 && start[len-1]=='\n' && start[len-2]==' ' && curpos==(startpos+len-1)) {
                /* count the number of spaces at end of line */
                n=0;
                while((len-1-n-1)>=0 && start[len-1-n-1]==' ')
                        n++;
                /* delete the counted number of spaces at end of line */
                redata_op_del(re->data,startpos+len-1-n,n,NULL);
                if(trimmed!=NULL)
                        *trimmed=n;
        }
        return(0);
}

long
re_getmatchingbracket(re_t *re,long posini, char originalchar, char *matchingchar)
{
        char *ori,*dest;
        char *pairs="[]{}<>()";
        int is_backwards;
        int colorindex,newcolor;
        long realstart;
        int line;
        long pos;
        long posnextori,posnextdest;
        long posnext;
        int counter;
        if(re==NULL)
                return(-1); /* sanity check failed */
        if((ori=strchr(pairs,originalchar))==NULL)
                return(-1); /* unknown char */
        is_backwards=(ori-pairs)%2;
        dest=ori+((is_backwards)?-1:1);
        if(redata_pos2linecol(re->data,posini,&line,NULL)==-1
          || redata_line_realstart(re->data,posini,&realstart)==-1) {
                return(-1); /* couldn't get line number or startpos */
        }
        colorindex=redata_highlighter_getcolorindex(re->data,line,posini-realstart);
        pos=posini;
        counter=1;
        while(1) {
                /* get the next pos */
                if(!is_backwards) {
                        posnextori=redata_searchforward(re->data,pos+1,ori,1);
                        posnextdest=redata_searchforward(re->data,pos+1,dest,1);
                        posnext=(posnextori==-1)?posnextdest:(posnextdest==-1)?posnextori:(posnextori<posnextdest)?posnextori:posnextdest;
                } else {
                        posnextori=redata_searchbackwards(re->data,pos-1,ori,1);
                        posnextdest=redata_searchbackwards(re->data,pos-1,dest,1);
                        posnext=(posnextori==-1)?posnextdest:(posnextdest==-1)?posnextori:(posnextori>posnextdest)?posnextori:posnextdest;
                }
                if(posnext==-1)
                        break; /* search ended and couln't get the counter to zero */
                /* check that the color index is ok */
                if(redata_pos2linecol(re->data,posnext,&line,NULL)==-1
                  || redata_line_realstart(re->data,posnext,&realstart)==-1) {
                        return(-1); /* couldn't get line number or startpos */
                }
                newcolor=redata_highlighter_getcolorindex(re->data,line,posnext-realstart);
                if(colorindex!=newcolor) {
                        pos=posnext;
                        continue; /* it doesn't have the same color */
                }
                /* do the math */
                if(posnext==posnextori)
                        counter++;
                if(posnext==posnextdest)
                        counter--;
                pos=posnext;
                if(counter==0) {
                        if(matchingchar!=NULL)
                                *matchingchar=*dest;
                        return(posnext); /* found matching bracket */
                }
        }
        return(-1);
}

int
re_drawheader_editing(re_t *re)
{
        long cursorpos;
        char linebuf[32],colbuf[32],posbuf[32],sizebuf[32];
        char *spaceslinebuf,*spacescolbuf,*spacesposbuf,*spacessizebuf;
        char spaces[128];
        int lenfilename;
        char *filename;
        char *spacesfilename;
        if(re==NULL)
                return(-1);
        if(redata_linecol2pos(re->data,re->curline,re->curcol,&cursorpos,NULL)!=0)
                return(0); /* error obtaining position */
        reui_fill(re->ui,0,0,re->ui->w,re->ui->fontheight,redata_needs_saving(re->data)?COLOR_STATUSBG:COLOR_INFOBG);
        /* for the user, lines start at 0 (internally they start at 0) */
        memset(spaces,' ',sizeof(spaces));
        spaces[sizeof(spaces)-1]='\0';
        snprintf(linebuf,sizeof(linebuf),"%i",re->curline+1),linebuf[sizeof(linebuf)-1]='\0';
        spaceslinebuf=spaces+sizeof(spaces)-1-strlen(linebuf);
        snprintf(colbuf,sizeof(colbuf),"%i",re->curcol+1),colbuf[sizeof(colbuf)-1]='\0';
        spacescolbuf=spaces+sizeof(spaces)-1-strlen(colbuf);
        snprintf(posbuf,sizeof(posbuf),"%li",cursorpos),posbuf[sizeof(posbuf)-1]='\0';
        spacesposbuf=spaces+sizeof(spaces)-1-strlen(posbuf);
        snprintf(sizebuf,sizeof(sizebuf),"%li",redata_getused(re->data)),sizebuf[sizeof(sizebuf)-1]='\0';
        spacessizebuf=spaces+sizeof(spaces)-1-strlen(sizebuf);
        lenfilename=strlen(re->filename);
        filename=((lenfilename)>(sizeof(spaces)-1))?(re->filename+lenfilename-sizeof(spaces)-1):re->filename;
        spacesfilename=spaces+sizeof(spaces)-1-strlen(filename);
        reui_printf(re->ui,0,0,redata_needs_saving(re->data)?COLOR_STATUSFGLIGHT:COLOR_INFOFGLIGHT
           ,"File:%s%s Line:%s Col:%s Pos:%s Size:%s"
          ,spacesfilename,redata_needs_saving(re->data)?"*":" ",spaceslinebuf,spacescolbuf,spacesposbuf,spacessizebuf);
        reui_printf(re->ui,0,0,redata_needs_saving(re->data)?COLOR_STATUSFG:COLOR_INFOFG
           ,"     %s%s      %s     %s     %s      %s"
          ,filename," ",linebuf,colbuf,posbuf,sizebuf);
        re->headerdirty=0;
        re->ui->rendererdirty=1;
        return(0);
}

int
re_drawheader_command(re_t *re)
{
        if(re==NULL)
                return(-1);
        if(re->command==NULL)
                return(-1);
        if(re->command[0]=='\0') {
                reui_fill(re->ui,0,0,re->ui->w,re->ui->fontheight,COLOR_QUERYBG);
                reui_printf(re->ui,0,0,COLOR_QUERYFG,"Command:");
        } else if(strcmp(re->command,COMMAND_WARNING)==0 || strcmp(re->command,COMMAND_CONFIRMEXIT)==0) {
                reui_fill(re->ui,0,0,re->ui->w,re->ui->fontheight,COLOR_WARNINGBG);
                reui_printf(re->ui,0,0,COLOR_WARNINGFG,"%s %s",re->command,re->commandbuf);
        } else if(strcmp(re->command,COMMAND_INFO)==0) {
                reui_fill(re->ui,0,0,re->ui->w,re->ui->fontheight,COLOR_INFOBG);
                reui_printf(re->ui,0,0,COLOR_INFOFG,"%s %s",re->command,re->commandbuf);
        } else if(strcmp(re->command,COMMAND_QUESTION)==0) {
                question_t *q;
                reui_fill(re->ui,0,0,re->ui->w,re->ui->fontheight,COLOR_QUERYBG);
#warning XXX TODO: suppont arbitrary number of options, highlight current option
                q=re->question;
                re->commandbuf[sizeof(re->commandbuf)-1]='\0';
                reui_printf(re->ui,0,0,COLOR_QUERYFG,"%s %s: %s %s%s%s%s%s%s%s%s: %s"
                   ,re->command
                   ,(q->titleshort!=NULL)?q->titleshort:q->title
                   ,(q->bodyshort!=NULL)?q->bodyshort:q->body
                   ,(q->nopts>0)?"1.":"",(q->nopts>0)?(q->optsshort!=NULL)?q->optsshort[0]:q->opts[0]:""
                   ,(q->nopts>1)?" 2.":"",(q->nopts>1)?(q->optsshort!=NULL)?q->optsshort[1]:q->opts[1]:""
                   ,(q->nopts>2)?" 3.":"",(q->nopts>2)?(q->optsshort!=NULL)?q->optsshort[2]:q->opts[2]:""
                   ,(q->nopts>3)?" 4.":"",(q->nopts>4)?(q->optsshort!=NULL)?q->optsshort[3]:q->opts[3]:""
                   ,re->commandbuf);
        } else {
                reui_fill(re->ui,0,0,re->ui->w,re->ui->fontheight,COLOR_QUERYBG);
                re->commandbuf[sizeof(re->commandbuf)-1]='\0';
                reui_printf(re->ui,0,0,COLOR_QUERYFG,"%s %s",re->command,re->commandbuf);
        }
        re->headerdirty=0;
        re->ui->rendererdirty=1;
        return(0);
}


int
re_drawcontents(re_t *re)
{
        long pos,newpos;
        char *ptr;
        int len;
        int y,row,tmprow;
        char *curptr;
        int curptrlen;
        int has_nl;
        int is_continuation;
        int tmpcol,availcol;
        int in_error;
        long realstart,realend;
        int drawn_cursor;
        hcolor_t *colors;
        int ncolors;
        linecolor_t *linecolors;
        int nlinecolors;
        int matchingpos;
        char matchingchar;
        int mline,mcol;
        long cursorpos;
        const char *hint;
        const char selcolornormal[]={"\xde\xcf\x7f\xff"};
        const char selcolorcurline[]={"\xf0\xea\xc9\xff"};
        if(re==NULL)
                return(-1);
        if(redata_linecol2pos(re->data,re->curline,re->curcol,&cursorpos,NULL)!=0)
                return(0); /* error obtaining position */
        reui_fill(re->ui,re->x,re->y,re->w,re->h,"\xdf\xdf\xdf\xff");
        row=re->curline-re->originline;
        pos=cursorpos;
        while(row>0 && pos>0) {
                if(redata_line_rawinfo(re->data,pos,&newpos,NULL,NULL,&is_continuation)==-1)
                        return(-1);
                pos=(newpos>0)?newpos-1:0;
                if(!is_continuation)
                        row--;
        }
        /* highlight current line */
        reui_fill(re->ui,re->x,re->y+(re->curline-re->originline)*re->ui->fontheight,re->w,re->ui->fontheight+1,"\xef\xef\xef\xff");
        /* highlight the selection */
        if(re->selactive) {
                const char *selcolor;
                tmprow=row;
                for(y=re->y;y<(re->y+re->h);y+=re->ui->fontheight,row++) {
                        selcolor=(row==(re->curline-re->originline))?selcolorcurline:selcolornormal;
                        if((re->originline+row)==re->sellinefrom && (re->originline+row)==re->sellineto) {
                                reui_fill(re->ui,re->x+(re->selcolfrom-re->origincol)*re->ui->fontwidth,re->y+row*re->ui->fontheight,(re->selcolto-re->selcolfrom)*re->ui->fontwidth,re->ui->fontheight+1,selcolor);
                        } else if((re->originline+row)==re->sellinefrom) {
                                int x1;
                                x1=re->x+(re->selcolfrom-re->origincol)*re->ui->fontwidth;
                                if(x1<(re->x+re->w))
                                        reui_fill(re->ui,x1,re->y+row*re->ui->fontheight,re->w-x1,re->ui->fontheight+1,selcolor);
                        } else if((re->originline+row)>re->sellinefrom && (re->originline+row)<re->sellineto) {
                                reui_fill(re->ui,re->x,re->y+row*re->ui->fontheight,re->w,re->ui->fontheight+1,selcolor);
                        } else if((re->originline+row)==re->sellineto) {
                                int x2;
                                x2=re->x+(re->selcolto-re->origincol)*re->ui->fontwidth;
                                if(x2>(re->x))
                                        reui_fill(re->ui,re->x,re->y+row*re->ui->fontheight,x2-re->x,re->ui->fontheight+1,selcolor);
                        }

                }
                row=tmprow;
        }
        /* draw the lines */
        drawn_cursor=0;
        colors=redata_highlighter_getcolors(re->data,&ncolors);
        matchingpos=-1;
        matchingchar='\0';
        for(y=re->y;y<(re->y+re->h);y+=re->ui->fontheight,row++) {
                /* definition of vars for tracking linecolor usage */
                int curlinecolor; /* current linecolor */
                int usedlenlinecolor; /* number of bytes of current linecolor already drawn */
                /* end of definitions */
                if(redata_line_realstart(re->data,pos,&realstart)==-1 || redata_line_realend(re->data,pos,&realend)==-1) {
                        break; /* couldn't get real start/end */
                }
                in_error=0;
                linecolors=(colors==NULL)?NULL:redata_highlighter_getline(re->data,re->originline+row,&nlinecolors);
                curlinecolor=0;
                usedlenlinecolor=0;
                for(tmpcol=0,pos=realstart,availcol=0;tmpcol<(re->origincol+re->maxcol) && pos<=realend;pos=newpos+len,tmpcol+=availcol) {
                        if(redata_line_rawinfo(re->data,pos,&newpos,&ptr,&len,&is_continuation)==-1) {
                                in_error=1;
                                break; /* couldn't get this line part info */
                        }
                        has_nl=((len>0 && ptr[len-1]=='\n')?1:0);
                        availcol=redata_generic_utf8len(ptr,len-has_nl);
#warning TODO: consider tabs
                        if(linecolors!=NULL) {
                                int used,usedcol; /* number of bytes/columns used of redata chunk (those are already drawn) */
                                used=usedcol=0;
                                /* while the avail text is larger than the linecolor len */
                                while(curlinecolor<nlinecolors && (len-has_nl-used)>=(linecolors[curlinecolor].len-usedlenlinecolor)) {
                                        reui_write(re->ui,re->x+(tmpcol-re->origincol+usedcol)*re->ui->fontwidth,y,colors[linecolors[curlinecolor].color].rgba,ptr+used,linecolors[curlinecolor].len-usedlenlinecolor);
                                        usedcol+=redata_generic_utf8len(ptr+used,linecolors[curlinecolor].len-usedlenlinecolor);
                                        used+=linecolors[curlinecolor].len-usedlenlinecolor;
                                        curlinecolor++;
                                        usedlenlinecolor=0;
                                }
                                /* for the last bytes of avail text, after writing them we save how many bytes we have processed of that linecolor to be able to continue later */
                                if(curlinecolor<nlinecolors && (len-has_nl-used)>0 && (linecolors[curlinecolor].len-usedlenlinecolor)>(len-has_nl-used)) {
                                        reui_write(re->ui,re->x+(tmpcol-re->origincol+usedcol)*re->ui->fontwidth,y,colors[linecolors[curlinecolor].color].rgba,ptr+used,(len-has_nl-used));
                                        usedcol+=redata_generic_utf8len(ptr+used,(len-has_nl-used));
                                        usedlenlinecolor+=(len-has_nl-used);
                                        used+=(len-has_nl-used);
                                }
                        } else {
                                reui_write(re->ui,re->x+(tmpcol-re->origincol)*re->ui->fontwidth,y,"\x00\x00\x00\xff",ptr,len-has_nl);
                        }
                        if(row==(re->curline-re->originline)) {
                                if(re->curcol>=tmpcol && re->curcol<(tmpcol+availcol)) {
                                        int utf8charlen;
                                        /* draw cursor */
                                        reui_fill(re->ui,re->x+re->ui->fontwidth*(re->curcol-re->origincol),y,re->ui->fontwidth,re->ui->fontheight+1,"\x00\x00\x00\xff");
                                        drawn_cursor=1;
                                        curptr=redata_generic_utf8col(ptr,len-has_nl,re->curcol-tmpcol);
                                        curptrlen=(curptr==NULL)?0:(len-has_nl)-(curptr-ptr);
                                        utf8charlen=redata_generic_utf8charlen(curptr,curptrlen);
                                        reui_write(re->ui,re->x+re->ui->fontwidth*(re->curcol-re->origincol),y,"\xff\xff\xff\xff",curptr,utf8charlen);
                                        /* get matching braces/parens/anglebracket/curlybraces for highlighting */
                                        if(utf8charlen==1 && strchr("[]{}<>()",*curptr)!=NULL)
                                                matchingpos=re_getmatchingbracket(re,cursorpos,*curptr,&matchingchar);
                                }
                        }
                }
                if(row==(re->curline-re->originline) && !drawn_cursor)
                        reui_fill(re->ui,re->x+re->ui->fontwidth*(re->curcol-re->origincol),y,re->ui->fontwidth,re->ui->fontheight+1,"\x00\x00\x00\xff");
                if(in_error)
                        break;
                pos=realend+1;
/*LONG LINE LEFT FOR DEBUGGING PURPOSES: reui_write(re->ui,re->x+re->ui->fontwidth*(re->curcol-re->origincol),y,"\xff\xff\xff\xff",curptr,redata_generic_utf8charlen(ptr,curptrlen));*/
        }
        /* highlight matching parens/brace/... if applicable */
        if(matchingpos!=-1 && redata_pos2linecol(re->data,matchingpos,&mline,&mcol)!=-1) {
                int x,y;
                char *fg="\xff\x00\x00\x80";
                char *bg="\xff\xff\xff\xaf";
                x=re->x+(mcol-re->origincol)*re->ui->fontwidth+(re->ui->fontwidth/2);
                x=(x<re->x)?re->x:(x>=(re->x+re->w))?(re->x+re->w-1):x;
                y=re->y+(mline-re->originline)*re->ui->fontheight+(re->ui->fontheight/2);
                y=(y<re->y)?re->y:(y>=(re->y+re->h))?(re->y+re->h-1):y;
                if(mline<re->originline)
                        reui_balloon(re->ui, 'n', x, re->y, fg, bg, &matchingchar,1);
                else if(mline>=(re->originline+re->maxrow))
                        reui_balloon(re->ui, 's', x, re->y+re->h-1, fg, bg, &matchingchar,1);
                else if(mcol<re->origincol)
                        reui_balloon(re->ui, 'w', re->x, y, fg, bg, &matchingchar,1);
                else if(mcol>=(re->origincol+re->maxcol))
                        reui_balloon(re->ui, 'e', re->x+re->w-1,y, fg, bg, &matchingchar,1);
                else
                        reui_balloon(re->ui, '\0', x,y, fg, bg, &matchingchar,1);
        }
        /* display prototypes info if applicable */
        hint=redata_prototypes_get(re->data, cursorpos);
        if(hint!=NULL) {
                if((re->curline-re->originline)>=(re->maxrow/2))
                        reui_balloon(re->ui, '\0', re->x+re->w/2, re->y+re->ui->fontheight*3/2, "\x80\x80\x80\xff", "\xff\xff\xff\xcf",(char *) hint,strlen(hint));
                else
                        reui_balloon(re->ui, '\0', re->x+re->w/2, re->y+re->h-re->ui->fontheight*3/2, "\x80\x80\x80\xff", "\xff\xff\xff\xcf",(char *)hint,strlen(hint));
        }
        /* all done */
        re->contentsdirty=0;
        re->ui->rendererdirty=1;
        return(0);
}