From 4addb711c6a1a282b0a59bf03e850a86ba2ead69 Mon Sep 17 00:00:00 2001 From: tomsmeding Date: Sun, 8 Jan 2017 22:43:35 +0100 Subject: Initial --- .gitignore | 3 + Makefile | 21 ++++++ buffer.cpp | 221 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ buffer.h | 47 +++++++++++++ command.cpp | 33 +++++++++ command.h | 29 ++++++++ config.cpp | 17 +++++ config.h | 12 ++++ global.h | 6 ++ main.cpp | 20 ++++++ manager.cpp | 151 ++++++++++++++++++++++++++++++++++++++++ manager.h | 24 +++++++ textblob.cpp | 224 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ textblob.h | 59 ++++++++++++++++ throw.h | 11 +++ 15 files changed, 878 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 buffer.cpp create mode 100644 buffer.h create mode 100644 command.cpp create mode 100644 command.h create mode 100644 config.cpp create mode 100644 config.h create mode 100644 global.h create mode 100644 main.cpp create mode 100644 manager.cpp create mode 100644 manager.h create mode 100644 textblob.cpp create mode 100644 textblob.h create mode 100644 throw.h 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 +#include +#include +#include +#include + +#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::layoutLine(const string &line,i64 linenum,i64 width){ + assert(width>=4); + 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=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.linecursor.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 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()); + bottomLine++; + } + 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.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 + +#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 cells; + }; + vector screen; + i64 lastWidth=-1,lastHeight=-1; //excluding the gutter + + struct Cursorpos{ + i64 line,x; + }; + Cursorpos cursor={0,0}; + + vector 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 args) + :cmd(cmd),args(args){} + +Command::Command(string cmd,initializer_list l) + :cmd(cmd),args(l.begin(),l.end()){} + +Command::Command(initializer_list l) + :cmd(*l.begin()),args(l.begin()+1,l.end()){} + + +const string& Command::command() const { + return cmd; +} + +const vector& 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 +#include +#include + +#include "global.h" + +using namespace std; + + +class Command{ + const string cmd; + const vector args; + +public: + Command(string cmd); + Command(string cmd,vector args); + Command(string cmd,initializer_list l); + Command(initializer_list l); + template + Command(string cmd,A... a) + :cmd(cmd),args{a...}{} + + const string& command() const; + const vector& 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 + +#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 + +#include "command.h" + +using namespace std; + + +using Keybindings = unordered_map; + +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 + +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 +#include + +#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 +#include + +#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[i].filename; + string tabtext=" "; + for(size_t j=0;j=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 + +#include "buffer.h" +#include "command.h" +#include "global.h" + +using namespace std; + + +class Manager{ + vector 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 +#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<=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 +#include +#include + +#include "global.h" + +using namespace std; + + +class TextBlob{ + deque 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 + +using namespace std; + + +#define THROW(desc) do { \ + fprintf(stderr,"THROW: %s\n",desc); \ + __asm("int3\n\t"); \ + } while(0) -- cgit v1.2.3-70-g09d2