summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortomsmeding <tom.smeding@gmail.com>2017-01-08 22:43:35 +0100
committertomsmeding <tom.smeding@gmail.com>2017-01-08 22:43:35 +0100
commit4addb711c6a1a282b0a59bf03e850a86ba2ead69 (patch)
treef7e9c4a594d1c4fa41a6ebc6371279762a9a580a
Initial
-rw-r--r--.gitignore3
-rw-r--r--Makefile21
-rw-r--r--buffer.cpp221
-rw-r--r--buffer.h47
-rw-r--r--command.cpp33
-rw-r--r--command.h29
-rw-r--r--config.cpp17
-rw-r--r--config.h12
-rw-r--r--global.h6
-rw-r--r--main.cpp20
-rw-r--r--manager.cpp151
-rw-r--r--manager.h24
-rw-r--r--textblob.cpp224
-rw-r--r--textblob.h59
-rw-r--r--throw.h11
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;
+};
diff --git a/throw.h b/throw.h
new file mode 100644
index 0000000..7ae211a
--- /dev/null
+++ b/throw.h
@@ -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)