#08

// ebuf.h --- editor buffer API.
#include <stdbool.h>
#define MAXSTR 100
struct ebuf;
typedef struct ebuf *ebufp;
ebufp ebuf_new();            // create ebuf
bool ebuf_iseof(ebufp e);    // see if current line is EOF
bool ebuf_forward(ebufp e);  // forward 1 line
bool ebuf_backward(ebufp e); // backward 1 line
void ebuf_top(ebufp e);      // go to top
char *ebuf_str(ebufp e);    // obtain current line string 
void ebuf_insert(ebufp e, char *s); // insert a line
// ebufdemo.c --- demonstration of ebuf.
#include <stdio.h>
#include "ebuf.h"

int main(void) {
  ebufp e = ebuf_new();
  ebuf_insert(e, "abc");
  ebuf_insert(e, "def");
  ebuf_insert(e, "ghi");
  ebuf_top(e);
  while(!ebuf_iseof(e)) {
    printf("%s\n", ebuf_str(e)); ebuf_forward(e);
  }
  while(ebuf_backward(e)) { printf("%s\n", ebuf_str(e)); }
  return 0;
}
// ebuf.c --- editor buffer implementation
#include <stdlib.h>
#include <string.h>
#include "ebuf.h"
struct line {
  struct line *prev, *next; char str[MAXSTR];
};
struct ebuf { struct line *head, *cur; };
ebufp ebuf_new() {
  ebufp r = (ebufp)malloc(sizeof(struct ebuf));
  r->head = (struct line*)malloc(sizeof(struct line));
  r->cur = r->head->next = r->head->prev = r->head;
  strcpy(r->head->str, "EOF"); return r;
}
bool ebuf_iseof(ebufp e) { return e->cur == e->head; }
bool ebuf_forward(ebufp e) {
  if(e->cur == e->head) { return false; }
  e->cur = e->cur->next; return true;
}
bool ebuf_backward(ebufp e) {
  if(e->cur->prev == e->head) { return false; }
  e->cur = e->cur->prev; return true;
}
void ebuf_top(ebufp e) { e->cur = e->head->next; }
char *ebuf_str(ebufp e) { return e->cur->str; }
void ebuf_insert(ebufp e, char *s) {
  struct line *p = (struct line*)malloc(sizeof(struct line));
  strncpy(p->str, s, MAXSTR); p->str[MAXSTR-1] = '\0';
  p->prev = e->cur->prev; p->next = e->cur;
  e->cur->prev->next = p; e->cur->prev = p;
}
// test_ebuf.c --- unit test for ebuf.
#include <stdio.h>
#include <string.h>
#include "ebuf.h"
void expect_str(char *s1, char *s2, char *msg) {
  printf("%s '%s':'%s' %s\n", strcmp(s1, s2)?"NG":"OK", s1, s2, msg);
}
int main(void) {
  ebufp e = ebuf_new();
  ebuf_insert(e, "abc"); ebuf_insert(e, "def");
  ebuf_top(e); expect_str(ebuf_str(e), "abc", "line 1: abc");
  ebuf_forward(e); expect_str(ebuf_str(e), "def", "line 2: def");
  ebuf_insert(e, "ghi"); ebuf_top(e);
  ebuf_forward(e); expect_str(ebuf_str(e), "ghi", "new line 2: ghi");
  ebuf_forward(e); expect_str(ebuf_str(e), "def", "new line 3: def");
  return 0;
}
// editor.c --- a simple line editor.
#include <stdio.h>
#include "ebuf.h"

bool getl(char s[], int lim) {
  int c, i = 0;
  for(c = getchar(); c != EOF && c != '\n'; c = getchar()) {
    s[i++] = c; if(i+1 >= lim) { break; }
  }
  s[i] = '\0'; return c != EOF;
}
int main(void) {
  char buf[200];
  ebufp e = ebuf_new();
  printf("> ");
  while(getl(buf, 200)) {
    if(buf[0] == 'q') { // quit
      break;
    } else if(buf[0] == 'p') { // print
      printf("   %s\n", ebuf_str(e));
    } else if(buf[0] == 't') { // top
      ebuf_top(e); 
    } else if(buf[0] == 'f') { // fwd
      ebuf_forward(e); 
    } else if(buf[0] == 'b') { // back
      ebuf_backward(e); 
    } else if(buf[0] == 'i') { // insert
      ebuf_insert(e, buf+1);
    } else {  // other --- fwd and print
      ebuf_forward(e); printf("   %s\n", ebuf_str(e));
    }
    printf("> ");
  }
  return 0;
}
bool readfile(ebufp e, char *fname) {
  char str[200];
  FILE *f = fopen(fname, "r");
  if(f == NULL) { return false; }
  while(fgets(str, 200, f) != NULL) {
    int len = strlen(str);
    if(len > 0) { str[len-1] = '\0'; }
    ebuf_insert(e, str);
  }
  fclose(f); return true;
}
bool writefile(ebufp e, char *fname) {
  FILE *f = fopen(fname, "w");
  if(f == NULL) { return false; }
  ebuf_top(e);
  while(!ebuf_iseof(e)) {
    fprintf(f, "%s\n", ebuf_str(e)); ebuf_forward(e);
  }
  fclose(f); return true;
}
// ncursesdemo.c --- show usage of ncurses.
#include <ncurses.h>
#include <stdlib.h>

int main(void) {
  initscr(); noecho(); cbreak(); system("stty raw"); clear();
  move(10, 10); addstr("press any key"); refresh();
  int ch = getch(); addch('a'); addch('b'); refresh();
  ch = getch(); move(10, 15); insch('a'); insch('b'); refresh();
  ch = getch(); delch(); move(10, 20); clrtoeol(); refresh();
  ch = getch(); endwin(); return 0;
}
// sedit.c --- very primitive screen editor w/ ncurses.
#include <stdio.h>
#include <stdbool.h>
#include <ncurses.h>
#include <stdlib.h>
#include <string.h>
#include "ebuf.h"
bool readfile(ebufp e, char *fname) {
  char str[200];
  FILE *f = fopen(fname, "r");
  if(f == NULL) { return false; }
  while(fgets(str, 200, f) != NULL) {
    int len = strlen(str);
    if(len > 0) { str[len-1] = '\0'; }
    ebuf_insert(e, str);
  }
  fclose(f); return true;
}
bool writefile(ebufp e, char *fname) {
  FILE *f = fopen(fname, "w");
  if(f == NULL) { return false; }
  ebuf_top(e);
  while(!ebuf_iseof(e)) {
    fprintf(f, "%s\n", ebuf_str(e)); ebuf_forward(e);
  }
  fclose(f); return true;
}
void inschar(char *s, char ch, int p, int len) {
  int i;
  for(i = len + 1; i > p; --i) { s[i] = s[i-1]; }
  s[p] = ch;
}
void delchar(char *s, int p, int len) {
  int i;
  for(i = p; i < len; ++i) { s[i] = s[i+1]; }
}
bool handleline(int c, char *s, int *pos, int *len) {
  if(c >= ' ' && c <= '~') {
    if(*len >= MAXSTR-1) { return false; }
    insch(c); inschar(s, c, *pos, (*len)++); move(10, ++(*pos));
  } else if(c == 'B'-'@') {
    if(*pos > 0) { move(10, --(*pos)); }
  } else if(c == 'F'-'@') {
    if(*pos < *len) { move(10, ++(*pos)); }
  } else if(c == 'H'-'@') {
    if(*pos <= 0) { return false; }
    move(10, *pos - 1); delch(); delchar(s, --(*pos), (*len)--);
  } else {
    return false;
  }
  return true;
}
int main(int argc, char *argv[]) {
  ebufp e = ebuf_new();
  if(!readfile(e, argv[1])) { printf("?\n"); return 1; }
  ebuf_top(e);
  int len, pos, ch; char *str;
  bool show = true;
  initscr(); noecho(); cbreak(); system("stty raw"); clear();
  while(true) {
    if(show) {
      str = ebuf_str(e); len = strlen(str); pos = 0;
      move(10, 0); addstr(str); clrtoeol(); move(10, 0); show = false;
    }
    refresh(); ch = getch();
    if(handleline(ch, str, &pos, &len)) {
      // do nothing
    } else if(ch == 'J'-'@') {
      ebuf_forward(e); ebuf_insert(e, "");
      ebuf_backward(e); show = true;
    } else if(ch == 'N'-'@') {
      ebuf_forward(e); show = true;
    } else if(ch == 'P'-'@') {
      ebuf_backward(e); show = true;
    } else if(ch == 'Z'-'@') {
      break;
    }
  }
  endwin(); writefile(e, argv[1]); return 0;
}