single file version using ncurses, based on https://github.com/hughbarney/zepto

This commit is contained in:
Hugh Barney 2017-03-23 22:41:14 +00:00
commit 53cf262b91
4 changed files with 901 additions and 0 deletions

128
README.md Normal file
View File

@ -0,0 +1,128 @@
#Zepto Emacs
Smallest usable Editor in less than 1000 lines of C.
Zepto Emacs is the smallest in a family of Emacs type editors inspired by MicroEmacs, Nano, Pico and my earlier project known as Perfect Emacs [1].
* Zepto is a single window minimal editor in less than 1000 lines of C that uses VT100 escape sequences instead of ncurses.
* Atto is the smallest fuctional Emacs in less than 2000 lines of C.
* FemtoEmacs is an Atto based Emacs with the FemtoLisp extension language.
> A designer knows he has achieved perfection not when there is nothing left to add, but when there is nothing left to take away.
> -- <cite>Antoine de Saint-Exupery</cite>
##Goals of Zepto Emacs
* Provide just enough editing features to be able to make small changes to files
* Act as an experimental platform for using VT100 / rawio instead of using the curses library
* Smallest code footprint to demonstrate the buffer editor concept without the distraction of more advanced editor features.
* Be easy to understand without requiring extensive study (to encourage further experimentation).
##Why the name Zepto ?
The small Emacs naming scheme appears to use sub-unit prefixes in decending order with each further reduction of functionality. The Nano and Pico Emacs editors have been around for a while.
* Nano means 10 to the power of minus 9
* Pico means 10 to the power of minus 12
* Femto means 10 to power of minus 15
* Atto means 10 to power of minus 18
* Zepto means 10 to the power of minus 21
In Defining Atto as the lowest functional Emacs I have had to consider the essential feature set that makes Emacs, 'Emacs'. I have defined this point as a basic Emacs command set and key bindings; the ability to edit multiple files (buffers), and switch between them; edit the buffers in mutliple windows, cut, copy and paste; forward and reverse searching, a replace function and basic syntax hilighting. The proviso being that all this will fit in less than 2000 lines of C.
Zepto has the smallest possible feature set to make a viable file editor. Zepto supports basic movement around the file, character insertion, deletion, backspace, line deletion, cut, copy, paste and the ability to search for a text string. Although Zepto uses a subset of the Emacs keyboard command set; it cant really be considered to be an Emacs in that it does not support the editing of multiple files in multiple windows.
##Derivation
Femto, Atto and Zepto is based on the public domain code of Anthony Howe's editor (commonly known as Anthony's Editor or AE, [2]). Rather than representing a file as a linked list of lines, the AE Editor uses the concept of a Buffer-Gap [4,5,6]. A Buffer-Gap editor stores the file in a single piece of contiguous memory with some extra unused space known as the buffer gap. On character insertion and deletion the gap is first moved to the current point. A character deletion then extends the gap by moving the gap pointer back by 1 OR the gap is reduced by 1 when a character is inserted. The Buffer-Gap technique is elegant and significantly reduces the amount of code required to load a file, modify it and redraw the display. The proof of this is seen when you consider that Atto supports almost the same command set that Pico supports, but Pico requires almost 17 times the amount of code.
##Comparisons with Other Emacs Implementations
Editor Binary BinSize KLOC Files
zepto zepto 25962 1.052 9
atto atto 33002 1.9k 12
pEmacs pe 59465 5.7K 16
Esatz-Emacs ee 59050 5.7K 14
GNOME GNOME 55922 9.8k 13
Zile zile 257360 11.7k 48
Mg mg 585313 16.5K 50
uEmacs/Pk em 147546 17.5K 34
Pico pico 438534 24.0k 29
Nano nano 192008 24.8K 17
jove jove 248824 34.7k 94
Qemacs qe 379968 36.9k 59
ue3.10 uemacs 171664 52.4K 16
GNUEmacs emacs 14632920 358.0k 186
##Starting Zepto
Zepto can only open one file at a time. The filename to edit must be specified on the command line.
$ zepto filename
##Zepto Key Bindings
C-A begining-of-line
C-B backward-character
C-D delete-char
C-E end-of-line
C-F forward Character
C-G Abort (at prompts)
C-H backspace
C-I handle-tab
C-J newline
C-K kill-to-eol
C-L refresh display
C-M Carrage Return
C-N next line
C-P previous line
C-S search-forwards
C-V Page Down
C-X CTRL-X command prefix
esc-< Start of file
esc-> End of file
esc-v Page Up
^X^C Exit. Any unsaved files will require confirmation.
^X^S Save current buffer to disk, using the buffer's filename as the name of
Home Beginning-of-line
End End-of-line
Del Delete character under cursor
Ins Toggle Overwrite Mode
Left Move left
Right Move point right
Up Move to the previous line
Down Move to the next line
Backspace delete caharacter on the left
###Searching
C-S enters the search prompt, where you type the search string
BACKSPACE - will reduce the search string, any other character will extend it
C-S at the search prompt will search forward, will wrap at end of the buffer
ESC will escape from the search prompt and return to the point of the match
C-G abort the search and return to point before the search started
###Copying and moving
C-<spacebar> Set mark at current position
^W Delete region
^Y Yank back kill buffer at cursor
esc-w Copy Region
esc-k Kill Region
A region is defined as the area between this mark and the current cursor position. The kill buffer is the text which has been most recently deleted or copied.
Generally, the procedure for copying or moving text is:
1. Mark out region using M-<spacebar> at the beginning and move the cursor to the end.
2. Delete it (with ^W) or copy it (with M-W) into the kill buffer.
3. Move the cursor to the desired location and yank it back (with ^Y).
##Copying
Zepto code is released to the public domain.
hughbarney AT gmail.com 2017
##References
[1] Perfect Emacs - https://github.com/hughbarney/pEmacs
[2] Anthony's Editor - https://github.com/hughbarney/Anthony-s-Editor
[3] MG - https://github.com/rzalamena/mg
[4] Jonathan Payne, Buffer-Gap: http://ned.rubyforge.org/doc/buffer-gap.txt
[5] Anthony Howe, http://ned.rubyforge.org/doc/editor-101.txt
[6] Anthony Howe, http://ned.rubyforge.org/doc/editor-102.txt

17
makefile Normal file
View File

@ -0,0 +1,17 @@
CC = cc
CFLAGS += -O -std=c11 -Wall -pedantic
#CFLAGS = -O -Wall
LIBS = -lncurses
LD = cc
CP = cp
MV = mv
RM = rm
zep : zep.c
$(LD) $(CFLAGS) -o zep zep.c $(LIBS)
clean:
-$(RM) zep zep.o
install:
-$(CP) zep $(HOME)/bin/zep

BIN
zep Executable file

Binary file not shown.

756
zep.c Normal file
View File

@ -0,0 +1,756 @@
/* zep.c, Zep Emacs, Public Domain, Hugh Barney, 2017, Derived from: Anthony's Editor January 93 */
#include <stdlib.h>
#include <stdarg.h>
#include <assert.h>
#include <curses.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <ctype.h>
#include <limits.h>
#include <string.h>
#include <unistd.h>
#include <termios.h>
#define B_MODIFIED 0x01 /* modified buffer */
#define MSGLINE (LINES-1)
#define CHUNK 8096L
#define K_BUFFER_LENGTH 256
#define MAX_FNAME 256
#define TEMPBUF 512
#define MIN_GAP_EXPAND 512
#define NOMARK -1
#define STRBUF_M 64
typedef unsigned char char_t;
typedef long point_t;
typedef struct keymap_t {
char *key_desc; /* name of bound function */
char *key_bytes; /* the string of bytes when this key is pressed */
void (*func)(void);
} keymap_t;
typedef struct buffer_t
{
point_t b_mark; /* the mark */
point_t b_point; /* the point */
point_t b_page; /* start of page */
point_t b_epage; /* end of page */
char_t *b_buf; /* start of buffer */
char_t *b_ebuf; /* end of buffer */
char_t *b_gap; /* start of gap */
char_t *b_egap; /* end of gap */
char w_top; /* Origin 0 top row of window */
char w_rows; /* no. of rows of text in window */
int b_row; /* cursor row */
int b_col; /* cursor col */
char b_fname[MAX_FNAME + 1]; /* filename */
char b_flags; /* buffer flags */
} buffer_t;
/*
* Some compilers define size_t as a unsigned 16 bit number while
* point_t and off_t might be defined as a signed 32 bit number.
* malloc(), realloc(), fread(), and fwrite() take size_t parameters,
* which means there will be some size limits because size_t is too
* small of a type.
*/
#define MAX_SIZE_T ((unsigned long) (size_t) ~0)
int done;
char_t *input;
int msgflag;
char msgline[TEMPBUF];
char temp[TEMPBUF];
keymap_t *key_return;
keymap_t *key_map;
buffer_t *curbp;
point_t nscrap = 0;
char_t *scrap = NULL;
char searchtext[STRBUF_M];
buffer_t* new_buffer()
{
buffer_t *bp = (buffer_t *)malloc(sizeof(buffer_t));
assert(bp != NULL);
bp->b_point = 0;
bp->b_page = 0;
bp->b_epage = 0;
bp->b_flags = 0;
bp->b_buf = NULL;
bp->b_ebuf = NULL;
bp->b_gap = NULL;
bp->b_egap = NULL;
bp->b_fname[0] = '\0';
bp->w_top = 0;
bp->w_rows = LINES - 2;
return bp;
}
void fatal(char *msg)
{
move(LINES-1, 0);
refresh();
endwin();
noraw();
printf("\nzep: %s\n", msg);
exit(1);
}
void msg(char *msg, ...)
{
va_list args;
va_start(args, msg);
(void)vsprintf(msgline, msg, args);
va_end(args);
msgflag = TRUE;
}
/* Given a buffer offset, convert it to a pointer into the buffer */
char_t * ptr(buffer_t *bp, register point_t offset)
{
if (offset < 0) return (bp->b_buf);
return (bp->b_buf+offset + (bp->b_buf + offset < bp->b_gap ? 0 : bp->b_egap-bp->b_gap));
}
/* Given a pointer into the buffer, convert it to a buffer offset */
point_t pos(buffer_t *bp, register char_t *cp)
{
assert(bp->b_buf <= cp && cp <= bp->b_ebuf);
return (cp - bp->b_buf - (cp < bp->b_egap ? 0 : bp->b_egap - bp->b_gap));
}
/* Enlarge gap by n chars, position of gap cannot change */
int growgap(buffer_t *bp, point_t n)
{
char_t *new;
point_t buflen, newlen, xgap, xegap;
assert(bp->b_buf <= bp->b_gap);
assert(bp->b_gap <= bp->b_egap);
assert(bp->b_egap <= bp->b_ebuf);
xgap = bp->b_gap - bp->b_buf;
xegap = bp->b_egap - bp->b_buf;
buflen = bp->b_ebuf - bp->b_buf;
/* reduce number of reallocs by growing by a minimum amount */
n = (n < MIN_GAP_EXPAND ? MIN_GAP_EXPAND : n);
newlen = buflen + n * sizeof (char_t);
if (buflen == 0) {
if (newlen < 0 || MAX_SIZE_T < newlen) fatal("Failed to allocate required memory.\n");
new = (char_t*) malloc((size_t) newlen);
if (new == NULL) fatal("Failed to allocate required memory.\n");
} else {
if (newlen < 0 || MAX_SIZE_T < newlen) {
msg("Failed to allocate required memory");
return (FALSE);
}
new = (char_t*) realloc(bp->b_buf, (size_t) newlen);
if (new == NULL) {
msg("Failed to allocate required memory"); /* Report non-fatal error. */
return (FALSE);
}
}
/* Relocate pointers in new buffer and append the new
* extension to the end of the gap.
*/
bp->b_buf = new;
bp->b_gap = bp->b_buf + xgap;
bp->b_ebuf = bp->b_buf + buflen;
bp->b_egap = bp->b_buf + newlen;
while (xegap < buflen--)
*--bp->b_egap = *--bp->b_ebuf;
bp->b_ebuf = bp->b_buf + newlen;
assert(bp->b_buf < bp->b_ebuf); /* Buffer must exist. */
assert(bp->b_buf <= bp->b_gap);
assert(bp->b_gap < bp->b_egap); /* Gap must grow only. */
assert(bp->b_egap <= bp->b_ebuf);
return (TRUE);
}
point_t movegap(buffer_t *bp, point_t offset)
{
char_t *p = ptr(bp, offset);
while (p < bp->b_gap)
*--bp->b_egap = *--bp->b_gap;
while (bp->b_egap < p)
*bp->b_gap++ = *bp->b_egap++;
assert(bp->b_gap <= bp->b_egap);
assert(bp->b_buf <= bp->b_gap);
assert(bp->b_egap <= bp->b_ebuf);
return (pos(bp, bp->b_egap));
}
void save()
{
FILE *fp;
point_t length;
fp = fopen(curbp->b_fname, "w");
if (fp == NULL) msg("Failed to open file \"%s\".", curbp->b_fname);
(void) movegap(curbp, (point_t) 0);
length = (point_t) (curbp->b_ebuf - curbp->b_egap);
if (fwrite(curbp->b_egap, sizeof (char), (size_t) length, fp) != length)
msg("Failed to write file \"%s\".", curbp->b_fname);
fclose(fp);
curbp->b_flags &= ~B_MODIFIED;
msg("File \"%s\" %ld bytes saved.", curbp->b_fname, pos(curbp, curbp->b_ebuf));
}
/* reads file into buffer at point */
int insert_file(char *fn, int modflag)
{
FILE *fp;
size_t len;
struct stat sb;
if (stat(fn, &sb) < 0) {
msg("Failed to find file \"%s\".", fn);
return (FALSE);
}
if (MAX_SIZE_T < sb.st_size) {
msg("File \"%s\" is too big to load.", fn);
return (FALSE);
}
if (curbp->b_egap - curbp->b_gap < sb.st_size * sizeof (char_t) && !growgap(curbp, sb.st_size))
return (FALSE);
if ((fp = fopen(fn, "r")) == NULL) {
msg("Failed to open file \"%s\".", fn);
return (FALSE);
}
curbp->b_point = movegap(curbp, curbp->b_point);
curbp->b_gap += len = fread(curbp->b_gap, sizeof (char), (size_t) sb.st_size, fp);
if (fclose(fp) != 0) {
msg("Failed to close file \"%s\".", fn);
return (FALSE);
}
curbp->b_flags &= (modflag ? B_MODIFIED : ~B_MODIFIED);
msg("File \"%s\" %ld bytes read.", fn, len);
return (TRUE);
}
char_t *get_key(keymap_t *keys, keymap_t **key_return)
{
keymap_t *k;
int submatch;
static char_t buffer[K_BUFFER_LENGTH];
static char_t *record = buffer;
*key_return = NULL;
/* if recorded bytes remain, return next recorded byte. */
if (*record != '\0') {
*key_return = NULL;
return record++;
}
/* reset record buffer. */
record = buffer;
do {
assert(K_BUFFER_LENGTH > record - buffer);
/* read and record one byte. */
*record++ = (unsigned)getch();
*record = '\0';
/* if recorded bytes match any multi-byte sequence... */
for (k = keys, submatch = 0; k->key_bytes != NULL; ++k) {
char_t *p, *q;
for (p = buffer, q = (char_t *)k->key_bytes; *p == *q; ++p, ++q) {
/* an exact match */
if (*q == '\0' && *p == '\0') {
record = buffer;
*record = '\0';
*key_return = k;
return record; /* empty string */
}
}
/* record bytes match part of a command sequence */
if (*p == '\0' && *q != '\0') {
submatch = 1;
}
}
} while (submatch);
/* nothing matched, return recorded bytes. */
record = buffer;
return (record++);
}
/* Reverse scan for start of logical line containing offset */
point_t lnstart(buffer_t *bp, register point_t off)
{
register char_t *p;
do
p = ptr(bp, --off);
while (bp->b_buf < p && *p != '\n');
return (bp->b_buf < p ? ++off : 0);
}
/* Forward scan for start of logical line segment containing 'finish' */
point_t segstart(buffer_t *bp, point_t start, point_t finish)
{
char_t *p;
int c = 0;
point_t scan = start;
while (scan < finish) {
p = ptr(bp, scan);
if (*p == '\n') {
c = 0;
start = scan+1;
} else if (COLS <= c) {
c = 0;
start = scan;
}
++scan;
c += *p == '\t' ? 8 - (c & 7) : 1;
}
return (c < COLS ? start : finish);
}
/* Forward scan for start of logical line segment following 'finish' */
point_t segnext(buffer_t *bp, point_t start, point_t finish)
{
char_t *p;
int c = 0;
point_t scan = segstart(bp, start, finish);
for (;;) {
p = ptr(bp, scan);
if (bp->b_ebuf <= p || COLS <= c)
break;
++scan;
if (*p == '\n')
break;
c += *p == '\t' ? 8 - (c & 7) : 1;
}
return (p < bp->b_ebuf ? scan : pos(bp, bp->b_ebuf));
}
/* Move up one screen line */
point_t upup(buffer_t *bp, point_t off)
{
point_t curr = lnstart(bp, off);
point_t seg = segstart(bp, curr, off);
if (curr < seg)
off = segstart(bp, curr, seg-1);
else
off = segstart(bp, lnstart(bp,curr-1), curr-1);
return (off);
}
/* Move down one screen line */
point_t dndn(buffer_t *bp, point_t off)
{
return (segnext(bp, lnstart(bp,off), off));
}
/* Return the offset of a column on the specified line */
point_t lncolumn(buffer_t *bp, point_t offset, int column)
{
int c = 0;
char_t *p;
while ((p = ptr(bp, offset)) < bp->b_ebuf && *p != '\n' && c < column) {
c += *p == '\t' ? 8 - (c & 7) : 1;
++offset;
}
return (offset);
}
void modeline(buffer_t *bp)
{
int i;
char mch;
standout();
move(bp->w_top + bp->w_rows, 0);
mch = ((bp->b_flags & B_MODIFIED) ? '*' : '=');
sprintf(temp, "=%c Zep: == %s ", mch, bp->b_fname);
addstr(temp);
for (i = strlen(temp) + 1; i <= COLS; i++)
addch('=');
standend();
}
void dispmsg()
{
move(MSGLINE, 0);
if (msgflag) {
addstr(msgline);
msgflag = FALSE;
}
clrtoeol();
}
void display()
{
char_t *p;
int i, j, k;
buffer_t *bp = curbp;
/* find start of screen, handle scroll up off page or top of file */
/* point is always within b_page and b_epage */
if (bp->b_point < bp->b_page)
bp->b_page = segstart(bp, lnstart(bp,bp->b_point), bp->b_point);
/* reframe when scrolled off bottom */
if (bp->b_epage <= bp->b_point) {
/* Find end of screen plus one. */
bp->b_page = dndn(bp, bp->b_point);
/* if we scoll to EOF we show 1 blank line at bottom of screen */
if (pos(bp, bp->b_ebuf) <= bp->b_page) {
bp->b_page = pos(bp, bp->b_ebuf);
i = bp->w_rows - 1;
} else {
i = bp->w_rows - 0;
}
/* Scan backwards the required number of lines. */
while (0 < i--)
bp->b_page = upup(bp, bp->b_page);
}
move(bp->w_top, 0); /* start from top of window */
i = bp->w_top;
j = 0;
bp->b_epage = bp->b_page;
/* paint screen from top of page until we hit maxline */
while (1) {
/* reached point - store the cursor position */
if (bp->b_point == bp->b_epage) {
bp->b_row = i;
bp->b_col = j;
}
p = ptr(bp, bp->b_epage);
if (bp->w_top + bp->w_rows <= i || bp->b_ebuf <= p) /* maxline */
break;
if (*p != '\r') {
if (isprint(*p) || *p == '\t' || *p == '\n') {
j += *p == '\t' ? 8-(j&7) : 1;
addch(*p);
} else {
const char *ctrl = unctrl(*p);
j += (int) strlen(ctrl);
addstr(ctrl);
}
}
if (*p == '\n' || COLS <= j) {
j -= COLS;
if (j < 0)
j = 0;
++i;
}
++bp->b_epage;
}
/* replacement for clrtobot() to bottom of window */
for (k=i; k < bp->w_top + bp->w_rows; k++) {
move(k, j); /* clear from very last char not start of line */
clrtoeol();
j = 0; /* thereafter start of line */
}
modeline(bp);
dispmsg();
move(bp->b_row, bp->b_col); /* set cursor */
refresh();
}
void top() { curbp->b_point = 0; }
void bottom() { curbp->b_epage = curbp->b_point = pos(curbp, curbp->b_ebuf); }
void left() { if (0 < curbp->b_point) --curbp->b_point; }
void right() { if (curbp->b_point < pos(curbp, curbp->b_ebuf)) ++curbp->b_point; }
void up() { curbp->b_point = lncolumn(curbp, upup(curbp, curbp->b_point),curbp->b_col); }
void down() { curbp->b_point = lncolumn(curbp, dndn(curbp, curbp->b_point),curbp->b_col); }
void lnbegin() { curbp->b_point = segstart(curbp, lnstart(curbp,curbp->b_point), curbp->b_point); }
void quit() { done = 1; }
void lnend()
{
curbp->b_point = dndn(curbp, curbp->b_point);
left();
}
void pgdown()
{
curbp->b_page = curbp->b_point = upup(curbp, curbp->b_epage);
while (0 < curbp->b_row--)
down();
curbp->b_epage = pos(curbp, curbp->b_ebuf);
}
void pgup()
{
int i = curbp->w_rows;
while (0 < --i) {
curbp->b_page = upup(curbp, curbp->b_page);
up();
}
}
void insert()
{
assert(curbp->b_gap <= curbp->b_egap);
if (curbp->b_gap == curbp->b_egap && !growgap(curbp, CHUNK)) return;
curbp->b_point = movegap(curbp, curbp->b_point);
*curbp->b_gap++ = *input == '\r' ? '\n' : *input;
curbp->b_point = pos(curbp, curbp->b_egap);
curbp->b_flags |= B_MODIFIED;
}
void backsp()
{
curbp->b_point = movegap(curbp, curbp->b_point);
if (curbp->b_buf < curbp->b_gap) {
--curbp->b_gap;
curbp->b_flags |= B_MODIFIED;
}
curbp->b_point = pos(curbp, curbp->b_egap);
}
void delete()
{
curbp->b_point = movegap(curbp, curbp->b_point);
if (curbp->b_egap < curbp->b_ebuf) {
curbp->b_point = pos(curbp, ++curbp->b_egap);
curbp->b_flags |= B_MODIFIED;
}
}
void set_mark()
{
curbp->b_mark = (curbp->b_mark == curbp->b_point ? NOMARK : curbp->b_point);
msg("Mark set");
}
void copy_cut(int cut)
{
char_t *p;
/* if no mark or point == marker, nothing doing */
if (curbp->b_mark == NOMARK || curbp->b_point == curbp->b_mark) return;
if (scrap != NULL) {
free(scrap);
scrap = NULL;
}
if (curbp->b_point < curbp->b_mark) {
/* point above marker: move gap under point, region = marker - point */
(void)movegap(curbp, curbp->b_point);
p = ptr(curbp, curbp->b_point);
nscrap = curbp->b_mark - curbp->b_point;
} else {
/* if point below marker: move gap under marker, region = point - marker */
(void)movegap(curbp, curbp->b_mark);
p = ptr(curbp, curbp->b_mark);
nscrap = curbp->b_point - curbp->b_mark;
}
if ((scrap = (char_t*) malloc(nscrap)) == NULL) {
msg("No more memory available.");
} else {
(void)memcpy(scrap, p, nscrap * sizeof (char_t));
if (cut) {
curbp->b_egap += nscrap; /* if cut expand gap down */
curbp->b_point = pos(curbp, curbp->b_egap); /* set point to after region */
curbp->b_flags |= B_MODIFIED;
msg("%ld bytes cut.", nscrap);
} else {
msg("%ld bytes copied.", nscrap);
}
curbp->b_mark = NOMARK; /* unmark */
}
}
void paste()
{
if (nscrap <= 0) {
msg("Nothing to paste.");
} else if (nscrap < curbp->b_egap - curbp->b_gap || growgap(curbp, nscrap)) {
curbp->b_point = movegap(curbp, curbp->b_point);
memcpy(curbp->b_gap, scrap, nscrap * sizeof (char_t));
curbp->b_gap += nscrap;
curbp->b_point = pos(curbp, curbp->b_egap);
curbp->b_flags |= B_MODIFIED;
}
}
void copy() { copy_cut(FALSE); }
void cut() { copy_cut(TRUE); }
void killtoeol()
{
/* point = start of empty line or last char in file */
if (*(ptr(curbp, curbp->b_point)) == 0xa || (curbp->b_point + 1 == ((curbp->b_ebuf - curbp->b_buf) - (curbp->b_egap - curbp->b_gap))) ) {
delete();
} else {
curbp->b_mark = curbp->b_point;
lnend();
copy_cut(TRUE);
}
}
point_t search_forward(buffer_t *bp, point_t start_p, char *stext)
{
point_t end_p = pos(bp, bp->b_ebuf);
point_t p,pp;
char* s;
if (0 == strlen(stext))
return start_p;
for (p=start_p; p < end_p; p++) {
for (s=stext, pp=p; *s == *(ptr(bp, pp)) && *s !='\0' && pp < end_p; s++, pp++)
;
if (*s == '\0')
return pp;
}
return -1;
}
void search()
{
int cpos = 0;
int c;
point_t o_point = curbp->b_point;
point_t found;
searchtext[0] = '\0';
msg("Search: %s", searchtext);
dispmsg();
cpos = strlen(searchtext);
for (;;) {
refresh();
c = getch();
/* ignore control keys other than C-g, backspace, CR, C-s, C-R, ESC */
if (c < 32 && c != 07 && c != 0x08 && c != 0x13 && c != 0x12 && c != 0x1b)
continue;
switch(c) {
case 0x1b: /* esc */
searchtext[cpos] = '\0';
flushinp(); /* discard any escape sequence without writing in buffer */
return;
case 0x07: /* ctrl-g */
curbp->b_point = o_point;
return;
case 0x13: /* ctrl-s, do the search */
found = search_forward(curbp, curbp->b_point, searchtext);
if (found != -1 ) {
curbp->b_point = found;
msg("Search: %s", searchtext);
display();
} else {
msg("Failing Search: %s", searchtext);
dispmsg();
curbp->b_point = 0;
}
break;
case 0x7f: /* del, erase */
case 0x08: /* backspace */
if (cpos == 0)
continue;
searchtext[--cpos] = '\0';
msg("Search: %s", searchtext);
dispmsg();
break;
default:
if (cpos < STRBUF_M - 1) {
searchtext[cpos++] = c;
searchtext[cpos] = '\0';
msg("Search: %s", searchtext);
dispmsg();
}
break;
}
}
}
/* the key bindings: desc, keys, func */
keymap_t keymap[] = {
{"C-a beginning-of-line ", "\x01", lnbegin },
{"C-b ", "\x02", left },
{"C-d forward-delete-char ", "\x04", delete },
{"C-e end-of-line ", "\x05", lnend },
{"C-f ", "\x06", right },
{"C-n ", "\x0E", down },
{"C-p ", "\x10", up },
{"C-h backspace ", "\x08", backsp },
{"C-k kill-to-eol ", "\x0B", killtoeol },
{"C-s search ", "\x13", search },
{"C-v ", "\x16", pgdown },
{"C-w kill-region ", "\x17", cut},
{"C-y yank ", "\x19", paste},
{"C-space set-mark ", "\x00", set_mark },
{"esc @ set-mark ", "\x1B\x40", set_mark },
{"esc k kill-region ", "\x1B\x6B", cut },
{"esc v ", "\x1B\x76", pgup },
{"esc w copy-region ", "\x1B\x77", copy},
{"esc < beg-of-buf ", "\x1B\x3C", top },
{"esc > end-of-buf ", "\x1B\x3E", bottom },
{"up previous-line ", "\x1B\x5B\x41", up },
{"down next-line ", "\x1B\x5B\x42", down },
{"left backward-character ", "\x1B\x5B\x44", left },
{"right forward-character ", "\x1B\x5B\x43", right },
{"home beginning-of-line ", "\x1B\x4F\x48", lnbegin },
{"end end-of-line ", "\x1B\x4F\x46", lnend },
{"DEL forward-delete-char ", "\x1B\x5B\x33\x7E", delete },
{"backspace delete-left ", "\x7f", backsp },
{"PgUp ", "\x1B\x5B\x35\x7E",pgup },
{"PgDn ", "\x1B\x5B\x36\x7E", pgdown },
{"C-x C-s save-buffer ", "\x18\x13", save },
{"C-x C-c exit ", "\x18\x03", quit },
{"K_ERROR ", NULL, NULL }
};
int main(int argc, char **argv)
{
if (argc != 2) fatal("usage: zep filename\n");
initscr();
raw();
noecho();
curbp = new_buffer();
(void)insert_file(argv[1], FALSE);
/* Save filename irregardless of load() success. */
strncpy(curbp->b_fname, argv[1], MAX_FNAME);
curbp->b_fname[MAX_FNAME] = '\0'; /* force truncation */
if (!growgap(curbp, CHUNK)) fatal("Failed to allocate required memory.\n");
key_map = keymap;
while (!done) {
display();
input = get_key(key_map, &key_return);
if (key_return != NULL) {
(key_return->func)();
} else {
/* allow TAB and NEWLINE, any other control char is 'Not Bound' */
if (*input > 31 || *input == 13 || *input == 9)
insert();
else {
fflush(stdin);
msg("Not bound");
}
}
}
move(MSGLINE, 0);
refresh();
noraw();
endwin();
return 0;
}