Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- /** text-ed.c - stupid little text editor, line command terminal interface
- - enter ".l" to insert a literal newline
- - enter ".." to insert a literal period (period is command escape character)
- - enter ".?" for additional help
- - cursor movements with ".[" and ".]" and ".p" and ".n"
- - this is the poorest text editor you have ever seen! and awkward to use...
- - Public Domain, do what you want with it!
- --> this program can be difficult to debug, beware edge cases, all manner of edge cases.
- every new feature improves usability.
- NOTE: not usable enough to be comfortable to use yet however.
- --> you may want to have a mini scratch buffer and insert strings instead of single chars.
- that would introduce new possible edge cases but improve effeciency.
- --> you may want to have simple string search
- initial release by Wayne Colvin, programmed aprox. Fri? Mar 29 2019 - Mon, Apr 1 2019.
- **/
- #include <stdio.h> /* printf(), puts(), getc(), fgets(), EOF */
- #include <stdlib.h> /* exit() */
- #include <ctype.h> /* isdigit(), isblank() */
- #define BLOCK 1024
- #define BUFSZ (1024 * BLOCK)
- #define SHOWLINES 21
- #define CMD '.'
- unsigned char TEXT[BUFSZ];
- int pt = 0;
- int len = 0;
- int column = 0;
- /* sticky column is for moving up/down lines, managed in command interface */
- int col_sticky = -1;
- char *filename = NULL;
- char name_buffer[BLOCK];
- /*----- buffer management -----*/
- void insert(int ch, int pos) {
- int i;
- i = BUFSZ-1;
- while(i--) if(i <= pos) break; else TEXT[i] = TEXT[i-1];
- TEXT[pos] = ch;
- if(len < BUFSZ) len++;
- if(pt < BUFSZ-1) pt++;
- return;
- }
- void delete(int pos) {
- int i;
- for(i = pos; i < BUFSZ-1; i++) TEXT[i] = TEXT[i+1];
- if(pos >= len) return;
- else len--;
- return;
- }
- /* this function uses an OUT parameter for secondary info */
- /* NULL accepted if info not needed */
- int get_line_col(int pos, int *col) {
- int i, c = '\n', line = 0;
- int dummy[1];
- if(!col) col = dummy;
- *col = 1;
- if(!len) { *col = 0; return 0; }
- for(i = 0; i <= pos; i++) {
- *col = *col + 1;
- if(c == '\n') { line++; *col = 1; }
- c = TEXT[i];
- }
- return line;
- }
- int get_startline(int pos) {
- while(pos--) if(TEXT[pos] == '\n') return pos+1;
- return 0;
- }
- int get_endline(int pos) {
- pos++;
- while(pos < len) if(TEXT[pos] == '\n') return pos; else pos++;
- return len;
- }
- int get_upline(int pos) {
- int i;
- if(col_sticky < 0) get_line_col(pos, &col_sticky);
- pos = get_startline(pos);
- pos--;
- if(pos < 0) pos = 0;
- pos = get_startline(pos);
- for(i = pos; i < pos+col_sticky; i++) { if(TEXT[i] == '\n') return i; }
- return pos + col_sticky - 1;
- }
- int get_downline(int pos) {
- int i;
- if(col_sticky < 0) get_line_col(pos, &col_sticky);
- /* we might already be at end of current line */
- if(TEXT[pos] != '\n') pos = get_endline(pos);
- pos++;
- if(pos + col_sticky >= len) return len;
- for(i = pos; i < pos+col_sticky; i++) { if(TEXT[i] == '\n') return i; }
- return pos + col_sticky - 1;
- }
- /* this should be rewriten if text buffer used better data structures */
- int seek_line(int n) {
- int i, x = 0, c = '\n';
- if(n < 1) return 0;
- for(i = 0; i < len; i++) {
- if(c == '\n') {
- x++;
- if(x == n) return i;
- }
- c = TEXT[i]; /* don't forget this part */
- }
- return len;
- }
- /*----- text buffer file I/O -----*/
- int load_file(char *fn) {
- int c;
- FILE *f = fopen(fn, "r");
- if(!f) return -1;
- c = fgetc(f);
- len = 0; /* global variable */
- while(c != EOF) {
- TEXT[len++] = c;
- if(len >= BUFSZ) { puts("file too big!"); fclose(f); exit(1); }
- c = getc(f);
- }
- fclose(f);
- column = 0; col_sticky = -1;
- return 1;
- }
- int write_file(char *fn) {
- int i, sz = 0;;
- FILE *f = fopen(fn, "w"); /* an empty filename doesn't create a temporary file, C doesn't work that way... */
- if(!f) return -1;
- for(i = 0; i < len; i++) { putc(TEXT[i], f); sz++; }
- return sz;
- }
- /*----- terminal/console interface -----*/
- char *RED = "\033[31m"; /* matching paren XXX */
- char *BG_RED = "\033[41m"; /* cursor position (except EOF */
- char *YELLOW = "\033[33m"; /* line numbers */
- char *BLUE = "\033[34m"; /* control-chars and dummy line */
- char *BG_BLUE = "\033[44m"; /* EOF cursor */
- char *CYAN = "\033[36m"; /* alert / notice */
- char *RESET = "\033[0m";
- char *EMPTY = "";
- /* the constant strings above aren't "compile-time constants", but toggle_ansi() still works */
- char *CURSOR="", *CURSOR_EOF="", *LINE="", *LINE_DUMMY="", *CTRL="", *ALERT="", *OFF="";
- int ANSI = 0;
- void toggle_ansi() {
- if(ANSI) { CURSOR=EMPTY; CURSOR_EOF=EMPTY; LINE=EMPTY; LINE_DUMMY=EMPTY; CTRL=EMPTY; ALERT=EMPTY; OFF=EMPTY; ANSI=0; }
- else { CURSOR=BG_RED; CURSOR_EOF=BG_BLUE; LINE=YELLOW; LINE_DUMMY=BLUE; CTRL=BLUE; ALERT=CYAN; OFF=RESET; ANSI=1; }
- return;
- }
- void print_char(c) {
- char ctrl_char[] = "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_";
- if(c < sizeof(ctrl_char)-1) printf("^%c", ctrl_char[c]);
- else printf("%c", c);
- return;
- }
- void print_text_view(int pos) {
- int i, prev, c, num;
- int line, total, before, after;
- line = get_line_col(pos, &column);
- total = get_line_col(len-1, NULL); /* Note: line can exceed total when at EOF */
- before = line - (SHOWLINES / 2); if(before < 1) before = 1;
- after = before + SHOWLINES; if(after > total) after = total;
- /* greater-than is possible if cursor is at EOF */
- if(total <= after) { after = total; before = after - SHOWLINES; if(before < 1) before = 1; }
- c = '\n'; num = 0;
- printf("%s", "\n");
- for(i = 0; i < len; i++) {
- prev = c; c = TEXT[i];
- if(prev == '\n') num++;
- if(num < before || after < num) continue;
- if(prev == '\n') printf("%s%3d%s ", LINE, num, OFF);
- if(i != pos) { if(c < ' ') { printf("%s", CTRL); print_char(c); printf("%s", OFF); } else print_char(c); }
- else { printf("%s", CURSOR); print_char(c); printf("%s", OFF); }
- if(c == '\n') printf("%s", "\n");
- }
- if(!len || TEXT[len-1] == '\n') { printf("%s%c %s ", LINE_DUMMY, '~', OFF); }
- if(pos >= len) printf("%s$%s", CURSOR_EOF, OFF);
- puts("");
- printf("%schar %d/%d line %d/%d col=%d %s|", ALERT, pos, len, line, total, column, OFF);
- printf(" %sfile='%s' %s|", ALERT, filename, OFF);
- printf(" press '%s.?%s' for help.\n", ALERT, OFF);
- return;
- }
- int eat_line() {
- int ch;
- while(1) {
- ch = getc(stdin);
- if(ch == '\n' || ch == EOF) break;
- }
- return ch;
- }
- /* C automatic stdout flushing requires the prompt to be written first */
- /* we usually use eat_line() before calling this to flush input line */
- void pause() {
- int ch;
- puts(" [press ENTER to continue]");
- ch = eat_line();
- ungetc(ch, stdin);
- return;
- }
- void prompt_filename() {
- int i;
- eat_line();
- printf("%snew filename please%s: ", ALERT, OFF);
- /* name_buffer must be global */
- fgets(name_buffer, sizeof(name_buffer)-1, stdin);
- for(i = 0; i < BLOCK; i++) {
- if(name_buffer[i] == '\n') name_buffer[i] = '\0';
- }
- return;
- }
- /*----- command interface -----*/
- int CMD_MODE;
- int X; /* repete multiplier */
- void cmd(int *c) {
- int f_size, tmp;
- /* most actions remove stickyness, restore for exceptions then */
- tmp = col_sticky; col_sticky = -1;
- if(X <= 0) X = 1;
- switch(*c) {
- case 'q' : X = 1; col_sticky = tmp; putc('\n', stdout); exit(0); break;
- case '/' : X = 1;
- col_sticky = tmp;
- prompt_filename(); /* fgets() consumes the newline, eat_line() not needed this time */
- filename=name_buffer;
- if(!filename || !filename[0]) printf("\nWARNING: %sfilename empty%s", ALERT, OFF);
- printf("%sfilename is now: '%s%s%s'%s", ALERT, OFF, filename, ALERT, OFF);
- pause();
- break;
- case 'w' : X = 1;
- col_sticky = tmp;
- if(!filename || !filename[0]) {
- printf("%sset a filename with%s '.n' %sfirst!%s", ALERT, OFF, ALERT, OFF);
- eat_line();
- pause();
- break;
- }
- else {
- f_size = write_file(filename);
- if(f_size >= 0) printf("%swrote%s %d %sbytes%s", ALERT, OFF, f_size, ALERT, OFF);
- else printf("%ssomething went wrong!%s", ALERT, OFF);
- eat_line();
- pause();
- }
- break;
- //case 'a' : toggle_ansi(); break;
- case '.' : insert('.', pt); CMD_MODE = 0; break;
- case 'l' : insert('\n', pt); break;
- case 'x' : delete(pt); break;
- case '[' : if(pt > 0) pt--; break;
- case ']' : if(pt < len) pt++; break;
- case '<' : pt = get_startline(pt); break;
- case '>' : pt = get_endline(pt); break;
- case 'p' : col_sticky = tmp;
- pt = get_upline(pt);
- break;
- case 'n' : col_sticky = tmp;
- pt = get_downline(pt);
- break;
- case 'g' : pt = seek_line(X); X = 0; break;
- /* this feature was hard to debug! writing pause() was best move */
- /* better indent commands to see easier off edge of screen... */
- default: X = 1;
- col_sticky = tmp;
- eat_line();
- printf("%sHELP**-----%s\n", ALERT, OFF);
- puts("");
- puts(" .q quit");
- puts(" ./ assign a filename for output (about 1023 char limit, no whitespace, including path if any)");
- puts(" .w output text buffer to active filename (assigned or same editor opened)");
- puts(" .. insert a literal '.' and exit command mode (repete accepted)");
- puts(" .l insert a literal newline (typed newlines are for line command input)");
- puts(" .x delete character");
- puts(" .[ back cursor");
- puts(" .] forward cursor");
- puts(" .< goto start of line");
- puts(" .> goto end of line");
- puts(" .p up line [previous]");
- puts(" .n down line [next]");
- puts(" .g goto line number (uses number prefix, might jump to beginning or end)");
- puts("");
- puts("most commands can have a positive number in front for repetition");
- puts("if the command character '.' wasn't entered then insert text literally (except . and newlines)");
- puts("");
- printf("%s-----**HELP%s", ALERT, OFF);
- pause();
- break;
- };
- return;
- }
- /*----- MAIN -----*/
- /* maybe we should accept options (besides a single filename) */
- int main(int argc, char *argv[]) {
- int f_size, c = '?';
- FILE *f = NULL;
- len = 0; pt = 0; CMD_MODE = 0;
- if(argc > 1) {
- filename = argv[1];
- f_size = load_file(filename);
- if(f_size < 0) { printf("couldn't open file '%s'\n", filename); exit(1); }
- }
- toggle_ansi();
- print_text_view(pt);
- c = getc(stdin);
- while(c != EOF) {
- if(c == '\n') { CMD_MODE = 0; print_text_view(pt); }
- else if(c == CMD && !CMD_MODE) { CMD_MODE = 1; }
- else if(CMD_MODE && isdigit(c)) { ungetc(c, stdin); scanf("%d", &X); }
- else if(CMD_MODE && !isblank(c)) {
- /* if X were less than 1 then cmd() will fix it */
- do { cmd(&c); X--; } while(X > 0);
- }
- else insert(c, pt);
- c = getc(stdin);
- }
- putc('\n', stdout);
- return 0;
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement