tiddles: beginnings of new editor
authorAlan Cox <alan@linux.intel.com>
Sun, 31 May 2015 22:08:32 +0000 (23:08 +0100)
committerAlan Cox <alan@linux.intel.com>
Sun, 31 May 2015 22:08:32 +0000 (23:08 +0100)
Just sticking the code somewhere for the moment while I work on it

Applications/util/tiddles.c [new file with mode: 0644]

diff --git a/Applications/util/tiddles.c b/Applications/util/tiddles.c
new file mode 100644 (file)
index 0000000..ec04747
--- /dev/null
@@ -0,0 +1,645 @@
+/*
+ *     Tiddles: a tiny editor
+ *
+ *     Work in progress. Just keeping it in git while it is worked on
+ */
+
+struct line {
+  uint32_t offset;             /* Offset in base file */
+  uint32_t newoff;             /* Offset during reconstruct */
+  uint8_t *ptr;                        /* If in memory */
+  uint8_t flags;
+#define L_DIRTY                1
+#define L_EXTEND       2       /* In the spew file not the original */
+  uint8_t len;                 /* Disc len */
+  uint8_t mlen;                        /* Length in memory */
+  uint8_t mspc;                        /* Allocation size in memory */
+};
+
+
+/* Multi purpose disc buffer */
+static char ibuf[512];
+static int ibuflen;
+static char *ibufptr;
+static int ibuffd;
+
+/* Multipurpose second buffer */
+static char lbuf[256];
+
+static int in_fd, out_fd, spew_fd;
+static off_t spew_pos;
+
+/* Simple interface for buffered file reading */
+static int ibuf_getmore(void)
+{
+  ibuflen = read(ibuffd, ibuf, 512);
+  if (ibuflen <= 0)
+    return 0;
+  ibufptr = ibuf;
+  return ibuflen;
+}
+
+static int getch(void)
+{
+  if (ibufptr == ibuf + ibuflen)
+    if (ibuf_getmore(ibuffd) == 0)
+      return -1;
+
+  return *ibufptr++;
+}
+
+static int open_in(int in_fd)
+{
+  ibuffd = fd;
+  return ibuf_getmore();
+}
+
+/* Tunable allocation strategy */
+
+/* Bytes of spew space to allocate for a given line. We want to avoid
+   excess but also avoid lots of extra spew creation */
+static uint8_t spew_size(uint8_t l)
+{
+  if (l < 128)
+    return 128;
+  return 256;
+}
+
+/* Bytes of memory to allocate for a given line */
+static uint8_t spew_size(uint8_t l)
+{
+  if (l < 48)
+    return 64;
+  if (l < 80)
+    return 96;
+  if (l < 128)
+    return 160;
+  return 256;
+}
+
+/*
+ *     Add a record to the spew file and update its line information
+ *     accordingly.
+ */
+
+static int append_spew(struct line *l)
+{
+  int bytes;
+  if (lseek(spew_fd, spew_pos, 0)) != spew_pos)
+    return -1;
+  bytes = write(spew_fd, l->buf, l->mlen);
+  if (bytes != l->mlen)
+    return -1;
+  l->offset = spew_pos;
+  l->flags |= L_EXTEND;
+  l->len = l->mlen;
+  spew_pos += spew_size(l);
+}
+
+/*
+ * Update a line in the disc file
+ */
+
+static int write_bytes(int ofd, int infd, struct line *l)
+{
+  uint8_t *bp = l->ptr;
+  /* FIXME: needs buffer algorithms */
+  
+  if (bp != NULL)
+    l->len = l->mlen;          /* Disc length becomes mem length */
+  else {
+    /* Doing a disc to disc transfer */
+    bp = lbuf;
+    if (infd == -1)
+      panic("no in no buf");
+    if (read(infd, lbuf, l->len) != len)
+      return -1;
+  }
+  if (write(ofd, bp, l->len) != len)
+    return -1;
+  return 0;
+}
+
+/*
+ *     Reconstruct the edits we have made into a single coherent
+ *     file on out_fd. At this point spew can be discarded. in and out
+ *     must not be the same.
+ */
+
+static int reconstruct(void)
+{
+  struct line *lp = lines;
+  uint32_t pos = 0;            /* In output */
+  int err;
+
+  while(lp < lines_end) {
+    if (lp->flags & L_EXTEND)
+      err = write_bytes(out_fd, spew_fd, lp);
+    else
+      err = write_bytes(out_fd, in_fd, lp);
+    if (err)
+      return -1;
+    lp->newoff = pos;
+    pos += len;
+    lp++;
+  }
+  
+  lp = lines;
+  while (lp < lines_end) {
+    lp->offset = lp->newoff;
+    lp->flags &= ~(L_DIRTY|L_EXTEND);
+  }
+  spew_pos = 0L;
+  return 0;
+
+}
+
+/*
+ * Write a line back to disc. If it fits in its existing space in the
+ * spew file then put it back there. If not then add it to the spew file
+ * and we will rebuild later. Don't write back to in, in is the input file
+ * and sacrosanct until we correctly write out a final new file.
+ */
+
+static int write_back(struct line *l)
+{
+  int spc = spew_size(l->len);
+  if (l->ptr == NULL)
+    panic("dirty no ptr");
+  if (!(l->flags & L_EXTEND) || spc < l->mlen) {
+    if (append_spew(l) == -1)
+      return -1;
+  } else {
+    /* Reuse existing spew */
+    if (write_bytes(spew_fd, -1, l) == -1)
+      return -1;
+  }
+  l->flags &= ~L_DIRTY;
+  return 0;
+}
+    
+/* Flush out the lines we don't need.
+   We should be smarter about this - keep a pool measure and
+   trim when we hit it ??
+ */
+
+static int trim_window(struct line *s, struct line *e)
+{
+  if (s == NULL)
+    return;
+  while (s < e) {
+    if (s->flags & L_DIRTY) {
+      if (writeback(s) == -1)
+        return -1;
+      free(s->buf);
+      s->buf = NULL;
+    }
+    s++;
+  }
+}
+
+/* Make sure the visible window of lines is present in memory */
+static int load_window(struct line *l, int num)
+{
+  struct line *le = l + num;
+  
+  if (trim_buffer(viewstart, l))
+    return -1;
+  if (trim_buffer(le, viewend))
+    return -1;
+
+  while (l < le) {
+    if (l->buf == NULL) {
+      readin(l);
+    l++;
+  }
+  viewstart = l;
+  viewend = le;
+  return 0;
+}  
+    
+static int delete_line(struct line *l)
+{
+  /* We take a hit here but it saves a lot of data structure crap elsehwere.
+     We could defer deletes but it makes things like goto harder, unless we
+     defer until we next need a line finding etc .. still icky */
+  if (l->ptr)
+    free(l->ptr);
+  memmove(l, l + 1, lines_end - (l + 1));
+  lines_end--;
+  /* FIXME: count this into spew rebuild hints ? */
+  return 0;
+}
+
+static int insert_line(struct line *l)
+{
+  void *p;
+
+  if (lines_end >= lines_realend) {
+    /* We don't want to do this every few lines */
+    lines_realend += 256;
+
+    /* TODO: Flush everything we can here and retry on fail. If that
+       fails we could write the entire thing out and restart the load
+       and edit */
+
+    p = malloc((lines_realend - lines) * sizeof(struct line));
+    if (p == NULL) {
+      lines_realend -= 256;
+      /* Consider flushing the file entirely and reloading our window */
+      return NULL;
+    }
+    memcpy(p, lines, (lines_end - lines) * sizeof(struct line));
+  }
+  lines_end++;
+  memmove(l, l+1, lines_end - (l + 1));
+  l->flags = L_DIRTY;
+  l->offset = 0L;      /* Doesn't matter as will go to spew */
+  l->ptr = malloc(64);
+  l->len = 0;
+  l->mlen = 1;
+  *l->ptr = '\n';
+  l->mspc = 64;
+  if (l->ptr == NULL) {
+    delete_line(l);
+    return NULL;
+  }
+  return l;
+}
+
+static int line_insert(struct line *l, int off, uint8_t c)
+{
+  if (!l->ptr)
+    panic("insnoptr");
+  l->flags |= L_DIRTY;
+  /* Maximum line size */
+  if (off > 255)
+    return -1;
+  if (off >= l->mspc) {
+    size = line_size(off);
+    uint8_t *p = malloc(size);
+    if (p == NULL)
+      return -1;
+    l->mspc = size;
+    l->mlen++;
+    memmove(p, l->ptr, off);
+    p[off] = c;
+    memmove(p + off + 1, l->ptr + off, l->mlen - off);
+    free(l->ptr);
+    l->ptr = p;
+    return 0;
+  }
+  memmove(l->ptr + off + 1, l->ptr + off, l->mlen - off);
+  l->ptr[off] = c;
+  return 0;
+}
+
+static int line_delete(struct line *l, int off)
+{
+  memove(l->ptr + off, l->ptr + off + 1, l->mlen - off);
+  l->mlen --;
+  l->flags |= L_DIRTY;
+  return 0;
+}
+
+static int line_wipe(struct line *l, int off)
+{
+  if (l->mlen) {
+    l->mlen = 0;
+    l->flags |= L_DIRTY;
+  }
+  return 0;
+}
+
+static int line_replace(struct line *l, int off, uint8_t c)
+{
+  uint8_t *p = l->ptr + off;
+  if (off >= l->mlen)
+    panic("reptl");
+  if (*p != c) {
+    *p = c;
+    l->flags |= L_DIRTY;
+  }
+}
+
+/* Join two lines */
+
+static int line_join(struct line *a, struct line *b)
+{
+  int n = 0;
+  int nlen = a->mlen + b->mlen;
+
+  if (nlen > a->mspc) {
+    uint8_t *p;
+    size = line_size(nlen);
+    p = malloc(size);
+    if (p == NULL)
+      return -1;
+    a->mspc = size;
+  }
+  if (a->ptr[a->mlen-1] == '\n')
+    a->mlen--;
+  memcpy(a->ptr + a->mlen, b->ptr, b->mlen);
+  a->mlen += b->mlen;
+  return line_delete(b);
+}
+
+/* Load the input file: FIXME: what to do about overlong lines */
+
+static int load_file(int infd)
+{
+  struct stat s;
+  int lnum;            /* Guess of size */
+  uint8_t *lp = lbuf;
+  uint8_t *le = lbuf + sizeof(lbuf);
+  int c;
+  long pos = 0;
+  long lpos;
+
+  if (fstat(infd, &s) == -1 || !S_ISREG(s.st_mode))
+    return -1;
+  lnum = s.st_size / 32;
+  lnum /= 32;
+  lnum += 256;
+  /* FIXME: overflow check ? */
+  lines = malloc(lnum * sizeof(struct line));
+  if (lines == NULL)
+    return -1;
+  memset(lines, 0, lnum * sizeof(struct line));
+  lines_realend = lines + lnum;
+  lines_end = lines;
+  
+  if (open_in(infd) == -1)
+    return -1;
+
+  lpos = pos;  /* Save offset */
+  while((c = getch()) != -1) {
+    if (c == '\n') {
+      l = insert_line(lines_end);
+      if (l == NULL)
+        return -1;
+      l->pos = lpos;
+      l->len = pos - lpos + 1; /* We keep the \n */
+      lpos = pos + 1;
+    } else {
+      *lp++ = c;
+      if (lp == le) {
+        /* line too long ?? truncate or wrap ?? FIXME */
+        lp--;
+      }
+    }
+    pos++;
+  }
+  if (pos != lpos) {
+    insert_line(lines_end);
+    l->pos = lpos;
+    l->len = pos - lpos;
+  }
+  return 0;
+}
+
+/* Video layer: we need termcap in the end */
+
+void crlf(void)
+{
+  write(1, "\n");
+}
+
+void home(void)
+{
+  write(1, "\033H",2);
+}
+
+static char moveto_buf[4] = "\033Y  ";
+
+void moveto(int y, int x)
+{
+  moveto_buf[2] = y + ' ';
+  moveto_buf[3] = x + ' ';
+  write(1, moveto_buf, 4);
+}
+
+
+void setcursor(void)
+{
+  moveto(cursory, cursorx);
+  cursor_valid = 1;
+}
+  
+/* FIXME: end line without \n ?? */
+
+static void reload_window(void)
+{
+  load_window(cursorline, num_lines);
+}
+
+static void redraw_line(struct line *l)
+{
+  int n = l->mlen - viewleft;
+  n--;
+  if (n)
+    write(1, l->ptr + viewleft, n);
+  /* Actually it might be valid in a few cases.. so maybe check one day */
+  cursorvalid = 0;
+}
+
+static void redraw_status()
+{
+}
+
+/* FIXME: need to blank lines below the end of file ! */
+
+static void redraw(void)
+{
+  struct line *l = viewstart;
+  home();
+  while(l < viewend)
+    redraw_line(l);
+    crlf();
+    l++;
+  }
+  redraw_status();
+  crlf();
+}
+
+static void redraw_down(void)
+{
+  struct line *l = cursorline;
+  moveto(cursory, 0);
+  while(l < viewend)
+    redraw_line(l);
+    crlf();
+    l++;
+  }
+  redraw_status();
+  crlf();
+}
+
+static void view_adjust(int shift)
+{
+   if (viewleft + shift < 0)
+     shift = -viewleft;
+   cursorx -= shift;
+   viewleft += shift;
+   async_dirty = 1;    /* Catch up display when we get a gap */
+   cursorvalid = 0;
+}
+
+static void vert_adjust_up(void)
+{
+  int n = cursorline - lines;
+  if (n > bottom_line / 2)
+    n = bottom_line / 2;
+  cursory += n;
+  cursorline -= n;
+  async_dirty = 1;
+  cursorvalid = 0;
+}
+
+static void vert_adjust_down(void)
+{
+  int n = lines_end - cursorline;
+  if (n > bottom_line / 2)
+    n = bottom_line / 2;
+  cursory -= n;
+  cursorline += n;
+  async_dirty = 1;
+  cursorvalid = 0;
+}
+
+
+/* Special characters are not handled here yet */
+static void ins_at_cursor(uint8_t c)
+{
+
+  if (c == '\n') {
+    if(insert_line(cursorline + 1) == -1) {
+      beep();
+      return;
+    }
+    redraw_below();
+    return;
+  }
+  
+  /* FIXME: play with this - probably better to look ahead if line right
+     is off screen */
+  if (cursorx == right_column)
+    /* Assume display >= 16 wide! */
+    view_adjust(16);
+
+  if (line_insert(cursorline, cursorx, c) == -1) {
+    beep();
+    return;
+  }
+
+  if (cursorx == cursorline->mlen - viewleft - 1 && cursorvalid)
+    write(1, &c, 1);
+  else if (cursorvalid) {
+    /* Bytes left in line */
+    int n = cursorline->mlen - viewleft - cursorx - 1;
+    int r = right_column - cursorright;
+    if (n > r)
+      n = r;
+    if (n) {
+      write(1, cursorline->ptr + viewleft, n);
+      cursorvalid = 0;
+    }
+  }
+  else /* Hard .. */
+    redraw_line(cursorline);
+  cursorx++;
+}
+
+static void del_at_cursor(uint8_t c)
+{
+  int i;
+  if (cursorx == 0 && viewleft == 0) {
+    if (cursorline == line)    /* Start of file */
+      return;
+    cursorline--;
+    if (line_join(cursorline, cursorline + 1) == -1) {
+      beep();
+      return;
+    }
+    reload_window();
+    redraw_down(cursorline);
+    return;
+  }
+  if (cursorx < 16 && viewleft)
+    view_adjust(-16);          /* Assumes we always work in 16s! */
+  if (line_delete(cursorline, cursorx + viewleft) == -1) {
+    beep();
+    return;
+  }
+  if (cursorx == cursorline->mlen - viewleft - 1 && cursorvalid)
+    backspace();
+    write(1, " ", 1);
+    backspace();
+  /* Below is common in ins/del - split this bit out */
+  } else if (cursorvalid) {
+    /* Bytes left in line */
+    int n = cursorline->mlen - viewleft - cursorx - 1;
+    int r = right_column - cursorright;
+    if (n > r)
+      n = r;
+    if (n) {
+      write(1, cursorline->ptr + viewleft, n);
+      cursorvalid = 0;
+    }
+  }
+  else /* Hard .. */
+    redraw_line(cursorline);
+}
+
+static void cursor_left(void)
+{
+  if (cursorx < 16 && viewleft)
+    view_adjust(-16);
+  if (cursorx) {
+    cursorx--;
+    setcursor();
+  }
+  else
+    cursor_up();
+}
+
+static void cursor_right(void)
+{
+  if (cursorx == right_column)
+    view_adjust(16);
+  if (cursorx < cursorline->mlen) {
+    cursorx++;
+    setcursor();
+  }
+  else
+    cursor_down();
+}
+
+static void cursor_up(void)
+{
+  if (cursorline == line)
+    return;
+
+  if (cursory < 2 && viewdown)
+    vert_adjust_up();
+  cursory--;
+  cursorline--;
+  if (cursorx > cursorline->mlen)
+    cursorx = cursorline->mlen;
+  setcursor();
+}
+
+static void cursor_down(void)
+{
+  if (cursorline == line_end - 1)
+    return;
+  if (cursory >= bottom_row)   /* FIXME need to consider if lines below 
+                                   screen and scroll earlier */
+    vert_adjust_down();
+  cursory++;
+  cursorline++;
+  if (cursorx > cursorline->mlen)
+    cursorx = cursorline->mlen;
+  setcursor();
+}
+