diff options
author | tomsmeding <tom.smeding@gmail.com> | 2017-01-08 22:43:35 +0100 |
---|---|---|
committer | tomsmeding <tom.smeding@gmail.com> | 2017-01-08 22:43:35 +0100 |
commit | 4addb711c6a1a282b0a59bf03e850a86ba2ead69 (patch) | |
tree | f7e9c4a594d1c4fa41a6ebc6371279762a9a580a |
Initial
-rw-r--r-- | .gitignore | 3 | ||||
-rw-r--r-- | Makefile | 21 | ||||
-rw-r--r-- | buffer.cpp | 221 | ||||
-rw-r--r-- | buffer.h | 47 | ||||
-rw-r--r-- | command.cpp | 33 | ||||
-rw-r--r-- | command.h | 29 | ||||
-rw-r--r-- | config.cpp | 17 | ||||
-rw-r--r-- | config.h | 12 | ||||
-rw-r--r-- | global.h | 6 | ||||
-rw-r--r-- | main.cpp | 20 | ||||
-rw-r--r-- | manager.cpp | 151 | ||||
-rw-r--r-- | manager.h | 24 | ||||
-rw-r--r-- | textblob.cpp | 224 | ||||
-rw-r--r-- | textblob.h | 59 | ||||
-rw-r--r-- | throw.h | 11 |
15 files changed, 878 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f5cb472 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.o +ede +*.dSYM diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..1db70d1 --- /dev/null +++ b/Makefile @@ -0,0 +1,21 @@ +CXX = g++ +CXXFLAGS = -Wall -Wextra -std=c++11 -g -fwrapv -I$(HOME)/prefix/include +LDFLAGS = -L$(HOME)/prefix/lib -ltermio +TARGET = ede + +.PHONY: all clean remake + +all: $(TARGET) + +clean: + rm -f *.o $(TARGET) + +remake: clean + make all + + +$(TARGET): $(patsubst %.cpp,%.o,$(wildcard *.cpp)) + $(CXX) $^ -o $@ $(LDFLAGS) + +%.o: %.cpp $(wildcard *.h) + $(CXX) $(CXXFLAGS) -c -o $@ $< diff --git a/buffer.cpp b/buffer.cpp new file mode 100644 index 0000000..e32492b --- /dev/null +++ b/buffer.cpp @@ -0,0 +1,221 @@ +#include <stdexcept> +#include <fstream> +#include <cassert> +#include <cmath> +#include <termio.h> + +#include "buffer.h" +#include "manager.h" +#include "throw.h" + +using namespace std; + + +Buffer::Buffer(Manager *manager) + :manager(manager){} + +void Buffer::receive(const Command &cmd){ + 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; + } + tb.read(file); + } else if(cmd[0]=="insert_char"){ + char c=cmd[1][0]; + if(c=='\n'){ + assert(false); + } else { + tb.insert(cursor.line,cursor.x,cmd[1][0]); + cursor.x++; + } + //TODO: optimise this + renewLayout(screen[0].line,screen[0].part,lastWidth,lastHeight); + } else { + THROW("Unknown command"); + } +} + +static string showChar(char c){ + if(c>=32&&c<127)return {c}; + return {'\\','x', + "0123456789abcdef"[(unsigned char)c/16], + "0123456789abcdef"[(unsigned char)c%16]}; +} + +/*static i64 charWidth(char c){ + return showChar(c).size(); +}*/ + +vector<Buffer::ScreenLine> Buffer::layoutLine(const string &line,i64 linenum,i64 width){ + assert(width>=4); + vector<ScreenLine> layout; + vector<Cell> cur; + i64 x=0; + i64 linepart=0,fromx=0; + for(i64 i=0;i<(i64)line.size();i++){ + char c=line[i]; + string repr=showChar(c); + 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',repr.size()!=1}; + 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<tb.numLines()&&cursor.x<=tb.lineLen(cursor.line)); + + //Layout the line the cursor is on. + screen=layoutLine(tb[cursor.line],cursor.line,width); + i64 cursorScreenY=0; + while(cursorScreenY+1<(i64)screen.size()&&screen[cursorScreenY+1].fromx>cursor.x){ + cursorScreenY++; + } + + //First, layout lines above the cursor line until the cursor is at or past halfway + //the screen, or the buffer is exhausted. + i64 topLine=cursor.line-1; //logical line + while(topLine>=0&&cursorScreenY<targetCursorScreenY){ + vector<ScreenLine> 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<ScreenLine> spliced; + if(cursorScreenY>targetCursorScreenY){ + for(i64 i=0;i<cursorScreenY-targetCursorScreenY;i++){ + spliced.push_back(move(screen[i])); + } + screen.erase(screen.begin(),screen.begin()+(cursorScreenY-targetCursorScreenY)); + } + + //Then, layout lines *under* the cursor line until the screen is full, or the buffer is + //exhausted. + i64 bottomLine=cursor.line+1; //logical line + while(bottomLine<tb.numLines()&&(i64)screen.size()<height){ + vector<ScreenLine> layout=layoutLine(tb[bottomLine],bottomLine,width); + screen.insert(screen.end(),layout.begin(),layout.end()); + bottomLine++; + } + if((i64)screen.size()<height){ + //Oops, we overran a bit. Prune the end so that we fill the screen exactly. + screen.resize(height); + } else if((i64)screen.size()<height){ + //We didn't even manage to fill the whole screen. Try to fill up the gap, first with the + //lines in `spliced`, and then with extra logical lines from the textblob. + i64 endgap=height-screen.size(); + if(endgap<=(i64)spliced.size()){ + //`spliced` contains enough lines to fill the gap. + screen.insert(screen.begin(),spliced.begin()+(spliced.size()-endgap),spliced.end()); + } else { + //We'll have to pull in some more lines from the textblob. + screen.insert(screen.begin(),spliced.begin(),spliced.end()); + while(topLine>=0&&(i64)screen.size()<height){ + vector<ScreenLine> 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<tb.numLines()&&(i64)screen.size()<height){ + vector<ScreenLine> layout=layoutLine(tb[bottomLine],bottomLine,width); + screen.insert(screen.begin(),layout.begin(),layout.end()); + bottomLine++; + } + + if((i64)screen.size()>height){ + screen.resize(height); + } +} + +i64 numberWidth(i64 number){ + if(number<0)return 1+numberWidth(-number); + if(number==0)return 1; + return (i64)log10(number)+1; +} + +void Buffer::show(i64 atx,i64 aty,i64 width,i64 height){ + static const Style gutterStyle={3,9,false,false}; + static const Style textStyle={9,9,false,false}; + static const Style specialStyle={5,9,false,false}; + + i64 gutterWidth=numberWidth(tb.numLines())+2; + i64 newWidth=width-gutterWidth; + if(newWidth!=lastWidth||height!=lastHeight){ + if(lastWidth==-1)performInitialLayout(newWidth,height); + else renewLayout(screen[0].line,screen[0].part,newWidth,height); + lastWidth=newWidth; + lastHeight=height; + } + pushcursor(); + i64 y; + for(y=0;y<(i64)screen.size();y++){ + moveto(atx,aty+y); + setstyle(&gutterStyle); + i64 nspaces=gutterWidth-1-numberWidth(screen[y].line+1); + for(i64 i=0;i<nspaces;i++){ + tputc(' '); + } + tprintf("%lld ",screen[y].line+1); + + setstyle(&textStyle); + for(const Cell &cell : screen[y].cells){ + if(cell.special)setstyle(&specialStyle); + else setstyle(&textStyle); + tputc(cell.c); + } + } + setstyle(&textStyle); + if(y<height)fillrect(atx,aty+y,width,height-y,' '); +} diff --git a/buffer.h b/buffer.h new file mode 100644 index 0000000..19b81ee --- /dev/null +++ b/buffer.h @@ -0,0 +1,47 @@ +#pragma once + +#include <string> + +#include "command.h" +#include "textblob.h" + +using namespace std; + + +//Everything is zero-based. + +class Manager; + +class Buffer{ + TextBlob tb; + Manager *manager; + + struct Cell{ + char c; + bool special; + }; + struct ScreenLine{ + i64 line,part; //part: n'th screen line of the logical line `line` + i64 fromx; //starting at what x into the line does this screen line have text + vector<Cell> cells; + }; + vector<ScreenLine> screen; + i64 lastWidth=-1,lastHeight=-1; //excluding the gutter + + struct Cursorpos{ + i64 line,x; + }; + Cursorpos cursor={0,0}; + + vector<ScreenLine> layoutLine(const string &line,i64 linenum,i64 width); + void performInitialLayout(i64 width,i64 height); + void renewLayout(i64 topLine,i64 topPart,i64 width,i64 height); + +public: + string filename; + + Buffer(Manager *manager); + + void receive(const Command &cmd); + void show(i64 atx,i64 aty,i64 width,i64 height); +}; diff --git a/command.cpp b/command.cpp new file mode 100644 index 0000000..f8f91b9 --- /dev/null +++ b/command.cpp @@ -0,0 +1,33 @@ +#include "command.h" + +using namespace std; + + +Command::Command(string cmd) + :cmd(cmd){} + +Command::Command(string cmd,vector<string> args) + :cmd(cmd),args(args){} + +Command::Command(string cmd,initializer_list<string> l) + :cmd(cmd),args(l.begin(),l.end()){} + +Command::Command(initializer_list<string> l) + :cmd(*l.begin()),args(l.begin()+1,l.end()){} + + +const string& Command::command() const { + return cmd; +} + +const vector<string>& Command::arguments() const { + return args; +} + +const string& Command::argument(i64 index) const { + return args.at(index); +} + +const string& Command::operator[](i64 index) const { + return index==0?cmd:args.at(index-1); +} diff --git a/command.h b/command.h new file mode 100644 index 0000000..5ee7d24 --- /dev/null +++ b/command.h @@ -0,0 +1,29 @@ +#pragma once + +#include <initializer_list> +#include <string> +#include <vector> + +#include "global.h" + +using namespace std; + + +class Command{ + const string cmd; + const vector<string> args; + +public: + Command(string cmd); + Command(string cmd,vector<string> args); + Command(string cmd,initializer_list<string> l); + Command(initializer_list<string> l); + template <typename... A> + Command(string cmd,A... a) + :cmd(cmd),args{a...}{} + + const string& command() const; + const vector<string>& arguments() const; + const string& argument(i64 index) const; + const string& operator[](i64 index) const; // 0 is command, >=1 is arguments +}; diff --git a/config.cpp b/config.cpp new file mode 100644 index 0000000..c2c12b7 --- /dev/null +++ b/config.cpp @@ -0,0 +1,17 @@ +#include <termio.h> + +#include "command.h" +#include "config.h" + +using namespace std; + + +Keybindings global_keybindings={ + {KEY_CTRL+'Q',{"quit_app"}} +}; + +class Init{public: Init(){ + for(int i=32;i<127;i++){ + global_keybindings.emplace(i,Command("insert_char",string(1,(char)i))); + } +}} init_object; diff --git a/config.h b/config.h new file mode 100644 index 0000000..0f9ab5d --- /dev/null +++ b/config.h @@ -0,0 +1,12 @@ +#pragma once + +#include <unordered_map> + +#include "command.h" + +using namespace std; + + +using Keybindings = unordered_map<int,Command>; + +extern Keybindings global_keybindings; diff --git a/global.h b/global.h new file mode 100644 index 0000000..42a8180 --- /dev/null +++ b/global.h @@ -0,0 +1,6 @@ +#pragma once + +#include <cstdint> + +using i64 = int64_t; +using u64 = uint64_t; diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..ec25d28 --- /dev/null +++ b/main.cpp @@ -0,0 +1,20 @@ +#include <exception> +#include <termio.h> + +#include "manager.h" + +using namespace std; + + +int main(int argc,char **argv){ + set_terminate([](){ + __asm("int3\n\t"); + }); + + installCLhandler(true); + Manager manager; + for(int i=1;i<argc;i++){ + manager.receive({"open_file",argv[i]}); + } + return manager.io(); +} diff --git a/manager.cpp b/manager.cpp new file mode 100644 index 0000000..01c9e01 --- /dev/null +++ b/manager.cpp @@ -0,0 +1,151 @@ +#include <cassert> +#include <termio.h> + +#include "config.h" +#include "manager.h" +#include "throw.h" + +using namespace std; + + +Manager::Manager() + :activeIdx(-1){} + +void Manager::receive(const Command &cmd){ + if(cmd[0]=="open_file"){ + buffers.emplace_back(this); + buffers.back().receive(cmd); + } else if(cmd[0]=="insert_char"){ + if(activeIdx==-1)bel(); + else buffers[activeIdx].receive(cmd); + } else if(cmd[0]=="error"){ + pushcursor(); + Size termsize=gettermsize(); + moveto(0,termsize.h-1); + Style errorStyle={1,9,true,false}; + setstyle(&errorStyle); + tprintf("%s",cmd[1].data()); + popcursor(); + } else if(cmd[0]=="quit_app"){ + should_quit=true; + } else { + THROW("Unknown command"); + } +} + +void Manager::show(){ + if(buffers.size()==0)return; + + string bartext; + i64 hilight_start=-1,hilight_len; + for(size_t i=0;i<buffers.size();i++){ + const string &fname=buffers[i].filename.size()==0?"<New file>":buffers[i].filename; + string tabtext=" "; + for(size_t j=0;j<fname.size();j++){ + if(fname[j]=='/'){ + tabtext+='/'; + if(j+1<fname.size()){ + tabtext+=fname[j+1]; + j++; + } + } + } + + i64 nameidx; + for(nameidx=fname.size()-1;nameidx>=0;nameidx--){ + if(fname[nameidx]=='/'){ + nameidx++; + break; + } + } + + //+1 because the first char was already appended + tabtext+=fname.substr(nameidx+1,string::npos); + + tabtext+=' '; + + if((i64)i==activeIdx){ + hilight_start=bartext.size(); + hilight_len=tabtext.length(); + } + bartext+=tabtext; + } + + assert(hilight_start!=-1); + + Size termsize=gettermsize(); + i64 print_start=-1; + if(bartext.size()<=(size_t)termsize.w){ + print_start=0; + } else { + print_start=hilight_start+hilight_len/2-termsize.w/2; + if(print_start<0){ + print_start=0; + } else if(print_start+(size_t)termsize.w>=bartext.size()){ + print_start=bartext.size()-termsize.w; + } + } + + Style backStyle={7,0,false,false}; + Style activeStyle={7,4,true,false}; + + pushcursor(); + moveto(0,0); + setstyle(&backStyle); + if(print_start>=hilight_start+hilight_len||print_start+termsize.w<=hilight_start){ + string text=bartext.substr(print_start,termsize.w); + tprintf("%s",text.data()); + } else { + string text=bartext.substr(print_start,hilight_start-print_start); + tprintf("%s",text.data()); + + text=bartext.substr(hilight_start,hilight_len); + setstyle(&activeStyle); + tprintf("%s",text.data()); + + text=bartext.substr(hilight_start+hilight_len,termsize.w-(hilight_start+hilight_len-print_start)); + setstyle(&backStyle); + tprintf("%s",text.data()); + } + popcursor(); +} + +class TermioRAII{ +public: + TermioRAII(){ + initscreen(); + initkeyboard(true); + } + ~TermioRAII(){ + endkeyboard(); + endscreen(); + } +}; + +int Manager::io(){ + TermioRAII termioRAII; + + if(activeIdx==-1){ + buffers.emplace_back(this); + activeIdx=0; + } + + while(true){ + Size termsize=gettermsize(); + show(); + buffers[activeIdx].show(0,1,termsize.w,termsize.h-2); + redraw(); + + int key=tgetkey(); + + auto it=global_keybindings.find(key); + if(it!=global_keybindings.end()){ + receive(it->second); + } else { + receive({"error","Unbound key: "+to_string(key)}); + } + if(should_quit)break; + } + + return 0; +} diff --git a/manager.h b/manager.h new file mode 100644 index 0000000..eca83a1 --- /dev/null +++ b/manager.h @@ -0,0 +1,24 @@ +#pragma once + +#include <vector> + +#include "buffer.h" +#include "command.h" +#include "global.h" + +using namespace std; + + +class Manager{ + vector<Buffer> buffers; + i64 activeIdx; + bool should_quit=false; + +public: + Manager(); + + void receive(const Command &cmd); + void show(); + + int io(); +}; diff --git a/textblob.cpp b/textblob.cpp new file mode 100644 index 0000000..544b26e --- /dev/null +++ b/textblob.cpp @@ -0,0 +1,224 @@ +#include <stdexcept> +#include "textblob.h" + +using namespace std; + + +bool TextBlob::isInRange(i64 y,i64 x) const { + return y>=0&&y<(i64)lines.size()&&x>=0&&x<(i64)lines[y].size(); +} + +bool TextBlob::isInRangeP1(i64 y,i64 x) const { + return y>=0&&y<=(i64)lines.size()&&x>=0&&(y==(i64)lines.size()?x==0:x<=(i64)lines[y].size()); +} + +bool TextBlob::isInRange(i64 y) const { + return y>=0&&y<(i64)lines.size(); +} + +bool TextBlob::isInRangeP1(i64 y) const { + return y>=0&&y<=(i64)lines.size(); +} + + +void TextBlob::checkInRange(i64 y,i64 x) const { + if(!isInRange(y,x)){ + throw out_of_range("Position y="+to_string(y)+" x="+to_string(x)+" not in range of TextBlob"); + } +} + +void TextBlob::checkInRangeP1(i64 y,i64 x) const { + if(!isInRangeP1(y,x)){ + throw out_of_range("Position y="+to_string(y)+" x="+to_string(x)+" not in range of TextBlob"); + } +} + +void TextBlob::checkInRange(i64 y) const { + if(!isInRange(y)){ + throw out_of_range("Position y="+to_string(y)+" not in range of TextBlob"); + } +} + +void TextBlob::checkInRangeP1(i64 y) const { + if(!isInRangeP1(y)){ + throw out_of_range("Position y="+to_string(y)+" not in range of TextBlob"); + } +} + + +void TextBlob::clear(){ + lines.clear(); +} + +void TextBlob::read(istream &is){ + lines.clear(); + while(true){ + lines.emplace_back(); + getline(is,lines.back()); + if(lines.back().size()==0)break; + if(!is)break; + } +} + +void TextBlob::write(ostream &os) const { + for(const string &line : lines){ + os<<line<<'\n'; + } + os<<flush; +} + +void TextBlob::setText(const string &text){ + lines.clear(); + insertString(0,0,text); +} + +string TextBlob::fullText() const { + string res; + for(const string &line : lines){ + res+=line+'\n'; + } + return res; +} + +char TextBlob::replace(i64 y,i64 x,char c){ + checkInRange(y,x); + char old=lines[y][x]; + lines[y][x]=c; + return old; +} + +void TextBlob::insert(i64 y,i64 x,char c){ + if(lines.size()==0&&y==0&&x==0){ + lines.emplace_back(); + } else { + checkInRange(y); checkInRangeP1(y,x); + } + if(c=='\n'){ + lines.emplace(lines.begin()+(y+1),lines[y],x); + lines[y].erase(x); + } else { + lines[y].insert(x,1,c); + } +} + +void TextBlob::insertLine(i64 y,const string &line){ + checkInRangeP1(y); + if(line.find('\n')!=string::npos){ + throw invalid_argument("Newline in argument to TextBlob::insertLine"); + } + lines.insert(lines.begin()+y,line); +} + +void TextBlob::insertString(i64 y,i64 x,const string &str){ + if(lines.size()==0&&y==0&&x==0){ + lines.emplace_back(); + } else { + checkInRange(y); checkInRangeP1(y,x); + } + size_t idx=str.find('\n'); + if(idx==string::npos){ + lines[y].insert(x,str); + return; + } + lines[y].insert(x,str,0,idx); + i64 afterfirstpart=x+idx; + i64 nwholelines=0; + while(true){ + size_t idx2=str.find('\n',idx+1); + if(idx2==string::npos)break; + nwholelines++; + lines.emplace(lines.begin()+(y+nwholelines),str,idx+1,idx2-idx-1); + idx=idx2; + } + if(y+nwholelines+1==(i64)lines.size()){ + lines.emplace_back(str,idx+1); + } else { + lines.emplace(lines.begin()+(y+nwholelines+1),str,idx+1,string::npos); + lines[y+nwholelines+1].append(lines[y],afterfirstpart,string::npos); + lines[y].erase(afterfirstpart); + } +} + +char TextBlob::erase(i64 y,i64 x){ + checkInRange(y); checkInRangeP1(y,x); + if(x<(i64)lines[y].size()){ + char c=lines[y][x]; + lines[y].erase(x,1); + return c; + } else if(y!=(i64)lines.size()-1){ + lines[y]+=lines[y+1]; + lines.erase(lines.begin()+(y+1)); + return '\n'; + } else { + return '\n'; + } +} + +string TextBlob::erase(i64 y,i64 x,i64 nchars){ + checkInRange(y); checkInRangeP1(y,x); + if(nchars<=(i64)lines[y].size()-x){ + string old(lines[y],x,nchars); + lines[y].erase(x,nchars); + return old; + } + i64 accum=lines[y].size()-x+1; + i64 y2; + for(y2=y+1;y2<(i64)lines.size();y2++){ + accum+=lines[y2].size()+1; + if(accum>=nchars)break; + } + if(y2==(i64)lines.size()){ + throw out_of_range("Not enough characters present in TextBlob::erase(y,x,nchars)"); + } + string old; + old.reserve(nchars); + old.append(lines[y],x,string::npos); + nchars-=old.size(); + lines[y].erase(x); + while(true){ + if(nchars==0)return old; + old+='\n'; + nchars--; + if(nchars==0){ + if(y==(i64)lines.size()-1)return old; + lines[y]+=lines[y+1]; + lines.erase(lines.begin()+(y+1)); + return old; + } + if(nchars<(i64)lines[y+1].size()){ + old.append(lines[y+1],0,nchars); + lines[y].append(lines[y+1],nchars,string::npos); + lines.erase(lines.begin()+(y+1)); + return old; + } + nchars-=lines[y+1].size(); + old+=lines[y+1]; + lines.erase(lines.begin()+(y+1)); + } +} + +string& TextBlob::operator[](i64 y){ + checkInRange(y); + return lines[y]; +} + +const string& TextBlob::operator[](i64 y) const { + checkInRange(y); + return lines[y]; +} + +string TextBlob::getLine(i64 y) const{ + checkInRangeP1(y); + if(y==(i64)lines.size())return {}; + else return lines[y]; +} + +i64 TextBlob::numLines() const { + return lines.size(); +} + +i64 TextBlob::lineLen(i64 y) const { + checkInRangeP1(y); + if(y==(i64)lines.size())return 0; + return lines[y].size(); +} diff --git a/textblob.h b/textblob.h new file mode 100644 index 0000000..3246c8d --- /dev/null +++ b/textblob.h @@ -0,0 +1,59 @@ +#pragma once + +#include <iostream> +#include <deque> +#include <string> + +#include "global.h" + +using namespace std; + + +class TextBlob{ + deque<string> lines; + + void checkInRange(i64 y,i64 x) const; + void checkInRangeP1(i64 y,i64 x) const; + void checkInRange(i64 y) const; + void checkInRangeP1(i64 y) const; + +public: + TextBlob() = default; + TextBlob(const TextBlob&) = default; + TextBlob(TextBlob&&) = default; + + TextBlob& operator=(const TextBlob &other) = default; + TextBlob& operator=(TextBlob &&other) = default; + + bool isInRange(i64 y,i64 x) const; + bool isInRangeP1(i64 y,i64 x) const; + bool isInRange(i64 y) const; + bool isInRangeP1(i64 y) const; + + void clear(); + + void read(istream &is); + void write(ostream &os) const; + + void setText(const string &text); + string fullText() const; + + //returns original + char replace(i64 y,i64 x,char c); + + //All inserts are BEFORE. Pass one-past-end index to append. + void insert(i64 y,i64 x,char c); + void insertLine(i64 y,const string &line); + void insertString(i64 y,i64 x,const string &str); + + //functions return original + char erase(i64 y,i64 x); + string erase(i64 y,i64 x,i64 nchars); + + string& operator[](i64 y); + const string& operator[](i64 y) const; + string getLine(i64 y) const; //returns a copy + + i64 numLines() const; + i64 lineLen(i64 y) const; +}; @@ -0,0 +1,11 @@ +#pragma once + +#include <cstdio> + +using namespace std; + + +#define THROW(desc) do { \ + fprintf(stderr,"THROW: %s\n",desc); \ + __asm("int3\n\t"); \ + } while(0) |