/* de.d -- The DE editor made for use with DRE's concatenative module * version 0.1, November 25th, 2007 * * Copyright (C) 2007 Joseph Pusdesris * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. */ import std.string; import std.file; import std.conv; //TODO Empty files //bindings align(4){ int NCURSES_BITS(int mask, int shift){ return (mask) << ((shift) + NCURSES_ATTR_SHIFT); } int COLOR_PAIR(int n){ return NCURSES_BITS(n, 0); } const int NCURSES_ATTR_SHIFT = 8; const int A_UNDERLINE = NCURSES_BITS(1UL,9); const int A_BOLD = NCURSES_BITS(1UL,13); const int COLOR_BLACK = 0; const int COLOR_RED = 1; const int COLOR_GREEN = 2; const int COLOR_YELLOW = 3; const int COLOR_BLUE = 4; const int COLOR_MAGENTA = 5; const int COLOR_CYAN = 6; const int COLOR_WHITE = 7; extern (C) extern int COLS; extern (C) extern int LINES; extern (C) int addch(uint); extern (C) void* stdscr; extern (C) void* initscr(); extern (C) int init_pair(short, short, short); extern (C) int clrtobot(); extern (C) int addstr(char *); extern (C) int attron(int); extern (C) int attroff(int); extern (C) int endwin(); extern (C) int move(int, int); extern (C) int getch(); extern (C) int nonl(); extern (C) int keypad(void*, bool); extern (C) int cbreak(); extern (C) int noecho(); extern (C) bool has_colors(); extern (C) int start_color(); //signals typedef void(*sighandler)(int); extern(C)void function(int) signal(int, sighandler); } void finish(int sig){ endwin(); throw new Exception("cool"); } void resized(int sig){ Editor.resize(); } static this(){ signal(2, &finish); signal(28, &resized); } struct Color{ const char[6] ansi; const int number; Color* next; } Color blue = Color(" \033[94m", 4, &red); Color yellow = Color(" \033[93m", 3, &blue); Color green = Color(" \033[92m", 2, &yellow); Color red = Color(" \033[91m", 1, &green); struct segment{ char[] string; Color color = Color(" \033[91m", 1, &green); } alias segment[] line; int size(line l){ int si; foreach(s; l) si += s.string.length; return si; } int rows(line l){ return l.size() / Editor.cols + 1; } void write(line l, inout int room, int offset){ int lineSize = l.rows() - offset; room -= (lineSize - offset); if(room < 0){ room = 0; //Will lines seriously ever be this big? } else{ int start = offset * Editor.cols; int segmentNumber = l.getSegmentNumber(start); int innerIndex = l.getInnerIndexDammit(start); attron(COLOR_PAIR(l[segmentNumber].color.number)); addstr(l[segmentNumber].string[innerIndex..$].toStringz()); foreach(s; l[segmentNumber+1..$]){ attron(COLOR_PAIR(s.color.number)); addstr(s.string.toStringz()); } } } void write(line l, inout int room){ int lineSize = l.rows(); room -= lineSize; if(room < 0){ //TODO Stop at end //No hurry, will rarely be this large room = 0; foreach(s; l){ attron(COLOR_PAIR(s.color.number)); addstr(s.string.toStringz()); } } else{ foreach(s; l){ attron(COLOR_PAIR(s.color.number)); addstr(s.string.toStringz()); } } } int getSegmentNumber(line l, int currentChar){ foreach(int index, s; l){ currentChar -= s.string.length; if(currentChar <= 0) return index; } return l.length - 1; //past line } segment* getsegment(line l, int currentChar){ return &l[getSegmentNumber(l, currentChar)]; } int getInnerIndexDammit(line l, int currentChar){ foreach(int index, s; l){ if(currentChar < s.string.length) //<= ? return currentChar; else currentChar -= s.string.length; } return l[$-1].string.length-1; //past line } char getCharDammit(line l, int currentChar){ return getsegment(l, currentChar).string[getInnerIndexDammit(l, currentChar)]; //unoptimal, but clean } class File{ char[] name; int currentX = 0, currentY = 1; //cursor in screen int currentLine = 0, currentChar = 0; //cursor in data line[] data; this(char[] filename){ this.name = filename; if(exists(filename)){ char[][] lines = splitlines(cast(char[])read(filename)); data.length = lines.length; foreach(int index, inout l; lines){ data[index].length = 1; l ~= '\n'; loop: if(l.length < 6){ data[index][$-1].string ~= l; } else{ switch(l[0..6]){ case " \033[91m": data[index] ~= segment("", red); break; case " \033[92m": data[index] ~= segment("", green); break; case " \033[93m": data[index] ~= segment("", yellow); break; case " \033[94m": data[index] ~= segment("", blue); break; default: data[index][$-1].string ~= l[0]; l = l[1..$]; } goto loop; } } } } line getLine(){ return data[currentLine]; } segment* getSegment(int offset = 0){ return getsegment(getLine(), currentChar + offset + 1); } Color getColor(){ return getSegment().color; } int getInnerIndex(int offset = 0){ return getLine().getInnerIndexDammit(currentChar + offset); } char getChar(int offset = 0){ return getLine().getCharDammit(currentChar + offset); } void append(char[] s){ int innerIndex = getInnerIndex(); getSegment().string = getSegment().string[0..innerIndex] ~ s ~ getSegment().string[innerIndex..$]; for(int i = 0; i getLine().size()){ currentChar = getLine().size() - 1; currentX = currentChar % Editor.cols; } int rowsBelowNew = (getLine().size() - currentChar) / Editor.cols + 1;; currentY -= rowsAboveOld + rowsBelowNew; if(currentY <= 1) currentY = 1; } } void down(){ if(currentLine != data.length-1){ int rowsBelowOld = getLine().rows() - (currentChar / Editor.cols); //fix algorithm currentLine++; if(currentChar > getLine().size()){ currentChar = getLine().size() - 1; currentX = currentChar % Editor.cols; } int rowsAboveNew = currentChar / Editor.cols; currentY += rowsBelowOld + rowsAboveNew; if(currentY >= Editor.rows-3) currentY = Editor.rows-3; } } void right(){ if(currentChar != getLine().size()-1){ currentChar++; if(currentX == Editor.cols-1){ currentY++; if(currentY >= Editor.rows-3) currentY = Editor.rows-3; } currentX = currentChar % Editor.cols; } } void left(){ if(currentChar){ currentChar--; if(!currentX){ currentY--; if(currentY <= 1) currentY = 1; } currentX = currentChar % Editor.cols; } } void type(int c){ switch(c){ case ' ': int innerIndex; int segmentNumba; if(!(getInnerIndex())) getSegment().color = *(getSegment().color.next); else if(!(getInnerIndex(1))) getSegment(1).color = *(getSegment(1).color.next); else if(getChar() == ' '){ innerIndex = getInnerIndex(1); segmentNumba = getSegmentNumber(getLine(), currentChar)+1; goto split; } else if(getChar(-1) == ' '){ innerIndex = getInnerIndex(); segmentNumba = getSegmentNumber(getLine(), currentChar)+1; split: segment s = segment(getSegment().string[innerIndex..$], *(getSegment().color.next)); getSegment().string = getSegment().string[0..innerIndex]; data[currentLine] = getLine()[0..segmentNumba] ~s~ getLine()[segmentNumba..$]; } else goto default; break; case 261: //right right(); break; case 260: //left left(); break; case 259: //up up(); break; case 258: //right down(); break; //TODO backspacing segments and lines away case 127: //backspace if(currentChar){ int innerIndex = getInnerIndex(); getSegment().string = getSegment().string[0..innerIndex-1] ~ getSegment().string[innerIndex..$]; left(); } break; case 13://return break; case 330://del int innerIndex = getInnerIndex(); getSegment().string = getSegment().string[0..innerIndex] ~ getSegment().string[innerIndex+1..$]; //TODO Check of EOL and merge //TODO Check for segment end break; default: //throw new Exception(std.conv.toString(c)); append([cast(char)c]); } } void returnto(){ attron(COLOR_PAIR(getColor().number)); move(currentY, currentX); } void display(){ Editor.drawInformation(); move(1,0); clrtobot(); int rowsAbove = currentY - 1;//constant value trying to get "rowsCountedSoFar" to int linePosition = currentLine;//counting up, where are we now? int rowsLeftInLine; //how many rows are above the screen? rowsAbove -= currentChar / Editor.cols; while(rowsAbove){ linePosition--;//up one line of data rowsLeftInLine = data[linePosition].rows();//How many rows does this line of data take up? while(rowsLeftInLine != 0){//subtract each row from how many remain above cursor in screen if(!(rowsAbove)) break; rowsLeftInLine--; rowsAbove--; } } int roomLeft = Editor.rows-3; data[linePosition].write(roomLeft, rowsLeftInLine); linePosition++; while(roomLeft && (data.length - linePosition)){ data[linePosition].write(roomLeft); linePosition++; } Editor.highLightColor(getColor().number); Editor.drawCommands(); returnto(); } } class Editor{ static int cols, rows; static File[] opened; static int currentFile = 0; static File getFile(){ return opened[currentFile]; } //TODO Those variables do not always functin static void resize(){ int x, y; resize(COLS, LINES); } static void resize(int c, int r){ rows = r; if(cols != c){ cols = c; drawInformation(); } } static void init(){ initscr(); // initialize the curses library keypad(stdscr, true); // enable keyboard mapping nonl(); //tell curses not to do NL->CR/NL on output cbreak(); //take input chars one at a time, no wait noecho(); //don't echo input if (has_colors()){ start_color(); // Simple color assignment init_pair(red.number, COLOR_RED, COLOR_BLACK); init_pair(green.number, COLOR_GREEN, COLOR_BLACK); init_pair(yellow.number, COLOR_YELLOW, COLOR_BLACK); init_pair(blue.number, COLOR_BLUE, COLOR_BLACK); init_pair(10, COLOR_WHITE, COLOR_RED); init_pair(11, COLOR_WHITE, COLOR_GREEN); init_pair(12, COLOR_WHITE, COLOR_YELLOW); init_pair(13, COLOR_WHITE, COLOR_BLUE); init_pair(COLOR_WHITE, COLOR_BLACK, COLOR_WHITE); init_pair(8, COLOR_WHITE, COLOR_BLACK); } else throw new Exception("No colors"); resize(); } static void inputLoop(){ Editor.getFile().display(); while(1){ int c = getch(); switch(c){ case 14://TODO New throw new Exception("^n"); case 17://TODO Quit throw new Exception("^q"); case 18://TODO Run throw new Exception("^r"); case 19://TODO Save throw new Exception("^s"); case 27: //^arrows int c2; if((c2=getch()) == 91) if((c2=getch()) == 49) if((c2=getch()) == 59) if((c2=getch()) == 53) if((c2=getch()) == 67){ //^right if(currentFile < opened.length-1){ Editor.getFile().display(); currentFile++; } break; } else if(c2 == 68){ //^left if(currentFile > 0){ Editor.getFile().display(); currentFile--; } break; } throw new Exception(std.conv.toString(c2)); break; default: Editor.getFile().type(c); } getFile().display(); } } static bool save(){ return true; } static void drawInformation(){ move(0, 0); attron(COLOR_PAIR(7)); char[] line = "File: "~getFile().name; char[] spaces; spaces.length = cols / 2 - line.length - 3; foreach(inout c; spaces) c = ' '; line ~= spaces.dup; if((getFile().data.length -1)) line ~= std.conv.toString(getFile().currentLine) ~','~ std.conv.toString(100*getFile().currentLine/(getFile().data.length-1)) ~'%'; else line ~= std.conv.toString(getFile().currentLine); char[] fileNum = std.conv.toString(currentFile+1) ~ '/' ~ std.conv.toString(opened.length); spaces.length = cols - fileNum.length - line.length; foreach(inout c; spaces) c = ' '; line ~= spaces.dup ~ fileNum; addstr(line.toStringz()); } static void highLightColor(int currentColor){ int spacing = cols/6; drawColors(); switch(currentColor){ case 1: move(rows-2, spacing); attron(COLOR_PAIR(10)); addstr("evaluate"); break; case 2: move(rows-2, spacing*2); attron(COLOR_PAIR(11)); addstr("literal"); //think of better word break; case 3: move(rows-2, spacing*3); attron(COLOR_PAIR(12)); addstr("contract"); //think of use break; case 4: move(rows-2, spacing * 4); attron(COLOR_PAIR(13)); addstr("comment"); break; default: throw new Exception("Invalid color"); } getFile().returnto(); } static void drawColors(){ int spacing = cols/6; attron(COLOR_PAIR(8)); move(rows - 2, spacing-1); addstr("["); move(rows - 2, spacing); attron(COLOR_PAIR(1)); addstr("evaluate"); move(rows - 2, spacing*2); attron(COLOR_PAIR(2)); addstr("literal"); //think of better word move(rows - 2, spacing*3); attron(COLOR_PAIR(3)); addstr("contract"); //think of use move(rows - 2, spacing*4); attron(COLOR_PAIR(4)); addstr("comment"); attroff(A_UNDERLINE); move(rows - 2, spacing*4+"comment".length); attron(COLOR_PAIR(8)); addstr("]"); } static void drawCommands(){ int spacing = cols/7; move(rows-1, 0); attron(COLOR_PAIR(7)); attroff(A_BOLD); addstr("^X"); attron(COLOR_PAIR(8) | A_BOLD); addstr(" Exit"); move(rows-1, spacing); attron(COLOR_PAIR(7)); attroff(A_BOLD); addstr("^S"); attron(COLOR_PAIR(8) | A_BOLD); addstr(" Save"); move(rows-1, spacing*2); attron(COLOR_PAIR(7)); attroff(A_BOLD); addstr("^N"); attron(COLOR_PAIR(8) | A_BOLD); addstr(" New"); move(rows-1, spacing*3); attron(COLOR_PAIR(7)); attroff(A_BOLD); addstr("^R"); attron(COLOR_PAIR(8) | A_BOLD); addstr(" Run"); move(rows-1, spacing*4); attron(COLOR_PAIR(7)); attroff(A_BOLD); addstr("^dir"); attron(COLOR_PAIR(8) | A_BOLD); addstr(" Swap File"); move(rows-1, cols-"[space] Swap Color".length); attron(COLOR_PAIR(7)); attroff(A_BOLD); addstr("[space]"); attron(COLOR_PAIR(8) | A_BOLD); addstr(" Swap Color"); attroff(A_BOLD); } } void main(char[][] args){ //Editor.opened.length++; if(args.length != 1) foreach(arg; args[1..$])//add new file to list Editor.opened ~= new File(arg); else //Editor.opened ~= new File(); throw new Exception("File creation not implemented yet."); Editor.init(); while(1){ try Editor.inputLoop(); catch(Exception e){ if(Editor.save()){ endwin(); throw e; } } } }