/* * recenteditor.c * * A programmers editor * * Author: Dario Rodriguez dario@softhome.net * This program is licensed under the terms of GNU GPL v2.1+ */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <limits.h> #include <signal.h> #include <time.h> #include "re_data.h" #include "re_plugin_unsaved.h" #include "re_ui.h" #include "ext/socklib.h" #define LINEFORCESCROLL 1 #define COLFORCESCROLL 5 #define IDLETIMEOUTSECONDS 10 #define COMMANDBUFSIZE 1024 #define COMMAND_WARNING "(!)" #define COMMAND_GOTOLINE "Go to line: " #define COMMAND_QUESTION "(?)" #define COMMAND_EXIT "Exit" #define COLOR_STATUSBG "\x00\x00\xff\xff" #define COLOR_STATUSFG "\xff\xff\x00\xff" #define COLOR_QUERYBG "\xc0\xff\x00\xff" #define COLOR_QUERYFG "\x3f\x00\xff\xff" #define COLOR_WARNINGBG "\xff\x00\x00\xff" #define COLOR_WARNINGFG "\xff\xff\x00\xff" typedef struct re_t { redata_t *data; reui_t *ui; int flag_newfile; char filename[PATH_MAX]; int x, y, w, h; // contents rect long cursorpos; int originline,origincol; int curline,curcol; int maxrow,maxcol; int headerdirty; int contentsdirty; int command_first_key; char *command; char commandbuf[COMMANDBUFSIZE]; question_t *question; int showingwarning; int ignorenkeys; } re_t; volatile int flag_sigint; volatile int flag_sigpipe; static int setsignal(int num, void (*sighandler)(int)); static void sighandler_sigint(int num); static void sighandler_sigpipe(int num); re_t *re_init(void); void re_free(re_t *re); int re_setuidata(re_t *re); int re_setfilename(re_t *re, char *filename); int re_fixorigin(re_t *re); int re_fixorigin_center(re_t *re); int re_processkey_editing(re_t *re, SDL_Event *event); int re_processkey_commandwait(re_t *re, SDL_Event *event); int re_processkey_commanddata(re_t *re, SDL_Event *event); int re_processcommand(re_t *re); int re_moveupdown(re_t *re, int totalinc); int re_moveleftright(re_t *re, int totalinc); int re_rtrim(re_t *re, long curpos, int *trimmed); int re_drawheader_editing(re_t *re); int re_drawheader_command(re_t *re); int re_drawcontents(re_t *re); int main(int argc, char *argv[]) { re_t *re; int do_exit; SDL_Event event; sselect *ssel; int flag_had_events; time_t lastidle,now; int load_pending; if(argc!=2 || strcmp(argv[argc-1],"--help")==0) { fprintf(stderr,"Syntax: %s filename\n",argv[0]); return(1); } if((re=re_init())==NULL) { fprintf(stderr,"ERROR: couldn't init structs.\n"); return(2); } if((ssel=sselect_init())==NULL) { fprintf(stderr,"ERROR: couln't init internal data.\n"); re_free(re),re=NULL; return(2); } if(re_setfilename(re,argv[argc-1])!=0) { fprintf(stderr,"ERROR: filename too long.\n"); re_free(re),re=NULL; return(2); } reui_title(re->ui,re->filename); flag_sigint=flag_sigpipe=0; setsignal(SIGINT,sighandler_sigint); setsignal(SIGPIPE,sighandler_sigpipe); do_exit=0; reui_scr2renderer(re->ui,0,0,re->ui->w,re->ui->h); re_setuidata(re); re->headerdirty=1; re->contentsdirty=1; flag_had_events=0; SDL_StartTextInput(); lastidle=0; load_pending=1; redata_loadquestions_setup(re->data, re->filename); while(do_exit==0 && flag_sigint==0) { /* load file (or ask pre-load questions), if pending */ if(load_pending && re->command==NULL) { question_t *question; if((question=redata_loadquestions_getnext(re->data))!=NULL) { re->command=COMMAND_QUESTION; re->question=question; #if 0 fprintf(stderr,"QUESTION INIT: %s: %s\n",re->question->title,re->question->body); #endif } else { long startpos; char *startptr; int len; if(redata_load(re->data,re->filename,NULL)==-1) re->flag_newfile=1; else re->flag_newfile=0; re->originline=re->origincol=0; re->curline=re->curcol=0; re->cursorpos=0; load_pending=0; re->headerdirty=1; re->contentsdirty=1; /* check if last character of file is '\n', if not, add it */ if(redata_getused(re->data)==0 || (redata_line_rawinfo(re->data,redata_getused(re->data)-1,&startpos,&startptr,&len,NULL)!=-1 && len>0 && startptr[len-1]!='\n')) { redata_op_add(re->data,redata_getused(re->data),"\n",1,NULL); re->command=COMMAND_WARNING; snprintf(re->commandbuf,sizeof(re->commandbuf),"Added missing \\n at end of file"); re->commandbuf[sizeof(re->commandbuf)-1]='\0'; } } } if(re->headerdirty) { if(re->command==NULL) { #if 0 fprintf(stderr,"REDRAW Header (editing)\n"); #endif re_drawheader_editing(re); re->showingwarning=0; } else { #if 0 fprintf(stderr,"REDRAW Header (command)\n"); #endif re_drawheader_command(re); if(strcmp(re->command,COMMAND_WARNING)==0) { /* the warnings only get shown once, remove it */ re->command=NULL; re->commandbuf[0]='\0'; re->showingwarning=1; } else { re->showingwarning=0; } } } if(re->contentsdirty) re_drawcontents(re); if(re->ui->rendererdirty) { #if 0 fprintf(stderr,"RENDER\n"); #endif reui_present(re->ui); } sselect_wait(ssel,(flag_had_events)?10:100); flag_had_events=(flag_had_events>0)?flag_had_events-1:0; SDL_PumpEvents(); while(SDL_PeepEvents(&event,1,SDL_GETEVENT,SDL_FIRSTEVENT,SDL_LASTEVENT)>0) { flag_had_events=10; switch(event.type) { case SDL_QUIT: do_exit=1; break; case SDL_KEYDOWN: case SDL_TEXTINPUT: case SDL_TEXTEDITING: if(re->command==NULL || strcmp(re->command,COMMAND_WARNING)==0) re_processkey_editing(re,&event); else if(re->command[0]=='\0') re_processkey_commandwait(re,&event); else re_processkey_commanddata(re,&event); break; case SDL_WINDOWEVENT: #if 0 fprintf(stderr,"WINDOWEVENT: %s(%li) data1:%li data2:%li\n", (event.window.event==SDL_WINDOWEVENT_NONE)?"NONE": (event.window.event==SDL_WINDOWEVENT_SHOWN)?"SHOWN": (event.window.event==SDL_WINDOWEVENT_HIDDEN)?"HIDDEN": (event.window.event==SDL_WINDOWEVENT_EXPOSED)?"EXPOSED": (event.window.event==SDL_WINDOWEVENT_MOVED)?"MOVED": (event.window.event==SDL_WINDOWEVENT_RESIZED)?"RESIZED": (event.window.event==SDL_WINDOWEVENT_SIZE_CHANGED)?"SIZE_CHANGED": (event.window.event==SDL_WINDOWEVENT_MINIMIZED)?"MINIMIZED": (event.window.event==SDL_WINDOWEVENT_MAXIMIZED)?"MAXIMIZED": (event.window.event==SDL_WINDOWEVENT_RESTORED)?"RESTORED": (event.window.event==SDL_WINDOWEVENT_FOCUS_GAINED)?"FOCUS_GAINED": (event.window.event==SDL_WINDOWEVENT_FOCUS_LOST)?"FOCUS_LOST": (event.window.event==SDL_WINDOWEVENT_CLOSE)?"CLOSE": (event.window.event==SDL_WINDOWEVENT_TAKE_FOCUS)?"TAKE_FOCUS": "UNKNOWN", (long) event.window.event,(long)event.window.data1,(long)event.window.data2); #endif if(event.window.event==SDL_WINDOWEVENT_SHOWN || event.window.event==SDL_WINDOWEVENT_EXPOSED) { if(!re->showingwarning) re->headerdirty=1; re->contentsdirty=1; } else if((event.window.event==SDL_WINDOWEVENT_RESIZED || event.window.event==SDL_WINDOWEVENT_SIZE_CHANGED) && (event.window.data1!=re->ui->w || event.window.data2!=re->ui->h)) { #if 0 fprintf(stderr,"Resizing from %ix%i to %ix%i...\n",re->ui->w,re->ui->h,(int)event.window.data1,(int)event.window.data2); #endif reui_resize(re->ui,event.window.data1,event.window.data2); re_setuidata(re); re->headerdirty=1; re->contentsdirty=1; } break; default: break; } } /* additional processing of global commands */ if(re->command!=NULL && strcmp(re->command,COMMAND_EXIT)==0) do_exit=1; /* timeouts */ now=time(NULL); if((lastidle+IDLETIMEOUTSECONDS)<now || (lastidle-IDLETIMEOUTSECONDS)>now) { lastidle=now; redata_idleproc(re->data,re->filename); } } SDL_StopTextInput(); sselect_free(ssel),ssel=NULL; re_free(re),re=NULL; return(0); } static int setsignal(int num, void (*sighandler)(int)) { struct sigaction sa; sa.sa_handler=sighandler; sigemptyset(&sa.sa_mask); sa.sa_flags=0; return(sigaction(num,&sa,0)); } static void sighandler_sigint(int num) { flag_sigint=1; } static void sighandler_sigpipe(int num) { flag_sigpipe=1; } re_t * re_init(void) { re_t *re; if((re=malloc(sizeof(re_t)))==NULL) return(NULL); /* insuf. mem. */ memset(re,0,sizeof(re_t)); if((re->data=redata_init(redata_unsaved_register,NULL))==NULL) { re_free(re),re=NULL; return(NULL); /* insuf. mem. */ } if((re->ui=reui_init())==NULL) { re_free(re),re=NULL; return(NULL); /* video init error */ } return(re); } void re_free(re_t *re) { if(re==NULL) return; /* all done */ if(re->ui!=NULL) reui_free(re->ui),re->ui=NULL; if(re->data!=NULL) redata_free(re->data),re->data=NULL; free(re),re=NULL; return; } int re_setuidata(re_t *re) { if(re==NULL) return(-1); re->x=0,re->y=re->ui->fontheight,re->w=re->ui->w,re->h=re->ui->h-re->y; re->maxrow=re->h/re->ui->fontheight-1; re->maxcol=re->w/re->ui->fontwidth-1; re_fixorigin(re); return(0); } int re_setfilename(re_t *re, char *filename) { if(re==NULL || filename==NULL) return(-1); strncpy(re->filename,filename,sizeof(re->filename)); re->filename[sizeof(re->filename)-1]='\0'; if(strcmp(filename,re->filename)!=0) return(-1); /* filename too long */ return(0); } int re_fixorigin(re_t *re) { if(re==NULL) return(-1); if(re->curline<(re->originline+LINEFORCESCROLL)) { re->originline=re->curline-LINEFORCESCROLL; re->originline=(re->originline<0)?0:re->originline; } if(re->curline>(re->originline+re->maxrow-LINEFORCESCROLL)) { re->originline=re->curline+LINEFORCESCROLL-re->maxrow; re->originline=(re->originline<0)?0:(re->originline>re->curline)?re->curline:re->originline; } if(re->curcol<re->origincol) { re->origincol=re->curcol; re->origincol=(re->origincol<0)?0:re->origincol; } if(re->curcol>=(re->origincol+re->maxcol-COLFORCESCROLL)) { re->origincol=re->curcol+COLFORCESCROLL-re->maxcol; re->origincol=(re->origincol<0)?0:re->origincol; } return(0); } int re_fixorigin_center(re_t *re) { if(re==NULL) return(-1); if(re->curline<re->originline || re->curline>=(re->originline+re->maxrow)) { re->originline=re->curline-(re->maxrow/2); re->originline=(re->originline<0)?0:re->originline; } re->origincol=(re->curcol>(re->maxcol-COLFORCESCROLL))?(re->curcol-(re->maxcol-COLFORCESCROLL)):0; return(0); } int re_processkey_editing(re_t *re, SDL_Event *event) { SDL_Event fakeevent; if(re==NULL || event==NULL) return(-1); /* sanity check failed */ /* convert RETURN KEYDOWN to TEXTINPUT */ if(event->type==SDL_KEYDOWN && event->key.keysym.sym==SDLK_RETURN) { #warning file having two \n at the end, delete the second with DEL staying to the right of the first, if we go down then, it does an scroll but it shouldn't memset(&fakeevent,0,sizeof(SDL_Event)); event=&fakeevent; event->type=SDL_TEXTINPUT; strncpy(event->text.text,"\n",sizeof(event->text.text)); event->text.text[sizeof(event->text.text)-1]='\0'; } else if(event->type==SDL_KEYDOWN && event->key.keysym.sym==SDLK_TAB) { static char spaces[8]={" "}; memset(&fakeevent,0,sizeof(SDL_Event)); event=&fakeevent; event->type=SDL_TEXTINPUT; strncpy(event->text.text,spaces+(re->curcol%8),sizeof(event->text.text)); event->text.text[sizeof(event->text.text)-1]='\0'; } else if(event->type==SDL_KEYDOWN && event->key.keysym.sym==SDLK_DELETE) { #if 0 fprintf(stderr,"SDL_KEYDOWN: DELETE\n"); #endif long realend; int at_end; if(re->cursorpos==redata_getused(re->data)) return(0); /* already at end, nothing to delete */ if(redata_line_realend(re->data,re->cursorpos,&realend)==-1) return(-1); /* couldn't get current line info */ at_end=(re->cursorpos==realend)?1:0; if(at_end && (re->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,re->cursorpos,NULL,&col)==-1) return(-1); /* couldn't get line info */ if(redata_op_addn(re->data,re->cursorpos,' ',re->curcol-col,NULL)!=0) return(-1); /* couldn't add spaces up to current displayed pos */ re->cursorpos+=re->curcol-col; re->cursorpos++; re->curline++; re->curcol=0; } else { re_moveleftright(re,1); } #if 0 fprintf(stderr,"SDL_KEYDOWN: fake setup ok\n"); #endif } /* special case: text editing event */ if(event->type==SDL_TEXTINPUT) { int len; long realend; int at_end; int nspaces; if(re->ignorenkeys>0) { re->ignorenkeys--; return(0); /* this is an already processed key, ignore it */ } #if 0 fprintf(stderr,"SDL_TEXTINPUT:\"%s\"\n",event->text.text); #endif if(redata_line_realend(re->data,re->cursorpos,&realend)==-1) return(-1); /* couldn't get current line info */ at_end=(re->cursorpos==realend)?1:0; for(nspaces=0;event->text.text[nspaces]==' ';nspaces++) ; if(nspaces>0 && event->text.text[nspaces]=='\0' && at_end) { /* instead of adding spaces at the end of the line, we move the cursor to the right */ re_moveleftright(re,nspaces); return(0); /* no need to add spaces at the end of the line */ } redata_undo_groupinit(re->data,NULL); if(!(event->text.text[0]=='\n' && event->text.text[1]=='\0') && at_end) { int col; if(redata_pos2linecol(re->data,re->cursorpos,NULL,&col)==-1) return(-1); /* couldn't get line info */ if(redata_op_addn(re->data,re->cursorpos,' ',re->curcol-col,NULL)!=0) return(-1); /* couldn't add spaces up to current displayed pos */ /* increment cursorpos; spaces are 1 byte, so the number of columns advanced is the number of bytes advanced */ re->cursorpos+=re->curcol-col; } len=strlen(event->text.text); if(redata_op_add(re->data,re->cursorpos,event->text.text,len,NULL)!=0) return(-1); /* couldn't add requested text */ re->cursorpos+=len; if(event->text.text[0]=='\n' && event->text.text[1]=='\0') { int trimmed; if(re_rtrim(re,re->cursorpos-len,&trimmed)) re->cursorpos-=trimmed; re->curline++; re->curcol=0; } else { re->curcol+=redata_generic_utf8len(event->text.text,len); } redata_undo_groupcommit(re->data,NULL); re->headerdirty=1; re->contentsdirty=1; } #if 0 else if(event->type==SDL_TEXTEDITING) { /* NOTE: on window enter events we cam also receive this event (!) */ #warning TODO: CJK support fprintf(stderr,"SDL_TEXTEDITING: composition:\"%s\" start:%i len:%i\n",event->edit.text,event->edit.start,event->edit.length); } #endif /* default case: keydown event */ if(event->type!=SDL_KEYDOWN) return(0); /* Ignore other possible events */ #if 0 fprintf(stderr,"SDL_KEYDOWN: sym:%i\n",event->key.keysym.sym); #endif if(event->key.keysym.sym==SDLK_DOWN || event->key.keysym.sym==SDLK_UP) { re_moveupdown(re,(event->key.keysym.sym==SDLK_UP)?-1:1); } else if(event->key.keysym.sym==SDLK_LEFT || event->key.keysym.sym==SDLK_RIGHT) { re_moveleftright(re,(event->key.keysym.sym==SDLK_LEFT)?-1:1); } else if((SDL_GetModState()&KMOD_CTRL)!=0 && (event->key.keysym.sym==SDLK_PAGEDOWN || event->key.keysym.sym==SDLK_PAGEUP)) { re->cursorpos=(event->key.keysym.sym==SDLK_PAGEDOWN)?(redata_getused(re->data)-1):0; re->cursorpos=(re->cursorpos<0)?0:re->cursorpos; redata_pos2linecol(re->data,re->cursorpos,&(re->curline),&(re->curcol)); re_fixorigin_center(re); re->headerdirty=1; re->contentsdirty=1; } else if(event->key.keysym.sym==SDLK_PAGEDOWN || event->key.keysym.sym==SDLK_PAGEUP) { re_moveupdown(re,(event->key.keysym.sym==SDLK_PAGEUP)?-(re->maxrow):re->maxrow); } else if(event->key.keysym.sym==SDLK_HOME || event->key.keysym.sym==SDLK_END) { long newpos; if((event->key.keysym.sym==SDLK_HOME && redata_line_realstart(re->data,re->cursorpos,&newpos)==-1) || (event->key.keysym.sym==SDLK_END && redata_line_realend(re->data,re->cursorpos,&newpos)==-1)) { return(-1); /* couldn't get destination pos data */ } re->cursorpos=newpos; if(redata_pos2linecol(re->data,re->cursorpos,NULL,&(re->curcol))==-1) return(-1); /* couldn't get col of current pos */; re_fixorigin(re); re->headerdirty=1; re->contentsdirty=1; } else if(event->key.keysym.sym==SDLK_BACKSPACE && re->cursorpos>0) { int line,col; long startpos,newpos,realstart,start; char *ptr; int len; int trimmed; int doneinc; #if 0 fprintf(stderr,"SDL_KEYDOWN: BACKSPACE%s\n",(event==&fakeevent)?" (fake)":""); #endif if(redata_pos2linecol(re->data,re->cursorpos,&line,&col)==-1) return(-1); /* couldn't get current line data */ if(col<re->curcol) { re_moveleftright(re,-1); return(0); /* were hovering to the right of last char, only had to move left */ } if(event!=&fakeevent) redata_undo_groupinit(re->data,NULL); /* delete the last character */ if(col>0) { if(redata_line_realstart(re->data,re->cursorpos,&realstart)==-1) return(-1); /* couldn't get line info */ /* get the start of the part to delete */ doneinc=0; for(ptr=NULL,len=0,start=0,newpos=re->cursorpos;doneinc!=1;) { if((newpos-1)<realstart) break; newpos--; if(ptr==NULL || (start+len)<=newpos || newpos<start) { if(redata_line_rawinfo(re->data,newpos,&start,&ptr,&len,NULL)==-1) return(-1); /* couldn't get line data */ } if(redata_generic_utf8isstartbyte(ptr[newpos-start])) doneinc++; } /* delete */ redata_op_del(re->data,newpos,re->cursorpos-newpos,NULL); re->cursorpos=newpos; } else { long delpos; /* to make the code trivial, we're being lazy on '\n' deletion: we call ...rawinfo() for each byte in the multibyte utf-8 sequence */ for(delpos=re->cursorpos-1 ;delpos>0 && redata_line_rawinfo(re->data,delpos,&startpos,&ptr,&len,NULL)==0;) { if(redata_generic_utf8isstartbyte(ptr[delpos-startpos])) break; } redata_op_del(re->data,delpos,re->cursorpos-delpos,NULL); re->cursorpos=delpos; } redata_pos2linecol(re->data,re->cursorpos,&(re->curline),&(re->curcol)); /* if cursor at end of line and there are trailing spaces at the end, delete them too */ if(re_rtrim(re,re->cursorpos,&trimmed)) re->cursorpos-=trimmed; /* end of deletion */ redata_undo_groupcommit(re->data,NULL); re->headerdirty=1; re->contentsdirty=1; } else if(event->key.keysym.sym==SDLK_q && (SDL_GetModState()&KMOD_CTRL)!=0) { fprintf(stderr,"re_processkey(): received Control+q\n"); re->command=""; re->command_first_key='q'; re->headerdirty=1; } else if(event->key.keysym.sym==SDLK_k && (SDL_GetModState()&KMOD_CTRL)!=0) { fprintf(stderr,"re_processkey(): received Control+k\n"); 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; fprintf(stderr,"re_processkey(): received Control+z\n"); if(redata_op_undo(re->data,&newpos)) return(-1); re->cursorpos=newpos; redata_pos2linecol(re->data,re->cursorpos,&(re->curline),&(re->curcol)); re_fixorigin(re); re->headerdirty=1; 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->headerdirty=1; } else if(re->command_first_key=='k' && event->key.keysym.sym==SDLK_q) { re->command=COMMAND_EXIT; re->commandbuf[0]='\0'; re->headerdirty=1; } else { re->command=NULL; re->headerdirty=1; } if(!(SDL_GetModState()&KMOD_CTRL)) re->ignorenkeys=1; /* control was not pressed, we will receive again this key as SDL_TEXTINPUT */ return(0); } int re_processkey_commanddata(re_t *re, SDL_Event *event) { if(re==NULL || event==NULL) return(-1); /* sanity check failed */ /* special case: text editing event */ if(event->type==SDL_TEXTINPUT) { int len; if(re->ignorenkeys>0) { re->ignorenkeys--; return(0); /* this is an already processed key, ignore it */ } len=strlen(event->text.text); if((strlen(re->commandbuf)+len+1)>=sizeof(re->commandbuf)) return(-1); /* No space for text */ memcpy(re->commandbuf+strlen(re->commandbuf),event->text.text,len+1); re->commandbuf[sizeof(re->commandbuf)-1]='\0'; re->headerdirty=1; } /* default case: keydown event */ if(event->type!=SDL_KEYDOWN) return(0); /* Ignore other possible events */ if(event->key.keysym.sym==SDLK_ESCAPE) { re->command=NULL; re->commandbuf[0]='\0'; re->headerdirty=1; #warning XXX: TODO: re->question: Make cursor change option, make numbers change option } else if(event->key.keysym.sym==SDLK_BACKSPACE && re->commandbuf[0]!='\0') { int nchar; char *ptr; if((nchar=redata_generic_utf8len(re->commandbuf,strlen(re->commandbuf)))<=0) return(-1); /* error parsing commandbuf */ if((ptr=redata_generic_utf8col(re->commandbuf,strlen(re->commandbuf),nchar-1))==NULL) return(-1); /* error positioning in commandbuf */ *ptr='\0'; re->headerdirty=1; } else if(event->key.keysym.sym==SDLK_RETURN) { re_processcommand(re); } return(0); } int re_processcommand(re_t *re) { if(re==NULL || re->command==NULL) return(-1); re->commandbuf[sizeof(re->commandbuf)-1]='\0'; if(strcmp(re->command,COMMAND_GOTOLINE)==0) { int line; long pos; line=atoi(re->commandbuf)-1; line=(line<0)?0:line; if(redata_linecol2pos(re->data,line,re->curcol,&pos,NULL)==-1) { re->command=COMMAND_WARNING; snprintf(re->commandbuf,sizeof(re->commandbuf),"Unknown line"); re->commandbuf[sizeof(re->commandbuf)-1]='\0'; re->headerdirty=1; return(-1); } re->cursorpos=pos; re->curline=line; /* position the line in the center of viewport */ re->originline=line-(re->maxrow/2); re->headerdirty=1; re->contentsdirty=1; } 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; if(re==NULL) return(-1); /* sanity check failed */ if(totalinc==0) return(0); /* nothing to do */ inc=(totalinc<0)?-1:1; newpos=re->cursorpos; /* get rid of compiler warning (will be overwitten in the loop as totalinc never is 0) */ for(doneinc=0;doneinc!=totalinc;doneinc+=inc) { #if 0 fprintf(stderr,"MOVING from cursorpos:%li line:%i col:%i\n",re->cursorpos,re->curline,re->curcol); #endif if((inc==-1 && redata_line_prevrealstart(re->data,re->cursorpos,&realstart)==-1) || (inc==1 && redata_line_nextrealstart(re->data,re->cursorpos,&realstart)==-1)) { break; /* couldn't get current line data, we are at start/end */ } re->cursorpos=realstart; re->curline+=inc; re_fixorigin(re); #if 0 fprintf(stderr,"MOVING to cursorpos:%li line:%i col:%i\n",re->cursorpos,re->curline,re->curcol); #endif } if(redata_line_inccol(re->data,re->cursorpos,re->curcol,&newpos2,NULL)==-1) { /* error advancing cursor, "emergency" repositioning */ #if 0 fprintf(stderr,"COLUMN ERROR\n"); #endif re->curcol=0; newpos2=newpos; } #if 0 fprintf(stderr,"COLUMN from cursorpos:%li line:%i col:%i\n",re->cursorpos,re->curline,re->curcol); #endif re->cursorpos=newpos2; #if 0 fprintf(stderr,"COLUMN to cursorpos:%li line:%i col:%i\n",re->cursorpos,re->curline,re->curcol); #endif re->contentsdirty=1; re->headerdirty=1; return(0); } int re_moveleftright(re_t *re, int totalinc) { long newpos,realstart; int inc,doneinc; char *ptr; int len; long start; int tmpcol,oldcol; if(re==NULL) return(-1); /* sanity check failed */ if(totalinc==0) return(0); /* nothing to do */ oldcol=re->curcol; if(totalinc<0 && (re->curcol+totalinc)<=0) { /* we'll land on the start of the line -- do it trivially */ if(redata_line_realstart(re->data,re->cursorpos,&realstart)==-1) return(-1); /* couldn't get current pos */ re->curcol=0; re->cursorpos=realstart; } else { /* move a char at a time */ if(redata_line_realstart(re->data,re->cursorpos,&realstart)==-1) return(-1); /* couldn't get current pos */ inc=(totalinc<0)?-1:1; doneinc=0; if(redata_pos2linecol(re->data,re->cursorpos,NULL,&tmpcol)==-1) return(-1); /* couldn't get current pos */ /* special case: we're just over the '\n' going right, we have to move 1 to enable the generic case to work */ if(tmpcol==re->curcol && inc>0) { if(redata_line_rawinfo(re->data,re->cursorpos,&start,&ptr,&len,NULL)==-1) return(-1); /* couldn't get line data */ if(ptr[re->cursorpos-start]=='\n') { tmpcol++; totalinc--; re->curcol++; } } /* generic case: cursor after the \n ("floating") */ if(tmpcol<re->curcol) { int avail; if(inc>0) { doneinc=totalinc; } else { avail=re->curcol-tmpcol; doneinc=(avail>=totalinc)?totalinc:avail; } } /* generic case: move one char at a time */ for(ptr=NULL,len=0,start=0,newpos=re->cursorpos;doneinc!=totalinc;) { if((newpos+inc)<realstart) break; newpos+=inc; if(ptr==NULL || (start+len)<=newpos || newpos<start) { if(redata_line_rawinfo(re->data,newpos,&start,&ptr,&len,NULL)==-1) return(-1); /* couldn't get line data */ } if(redata_generic_utf8isstartbyte(ptr[newpos-start])) doneinc+=inc; if(ptr[newpos-start]=='\n') break; } re->cursorpos=newpos; re->curcol+=doneinc; if(inc>0 && doneinc<totalinc && ptr!=NULL && ptr[newpos-start]=='\n') re->curcol+=(totalinc-doneinc); } if(re->curcol!=oldcol) { re->contentsdirty=1; re->headerdirty=1; if(re->curcol<(re->origincol+COLFORCESCROLL) && re->origincol>0) { re->origincol=(re->curcol-COLFORCESCROLL)/COLFORCESCROLL; re->origincol=re->origincol*COLFORCESCROLL; re->origincol=(re->origincol<0)?0:re->origincol; } if(re->curcol>(re->origincol+re->maxcol-COLFORCESCROLL)) { re->origincol=re->curcol+COLFORCESCROLL-re->maxcol-(re->curcol%COLFORCESCROLL); re->origincol=(re->origincol<0)?0:re->origincol; } } return(0); } int re_rtrim(re_t *re, long curpos, int *trimmed) { long startpos; char *start; int len; int n; if(re==NULL || curpos<0 || curpos>redata_getused(re->data)) return(-1); if(trimmed!=NULL) *trimmed=0; if(redata_line_rawinfo(re->data,curpos,&startpos,&start,&len,NULL)==0 && len>1 && start[len-1]=='\n' && start[len-2]==' ' && curpos==(startpos+len-1)) { for(n=1;(n+1)<(len-1) && start[len-1-n-1]==' ';n++) ; #if 0 fprintf(stderr,"Trying to DELETE SPACES del %i bytes\n",n); #endif redata_op_del(re->data,curpos-n,n,NULL); re->cursorpos-=n; if(trimmed!=NULL) *trimmed=n; } return(0); } int re_drawheader_editing(re_t *re) { int line,col; if(re==NULL) return(-1); if(redata_pos2linecol(re->data,re->cursorpos,&line,&col)==-1) line=col=-1; reui_fill(re->ui,0,0,re->ui->w,re->ui->fontheight,COLOR_STATUSBG); /* for the user, lines start at 0 (internally they start at 0) */ reui_printf(re->ui,0,0,COLOR_STATUSFG,"File: %s Line:%i (%i) Col:%i (%i) Pos:%li",re->filename,re->curline+1,line+1,re->curcol+1,col+1,re->cursorpos); re->headerdirty=0; re->ui->rendererdirty=1; return(0); } int re_drawheader_command(re_t *re) { if(re==NULL) return(-1); if(re->command==NULL) return(-1); if(re->command[0]=='\0') { reui_fill(re->ui,0,0,re->ui->w,re->ui->fontheight,COLOR_QUERYBG); reui_printf(re->ui,0,0,COLOR_QUERYFG,"Command:"); } else if(strcmp(re->command,COMMAND_WARNING)==0) { reui_fill(re->ui,0,0,re->ui->w,re->ui->fontheight,COLOR_WARNINGBG); reui_printf(re->ui,0,0,COLOR_WARNINGFG,"%s %s",re->command,re->commandbuf); } else if(strcmp(re->command,COMMAND_QUESTION)==0) { question_t *q; reui_fill(re->ui,0,0,re->ui->w,re->ui->fontheight,COLOR_QUERYBG); #warning XXX TODO: suppont arbitrary number of options, highlight current option q=re->question; re->commandbuf[sizeof(re->commandbuf)-1]='\0'; reui_printf(re->ui,0,0,COLOR_QUERYFG,"%s %s: %s %s%s%s%s%s%s%s%s: %s" ,re->command ,(q->titleshort!=NULL)?q->titleshort:q->title ,(q->bodyshort!=NULL)?q->bodyshort:q->body ,(q->nopts>0)?"1.":"",(q->nopts>0)?(q->optsshort!=NULL)?q->optsshort[0]:q->opts[0]:"" ,(q->nopts>1)?" 2.":"",(q->nopts>1)?(q->optsshort!=NULL)?q->optsshort[1]:q->opts[1]:"" ,(q->nopts>2)?" 3.":"",(q->nopts>2)?(q->optsshort!=NULL)?q->optsshort[2]:q->opts[2]:"" ,(q->nopts>3)?" 4.":"",(q->nopts>4)?(q->optsshort!=NULL)?q->optsshort[3]:q->opts[3]:"" ,re->commandbuf); } else { reui_fill(re->ui,0,0,re->ui->w,re->ui->fontheight,COLOR_QUERYBG); re->commandbuf[sizeof(re->commandbuf)-1]='\0'; reui_printf(re->ui,0,0,COLOR_QUERYFG,"%s %s",re->command,re->commandbuf); } re->headerdirty=0; re->ui->rendererdirty=1; return(0); } int re_drawcontents(re_t *re) { long pos,newpos; char *ptr; int len; int y,row; char *curptr; int curptrlen; int has_nl; int is_continuation; int tmpcol,availcol; int in_error; long realstart,realend; int drawn_cursor; if(re==NULL) return(-1); reui_fill(re->ui,re->x,re->y,re->w,re->h,"\xdf\xdf\xdf\xff"); row=re->curline-re->originline; pos=re->cursorpos; while(row>0 && pos>0) { if(redata_line_rawinfo(re->data,pos,&newpos,NULL,NULL,&is_continuation)==-1) return(-1); pos=(newpos>0)?newpos-1:0; if(!is_continuation) row--; } /* highlight current line */ reui_fill(re->ui,re->x,re->y+(re->curline-re->originline)*re->ui->fontheight,re->w,re->ui->fontheight+1,"\xef\xef\xef\xff"); /* draw the lines */ drawn_cursor=0; for(y=re->y;y<(re->y+re->h);y+=re->ui->fontheight,row++) { if(redata_line_realstart(re->data,pos,&realstart)==-1 || redata_line_realend(re->data,pos,&realend)==-1) { break; /* couldn't get real start/end */ } in_error=0; for(tmpcol=0,pos=realstart,availcol=0;tmpcol<(re->origincol+re->maxcol) && pos<=realend;pos=newpos+len,tmpcol+=availcol) { if(redata_line_rawinfo(re->data,pos,&newpos,&ptr,&len,&is_continuation)==-1) { in_error=1; break; /* couldn't get this line part info */ } has_nl=((len>0 && ptr[len-1]=='\n')?1:0); availcol=redata_generic_utf8len(ptr,len-has_nl); #warning TODO: consider tabs reui_write(re->ui,re->x+(tmpcol-re->origincol)*re->ui->fontwidth,y,"\x00\x00\x00\xff",ptr,len-has_nl); if(row==(re->curline-re->originline)) { if(re->curcol>=tmpcol && re->curcol<(tmpcol+availcol)) { reui_fill(re->ui,re->x+re->ui->fontwidth*(re->curcol-re->origincol),y,re->ui->fontwidth,re->ui->fontheight+1,"\x00\x00\x00\xff"); drawn_cursor=1; curptr=redata_generic_utf8col(ptr,len-has_nl,re->curcol-tmpcol); curptrlen=(curptr==NULL)?0:(len-has_nl)-(curptr-ptr); reui_write(re->ui,re->x+re->ui->fontwidth*(re->curcol-re->origincol),y,"\xff\xff\xff\xff",curptr,redata_generic_utf8charlen(curptr,curptrlen)); } #warning TODO: if it is one of '[','{','<','>','}',']', highlight the matching bracket/parens/anglebracket. } } if(row==(re->curline-re->originline) && !drawn_cursor) reui_fill(re->ui,re->x+re->ui->fontwidth*(re->curcol-re->origincol),y,re->ui->fontwidth,re->ui->fontheight+1,"\x00\x00\x00\xff"); if(in_error) break; pos=realend+1; /*LONG LINE LEFT FOR DEBUGGING PURPOSES: reui_write(re->ui,re->x+re->ui->fontwidth*(re->curcol-re->origincol),y,"\xff\xff\xff\xff",curptr,redata_generic_utf8charlen(ptr,curptrlen));*/ } re->contentsdirty=0; re->ui->rendererdirty=1; return(0); }