#include #include #include #include #include #include #include #include "buffer.h" #include "manager.h" #include "throw.h" using namespace std; Buffer::Buffer(Manager *manager) :manager(manager){} Buffer::Buffer(Manager *manager,bool singleLineMode) :manager(manager),singleLineMode(singleLineMode){} void Buffer::handleCommand(const Command &cmd){ //TODO: optimise the relayoutScreen's in this function if(cmd[0]=="open_file"){ const string &fname=cmd[1]; filename=fname; ifstream file(fname); if(!file){ manager->receive({"error","Cannot open file '"+fname+"'"}); return; } i64 nread=tb.read(file); cursor.line=0; cursor.x=0; screen.clear(); dirty=false; manager->receive({"info","Opened, read "+to_string(nread)+" characters"}); } else if(cmd[0]=="save_file"){ if(filename.size()==0){ manager->receive({"save_prompt"}); return; } ofstream file(filename); if(!file){ manager->receive({"error","Cannot open file '"+filename+"' for writing"}); return; } i64 nwritten=tb.write(file); dirty=false; manager->receive({"info","Saved, written "+to_string(nwritten)+" characters"}); } else if(cmd[0]=="insert_char"){ char c=cmd[1][0]; assert(c!='\n'); tb.insert(cursor.line,cursor.x,cmd[1][0]); dirty=true; cursor.x++; relayoutScreen(); if(findCursorInScreen().x==-1){ screen.erase(screen.begin()); relayoutScreen(); } } else if(cmd[0]=="insert_newline"){ tb.insert(cursor.line,cursor.x,'\n'); dirty=true; cursor.x=0; cursor.line++; relayoutScreen(); if(findCursorInScreen().x==-1){ screen.erase(screen.begin()); relayoutScreen(); } } else if(cmd[0]=="delete_backward"){ if(cursor.x==0&&cursor.line==0){ bel(); return; } if(cursor.x>0){ cursor.x--; tb.erase(cursor.line,cursor.x); } else { cursor.line--; cursor.x=tb.lineLen(cursor.line); tb.erase(cursor.line,cursor.x); } dirty=true; relayoutScreen(); if(findCursorInScreen().x==-1){ scrollUp(); } else { relayoutScreen(); } } else if(cmd[0]=="delete_forward"){ if(cursor.line==tb.numLines()-1&&cursor.x==tb.lineLen(cursor.line)){ bel(); return; } tb.erase(cursor.line,cursor.x); dirty=true; relayoutScreen(); } else if(cmd[0]=="move_forward"){ if(cursor.line==tb.numLines()-1&&cursor.x==tb.lineLen(cursor.line)){ bel(); return; } if(cursor.x==tb.lineLen(cursor.line)){ cursor.x=0; cursor.line++; } else { cursor.x++; } if(findCursorInScreen().x==-1){ screen.erase(screen.begin()); relayoutScreen(); } } else if(cmd[0]=="move_backward"){ if(cursor.line==0&&cursor.x==0){ bel(); return; } if(cursor.x==0){ cursor.line--; cursor.x=tb.lineLen(cursor.line); } else { cursor.x--; } if(findCursorInScreen().x==-1){ scrollUp(); } } else if(cmd[0]=="move_downward"){ Screenpos sp=findCursorInScreen(); if(sp.x==-1){ bel(); return; } if(sp.y==(i64)screen.size()-1){ if(screen[sp.y].line==tb.numLines()-1&& (screen[sp.y].cells.size()==0 ?tb.lineLen(screen[sp.y].line)==0 :screen[sp.y].cells.back().linex==tb.lineLen(screen[sp.y].line)-1)){ //so screen[sp.y] contains the end of that logical line if(cursor.x==tb.lineLen(screen[sp.y].line)){ bel(); } else { cursor.x=tb.lineLen(screen[sp.y].line); } return; } screen.erase(screen.begin()); relayoutScreen(); } else { sp.y++; } cursor.line=screen[sp.y].line; if(sp.x>=(i64)screen[sp.y].cells.size()){ if(screen[sp.y].cells.size()==0)cursor.x=0; else { cursor.x=screen[sp.y].cells.back().linex; //Now if we would've landed *after* the end of the line, go there. if(cursor.x==tb.lineLen(cursor.line)-1)cursor.x++; } } else { cursor.x=screen[sp.y].cells[sp.x].linex; } } else if(cmd[0]=="move_upward"){ Screenpos sp=findCursorInScreen(); if(sp.x==-1){ bel(); return; } if(sp.y==0){ if(screen[sp.y].line==0&&screen[sp.y].part==0){ if(cursor.x==0)bel(); else cursor.x=0; return; } scrollUp(); } else { sp.y--; } cursor.line=screen[sp.y].line; if(sp.x>=(i64)screen[sp.y].cells.size()){ if(screen[sp.y].cells.size()==0)cursor.x=0; else { cursor.x=screen[sp.y].cells.back().linex; //Now if we would've landed *after* the end of the line, go there. if(cursor.x==tb.lineLen(cursor.line)-1)cursor.x++; } } else { cursor.x=screen[sp.y].cells[sp.x].linex; } } else if(cmd[0]=="move_pagedown"){ Screenpos sp=findCursorInScreen(); if(sp.x==-1||screen.size()==0){ bel(); return; } i64 lastLine=screen.back().line; i64 lastX=screen.back().cells.size()==0 ? screen.back().fromx : screen.back().cells.back().linex+1; i64 newTopLine,newTopPart; if(lastX>=tb.lineLen(lastLine)){ if(lastLine==tb.numLines()-1){ cursor.line=lastLine; cursor.x=lastX; return; } newTopLine=lastLine+1; newTopPart=0; } else { newTopLine=lastLine; newTopPart=screen.back().part+1; } vector screenCopy=screen; renewLayout(newTopLine,newTopPart,lastWidth,lastHeight); if((i64)screen.size()=32&&c<127)return {c}; return {'\\','x', "0123456789abcdef"[(unsigned char)c/16], "0123456789abcdef"[(unsigned char)c%16]}; } vector Buffer::layoutLine(const string &line,i64 linenum,i64 width){ assert(width>=5); //5 == (maximum char repr width) + 1, where the 1 is an extra column on the right if(line.size()==0){ return {{linenum,0,0,{}}}; } vector layout; vector cur; i64 x=0; i64 linepart=0,fromx=0; for(i64 i=0;i<(i64)line.size();i++){ char c=line[i]; string repr; bool special=false; if(c=='\t'){ repr=string(4-cur.size()%4,' '); } else { repr=showChar(c); special=repr.size()>1; } assert(repr.size()>0); if(x+(i64)repr.size()>=width){ layout.push_back({linenum,linepart,fromx,move(cur)}); cur.clear(); linepart++; fromx=i; x=0; } Cell cell={'\0',special,i}; for(char rc : repr){ cell.c=rc; cur.push_back(cell); x++; } } if(cur.size()>0)layout.push_back({linenum,linepart,fromx,move(cur)}); return layout; } //Takes width without gutter void Buffer::performInitialLayout(i64 width,i64 height){ assert(width>0&&height>0); const i64 targetCursorScreenY=height/2; screen.clear(); if(tb.numLines()==0){ screen.push_back({0,0,0,{}}); return; } assert(cursor.line>=0&&cursor.x>=0&& cursor.line=0&&cursorScreenY layout=layoutLine(tb[topLine],topLine,width); screen.insert(screen.begin(),layout.begin(),layout.end()); cursorScreenY+=layout.size(); topLine--; } //Remove any lines from the top until the cursor is exactly halfway the screen. //Store the removed lines in `spliced`. vector spliced; if(cursorScreenY>targetCursorScreenY){ for(i64 i=0;i layout=layoutLine(tb[bottomLine],bottomLine,width); screen.insert(screen.end(),layout.begin(),layout.end()); // cerr<<"Added bottomLine="<height){ //Oops, we overran a bit. Prune the end so that we fill the screen exactly. screen.resize(height); } else if((i64)screen.size()=0&&(i64)screen.size() layout=layoutLine(tb[topLine],topLine,width); screen.insert(screen.begin(),layout.begin(),layout.end()); topLine--; } if((i64)screen.size()>height){ //Oops, we overran a bit. Prune the end so that we fill the screen exactly. screen.resize(height); } } } } //Takes width without gutter void Buffer::renewLayout(i64 topLine,i64 topPart,i64 width,i64 height){ assert(width>0&&height>0); assert(topLine>=0&&topPart>=0); screen.clear(); if(tb.numLines()==0){ screen.push_back({0,0,0,{}}); return; } screen=layoutLine(tb.getLine(topLine),topLine,width); if(topPart>0)screen.erase(screen.begin(),screen.begin()+topPart); i64 bottomLine=topLine+1; while(bottomLine layout=layoutLine(tb[bottomLine],bottomLine,width); screen.insert(screen.end(),layout.begin(),layout.end()); bottomLine++; } if((i64)screen.size()>height){ screen.resize(height); } } void Buffer::relayoutScreen(){ renewLayout(screen[0].line,screen[0].part,lastWidth,lastHeight); } void Buffer::scrollUp(){ assert(screen.size()!=0); if(screen[0].part>0){ renewLayout(screen[0].line,screen[0].part-1,lastWidth,lastHeight); return; } if(screen[0].line==0){ bel(); return; } i64 topLine=screen[0].line-1; screen=layoutLine(tb.getLine(topLine),topLine,lastWidth); renewLayout(screen.back().line,screen.back().part,lastWidth,lastHeight); } //Returns {-1,-1} if not found Buffer::Screenpos Buffer::findCursorInScreen() const { if(screen.size()==0)return {-1,-1}; if(cursor.x>tb.lineLen(cursor.line)){ THROW("Invalid cursor position: past end of line"); } auto lineit=lower_bound(screen.begin(),screen.end(),cursor.line,[this](const ScreenLine &sl,i64 line){ return sl.line &cells=screen[y].cells; auto chrit=lower_bound(cells.begin(),cells.end(),cursor.x,[](const Cell &cell,i64 x){ return cell.linexcursor.line)){ if(screenX==atx+width)curScreenX=screenX-1; else curScreenX=screenX; curScreenY=aty+y; } } setstyle(&textStyle); if(y