/* * 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 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_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; if((ssel=sselect_init())==NULL) { fprintf(stderr,"ERROR: filename too long.\n"); re_free(re),re=NULL; return(2); } reui_scr2renderer(re->ui,0,0,re->ui->w,re->ui->h); re_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 { 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; } } if(re->headerdirty) { if(re->command==NULL) { #if 0 fprintf(stderr,"REDRAW Header (editing)\n"); #endif re_drawheader_editing(re); } 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'; } } } 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) { re->headerdirty=1; re->contentsdirty=1; re->ui->rendererdirty=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; re->ui->rendererdirty=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; if(re->curline>=(re->originline+re->maxrow)) { re->originline=re->curline-re->maxrow; 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_setfilename(re_t *re, char *filename) { if(re==NULL || filename==NULL) return(-1); strncpy(re->filename,filename,sizeof(re->filename)); re->filename[sizeof(re->filename)-1]='\0'; if(strcmp(filename,re->filename)!=0) return(-1); /* filename too long */ return(0); } int re_processkey_editing(re_t *re, SDL_Event *event) { SDL_Event fakeevent; if(re==NULL || event==NULL) return(-1); /* sanity check failed */ /* convert RETURN KEYDOWN to TEXTINPUT */ #warning XXX TODO: If the "\n" of end-of-file is deleted using DEL, the editor bugs if(event->type==SDL_KEYDOWN && event->key.keysym.sym==SDLK_RETURN) { memset(&fakeevent,0,sizeof(SDL_Event)); event=&fakeevent; event->type=SDL_TEXTINPUT; strncpy(event->text.text,"\n",sizeof(event->text.text)); event->text.text[sizeof(event->text.text)-1]='\0'; } else if(event->type==SDL_KEYDOWN && event->key.keysym.sym==SDLK_TAB) { static char spaces[8]={" "}; memset(&fakeevent,0,sizeof(SDL_Event)); event=&fakeevent; event->type=SDL_TEXTINPUT; strncpy(event->text.text,spaces+(re->curcol%8),sizeof(event->text.text)); event->text.text[sizeof(event->text.text)-1]='\0'; } else if(event->type==SDL_KEYDOWN && event->key.keysym.sym==SDLK_DELETE) { #if 1 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; 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 1 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 1 fprintf(stderr,"SDL_TEXTINPUT:\"%s\"\n",event->text.text); #endif if(redata_line_realend(re->data,re->cursorpos,&realend)==-1) return(-1); /* couldn't get current line info */ at_end=(re->cursorpos==realend)?1:0; 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 1 fprintf(stderr,"SDL_KEYDOWN: sym:%i\n",event->key.keysym.sym); #endif if(event->key.keysym.sym==SDLK_DOWN || event->key.keysym.sym==SDLK_UP) { re_moveupdown(re,(event->key.keysym.sym==SDLK_UP)?-1:1); } else if(event->key.keysym.sym==SDLK_LEFT || event->key.keysym.sym==SDLK_RIGHT) { re_moveleftright(re,(event->key.keysym.sym==SDLK_LEFT)?-1:1); } else if(event->key.keysym.sym==SDLK_PAGEDOWN || event->key.keysym.sym==SDLK_PAGEUP) { re_moveupdown(re,(event->key.keysym.sym==SDLK_PAGEUP)?-(re->maxrow):re->maxrow); } else if(event->key.keysym.sym==SDLK_HOME || event->key.keysym.sym==SDLK_END) { long newpos; if((event->key.keysym.sym==SDLK_HOME && redata_line_realstart(re->data,re->cursorpos,&newpos)==-1) || (event->key.keysym.sym==SDLK_END && redata_line_realend(re->data,re->cursorpos,&newpos)==-1)) { return(-1); /* couldn't get destination pos data */ } re->cursorpos=newpos; if(redata_pos2linecol(re->data,re->cursorpos,NULL,&(re->curcol))==-1) return(-1); /* couldn't get col of current pos */; if(re->curcol<re->origincol) { re->origincol=re->curcol; re->origincol=(re->origincol<0)?0:re->origincol; } if(re->curcol>(re->origincol+re->maxcol-COLFORCESCROLL)) { re->origincol=re->curcol+COLFORCESCROLL-re->maxcol; re->origincol=(re->origincol<0)?0:re->origincol; } re->headerdirty=1; re->contentsdirty=1; } else if(event->key.keysym.sym==SDLK_BACKSPACE && re->cursorpos>0) { int line,col; long startpos,newpos,realstart,start; char *ptr; int len; int trimmed; int doneinc; #if 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)); 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; 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; if(re->curline<(re->originline+LINEFORCESCROLL)) { re->originline-=LINEFORCESCROLL; re->originline=(re->originline<0)?0:re->originline; } if(re->curline>(re->originline+re->maxrow-LINEFORCESCROLL)) re->originline+=LINEFORCESCROLL; #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++) ; fprintf(stderr,"Trying to DELETE SPACES del %i bytes\n",n); redata_op_del(re->data,curpos-n,n,NULL); re->cursorpos-=n; if(trimmed!=NULL) *trimmed=n; } return(0); } int re_drawheader_editing(re_t *re) { int line,col; if(re==NULL) return(-1); if(redata_pos2linecol(re->data,re->cursorpos,&line,&col)==-1) line=col=-1; reui_fill(re->ui,0,0,re->ui->w,re->ui->fontheight,COLOR_STATUSBG); /* for the user, lines start at 0 (internally they start at 0) */ reui_printf(re->ui,0,0,COLOR_STATUSFG,"File: %s Line:%i (%i) Col:%i (%i) Pos:%li",re->filename,re->curline+1,line+1,re->curcol+1,col+1,re->cursorpos); re->headerdirty=0; re->ui->rendererdirty=1; return(0); } int re_drawheader_command(re_t *re) { if(re==NULL) return(-1); if(re->command==NULL) return(-1); if(re->command[0]=='\0') { reui_fill(re->ui,0,0,re->ui->w,re->ui->fontheight,COLOR_QUERYBG); reui_printf(re->ui,0,0,COLOR_QUERYFG,"Command:"); } else if(strcmp(re->command,COMMAND_WARNING)==0) { reui_fill(re->ui,0,0,re->ui->w,re->ui->fontheight,COLOR_WARNINGBG); reui_printf(re->ui,0,0,COLOR_WARNINGFG,"%s %s",re->command,re->commandbuf); } else 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); }