/*
 * 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 <sys/time.h>
#include <sys/socket.h>  /* unix domain socket support */
#include <sys/un.h> /* unix domain socket support */
#include <sys/types.h> /* getpwuid() */
#include <pwd.h> /* getpwuid() */
#include <fcntl.h>
#include <sys/select.h>
#include <stdarg.h>
#include <sys/stat.h>
#include <limits.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 PRINTSBLOCKSIZE 32
#define COMMBUFSIZE 16384
#define COMMCLIENTSBLOCKSIZE 32
#define SOCKETFILENAMESIZE 1024
#define MAXFILENAMESIZE PATH_MAX+1
#define LISTENBACKLOG 5

#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"

#if 1
/* "forest" theme */
#define DEFAULT_COLOR_BACKGROUND "\xdf\xdf\xdf\xff"
#define DEFAULT_COLOR_PRINTOUTSTRIPE "\xef\xef\xef\xff"

#define DEFAULT_COLOR_CURSORBG "\x00\x00\x00\xff"
#define DEFAULT_COLOR_CURSORFG "\xff\xff\xff\xff"

#define DEFAULT_COLOR_TEXT "\x00\x00\x00\xff"

#define DEFAULT_COLOR_MATCHBG "\xff\xff\xff\xaf"
#define DEFAULT_COLOR_MATCHFG "\xff\x00\x00\x80"

#define DEFAULT_COLOR_STATUSBG "\x14\x3a\xaf\xff"
#define DEFAULT_COLOR_STATUSFG "\xe6\xdc\x5d\xff"
#define DEFAULT_COLOR_STATUSFGLIGHT "\x6f\x73\xa3\xff"

#define DEFAULT_COLOR_QUERYBG "\xad\x92\x5e\xff"
#define DEFAULT_COLOR_QUERYFG "\xd0\xef\x4f\xff"
#define DEFAULT_COLOR_QUERYBGOLD "\x83\x75\x59\xff"
#define DEFAULT_COLOR_QUERYFGOLD "\xb1\xc5\x5e\xff"

#define DEFAULT_COLOR_WARNINGBG "\xba\x07\x07\xff"
#define DEFAULT_COLOR_WARNINGFG "\xe6\xdc\x5d\xff"

#define DEFAULT_COLOR_INFOBG "\x4e\x8a\x4e\xff"
#define DEFAULT_COLOR_INFOFG "\xee\xee\x46\xff"
#define DEFAULT_COLOR_INFOFGLIGHT "\x84\xa4\x4c\xff"

#define DEFAULT_COLOR_SELNORMAL "\xde\xcf\x7f\xff"
#define DEFAULT_COLOR_CURLINE "\xf0\xea\xc9\xff"
#else
/* "alternative" theme */
#define DEFAULT_COLOR_BACKGROUND "\x1a\x1f\x35\xff"
#define DEFAULT_COLOR_PRINTOUTSTRIPE "\x0d\x11\x1e\xff"

#define DEFAULT_COLOR_CURSORBG "\xff\xff\xff\xff"
#define DEFAULT_COLOR_CURSORFG "\x0d\x11\x1e\xff"

#define DEFAULT_COLOR_TEXT "\xf2\xf0\xec\xff"

#define DEFAULT_COLOR_MATCHBG "\xff\xff\xff\xaf"
#define DEFAULT_COLOR_MATCHFG "\xff\x00\x00\x80"

#define DEFAULT_COLOR_STATUSBG "\x14\x3a\xaf\xff"
#define DEFAULT_COLOR_STATUSFG "\xe6\xdc\x5d\xff"
#define DEFAULT_COLOR_STATUSFGLIGHT "\x6f\x73\xa3\xff"

#define DEFAULT_COLOR_QUERYBG "\xad\x92\x5e\xff"
#define DEFAULT_COLOR_QUERYFG "\xd0\xef\x4f\xff"
#define DEFAULT_COLOR_QUERYBGOLD "\x83\x75\x59\xff"
#define DEFAULT_COLOR_QUERYFGOLD "\xb1\xc5\x5e\xff"

#define DEFAULT_COLOR_WARNINGBG "\xba\x07\x07\xff"
#define DEFAULT_COLOR_WARNINGFG "\xe6\xdc\x5d\xff"

#define DEFAULT_COLOR_INFOBG "\x4e\x8a\x4e\xff"
#define DEFAULT_COLOR_INFOFG "\xee\xee\x46\xff"
#define DEFAULT_COLOR_INFOFGLIGHT "\x84\xa4\x4c\xff"

#define DEFAULT_COLOR_SELNORMAL "\x4f\x48\x2b\xff"
#define DEFAULT_COLOR_CURLINE "\x6e\x64\x3c\xff"
#endif

#define COLOR_BACKGROUND(re) re->theme.color_background
#define COLOR_PRINTOUTSTRIPE(re) re->theme.color_printoutstripe
#define COLOR_CURSORBG(re) re->theme.color_cursorbg
#define COLOR_CURSORFG(re) re->theme.color_cursorfg
#define COLOR_TEXT(re) re->theme.color_text
#define COLOR_MATCHBG(re) re->theme.color_matchbg
#define COLOR_MATCHFG(re) re->theme.color_matchfg
#define COLOR_STATUSBG(re) re->theme.color_statusbg
#define COLOR_STATUSFG(re) re->theme.color_statusfg
#define COLOR_STATUSFGLIGHT(re) re->theme.color_statusfglight
#define COLOR_QUERYBG(re) re->theme.color_querybg
#define COLOR_QUERYFG(re) re->theme.color_queryfg
#define COLOR_QUERYBGOLD(re) re->theme.color_querybgold
#define COLOR_QUERYFGOLD(re) re->theme.color_queryfgold
#define COLOR_WARNINGBG(re) re->theme.color_warningbg
#define COLOR_WARNINGFG(re) re->theme.color_warningfg
#define COLOR_INFOBG(re) re->theme.color_infobg
#define COLOR_INFOFG(re) re->theme.color_infofg
#define COLOR_INFOFGLIGHT(re) re->theme.color_infofglight
#define COLOR_SELNORMAL(re) re->theme.color_selnormal
#define COLOR_CURLINE(re) re->theme.color_curline

typedef enum typeprintout_t {
        printoutView=0,
        printoutCopy,
        printoutHelp,
        printoutManpage,
        printoutManpageCurlang,
} typeprintout_t;

typedef struct printout_t {
        reui_t *ui;
        redata_t *data;
        typeprintout_t type;
        int originline;
        int origincol;
        int showonlyn;
        int mouseselactive;
        int dirty;
} printout_t;

typedef struct theme_t {
        int ntheme;
        char color_background[5];
        char color_printoutstripe[5];
        char color_cursorbg[5];
        char color_cursorfg[5];
        char color_text[5];
        char color_matchbg[5];
        char color_matchfg[5];
        char color_statusbg[5];
        char color_statusfg[5];
        char color_statusfglight[5];
        char color_querybg[5];
        char color_queryfg[5];
        char color_querybgold[5];
        char color_queryfgold[5];
        char color_warningbg[5];
        char color_warningfg[5];
        char color_infobg[5];
        char color_infofg[5];
        char color_infofglight[5];
        char color_selnormal[5];
        char color_curline[5];
} theme_t;

typedef struct commclient_t {
        int fd;
        int flag_connectedtoserver;
        char remoteid[MAXFILENAMESIZE];
        int sizebufin;
        int usedbufin;
        int gotbufin;
        char bufin[COMMBUFSIZE];
        int sizebufout;
        int usedbufout;
        int gotbufout;
        char bufout[COMMBUFSIZE];
} commclient_t;

typedef struct comms_t {
        int serverfd;
        char id[MAXFILENAMESIZE];
        char selectedid[MAXFILENAMESIZE];
        char socketfilename[SOCKETFILENAMESIZE];
        int sizeclients;
        int usedclients;
        commclient_t **clients;
} comms_t;

typedef struct re_t {
        redata_t *data;
        reui_t *ui;
        theme_t theme;
        int viewonly;
        int quirk_duplicates;
        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;
        struct timeval lastwheel;
        comms_t comms;
} 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, typeprintout_t typeprintout);
int re_delprint(re_t *re, int nprint);
int re_rtrim(re_t *re, long curpos, int *trimmed);
int re_extractword2buf(re_t *re, redata_t *data, long pos, char *buf, int sizebuf, int *usedbuf);
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 re_wheelaccel(re_t *re, int rawamount);
int re_themeset(re_t *re, int ntheme);
int re_socketinit(re_t *re, char *filename);
void re_socketfree(re_t *re);
int re_socketnewclient(re_t *re, int alreadyacceptedfd);
int re_socketstep(re_t *re);
int re_socketin(re_t *re, int nclient, char *line);
#ifndef __GNUC__
int re_socketout(re_t *re, int nclient, char *format, ...);
#else
        int re_socketout(re_t *re, int nclient, char *format, ...) __attribute__((format(printf,3,4)));
#endif

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';
                                        redata_needssaving_reset(re->data);
                                }
                                if(re_socketinit(re,re->filename)!=0) {
                                        fprintf(stderr,"WARNING: Couldn't init communication socket; there will be no integration with other tools\n");
                                }
                        }
                }
                /* 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);
                re_socketstep(re);
                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))
                          || (event.type==SDL_MOUSEWHEEL && (windowID=event.text.windowID)!=SDL_GetWindowID(re->ui->win))
                          || ((event.type==SDL_MOUSEBUTTONDOWN || event.type==SDL_MOUSEMOTION || event.type==SDL_MOUSEBUTTONUP) && (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;
                                /* transfor mousewheel events to keydown events */
                                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:
                                        case SDL_MOUSEWHEEL:
                                                if(event.type==SDL_KEYDOWN && event.key.keysym.sym==SDLK_ESCAPE) {
                                                        re_delprint(re,i);
                                                } if(event.type==SDL_MOUSEWHEEL || (event.type==SDL_KEYDOWN
                                                  && (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;
                                                        int wheelamount;
                                                        wheelamount=(event.type==SDL_MOUSEWHEEL)?re_wheelaccel(re,-event.wheel.y):0;
                                                        maxrow=printout->ui->h/printout->ui->fontheight-1;
                                                        neworiginline=printout->originline;
                                                        neworiginline+=(event.type==SDL_KEYDOWN && event.key.keysym.sym==SDLK_UP)?-1:
                                                                       (event.type==SDL_KEYDOWN && event.key.keysym.sym==SDLK_DOWN)?1:
                                                                       (event.type==SDL_KEYDOWN && event.key.keysym.sym==SDLK_PAGEUP)?-maxrow:
                                                                       (event.type==SDL_KEYDOWN && event.key.keysym.sym==SDLK_PAGEDOWN)?maxrow:
                                                                       (event.type==SDL_MOUSEWHEEL)?wheelamount:
                                                                       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.type==SDL_KEYDOWN && (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.type==SDL_KEYDOWN && (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;
                                        case SDL_MOUSEBUTTONDOWN:
                                        case SDL_MOUSEBUTTONUP:
                                        case SDL_MOUSEMOTION:
                                                if(!(event.type==SDL_MOUSEMOTION && re->mouseselactive==0)
                                                  && printout->type!=printoutHelp
                                                  && printout->type!=printoutManpage
                                                  && printout->type!=printoutManpageCurlang
                                                ) {
                                                        int mx,my;
                                                        int newposx,newposy;
                                                        long tmppos;
                                                        if(event.type==SDL_MOUSEBUTTONDOWN && event.button.button==SDL_BUTTON_LEFT) {
                                                                printout->mouseselactive=1;
                                                        } else if( event.type==SDL_MOUSEBUTTONUP && event.button.button==SDL_BUTTON_LEFT) {
                                                                printout->mouseselactive=0;
                                                        }
                                                        mx=(event.type!=SDL_MOUSEMOTION)?event.button.x:event.motion.x;
                                                        my=(event.type!=SDL_MOUSEMOTION)?event.button.y:event.motion.y;
                                                        newposx=(mx-(printout->ui->fontwidth*8+printout->ui->fontwidth/2))/printout->ui->fontwidth;
                                                        newposx=(newposx<0)?0:newposx;
                                                        newposy=my/printout->ui->fontheight;
                                                        if(redata_linecol2pos(re->data, printout->originline+newposy, printout->origincol+newposx,&tmppos,NULL)==0) {
                                                                re->curline=printout->originline+newposy;
                                                                re->curcol=printout->origincol+newposx;
                                                                re->headerdirty=1;
                                                                re->contentsdirty=1;
                                                                re_fixorigin_center(re);                                                        }
                                                }
                                                break;

                                }
                                continue; /* only want window events from printouts */
                        }
                        /* process main window events */
                        switch(event.type) {
                                case SDL_QUIT:
                                        if(redata_needssaving(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:
                                case SDL_MOUSEWHEEL:
                                        if(re->viewonly && (event.type==SDL_TEXTINPUT || event.type==SDL_TEXTEDITING))
                                                break;
                                        if(re->viewonly && (event.type==SDL_KEYDOWN || event.type==SDL_MOUSEWHEEL)) {
                                                if(event.type==SDL_KEYDOWN && event.key.keysym.sym==SDLK_ESCAPE) {
                                                        do_exit=1;
                                                } if(event.type==SDL_MOUSEWHEEL || (event.type==SDL_KEYDOWN
                                                  && (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;
                                                        int wheelamount;
                                                        wheelamount=(event.type==SDL_MOUSEWHEEL)?re_wheelaccel(re,-event.wheel.y):0;
                                                        neworiginline=re->originline;
                                                        neworiginline+=(event.type==SDL_KEYDOWN && event.key.keysym.sym==SDLK_UP)?-1:
                                                                       (event.type==SDL_KEYDOWN && event.key.keysym.sym==SDLK_DOWN)?1:
                                                                       (event.type==SDL_KEYDOWN && event.key.keysym.sym==SDLK_PAGEUP)?-re->maxrow:
                                                                       (event.type==SDL_KEYDOWN && event.key.keysym.sym==SDLK_PAGEDOWN)?re->maxrow:
                                                                       (event.type==SDL_MOUSEWHEEL)?wheelamount:
                                                                       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.type==SDL_KEYDOWN && (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 && !(SDL_GetModState()&KMOD_CTRL) && 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_MOUSEBUTTONUP && (SDL_GetModState()&KMOD_CTRL) && my>=re->y) {
                                                        /* control+click: search function definition */
                                                        int newposx,newposy;
                                                        long tmppos;
                                                        newposx=(mx-re->x-(re->showlinenumbers?(re->ui->fontwidth*7+re->ui->fontwidth/2):0))/re->ui->fontwidth;
                                                        newposx=(newposx<0)?0:newposx;
                                                        newposy=(my-re->y)/re->ui->fontheight;
                                                        if(redata_linecol2pos(re->data, re->originline+newposy, re->origincol+newposx,&tmppos,NULL)==0) {
                                                                char searchbuf[1024]; /* magic number: max. size of string to search (truncated if bigger) */
                                                                int usedsearchbuf;
                                                                long curpos;
                                                                redata_t *funclisting;
                                                                /* extract word, generate funclisting, search word in funclisting */
                                                                funclisting=NULL;
                                                                if(re_extractword2buf(re, re->data, tmppos, searchbuf, sizeof(searchbuf), &usedsearchbuf)==0
                                                                   && (funclisting=re_getfunclisting(re))!=NULL
                                                                   && (curpos=redata_searchforward(funclisting,0,searchbuf,usedsearchbuf))!=-1) {
                                                                        char linebuf[32],*cmdend;
                                                                        int funcline,funccol;
                                                                        if(redata_pos2linecol(funclisting,curpos,&funcline,&funccol)==0
                                                                           && redata_line_getstartstrtrimmed(funclisting,funcline,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;
                                                                                }
                                                                                /* position the cursor at the start of the line */
                                                                                re->origincol=0;
                                                                                re->curcol=0;
                                                                                /* redraw */
                                                                                re->contentsdirty=1;
                                                                        }
                                                                        redata_free(re->funclisting),re->funclisting=NULL;
                                                                }
                                                                if(funclisting!=NULL)
                                                                        redata_free(funclisting),funclisting=NULL;
                                                        }
                                                } 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 && !(SDL_GetModState()&KMOD_CTRL) && 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->showlinenumbers?(re->ui->fontwidth*7+re->ui->fontwidth/2):0))/re->ui->fontwidth;
                                                        newposx=(newposx<0)?0:newposx;
                                                        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;
        SDL_version linked_version;
        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;
        SDL_GetVersion(&linked_version);
        re->quirk_duplicates=(linked_version.major==2 && linked_version.minor==0 && linked_version.patch==9)?1:0;
        re_themeset(re,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;
        re_socketfree(re);
        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->showlinenumbers?(re->ui->fontwidth*7+re->ui->fontwidth/2):0))/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;
        int wheelamount=0;
        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[9]={"        "};
                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
        } else if(event->type==SDL_MOUSEWHEEL && event->wheel.y!=0) {
                wheelamount=re_wheelaccel(re,-event->wheel.y);
                memset(&fakeevent,0,sizeof(SDL_Event));
                event=&fakeevent;
                event->type=SDL_KEYDOWN;
                event->key.keysym.sym=(wheelamount>0)?SDLK_DOWN:SDLK_UP;
        }
        /* 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 || wheelamount!=0) && event->key.keysym.sym==SDLK_UP && re->originline==0)
                        return(0); /* nothing to do, already at top */
                move_res=re_moveupdown(re,(wheelamount!=0)?wheelamount:(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 || wheelamount!=0)) {
                        if(event->key.keysym.sym==SDLK_UP && re->originline>0) {
                                re->originline-=(wheelamount==0)?1:((wheelamount<0)?-wheelamount:wheelamount);
                                re->originline=(re->originline<0)?0:re->originline;
                                re->contentsdirty=1;
                        } else if(event->key.keysym.sym==SDLK_DOWN && re->originline<re->curline) {
                                re->originline+=(wheelamount==0)?1:((wheelamount<0)?-wheelamount:wheelamount);
                                re->originline=(re->originline>re->curline)?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_F1 && (SDL_GetModState()&(KMOD_CTRL|KMOD_SHIFT|KMOD_ALT))==0) {
                re_addprint(re,printoutHelp);
        } else if(event->key.keysym.sym==SDLK_F1 && (SDL_GetModState()&KMOD_CTRL)!=0 && (SDL_GetModState()&(KMOD_SHIFT|KMOD_ALT))==0 ) {
                re_addprint(re,printoutManpage);
        } else if(event->key.keysym.sym==SDLK_F1 && (SDL_GetModState()&KMOD_CTRL)!=0 && (SDL_GetModState()&KMOD_SHIFT)!=0 && (SDL_GetModState()&KMOD_ALT)==0) {
                re_addprint(re,printoutManpageCurlang);
        } 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) {
                /* NOTE: Control+'+'/'-' iterates between the font sizes (Control+'0' resets to the default font size) */
                /* NOTE: Control+shift+'+'/'-' iterates between the color permutations of the current theme in rgb, 6 in total (Control+shift+'0' resets to the default permutation) */
                if((SDL_GetModState()&KMOD_SHIFT)==0)
                        re_changefontsize(re, 1);
                else
                        re_themeset(re, re->theme.ntheme+1);
                re->ignorenkeys++;
        } else if(event->key.keysym.sym==SDLK_MINUS && (SDL_GetModState()&KMOD_CTRL)!=0) {
                if((SDL_GetModState()&KMOD_SHIFT)==0)
                        re_changefontsize(re, -1);
                else
                        re_themeset(re, re->theme.ntheme-1);
                re->ignorenkeys++;
        } else if(event->key.keysym.sym==SDLK_0 && (SDL_GetModState()&KMOD_CTRL)!=0) {
                if((SDL_GetModState()&KMOD_SHIFT)==0)
                        re_changefontsize(re, 0);
                else
                        re_themeset(re, 0);
                if(re->quirk_duplicates)
                        re->ignorenkeys++;
        } else if(event->key.keysym.sym==SDLK_5 && (SDL_GetModState()&KMOD_CTRL)!=0) {
                char cursorchar[7];
                int usedcursorchar;
                long cursorpos;
                int matchingpos;
                char matchingchar;
                int mline,mcol;
                usedcursorchar=0;
                if(redata_linecol2pos(re->data,re->curline,re->curcol,&cursorpos,NULL)==0
                  && redata_getutf8char(re->data,cursorpos,cursorchar,sizeof(cursorchar),&usedcursorchar)==0
                  && usedcursorchar==1
                  && strchr("[]{}<>()",*cursorchar)!=NULL
                  && (matchingpos=re_getmatchingbracket(re,cursorpos,*cursorchar,&matchingchar))!=-1
                  && redata_pos2linecol(re->data,matchingpos,&mline,&mcol)!=-1
                ) {
                        re->command=COMMAND_GOTOLINE;
                        snprintf(re->commandbuf,sizeof(re->commandbuf),"%i:%i",mline+1,mcol+1);
                        re->commandbuf[sizeof(re->commandbuf)-1]='\0';
                        re_processcommand(re);
                }
                if(re->quirk_duplicates)
                        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;
                }
                if(re->quirk_duplicates)
                        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);
                if(re->quirk_duplicates)
                        re->ignorenkeys++;
        } else if(/*re->selactive &&*/ event->key.keysym.sym==SDLK_p && (SDL_GetModState()&KMOD_CTRL)!=0) {
                re_addprint(re,printoutView);
        } else if(event->key.keysym.sym==SDLK_n && (SDL_GetModState()&KMOD_CTRL)!=0) {
                re->showlinenumbers=1-re->showlinenumbers;
                re_setuidata(re);
                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_needssaving(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)) {
                if(re->quirk_duplicates)
                        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,col;
                long pos;
                col=re->curcol;
                if(re->commandbuf[0]=='+') {
                        line=re->curline+atoi(re->commandbuf+1);
                } else if(re->commandbuf[0]=='-') {
                        line=re->curline-atoi(re->commandbuf+1);
                } else {
                        char *ptr;
                        line=atoi(re->commandbuf)-1;
                        for(ptr=re->commandbuf;*ptr>='0' && *ptr<='9';ptr++)
                                ;
                        if(*ptr==':')
                                col=atoi(ptr+1)-1,col=(col<0)?0:col;
                }
                line=(line<0)?0:line;
                if(redata_linecol2pos(re->data,line,col,&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;
                re->curcol=col;
                /* 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, typeprintout_t typeprintout)
{
        int i;
        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+PRINTSBLOCKSIZE)))==NULL)
                        return(-1); /* insuf. mem. */
                re->prints=newprints;
                memset(re->prints+re->sizeprints,0,sizeof(printout_t)*PRINTSBLOCKSIZE);
                re->sizeprints+=PRINTSBLOCKSIZE;
        }
        for(i=0;i<re->sizeprints;i++) {
                if(re->prints[i].ui==NULL)
                        break;
        }
        if(i>=re->sizeprints)
                return(-1); /* INTERNAL ERROR */
        if(typeprintout==printoutHelp
          || typeprintout==printoutCopy) {
                /* setup window */
                if((re->prints[i].ui=reui_init(DEFAULTFONTHEIGHT*re->fontheightpercent/100,re->ui))==NULL)
                        return(-1); /* couldn't init window */
                /* inmutable printout */
                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++;
                if(typeprintout==printoutCopy) {
                        /* copy contents (and set clipboard contents and unselect)*/
                        if(re->selactive) {
                                long frompos,topos;
                                int coldone;
                                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 if(typeprintout==printoutHelp) {
                        static char helptext[]={"\
Recenteditor help\n\
=================\n\
\n\
 F1           - Open a printout with this help\n\
 Shift+F1     - (TODO) Update hints with functions definitions of all open editors\n\
 Control+F1   - Open manpage of word under the cursor in a printout\n\
 F2           - Save\n\
\n\
 Cursor keys        - Move cursor\n\
 PageUp/PageDn      - Scroll one page up/down\n\
 Shift+Cursor       - Select\n\
 Mousewheel         - Scroll\n\
 Mouse click        - Change cursor position\n\
                      Click and hold on status bar to open function list\n\
 Control+Click      - Search function body of function name under the mouse\n\
 Tab key            - Insert 8 spaces\n\
 Control+Tab key    - Insert TAB character\n\
 Alt+Left/Right     - Change indentation of selection\n\
 Control+PgUp/PgDn  - Go to the beginning/end of the document\n\
 Home/End           - Go to the start/end of the line\n\
 Control+'0'        - Set default font size\n\
 Shift+Ctrl+'0'     - Set default color permutation\n\
 Control+'5'        - Go to the matching parenthesis or curly brace\n\
 Control+C          - Copy selection to clipboard. Will deselect selection.\n\
 Control+K+Command  - Block command, see below\n\
 Control+L          - Search next (requires a previous search with Control+Q+F)\n\
 Control+N          - Show/hide line numbers\n\
 Control+P          - Open printout window with current file or selection\n\
 Control+Q+Command  - Editing command, see below\n\
 Control+S          - Save\n\
 Control+X          - Cut selection into clipboard\n\
 Control+V          - Paste clipboard into current cursor position\n\
 Control+Z          - Undo\n\
 Control+'+'/'-'    - Iterates between font sizes\n\
 Shift+Ctrl+'+'/'-' - Iterates between color permutations\n\
\n\
\n\
Editing commands (Control+Q+Command)\n\
------------------------------------\n\
\n\
 Control+Q+L - Go to line number\n\
 Control+Q+F - Find\n\
 Control+Q+A - Search and replace\n\
\n\
\n\
Block commands (Control+K+Command)\n\
----------------------------------\n\
\n\
 Control+K+Q - Close editor (exit program -- don\'t ask, legacy key combo)\n\
 Control+K+H - Show/hide selection\n\
 Control+K+B - Set start of selection to cursor position\n\
 Control+K+K - Set end of selection to cursor position\n\
 Control+K+Y - Delete selection\n\
 Control+K+C - Copy selection to current cursor position (doesn\'t use clipboard)\n\
 Control+K+V - Move selection to current cursor position (doesn\'t use clipboard)\n\
\n\
\n\
Printout window\n\
---------------\n\
\n\
 Cursor/PageUp/PageDown - Scroll printout\n\
 Mousewheel             - Scroll printout\n\
"};
                        reui_title(re->prints[i].ui,"help");
                        redata_op_add(re->prints[i].data,0,helptext,sizeof(helptext)-1,NULL);
                        re->contentsdirty=1;
                }
        } else if(typeprintout==printoutManpage || typeprintout==printoutManpageCurlang) {
                char searchbuf[1024]; /* magic number: max. size of string to search (truncated if bigger) */
                char title[256];
                int lenprefix;
                int usedsearchbuf;
                long tmppos;
                int nread;
                FILE *fp;
                protolang_t curlang;
                curlang=redata_prototypes_detectlang(re->data);
                strncpy(searchbuf,(typeprintout==printoutManpageCurlang && curlang==protolang_c)?"man 2 "
                                  :(typeprintout==printoutManpageCurlang && curlang==protolang_tcl)?"man 3tcl "
                                  :"man ",sizeof(searchbuf));
                searchbuf[sizeof(searchbuf)-1]='\0';
                lenprefix=strlen(searchbuf);
                fp=NULL;
                if(redata_linecol2pos(re->data, re->curline, re->curcol,&tmppos,NULL)==0
                  && re_extractword2buf(re, re->data, tmppos, searchbuf+lenprefix, sizeof(searchbuf)-lenprefix, &usedsearchbuf)==0
                  && strncpy(title,searchbuf,sizeof(title))!=NULL
                  && (title[sizeof(title)-1]='\0')=='\0'
                  && (fp=popen(searchbuf,"r"))!=NULL
                  && (nread=fread(searchbuf,1,sizeof(searchbuf),fp))>0
                ) {
                        if((re->prints[i].ui=reui_init(DEFAULTFONTHEIGHT*re->fontheightpercent/100,re->ui))==NULL
                          || (re->prints[i].data=redata_init(redata_highlighter_register,NULL))==NULL) {
                                if(re->prints[i].ui!=NULL)
                                        reui_free(re->prints[i].ui),re->prints[i].ui=NULL;
                                return(-1); /* couldn't init data store */
                        }
                        re->usedprints++;
                        reui_title(re->prints[i].ui,title);
                        redata_op_add(re->prints[i].data,redata_getused(re->prints[i].data),searchbuf,nread,NULL);
                        while((nread=fread(searchbuf,1,sizeof(searchbuf),fp))>0) {
                                redata_op_add(re->prints[i].data,redata_getused(re->prints[i].data),searchbuf,nread,NULL);
                        }
                        pclose(fp),fp=NULL;
                        re->contentsdirty=1;
                } else {
                        if(fp!=NULL)
                                pclose(fp),fp=NULL;
                        re_delprint(re,i);
                        re->command=COMMAND_WARNING;
                        snprintf(re->commandbuf,sizeof(re->commandbuf),"Couldn't show manpage.");
                        re->commandbuf[sizeof(re->commandbuf)-1]='\0';
                        re->headerdirty=1;
                        return(0);
                }
        } else {
                char mytitle[256];
                /* setup window */
                if((re->prints[i].ui=reui_init(DEFAULTFONTHEIGHT*re->fontheightpercent/100,re->ui))==NULL)
                        return(-1); /* couldn't init window */
                /* setup title */
                snprintf(mytitle,sizeof(mytitle),"view %s",re->filename);
                mytitle[sizeof(mytitle)-1]='\0';
                reui_title(re->prints[i].ui,mytitle);
                /* printout is 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++;
        }
        re->prints[i].type=typeprintout;
        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);
}

int
re_extractword2buf(re_t *re, redata_t *data, long pos, char *buf, int sizebuf, int *usedbuf)
{
        char utfchar[5];
        int usedutfchar;
        long startpos,curpos,endpos;
        int c;
        if(re==NULL || data==NULL || pos<0)
                return(-1); /* sanity check failed */
        /* search start of word */
        for(curpos=startpos=pos;redata_getprevutf8char(re->data,curpos,utfchar,sizeof(utfchar),&usedutfchar)==0;startpos=curpos) {
                curpos-=usedutfchar;
                if(usedutfchar==1) {
                        c=utfchar[0];
                        if(!((c>='0' && c<='9') || (c>='a' && c<='z') || (c>='A' && c<='Z') || c=='_'))
                                break;
                }
        }
        /* search end of word */
        for(curpos=endpos=pos;redata_getutf8char(re->data,curpos,utfchar,sizeof(utfchar),&usedutfchar)==0;endpos=curpos) {
                curpos+=usedutfchar;
                if(usedutfchar==1) {
                        c=utfchar[0];
                        if(!((c>='0' && c<='9') || (c>='a' && c<='z') || (c>='A' && c<='Z') || c=='_'))
                                break;
                }
        }
        if(redata_getsubstr(re->data,startpos,endpos,buf,sizebuf-1,usedbuf)!=0)
                return(-1);
        buf[*usedbuf]='\0';
        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_needssaving(re->data)?COLOR_STATUSBG(re):COLOR_INFOBG(re));
        /* 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_needssaving(re->data)?COLOR_STATUSFGLIGHT(re):COLOR_INFOFGLIGHT(re)
           ,"File:%s%s Line:%s Col:%s Pos:%s Size:%s"
          ,spacesfilename,redata_needssaving(re->data)?"*":" ",spaceslinebuf,spacescolbuf,spacesposbuf,spacessizebuf);
        reui_printf(re->ui,0,0,redata_needssaving(re->data)?COLOR_STATUSFG(re):COLOR_INFOFG(re)
           ,"     %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(re));
                reui_printf(re->ui,0,0,COLOR_QUERYFG(re),"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(re));
                reui_printf(re->ui,0,0,COLOR_WARNINGFG(re),"%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(re));
                reui_printf(re->ui,0,0,COLOR_INFOFG(re),"%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(re));
#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(re),"%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));
                re->commandbuf[sizeof(re->commandbuf)-1]='\0';
                commandlen=redata_generic_utf8len(re->command,strlen(re->command));
                reui_printf(re->ui,0,0,COLOR_QUERYFG(re),"%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(re),"%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(re));
                } 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(re));
                        reui_printf(re->ui,re->ui->fontwidth*(commandlen+1),0,COLOR_QUERYFGOLD(re),"%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;
        char *bgcolor;
        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;
        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:
                   (printout->type==printoutHelp)?0:
                   (printout->type==printoutManpage)?0:
                   (printout->type==printoutManpageCurlang)?0:
                   1;
        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;
        }
        if(flaglineno) {
                linenosize=6;
                if(printout==NULL)
                        linenowidth=linenosize*ui->fontwidth+ui->fontwidth*3/2;
                else
                        linenowidth=linenosize*ui->fontwidth+ui->fontwidth*2;
                x0+=linenowidth;
        }
        if(redata_linecol2pos(data,curline,curcol,&cursorpos,NULL)!=0)
                return(0); /* error obtaining position */
        bgcolor=(printout!=NULL && (printout->type==printoutHelp || printout->type==printoutManpage || printout->type==printoutManpageCurlang ))
                ?COLOR_PRINTOUTSTRIPE(re)
                :COLOR_BACKGROUND(re);
        reui_fill(ui,x0-linenowidth,y0,w,h,bgcolor);
        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,COLOR_PRINTOUTSTRIPE(re));
        } else if(printout!=NULL && (printout->type==printoutHelp || printout->type==printoutManpage || printout->type==printoutManpageCurlang)) {
                ; /* help bgcolor already filled */
        } 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,COLOR_PRINTOUTSTRIPE(re));
        }
        /* 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))?COLOR_CURLINE(re):COLOR_SELNORMAL(re);
                        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,COLOR_TEXT(re),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=COLOR_TEXT(re);
                in_error=0;
                if(flaglineno && origincol==0) {
                        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,"\xb6\xb6\xb6\xff",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,COLOR_CURSORBG(re));
                                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,COLOR_CURSORFG(re));
                                else
                                        reui_write(ui,x0+ui->fontwidth*(curcol-origincol),y,COLOR_CURSORFG(re),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,COLOR_CURSORBG(re));
                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=COLOR_MATCHFG(re);
                char *bg=COLOR_MATCHBG(re);
                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,COLOR_BACKGROUND(re));
                                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);
}

int
re_wheelaccel(re_t *re, int rawamount)
{
        int wheelamount;
        struct timeval old;
        struct timezone dummy;
        int oldmsec,newmsec,d;
        if(re==NULL || rawamount==0)
                return(rawamount);
        memcpy(&old,&(re->lastwheel),sizeof(old));
        if(gettimeofday(&(re->lastwheel),&dummy)!=0 || old.tv_sec<(re->lastwheel.tv_sec-1) || old.tv_sec>re->lastwheel.tv_sec)
                return(rawamount);
        oldmsec=old.tv_usec/1000;
        newmsec=re->lastwheel.tv_usec/1000+((old.tv_sec<re->lastwheel.tv_sec)?1000:0);
        d=newmsec-oldmsec;
        wheelamount=rawamount;
        if(d<70)
                wheelamount*=10;
        return(wheelamount);
}

void
util_applypermutation(int *template3,char *colors,int invert)
{
        unsigned char c[3];
        if(template3==NULL || colors==NULL || template3[0]<0 || template3[0]>2 || template3[1]<0 || template3[1]>2 || template3[2]<0 || template3[2]>2)
                return; /* sanity check error */
        if(invert==0) {
                c[0]=((unsigned char *)colors)[0];
                c[1]=((unsigned char *)colors)[1];
                c[2]=((unsigned char *)colors)[2];
        } else {
                c[0]=255-((unsigned char *)colors)[0];
                c[1]=255-((unsigned char *)colors)[1];
                c[2]=255-((unsigned char *)colors)[2];
        }
        ((unsigned char *)colors)[0]=c[template3[0]];
        ((unsigned char *)colors)[1]=c[template3[1]];
        ((unsigned char *)colors)[2]=c[template3[2]];
        return;
}

int
re_themeset(re_t *re, int ntheme)
{
        int permutations[6][3]={{0,1,2},{0,2,1},{1,0,2},{1,2,0},{2,1,0},{2,0,1}};
        int *permutationtemplate;
        int invert;
        if(re==NULL)
                return(-1);
        /* num. themes: 3*2*1*2=12 (permutations of 3 colors plus inverted colors)*/
        ntheme=(ntheme<0)?12-((-ntheme)%12):ntheme;
        ntheme%=12;
        re->theme.ntheme=ntheme;
        /* compute invert and the number of permutation */
        invert=(ntheme>=6)?1:0;
        ntheme%=6;
        permutationtemplate=permutations[ntheme];
        /* reset colors and permutate them */
        strncpy(re->theme.color_background,DEFAULT_COLOR_BACKGROUND,sizeof(re->theme.color_background)),re->theme.color_background[sizeof(re->theme.color_background)-1]='\0',util_applypermutation(permutationtemplate,re->theme.color_background,invert);
        strncpy(re->theme.color_printoutstripe,DEFAULT_COLOR_PRINTOUTSTRIPE,sizeof(re->theme.color_printoutstripe)),re->theme.color_printoutstripe[sizeof(re->theme.color_printoutstripe)-1]='\0',util_applypermutation(permutationtemplate,re->theme.color_printoutstripe,invert);
        strncpy(re->theme.color_cursorbg,DEFAULT_COLOR_CURSORBG,sizeof(re->theme.color_cursorbg)),re->theme.color_cursorbg[sizeof(re->theme.color_cursorbg)-1]='\0',util_applypermutation(permutationtemplate,re->theme.color_cursorbg,invert);
        strncpy(re->theme.color_cursorfg,DEFAULT_COLOR_CURSORFG,sizeof(re->theme.color_cursorfg)),re->theme.color_cursorfg[sizeof(re->theme.color_cursorfg)-1]='\0',util_applypermutation(permutationtemplate,re->theme.color_cursorfg,invert);
        strncpy(re->theme.color_text,DEFAULT_COLOR_TEXT,sizeof(re->theme.color_text)),re->theme.color_text[sizeof(re->theme.color_text)-1]='\0',util_applypermutation(permutationtemplate,re->theme.color_text,invert);
        strncpy(re->theme.color_matchbg,DEFAULT_COLOR_MATCHBG,sizeof(re->theme.color_matchbg)),re->theme.color_matchbg[sizeof(re->theme.color_matchbg)-1]='\0',util_applypermutation(permutationtemplate,re->theme.color_matchbg,invert);
        strncpy(re->theme.color_matchfg,DEFAULT_COLOR_MATCHFG,sizeof(re->theme.color_matchfg)),re->theme.color_matchfg[sizeof(re->theme.color_matchfg)-1]='\0',util_applypermutation(permutationtemplate,re->theme.color_matchfg,invert);
        strncpy(re->theme.color_statusbg,DEFAULT_COLOR_STATUSBG,sizeof(re->theme.color_statusbg)),re->theme.color_statusbg[sizeof(re->theme.color_statusbg)-1]='\0',util_applypermutation(permutationtemplate,re->theme.color_statusbg,invert);
        strncpy(re->theme.color_statusfg,DEFAULT_COLOR_STATUSFG,sizeof(re->theme.color_statusfg)),re->theme.color_statusfg[sizeof(re->theme.color_statusfg)-1]='\0',util_applypermutation(permutationtemplate,re->theme.color_statusfg,invert);
        strncpy(re->theme.color_statusfglight,DEFAULT_COLOR_STATUSFGLIGHT,sizeof(re->theme.color_statusfglight)),re->theme.color_statusfglight[sizeof(re->theme.color_statusfglight)-1]='\0',util_applypermutation(permutationtemplate,re->theme.color_statusfglight,invert);
        strncpy(re->theme.color_querybg,DEFAULT_COLOR_QUERYBG,sizeof(re->theme.color_querybg)),re->theme.color_querybg[sizeof(re->theme.color_querybg)-1]='\0',util_applypermutation(permutationtemplate,re->theme.color_querybg,invert);
        strncpy(re->theme.color_queryfg,DEFAULT_COLOR_QUERYFG,sizeof(re->theme.color_queryfg)),re->theme.color_queryfg[sizeof(re->theme.color_queryfg)-1]='\0',util_applypermutation(permutationtemplate,re->theme.color_queryfg,invert);
        strncpy(re->theme.color_querybgold,DEFAULT_COLOR_QUERYBGOLD,sizeof(re->theme.color_querybgold)),re->theme.color_querybgold[sizeof(re->theme.color_querybgold)-1]='\0',util_applypermutation(permutationtemplate,re->theme.color_querybgold,invert);
        strncpy(re->theme.color_queryfgold,DEFAULT_COLOR_QUERYFGOLD,sizeof(re->theme.color_queryfgold)),re->theme.color_queryfgold[sizeof(re->theme.color_queryfgold)-1]='\0',util_applypermutation(permutationtemplate,re->theme.color_queryfgold,invert);
        strncpy(re->theme.color_warningbg,DEFAULT_COLOR_WARNINGBG,sizeof(re->theme.color_warningbg)),re->theme.color_warningbg[sizeof(re->theme.color_warningbg)-1]='\0',util_applypermutation(permutationtemplate,re->theme.color_warningbg,invert);
        strncpy(re->theme.color_warningfg,DEFAULT_COLOR_WARNINGFG,sizeof(re->theme.color_warningfg)),re->theme.color_warningfg[sizeof(re->theme.color_warningfg)-1]='\0',util_applypermutation(permutationtemplate,re->theme.color_warningfg,invert);
        strncpy(re->theme.color_infobg,DEFAULT_COLOR_INFOBG,sizeof(re->theme.color_infobg)),re->theme.color_infobg[sizeof(re->theme.color_infobg)-1]='\0',util_applypermutation(permutationtemplate,re->theme.color_infobg,invert);
        strncpy(re->theme.color_infofg,DEFAULT_COLOR_INFOFG,sizeof(re->theme.color_infofg)),re->theme.color_infofg[sizeof(re->theme.color_infofg)-1]='\0',util_applypermutation(permutationtemplate,re->theme.color_infofg,invert);
        strncpy(re->theme.color_infofglight,DEFAULT_COLOR_INFOFGLIGHT,sizeof(re->theme.color_infofglight)),re->theme.color_infofglight[sizeof(re->theme.color_infofglight)-1]='\0',util_applypermutation(permutationtemplate,re->theme.color_infofglight,invert);
        strncpy(re->theme.color_selnormal,DEFAULT_COLOR_SELNORMAL,sizeof(re->theme.color_selnormal)),re->theme.color_selnormal[sizeof(re->theme.color_selnormal)-1]='\0',util_applypermutation(permutationtemplate,re->theme.color_selnormal,invert);
        strncpy(re->theme.color_curline,DEFAULT_COLOR_CURLINE,sizeof(re->theme.color_curline)),re->theme.color_curline[sizeof(re->theme.color_curline)-1]='\0',util_applypermutation(permutationtemplate,re->theme.color_curline,invert);
        /* set theme in highlighter */
        redata_highlighter_settheme(re->data,ntheme,invert);
        /* force redraw */
        re->contentsdirty=1;
        re->headerdirty=1;
        return(0);
}

int
re_socketinit(re_t *re,char *filename)
{
        int i;
        comms_t *comms;
        commclient_t *client;
        struct sockaddr_un addr;
        char *ptr;
        struct passwd *passwd;
        char *username;
        int oldfd;
        char *errstr;
        if(re==NULL)
                return(-1);
        comms=&(re->comms);
        /* close all current comms */
        if(comms->serverfd!=-1) {
                close(comms->serverfd),comms->serverfd=-1;
                if(comms->socketfilename[0]!='\0')
                        unlink(comms->socketfilename),comms->socketfilename[0]='\0';
        }
        if(re->comms.clients!=NULL) {
                for(i=0;i<re->comms.sizeclients;i++) {
                        if((client=re->comms.clients[i])==NULL)
                                continue;
                        if(client->fd!=-1)
                                close(client->fd),client->fd=-1;
                }
                re->comms.usedclients=0;
        }
        /* fill the id */
        if(realpath(filename,re->comms.id)==NULL) {
                /* if the path cannot be resolved, copy the filename */
                strncpy(re->comms.id,filename,sizeof(re->comms.id));
                re->comms.id[sizeof(re->comms.id)-1]='\0';
        }
        re->comms.id[sizeof(re->comms.id)-1]='\0';
        /* prepare the new filepath for the socket */
        if((passwd=getpwuid(getuid()))==NULL || (username=passwd->pw_name)==NULL) {
                fprintf(stderr,"WARNING: Couldn't get username\n");
                return(-1);
        }
        ptr=strrchr(filename,'/');
        ptr=(ptr!=NULL)?ptr+1:filename;
        snprintf(comms->socketfilename,sizeof(comms->socketfilename),"/tmp/.re_%s",username);
        comms->socketfilename[sizeof(comms->socketfilename)-1]='\0';
        mkdir(comms->socketfilename,0700);
        snprintf(comms->socketfilename,sizeof(comms->socketfilename),"/tmp/.re_%s/%s",username,ptr);
        comms->socketfilename[sizeof(comms->socketfilename)-1]='\0';
        if(strlen(comms->socketfilename)>=(sizeof(addr.sun_path))) {
                comms->socketfilename[0]='\0';
                fprintf(stderr,"WARNING: Socket path filename too long\n");
                return(-1);
        }
        /* detect if there is a stale socket */
        if((errstr="create")==NULL
          || (oldfd=socket(AF_UNIX,SOCK_STREAM,0))==-1
          || (errstr="assing")==NULL /* this one never fails, just for completion */
          || (memset(&addr,0,sizeof(struct sockaddr_un)))==NULL
          || (addr.sun_family=AF_UNIX)!=AF_UNIX
          || strncpy(addr.sun_path,comms->socketfilename,sizeof(addr.sun_path))==NULL
          || (errstr="connect")==NULL
          || connect(oldfd,(struct sockaddr *)&addr,sizeof(addr))==-1
        ) {
#if 0
fprintf(stderr,"Check for other server result: %s fail.\n",errstr);
#endif
                close(oldfd),oldfd=-1; /* OK: the re is no server as couldn't connect */
        } else {
                fd_set writeset;
                struct timeval tv;
                FD_ZERO(&writeset);
                FD_SET(oldfd,&writeset);
                tv.tv_sec=tv.tv_usec=0;
                if(select(oldfd+1,NULL,&writeset,NULL,&tv)>0) {
                        int nclient;
                        comms->socketfilename[0]='\0';
                        nclient=re_socketnewclient(re,oldfd),oldfd=-1;
                        if(nclient!=-1) {
                                re_socketout(re,nclient,"id %s\n",comms->id);
                                comms->clients[nclient]->flag_connectedtoserver=1;
#if 0
fprintf(stderr,"nclient:%i id:\"%s\".\n",nclient,comms->id);
fprintf(stderr,"Connected to server.\n");
#endif
                                return(0);
                        }
                        fprintf(stderr,"WARNING: There is a process using the communication socket for the file and couldn't connect to it.\n");
                        return(-1);
                }
                close(oldfd),oldfd=-1;
        }
        unlink(comms->socketfilename);
        /* create and bind socket */
        if((errstr="create")==NULL
          || (comms->serverfd=socket(AF_UNIX,SOCK_STREAM,0))==-1
          || (errstr="assing")==NULL /* this one never fails, just for completion */
          || (memset(&addr,0,sizeof(struct sockaddr_un)))==NULL
          || (addr.sun_family=AF_UNIX)!=AF_UNIX
          || strncpy(addr.sun_path,comms->socketfilename,sizeof(addr.sun_path))==NULL
          || (errstr="bind")==NULL
          || bind(comms->serverfd,(struct sockaddr *)&addr,sizeof(addr))==-1
          || (errstr="listen")==NULL
          || listen(comms->serverfd,LISTENBACKLOG)==-1
          ) {
                comms->socketfilename[0]='\0';
                if(comms->serverfd!=-1)
                        close(comms->serverfd),comms->serverfd=-1;
                fprintf(stderr,"WARNING: Couldn't %s unix domain socket\n",errstr);
                return(-1);
        }
#if 0
fprintf(stderr,"Server registered.\n");
#endif
        return(0);
}

void
re_socketfree(re_t *re)
{
        int i;
        commclient_t *client;
        if(re==NULL)
                return; /* nothing to do */
        if(re->comms.serverfd!=-1)
                close(re->comms.serverfd),re->comms.serverfd=-1;
        if(re->comms.socketfilename[0]!='\0')
                unlink(re->comms.socketfilename),re->comms.socketfilename[0]='\0';
        if(re->comms.clients!=NULL) {
                for(i=0;i<re->comms.sizeclients;i++) {
                        if((client=re->comms.clients[i])==NULL)
                                continue;
                        if(client->fd!=-1)
                                close(client->fd),client->fd=-1;
                        free(client),client=NULL;
                        re->comms.clients[i]=NULL;
                }
                re->comms.usedclients=re->comms.sizeclients=0;
                free(re->comms.clients),re->comms.clients=NULL;
        }
        return;
}

int
re_socketnewclient(re_t *re, int alreadyacceptedfd)
{
        int i;
        int fd;
        commclient_t *client;
        struct sockaddr addr;
        socklen_t addrlen;
        addrlen=sizeof(addr);
        if(re==NULL)
                return(-1); /* sanity check failed */
        if(alreadyacceptedfd==-1)
                fd=accept(re->comms.serverfd,&addr,&addrlen);
        else
                fd=alreadyacceptedfd;
        if(re->comms.usedclients==re->comms.sizeclients) {
                commclient_t **newclients;
                if((newclients=realloc(re->comms.clients,sizeof(commclient_t *)*(re->comms.sizeclients+COMMCLIENTSBLOCKSIZE)))==NULL) {
                        close(fd),fd=-1;
                        return(-1); /* insuf. mem. */
                }
                re->comms.clients=newclients;
                memset(re->comms.clients+re->comms.sizeclients,0,sizeof(commclient_t *)*COMMCLIENTSBLOCKSIZE);
                re->comms.sizeclients+=COMMCLIENTSBLOCKSIZE;
        }
        for(i=0;i<re->comms.sizeclients;i++) {
                if(re->comms.clients[i]==NULL || re->comms.clients[i]->fd==-1)
                        break;
        }
        if(i>=re->comms.sizeclients) {
                close(fd),fd=-1;
                re->comms.usedclients=re->comms.sizeclients;
                return(-1); /* INTERNAL ERROR */
        }
        client=re->comms.clients[i];
        if(client==NULL) {
                if((client=malloc(sizeof(commclient_t)))==NULL) {
                        close(fd),fd=-1;
                        return(-1); /* INTERNAL ERROR */
                }
                re->comms.clients[i]=client;
                memset(client,0,sizeof(commclient_t));
                client->fd=-1;
                client->sizebufin=sizeof(client->bufin);
                client->sizebufout=sizeof(client->bufout);
        }
        client->fd=fd;
        client->usedbufin=client->gotbufin=0;
        client->usedbufout=client->gotbufout=0;
        re->comms.usedclients++;
#if 0
fprintf(stderr,"New client registered (%i).\n",i);
#endif
        return(i);
}

int re_socketstep(re_t *re)
{
        int maxfd;
        fd_set readset,writeset;
        struct timeval tv;
        int i;
        comms_t *comms;
        commclient_t *client;
        int avail,queued;
        int nread,nwritten;
        char *ptr,*next,*end;
        if(re==NULL)
                return(-1); /* sanity check error */
        comms=&(re->comms);
        FD_ZERO(&readset);
        FD_ZERO(&writeset);
        maxfd=0;
        if(comms->serverfd!=-1) {
                maxfd=comms->serverfd;
                FD_SET(comms->serverfd,&readset);
        }
        for(i=0;i<comms->sizeclients;i++) {
                if((client=comms->clients[i])==NULL || client->fd==-1)
                        continue;
                if(client->gotbufin>0) {
                        memmove(client->bufin,client->bufin+client->gotbufin,client->usedbufin-client->gotbufin);
                        client->usedbufin-=client->gotbufin;
                        client->gotbufin=0;
                }
                if((client->sizebufin-client->usedbufin)>0) {
                        FD_SET(client->fd,&readset);
                        maxfd=(maxfd<client->fd)?client->fd:maxfd;
                }
                if((client->usedbufout-client->gotbufout)>0) {
                        FD_SET(client->fd,&writeset);
                        maxfd=(maxfd<client->fd)?client->fd:maxfd;
                }
        }
        tv.tv_sec=tv.tv_usec=0;
        if(select(maxfd+1,&readset,&writeset,NULL,&tv)<=0)
                return(0); /* nothing to do */
        /* server */
        if(comms->serverfd!=-1 && FD_ISSET(comms->serverfd,&readset))
                re_socketnewclient(re,-1);
        /* clients */
        for(i=0;i<comms->sizeclients;i++) {
                if((client=comms->clients[i])==NULL || client->fd==-1)
                        continue;
                if(FD_ISSET(client->fd,&readset)) {
                        if((queued=sock_queued(client->fd))<=0) {
                                /* remote has closed the connection */
#if 0
fprintf(stderr,"Unregistering client, remote has closed the connection (%i).\n",i);
#endif
                                close(client->fd),client->fd=-1;
                                if(client->flag_connectedtoserver) {
                                        client->flag_connectedtoserver=0;
                                        re_socketinit(re,re->filename);
                                }
                                continue;
                        }
                        avail=(client->sizebufin-client->usedbufin);
                        queued=(queued>avail)?avail:queued;
                        if((nread=read(client->fd,client->bufin+client->usedbufin,queued))>0) {
#if 0
fprintf(stderr,"Read from client %li bytes (%i).\n",(long)nread,i);
#endif
                                client->usedbufin+=nread;
                                for(ptr=client->bufin+client->gotbufin
                                   ,end=client->bufin+client->usedbufin
                                   ,next=memchr(ptr,'\n',end-ptr)
                                   ,next=(next==NULL)?end:next
                                  ;client->fd!=-1 && next<end
                                  ;ptr=(next!=end)?next+1:end
                                   ,next=memchr(ptr,'\n',end-ptr)
                                   ,next=(next==NULL)?end:next
                                  ) {
                                        *next='\0';
                                        if(next>ptr && next[-1]=='\r')
                                                next[-1]='\0';
                                        re_socketin(re,i,ptr);
                                }
                                client->gotbufin=ptr-client->bufin;
                                if(client->fd==-1)
                                        continue; /* in case the socket has been closed inside re_socketin() */
                        }
                }
                if(FD_ISSET(client->fd,&writeset)) {
                        queued=client->usedbufout-client->gotbufout;
                        if((nwritten=write(client->fd,client->bufout+client->gotbufout,client->usedbufout-client->gotbufout))>0)
                                client->gotbufout+=nwritten;
#if 0
fprintf(stderr,"Write to client %li bytes (%i).\n",(long)nwritten,i);
#endif
                }
        }
        return(0);
}

int
re_socketin(re_t *re, int nclient, char *line)
{
        /* note that the '\n' delimiter has been already removed */
        /* Commands can be sent from the command line using socat as in: */
        /* "socat - UNIX-CONNECT:/tmp/.re_${USER}/filename.ext" */
        commclient_t *client;
        char *ptr;
        int i;
        if(re==NULL || nclient<0 || nclient>=re->comms.sizeclients || re->comms.clients[nclient]==NULL || line==NULL)
                return(-1); /* sanity check failed */
        client=re->comms.clients[nclient];
#if 0
fprintf(stderr,"Received from client \"%s\" (%i).\n",line,nclient);
#endif
        if(memcmp(line,"goto ",5)==0) {
                int oldline;
                int avail;
                ptr=line+5;
                /* change line */
#if 0
fprintf(stderr,"Changing line because of command from client \"%s\" (%i).\n",line,nclient);
#endif
                if(re->comms.selectedid[0]=='\0' || strcmp(re->comms.id,re->comms.selectedid)==0) {
                        re->command=COMMAND_GOTOLINE;
                        strncpy(re->commandbuf,ptr,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;
                        }
                }
                /* send end-of-results */
                re_socketout(re, nclient, "\n");
                /* forward command to all identified clients except the one who sent teh command */
                for(i=0;i<re->comms.sizeclients;i++) {
#if 0
                        if(re->comms.clients[i]!=NULL)
                                fprintf(stderr,"Client[%i] id:\"%s\"\n",i,re->comms.clients[i]->remoteid);
#endif
                        if(re->comms.clients[i]==NULL || re->comms.clients[i]->fd==-1 || re->comms.clients[i]->remoteid[0]=='\0')
                                continue;
                        if(re->comms.selectedid[0]=='\0' || strcmp(re->comms.clients[i]->remoteid,re->comms.selectedid)==0) {
                                avail=re->comms.clients[i]->sizebufout-re->comms.clients[i]->usedbufout;
                                if((strlen(line)+1)>avail) {
                                        fprintf(stderr,"Couldn't forward command to client because the output buffer is full (avail:%li, req:%li)\n",(long)avail,(long)(strlen(line)+1));
                                        continue;
                                }
                                re_socketout(re, i, "%s\n",line);
                        }
                }
        } else if(memcmp(line,"id ",3)==0) {
                ptr=line+3;
                /* change id */
#if 0
fprintf(stderr,"Storing client id because of command from client \"%s\" (%i).\n",line,nclient);
#endif
                strncpy(client->remoteid,ptr,sizeof(client->remoteid));
                client->remoteid[sizeof(client->remoteid)-1]='\0';
                /* send end-of-results */
                re_socketout(re, nclient, "\n");
        } else if(strcmp(line,"list")==0) {
                /* list ids */
#if 0
fprintf(stderr,"Listing ids because of command from client \"%s\" (%i).\n",line,nclient);
#endif
                re_socketout(re, nclient, "%s\n",re->comms.id);
                for(i=0;i<re->comms.sizeclients;i++) {
                        if(re->comms.clients[i]==NULL || re->comms.clients[i]->fd==-1 || re->comms.clients[i]->remoteid[0]=='\0')
                                continue;
                        re_socketout(re, nclient, "%s\n",re->comms.clients[i]->remoteid);
                }
                /* send end-of-results */
                re_socketout(re, nclient, "\n");
        } else if(strcmp(line,"select")==0) {
                /* reset select id */
#if 0
fprintf(stderr,"Resetting selected id because of command from client \"%s\" (%i).\n",line,nclient);
#endif
                re->comms.selectedid[0]='\0';
                /* send end-of-results */
                re_socketout(re, nclient, "\n");
        } else if(memcmp(line,"select ",7)==0) {
                ptr=line+7;
                /* select id */
#if 0
fprintf(stderr,"Changing selected id because of command from client \"%s\" (%i).\n",line,nclient);
#endif
                strncpy(re->comms.selectedid,ptr,sizeof(re->comms.selectedid));
                re->comms.selectedid[sizeof(re->comms.selectedid)-1]='\0';
                /* send end-of-results */
                re_socketout(re, nclient, "\n");
        } else if(line[0]=='\0' ) {
                ; /* ignore the end-of-message (should only receive them on forwarding clients) */
        } else if(strcmp(line,"quit")==0) {
                /* close socket */
#if 0
fprintf(stderr,"Closing socket because of command from client \"%s\" (%i).\n",line,nclient);
#endif
                close(re->comms.clients[nclient]->fd),re->comms.clients[nclient]->fd=-1;
                return(0);
        } else {
#if 1
fprintf(stderr,"Ignoring unknown command from client \"%s\" (%i).\n",line,nclient);
#endif
        }
        return(0);
}

int
re_socketout(re_t *re, int nclient, char *format, ...)
{
        /* note that the caller has to output the '\n' delimiter to mark the end of the line */
        commclient_t *client;
        va_list mylist;
        int avail;
        int res;
        if(re==NULL || nclient<0 || nclient>=re->comms.sizeclients || re->comms.clients[nclient]==NULL || re->comms.clients[nclient]->fd==-1)
                return(-1); /* sanity check error */
        client=re->comms.clients[nclient];
        memmove(client->bufout,client->bufout+client->gotbufout,client->usedbufout-client->gotbufout);
        client->usedbufout-=client->gotbufout;
        client->gotbufout=0;
        avail=client->sizebufout-client->usedbufout;
        va_start(mylist,format);
        res=vsnprintf(client->bufout+client->usedbufout,avail,format,mylist);
        va_end(mylist);
        if(res<0 || res>=avail)
                return(-1); /* doesn't fit or another error */
        client->usedbufout+=res;
        return(0);
}