From 42c77e998215fc013f42625a633c3868cd48d172 Mon Sep 17 00:00:00 2001 From: Chris Johns Date: Fri, 24 Oct 2014 16:06:54 -0700 Subject: shell: Add an editor to the shell. This is a small (21K on sparc) editor that provides some powerful features useful when a file needs editing on an embedded board. No need to copy files off, edit, copy back. --- cpukit/libmisc/Makefile.am | 2 +- cpukit/libmisc/shell/main_edit.c | 2259 ++++++++++++++++++++++++++++++++++++ cpukit/libmisc/shell/shellconfig.h | 6 + 3 files changed, 2266 insertions(+), 1 deletion(-) create mode 100644 cpukit/libmisc/shell/main_edit.c diff --git a/cpukit/libmisc/Makefile.am b/cpukit/libmisc/Makefile.am index 820a83801b..2f41ffaa7a 100644 --- a/cpukit/libmisc/Makefile.am +++ b/cpukit/libmisc/Makefile.am @@ -109,7 +109,7 @@ libshell_a_SOURCES = shell/cat_file.c shell/cmds.c shell/internal.h \ shell/main_time.c shell/main_mknod.c \ shell/main_setenv.c shell/main_getenv.c shell/main_unsetenv.c \ shell/main_mkrfs.c shell/main_debugrfs.c shell/main_df.c \ - shell/main_lsof.c \ + shell/main_lsof.c shell/main_edit.c \ shell/main_blkstats.c \ shell/shell-wait-for-input.c diff --git a/cpukit/libmisc/shell/main_edit.c b/cpukit/libmisc/shell/main_edit.c new file mode 100644 index 0000000000..fc7775ce61 --- /dev/null +++ b/cpukit/libmisc/shell/main_edit.c @@ -0,0 +1,2259 @@ +// +// edit.c +// +// Text editor +// +// Copyright (C) 2002 Michael Ringgaard. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. Neither the name of the project nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +// SUCH DAMAGE. +// + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef SANOS +#include +#endif + +#ifdef __rtems__ +#include +#include +#endif + +#if defined(__linux__) || defined(__rtems__) +#include +#include +#define O_BINARY 0 +static int linux_console; +#endif + +#define MINEXTEND 32768 +#define LINEBUF_EXTRA 32 + +#ifndef TABSIZE +#define TABSIZE 8 +#endif + +#ifndef INDENT +#define INDENT " " +#endif + +#define CLRSCR "\033[0J\x1b[H\x1b[J" +#define CLREOL "\033[K" +#define GOTOXY "\033[%d;%dH" +#define RESET_COLOR "\033[0m" + +#ifdef COLOR +#define TEXT_COLOR "\033[44m\033[37m\033[1m" +#define SELECT_COLOR "\033[47m\033[37m\033[1m" +#define STATUS_COLOR "\033[0m\033[47m\033[30m" +#else +#define TEXT_COLOR "\033[0m" +#define SELECT_COLOR "\033[7m\033[1m" +#define STATUS_COLOR "\033[1m\033[7m" +#endif + +// +// Key codes +// + +#define KEY_BACKSPACE 0x101 +#define KEY_ESC 0x102 +#define KEY_INS 0x103 +#define KEY_DEL 0x104 +#define KEY_LEFT 0x105 +#define KEY_RIGHT 0x106 +#define KEY_UP 0x107 +#define KEY_DOWN 0x108 +#define KEY_HOME 0x109 +#define KEY_END 0x10A +#define KEY_ENTER 0x10B +#define KEY_TAB 0x10C +#define KEY_PGUP 0x10D +#define KEY_PGDN 0x10E + +#define KEY_CTRL_LEFT 0x10F +#define KEY_CTRL_RIGHT 0x110 +#define KEY_CTRL_UP 0x111 +#define KEY_CTRL_DOWN 0x112 +#define KEY_CTRL_HOME 0x113 +#define KEY_CTRL_END 0x114 +#define KEY_CTRL_TAB 0x115 + +#define KEY_SHIFT_LEFT 0x116 +#define KEY_SHIFT_RIGHT 0x117 +#define KEY_SHIFT_UP 0x118 +#define KEY_SHIFT_DOWN 0x119 +#define KEY_SHIFT_PGUP 0x11A +#define KEY_SHIFT_PGDN 0x11B +#define KEY_SHIFT_HOME 0x11C +#define KEY_SHIFT_END 0x11D +#define KEY_SHIFT_TAB 0x11E + +#define KEY_SHIFT_CTRL_LEFT 0x11F +#define KEY_SHIFT_CTRL_RIGHT 0x120 +#define KEY_SHIFT_CTRL_UP 0x121 +#define KEY_SHIFT_CTRL_DOWN 0x122 +#define KEY_SHIFT_CTRL_HOME 0x123 +#define KEY_SHIFT_CTRL_END 0x124 + +#define KEY_F1 0x125 +#define KEY_F2 0x126 +#define KEY_F3 0x127 +#define KEY_F4 0x128 +#define KEY_F5 0x129 +#define KEY_F6 0x12a +#define KEY_F7 0x12b +#define KEY_F8 0x12c +#define KEY_F9 0x12d +#define KEY_F10 0x12e + +#define KEY_UNKNOWN 0xFFF + +#define ctrl(c) ((c) - 0x60) + +// +// Editor data block +// +// Structure of split buffer: +// +// +------------------+------------------+------------------+ +// | text before gap | gap | text after gap | +// +------------------+------------------+------------------+ +// ^ ^ ^ ^ +// | | | | +// start gap rest end +// + +struct env; + +struct undo { + int pos; // Editor position + int erased; // Size of erased contents + int inserted; // Size of inserted contents + unsigned char *undobuf; // Erased contents for undo + unsigned char *redobuf; // Inserted contents for redo + struct undo *next; // Next undo buffer + struct undo *prev; // Previous undo buffer +}; + +struct editor { + unsigned char *start; // Start of text buffer + unsigned char *gap; // Start of gap + unsigned char *rest; // End of gap + unsigned char *end; // End of text buffer + + int toppos; // Text position for current top screen line + int topline; // Line number for top of screen + int margin; // Position for leftmost column on screen + + int linepos; // Text position for current line + int line; // Current document line + int col; // Current document column + int lastcol; // Remembered column from last horizontal navigation + int anchor; // Anchor position for selection + + struct undo *undohead; // Start of undo buffer list + struct undo *undotail; // End of undo buffer list + struct undo *undo; // Undo/redo boundary + + int refresh; // Flag to trigger screen redraw + int lineupdate; // Flag to trigger redraw of current line + int dirty; // Dirty flag is set when the editor buffer has been changed + + int newfile; // File is a new file + int permissions; // File permissions + + int selecting; // Selecting active in the edtor. + + struct env *env; // Reference to global editor environment + struct editor *next; // Next editor + struct editor *prev; // Previous editor + + char filename[FILENAME_MAX]; +}; + +struct env { + struct editor *current; // Current editor + + unsigned char *clipboard; // Clipboard + int clipsize; // Clipboard size + + unsigned char *search; // Search text + unsigned char *linebuf; // Scratch buffer + + int cols; // Console columns + int lines; // Console lines + + int untitled; // Counter for untitled files +}; + +/* + * This is a hack to allow a simple way to inspect the keys to + * add extar decoding. It is not multi-user safe. + */ +#define KEY_HISTORY 1 +#if KEY_HISTORY +int key_history[32]; +size_t key_history_in; +#endif + +// +// Editor buffer functions +// + +static void clear_undo(struct editor *ed) { + struct undo *undo = ed->undohead; + while (undo) { + struct undo *next = undo->next; + free(undo->undobuf); + free(undo->redobuf); + free(undo); + undo = next; + } + ed->undohead = ed->undotail = ed->undo = NULL; +} + +static void reset_undo(struct editor *ed) { + while (ed->undotail != ed->undo) { + struct undo *undo = ed->undotail; + if (!undo) { + ed->undohead = NULL; + ed->undotail = NULL; + break; + } + ed->undotail = undo->prev; + if (undo->prev) undo->prev->next = NULL; + free(undo->undobuf); + free(undo->redobuf); + free(undo); + } + ed->undo = ed->undotail; +} + +static struct editor *create_editor(struct env *env) { + struct editor *ed = (struct editor *) malloc(sizeof(struct editor)); + memset(ed, 0, sizeof(struct editor)); + if (env->current) { + ed->next = env->current->next; + ed->prev = env->current; + env->current->next->prev = ed; + env->current->next = ed; + } else { + ed->next = ed->prev = ed; + } + ed->env = env; + env->current = ed; + return ed; +} + +static void delete_editor(struct editor *ed) { + if (ed->next == ed) { + ed->env->current = NULL; + } else { + ed->env->current = ed->prev; + } + ed->next->prev = ed->prev; + ed->prev->next = ed->next; + if (ed->start) free(ed->start); + clear_undo(ed); + free(ed); +} + +static struct editor *find_editor(struct env *env, char *filename) { + char fn[FILENAME_MAX]; + struct editor *ed = env->current; + struct editor *start = ed; + + if (!realpath(filename, fn)) strcpy(fn, filename); + + do { + if (strcmp(fn, ed->filename) == 0) return ed; + ed = ed->next; + } while (ed != start); + return NULL; +} + +static int new_file(struct editor *ed, char *filename) { + if (*filename) { + strcpy(ed->filename, filename); + } else { + sprintf(ed->filename, "Untitled-%d", ++ed->env->untitled); + ed->newfile = 1; + } + ed->permissions = 0644; + + ed->start = (unsigned char *) malloc(MINEXTEND); + if (!ed->start) return -1; +#ifdef DEBUG + memset(ed->start, 0, MINEXTEND); +#endif + + ed->gap = ed->start; + ed->rest = ed->end = ed->gap + MINEXTEND; + ed->anchor = -1; + + return 0; +} + +static int load_file(struct editor *ed, char *filename) { + struct stat statbuf; + int length; + int f; + + if (!realpath(filename, ed->filename)) return -1; + f = open(ed->filename, O_RDONLY | O_BINARY); + if (f < 0) return -1; + + if (fstat(f, &statbuf) < 0) goto err; + length = statbuf.st_size; + ed->permissions = statbuf.st_mode & 0777; + + ed->start = (unsigned char *) malloc(length + MINEXTEND); + if (!ed->start) goto err; +#ifdef DEBUG + memset(ed->start, 0, length + MINEXTEND); +#endif + if (read(f, ed->start, length) != length) goto err; + + ed->gap = ed->start + length; + ed->rest = ed->end = ed->gap + MINEXTEND; + ed->anchor = -1; + + close(f); + return 0; + +err: + close(f); + if (ed->start) { + free(ed->start); + ed->start = NULL; + } + return -1; +} + +static int save_file(struct editor *ed) { + int f; + + f = open(ed->filename, O_CREAT | O_TRUNC | O_WRONLY, ed->permissions); + if (f < 0) return -1; + + if (write(f, ed->start, ed->gap - ed->start) != ed->gap - ed->start) goto err; + if (write(f, ed->rest, ed->end - ed->rest) != ed->end - ed->rest) goto err; + + close(f); + ed->dirty = 0; + clear_undo(ed); + return 0; + +err: + close(f); + return -1; +} + +static int text_length(struct editor *ed) { + return (ed->gap - ed->start) + (ed->end - ed->rest); +} + +static unsigned char *text_ptr(struct editor *ed, int pos) { + unsigned char *p = ed->start + pos; + if (p >= ed->gap) p += (ed->rest - ed->gap); + return p; +} + +static void move_gap(struct editor *ed, int pos, int minsize) { + int gapsize = ed->rest - ed->gap; + unsigned char *p = text_ptr(ed, pos); + if (minsize < 0) minsize = 0; + + if (minsize <= gapsize) { + if (p != ed->rest) { + if (p < ed->gap) { + memmove(p + gapsize, p, ed->gap - p); + } else { + memmove(ed->gap, ed->rest, p - ed->rest); + } + ed->gap = ed->start + pos; + ed->rest = ed->gap + gapsize; + } + } else { + int newsize; + unsigned char *start; + unsigned char *gap; + unsigned char *rest; + unsigned char *end; + + if (gapsize + MINEXTEND > minsize) minsize = gapsize + MINEXTEND; + newsize = (ed->end - ed->start) - gapsize + minsize; + start = (unsigned char *) malloc(newsize); // TODO check for out of memory + gap = start + pos; + rest = gap + minsize; + end = start + newsize; + + if (p < ed->gap) { + memcpy(start, ed->start, pos); + memcpy(rest, p, ed->gap - p); + memcpy(end - (ed->end - ed->rest), ed->rest, ed->end - ed->rest); + } else { + memcpy(start, ed->start, ed->gap - ed->start); + memcpy(start + (ed->gap - ed->start), ed->rest, p - ed->rest); + memcpy(rest, p, ed->end - p); + } + + free(ed->start); + ed->start = start; + ed->gap = gap; + ed->rest = rest; + ed->end = end; + } + +#ifdef DEBUG + memset(ed->gap, 0, ed->rest - ed->gap); +#endif +} + +static void close_gap(struct editor *ed) { + int len = text_length(ed); + move_gap(ed, len, 1); + ed->start[len] = 0; +} + +static int get(struct editor *ed, int pos) { + unsigned char *p = text_ptr(ed, pos); + if (p >= ed->end) return -1; + return *p; +} + +static int compare(struct editor *ed, unsigned char *buf, int pos, int len) { + unsigned char *bufptr = buf; + unsigned char *p = ed->start + pos; + if (p >= ed->gap) p += (ed->rest - ed->gap); + + while (len > 0) { + if (p == ed->end) return 0; + if (*bufptr++ != *p) return 0; + len--; + if (++p == ed->gap) p = ed->rest; + } + + return 1; +} + +static int copy(struct editor *ed, unsigned char *buf, int pos, int len) { + unsigned char *bufptr = buf; + unsigned char *p = ed->start + pos; + if (p >= ed->gap) p += (ed->rest - ed->gap); + + while (len > 0) { + if (p == ed->end) break; + *bufptr++ = *p; + len--; + if (++p == ed->gap) p = ed->rest; + } + + return bufptr - buf; +} + +static void replace(struct editor *ed, int pos, int len, unsigned char *buf, int bufsize, int doundo) { + unsigned char *p; + struct undo *undo; + + // Store undo information + if (doundo) { + reset_undo(ed); + undo = ed->undotail; + if (undo && len == 0 && bufsize == 1 && undo->erased == 0 && pos == undo->pos + undo->inserted) { + // Insert character at end of current redo buffer + undo->redobuf = realloc(undo->redobuf, undo->inserted + 1); + undo->redobuf[undo->inserted] = *buf; + undo->inserted++; + } else if (undo && len == 1 && bufsize == 0 && undo->inserted == 0 && pos == undo->pos) { + // Erase character at end of current undo buffer + undo->undobuf = realloc(undo->undobuf, undo->erased + 1); + undo->undobuf[undo->erased] = get(ed, pos); + undo->erased++; + } else if (undo && len == 1 && bufsize == 0 && undo->inserted == 0 && pos == undo->pos - 1) { + // Erase character at beginning of current undo buffer + undo->pos--; + undo->undobuf = realloc(undo->undobuf, undo->erased + 1); + memmove(undo->undobuf + 1, undo->undobuf, undo->erased); + undo->undobuf[0] = get(ed, pos); + undo->erased++; + } else { + // Create new undo buffer + undo = (struct undo *) malloc(sizeof(struct undo)); + if (ed->undotail) ed->undotail->next = undo; + undo->prev = ed->undotail; + undo->next = NULL; + ed->undotail = ed->undo = undo; + if (!ed->undohead) ed->undohead = undo; + + undo->pos = pos; + undo->erased = len; + undo->inserted = bufsize; + undo->undobuf = undo->redobuf = NULL; + if (len > 0) { + undo->undobuf = malloc(len); + copy(ed, undo->undobuf, pos, len); + } + if (bufsize > 0) { + undo->redobuf = malloc(bufsize); + memcpy(undo->redobuf, buf, bufsize); + } + } + } + + p = ed->start + pos; + if (bufsize == 0 && p <= ed->gap && p + len >= ed->gap) { + // Handle deletions at the edges of the gap + ed->rest += len - (ed->gap - p); + ed->gap = p; + } else { + // Move the gap + move_gap(ed, pos + len, bufsize - len); + + // Replace contents + memcpy(ed->start + pos, buf, bufsize); + ed->gap = ed->start + pos + bufsize; + } + + // Mark buffer as dirty + ed->dirty = 1; +} + +static void insert(struct editor *ed, int pos, unsigned char *buf, int bufsize) { + replace(ed, pos, 0, buf, bufsize, 1); +} + +static void erase(struct editor *ed, int pos, int len) { + replace(ed, pos, len, NULL, 0, 1); +} + +// +// Navigation functions +// + +static int line_length(struct editor *ed, int linepos) { + int pos = linepos; + while (1) { + int ch = get(ed, pos); + if (ch < 0 || ch == '\n' || ch == '\r') break; + pos++; + } + + return pos - linepos; +} + +static int line_start(struct editor *ed, int pos) { + while (1) { + if (pos == 0) break; + if (get(ed, pos - 1) == '\n') break; + pos--; + } + + return pos; +} + +static int next_line(struct editor *ed, int pos) { + while (1) { + int ch = get(ed, pos); + if (ch < 0) return -1; + pos++; + if (ch == '\n') return pos; + } +} + +static int prev_line(struct editor *ed, int pos) { + if (pos == 0) return -1; + + while (pos > 0) { + int ch = get(ed, --pos); + if (ch == '\n') break; + } + + while (pos > 0) { + int ch = get(ed, --pos); + if (ch == '\n') return pos + 1; + } + + return 0; +} + +static int column(struct editor *ed, int linepos, int col) { + unsigned char *p = text_ptr(ed, linepos); + int c = 0; + while (col > 0) { + if (p == ed->end) break; + if (*p == '\t') { + int spaces = TABSIZE - c % TABSIZE; + c += spaces; + } else { + c++; + } + col--; + if (++p == ed->gap) p = ed->rest; + } + return c; +} + +static void moveto(struct editor *ed, int pos, int center) { + int scroll = 0; + for (;;) { + int cur = ed->linepos + ed->col; + if (pos < cur) { + if (pos >= ed->linepos) { + ed->col = pos - ed->linepos; + } else { + ed->col = 0; + ed->linepos = prev_line(ed, ed->linepos); + ed->line--; + + if (ed->topline > ed->line) { + ed->toppos = ed->linepos; + ed->topline--; + ed->refresh = 1; + scroll = 1; + } + } + } else if (pos > cur) { + int next = next_line(ed, ed->linepos); + if (next == -1) { + ed->col = line_length(ed, ed->linepos); + break; + } else if (pos < next) { + ed->col = pos - ed->linepos; + } else { + ed->col = 0; + ed->linepos = next; + ed->line++; + + if (ed->line >= ed->topline + ed->env->lines) { + ed->toppos = next_line(ed, ed->toppos); + ed->topline++; + ed->refresh = 1; + scroll = 1; + } + } + } else { + break; + } + } + + if (scroll && center) { + int tl = ed->line - ed->env->lines / 2; + if (tl < 0) tl = 0; + for (;;) { + if (ed->topline > tl) { + ed->toppos = prev_line(ed, ed->toppos); + ed->topline--; + } else if (ed->topline < tl) { + ed->toppos = next_line(ed, ed->toppos); + ed->topline++; + } else { + break; + } + } + } +} + +// +// Text selection +// + +static int get_selection(struct editor *ed, int *start, int *end) { + if (ed->anchor == -1) { + *start = *end = -1; + return 0; + } else { + int pos = ed->linepos + ed->col; + if (pos == ed->anchor) { + *start = *end = -1; + return 0; + } else if (pos < ed->anchor) { + *start = pos; + *end = ed->anchor; + } else { + *start = ed->anchor; + *end = pos; + } + } + return 1; +} + +static int get_selected_text(struct editor *ed, char *buffer, int size) { + int selstart, selend, len; + + if (!get_selection(ed, &selstart, &selend)) return 0; + len = selend - selstart; + if (len >= size) return 0; + copy(ed, (unsigned char*) buffer, selstart, len); + buffer[len] = 0; + return len; +} + +static void update_selection(struct editor *ed, int select) { + if (select) { + if (ed->anchor == -1) ed->anchor = ed->linepos + ed->col; + ed->refresh = 1; + } else { + if (ed->anchor != -1) ed->refresh = 1; + ed->anchor = -1; + } +} + +static int erase_selection(struct editor *ed) { + int selstart, selend; + + if (!get_selection(ed, &selstart, &selend)) return 0; + moveto(ed, selstart, 0); + erase(ed, selstart, selend - selstart); + ed->anchor = -1; + ed->refresh = 1; + return 1; +} + +static void select_all(struct editor *ed) { + ed->anchor = 0; + ed->refresh = 1; + moveto(ed, text_length(ed), 0); +} + +// +// Screen functions +// + +static void get_console_size(struct env *env) { +#ifdef __linux__ + struct winsize ws; + ioctl(0, TIOCGWINSZ, &ws); + env->cols = ws.ws_col; + env->lines = ws.ws_row - 1; +#elif defined(__rtems__) + env->cols = 80; + env->lines = 25; +#else + struct term *term = gettib()->proc->term; + env->cols = term->cols; + env->lines = term->lines - 1; +#endif + env->linebuf = realloc(env->linebuf, env->cols + LINEBUF_EXTRA); +} + +static void outch(char c) { + putchar(c); +} + +static void outbuf(unsigned char *buf, int len) { + fwrite(buf, 1, len, stdout); +} + +static void outstr(char *str) { + fputs(str, stdout); +} + +static void clear_screen() { + outstr(CLRSCR); +} + +static void gotoxy(int col, int line) { + char buf[32]; + + sprintf(buf, GOTOXY, line + 1, col + 1); + outstr(buf); +} + +// +// Keyboard functions +// + +static void get_modifier_keys(int *shift, int *ctrl) { + *shift = *ctrl = 0; +#ifdef __linux__ + if (linux_console) { + char modifiers = 6; + if (ioctl(0, TIOCLINUX, &modifiers) >= 0) { + if (modifiers & 1) *shift = 1; + if (modifiers & 4) *ctrl = 1; + } + } +#endif +} + +static int getachar() +{ + int ch = getchar(); +#if KEY_HISTORY + if (key_history_in < sizeof(key_history)) { + key_history[key_history_in++] = ch; + } else { + memmove(&key_history[0], &key_history[1], sizeof(key_history) - sizeof(key_history[0])); + key_history[key_history_in - 1] = ch; + } +#endif + return ch; +} + +static int getkey() { + int ch, shift, ctrl; + + ch = getachar(); + if (ch < 0) return ch; + + switch (ch) { + case 0x08: return KEY_BACKSPACE; + case 0x09: + get_modifier_keys(&shift, &ctrl); + if (shift) return KEY_SHIFT_TAB; + if (ctrl) return KEY_CTRL_TAB; + return KEY_TAB; +#ifdef SANOS + case 0x0D: return gettib()->proc->term->type == TERM_CONSOLE ? KEY_ENTER : KEY_UNKNOWN; + case 0x0A: return gettib()->proc->term->type != TERM_CONSOLE ? KEY_ENTER : KEY_UNKNOWN; +#else + case 0x0D: return KEY_ENTER; + case 0x0A: return KEY_ENTER; +#endif + case 0x1B: + ch = getachar(); + switch (ch) { + case 0x1B: return KEY_ESC; + case 0x4F: + ch = getachar(); + switch (ch) { + case 0x46: return KEY_END; + case 0x48: return KEY_HOME; + case 0x50: return KEY_F1; + case 0x51: return KEY_F2; + case 0x52: return KEY_F3; + case 0x53: return KEY_F4; + case 0x54: return KEY_F5; + default: return KEY_UNKNOWN; + } + break; + + case 0x5B: + get_modifier_keys(&shift, &ctrl); + ch = getachar(); + if (ch == 0x31) { + ch = getachar(); + switch (ch) { + case 0x35: + return getachar() == 0x7E ? KEY_F5 : KEY_UNKNOWN; + case 0x37: + return getachar() == 0x7E ? KEY_F6 : KEY_UNKNOWN; + case 0x3B: + ch = getachar(); + if (ch == 0x7E) return KEY_F7; + if (ch == 0x32) shift = 1; + if (ch == 0x35) ctrl = 1; + if (ch == 0x36) shift = ctrl = 1; + ch = getachar(); + break; + case 0x39: + return getachar() == 0x7E ? KEY_F8 : KEY_UNKNOWN; + case 0x7E: + if (shift && ctrl) return KEY_SHIFT_CTRL_HOME; + if (shift) return KEY_SHIFT_HOME; + if (ctrl) return KEY_CTRL_HOME; + return KEY_HOME; + default: + return KEY_UNKNOWN; + } + } + + switch (ch) { + case 0x31: + ch = getachar(); + if (ch != 0x7E) return KEY_UNKNOWN; + if (shift && ctrl) return KEY_SHIFT_CTRL_HOME; + if (shift) return KEY_SHIFT_HOME; + if (ctrl) return KEY_CTRL_HOME; + return KEY_HOME; + case 0x32: + ch = getachar(); + switch (ch) { + case 0x30: ch = getachar(); return KEY_F9; + case 0x31: ch = getachar(); return KEY_F10; + case 0x7E: return KEY_INS; + default: break; + } + return KEY_UNKNOWN; + case 0x33: return getachar() == 0x7E ? KEY_DEL : KEY_UNKNOWN; + case 0x34: + if (getachar() != 0x7E) return KEY_UNKNOWN; + if (shift && ctrl) return KEY_SHIFT_CTRL_END; + if (shift) return KEY_SHIFT_END; + if (ctrl) return KEY_CTRL_END; + return KEY_END; + case 0x35: + if (getachar() != 0x7E) return KEY_UNKNOWN; + if (shift) return KEY_SHIFT_PGUP; + return KEY_PGUP; + case 0x36: + if (getachar() != 0x7E) return KEY_UNKNOWN; + if (shift) return KEY_SHIFT_PGDN; + return KEY_PGDN; + case 0x41: + if (shift && ctrl) return KEY_SHIFT_CTRL_UP; + if (shift) return KEY_SHIFT_UP; + if (ctrl) return KEY_CTRL_UP; + return KEY_UP; + case 0x42: + if (shift && ctrl) return KEY_SHIFT_CTRL_DOWN; + if (shift) return KEY_SHIFT_DOWN; + if (ctrl) return KEY_CTRL_DOWN; + return KEY_DOWN; + case 0x43: + if (shift && ctrl) return KEY_SHIFT_CTRL_RIGHT; + if (shift) return KEY_SHIFT_RIGHT; + if (ctrl) return KEY_CTRL_RIGHT; + return KEY_RIGHT; + case 0x44: + if (shift && ctrl) return KEY_SHIFT_CTRL_LEFT; + if (shift) return KEY_SHIFT_LEFT; + if (ctrl) return KEY_CTRL_LEFT; + return KEY_LEFT; + case 0x46: + if (shift && ctrl) return KEY_SHIFT_CTRL_END; + if (shift) return KEY_SHIFT_END; + if (ctrl) return KEY_CTRL_END; + return KEY_END; + case 0x48: + if (shift && ctrl) return KEY_SHIFT_CTRL_HOME; + if (shift) return KEY_SHIFT_HOME; + if (ctrl) return KEY_CTRL_HOME; + return KEY_HOME; + case 0x5A: + return KEY_SHIFT_TAB; + case 0x5B: + ch = getachar(); + switch (ch) { + case 0x41: return KEY_F1; + case 0x43: return KEY_F3; + case 0x45: return KEY_F5; + } + return KEY_UNKNOWN; + + default: return KEY_UNKNOWN; + } + break; + + default: return KEY_UNKNOWN; + } + break; + + case 0x00: + case 0xE0: + ch = getachar(); + switch (ch) { + case 0x0F: return KEY_SHIFT_TAB; + case 0x3B: return KEY_F1; + case 0x3D: return KEY_F3; + case 0x3F: return KEY_F5; + case 0x47: return KEY_HOME; + case 0x48: return KEY_UP; + case 0x49: return KEY_PGUP; + case 0x4B: return KEY_LEFT; + case 0x4D: return KEY_RIGHT; + case 0x4F: return KEY_END; + case 0x50: return KEY_DOWN; + case 0x51: return KEY_PGDN; + case 0x52: return KEY_INS; + case 0x53: return KEY_DEL; + case 0x73: return KEY_CTRL_LEFT; + case 0x74: return KEY_CTRL_RIGHT; + case 0x75: return KEY_CTRL_END; + case 0x77: return KEY_CTRL_HOME; + case 0x8D: return KEY_CTRL_UP; + case 0x91: return KEY_CTRL_DOWN; + case 0x94: return KEY_CTRL_TAB; + case 0xB8: return KEY_SHIFT_UP; + case 0xB7: return KEY_SHIFT_HOME; + case 0xBF: return KEY_SHIFT_END; + case 0xB9: return KEY_SHIFT_PGUP; + case 0xBB: return KEY_SHIFT_LEFT; + case 0xBD: return KEY_SHIFT_RIGHT; + case 0xC0: return KEY_SHIFT_DOWN; + case 0xC1: return KEY_SHIFT_PGDN; + case 0xDB: return KEY_SHIFT_CTRL_LEFT; + case 0xDD: return KEY_SHIFT_CTRL_RIGHT; + case 0xD8: return KEY_SHIFT_CTRL_UP; + case 0xE0: return KEY_SHIFT_CTRL_DOWN; + case 0xD7: return KEY_SHIFT_CTRL_HOME; + case 0xDF: return KEY_SHIFT_CTRL_END; + + default: return KEY_UNKNOWN; + } + break; + + case 0x7F: return KEY_BACKSPACE; + + default: return ch; + } +} + +static int prompt(struct editor *ed, char *msg, int selection) { + int maxlen, len, ch; + char *buf = (char*) ed->env->linebuf; + + gotoxy(0, ed->env->lines); + outstr(STATUS_COLOR); + outstr(msg); + outstr(CLREOL); + + len = 0; + maxlen = ed->env->cols - strlen(msg) - 1; + if (selection) { + len = get_selected_text(ed, buf, maxlen); + outbuf((unsigned char*) buf, len); + } + + for (;;) { + fflush(stdout); + ch = getkey(); + if (ch == KEY_ESC) { + return 0; + } else if (ch == KEY_ENTER) { + buf[len] = 0; + return len > 0; + } else if (ch == KEY_BACKSPACE) { + if (len > 0) { + outstr("\b \b"); + len--; + } + } else if (ch >= ' ' && ch < 0x100 && len < maxlen) { + outch(ch); + buf[len++] = ch; + } + } +} + +static int ask() { + int ch = getachar(); + return ch == 'y' || ch == 'Y'; +} + +// +// Display functions +// + +static void display_message(struct editor *ed, char *fmt, ...) { + va_list args; + + va_start(args, fmt); + gotoxy(0, ed->env->lines); + outstr(STATUS_COLOR); + vprintf(fmt, args); + outstr(CLREOL TEXT_COLOR); + fflush(stdout); + va_end(args); +} + +static void draw_full_statusline(struct editor *ed) { + struct env *env = ed->env; + int namewidth = env->cols - 29; + gotoxy(0, env->lines); + sprintf((char*) env->linebuf, STATUS_COLOR "%*.*sF1=Help %c%c Ln %-6dCol %-4d" CLREOL TEXT_COLOR, -namewidth, namewidth, ed->filename, ed->selecting ? '+' : ' ', ed->dirty ? '*' : ' ', ed->line + 1, column(ed, ed->linepos, ed->col) + 1); + outstr((char*) env->linebuf); +} + +static void draw_statusline(struct editor *ed) { + gotoxy(ed->env->cols - 20, ed->env->lines); + sprintf((char*) ed->env->linebuf, STATUS_COLOR "%c Ln %-6dCol %-4d" CLREOL TEXT_COLOR, ed->dirty ? '*' : ' ', ed->line + 1, column(ed, ed->linepos, ed->col) + 1); + outstr((char*) ed->env->linebuf); +} + +static void display_line(struct editor *ed, int pos, int fullline) { + int hilite = 0; + int col = 0; + int margin = ed->margin; + int maxcol = ed->env->cols + margin; + unsigned char *bufptr = ed->env->linebuf; + unsigned char *p = text_ptr(ed, pos); + int selstart, selend, ch; + char *s; + + get_selection(ed, &selstart, &selend); + while (col < maxcol) { + if (margin == 0) { + if (!hilite && pos >= selstart && pos < selend) { + for (s = SELECT_COLOR; *s; s++) *bufptr++ = *s; + hilite = 1; + } else if (hilite && pos >= selend) { + for (s = TEXT_COLOR; *s; s++) *bufptr++ = *s; + hilite = 0; + } + } + + if (p == ed->end) break; + ch = *p; + if (ch == '\r' || ch == '\n') break; + + if (ch == '\t') { + int spaces = TABSIZE - col % TABSIZE; + while (spaces > 0 && col < maxcol) { + if (margin > 0) { + margin--; + } else { + *bufptr++ = ' '; + } + col++; + spaces--; + } + } else { + if (margin > 0) { + margin--; + } else { + *bufptr++ = ch; + } + col++; + } + + if (++p == ed->gap) p = ed->rest; + pos++; + } + +#if defined(__linux__) + if (hilite) { + while (col < maxcol) { + *bufptr++ = ' '; + col++; + } + } else { + if (col == margin) *bufptr++ = ' '; + } +#endif + + if (col < maxcol) { + for (s = CLREOL; *s; s++) *bufptr++ = *s; + if (fullline) { + memcpy(bufptr, "\r\n", 2); + bufptr += 2; + } + } + + if (hilite) { + for (s = TEXT_COLOR; *s; s++) *bufptr++ = *s; + } + + outbuf(ed->env->linebuf, bufptr - ed->env->linebuf); +} + +static void update_line(struct editor *ed) { + gotoxy(0, ed->line - ed->topline); + display_line(ed, ed->linepos, 0); +} + +static void draw_screen(struct editor *ed) { + int pos; + int i; + + gotoxy(0, 0); + outstr(TEXT_COLOR); + pos = ed->toppos; + for (i = 0; i < ed->env->lines; i++) { + if (pos < 0) { + outstr(CLREOL "\r\n"); + } else { + display_line(ed, pos, 1); + pos = next_line(ed, pos); + } + } +} + +static void position_cursor(struct editor *ed) { + int col = column(ed, ed->linepos, ed->col); + gotoxy(col - ed->margin, ed->line - ed->topline); +} + +// +// Cursor movement +// + +static void adjust(struct editor *ed) { + int col; + int ll = line_length(ed, ed->linepos); + ed->col = ed->lastcol; + if (ed->col > ll) ed->col = ll; + + col = column(ed, ed->linepos, ed->col); + while (col < ed->margin) { + ed->margin -= 4; + if (ed->margin < 0) ed->margin = 0; + ed->refresh = 1; + } + + while (col - ed->margin >= ed->env->cols) { + ed->margin += 4; + ed->refresh = 1; + } +} + +static void select_toggle(struct editor *ed) { + ed->selecting = ed->selecting ? 0 : 1; + update_selection(ed, ed->selecting); + adjust(ed); +} + +static void up(struct editor *ed, int select) { + int newpos; + + update_selection(ed, select); + + newpos = prev_line(ed, ed->linepos); + if (newpos < 0) return; + + ed->linepos = newpos; + ed->line--; + if (ed->line < ed->topline) { + ed->toppos = ed->linepos; + ed->topline = ed->line; + ed->refresh = 1; + } + + adjust(ed); +} + +static void down(struct editor *ed, int select) { + int newpos; + + update_selection(ed, select); + + newpos = next_line(ed, ed->linepos); + if (newpos < 0) return; + + ed->linepos = newpos; + ed->line++; + + if (ed->line >= ed->topline + ed->env->lines) { + ed->toppos = next_line(ed, ed->toppos); + ed->topline++; + ed->refresh = 1; + } + + adjust(ed); +} + +static void left(struct editor *ed, int select) { + update_selection(ed, select); + if (ed->col > 0) { + ed->col--; + } else { + int newpos = prev_line(ed, ed->linepos); + if (newpos < 0) return; + + ed->col = line_length(ed, newpos); + ed->linepos = newpos; + ed->line--; + if (ed->line < ed->topline) { + ed->toppos = ed->linepos; + ed->topline = ed->line; + ed->refresh = 1; + } + } + + ed->lastcol = ed->col; + adjust(ed); +} + +static void right(struct editor *ed, int select) { + update_selection(ed, select); + if (ed->col < line_length(ed, ed->linepos)) { + ed->col++; + } else { + int newpos = next_line(ed, ed->linepos); + if (newpos < 0) return; + + ed->col = 0; + ed->linepos = newpos; + ed->line++; + + if (ed->line >= ed->topline + ed->env->lines) { + ed->toppos = next_line(ed, ed->toppos); + ed->topline++; + ed->refresh = 1; + } + } + + ed->lastcol = ed->col; + adjust(ed); +} + +static int wordchar(int ch) { + return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9'); +} + +static void wordleft(struct editor *ed, int select) { + int pos, phase; + + update_selection(ed, select); + pos = ed->linepos + ed->col; + phase = 0; + while (pos > 0) { + int ch = get(ed, pos - 1); + if (phase == 0) { + if (wordchar(ch)) phase = 1; + } else { + if (!wordchar(ch)) break; + } + + pos--; + if (pos < ed->linepos) { + ed->linepos = prev_line(ed, ed->linepos); + ed->line--; + ed->refresh = 1; + } + } + ed->col = pos - ed->linepos; + if (ed->line < ed->topline) { + ed->toppos = ed->linepos; + ed->topline = ed->line; + } + + ed->lastcol = ed->col; + adjust(ed); +} + +static void wordright(struct editor *ed, int select) { + int pos, end, phase, next; + + update_selection(ed, select); + pos = ed->linepos + ed->col; + end = text_length(ed); + next = next_line(ed, ed->linepos); + phase = 0; + while (pos < end) { + int ch = get(ed, pos); + if (phase == 0) { + if (wordchar(ch)) phase = 1; + } else { + if (!wordchar(ch)) break; + } + + pos++; + if (pos == next) { + ed->linepos = next; + next = next_line(ed, ed->linepos); + ed->line++; + ed->refresh = 1; + } + } + ed->col = pos - ed->linepos; + if (ed->line >= ed->topline + ed->env->lines) { + ed->toppos = next_line(ed, ed->toppos); + ed->topline++; + } + + ed->lastcol = ed->col; + adjust(ed); +} + +static void home(struct editor *ed, int select) { + update_selection(ed, select); + ed->col = ed->lastcol = 0; + adjust(ed); +} + +static void end(struct editor *ed, int select) { + update_selection(ed, select); + ed->col = ed->lastcol = line_length(ed, ed->linepos); + adjust(ed); +} + +static void top(struct editor *ed, int select) { + update_selection(ed, select); + ed->toppos = ed->topline = ed->margin = 0; + ed->linepos = ed->line = ed->col = ed->lastcol = 0; + ed->refresh = 1; +} + +static void bottom(struct editor *ed, int select) { + update_selection(ed, select); + for (;;) { + int newpos = next_line(ed, ed->linepos); + if (newpos < 0) break; + + ed->linepos = newpos; + ed->line++; + + if (ed->line >= ed->topline + ed->env->lines) { + ed->toppos = next_line(ed, ed->toppos); + ed->topline++; + ed->refresh = 1; + } + } + ed->col = ed->lastcol = line_length(ed, ed->linepos); + adjust(ed); +} + +static void pageup(struct editor *ed, int select) { + int i; + + update_selection(ed, select); + if (ed->line < ed->env->lines) { + ed->linepos = ed->toppos = 0; + ed->line = ed->topline = 0; + } else { + for (i = 0; i < ed->env->lines; i++) { + int newpos = prev_line(ed, ed->linepos); + if (newpos < 0) return; + + ed->linepos = newpos; + ed->line--; + + if (ed->topline > 0) { + ed->toppos = prev_line(ed, ed->toppos); + ed->topline--; + } + } + } + + ed->refresh = 1; + adjust(ed); +} + +static void pagedown(struct editor *ed, int select) { + int i; + + update_selection(ed, select); + for (i = 0; i < ed->env->lines; i++) { + int newpos = next_line(ed, ed->linepos); + if (newpos < 0) break; + + ed->linepos = newpos; + ed->line++; + + ed->toppos = next_line(ed, ed->toppos); + ed->topline++; + } + + ed->refresh = 1; + adjust(ed); +} + +// +// Text editing +// + +static void insert_char(struct editor *ed, unsigned char ch) { + erase_selection(ed); + insert(ed, ed->linepos + ed->col, &ch, 1); + ed->col++; + ed->lastcol = ed->col; + adjust(ed); + if (!ed->refresh) ed->lineupdate = 1; +} + +static void newline(struct editor *ed) { + int p; + unsigned char ch; + + erase_selection(ed); +#if defined(__linux__) || defined(__rtems__) + insert(ed, ed->linepos + ed->col, (unsigned char*) "\n", 1); +#else + insert(ed, ed->linepos + ed->col, "\r\n", 2); +#endif + ed->col = ed->lastcol = 0; + ed->line++; + p = ed->linepos; + ed->linepos = next_line(ed, ed->linepos); + for (;;) { + ch = get(ed, p++); + if (ch == ' ' || ch == '\t') { + insert(ed, ed->linepos + ed->col, &ch, 1); + ed->col++; + } else { + break; + } + } + ed->lastcol = ed->col; + + ed->refresh = 1; + + if (ed->line >= ed->topline + ed->env->lines) { + ed->toppos = next_line(ed, ed->toppos); + ed->topline++; + ed->refresh = 1; + } + + adjust(ed); +} + +static void backspace(struct editor *ed) { + if (erase_selection(ed)) return; + if (ed->linepos + ed->col == 0) return; + if (ed->col == 0) { + int pos = ed->linepos; + erase(ed, --pos, 1); + if (get(ed, pos - 1) == '\r') erase(ed, --pos, 1); + + ed->line--; + ed->linepos = line_start(ed, pos); + ed->col = pos - ed->linepos; + ed->refresh = 1; + + if (ed->line < ed->topline) { + ed->toppos = ed->linepos; + ed->topline = ed->line; + } + } else { + ed->col--; + erase(ed, ed->linepos + ed->col, 1); + ed->lineupdate = 1; + } + + ed->lastcol = ed->col; + adjust(ed); +} + +static void del(struct editor *ed) { + int pos, ch; + + if (erase_selection(ed)) return; + pos = ed->linepos + ed->col; + ch = get(ed, pos); + if (ch < 0) return; + + erase(ed, pos, 1); + if (ch == '\r') { + ch = get(ed, pos); + if (ch == '\n') erase(ed, pos, 1); + } + + if (ch == '\n') { + ed->refresh = 1; + } else { + ed->lineupdate = 1; + } +} + +static void indent(struct editor *ed, unsigned char *indentation) { + int start, end, i, lines, toplines, newline, ch; + unsigned char *buffer, *p; + int buflen; + int width = strlen((const char*) indentation); + int pos = ed->linepos + ed->col; + + if (!get_selection(ed, &start, &end)) { + insert_char(ed, '\t'); + return; + } + + lines = 0; + toplines = 0; + newline = 1; + for (i = start; i < end; i++) { + if (i == ed->toppos) toplines = lines; + if (newline) { + lines++; + newline = 0; + } + if (get(ed, i) == '\n') newline = 1; + } + buflen = end - start + lines * width; + buffer = malloc(buflen); + if (!buffer) return; + + newline = 1; + p = buffer; + for (i = start; i < end; i++) { + if (newline) { + memcpy(p, indentation, width); + p += width; + newline = 0; + } + ch = get(ed, i); + *p++ = ch; + if (ch == '\n') newline = 1; + } + + replace(ed, start, end - start, buffer, buflen, 1); + free(buffer); + + if (ed->anchor < pos) { + pos += width * lines; + } else { + ed->anchor += width * lines; + } + + ed->toppos += width * toplines; + ed->linepos = line_start(ed, pos); + ed->col = ed->lastcol = pos - ed->linepos; + + adjust(ed); + ed->refresh = 1; +} + +static void unindent(struct editor *ed, unsigned char *indentation) { + int start, end, i, newline, ch, shrinkage, topofs; + unsigned char *buffer, *p; + int width = strlen((const char*) indentation); + int pos = ed->linepos + ed->col; + + if (!get_selection(ed, &start, &end)) return; + + buffer = malloc(end - start); + if (!buffer) return; + + newline = 1; + p = buffer; + i = start; + shrinkage = 0; + topofs = 0; + while (i < end) { + if (newline) { + newline = 0; + if (compare(ed, indentation, i, width)) { + i += width; + shrinkage += width; + if (i < ed->toppos) topofs -= width; + continue; + } + } + ch = get(ed, i++); + *p++ = ch; + if (ch == '\n') newline = 1; + } + + if (!shrinkage) { + free(buffer); + return; + } + + replace(ed, start, end - start, buffer, p - buffer, 1); + free(buffer); + + if (ed->anchor < pos) { + pos -= shrinkage; + } else { + ed->anchor -= shrinkage; + } + + ed->toppos += topofs; + ed->linepos = line_start(ed, pos); + ed->col = ed->lastcol = pos - ed->linepos; + + ed->refresh = 1; + adjust(ed); +} + +static void undo(struct editor *ed) { + if (!ed->undo) return; + moveto(ed, ed->undo->pos, 0); + replace(ed, ed->undo->pos, ed->undo->inserted, ed->undo->undobuf, ed->undo->erased, 0); + ed->undo = ed->undo->prev; + if (!ed->undo) ed->dirty = 0; + ed->anchor = -1; + ed->lastcol = ed->col; + ed->refresh = 1; +} + +static void redo(struct editor *ed) { + if (ed->undo) { + if (!ed->undo->next) return; + ed->undo = ed->undo->next; + } else { + if (!ed->undohead) return; + ed->undo = ed->undohead; + } + replace(ed, ed->undo->pos, ed->undo->erased, ed->undo->redobuf, ed->undo->inserted, 0); + moveto(ed, ed->undo->pos, 0); + ed->dirty = 1; + ed->anchor = -1; + ed->lastcol = ed->col; + ed->refresh = 1; +} + +// +// Clipboard +// + +static void copy_selection(struct editor *ed) { + int selstart, selend; + + if (!get_selection(ed, &selstart, &selend)) return; + ed->env->clipsize = selend - selstart; + ed->env->clipboard = (unsigned char *) realloc(ed->env->clipboard, ed->env->clipsize); + if (!ed->env->clipboard) return; + copy(ed, ed->env->clipboard, selstart, ed->env->clipsize); + select_toggle(ed); +} + +static void cut_selection(struct editor *ed) { + copy_selection(ed); + erase_selection(ed); + select_toggle(ed); +} + +static void paste_selection(struct editor *ed) { + erase_selection(ed); + insert(ed, ed->linepos + ed->col, ed->env->clipboard, ed->env->clipsize); + moveto(ed, ed->linepos + ed->col + ed->env->clipsize, 0); + ed->refresh = 1; +} + +// +// Editor Commands +// + +static void open_editor(struct editor *ed) { + int rc; + char *filename; + struct env *env = ed->env; + + if (!prompt(ed, "Open file: ", 1)) { + ed->refresh = 1; + return; + } + filename = (char*) ed->env->linebuf; + + ed = find_editor(ed->env, filename); + if (ed) { + env->current = ed; + } else { + ed = create_editor(env); + rc = load_file(ed, filename); + if (rc < 0) { + display_message(ed, "Error %d opening %s (%s)", errno, filename, strerror(errno)); + sleep(5); + delete_editor(ed); + ed = env->current; + } + } + ed->refresh = 1; +} + +static void new_editor(struct editor *ed) { + ed = create_editor(ed->env); + new_file(ed, ""); + ed->refresh = 1; +} + +static void read_from_stdin(struct editor *ed) { + char buffer[512]; + int n, pos; + + pos = 0; + while ((n = fread(buffer, 1, sizeof(buffer), stdin)) > 0) { + insert(ed, pos, (unsigned char*) buffer, n); + pos += n; + } + strcpy(ed->filename, ""); + ed->newfile = 1; + ed->dirty = 0; +} + +static void save_editor(struct editor *ed) { + int rc; + + if (!ed->dirty && !ed->newfile) return; + + if (ed->newfile) { + if (!prompt(ed, "Save as: ", 1)) { + ed->refresh = 1; + return; + } + + if (access((const char*) ed->env->linebuf, F_OK) == 0) { + display_message(ed, "Overwrite %s (y/n)? ", ed->env->linebuf); + if (!ask()) { + ed->refresh = 1; + return; + } + } + strcpy(ed->filename, (const char*) ed->env->linebuf); + ed->newfile = 0; + } + + rc = save_file(ed); + if (rc < 0) { + display_message(ed, "Error %d saving document (%s)", errno, strerror(errno)); + sleep(5); + } + + ed->refresh = 1; +} + +static void close_editor(struct editor *ed) { + struct env *env = ed->env; + + if (ed->dirty) { + display_message(ed, "Close %s without saving changes (y/n)? ", ed->filename); + if (!ask()) { + ed->refresh = 1; + return; + } + } + + delete_editor(ed); + + ed = env->current; + if (!ed) { + ed = create_editor(env); + new_file(ed, ""); + } + ed->refresh = 1; +} + +static void pipe_command(struct editor *ed) { +#ifdef __rtems__ + display_message(ed, "Not supported"); + sleep(3); +#else + FILE *f; + char buffer[512]; + int n; + int pos; + + if (!prompt(ed, "Command: ", 1)) { + ed->refresh = 1; + return; + } + +#ifdef SANOS + f = popen(ed->env->linebuf, "r2"); +#else + f = popen(ed->env->linebuf, "r"); +#endif + if (!f) { + display_message(ed, "Error %d running command (%s)", errno, strerror(errno)); + sleep(5); + } else { + erase_selection(ed); + pos = ed->linepos + ed->col; + while ((n = fread(buffer, 1, sizeof(buffer), f)) > 0) { + insert(ed, pos, buffer, n); + pos += n; + } + moveto(ed, pos, 0); + pclose(f); + } + ed->refresh = 1; +#endif +} + +static void find_text(struct editor *ed, int next) { + int slen; + + if (!next) { + if (!prompt(ed, "Find: ", 1)) { + ed->refresh = 1; + return; + } + if (ed->env->search) free(ed->env->search); + ed->env->search = (unsigned char*) strdup((const char*) ed->env->linebuf); + } + + if (!ed->env->search) return; + slen = strlen((const char*) ed->env->search); + if (slen > 0) { + unsigned char *match; + + close_gap(ed); + match = (unsigned char*) strstr((char*) ed->start + ed->linepos + ed->col, (char*) ed->env->search); + if (match != NULL) { + int pos = match - ed->start; + ed->anchor = pos; + moveto(ed, pos + slen, 1); + } else { + outch('\007'); + } + } + ed->refresh = 1; +} + +static void goto_line(struct editor *ed) { + int lineno, l, pos; + + ed->anchor = -1; + if (prompt(ed, "Goto line: ", 1)) { + lineno = atoi((char*) ed->env->linebuf); + if (lineno > 0) { + pos = 0; + for (l = 0; l < lineno - 1; l++) { + pos = next_line(ed, pos); + if (pos < 0) break; + } + } else { + pos = -1; + } + + if (pos >= 0) { + moveto(ed, pos, 1); + } else { + outch('\007'); + } + } + ed->refresh = 1; +} + +struct editor *next_file(struct editor *ed) { + ed = ed->env->current = ed->next; + ed->refresh = 1; + return ed; +} + +static void jump_to_editor(struct editor *ed) { + struct env *env = ed->env; + char filename[FILENAME_MAX]; + int lineno = 0; + + if (!get_selected_text(ed, filename, FILENAME_MAX)) { + int pos = ed->linepos + ed->col; + char *p = filename; + int left = FILENAME_MAX - 1; + while (left > 0) { + int ch = get(ed, pos); + if (ch < 0) break; + if (strchr("!@\"'#%&()[]{}*?+:;\r\n\t ", ch)) break; + *p++ = ch; + left--; + pos++; + } + *p = 0; + + if (get(ed, pos) == ':') { + pos++; + for (;;) { + int ch = get(ed, pos); + if (ch < 0) break; + if (ch >= '0' && ch <= '9') { + lineno = lineno * 10 + (ch - '0'); + } else { + break; + } + pos++; + } + } + } + if (!*filename) return; + + ed = find_editor(env, filename); + if (ed) { + env->current = ed; + } else { + ed = create_editor(env); + if (load_file(ed, filename) < 0) { + outch('\007'); + delete_editor(ed); + ed = env->current; + } + } + + if (lineno > 0) { + int pos = 0; + while (--lineno > 0) { + pos = next_line(ed, pos); + if (pos < 0) break; + } + if (pos >= 0) moveto(ed, pos, 1); + } + + ed->refresh = 1; +} + +static void redraw_screen(struct editor *ed) { + get_console_size(ed->env); + draw_screen(ed); +} + +static int quit(struct env *env) { + struct editor *ed = env->current; + struct editor *start = ed; + + do { + if (ed->dirty) { + display_message(ed, "Close %s without saving changes (y/n)? ", ed->filename); + if (!ask()) return 0; + } + ed = ed->next; + } while (ed != start); + + return 1; +} + +static void help(struct editor *ed) { + gotoxy(0, 0); + clear_screen(); + outstr("Editor Command Summary\r\n"); + outstr("======================\r\n\r\n"); + outstr(" Move one line up (*) Ctrl+N New editor\r\n"); + outstr(" Move one line down (*) Ctrl+O Open file\r\n"); + outstr(" Move one character left (*) Ctrl+S Save file\r\n"); + outstr(" Move one character right (*) Ctrl+W Close file\r\n"); + outstr(" Move one page up (*) Ctrl+Q Quit\r\n"); + outstr(" Move one page down (*) Ctrl+P Pipe command\r\n"); + outstr("Ctrl+ Move to previous word (*) Ctrl+A Select all\r\n"); + outstr("Ctrl+ Move to next word (*) Ctrl+C Copy selection to clipboard\r\n"); + outstr(" Move to start of line (*) Ctrl+X Cut selection to clipboard\r\n"); + outstr(" Move to end of line (*) Ctrl+V Paste from clipboard\r\n"); + outstr("Ctrl+ Move to start of file (*) Ctrl+Z Undo\r\n"); + outstr("Ctrl+ Move to end of file (*) Ctrl+R Redo\r\n"); + outstr(" Delete previous character Ctrl+F Find text\r\n"); + outstr(" Delete current character Ctrl+G Find next\r\n"); + outstr("Ctrl+ Next editor Ctrl+L Goto line\r\n"); + outstr(" Indent selection F1 Help\r\n"); + outstr("Shift+ Unindent selection F2 Select toggle\r\n"); + outstr(" (*) Extends selection, F2 toggles. F3 Navigate to file\r\n"); + outstr(" F4 Copy selection to clipboard\r\n"); + outstr(" Ctrl-Q/S may not work over F5 Redraw screen\r\n"); + outstr(" serial links, use funcions keys F9 Save file\r\n"); + outstr(" F10 Quit\r\n"); + outstr("Press any key to continue..."); + fflush(stdout); + + getkey(); + draw_screen(ed); + draw_full_statusline(ed); +} + +// +// Editor +// + +static void edit(struct editor *ed) { + int done = 0; + int key; + + ed->refresh = 1; + while (!done) { + if (ed->refresh) { + draw_screen(ed); + draw_full_statusline(ed); + ed->refresh = 0; + ed->lineupdate = 0; + } else if (ed->lineupdate) { + update_line(ed); + ed->lineupdate = 0; + draw_statusline(ed); + } else { + draw_statusline(ed); + } + + position_cursor(ed); + fflush(stdout); + key = getkey(); + + if (key >= ' ' && key <= 0x7F) { +#ifdef LESS + switch (key) { + case 'q': done = 1; break; + case '/': find_text(ed, 0); break; + } +#else + insert_char(ed, (unsigned char) key); +#endif + } else { + switch (key) { + case KEY_F1: help(ed); break; + case KEY_F2: select_toggle(ed); break; + case KEY_F3: jump_to_editor(ed); ed = ed->env->current; break; + case KEY_F4: copy_selection(ed); break; + case KEY_F5: redraw_screen(ed); break; + case KEY_F9: save_editor(ed); break; + case KEY_F10: done = 1; break; + +#if defined(__linux__) || defined(__rtems__) + case ctrl('y'): help(ed); break; + case ctrl('t'): top(ed, 0); break; + case ctrl('b'): bottom(ed, 0); break; +#endif + + case KEY_UP: up(ed, ed->selecting); break; + case KEY_DOWN: down(ed, ed->selecting); break; + case KEY_LEFT: left(ed, ed->selecting); break; + case KEY_RIGHT: right(ed, ed->selecting); break; + case KEY_HOME: home(ed, ed->selecting); break; + case KEY_END: end(ed, ed->selecting); break; + case KEY_PGUP: pageup(ed, ed->selecting); break; + case KEY_PGDN: pagedown(ed, ed->selecting); break; + + case KEY_CTRL_RIGHT: wordright(ed, ed->selecting); break; + case KEY_CTRL_LEFT: wordleft(ed, ed->selecting); break; + case KEY_CTRL_HOME: top(ed, ed->selecting); break; + case KEY_CTRL_END: bottom(ed, ed->selecting); break; + +#if SHIFT_SELECT + case KEY_SHIFT_UP: up(ed, 1); break; + case KEY_SHIFT_DOWN: down(ed, 1); break; + case KEY_SHIFT_LEFT: left(ed, 1); break; + case KEY_SHIFT_RIGHT: right(ed, 1); break; + case KEY_SHIFT_PGUP: pageup(ed, 1); break; + case KEY_SHIFT_PGDN: pagedown(ed, 1); break; + case KEY_SHIFT_HOME: home(ed, 1); break; + case KEY_SHIFT_END: end(ed, 1); break; + + case KEY_SHIFT_CTRL_RIGHT: wordright(ed, 1); break; + case KEY_SHIFT_CTRL_LEFT: wordleft(ed, 1); break; + case KEY_SHIFT_CTRL_HOME: top(ed, 1); break; + case KEY_SHIFT_CTRL_END: bottom(ed, 1); break; +#endif + + case KEY_CTRL_TAB: ed = next_file(ed); break; + + case ctrl('e'): select_toggle(ed); break; + case ctrl('a'): select_all(ed); break; + case ctrl('c'): copy_selection(ed); break; + case ctrl('f'): find_text(ed, 0); break; + case ctrl('l'): goto_line(ed); break; + case ctrl('g'): find_text(ed, 1); break; + case ctrl('q'): done = 1; break; +#ifdef LESS + case KEY_ESC: done = 1; break; +#else + case KEY_TAB: indent(ed, (unsigned char*) INDENT); break; + case KEY_SHIFT_TAB: unindent(ed, (unsigned char*) INDENT); break; + + case KEY_ENTER: newline(ed); break; + case KEY_BACKSPACE: backspace(ed); break; + case KEY_DEL: del(ed); break; + case ctrl('x'): cut_selection(ed); break; + case ctrl('z'): undo(ed); break; + case ctrl('r'): redo(ed); break; + case ctrl('v'): paste_selection(ed); break; + case ctrl('o'): open_editor(ed); ed = ed->env->current; break; + case ctrl('n'): new_editor(ed); ed = ed->env->current; break; + case ctrl('s'): save_editor(ed); break; + case ctrl('p'): pipe_command(ed); break; +#endif + case ctrl('w'): close_editor(ed); ed = ed->env->current; break; + } + } + } +} + +// +// main +// + +int rtems_shell_main_edit(int argc, char *argv[]) { + struct env env; + int rc; + int i; + sigset_t blocked_sigmask, orig_sigmask; +#if defined(__linux__) || defined(__rtems__) + struct termios tio; + struct termios orig_tio; +#endif +#ifdef SANOS + struct term *term; +#endif + + memset(&env, 0, sizeof(env)); + for (i = 1; i < argc; i++) { + struct editor *ed = create_editor(&env); + rc = load_file(ed, argv[i]); + if (rc < 0 && errno == ENOENT) rc = new_file(ed, argv[i]); + if (rc < 0) { + perror(argv[i]); + return 0; + } + } + if (env.current == NULL) { + struct editor *ed = create_editor(&env); + if (isatty(fileno(stdin))) { + new_file(ed, ""); + } else { + read_from_stdin(ed); + } + } + env.current = env.current->next; + +#ifdef SANOS + term = gettib()->proc->term; + if (fdin != term->ttyin) dup2(term->ttyin, fdin); + if (fdout != term->ttyout) dup2(term->ttyout, fdout); +#elif !defined(__rtems__) + if (!isatty(fileno(stdin))) { + if (!freopen("/dev/tty", "r", stdin)) perror("/dev/tty"); + } +#endif + + setvbuf(stdout, NULL, 0, 8192); + +#if defined(__linux__) || defined(__rtems__) + tcgetattr(0, &orig_tio); +#if !defined(__rtems__) + cfmakeraw(&tio); + tcsetattr(0, TCSANOW, &tio); +#endif + if (getenv("TERM") && strcmp(getenv("TERM"), "linux") == 0) { + linux_console = 1; + } else { + outstr(CLRSCR); + outstr("\033[3 q"); // xterm + outstr("\033]50;CursorShape=2\a"); // KDE + } +#endif + + get_console_size(&env); + + sigemptyset(&blocked_sigmask); + sigaddset(&blocked_sigmask, SIGINT); + sigaddset(&blocked_sigmask, SIGTSTP); + sigaddset(&blocked_sigmask, SIGABRT); + sigprocmask(SIG_BLOCK, &blocked_sigmask, &orig_sigmask); + + for (;;) { + if (!env.current) break; + edit(env.current); + if (quit(&env)) break; + } + + gotoxy(0, env.lines + 1); + outstr(RESET_COLOR CLREOL); +#if defined(__linux__) || defined(__rtems__) + tcsetattr(0, TCSANOW, &orig_tio); +#endif + + while (env.current) delete_editor(env.current); + + if (env.clipboard) free(env.clipboard); + if (env.search) free(env.search); + if (env.linebuf) free(env.linebuf); + + setbuf(stdout, NULL); + sigprocmask(SIG_SETMASK, &orig_sigmask, NULL); + + return 0; +} + +rtems_shell_cmd_t rtems_shell_EDIT_Command = { + "edit", /* name */ + "edit [file ...]", /* usage */ + "files", /* topic */ + rtems_shell_main_edit, /* command */ + NULL, /* alias */ + NULL /* next */ +}; diff --git a/cpukit/libmisc/shell/shellconfig.h b/cpukit/libmisc/shell/shellconfig.h index eacfac28f9..377695287c 100644 --- a/cpukit/libmisc/shell/shellconfig.h +++ b/cpukit/libmisc/shell/shellconfig.h @@ -39,6 +39,7 @@ extern rtems_shell_cmd_t rtems_shell_MMOVE_Command; extern rtems_shell_cmd_t rtems_shell_JOEL_Command; extern rtems_shell_cmd_t rtems_shell_DATE_Command; extern rtems_shell_cmd_t rtems_shell_ECHO_Command; +extern rtems_shell_cmd_t rtems_shell_EDIT_Command; extern rtems_shell_cmd_t rtems_shell_SLEEP_Command; extern rtems_shell_cmd_t rtems_shell_ID_Command; extern rtems_shell_cmd_t rtems_shell_TTY_Command; @@ -174,6 +175,11 @@ extern rtems_shell_alias_t *rtems_shell_Initial_aliases[]; defined(CONFIGURE_SHELL_COMMAND_ECHO) &rtems_shell_ECHO_Command, #endif + #if (defined(CONFIGURE_SHELL_COMMANDS_ALL) && \ + !defined(CONFIGURE_SHELL_NO_COMMAND_EDIT)) || \ + defined(CONFIGURE_SHELL_COMMAND_EDIT) + &rtems_shell_EDIT_Command, + #endif #if (defined(CONFIGURE_SHELL_COMMANDS_ALL) && \ !defined(CONFIGURE_SHELL_NO_COMMAND_SLEEP)) || \ defined(CONFIGURE_SHELL_COMMAND_SLEEP) -- cgit v1.2.3