/*
 * 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 "re_data.h"
#include "re_ui.h"
#include "ext/socklib.h"

#define LINEFORCESCROLL 1
#define COLFORCESCROLL 5

#define COMMANDBUFSIZE 1024

#define COMMAND_WARNING "(!)"
#define COMMAND_GOTOLINE "Go to line: "

#define COLOR_STATUSBG "\x00\x00\xff\xff"
#define COLOR_STATUSFG "\xff\xff\x00\xff"

#define COLOR_QUERYBG "\xc0\xff\x00\xff"
#define COLOR_QUERYFG "\x3f\x00\xff\xff"

#define COLOR_WARNINGBG "\xff\x00\x00\xff"
#define COLOR_WARNINGFG "\xff\xff\x00\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
        long cursorpos;
        int originline,origincol;
        int curline,curcol;
        int maxrow,maxcol;
        int headerdirty;
        int contentsdirty;
        char *command;
        char commandbuf[COMMANDBUFSIZE];
        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);


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

int re_setfilename(re_t *re, char *filename);
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_rtrim(re_t *re, long curpos, int *trimmed);
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;
        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);
        }
        if((redata_load(re->data,re->filename,NULL,NULL))!=0)
                re->flag_newfile=1;
        else
                re->flag_newfile=0;
#if 0
#warning TESTS
        {
                char buf[129];
                redata_hash(re->data,buf);
                fprintf(stderr,"%s %s\n",buf,re->filename);
        }
#endif
        reui_title(re->ui,re->filename);
        flag_sigint=flag_sigpipe=0;
        setsignal(SIGINT,sighandler_sigint);
        setsignal(SIGPIPE,sighandler_sigpipe);
        do_exit=0;
        if((ssel=sselect_init())==NULL) {
                fprintf(stderr,"ERROR: filename too long.\n");
                re_free(re),re=NULL;
                return(2);
        }
        reui_scr2renderer(re->ui,0,0,re->ui->w,re->ui->h);
        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->headerdirty=1;
        re->contentsdirty=1;
        flag_had_events=0;
        SDL_StartTextInput();
        while(do_exit==0 && flag_sigint==0) {
                if(re->headerdirty) {
                        if(re->command==NULL) {
fprintf(stderr,"REDRAW Header (editing)\n");
                                re_drawheader_editing(re);
                        } else {
fprintf(stderr,"REDRAW Header (command)\n");
                                re_drawheader_command(re);
                                if(strcmp(re->command,COMMAND_WARNING)==0) {
                                        /* the warnings only get shown once, remove it */
                                        re->command=NULL;
                                        re->commandbuf[0]='\0';
                                }
                        }
                }
                if(re->contentsdirty)
                        re_drawcontents(re);
                if(re->ui->rendererdirty)
                        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:
                                        do_exit=1;
                                        break;
                                case SDL_KEYDOWN:
                                case SDL_TEXTINPUT:
                                case SDL_TEXTEDITING:
                                        if(re->command==NULL || strcmp(re->command,COMMAND_WARNING)==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(event.window.event==SDL_WINDOWEVENT_SHOWN
                                          || event.window.event==SDL_WINDOWEVENT_EXPOSED) {
                                                re->ui->rendererdirty=1;
                                        }
                                        break;
                                default:
                                        break;
                        }
                }
        }
        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;
}


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(NULL))==NULL) {
                re_free(re),re=NULL;
                return(NULL); /* insuf. mem. */
        }
        if((re->ui=reui_init())==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;
        free(re),re=NULL;
        return;
}

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_processkey_editing(re_t *re, SDL_Event *event)
{
        SDL_Event returnevent;
        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(&returnevent,0,sizeof(SDL_Event));
                event=&returnevent;
                event->type=SDL_TEXTINPUT;
                strncpy(event->text.text,"\n",sizeof(event->text.text));
                event->text.text[sizeof(event->text.text)-1]='\0';
        }
        /* special case: text editing event */
        if(event->type==SDL_TEXTINPUT) {
                int len;
                long realend;
                int at_end;
                if(re->ignorenkeys>0) {
                        re->ignorenkeys--;
                        return(0); /* this is an already processed key, ignore it */
                }
#if 1
fprintf(stderr,"SDL_TEXTINPUT:\"%s\"\n",event->text.text);
#endif
                if(redata_line_realend(re->data,re->cursorpos,&realend)==-1)
                        return(-1); /* couldn't get current line info */
                at_end=(re->cursorpos==realend)?1:0;
                if(event->text.text[0]==' ' && event->text.text[1]=='\0' && at_end) {
                        /* instead of adding a space at the end of the line, we move the cursor to the right */
                        re_moveleftright(re,1);
                        return(0); /* no need to add spaces at the end of the line */
                }
                redata_undo_groupinit(re->data,NULL);
                if(event!=&returnevent && at_end) {
                        int col;
                        if(redata_pos2linecol(re->data,re->cursorpos,NULL,&col)==-1)
                                return(-1); /* couldn't get line info */
                        if(redata_op_addn(re->data,re->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 */
                        re->cursorpos+=re->curcol-col;
                }
                len=strlen(event->text.text);
                if(redata_op_add(re->data,re->cursorpos,event->text.text,len,NULL)!=0)
                        return(-1); /* couldn't add requested text */
                re->cursorpos+=len;
                if(event==&returnevent) {
                        int trimmed;
                        if(re_rtrim(re,re->cursorpos-len,&trimmed))
                                re->cursorpos-=trimmed;
                        re->curline++;
                        re->curcol=0;
                } else {
                        re->curcol+=redata_generic_utf8len(event->text.text,len);
                }
                redata_undo_groupcommit(re->data,NULL);
                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 1
fprintf(stderr,"SDL_KEYDOWN: sym:%i\n",event->key.keysym.sym);
#endif
        if(event->key.keysym.sym==SDLK_DOWN || event->key.keysym.sym==SDLK_UP) {
                re_moveupdown(re,(event->key.keysym.sym==SDLK_UP)?-1:1);
        } else if(event->key.keysym.sym==SDLK_LEFT || event->key.keysym.sym==SDLK_RIGHT) {
                re_moveleftright(re,(event->key.keysym.sym==SDLK_LEFT)?-1: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;
                if((event->key.keysym.sym==SDLK_HOME && redata_line_realstart(re->data,re->cursorpos,&newpos)==-1)
                  || (event->key.keysym.sym==SDLK_END && redata_line_realend(re->data,re->cursorpos,&newpos)==-1)) {
                        return(-1); /* couldn't get destination pos data */
                }
                re->cursorpos=newpos;
                if(redata_pos2linecol(re->data,re->cursorpos,NULL,&(re->curcol))==-1)
                        return(-1); /* couldn't get col of current pos */;
                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)) {
                        re->origincol=re->curcol+COLFORCESCROLL-re->maxcol;
                        re->origincol=(re->origincol<0)?0:re->origincol;
                }
                re->headerdirty=1;
                re->contentsdirty=1;
        } else if(event->key.keysym.sym==SDLK_BACKSPACE && re->cursorpos>0) {
                int line,col;
                long startpos,newpos,realstart,start;
                char *ptr;
                int len;
                int trimmed;
                int doneinc;
                if(redata_pos2linecol(re->data,re->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 */
                }
                redata_undo_groupinit(re->data,NULL);
                /* delete the last character */
                if(col>0) {
                        if(redata_line_realstart(re->data,re->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=re->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,re->cursorpos-newpos,NULL);
                        re->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=re->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,re->cursorpos-delpos,NULL);
                        re->cursorpos=delpos;
                }
                redata_pos2linecol(re->data,re->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,re->cursorpos,&trimmed))
                        re->cursorpos-=trimmed;
                /* end of deletion */
                redata_undo_groupcommit(re->data,NULL);
                re->headerdirty=1;
                re->contentsdirty=1;
        } else if(event->key.keysym.sym==SDLK_q && (SDL_GetModState()&KMOD_CTRL)!=0) {
fprintf(stderr,"re_processkey(): received Control+q\n");
                re->command="";
                re->headerdirty=1;
        }
        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(event->key.keysym.sym==SDLK_l) {
                re->command=COMMAND_GOTOLINE;
                re->commandbuf[0]='\0';
                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;
        } 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_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->cursorpos=pos;
                re->curline=line;
                /* position the line in the center of viewport */
                re->originline=line-(re->maxrow/2);
                re->headerdirty=1;
                re->contentsdirty=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;
        if(re==NULL)
                return(-1); /* sanity check failed */
        if(totalinc==0)
                return(0); /* nothing to do */
        inc=(totalinc<0)?-1:1;
        newpos=re->cursorpos; /* get rid of compiler warning (will be overwitten in the loop as totalinc never is 0) */
        for(doneinc=0;doneinc!=totalinc;doneinc+=inc) {
fprintf(stderr,"MOVING from cursorpos:%li line:%i col:%i\n",re->cursorpos,re->curline,re->curcol);
                if((inc==-1 && redata_line_prevrealstart(re->data,re->cursorpos,&realstart)==-1)
                   || (inc==1 && redata_line_nextrealstart(re->data,re->cursorpos,&realstart)==-1)) {
                        break; /* couldn't get current line data, we are at start/end */
                }
                re->cursorpos=realstart;
                re->curline+=inc;
                if(re->curline<(re->originline+LINEFORCESCROLL)) {
                        re->originline-=LINEFORCESCROLL;
                        re->originline=(re->originline<0)?0:re->originline;
                }
                if(re->curline>(re->originline+re->maxrow-LINEFORCESCROLL))
                        re->originline+=LINEFORCESCROLL;
fprintf(stderr,"MOVING   to cursorpos:%li line:%i col:%i\n",re->cursorpos,re->curline,re->curcol);
        }
        if(redata_line_inccol(re->data,re->cursorpos,re->curcol,&newpos2,NULL)==-1) {
                /* error advancing cursor, "emergency" repositioning */
fprintf(stderr,"COLUMN ERROR\n");
                re->curcol=0;
                newpos2=newpos;
        }
fprintf(stderr,"COLUMN from cursorpos:%li line:%i col:%i\n",re->cursorpos,re->curline,re->curcol);
        re->cursorpos=newpos2;
fprintf(stderr,"COLUMN   to cursorpos:%li line:%i col:%i\n",re->cursorpos,re->curline,re->curcol);
        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;
        if(re==NULL)
                return(-1); /* sanity check failed */
        if(totalinc==0)
                return(0); /* nothing to do */
        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,re->cursorpos,&realstart)==-1)
                        return(-1); /* couldn't get current pos */
                re->curcol=0;
                re->cursorpos=realstart;
        } else {
                /* move a char at a time */
                if(redata_line_realstart(re->data,re->cursorpos,&realstart)==-1)
                        return(-1); /* couldn't get current pos */
                inc=(totalinc<0)?-1:1;
                doneinc=0;
                if(redata_pos2linecol(re->data,re->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,re->cursorpos,&start,&ptr,&len,NULL)==-1)
                                return(-1); /* couldn't get line data */
                        if(ptr[re->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=re->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;
                }
                re->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->maxrow-COLFORCESCROLL)) {
                        re->origincol=re->curcol+COLFORCESCROLL-re->maxrow-(re->curcol%COLFORCESCROLL);
                        re->origincol=(re->origincol<0)?0:re->origincol;
                }
        }
        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)) {
                for(n=1;(n+1)<(len-1) && start[len-1-n-1]==' ';n++)
                        ;
fprintf(stderr,"Trying to DELETE SPACES del %i bytes\n",n);
                redata_op_del(re->data,curpos-n,n,NULL);
                re->cursorpos-=n;
                if(trimmed!=NULL)
                        *trimmed=n;
        }
        return(0);
}

int
re_drawheader_editing(re_t *re)
{
        int line,col;
        if(re==NULL)
                return(-1);
        if(redata_pos2linecol(re->data,re->cursorpos,&line,&col)==-1)
                line=col=-1;
        reui_fill(re->ui,0,0,re->ui->w,re->ui->fontheight,COLOR_STATUSBG);
        /* for the user, lines start at 0 (internally they start at 0) */
        reui_printf(re->ui,0,0,COLOR_STATUSFG,"File: %s Line:%i (%i) Col:%i (%i) Pos:%li",re->filename,re->curline+1,line+1,re->curcol+1,col+1,re->cursorpos);
        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) {
                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 {
                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;
        char *curptr;
        int curptrlen;
        int has_nl;
        int is_continuation;
        int tmpcol,availcol;
        int in_error;
        long realstart,realend;
        int drawn_cursor;
        if(re==NULL)
                return(-1);
        reui_fill(re->ui,re->x,re->y,re->w,re->h,"\xdf\xdf\xdf\xff");
        row=re->curline-re->originline;
        pos=re->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");
        /* draw the lines */
        drawn_cursor=0;
        for(y=re->y;y<(re->y+re->h);y+=re->ui->fontheight,row++) {
                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;
                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
                        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)) {
                                        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);
                                        reui_write(re->ui,re->x+re->ui->fontwidth*(re->curcol-re->origincol),y,"\xff\xff\xff\xff",curptr,redata_generic_utf8charlen(ptr,curptrlen));
                                }
#warning TODO: if it is one of  '[','{','<','>','}',']', highlight the matching bracket/parens/anglebracket.
                        }
                }
                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));*/
        }
        re->contentsdirty=0;
        re->ui->rendererdirty=1;
        return(0);
}