/*
 * recenteditor.c
 *
 * A programmers editor
 *
 * Author: Dario Rodriguez antartica@whereismybit.com
 * 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 SIZEBLOCKPRINTS 32

#define VIEWONLYPROGNAME "review"

#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_QUERYBGOLD "\x83\x75\x59\xff"
#define COLOR_QUERYFGOLD "\xb1\xc5\x5e\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 print_t {
        reui_t *ui;
        redata_t *data;
        int originline;
        int origincol;
        int showonlyn;
        int dirty;
} printout_t;

typedef struct re_t {
        redata_t *data;
        reui_t *ui;
        int viewonly;
        int flag_newfile;
        int showlinenumbers;
        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 selstartactive;
        int selstartline;
        int selstartcol;
        int selactive;
        int sellinefrom,sellineto;
        int selcolfrom,selcolto;
        int mouseselactive;
        int sellinestart,selcolstart;
        long sizeselectbuf;
        long usedselectbuf;
        char *selectbuf;
        char *command;
        char commandbuf[COMMANDBUFSIZE];
        int is_oldcommandbuf;
        char cachelastsearch[COMMANDBUFSIZE];
        char cachelastreplacewith[COMMANDBUFSIZE];
        question_t *question;
        int showingwarning;
        int ignorenkeys;
        int sizeprints;
        int usedprints;
        printout_t *prints;
        redata_t *funclisting;
        int originlinefunclisting;
        int curlinefunclisting;
} 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(int viewonly);
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_textinsert(re_t *re, char *text, int sizetext);
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_clipget(re_t *re);
int re_addprint(re_t *re);
int re_delprint(re_t *re, int nprint);
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, printout_t *printout);
redata_t *re_getfunclisting(re_t *re);
int re_funclistingxy2line(re_t *re,int mx,int my);


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;
        int viewonly;
        int i,l;
        if(argc!=2 || strcmp(argv[argc-1],"--help")==0) {
                fprintf(stderr,"Syntax: %s filename\n",argv[0]);
                return(1);
        }
        viewonly=0;
        if((i=strlen(argv[0]))>=(l=strlen(VIEWONLYPROGNAME)) && strcmp(argv[0]+i-l,VIEWONLYPROGNAME)==0)
                viewonly=1;
        if((re=re_init(viewonly))==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';
                                }
                        }
                }
                /* workaround for some nvidia linux drivers, that show old data on partial updates */
                if(re->headerdirty || re->contentsdirty || re->ui->rendererdirty) {
                        re->headerdirty=1;
                        re->contentsdirty=1;
                        re->ui->rendererdirty=1;
                }
                /* end of fix */
                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) {
                        /* set associated printouts as dirty too */
                        for(i=0;i<re->sizeprints;i++) {
                                if(re->prints[i].ui!=NULL && re->prints[i].data==NULL)
                                        re->prints[i].dirty=1;
                        }
                        /* redraw contents */
                        re_drawcontents(re,NULL);
                }
                if(re->ui->rendererdirty) {
#if 0
fprintf(stderr,"RENDER\n");
#endif
                        reui_present(re->ui);
                }
                for(i=0;i<re->sizeprints;i++) {
                        if(re->prints[i].ui==NULL)
                                continue;
                        if(re->prints[i].dirty)
                                re_drawcontents(re,re->prints+i);
                        if(re->prints[i].ui->rendererdirty)
                                reui_present(re->prints[i].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) {
                        Uint32 windowID;
                        flag_had_events=10;
                        /* Process printout events */
                        if((event.type==SDL_WINDOWEVENT && (windowID=event.window.windowID)!=SDL_GetWindowID(re->ui->win))
                          || (event.type==SDL_KEYDOWN && (windowID=event.key.windowID)!=SDL_GetWindowID(re->ui->win))
                          || (event.type==SDL_TEXTEDITING && (windowID=event.edit.windowID)!=SDL_GetWindowID(re->ui->win))
                          || (event.type==SDL_TEXTINPUT && (windowID=event.text.windowID)!=SDL_GetWindowID(re->ui->win))
                          ) {
                                printout_t *printout;
                                for(i=0;i<re->sizeprints;i++) {
                                        if(re->prints[i].ui!=NULL
                                          && windowID==SDL_GetWindowID(re->prints[i].ui->win)) {
                                                break;
                                        }
                                }
                                if(i>=re->sizeprints)
                                        continue; /* unknown window */
                                printout=re->prints+i;
                                switch(event.type) {
                                        case SDL_WINDOWEVENT:
                                                if(event.window.event==SDL_WINDOWEVENT_SHOWN
                                                  || event.window.event==SDL_WINDOWEVENT_EXPOSED) {
                                                        printout->dirty=1;
                                                } else if((event.window.event==SDL_WINDOWEVENT_RESIZED
                                                  || event.window.event==SDL_WINDOWEVENT_SIZE_CHANGED)
                                                  && (event.window.data1!=printout->ui->w || event.window.data2!=printout->ui->h)) {
                                                        reui_resize(printout->ui,event.window.data1,event.window.data2);
                                                        printout->dirty=1;
                                                } else if(event.window.event==SDL_WINDOWEVENT_CLOSE) {
                                                        re_delprint(re,i);
                                                }
                                                break;
                                        case SDL_KEYDOWN:
                                                if(event.key.keysym.sym==SDLK_ESCAPE) {
                                                        re_delprint(re,i);
                                                } if(event.key.keysym.sym==SDLK_DOWN || event.key.keysym.sym==SDLK_UP || event.key.keysym.sym==SDLK_PAGEDOWN || event.key.keysym.sym==SDLK_PAGEUP) {
                                                        int neworiginline;
                                                        int maxrow;
                                                        int linetotal;
                                                        maxrow=printout->ui->h/printout->ui->fontheight-1;
                                                        neworiginline=printout->originline;
                                                        neworiginline+=(event.key.keysym.sym==SDLK_UP)?-1:
                                                                       (event.key.keysym.sym==SDLK_DOWN)?1:
                                                                       (event.key.keysym.sym==SDLK_PAGEUP)?-maxrow:
                                                                       (event.key.keysym.sym==SDLK_PAGEDOWN)?maxrow:
                                                                       0;
                                                        linetotal=redata_line_total((printout->data!=NULL)?printout->data:re->data);
                                                        neworiginline=(neworiginline<0)?0:neworiginline;
                                                        neworiginline=(neworiginline>linetotal)?linetotal:neworiginline;
                                                        if(neworiginline==printout->originline)
                                                                break; /* nothing to do, at top */
                                                        printout->originline=neworiginline;
                                                        printout->dirty=1;
                                                } else if(event.key.keysym.sym==SDLK_DOWN || event.key.keysym.sym==SDLK_UP) {
                                                        if(event.key.keysym.sym==SDLK_UP && printout->originline==0)
                                                                break; /* nothing to do, at top */
                                                        printout->originline+=((event.key.keysym.sym==SDLK_UP)?-1:1);
                                                        printout->dirty=1;
                                                } else if(event.key.keysym.sym==SDLK_LEFT || event.key.keysym.sym==SDLK_RIGHT) {
                                                        if(event.key.keysym.sym==SDLK_LEFT && printout->origincol==0)
                                                                break; /* nothing to do, at top */
                                                        printout->origincol+=((event.key.keysym.sym==SDLK_LEFT)?-1:1)*8;
                                                        printout->origincol=(printout->origincol<0)?0:printout->origincol;
                                                        printout->dirty=1;
                                                }
                                                break;
                                }
                                continue; /* only want window events from printouts */
                        }
                        /* process main window events */
                        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_KEYUP:
                                        if(event.key.keysym.sym==SDLK_RSHIFT || event.key.keysym.sym==SDLK_LSHIFT) {
                                                re->selstartactive=0;
                                        }
                                        break;
                                case SDL_KEYDOWN:
                                case SDL_TEXTINPUT:
                                case SDL_TEXTEDITING:
                                        if(re->viewonly && (event.type==SDL_TEXTINPUT || event.type==SDL_TEXTEDITING))
                                                break;
                                        if(re->viewonly && event.type==SDL_KEYDOWN) {
                                                if(event.key.keysym.sym==SDLK_ESCAPE) {
                                                        do_exit=1;
                                                } if(event.key.keysym.sym==SDLK_DOWN || event.key.keysym.sym==SDLK_UP || event.key.keysym.sym==SDLK_PAGEDOWN || event.key.keysym.sym==SDLK_PAGEUP) {
                                                        int neworiginline;
                                                        int linetotal;
                                                        neworiginline=re->originline;
                                                        neworiginline+=(event.key.keysym.sym==SDLK_UP)?-1:
                                                                       (event.key.keysym.sym==SDLK_DOWN)?1:
                                                                       (event.key.keysym.sym==SDLK_PAGEUP)?-re->maxrow:
                                                                       (event.key.keysym.sym==SDLK_PAGEDOWN)?re->maxrow:
                                                                       0;
                                                        linetotal=redata_line_total(re->data);
                                                        neworiginline=(neworiginline<0)?0:neworiginline;
                                                        neworiginline=(neworiginline>linetotal)?linetotal:neworiginline;
                                                        if(neworiginline==re->originline)
                                                                break; /* nothing to do, at top */
                                                        re->curline+=(neworiginline-re->originline);
                                                        re->originline=neworiginline;
                                                        re->headerdirty=1;
                                                        re->contentsdirty=1;
                                                } else if(event.key.keysym.sym==SDLK_LEFT || event.key.keysym.sym==SDLK_RIGHT) {
                                                        if(event.key.keysym.sym==SDLK_LEFT && re->origincol==0)
                                                                break; /* nothing to do, at top */
                                                        re->origincol+=((event.key.keysym.sym==SDLK_LEFT)?-1:1)*8;
                                                        re->origincol=(re->origincol<0)?0:re->origincol;
                                                        re->headerdirty=1;
                                                        re->contentsdirty=1;
                                                }
                                                break;
                                        }
                                        if(!re->viewonly && event.type==SDL_KEYDOWN && (event.key.keysym.sym==SDLK_RSHIFT || event.key.keysym.sym==SDLK_LSHIFT)) {
                                                re->selstartactive=1;
                                                re->selstartline=re->curline;
                                                re->selstartcol=re->curcol;
                                        }
                                        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;
                                case SDL_MOUSEBUTTONDOWN:
                                case SDL_MOUSEBUTTONUP:
                                case SDL_MOUSEMOTION:
                                        {
#warning TODO: select with mouse
                                                int mx,my;
                                                mx=(event.type!=SDL_MOUSEMOTION)?event.button.x:event.motion.x;
                                                my=(event.type!=SDL_MOUSEMOTION)?event.button.y:event.motion.y;
                                                if(re->mouseselactive==0 && event.type==SDL_MOUSEBUTTONDOWN && my<re->y && re->funclisting==NULL) {
                                                        redata_t *funclisting;
                                                        if((funclisting=re_getfunclisting(re))==NULL)
                                                                break; /* mem. insuf. */
                                                        if(re->funclisting!=NULL)
                                                                redata_free(re->funclisting),re->funclisting=NULL;
                                                        re->funclisting=funclisting;
                                                        re->originlinefunclisting=0;
                                                        re->curlinefunclisting=-1;
                                                        re->contentsdirty=1;
                                                } else if(event.type==SDL_MOUSEBUTTONUP && re->funclisting!=NULL) {
                                                        char linebuf[32],*cmdend;
                                                        if(redata_line_getstartstrtrimmed(re->funclisting,re->curlinefunclisting,linebuf,sizeof(linebuf)," ")==0) {
                                                                int oldline;
                                                                if((cmdend=strchr(linebuf,':'))!=NULL)
                                                                        *cmdend='\0';
                                                                re->command=COMMAND_GOTOLINE;
                                                                strncpy(re->commandbuf,linebuf,sizeof(re->commandbuf));
                                                                re->commandbuf[sizeof(re->commandbuf)-1]='\0';
                                                                oldline=re->curline;
                                                                re_processcommand(re);
                                                                if(oldline!=re->curline) {
                                                                        /* position the cursor near the top of the window */
                                                                        re->originline=re->curline-3;
                                                                        re->originline=(re->originline<0)?0:re->originline;
                                                                }
                                                        }
                                                        redata_free(re->funclisting),re->funclisting=NULL;
                                                        re->contentsdirty=1;
                                                } else if(event.type==SDL_MOUSEMOTION && re->funclisting!=NULL) {
                                                        if(mx<re->ui->fontwidth*10) {
                                                                /* scroll */
                                                                int total;
                                                                total=redata_line_total(re->funclisting);
                                                                if(my<re->y) {
                                                                        if(re->originlinefunclisting!=0) {
                                                                                re->originlinefunclisting=0;
                                                                                re->contentsdirty=1;
                                                                        }
                                                                } else if(my>re->y && re->h>re->y) {
                                                                        int old;
                                                                        old=re->originlinefunclisting;
                                                                        re->originlinefunclisting=total*(my-re->y)/(re->h-re->y);
                                                                        if(old!=re->originlinefunclisting)
                                                                                re->contentsdirty=1;
                                                                }
                                                        } else {
                                                                /* select */
                                                                int oldlinefunclisting;
                                                                oldlinefunclisting=re->curlinefunclisting;
                                                                re->curlinefunclisting=re_funclistingxy2line(re,mx,my);
                                                                if(oldlinefunclisting!=re->curlinefunclisting)
                                                                        re->contentsdirty=1;
                                                        }
                                                } else if(!(event.type==SDL_MOUSEMOTION && (re->mouseselactive==0 || my<re->y || mx<re->x))) {
                                                        int newposx,newposy;
                                                        long tmppos;
                                                        if(event.type==SDL_MOUSEBUTTONDOWN && event.button.button==SDL_BUTTON_LEFT) {
                                                                re->mouseselactive=1;
                                                        } else if( event.type==SDL_MOUSEBUTTONUP && event.button.button==SDL_BUTTON_LEFT) {
                                                                re->mouseselactive=0;
                                                        }
                                                        newposx=(mx-re->x)/re->ui->fontwidth;
                                                        newposy=(my-re->y)/re->ui->fontheight;
                                                        if(redata_linecol2pos(re->data, re->originline+newposy, re->origincol+newposx,&tmppos,NULL)==0) {
                                                                re->curline=re->originline+newposy;
                                                                re->curcol=re->origincol+newposx;
                                                                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(int viewonly)
{
        re_t *re;
        if((re=malloc(sizeof(re_t)))==NULL)
                return(NULL); /* insuf. mem. */
        memset(re,0,sizeof(re_t));
        if(viewonly==0) {
                re->data=redata_init(
                  redata_unsaved_register,
                  redata_highlighter_register,
                  redata_prototypes_register,
                  NULL);
        } else { /* viewonly */
               re->data=redata_init(
                  redata_highlighter_register,
                  NULL);
        }
        if(re->data==NULL) {
                re_free(re),re=NULL;
                return(NULL); /* insuf. mem. */
        }
        re->fontheightpercent=100;
        if((re->ui=reui_init(DEFAULTFONTHEIGHT*re->fontheightpercent/100,NULL))==NULL) {
                re_free(re),re=NULL;
                return(NULL); /* video init error */
        }
        re->viewonly=(viewonly!=0)?1:0;
        return(re);
}

void
re_free(re_t *re)
{
        int i;
        if(re==NULL)
                return; /* all done */
        if(re->funclisting!=NULL)
                redata_free(re->funclisting),re->funclisting=NULL;
        for(i=0;i<re->sizeprints;i++) {
                if(re->prints[i].ui!=NULL)
                        re_delprint(re,i);
        }
        if(re->prints!=NULL)
                free(re->prints),re->prints=NULL,re->sizeprints=re->usedprints=0;
        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_textinsert(re_t *re, char *text, int sizetext)
{
        long realend;
        int at_end;
        int nspaces;
        long cursorpos;
        long selstart,selend;
        int fixsel;
        char *ptr,*next;
        int len;
        int trimmed;
        if(re==NULL || text==NULL || sizetext<=0)
                return(-1); /* sanity check failed */
        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 */
        }
        fixsel=0;
        selstart=selend=0;
        at_end=(cursorpos==realend)?1:0;
        for(nspaces=0;nspaces<sizetext && text[nspaces]==' ';nspaces++)
                ;
        if(nspaces>0 && nspaces==sizetext && 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(!(text[0]=='\n' && sizetext==1) && 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;
        }
        if(re->selactive
          && redata_linecol2pos(re->data,re->sellinefrom,re->selcolfrom,&selstart,NULL)==0
          && redata_linecol2pos(re->data,re->sellineto,re->selcolto,&selend,NULL)==0) {
                fixsel=1;
        }
        if(redata_op_add(re->data,cursorpos,text,sizetext,NULL)!=0)
                return(-1); /* couldn't add requested text */
        /* fix selection */
        if(fixsel) {
                if(cursorpos<=selstart)
                        redata_pos2linecol(re->data,selstart+sizetext,&(re->sellinefrom),&(re->selcolfrom));
                if(cursorpos<=selend)
                        redata_pos2linecol(re->data,selend+sizetext,&(re->sellineto),&(re->selcolto));
        }
        /* fix printouts scope */
        if(re->usedprints>0) {
                int i;
                int numnl;
                printout_t *printout;
                for(numnl=0,ptr=strchr(text,'\n');ptr!=NULL;ptr=strchr(ptr+1,'\n'))
                        numnl++;
                for(i=0;i<re->usedprints;i++) {
                        printout=re->prints+i;
                        if(printout->data!=NULL)
                                continue; /* has its own copy of the data */
                        if(printout->originline>re->curline) {
                                printout->originline+=numnl;
                        } else if(printout->showonlyn>0
                            && re->curline>=printout->originline
                            && re->curline<(printout->originline+printout->showonlyn)) {
                                printout->showonlyn+=numnl;
                        }
                }
        }
        /* fix cursor pos */
        for(ptr=text,next=memchr(text,'\n',sizetext);ptr!=NULL;ptr=(next!=NULL)?next+1:NULL,next=(ptr!=NULL)?memchr(ptr,'\n',sizetext-(ptr-text)):NULL) {
                len=(next!=NULL)?(next-ptr):sizetext-(ptr-text);
                re->curcol+=redata_generic_utf8len(ptr,len);
                cursorpos+=len;
                if(next!=NULL) {
                        cursorpos++;
                        if(re_rtrim(re,cursorpos-1,&trimmed)!=-1)
                                cursorpos-=trimmed;
                        re->curline++;
                        re->curcol=0;
                }
        }
        /* trim last line inserted */
        if(redata_getchar(re->data,cursorpos)=='\n' && re_rtrim(re,cursorpos,&trimmed)!=-1)
                cursorpos-=trimmed;
        redata_undo_groupcommit(re->data,NULL);
        re_fixorigin(re);
        re->headerdirty=1;
        re->contentsdirty=1;
        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;
                /* If control is pressed, insert a real tab, otherwise, insert spaces until next tabstop */

                if((SDL_GetModState()&KMOD_CTRL)!=0) {
                        strncpy(event->text.text,"\t",sizeof(event->text.text));
                        event->text.text[sizeof(event->text.text)-1]='\0';
                } else {
                        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) {
                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
                re_textinsert(re,event->text.text,strlen(event->text.text));
        }
#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 move_res;
                int oldcol=re->curcol,oldline=re->curline;
                if((SDL_GetModState()&KMOD_CTRL)!=0 && event->key.keysym.sym==SDLK_UP && re->originline==0)
                        return(0); /* nothing to do, already at top */
                move_res=re_moveupdown(re,(event->key.keysym.sym==SDLK_UP)?-1:1);
                if(move_res==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);
                }
                if(move_res==0 && (SDL_GetModState()&KMOD_CTRL)!=0) {
                        if(event->key.keysym.sym==SDLK_UP && re->originline>0) {
                                re->originline--;
                                re->contentsdirty=1;
                        } else if(event->key.keysym.sym==SDLK_DOWN && re->originline<re->curline) {
                                re->originline++;
                                re->contentsdirty=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;
                int oldcol=re->curcol,oldline=re->curline;
                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);
                if((SDL_GetModState()&KMOD_SHIFT)!=0 && (re->curcol!=oldcol || re->curline!=oldline))
                        re_sel_resize(re,oldcol,oldline,(event->key.keysym.sym==SDLK_PAGEUP)?-1:1);
                re->headerdirty=1;
                re->contentsdirty=1;
        } else if(event->key.keysym.sym==SDLK_PAGEDOWN || event->key.keysym.sym==SDLK_PAGEUP) {
                int oldcol=re->curcol,oldline=re->curline;
                re_moveupdown(re,(event->key.keysym.sym==SDLK_PAGEUP)?-(re->maxrow):re->maxrow);
                if((SDL_GetModState()&KMOD_SHIFT)!=0 && (re->curcol!=oldcol || re->curline!=oldline))
                        re_sel_resize(re,oldcol,oldline,(event->key.keysym.sym==SDLK_PAGEUP)?-1:1);
        } else if(event->key.keysym.sym==SDLK_HOME || event->key.keysym.sym==SDLK_END) {
                long newpos;
                long cursorpos;
                int oldcol=re->curcol,oldline=re->curline;
                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 */;
                if((SDL_GetModState()&KMOD_SHIFT)!=0 && (re->curcol!=oldcol || re->curline!=oldline))
                        re_sel_resize(re,oldcol,oldline,(event->key.keysym.sym==SDLK_HOME)?-1:1);
                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,delpos,realstart,start;
                long dellen;
                char *ptr;
                int len;
                int trimmed;
                int doneinc;
                long cursorpos;
                long selstart,selend;
                int fixsel;
                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 */
                fixsel=0;
                selstart=selend=0;
                if(re->selactive
                  && redata_linecol2pos(re->data,re->sellinefrom,re->selcolfrom,&selstart,NULL)==0
                  && redata_linecol2pos(re->data,re->sellineto,re->selcolto,&selend,NULL)==0) {
                        fixsel=1;
                }           
                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,delpos=cursorpos;doneinc!=1;) {
                                if((delpos-1)<realstart)
                                        break;
                                delpos--;
                                if(ptr==NULL || (start+len)<=delpos || delpos<start) {
                                        if(redata_line_rawinfo(re->data,delpos,&start,&ptr,&len,NULL)==-1)
                                                return(-1); /* couldn't get line data */
                                }
                                if(redata_generic_utf8isstartbyte(ptr[delpos-start]))
                                        doneinc++;
                        }
                        /* delete */
                        dellen=cursorpos-delpos;
                        redata_op_del(re->data,delpos,dellen,NULL);
                        cursorpos=delpos;
                } else {
                        /* 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;
                        }
                        dellen=cursorpos-delpos;
                        redata_op_del(re->data,delpos,dellen,NULL);
                        cursorpos=delpos;
                        /* fix printouts scope */
                        if(re->usedprints>0) {
                                int i;
                                printout_t *printout;
                                for(i=0;i<re->usedprints;i++) {
                                        printout=re->prints+i;
                                        if(printout->data!=NULL)
                                                continue; /* has its own copy of the data */
                                        if(printout->originline>re->curline) {
                                                printout->originline--;
                                        } else if(printout->showonlyn>1
                                            && re->curline>=printout->originline
                                            && re->curline<(printout->originline+printout->showonlyn)) {
                                                printout->showonlyn--;
                                        }
                                }
                        }
                }
                redata_pos2linecol(re->data,cursorpos,&(re->curline),&(re->curcol));
                if(fixsel) {
                        if(delpos<=selstart) {
                                /* if we deleted the start of selection, make the start of selection the cursorpos */
                                if(selstart>=delpos && selstart<(delpos+dellen))
                                        redata_pos2linecol(re->data,cursorpos,&(re->sellinefrom),&(re->selcolfrom));
                                else
                                        redata_pos2linecol(re->data,selstart-dellen,&(re->sellinefrom),&(re->selcolfrom));
                        }
                        if(delpos<=selend) {
                                /* if we deleted the end of selection, make the start of selection the cursorpos */
                                if(selend>=delpos && selend<(delpos+dellen))
                                        redata_pos2linecol(re->data,cursorpos,&(re->sellineto),&(re->selcolto));
                                else
                                        redata_pos2linecol(re->data,selend-dellen,&(re->sellineto),&(re->selcolto));
                        }
                }
                /* 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) {
                                int trimmed;
                                redata_undo_groupinit(re->data,NULL);
                                redata_op_del(re->data,frompos,topos-frompos,NULL);
                                redata_pos2linecol(re->data,frompos,&(re->curline),&(re->curcol));
                                if(redata_getchar(re->data,frompos)=='\n' && re_rtrim(re,frompos,&trimmed)!=-1)
                                        frompos-=trimmed;
                                redata_undo_groupcommit(re->data,NULL);
                                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()) {
                        char *ptr;
                        re_selectbuf_replace(re,(ptr=SDL_GetClipboardText()));
                        SDL_free(ptr),ptr=NULL;
                }
                if(SDL_HasClipboardText()) {
                        char *ptr;
                        re_selectbuf_replace(re,(ptr=SDL_GetClipboardText()));
                        SDL_free(ptr),ptr=NULL;
                }
                if(re->usedselectbuf>0)
                        re_textinsert(re, re->selectbuf,re->usedselectbuf);
                re->ignorenkeys++;
        } else if(/*re->selactive &&*/ event->key.keysym.sym==SDLK_p && (SDL_GetModState()&KMOD_CTRL)!=0) {
                re_addprint(re);
        } else if(event->key.keysym.sym==SDLK_l && (SDL_GetModState()&KMOD_CTRL)!=0) {
                re->showlinenumbers=1-re->showlinenumbers;
                re->contentsdirty=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(re->command_first_key=='q' && event->key.keysym.sym==SDLK_l) {
                re->command=COMMAND_GOTOLINE;
                re->commandbuf[0]='\0';
                re->is_oldcommandbuf=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->is_oldcommandbuf=1;
                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->is_oldcommandbuf=1;
                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->is_oldcommandbuf=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)
{
        SDL_Event fakeevent;
        if(re==NULL || event==NULL)
                return(-1); /* sanity check failed */
        /* special case: text editing event */
        if(event->type==SDL_KEYDOWN && event->key.keysym.sym==SDLK_TAB && (SDL_GetModState()&KMOD_CTRL)!=0) {
                /* If control+tab is pressed, insert a real tab (to be able to search for them using Control+F) */
                memset(&fakeevent,0,sizeof(SDL_Event));
                event=&fakeevent;
                event->type=SDL_TEXTINPUT;
                strncpy(event->text.text,"\t",sizeof(event->text.text));
                event->text.text[sizeof(event->text.text)-1]='\0';
        }
        if(event->type==SDL_TEXTINPUT) {
                int len;
                if(re->ignorenkeys>0) {
                        re->ignorenkeys--;
                        return(0); /* this is an already processed key, ignore it */
                }
                if(re->is_oldcommandbuf) {
                        re->commandbuf[0]='\0';
                        re->is_oldcommandbuf=0;
                }
                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(re->is_oldcommandbuf) {
                        re->commandbuf[0]='\0';
                        re->is_oldcommandbuf=0;
                        re->headerdirty=1;
                        return(0);
                }
                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_RIGHT || event->key.keysym.sym==SDLK_END) {
                if(re->is_oldcommandbuf) {
                        re->is_oldcommandbuf=0;
                        re->headerdirty=1;
                        return(0);
                }
        } 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;
                if(re->commandbuf[0]=='+')
                        line=re->curline+atoi(re->commandbuf+1);
                else if(re->commandbuf[0]=='-')
                        line=re->curline-atoi(re->commandbuf+1);
                else
                        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->is_oldcommandbuf=1;
                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->is_oldcommandbuf=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);
                        endpos=endpos-slen+rlen;
                }
                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(re->selstartactive==0) {
                /* we don't know the start position, manage with the nifo we have */
                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);
                        }
                }
        } else {
                /* we know the start position, try to select accordingly */
                if(re->curline<re->selstartline || (re->curline==re->selstartline && re->curcol<=re->selstartcol)) {
                        re_sel_setstart(re,re->curline,re->curcol);
                        re_sel_setend(re,re->selstartline,re->selstartcol);
                } else {
                        re_sel_setstart(re,re->selstartline,re->selstartcol);
                        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)
{
        if(re==NULL || size<0 || nadditionalspaces<0 || (frompos+size)>redata_getused(re->data))
                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. */
        }
        if(redata_getdata(re->data,frompos,size,re->selectbuf)!=0) {
                re->usedselectbuf=0;
                return(-1); /* internal error */
        }
        re->usedselectbuf=size;
        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_clipget(re_t *re)
{
#warning TODO
        return(-1);
}


int
re_addprint(re_t *re)
{
        int i;
        long frompos,topos;
        int coldone;
        if(re==NULL)
                return(-1);
        /* ensure space for the new printout */
        if(re->usedprints==re->sizeprints) {
                printout_t *newprints;
                if((newprints=realloc(re->prints,sizeof(printout_t)*(re->sizeprints+SIZEBLOCKPRINTS)))==NULL)
                        return(-1); /* insuf. mem. */
                re->prints=newprints;
                memset(re->prints+re->sizeprints,0,sizeof(printout_t)*SIZEBLOCKPRINTS);
                re->sizeprints+=SIZEBLOCKPRINTS;
        }
        for(i=0;i<re->sizeprints;i++) {
                if(re->prints[i].ui==NULL)
                        break;
        }
        if(i>=re->sizeprints)
                return(-1); /* INTERNAL ERROR */
        /* setup window */
        if((re->prints[i].ui=reui_init(DEFAULTFONTHEIGHT*re->fontheightpercent/100,re->ui))==NULL)
                return(-1); /* couldn't init window */
#if 0
/* option A: printouts are immutable */
        if((re->prints[i].data=redata_init(redata_highlighter_register,NULL))==NULL) {
                reui_free(re->prints[i].ui),re->prints[i].ui=NULL;
                return(-1); /* couldn't init data store */
        }
        re->usedprints++;
        /* copy contents (and set clipboard contents and unselect)*/
        if(re->selactive) {
                if(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_op_add(re->prints[i].data,0,re->selectbuf,strlen(re->selectbuf),NULL);
                        SDL_SetClipboardText(re->selectbuf);
                        re_sel_toggle(re);
                        re->contentsdirty=1;
                }
        } else {
                re_selectbuf_fill(re,0,redata_getused(re->data),0);
                redata_op_add(re->prints[i].data,0,re->selectbuf,strlen(re->selectbuf),NULL);
                SDL_SetClipboardText(re->selectbuf);
                re->contentsdirty=1;
        }
#else
/* option B: printouts are a window into a fixed place of the file */
        re->prints[i].data=NULL;
        re->prints[i].originline=(re->selactive)?re->sellinefrom:0;
        re->prints[i].origincol=0;
        re->prints[i].showonlyn=0 /* (re->selactive)?re->sellineto-re->sellinefrom+1:0 */ ;
        re->usedprints++;
#endif
        re->prints[i].dirty=1;
        return(0);
}

int
re_delprint(re_t *re, int nprint)
{
        if(re==NULL || nprint<0 || nprint>re->sizeprints || re->prints[nprint].ui==NULL)
                return(-1);
        reui_free(re->prints[nprint].ui),re->prints[nprint].ui=NULL;
        if(re->prints[nprint].data!=NULL)
                redata_free(re->prints[nprint].data),re->prints[nprint].data=NULL;
        re->usedprints--;
        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 {
                int commandlen;
                int commandbuflen;
                reui_fill(re->ui,0,0,re->ui->w,re->ui->fontheight,COLOR_QUERYBG);
                re->commandbuf[sizeof(re->commandbuf)-1]='\0';
                commandlen=redata_generic_utf8len(re->command,strlen(re->command));
                reui_printf(re->ui,0,0,COLOR_QUERYFG,"%s",re->command);
                commandbuflen=redata_generic_utf8len(re->commandbuf,strlen(re->commandbuf));
                if(!(re->is_oldcommandbuf) || re->commandbuf[0]=='\0') {
                        reui_printf(re->ui,re->ui->fontwidth*(commandlen+1),0,COLOR_QUERYFG,"%s",re->commandbuf);
                        /* draw something that to indicate the end of the commandbuf (useful if commandbuf ends in spaces) */
                        reui_fill(re->ui,re->ui->fontwidth*(commandlen+1+commandbuflen)+1,re->ui->fontheight/2,1,re->ui->fontheight/2,COLOR_QUERYFG);
                } else {
                        reui_fillrounded(re->ui,re->ui->fontwidth*(commandlen+1)-re->ui->fontwidth/2,0,re->ui->fontwidth*(commandbuflen+1)+1,re->ui->fontheight,COLOR_QUERYBGOLD);
                        reui_printf(re->ui,re->ui->fontwidth*(commandlen+1),0,COLOR_QUERYFGOLD,"%s",re->commandbuf);
                }
        }
        re->headerdirty=0;
        re->ui->rendererdirty=1;
        return(0);
}


int
re_drawcontents(re_t *re, printout_t *printout)
{
        reui_t *ui;
        redata_t *data;
        int x0,y0,w,h;
        int originline,origincol;
        int curline,curcol;
        int maxcol,maxrow;
        long pos,newpos;
        int y,row,tmprow;
        long cursorpos;
        int is_continuation;
        printout_t fakeprintout;
        hcolor_t *colors,fakecolor;
        int ncolors;
        const char selcolornormal[]={"\xde\xcf\x7f\xff"};
        const char selcolorcurline[]={"\xf0\xea\xc9\xff"};
        long realstart,realend;
        linecolor_t *linecolors,fakelinecolors;
        int nlinecolors;
        int drawn_cursor;
        int matchingpos;
        char matchingchar;
        int mline,mcol;
        int showonlyn;
        int flaglineno;
        int linenowidth;
        int linenosize;
        if(re==NULL || (printout!=NULL && printout->ui==NULL))
                return(-1);
        /* init vars and clear screen */
        ui=re->ui;
        data=re->data;
        x0=re->x;
        y0=re->y;
        w=re->w;
        h=re->h;
        originline=re->originline;
        origincol=re->origincol;
        curline=re->curline;
        curcol=re->curcol;
        maxcol=re->maxcol;
        maxrow=re->maxrow;
        showonlyn=0;
        flaglineno=(printout==NULL)?re->showlinenumbers:0;
        linenosize=linenowidth=0;
        if(printout==NULL && re->viewonly==1) {
                memset(&fakeprintout,0,sizeof(printout_t));
                fakeprintout.ui=re->ui;
                fakeprintout.data=re->data;
                fakeprintout.originline=re->originline;
                fakeprintout.origincol=re->origincol;
                printout=&fakeprintout;
        } else if(printout!=NULL) {
                ui=printout->ui;
                data=(printout->data==NULL)?re->data:printout->data;
                x0=0;
                y0=0;
                w=ui->w;
                h=ui->h;
                originline=curline=printout->originline;
                origincol=curcol=printout->origincol;
                maxcol=w/ui->fontwidth-1;
                maxrow=h/ui->fontheight-1;
                showonlyn=printout->showonlyn;
                flaglineno=1;
        }
        if(flaglineno) {
                linenosize=6;
                linenowidth=linenosize*ui->fontwidth+ui->fontwidth*3/2;
                x0+=linenowidth;
        }
        if(redata_linecol2pos(data,curline,curcol,&cursorpos,NULL)!=0)
                return(0); /* error obtaining position */
        reui_fill(ui,x0-linenowidth,y0,w,h,"\xdf\xdf\xdf\xff");
        row=curline-originline;
        pos=cursorpos;
        /* get position/row/col of character at top left of screen */
        while(row>0 && pos>0) {
                if(redata_line_rawinfo(data,pos,&newpos,NULL,NULL,&is_continuation)==-1)
                        return(-1);
                pos=(newpos>0)?newpos-1:0;
                if(!is_continuation)
                        row--;
        }
        /* highlight current line (in printouts, highlight alternating lines) */
        if(printout==NULL) {
                reui_fill(ui,x0-linenowidth,y0+(curline-originline)*ui->fontheight,w,ui->fontheight+1,"\xef\xef\xef\xff");
        } else {
                for(y=y0+((printout->data==NULL)?1:(printout->originline%2))*ui->fontheight;y<(y0+h);y+=ui->fontheight*2)
                        reui_fill(ui,x0-linenowidth,y,w,ui->fontheight+1,"\xef\xef\xef\xff");
        }
        /* highlight the selection */
        if(printout==NULL && re->selactive) {
                const char *selcolor;
                tmprow=row;
                for(y=y0;y<(y0+h);y+=ui->fontheight,row++) {
                        selcolor=(row==(curline-originline))?selcolorcurline:selcolornormal;
                        if((originline+row)==re->sellinefrom && (originline+row)==re->sellineto) {
                                reui_fill(ui,x0+(re->selcolfrom-origincol)*ui->fontwidth,y0+row*ui->fontheight,(re->selcolto-re->selcolfrom)*ui->fontwidth,ui->fontheight+1,selcolor);
                        } else if((originline+row)==re->sellinefrom) {
                                int x1;
                                x1=x0+(re->selcolfrom-origincol)*ui->fontwidth;
                                if(x1<(x0+w))
                                        reui_fill(ui,x1,y0+row*ui->fontheight,w-x1,ui->fontheight+1,selcolor);
                        } else if((originline+row)>re->sellinefrom && (originline+row)<re->sellineto) {
                                reui_fill(ui,x0,y0+row*ui->fontheight,w,ui->fontheight+1,selcolor);
                        } else if((originline+row)==re->sellineto) {
                                int x2;
                                x2=x0+(re->selcolto-origincol)*ui->fontwidth;
                                if(x2>(x0))
                                        reui_fill(ui,x0,y0+row*ui->fontheight,x2-x0,ui->fontheight+1,selcolor);
                        }
                }
                row=tmprow;
        }
        /* draw the lines */
        if((colors=redata_highlighter_getcolors(data,&ncolors))==NULL) {
                colors=&fakecolor;
                ncolors=1;
                memcpy(fakecolor.rgba,"\x00\x00\x00\xff",5);
        }
        drawn_cursor=0;
        matchingpos=-1;
        matchingchar='\0';
        for(y=y0;y<(y0+h);y+=ui->fontheight,row++) {
                int in_error;
                int availcol,tmpcol,len;
                char *lastcolor;
                /* definition of vars for tracking linecolor usage */
                int curlinecolor; /* current linecolor */
                int usedlenlinecolor; /* number of bytes of current linecolor already drawn */
                if(showonlyn>0 && ui->fontheight>0 && (showonlyn)<((y-y0)/ui->fontheight))
                        break; /* this printout is configured to only show up to here */
                /* get start/end of line */
                if(redata_line_realstart(data,pos,&realstart)==-1 || redata_line_realend(data,pos,&realend)==-1)
                        break; /* couldn't get real start/end */
                /* setup colors */
                if(colors==(&fakecolor) || (linecolors=redata_highlighter_getline(data,originline+row,&nlinecolors))==NULL) {
                        linecolors=&fakelinecolors;
                        fakelinecolors.len=1;
                        fakelinecolors.color=0;
                }
                curlinecolor=0;
                usedlenlinecolor=0;
                lastcolor="\x00\x00\x00\xff";                      
                in_error=0;
                if(flaglineno) {
                        int i,n;
                        char buf[2]={0};
                        for(i=linenosize,n=originline+row+1;i>=0;i--,n/=10) {
                                buf[0]=(n>0 || i==linenosize)?(n%10)+'0':' ';
                                reui_write(ui,x0-linenowidth+(i-1)*ui->fontwidth,y,"\x00\x00\x00\x2f",buf,1);
                        }
                }
                /* draw each part of this line */
                for(tmpcol=0,pos=realstart,availcol=0;tmpcol<(origincol+maxcol) && pos<=realend;pos=newpos+len,tmpcol+=availcol) {
                        int has_nl;
                        char *ptr,*aux;
                        int incompletestart,incompleteend,endreq;
                        int used,usedcol; /* number of bytes/columns used of redata chunk (those are already drawn) */
                        if(redata_line_rawinfo(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_utf8lenincomplete(ptr,len-has_nl,&incompletestart,&incompleteend,&endreq);
                        /* display the line */
                        used=incompletestart;
                        usedcol=0;
                        /* while the avail text is larger than the linecolor len */
                        while(curlinecolor<nlinecolors && (len-has_nl-incompleteend-used)>=(linecolors[curlinecolor].len-usedlenlinecolor)) {
                                lastcolor=colors[linecolors[curlinecolor].color].rgba;
                                while((aux=memchr(ptr+used,'\t',linecolors[curlinecolor].len-usedlenlinecolor))!=NULL) {
                                        /* there is a tab, we want it highlighted with red bg */
                                        int thislen=aux-(ptr+used);
                                        reui_write(ui,x0+(tmpcol-origincol+usedcol)*ui->fontwidth,y,lastcolor,ptr+used,thislen);
                                        usedcol+=redata_generic_utf8len(ptr+used,thislen);
                                        used+=thislen;
                                        usedlenlinecolor+=thislen;
                                        reui_fill(ui,x0+(tmpcol-origincol+usedcol)*ui->fontwidth,y,ui->fontwidth,ui->fontheight+1,"\xba\x07\x07\xff");
                                        usedcol++;
                                        used++;
                                        usedlenlinecolor++;
                                }
                                reui_write(ui,x0+(tmpcol-origincol+usedcol)*ui->fontwidth,y,lastcolor,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((len-has_nl-incompleteend-used)>0) {
                                if(curlinecolor<nlinecolors && (linecolors[curlinecolor].len-usedlenlinecolor)>(len-has_nl-incompleteend-used)) {
                                        lastcolor=colors[linecolors[curlinecolor].color].rgba;
                                        usedlenlinecolor+=(len-has_nl-incompleteend-used);
                                        if(usedlenlinecolor==linecolors[curlinecolor].len) {
                                                curlinecolor++;
                                                usedlenlinecolor=0;
                                        }
                                }
                                reui_write(ui,x0+(tmpcol-origincol+usedcol)*ui->fontwidth,y,lastcolor,ptr+used,(len-has_nl-incompleteend-used));
                                usedcol+=redata_generic_utf8len(ptr+used,(len-has_nl-incompleteend-used));
                                used+=(len-has_nl-incompleteend-used);
                        }
                        /* if the last utf-8 char is broken into several blocks */
                        if(incompleteend>0) {
                                char broken[7];
                                int usedbroken;
                                usedbroken=0;
                                redata_getutf8char(re->data,newpos+len-has_nl-incompleteend,broken,sizeof(broken),&usedbroken);
                                /* write the just-recomposer character in the correct color */
                                if(curlinecolor<nlinecolors && (linecolors[curlinecolor].len-usedlenlinecolor)>=1) {
                                        lastcolor=colors[linecolors[curlinecolor].color].rgba;
                                        usedlenlinecolor+=1;
                                        if(usedlenlinecolor==linecolors[curlinecolor].len) {
                                                curlinecolor++;
                                                usedlenlinecolor=0;
                                        }
                                }
                                reui_write(ui,x0+(tmpcol-origincol+usedcol)*ui->fontwidth,y,lastcolor,broken,usedbroken);
                                usedcol++;
                                availcol++;
                        }
                        /* draw cursor if applicable */
                        if(printout==NULL && row==(curline-originline) && curcol>=tmpcol && curcol<(tmpcol+availcol)) {
                                char cursorchar[7];
                                int usedcursorchar;
                                /* draw cursor */
                                reui_fill(ui,x0+ui->fontwidth*(curcol-origincol),y,ui->fontwidth,ui->fontheight+1,"\x00\x00\x00\xff");
                                drawn_cursor=1;
                                usedcursorchar=0;
                                redata_getutf8char(re->data,cursorpos,cursorchar,sizeof(cursorchar),&usedcursorchar);
                                /* tab chars are drawn as an almost filled block, other chars are written as-is */
                                if(usedcursorchar==1 && *cursorchar=='\t')
                                        reui_fill(ui,x0+ui->fontwidth*(curcol-origincol)+1,y+1,ui->fontwidth-2,ui->fontheight+1-2,"\xff\xff\xff\xff");
                                else
                                        reui_write(ui,x0+ui->fontwidth*(curcol-origincol),y,"\xff\xff\xff\xff",cursorchar,usedcursorchar);
                                /* get matching braces/parens/anglebracket/curlybraces for highlighting */
                                if(usedcursorchar==1 && strchr("[]{}<>()",*cursorchar)!=NULL)
                                        matchingpos=re_getmatchingbracket(re,cursorpos,*cursorchar,&matchingchar);
                        }
                }
                if(printout==NULL && row==(curline-originline) && !drawn_cursor)
                        reui_fill(ui,x0+ui->fontwidth*(curcol-origincol),y,ui->fontwidth,ui->fontheight+1,"\x00\x00\x00\xff");
                if(in_error)
                        break;
                pos=realend+1;
        }
        /* highlight matching parens/brace/... if applicable */
        if(printout==NULL && matchingpos!=-1 && redata_pos2linecol(data,matchingpos,&mline,&mcol)!=-1) {
                int x,y;
                char *fg="\xff\x00\x00\x80";
                char *bg="\xff\xff\xff\xaf";
                x=x0+(mcol-origincol)*ui->fontwidth+(ui->fontwidth/2);
                x=(x<x0)?x0:(x>=(x0+w))?(x0+w-1):x;
                y=y0+(mline-originline)*ui->fontheight+(ui->fontheight/2);
                y=(y<y0)?y0:(y>=(y0+h))?(y0+h-1):y;
                if(mline<originline)
                        reui_balloon(ui, 'n', x, y0, fg, bg, &matchingchar,1);
                else if(mline>=(originline+maxrow))
                        reui_balloon(ui, 's', x, y0+h-1, fg, bg, &matchingchar,1);
                else if(mcol<origincol)
                        reui_balloon(ui, 'w', x0, y, fg, bg, &matchingchar,1);
                else if(mcol>=(origincol+maxcol))
                        reui_balloon(ui, 'e', x0+w-1,y, fg, bg, &matchingchar,1);
                else
                        reui_balloon(ui, '\0', x,y, fg, bg, &matchingchar,1);
        }
        if(printout==NULL) {
                const char *hint;
                hint=redata_prototypes_get(data, cursorpos);
                if(hint!=NULL) {
                        if((curline-originline)>=3)
                                reui_balloon(ui, '\0', x0+w/2, y0+ui->fontheight*3/2, "\x80\x80\x80\xff", "\xff\xff\xff\xcf",(char *) hint,strlen(hint));
                        else
                                reui_balloon(ui, '\0', x0+w/2, y0+ui->fontheight*((curline-originline)*2+5)/2, "\x80\x80\x80\xff", "\xff\xff\xff\xcf",(char *) hint,strlen(hint));
                }
        }
        /* show func listing if requested */
        if(printout==NULL && re->funclisting!=NULL) {
                int total,i,ypos;
                char linebuf[1024];
                char *fg;
                reui_fillblended(ui,x0-linenowidth,y0,w,re->h,"\x44\x33\x22\xef");
                for(total=redata_line_total(re->funclisting),i=re->originlinefunclisting,ypos=y0;i<total && ypos<re->h;i++,ypos+=(ui->fontheight/((linebuf[0]=='\0')?2:1))) {
                        fg="\xff\xff\xff\xff";
                        if(i==re->curlinefunclisting) {
                                reui_fill(ui,x0-linenowidth+ui->fontwidth*3,ypos,w-ui->fontwidth*6,ui->fontheight,"\xdf\xdf\xdf\xff");
                                fg="\x00\x00\x00\xff";
                        }
                        if(redata_line_getstartstr(re->funclisting,i,linebuf,sizeof(linebuf))!=0)
                                break;
                        reui_write(ui,x0-linenowidth+ui->fontwidth*3,ypos,fg,linebuf,strlen(linebuf));
                }
        }
        /* all done */
        if(printout==NULL || printout==&fakeprintout) {
                re->contentsdirty=0;
                ui->rendererdirty=1;
        } else {
                printout->dirty=0;
                printout->ui->rendererdirty=1;
        }
        return(0);
}

redata_t *
re_getfunclisting(re_t *re)
{
        int lineno,total;
        char startbuf[3];
        char endbuf[3];
        char nextendbuf[3];
        int flag_havenextendbuf;
        int flag_isstart;
        int flag_semicolon;
        int flag_curlybrace;
        int flag_nextcurlybrace;
        int flag_previsstart;
        char linebuf[1024];
        char annotatedlinebuf[1024];
        redata_t *newdata;
        if(re==NULL)
                return(NULL); /* sanity check error */;
        if((newdata=redata_init(NULL))==NULL)
                return(NULL); /* couldn't init new buffer */
        flag_havenextendbuf=0;
        for(lineno=0,total=redata_line_total(re->data);lineno<total;lineno++) {
                if(redata_line_getstartstr(re->data,lineno,startbuf,sizeof(startbuf))!=0)
                        break; /* internal error */
                if(flag_havenextendbuf==1) {
                        strncpy(endbuf,nextendbuf,sizeof(endbuf));
                        endbuf[sizeof(endbuf)-1]='\0';
                } else if(redata_line_getendstrtrimmed(re->data,lineno,endbuf,sizeof(endbuf)," \t")!=0) {
                        break; /* internal error */
                }
                flag_havenextendbuf=flag_nextcurlybrace=0;
                if(redata_line_getendstrtrimmed(re->data,lineno+1,nextendbuf,sizeof(nextendbuf)," \t")==0) {
                        flag_havenextendbuf=1;
                        flag_nextcurlybrace=(*nextendbuf!='\0' && nextendbuf[strlen(nextendbuf)-1]=='{')?1:0;
                }
                flag_isstart=(*startbuf!='\0' && strchr("# \t{/",*startbuf)==NULL)?1:0;
                flag_semicolon=(*endbuf!='\0' && endbuf[strlen(endbuf)-1]==';')?1:0;
                flag_curlybrace=(*endbuf!='\0' && endbuf[strlen(endbuf)-1]=='{')?1:0;

                if(flag_isstart && !flag_semicolon && (flag_curlybrace || (flag_havenextendbuf && flag_nextcurlybrace))) {
                        if(flag_previsstart) {
                                snprintf(annotatedlinebuf,sizeof(annotatedlinebuf),"%6i: %s\n",lineno+1-1,linebuf);
                                annotatedlinebuf[sizeof(annotatedlinebuf)-1]='\0';
                                redata_op_add(newdata,redata_getused(newdata),annotatedlinebuf,strlen(annotatedlinebuf),NULL);
                                flag_previsstart=0;
                        }
                        redata_line_getstartstr(re->data,lineno,linebuf,sizeof(linebuf));
                        snprintf(annotatedlinebuf,sizeof(annotatedlinebuf),"%6i: %s\n\n",lineno+1,linebuf);
                        annotatedlinebuf[sizeof(annotatedlinebuf)-1]='\0';
                        redata_op_add(newdata,redata_getused(newdata),annotatedlinebuf,strlen(annotatedlinebuf),NULL);
                } else if(flag_isstart) {
                        redata_line_getstartstr(re->data,lineno,linebuf,sizeof(linebuf));
                        flag_previsstart=1;
                } else {
                        flag_previsstart=0;
                }
        }
        return(newdata);
}

int
re_funclistingxy2line(re_t *re,int mx,int my)
{
        int total,i,ypos;
        char linebuf[2];
        int h;
        if(re==NULL || mx<0 || my<0)
                return(-1);
        for(total=redata_line_total(re->funclisting),i=re->originlinefunclisting,ypos=re->y;i<total && ypos<re->h;i++,ypos+=h) {
                if(redata_line_getstartstr(re->funclisting,i,linebuf,sizeof(linebuf))!=0)
                        break;
                h=(re->ui->fontheight/((linebuf[0]=='\0')?2:1));
                if(my>=ypos && my<(ypos+h)) {
                        if(linebuf[0]=='\0')
                                return(-1);
                        return(i);
                }
        }
        return(-1);
}