/* * 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_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 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" #define COLOR_STATUSBG "\x14\x3a\xaf\xff" #define COLOR_STATUSFG "\xe6\xdc\x5d\xff" #define COLOR_QUERYBG "\xad\x92\x5e\xff" #define COLOR_QUERYFG "\xd0\xef\x4f\xff" #define COLOR_WARNINGBG "\xba\x07\x07\xff" #define COLOR_WARNINGFG "\xe6\xdc\x5d\xff" #define COLOR_INFOBG "\x4e\x8a\x4e\xff" #define COLOR_INFOFG "\xee\xee\x46\xff" typedef struct re_t { redata_t *data; reui_t *ui; int flag_newfile; 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 selactive; int sellinefrom,sellineto; int selcolfrom,selcolto; long sizeselectbuf; long usedselectbuf; char *selectbuf; char *command; char commandbuf[COMMANDBUFSIZE]; char cachelastsearch[COMMANDBUFSIZE]; char cachelastreplacewith[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); static int mystricmp(const char *s1, const char *s2); 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_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_rtrim(re_t *re, long curpos, int *trimmed); long re_getmatchingbracket(re_t *re,long posini, char originalchar, char *matchingchar); int re_drawheader_editing(re_t *re); int re_drawheader_command(re_t *re); int re_drawcontents(re_t *re); 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; 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 || 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) 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: if(redata_needs_saving(re->data)) { re->command=COMMAND_CONFIRMEXIT; re->commandbuf[0]='\0'; re->headerdirty=1; } else { do_exit=1; } break; case SDL_KEYDOWN: case SDL_TEXTINPUT: case SDL_TEXTEDITING: 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; 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(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, redata_highlighter_register, redata_prototypes_register, NULL))==NULL) { re_free(re),re=NULL; return(NULL); /* insuf. mem. */ } re->fontheightpercent=100; if((re->ui=reui_init(DEFAULTFONTHEIGHT*re->fontheightpercent/100))==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; if(re->selectbuf!=NULL) free(re->selectbuf),re->selectbuf=NULL,re->usedselectbuf=re->sizeselectbuf=0; free(re),re=NULL; return; } int re_setuidata(re_t *re) { if(re==NULL) return(-1); re->x=0,re->y=re->ui->fontheight,re->w=re->ui->w,re->h=re->ui->h-re->y; re->maxrow=re->h/re->ui->fontheight-1; re->maxcol=re->w/re->ui->fontwidth-1; re_fixorigin(re); return(0); } int re_setfilename(re_t *re, char *filename) { if(re==NULL || filename==NULL) return(-1); strncpy(re->filename,filename,sizeof(re->filename)); re->filename[sizeof(re->filename)-1]='\0'; if(strcmp(filename,re->filename)!=0) return(-1); /* filename too long */ return(0); } int re_fixorigin(re_t *re) { if(re==NULL) return(-1); if(re->curline<(re->originline+LINEFORCESCROLL)) { re->originline=re->curline-LINEFORCESCROLL; re->originline=(re->originline<0)?0:re->originline; } if(re->curline>(re->originline+re->maxrow-LINEFORCESCROLL)) { re->originline=re->curline+LINEFORCESCROLL-re->maxrow; re->originline=(re->originline<0)?0:(re->originline>re->curline)?re->curline:re->originline; } if(re->curcol<re->origincol) { re->origincol=re->curcol; re->origincol=(re->origincol<0)?0:re->origincol; } if(re->curcol>=(re->origincol+re->maxcol-COLFORCESCROLL)) { 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) { 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; long cursorpos; if(redata_linecol2pos(re->data,re->curline,re->curcol,&cursorpos,NULL)!=0 || cursorpos==redata_getused(re->data)) { return(0); /* already at end, nothing to delete */ } if(redata_line_realend(re->data,cursorpos,&realend)==-1) return(-1); /* couldn't get current line info */ at_end=(cursorpos==realend)?1:0; if(at_end && (cursorpos+1)==redata_getused(re->data)) return(-1); /* we shouldn't erase the final '\n' */ memset(&fakeevent,0,sizeof(SDL_Event)); event=&fakeevent; event->type=SDL_KEYDOWN; event->key.keysym.sym=SDLK_BACKSPACE; redata_undo_groupinit(re->data,NULL); if(at_end) { int col; if(redata_pos2linecol(re->data,cursorpos,NULL,&col)==-1) return(-1); /* couldn't get line info */ if(redata_op_addn(re->data,cursorpos,' ',re->curcol-col,NULL)!=0) return(-1); /* couldn't add spaces up to current displayed pos */ re->curline++; re->curcol=0; } else { re_moveleftright(re,1); } #if 0 fprintf(stderr,"SDL_KEYDOWN: fake setup ok\n"); #endif } /* special case: text editing event */ if(event->type==SDL_TEXTINPUT) { int len; long realend; int at_end; int nspaces; long cursorpos; 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_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 */ } at_end=(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,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; } len=strlen(event->text.text); if(redata_op_add(re->data,cursorpos,event->text.text,len,NULL)!=0) return(-1); /* couldn't add requested text */ cursorpos+=len; if(event->text.text[0]=='\n' && event->text.text[1]=='\0') { int trimmed; if(re_rtrim(re,cursorpos-len,&trimmed)) cursorpos-=trimmed; #if 1 #if 0 fprintf(stderr,"SDL_TEXTINPUT: Insering newline\n"); #endif #endif 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((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 oldcol=re->curcol,oldline=re->curline; if(re_moveupdown(re,(event->key.keysym.sym==SDLK_UP)?-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_UP)?-1: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; 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); 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; long cursorpos; 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 */; 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,newpos,realstart,start; char *ptr; int len; int trimmed; int doneinc; long cursorpos; 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 */ 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,newpos=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,cursorpos-newpos,NULL); 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=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,cursorpos-delpos,NULL); cursorpos=delpos; } redata_pos2linecol(re->data,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,cursorpos,&trimmed)) 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) { re->command=""; re->command_first_key='q'; re->headerdirty=1; } else if(event->key.keysym.sym==SDLK_F2 || (event->key.keysym.sym==SDLK_s && (SDL_GetModState()&KMOD_CTRL)!=0)) { char *errormsg=NULL; if(redata_save(re->data,re->filename,&errormsg)!=-1) errormsg="File saved"; re->command=COMMAND_INFO; snprintf(re->commandbuf,sizeof(re->commandbuf),errormsg); re->commandbuf[sizeof(re->commandbuf)-1]='\0'; re->headerdirty=1; } else if(event->key.keysym.sym==SDLK_k && (SDL_GetModState()&KMOD_CTRL)!=0) { re->command=""; re->command_first_key='k'; re->headerdirty=1; } else if(event->key.keysym.sym==SDLK_z && (SDL_GetModState()&KMOD_CTRL)!=0) { long newpos; if(redata_op_undo(re->data,&newpos)) return(-1); redata_pos2linecol(re->data,newpos,&(re->curline),&(re->curcol)); re_fixorigin(re); re->headerdirty=1; re->contentsdirty=1; } else if(event->key.keysym.sym==SDLK_l && (SDL_GetModState()&KMOD_CTRL)!=0 && re->cachelastsearch[0]!='\0') { long newpos; long cursorpos; if(redata_linecol2pos(re->data,re->curline,re->curcol,&cursorpos,NULL)!=0) return(-1); /* error obtaining position */ /* search next (forward) */ if((newpos=redata_searchforward(re->data,cursorpos+1,re->cachelastsearch,strlen(re->cachelastsearch)))==-1) { re->command=COMMAND_WARNING; snprintf(re->commandbuf,sizeof(re->commandbuf),"String not found"); re->commandbuf[sizeof(re->commandbuf)-1]='\0'; re->headerdirty=1; return(-1); } cursorpos=newpos; redata_pos2linecol(re->data,cursorpos,&(re->curline),&(re->curcol)); re_fixorigin_center(re); re->headerdirty=1; re->contentsdirty=1; } else if(event->key.keysym.sym==SDLK_PLUS && (SDL_GetModState()&KMOD_CTRL)!=0) { re_changefontsize(re, 1); re->ignorenkeys++; } else if(event->key.keysym.sym==SDLK_MINUS && (SDL_GetModState()&KMOD_CTRL)!=0) { re_changefontsize(re, -1); re->ignorenkeys++; } else if(event->key.keysym.sym==SDLK_0 && (SDL_GetModState()&KMOD_CTRL)!=0) { re_changefontsize(re, 0); re->ignorenkeys++; } else if((event->key.keysym.sym==SDLK_c || event->key.keysym.sym==SDLK_x) && (SDL_GetModState()&KMOD_CTRL)!=0) { long frompos,topos; int coldone; int is_cut=(event->key.keysym.sym==SDLK_x)?1:0; /* Mac-HIG/OpenLook-style copy (ctrl+c) / cut (ctrl+x) */ if(re->selactive && redata_linecol2pos(re->data,re->sellinefrom,re->selcolfrom,&frompos,NULL)==0 && redata_linecol2pos(re->data,re->sellineto,re->selcolto,&topos,&coldone)==0) { re_selectbuf_fill(re,frompos,topos-frompos,re->selcolto-coldone); SDL_SetClipboardText(re->selectbuf); if(is_cut) { redata_undo_groupinit(re->data,NULL); redata_op_del(re->data,frompos,topos-frompos,NULL); redata_undo_groupcommit(re->data,NULL); redata_pos2linecol(re->data,frompos,&(re->curline),&(re->curcol)); re_fixorigin(re); re->headerdirty=1; } re_sel_toggle(re); re->contentsdirty=1; } re->ignorenkeys++; } else if(event->key.keysym.sym==SDLK_v && (SDL_GetModState()&KMOD_CTRL)!=0) { long cursorpos; /* Mac-HIG/OpenLook-style paste (ctrl+v)*/ if(redata_linecol2pos(re->data,re->curline,re->curcol,&cursorpos,NULL)!=0) return(-1); /* error obtaining position */ if(re->selactive) re_sel_toggle(re); if(SDL_HasClipboardText()) re_selectbuf_replace(re,SDL_GetClipboardText()); if(re->usedselectbuf>0) { redata_undo_groupinit(re->data,NULL); redata_op_add(re->data,cursorpos,re->selectbuf,re->usedselectbuf,NULL); redata_undo_groupcommit(re->data,NULL); cursorpos+=re->usedselectbuf; redata_pos2linecol(re->data,cursorpos,&(re->curline),&(re->curcol)); re_fixorigin(re); re->headerdirty=1; re->contentsdirty=1; } re->ignorenkeys++; } 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=='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->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->headerdirty=1; } else if(re->command_first_key=='k' && event->key.keysym.sym==SDLK_q) { if(redata_needs_saving(re->data)) { re->command=COMMAND_CONFIRMEXIT; re->commandbuf[0]='\0'; } else { re->command=COMMAND_EXIT; re->commandbuf[0]='\0'; } re->headerdirty=1; } else if(re->command_first_key=='k' && event->key.keysym.sym==SDLK_h) { re_sel_toggle(re); re->command=NULL; re->headerdirty=1; } else if(re->command_first_key=='k' && event->key.keysym.sym==SDLK_b) { re_sel_setstart(re,re->curline,re->curcol); re->command=NULL; re->headerdirty=1; } else if(re->command_first_key=='k' && event->key.keysym.sym==SDLK_k) { re_sel_setend(re,re->curline,re->curcol); re->command=NULL; re->headerdirty=1; } else if(re->command_first_key=='k' && event->key.keysym.sym==SDLK_y) { long frompos,topos; int coldone; long cursorpos; /* wordstar-style blockdel (ctrl+k+y) */ if(redata_linecol2pos(re->data,re->curline,re->curcol,&cursorpos,NULL)!=0) return(-1); /* error obtaining position */ if(re->selactive && redata_linecol2pos(re->data,re->sellinefrom,re->selcolfrom,&frompos,NULL)==0 && redata_linecol2pos(re->data,re->sellineto,re->selcolto,&topos,&coldone)==0) { re_selectbuf_fill(re,frompos,topos-frompos,re->selcolto-coldone); redata_undo_groupinit(re->data,NULL); redata_op_del(re->data,frompos,topos-frompos,NULL); redata_undo_groupcommit(re->data,NULL); if(cursorpos>frompos) { cursorpos=frompos; redata_pos2linecol(re->data,cursorpos,&(re->curline),&(re->curcol)); re_fixorigin(re); } re->headerdirty=1; re->contentsdirty=1; } if(re->selactive) re_sel_toggle(re); re->command=NULL; re->headerdirty=1; } else if(re->command_first_key=='k' && (event->key.keysym.sym==SDLK_c || event->key.keysym.sym==SDLK_v)) { long frompos,topos,insertpos; int tocoldone,coldone; /* wordstar-style blockcopy (ctrl+k+c) or move (ctrl+k+v)*/ int is_move=(event->key.keysym.sym==SDLK_v)?1:0; if(re->selactive && redata_linecol2pos(re->data,re->sellinefrom,re->selcolfrom,&frompos,NULL)==0 && redata_linecol2pos(re->data,re->sellineto,re->selcolto,&topos,&tocoldone)==0 && redata_linecol2pos(re->data,re->curline,re->curcol,&insertpos,&coldone)==0 && re_selectbuf_fill(re,frompos,topos-frompos,re->selcolto-tocoldone)==0 && redata_preallocate(re->data,redata_getused(re->data)+(re->curcol-coldone)+(is_move?0:re->usedselectbuf))==0) { if(is_move && insertpos>=frompos && insertpos<topos) { re->curline=re->sellinefrom; re->curcol=re->selcolfrom; redata_linecol2pos(re->data,re->curline,re->curcol,&insertpos,&coldone); } redata_undo_groupinit(re->data,NULL); if(coldone<re->curcol && redata_op_addn(re->data,insertpos,' ',re->curcol-coldone,NULL)==0) { insertpos+=re->curcol-coldone; } if(is_move) { long cursorpos; redata_op_del(re->data,frompos,re->usedselectbuf,NULL); redata_op_add(re->data,insertpos-((insertpos>frompos)?re->usedselectbuf:0),re->selectbuf,re->usedselectbuf,NULL); cursorpos=insertpos-((insertpos>frompos)?re->usedselectbuf:0)+re->usedselectbuf; redata_pos2linecol(re->data,cursorpos,&(re->curline),&(re->curcol)); redata_pos2linecol(re->data,cursorpos-re->usedselectbuf,&(re->sellinefrom),&(re->selcolfrom)); re->sellineto=re->curline; re->selcolto=re->curcol; } else { long cursorpos; redata_op_add(re->data,insertpos,re->selectbuf,re->usedselectbuf,NULL); cursorpos=insertpos+re->usedselectbuf; redata_pos2linecol(re->data,cursorpos,&(re->curline),&(re->curcol)); redata_pos2linecol(re->data,insertpos,&(re->sellinefrom),&(re->selcolfrom)); re->sellineto=re->curline; re->selcolto=re->curcol; } redata_undo_groupcommit(re->data,NULL); re_fixorigin(re); re->headerdirty=1; re->contentsdirty=1; } re->command=NULL; re->headerdirty=1; } else { re->command=NULL; re->headerdirty=1; } if(!(SDL_GetModState()&KMOD_CTRL)) re->ignorenkeys=1; /* control was not pressed, we will receive again this key as SDL_TEXTINPUT */ return(0); } int re_processkey_commanddata(re_t *re, SDL_Event *event) { 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_CONFIRMEXIT)==0) { if(strcmp(re->commandbuf,"y")==0) { re->command=COMMAND_EXIT; re->commandbuf[0]='\0'; re->headerdirty=1; return(0); } re->headerdirty=1; } else if(strcmp(re->command,COMMAND_GOTOLINE)==0) { int line; long pos; line=atoi(re->commandbuf)-1; line=(line<0)?0:line; if(redata_linecol2pos(re->data,line,re->curcol,&pos,NULL)==-1) { re->command=COMMAND_WARNING; snprintf(re->commandbuf,sizeof(re->commandbuf),"Unknown line"); re->commandbuf[sizeof(re->commandbuf)-1]='\0'; re->headerdirty=1; return(-1); } re->curline=line; /* position the line in the center of viewport */ re->originline=line-(re->maxrow/2); re->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->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->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); } 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[]={30,50,67,80,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(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); } } 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) { int nchunk,off,avail; long n; if(re==NULL || size<0 || nadditionalspaces<0 || (frompos+size)>redata_getused(re->data) || redata_getposptr(re->data,frompos,&nchunk,&off)!=0) 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. */ } for(n=0;n<size && nchunk<re->data->sizechunks;nchunk++,off=0) { avail=re->data->chunks[nchunk]->useddata-off; if(avail<=0) continue; if(avail>(size-n)) avail=size-n; memcpy(re->selectbuf+n,re->data->chunks[nchunk]->data+off,avail); n+=avail; } re->usedselectbuf=n; 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_rtrim(re_t *re, long curpos, int *trimmed) { long startpos; char *start; int len; int n; if(re==NULL || curpos<0 || curpos>redata_getused(re->data)) return(-1); if(trimmed!=NULL) *trimmed=0; if(redata_line_rawinfo(re->data,curpos,&startpos,&start,&len,NULL)==0 && len>1 && start[len-1]=='\n' && start[len-2]==' ' && curpos==(startpos+len-1)) { /* count the number of spaces at end of line */ n=0; while((len-1-n-1)>=0 && start[len-1-n-1]==' ') n++; /* delete the counted number of spaces at end of line */ redata_op_del(re->data,startpos+len-1-n,n,NULL); if(trimmed!=NULL) *trimmed=n; } return(0); } long re_getmatchingbracket(re_t *re,long posini, char originalchar, char *matchingchar) { char *ori,*dest; char *pairs="[]{}<>()"; int is_backwards; int colorindex,newcolor; long realstart; int line; long pos; long posnextori,posnextdest; long posnext; int counter; if(re==NULL) return(-1); /* sanity check failed */ if((ori=strchr(pairs,originalchar))==NULL) return(-1); /* unknown char */ is_backwards=(ori-pairs)%2; dest=ori+((is_backwards)?-1:1); if(redata_pos2linecol(re->data,posini,&line,NULL)==-1 || redata_line_realstart(re->data,posini,&realstart)==-1) { return(-1); /* couldn't get line number or startpos */ } colorindex=redata_highlighter_getcolorindex(re->data,line,posini-realstart); pos=posini; counter=1; while(1) { /* get the next pos */ if(!is_backwards) { posnextori=redata_searchforward(re->data,pos+1,ori,1); posnextdest=redata_searchforward(re->data,pos+1,dest,1); posnext=(posnextori==-1)?posnextdest:(posnextdest==-1)?posnextori:(posnextori<posnextdest)?posnextori:posnextdest; } else { posnextori=redata_searchbackwards(re->data,pos-1,ori,1); posnextdest=redata_searchbackwards(re->data,pos-1,dest,1); posnext=(posnextori==-1)?posnextdest:(posnextdest==-1)?posnextori:(posnextori>posnextdest)?posnextori:posnextdest; } if(posnext==-1) break; /* search ended and couln't get the counter to zero */ /* check that the color index is ok */ if(redata_pos2linecol(re->data,posnext,&line,NULL)==-1 || redata_line_realstart(re->data,posnext,&realstart)==-1) { return(-1); /* couldn't get line number or startpos */ } newcolor=redata_highlighter_getcolorindex(re->data,line,posnext-realstart); if(colorindex!=newcolor) { pos=posnext; continue; /* it doesn't have the same color */ } /* do the math */ if(posnext==posnextori) counter++; if(posnext==posnextdest) counter--; pos=posnext; if(counter==0) { if(matchingchar!=NULL) *matchingchar=*dest; return(posnext); /* found matching bracket */ } } return(-1); } int re_drawheader_editing(re_t *re) { long cursorpos; 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,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 Col:%i Pos:%li Size:%li %s" ,re->filename,re->curline+1,re->curcol+1,cursorpos,redata_getused(re->data) ,""); #warning #error MAKE THE PREVIOUS LINE LAST %s print: "CHANGED" when unsaved¬-in-reu, "changed" when unsaved but is in reu, "saved" when it is saved. re->headerdirty=0; re->ui->rendererdirty=1; return(0); } int re_drawheader_command(re_t *re) { if(re==NULL) return(-1); if(re->command==NULL) return(-1); if(re->command[0]=='\0') { reui_fill(re->ui,0,0,re->ui->w,re->ui->fontheight,COLOR_QUERYBG); reui_printf(re->ui,0,0,COLOR_QUERYFG,"Command:"); } else if(strcmp(re->command,COMMAND_WARNING)==0 || strcmp(re->command,COMMAND_CONFIRMEXIT)==0) { reui_fill(re->ui,0,0,re->ui->w,re->ui->fontheight,COLOR_WARNINGBG); reui_printf(re->ui,0,0,COLOR_WARNINGFG,"%s %s",re->command,re->commandbuf); } else if(strcmp(re->command,COMMAND_INFO)==0) { reui_fill(re->ui,0,0,re->ui->w,re->ui->fontheight,COLOR_INFOBG); reui_printf(re->ui,0,0,COLOR_INFOFG,"%s %s",re->command,re->commandbuf); } else if(strcmp(re->command,COMMAND_QUESTION)==0) { question_t *q; reui_fill(re->ui,0,0,re->ui->w,re->ui->fontheight,COLOR_QUERYBG); #warning XXX TODO: suppont arbitrary number of options, highlight current option q=re->question; re->commandbuf[sizeof(re->commandbuf)-1]='\0'; reui_printf(re->ui,0,0,COLOR_QUERYFG,"%s %s: %s %s%s%s%s%s%s%s%s: %s" ,re->command ,(q->titleshort!=NULL)?q->titleshort:q->title ,(q->bodyshort!=NULL)?q->bodyshort:q->body ,(q->nopts>0)?"1.":"",(q->nopts>0)?(q->optsshort!=NULL)?q->optsshort[0]:q->opts[0]:"" ,(q->nopts>1)?" 2.":"",(q->nopts>1)?(q->optsshort!=NULL)?q->optsshort[1]:q->opts[1]:"" ,(q->nopts>2)?" 3.":"",(q->nopts>2)?(q->optsshort!=NULL)?q->optsshort[2]:q->opts[2]:"" ,(q->nopts>3)?" 4.":"",(q->nopts>4)?(q->optsshort!=NULL)?q->optsshort[3]:q->opts[3]:"" ,re->commandbuf); } else { 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,tmprow; char *curptr; int curptrlen; int has_nl; int is_continuation; int tmpcol,availcol; int in_error; long realstart,realend; int drawn_cursor; hcolor_t *colors; int ncolors; linecolor_t *linecolors; int nlinecolors; int matchingpos; char matchingchar; int mline,mcol; long cursorpos; const char *hint; const char selcolornormal[]={"\xde\xcf\x7f\xff"}; const char selcolorcurline[]={"\xf0\xea\xc9\xff"}; if(re==NULL) return(-1); if(redata_linecol2pos(re->data,re->curline,re->curcol,&cursorpos,NULL)!=0) return(0); /* error obtaining position */ #if 0 fprintf(stderr,"re_drawcontents: pre reuifill1\n"); #endif reui_fill(re->ui,re->x,re->y,re->w,re->h,"\xdf\xdf\xdf\xff"); #if 0 fprintf(stderr,"re_drawcontents: post reuifill1\n"); #endif row=re->curline-re->originline; pos=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 */ #if 0 fprintf(stderr,"re_drawcontents: pre reuifill2\n"); #endif 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"); #if 0 fprintf(stderr,"re_drawcontents: post reuifill2\n"); #endif /* highlight the selection */ if(re->selactive) { const char *selcolor; tmprow=row; #if 0 fprintf(stderr,"re_drawcontents: reuifill3: "); #endif for(y=re->y;y<(re->y+re->h);y+=re->ui->fontheight,row++) { selcolor=(row==(re->curline-re->originline))?selcolorcurline:selcolornormal; if((re->originline+row)==re->sellinefrom && (re->originline+row)==re->sellineto) { reui_fill(re->ui,re->x+(re->selcolfrom-re->origincol)*re->ui->fontwidth,re->y+row*re->ui->fontheight,(re->selcolto-re->selcolfrom)*re->ui->fontwidth,re->ui->fontheight+1,selcolor); } else if((re->originline+row)==re->sellinefrom) { int x1; x1=re->x+(re->selcolfrom-re->origincol)*re->ui->fontwidth; if(x1<(re->x+re->w)) reui_fill(re->ui,x1,re->y+row*re->ui->fontheight,re->w-x1,re->ui->fontheight+1,selcolor); } else if((re->originline+row)>re->sellinefrom && (re->originline+row)<re->sellineto) { #if 0 fprintf(stderr,"{(ui,%i,%i,%i,%i,#%02X%02X%02X%02X)",re->x,re->y+row*re->ui->fontheight,re->w,re->ui->fontheight+1,((unsigned char *)selcolor)[0],((unsigned char *)selcolor)[1],((unsigned char *)selcolor)[2],((unsigned char *)selcolor)[3]); #endif reui_fill(re->ui,re->x,re->y+row*re->ui->fontheight,re->w,re->ui->fontheight+1,selcolor); #if 0 fprintf(stderr,"}"); #endif } else if((re->originline+row)==re->sellineto) { int x2; x2=re->x+(re->selcolto-re->origincol)*re->ui->fontwidth; if(x2>(re->x)) reui_fill(re->ui,re->x,re->y+row*re->ui->fontheight,x2-re->x,re->ui->fontheight+1,selcolor); } } row=tmprow; #if 0 fprintf(stderr,"\n"); #endif } /* draw the lines */ drawn_cursor=0; colors=redata_highlighter_getcolors(re->data,&ncolors); matchingpos=-1; matchingchar='\0'; for(y=re->y;y<(re->y+re->h);y+=re->ui->fontheight,row++) { /* definition of vars for tracking linecolor usage */ int curlinecolor; /* current linecolor */ int usedlenlinecolor; /* number of bytes of current linecolor already drawn */ /* end of definitions */ 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; linecolors=(colors==NULL)?NULL:redata_highlighter_getline(re->data,re->originline+row,&nlinecolors); curlinecolor=0; usedlenlinecolor=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 if(linecolors!=NULL) { int used,usedcol; /* number of bytes/columns used of redata chunk (those are already drawn) */ used=usedcol=0; /* while the avail text is larger than the linecolor len */ while(curlinecolor<nlinecolors && (len-has_nl-used)>=(linecolors[curlinecolor].len-usedlenlinecolor)) { reui_write(re->ui,re->x+(tmpcol-re->origincol+usedcol)*re->ui->fontwidth,y,colors[linecolors[curlinecolor].color].rgba,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(curlinecolor<nlinecolors && (len-has_nl-used)>0 && (linecolors[curlinecolor].len-usedlenlinecolor)>(len-has_nl-used)) { reui_write(re->ui,re->x+(tmpcol-re->origincol+usedcol)*re->ui->fontwidth,y,colors[linecolors[curlinecolor].color].rgba,ptr+used,(len-has_nl-used)); usedcol+=redata_generic_utf8len(ptr+used,(len-has_nl-used)); usedlenlinecolor+=(len-has_nl-used); used+=(len-has_nl-used); } } else { 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)) { int utf8charlen; /* draw 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"); drawn_cursor=1; curptr=redata_generic_utf8col(ptr,len-has_nl,re->curcol-tmpcol); curptrlen=(curptr==NULL)?0:(len-has_nl)-(curptr-ptr); utf8charlen=redata_generic_utf8charlen(curptr,curptrlen); reui_write(re->ui,re->x+re->ui->fontwidth*(re->curcol-re->origincol),y,"\xff\xff\xff\xff",curptr,utf8charlen); /* get matching braces/parens/anglebracket/curlybraces for highlighting */ if(utf8charlen==1 && strchr("[]{}<>()",*curptr)!=NULL) matchingpos=re_getmatchingbracket(re,cursorpos,*curptr,&matchingchar); } } } 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));*/ } /* highlight matching parens/brace/... if applicable */ if(matchingpos!=-1 && redata_pos2linecol(re->data,matchingpos,&mline,&mcol)!=-1) { int x,y; char *fg="\xff\x00\x00\x80"; char *bg="\xff\xff\xff\xaf"; x=re->x+(mcol-re->origincol)*re->ui->fontwidth+(re->ui->fontwidth/2); x=(x<re->x)?re->x:(x>=(re->x+re->w))?(re->x+re->w-1):x; y=re->y+(mline-re->originline)*re->ui->fontheight+(re->ui->fontheight/2); y=(y<re->y)?re->y:(y>=(re->y+re->h))?(re->y+re->h-1):y; if(mline<re->originline) reui_balloon(re->ui, 'n', x, re->y, fg, bg, &matchingchar,1); else if(mline>=(re->originline+re->maxrow)) reui_balloon(re->ui, 's', x, re->y+re->h-1, fg, bg, &matchingchar,1); else if(mcol<re->origincol) reui_balloon(re->ui, 'w', re->x, y, fg, bg, &matchingchar,1); else if(mcol>=(re->origincol+re->maxcol)) reui_balloon(re->ui, 'e', re->x+re->w-1,y, fg, bg, &matchingchar,1); else reui_balloon(re->ui, '\0', x,y, fg, bg, &matchingchar,1); } /* display prototypes info if applicable */ hint=redata_prototypes_get(re->data, cursorpos); if(hint!=NULL) { if((re->curline-re->originline)>=(re->maxrow/2)) reui_balloon(re->ui, '\0', re->x+re->w/2, re->y+re->ui->fontheight*3/2, "\x80\x80\x80\xff", "\xff\xff\xff\xcf",(char *) hint,strlen(hint)); else reui_balloon(re->ui, '\0', re->x+re->w/2, re->y+re->h-re->ui->fontheight*3/2, "\x80\x80\x80\xff", "\xff\xff\xff\xcf",(char *)hint,strlen(hint)); } /* all done */ re->contentsdirty=0; re->ui->rendererdirty=1; return(0); }