| /* |
| * Copyright (c) 2007 Sascha Hauer <s.hauer@pengutronix.de>, Pengutronix |
| * |
| * See file CREDITS for list of people who contributed to this |
| * project. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 |
| * as published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| */ |
| |
| /** |
| * @file |
| * @brief A tiny editor implementation |
| */ |
| |
| #include <common.h> |
| #include <command.h> |
| #include <malloc.h> |
| #include <fs.h> |
| #include <linux/ctype.h> |
| #include <fcntl.h> |
| #include <readkey.h> |
| #include <errno.h> |
| #include <xfuncs.h> |
| #include <linux/stat.h> |
| |
| #define TABSPACE 8 |
| |
| struct line { |
| int length; |
| struct line *next; |
| struct line *prev; |
| char *data; |
| }; |
| |
| static struct line *buffer; |
| |
| static struct line *lastscrline; |
| |
| static int screenwidth = 80; |
| static int screenheight = 25; |
| |
| static int cursx = 0; /* position on screen */ |
| static int cursy = 0; |
| |
| static int textx = 0; /* position in text */ |
| |
| static struct line *curline; /* line where the cursor is */ |
| |
| static struct line *scrline; /* the first line on screen */ |
| static int scrcol = 0; /* the first column on screen */ |
| |
| static void pos(int x, int y) |
| { |
| printf("%c[%d;%dH", 27, y + 1, x + 1); |
| } |
| |
| static char *screenline(char *line, int *pos) |
| { |
| int i, outpos = 0; |
| static char lbuf[1024]; |
| |
| memset(lbuf, 0, 1024); |
| |
| if (!line) { |
| lbuf[0] = '~'; |
| return lbuf; |
| } |
| |
| for (i = 0; outpos < 1024; i++) { |
| if (i == textx && pos) |
| *pos = outpos; |
| if (!line[i]) |
| break; |
| if (line[i] == '\t') { |
| lbuf[outpos++] = ' '; |
| while (outpos % TABSPACE) |
| lbuf[outpos++] = ' '; |
| continue; |
| } |
| lbuf[outpos++] = line[i]; |
| } |
| |
| return lbuf; |
| } |
| |
| static int setpos(char *line, int position) |
| { |
| int i = 0; |
| int linepos = 0; |
| |
| while(line[linepos]) { |
| if (line[linepos] == '\t') |
| while ((i + 1) % TABSPACE) |
| i++; |
| if (i >= position) |
| return linepos; |
| linepos++; |
| i++; |
| } |
| |
| return linepos; |
| } |
| |
| static void refresh_line(struct line *line, int ypos) |
| { |
| char *str = screenline(line->data, NULL) + scrcol; |
| pos(0, ypos); |
| str[screenwidth] = 0; |
| printf("%s%c[K", str, 27); |
| pos(cursx, cursy); |
| } |
| |
| /* |
| * Most sane terminal programs can do ansi screen scrolling. |
| * Unfortunately one of the most popular programs cannot: |
| * minicom. |
| * Grmpf! |
| */ |
| static int smartscroll = 0; |
| |
| static void refresh(int full) |
| { |
| int i; |
| struct line *l = scrline; |
| |
| if (!full) { |
| if (smartscroll) { |
| if (scrline->next == lastscrline) { |
| printf("%c[1T", 27); |
| refresh_line(scrline, 0); |
| pos(0, screenheight); |
| printf("%*s", screenwidth, ""); |
| return; |
| } |
| |
| if (scrline->prev == lastscrline) { |
| printf("%c[1S", 27); |
| for (i = 0; i < screenheight - 1; i++) { |
| l = l->next; |
| if (!l) |
| return; |
| } |
| refresh_line(l, screenheight - 1); |
| return; |
| } |
| } else { |
| refresh(1); |
| return; |
| } |
| } |
| |
| for (i = 0; i < screenheight; i++) { |
| refresh_line(l, i); |
| l = l->next; |
| if (!l) |
| break; |
| } |
| |
| i++; |
| while (i < screenheight) { |
| pos(0, i++); |
| printf("~"); |
| } |
| } |
| |
| static void line_free(struct line *line) |
| { |
| free(line->data); |
| free(line); |
| } |
| |
| static struct line *line_realloc(int len, struct line *line) |
| { |
| int size = 32; |
| |
| if (!line) |
| line = xzalloc(sizeof(struct line)); |
| |
| while (size < len) |
| size <<= 1; |
| |
| line->data = xrealloc(line->data, size); |
| return line; |
| } |
| |
| static int edit_read_file(const char *path) |
| { |
| struct line *line; |
| struct line *lastline = NULL; |
| char *filebuffer; |
| char *linestr, *lineend; |
| struct stat s; |
| |
| if (!stat(path, &s)) { |
| filebuffer = read_file(path, NULL); |
| if (!filebuffer) { |
| printf("could not read %s: %s\n", path, errno_str()); |
| return -1; |
| } |
| |
| linestr = filebuffer; |
| while (1) { |
| if (!*linestr) |
| break; |
| |
| lineend = strchr(linestr, '\n'); |
| |
| if (!lineend && !*linestr) |
| break; |
| |
| if (lineend) |
| *lineend = 0; |
| |
| line = line_realloc(strlen(linestr) + 1, NULL); |
| if (!buffer) |
| buffer = line; |
| memcpy(line->data, linestr, strlen(linestr) + 1); |
| line->prev = lastline; |
| if (lastline) |
| lastline->next = line; |
| line->next = NULL; |
| lastline = line; |
| |
| if (!lineend) |
| break; |
| |
| linestr = lineend + 1; |
| } |
| free(filebuffer); |
| } |
| |
| if (!buffer) { |
| buffer = line_realloc(0, NULL); |
| buffer->data[0] = 0; |
| } |
| |
| return 0; |
| } |
| |
| static void free_buffer(void) |
| { |
| struct line *line, *tmp; |
| |
| line = buffer; |
| |
| while(line) { |
| tmp = line->next; |
| line_free(line); |
| line = tmp; |
| } |
| } |
| |
| static int save_file(const char *path) |
| { |
| struct line *line, *tmp; |
| int fd; |
| |
| fd = open(path, O_WRONLY | O_TRUNC | O_CREAT); |
| if (fd < 0) { |
| printf("could not open file for writing: %s\n", errno_str()); |
| return -1; |
| } |
| |
| line = buffer; |
| |
| while(line) { |
| tmp = line->next; |
| write(fd, line->data, strlen(line->data)); |
| write(fd, "\n", 1); |
| line = tmp; |
| } |
| close(fd); |
| return 0; |
| } |
| |
| static void insert_char(char c) |
| { |
| int pos = textx; |
| char *line; |
| int end = strlen(curline->data); |
| |
| line_realloc(strlen(curline->data) + 2, curline); |
| line = curline->data; |
| |
| while (end >= pos) { |
| line[end + 1] = line[end]; |
| end--; |
| } |
| line[pos] = c; |
| textx++; |
| refresh_line(curline, cursy); |
| } |
| |
| static void delete_char(int pos) |
| { |
| char *line = curline->data; |
| int end = strlen(line); |
| |
| while (pos < end) { |
| line[pos] = line[pos + 1]; |
| pos++; |
| } |
| refresh_line(curline, cursy); |
| } |
| |
| static void split_line(void) |
| { |
| int length = strlen(curline->data + textx); |
| struct line *newline = line_realloc(length + 1, NULL); |
| struct line *tmp; |
| |
| memcpy(newline->data, curline->data + textx, length + 1); |
| |
| curline->data[textx] = 0; |
| |
| tmp = curline->next; |
| curline->next = newline; |
| newline->prev = curline; |
| newline->next = tmp; |
| if (tmp) |
| tmp->prev = newline; |
| |
| textx = 0; |
| cursy++; |
| curline = curline->next; |
| refresh(1); |
| } |
| |
| static void merge_line(struct line *line) |
| { |
| struct line *tmp; |
| |
| line_realloc(strlen(line->data) + strlen(line->next->data) + 1, line); |
| |
| tmp = line->next; |
| |
| line->next = line->next->next; |
| if (line->next) |
| line->next->prev = line; |
| strcat(line->data, tmp->data); |
| |
| line_free(tmp); |
| |
| refresh(1); |
| } |
| |
| /* not a good idea on slow serial lines */ |
| /* #define GETWINSIZE */ |
| |
| #ifdef GETWINSIZE |
| static void getwinsize(void) { |
| int y, yy = 25, xx = 80, i, n, r; |
| char buf[100]; |
| char *endp; |
| |
| for (y = 25; y < 320; y++) { |
| pos(y, y); |
| printf("%c[6n", 27); |
| i = 0; |
| while ((r = getc()) != 'R') { |
| buf[i] = r; |
| i++; |
| } |
| n = simple_strtoul(buf + 2, &endp, 10); |
| if (n == y + 1) |
| yy = y + 1; |
| n = simple_strtoul(endp + 1, NULL, 10); |
| if (n == y + 1) |
| xx = y + 1; |
| } |
| pos(0,0); |
| screenheight = yy; |
| screenwidth = xx; |
| printf("%d %d\n", xx, yy); |
| mdelay(1000); |
| } |
| #endif |
| |
| static int do_edit(struct command * cmdtp, int argc, char *argv[]) |
| { |
| int lastscrcol; |
| int i; |
| int linepos; |
| int c; |
| |
| if (argc != 2) |
| return COMMAND_ERROR_USAGE; |
| |
| /* check if we are called as "sedit" instead of "edit" */ |
| if (*argv[0] == 's') |
| smartscroll = 1; |
| |
| buffer = NULL; |
| if(edit_read_file(argv[1])) |
| return 1; |
| |
| #ifdef GETWINSIZE |
| getwinsize(); |
| #endif |
| |
| cursx = 0; |
| cursy = 0; |
| textx = 0; |
| scrcol = 0; |
| curline = buffer; |
| scrline = curline; |
| lastscrline = scrline; |
| lastscrcol = 0; |
| |
| printf("%c[2J", 27); |
| refresh(1); |
| |
| while (1) { |
| int curlen = strlen(curline->data); |
| |
| if (textx > curlen) |
| textx = curlen; |
| if (textx < 0) |
| textx = 0; |
| |
| screenline(curline->data, &linepos); |
| |
| if (linepos > scrcol + screenwidth) |
| scrcol = linepos - screenwidth; |
| |
| if (scrcol > linepos) |
| scrcol = linepos; |
| |
| cursx = linepos - scrcol; |
| |
| while (cursy >= screenheight) { |
| cursy--; |
| scrline = scrline->next; |
| } |
| |
| while (cursy < 0) { |
| cursy++; |
| scrline = scrline->prev; |
| } |
| |
| if (scrline != lastscrline || scrcol != lastscrcol) |
| refresh(0); |
| |
| lastscrcol = scrcol; |
| lastscrline = scrline; |
| pos(cursx, cursy); |
| |
| c = read_key(); |
| switch (c) { |
| case KEY_UP: |
| if (!curline->prev) |
| continue; |
| |
| curline = curline->prev; |
| cursy--; |
| textx = setpos(curline->data, linepos); |
| break; |
| case KEY_DOWN: |
| if (!curline->next) |
| continue; |
| |
| curline = curline->next; |
| cursy++; |
| textx = setpos(curline->data, linepos); |
| break; |
| case KEY_RIGHT: |
| textx++; |
| break; |
| case KEY_LEFT: |
| textx--; |
| break; |
| case KEY_HOME: |
| textx = 0; |
| break; |
| case KEY_END: |
| textx = curlen; |
| break; |
| case KEY_PAGEUP: |
| for (i = 0; i < screenheight - 1; i++) { |
| if (!curline->prev) |
| break; |
| cursy--; |
| curline = curline->prev; |
| } |
| textx = setpos(curline->data, linepos); |
| break; |
| case KEY_PAGEDOWN: |
| for (i = 0; i < screenheight - 1; i++) { |
| if (!curline->next) |
| break; |
| cursy++; |
| curline = curline->next; |
| } |
| textx = setpos(curline->data, linepos); |
| break; |
| case KEY_DEL: |
| if (textx == curlen) { |
| if (curline->next) |
| merge_line(curline); |
| } else |
| delete_char(textx); |
| break; |
| case 13: |
| case 10: |
| split_line(); |
| break; |
| case 127: |
| case 8: |
| if (textx > 0) { |
| textx--; |
| delete_char(textx); |
| } else { |
| if (!curline->prev) |
| break; |
| curline = curline->prev; |
| cursy--; |
| textx = strlen(curline->data); |
| merge_line(curline); |
| } |
| break; |
| case 4: |
| save_file(argv[1]); |
| goto out; |
| case 3: |
| goto out; |
| default: |
| if ((signed char)c != -1) |
| insert_char(c); |
| } |
| } |
| out: |
| free_buffer(); |
| printf("%c[2J", 27); |
| printf("\n"); |
| return 0; |
| } |
| |
| static const char *edit_aliases[] = { "sedit", NULL}; |
| |
| BAREBOX_CMD_HELP_START(edit) |
| BAREBOX_CMD_HELP_USAGE("(s)edit <file>\n") |
| BAREBOX_CMD_HELP_SHORT("A small editor. <ctrl-c> is exit, <ctrl-d> exit-with-save.\n") |
| BAREBOX_CMD_HELP_END |
| |
| /** |
| * @page edit_command |
| |
| <p> Barebox contains a small text editor which can be used to edit |
| config files in /env. You can move the cursor around with the arrow keys |
| and type characters. </p> |
| |
| If called as sedit, the editor uses ansi codes to scroll the screen. |
| */ |
| |
| BAREBOX_CMD_START(edit) |
| .cmd = do_edit, |
| .aliases = edit_aliases, |
| .usage = "Usage: (s)edit <file>", |
| BAREBOX_CMD_HELP(cmd_edit_help) |
| BAREBOX_CMD_END |
| |