From 34c5ee4c2a489a506e93d5b303fbc80b263747f0 Mon Sep 17 00:00:00 2001 From: onefang Date: Thu, 27 Feb 2020 11:18:47 +1000 Subject: The new sledjchisl binary, to replace the management scripts and web stuff. --- src/boxes.c | 2607 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2607 insertions(+) create mode 100644 src/boxes.c (limited to 'src/boxes.c') diff --git a/src/boxes.c b/src/boxes.c new file mode 100644 index 0000000..0a293b4 --- /dev/null +++ b/src/boxes.c @@ -0,0 +1,2607 @@ +/* boxes.c - Generic editor development sandbox. + * + * Copyright 2012 David Seikel + * + * Not in SUSv4. An entirely new invention, thus no web site either. + * See - + * http://pubs.opengroup.org/onlinepubs/9699919799/utilities/ex.html + * http://pubs.opengroup.org/onlinepubs/9699919799/utilities/more.html + * http://pubs.opengroup.org/onlinepubs/9699919799/utilities/sed.html + * http://pubs.opengroup.org/onlinepubs/9699919799/utilities/vi.html + * http://linux.die.net/man/1/less + +USE_BOXES(NEWTOY(boxes, "w#h#m(mode):a(stickchars)1", TOYFLAG_USR|TOYFLAG_BIN)) + +config BOXES + bool "boxes" + default y + help + usage: boxes [-m|--mode mode] [-a|--stickchars] [-w width] [-h height] + + Generic text editor and pager. + + Mode selects which editor or text viewr it emulates, the choices are - + emacs is a microemacs type editor. + joe is a joe / wordstar type editor. + less is a less type pager. + mcedit (the default) is cooledit / mcedit type editor. + more is a more type pager. + nano is a nano / pico type editor. + vi is a vi type editor. + + Stick chars means to use ASCII for the boxes instead of "graphics" characters. +*/ + + +#include "toys.h" +#include "lib/handlekeys.h" + +#include +#include +#include + + + +GLOBALS( + char *mode; + long h, w; +) + +#define TT this.boxes + +#define FLAG_a 2 +#define FLAG_m 4 +#define FLAG_h 8 +#define FLAG_w 16 + + +/* This is trying to be a generic text editing, text viewing, and terminal + * handling system. The current code is a work in progress, and the design + * may change. Certainly at this moment it's only partly written. It is + * "usable" though, for a very small value of "usable". In the following + * I'll use "editors" to refer to the toys using this, though not all will + * be editors. + * + * The things it is targeting are - vi and more (part of the standards, so + * part of the toybox TODO), less (also on the toybox TODO), joe and + * wordstar (coz Rob said they would be good to include), nano (again Rob + * thinks it would be good and I agree), microemacs (to avoid religous + * wars), and mcedit (coz that's what I actually use). The ex editor comes + * along for the ride coz vi is basically a screen editor wrapper around + * the ex line editor. Sed might be supported coz I'll need to do basic + * editing functions that are common to the editors, and sed needs the same + * editing functions. + * + * I will also use this for a midnight commander clone as discussed on the + * mailing list. This would have things in common with emacs dired, so we + * might get that as well. Parts of this code could also be used for a + * file chooser, as used by some of the editors we are targeting. Finally, + * the terminal handling stuff might be useful for other toys, so should be + * generic in it's own right. Oh, screen is listed in the toybox TODO as + * "maybe", so I'll poke at that to. + * + * The basic building blocks are box, content, context, edit line, and + * view. A box is an on screen rectanglur area. Content is a file, and + * the text that is in that file. A context represents a particular editor + * type, it has key mappings and other similar stuff. The edit line is a + * single line where editing happens, it's similar to readline. A view is + * a view into a content, there can be many, it represents that portion of + * the content that is on screen right now. + * + * I plan on splitting these things up a bit so they can be used + * separately. Then I can make actually toybox libraries out of them. For + * now it's all one big file for ease of development. + * + * The screen is split into boxes, by default there are only two, the main + * text area and the command line at the bottom. Each box contains a view, + * and each view points to a content (file) for it's text. A content can + * have many views. Each content has a context (editor). There is only + * ever one edit line, it's the line that is being edited at the moment. + * The edit line moves within and between boxes (including the command + * line) as needed. + * + * The justification for boxes is that most of the editors we are trying to + * emulate include some splitting up of the screen area for various + * reasons, and some of them include a split window system as well. So + * this boxes concept covers command line, main editing area, split windows, + * menus, on screen display of command keys, file selection, and anything + * else that might be needed. + * + * To keep things simple boxes are organised as a binary tree of boxes. + * There is a root box, it's a global. Each box can have two sub boxes. + * Only the leaf nodes of the box tree are visible on the screen. Each box + * with sub boxes is split either horizontally or vertically. Navigating + * through the boxes goes depth first. + * + * A content keeps track of a file and it's text. Each content also has a + * context, which is a collection of the things that define a particular + * editor. (I might move the context pointer from content to view, makes + * more sense I think.) + * + * A context is the heart of the generic part of the system. Any given + * toybox command that uses this system would basically define a context + * and pass that to the rest of the system. See the end of this file for a + * few examples. A context holds a list of command to C function mappings, + * key to command mappings, and a list of modes. + * + * Most of the editors targetted include a command line where the user + * types in editor commands, and each of those editors has different + * commands. They would mostly use the same editing C functions though, or + * short wrappers around them. The vi context at the end of this file is a + * good example, it has a bunch of short C wrappers around some editing + * functions, or uses standard C editing functions directly. So a context + * has an editor command to C function mapping. + * + * The editors respond to keystrokes, and those keystrokes invoke editor + * commands, often in a modal way. So there are keystroke to editor + * command mappings. To cater for editing modes, each context has a list + * of modes, and each mode can have key to command mappings, as well as + * menu to command mappings, and a list of displayed key/command pairs. + * Menu and key/command pair display is not written yet. Most editors have + * a system for remapping key to command mappings, that's not supported + * yet. Emacs has a heirarchy of key to command mappings, that's not + * supported yet. Some twiddling of the current design would be needed for + * those. + * + * The key mappings used can be multiple keystrokes in a sequence, the + * system caters for that. Some can be multi byte like function keys, and + * even different strings of bytes depending on the terminal type. To + * simplify this, there is a table that maps various terminals ideas of + * special keys to key names, and the mapping of keys to commands uses + * those key names. + * + * A view represents the on screen visible portion of a content within a + * box. To cater for split window style editors, a content can have many + * views. It deals with keeping track of what's shown on screen, mapping + * the on screen representation of the text to the stored text during input + * and output. Each box holds one view. + * + * The edit line is basically a movable readline. There are editing C + * functions for moving it up and down lines within a view, and for + * shifting the edit line to some other box. So an editor would map the + * cursor keys for "up a line" and "down a line" to these C functions for + * example. Actual readline style functionality is just the bottom command + * box being a single line view, with the history file loaded into it's + * content, and the Enter key mapped to the editor contexts "do this + * command" function. Using most of the system with not much special casing. + * + * + * I assume that there wont be a terribly large number of boxes. + * Things like minimum box sizes, current maximum screen sizes, and the fact + * that they all have to be on the screen mean that this assumption should + * be safe. It's likely that most of the time there will be only a few at + * most. The point is there is a built in limit, there's only so many non + * overlapping boxes with textual contents that you can squeeze onto one + * terminal screen at once. + * + * I assume that there will only be one command line, no matter how many boxes, + * and that the command line is for the current box. + * + * I use a wide screen monitor and small font. + * My usual terminal is 104 x 380 characters. + * There will be people with bigger monitors and smaller fonts. + * So use sixteen bits for storing screen positions and the like. + * Eight bits wont cut it. + * + * + * These are the escape sequences we send - + * \x1B[m reset attributes and colours + * \x1B[1m turn on bold + * \x1B[%d;%dH move cursor + * Plus some experimentation with turning on mouse reporting that's not + * currently used. + * + * + * TODO - disentangle boxes from views. + * + * TODO - should split this up into editing, UI, and boxes parts, + * so the developer can leave out bits they are not using. + * + * TODO - Show status line instead of command line when it's not being edited. + * + * TODO - should review it all for UTF8 readiness. Think I can pull that off + * by keeping everything on the output side as "screen position", and using + * the formatter to sort out the input to output mapping. + * + * TODO - see if there are any simple shortcuts to avoid recalculating + * everything all the time. And to avoid screen redraws. + */ + +/* Robs "It's too big" lament. + +> So when you give me code, assume I'm dumber than you. Because in this +> context, I am. I need bite sized pieces, each of which I can +> understand in its entirety before moving on to the next. + +As I mentioned in my last email to the Aboriginal linux list, I wrote +the large blob so I could see how the ideas all work to do the generic +editor stuff and other things. It kinda needs to be that big to do +anything that is actually useful. So, onto musings about cutting it up +into bite sized bits... + +You mentioned on the Aboriginal Linux list that you wanted a +"readline", and you listed some features. My reply was that you had +basically listed the features of my "basic editor". The main +difference between "readline" and a full screen editor, is that the +editor is fullscreen, while the "readline" is one line. They both have +to deal with a list of lines, going up and down through those lines, +editing the contents of those lines, one line at a time. For persistent +line history, they both have to save and load those lines to a file. +Other than that "readline" adds other behaviour, like moving the +current line to the end when you hit return and presenting that line to +the caller (here's the command). So just making "readline" wont cut +out much of the code. In fact, my "readline" is really just a thin +wrapper around the rest of the code. + +Starting from the other end of the size spectrum, I guess "find out +terminal size" is a small enough bite sized chunk. To actually do +anything useful with that you would probably start to write stuff I've +already written though. Would be better off just using the stuff I've +already written. On the other hand, maybe that would be useful for +"ls" listings and the like, then we can start with just that bit? + +I guess the smallest useful command I have in my huge blob of code +would be "more". I could even cut it down more. Show a page of text, +show the next page of text when you hit the space bar, quit when you +hit "q". Even then, I would estimate it would only cut out a third of +the code. + +On the other hand, there's a bunch of crap in that blob that is for +future plans, and is not doing anything now. + +In the end though, breaking it up into suitable bite sized bits might +be almost as hard as writing it in the first place. I'll see what I +can do, when I find some time. I did want to break it up into three +bits anyway, and there's a TODO to untangle a couple of bits that are +currently too tightly coupled. + +*/ + +/* Robs contradiction + +On Thu, 27 Dec 2012 06:06:53 -0600 Rob Landley wrote: + +> On 12/27/2012 12:56:07 AM, David Seikel wrote: +> > On Thu, 27 Dec 2012 00:37:46 -0600 Rob Landley +> > wrote: +> > > Since ls isn't doiing any of that, probing would just be awkward +> > > so toysh should set COLUMNS to a sane value. The problem is, +> > > we're not using toysh yet because it's still just a stub... +> > +> > I got some or all of that terminal sizing stuff in my editor thingy +> > already if I remember correctly. I remember you wanted me to cut it +> > down into tiny bits to start with, so you don't have to digest the +> > huge lump I sent. +> +> Basically what I'd like is a self-contained line reader. More or less +> a getline variant that can handle cursoring left and right to insert +> stuff, handle backspace past a wordwrap (which on unix requires +> knowing the screen width and your current cursor position), and one +> that lets up/down escape sequences be hooked for less/more screen +> scrolling, vi line movement, shell command history, and so on. + +Which is most of an editor already, so how to break it up into bite +sized morsels? + +> There's sort of three levels here, the first is "parse raw input, +> assembling escape sequences into cursor-left and similar as +> necessary". (That's the one I had to redo in busybox vi because they +> didn't do it right.) +> +> The second is "edit a line of text, handling cursoring _within_ the +> line". That's the better/interactive getline(). +> +> The third is handling escapes from that line triggering behavior in +> the larger command (cursor up and cursor down do different things in +> less, in vi, and in shell command history). +> +> The fiddly bit is that terminal_size() sort of has to integrate with +> all of these. Possibly I need to have width and height be members of +> struct toy_context. Or have the better_getline() take a pointer to a +> state structure it can update, containing that info... + +*/ + + +static char *borderChars[][6] = +{ + {"-", "|", "+", "+", "+", "+"}, // "stick" characters. + {"\xE2\x94\x80", "\xE2\x94\x82", "\xE2\x94\x8C", "\xE2\x94\x90", "\xE2\x94\x94", "\xE2\x94\x98"}, // UTF-8 + {"\x71", "\x78", "\x6C", "\x6B", "\x6D", "\x6A"}, // VT100 alternate character set. + {"\xC4", "\xB3", "\xDA", "\xBF", "\xC0", "\xD9"} // DOS +}; + +static char *borderCharsCurrent[][6] = +{ + {"=", "#", "+", "+", "+", "+"}, // "stick" characters. + {"\xE2\x95\x90", "\xE2\x95\x91", "\xE2\x95\x94", "\xE2\x95\x97", "\xE2\x95\x9A", "\xE2\x95\x9D"}, // UTF-8 + {"\x71", "\x78", "\x6C", "\x6B", "\x6D", "\x6A"}, // VT100 alternate character set has none of these. B-( + {"\xCD", "\xBA", "\xC9", "\xBB", "\xC8", "\xBC"} // DOS +}; + + +typedef struct _box box; +typedef struct _view view; + +typedef void (*boxFunction) (box *box); +typedef void (*eventHandler) (view *view); + +struct function +{ + char *name; // Name for script purposes. + char *description; // Human name for the menus. + char type; + union + { + eventHandler handler; + char *scriptCallback; + }; +}; + +struct keyCommand +{ + char *key, *command; +}; + +struct item +{ + char *text; // What's shown to humans. + struct key *key; // Shortcut key while the menu is displayed. + // If there happens to be a key bound to the same command, the menu system should find that and show it to. + char type; + union + { + char *command; + struct item *items; // An array of next level menu items. + }; +}; + +struct borderWidget +{ + char *text, *command; +}; + +// TODO - a generic "part of text", and what is used to define them. +// For instance - word, line, paragraph, section. +// Each context can have a collection of these. + +struct mode +{ + struct keyCommand *keys; // An array of key to command mappings. + struct item *items; // An array of top level menu items. + struct item *functionKeys; // An array of single level "menus". Used to show key commands. + uint8_t flags; // commandMode. +}; + +/* +Have a common menu up the top. + MC has a menu that changes per mode. + Nano has no menu. +Have a common display of certain keys down the bottom. + MC is one row of F1 to F10, but changes for edit / view / file browse. But those are contexts here. + Nano is 12 random Ctrl keys, possibly in two lines, that changes depending on the editor mode, like editing the prompt line for various reasons, help. +*/ +struct context // Defines a context for content. Text viewer, editor, file browser for instance. +{ + struct function *commands; // The master list, the ones pointed to by the menus etc should be in this list. + struct mode *modes; // A possible empty array of modes, indexed by the view. + // OR might be better to have these as a linked list, so that things like Emacs can have it's mode keymap hierarcy. + eventHandler handler; // TODO - Might be better to put this in the modes. I think vi will need that. + // Should set the damage list if it needs a redraw, and flags if border or status line needs updating. + // Keyboard / mouse events if the box did not handle them itself. + // DrawAll event for when drawing the box for the first time, on reveals for coming out of full screen, or user hit the redraw key. + // Scroll event if the content wants to handle that itself. + // Timer event for things like top that might want to have this called regularly. + boxFunction doneRedraw; // The box is done with it's redraw, so we can free the damage list or whatever now. + boxFunction delete; + // This can be used as the sub struct for various context types. Like viewer, editor, file browser, top, etc. + // Could even be an object hierarchy, like generic editor, which Basic vi inherits from. + // Or not, since the commands might be different / more of them. +}; + +// TODO - might be better off just having a general purpose "widget" which includes details of where it gets attached. +// Status lines can have them to. +struct border +{ + struct borderWidget *topLeft, *topMiddle, *topRight; + struct borderWidget *bottomLeft, *bottomMiddle, *bottomRight; + struct borderWidget *left, *right; +}; + +struct line +{ + struct line *next, *prev; + uint32_t length; // Careful, this is the length of the allocated memory for real lines, but the number of lines in the header node. + char *line; // Should be blank for the header. +}; + +struct damage +{ + struct damage *next; // A list for faster draws? + uint16_t X, Y, W, H; // The rectangle to be redrawn. + uint16_t offset; // Offest from the left for showing lines. + struct line *lines; // Pointer to a list of text lines, or NULL. + // Note - likely a pointer into the middle of the line list in a content. +}; + +struct content // For various instances of context types. + // Editor / text viewer might have several files open, so one of these per file. + // MC might have several directories open, one of these per directory. No idea why you might want to do this. lol +{ + struct context *context; + char *name, *file, *path; + struct line lines; +// file type +// double linked list of bookmarks, pointer to line, character position, length (or ending position?), type, blob for types to keep context. + uint16_t minW, minH, maxW, maxH; + uint8_t flags; // readOnly, modified. + // This can be used as the sub struct for various content types. +}; + +struct _view +{ + struct content *content; + box *box; + struct border *border; // Can be NULL. + char *statusLine; // Text of the status line, or NULL if none. + int mode; // For those contexts that can be modal. Normally used to index the keys, menus, and key displays. + struct damage *damage; // Can be NULL. If not NULL after context->doneRedraw(), box will free it and it's children. + // TODO - Gotta be careful of overlapping views. + void *data; // The context controls this blob, it's specific to each box. + uint32_t offsetX, offsetY; // Offset within the content, coz box handles scrolling, usually. + uint16_t X, Y, W, H; // Position and size of the content area within the box. Calculated, but cached coz that might be needed for speed. + uint16_t cX, cY; // Cursor position within the content. + uint16_t iX, oW; // Cursor position inside the lines input text, in case the formatter makes it different, and output length. + char *output; // The current line formatted for output. + uint8_t flags; // redrawStatus, redrawBorder; + + // Assumption is that each box has it's own editLine (likely the line being edited), or that there's a box that is just the editLine (emacs minibuffer, and command lines for other proggies). + struct line *line; // Pointer to the current line, might be the only line. + char *prompt; // Optional prompt for the editLine. + +// Display mode / format hook. +// view specific bookmarks, including highlighted block and it's type. +// Linked list of selected lines for a filtered view, or processing only those lines. +// Linked list of pointers to struct keyCommand, for emacs keymaps hierarchy, and anything similar in other editors. Plus some way of dealing with emacs minor mode keymaps. +}; + +struct _box +{ + box *sub1, *sub2, *parent; + view *view; // This boxes view into it's content. For sharing contents, like a split pane editor for instance, there might be more than one box with this content, but a different view. + // If it's just a parent box, it wont have this, so just make it a damn pointer, that's the simplest thing. lol + // TODO - Are parent boxes getting a view anyway? + uint16_t X, Y, W, H; // Position and size of the box itself, not the content. Calculated, but cached coz that might be needed for speed. + float split; // Ratio of sub1's part of the split, the sub2 box gets the rest. + uint8_t flags; // Various flags. +}; + + +// Sometimes you just can't avoid circular definitions. +void drawBox(box *box); + + +#define BOX_HSPLIT 1 // Marks if it's a horizontally or vertically split. +#define BOX_BORDER 2 // Mark if it has a border, often full screen boxes wont. + +static int overWriteMode; +static box *rootBox; // Parent of the rest of the boxes, or the only box. Always a full screen. +static box *currentBox; +static view *commandLine; +static int commandMode; + +#define MEM_SIZE 128 // Chunk size for line memory allocation. + +// Inserts the line after the given line, or at the end of content if no line. +struct line *addLine(struct content *content, struct line *line, char *text, uint32_t length) +{ + struct line *result = NULL; + uint32_t len; + + if (!length) + length = strlen(text); + // Round length up. + len = (((length + 1) / MEM_SIZE) + 1) * MEM_SIZE; + result = xzalloc(sizeof(struct line)); + result->line = xzalloc(len); + result->length = len; + strncpy(result->line, text, length); + + if (content) + { + if (!line) + line = content->lines.prev; + + result->next = line->next; + result->prev = line; + + line->next->prev = result; + line->next = result; + + content->lines.length++; + } + else + { + result->next = result; + result->prev = result; + } + + return result; +} + +void freeLine(struct content *content, struct line *line) +{ + line->next->prev = line->prev; + line->prev->next = line->next; + if (content) + content->lines.length--; + free(line->line); + free(line); +} + +void loadFile(struct content *content) +{ + int fd = open(content->path, O_RDONLY); + + if (-1 != fd) + { + char *temp = NULL; + long len; + + do + { + // TODO - get_line() is slow, and wont help much with DOS and Mac line endings. + temp = get_line(fd); + if (temp) + addLine(content, NULL, temp, len); + } while (temp); + close(fd); + } +} + +// TODO - load and save should be able to deal with pipes, and with loading only parts of files, to load more parts later. + +void saveFile(struct content *content) +{ +// TODO - Should do "Save as" as well. Which is just a matter of changing content->path before calling this. + int fd; + + fd = open(content->path, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH); + + if (-1 != fd) + { + struct line *line = content->lines.next; + + while (&(content->lines) != line) // We are at the end if we have wrapped to the beginning. + { + write(fd, line->line, strlen(line->line)); + write(fd, "\n", 1); + line = line->next; + } + close(fd); + } + else + { + fprintf(stderr, "Can't open file %s\n", content->path); + exit(1); + } +} + +struct content *addContent(char *name, struct context *context, char *filePath) +{ + struct content *result = xzalloc(sizeof(struct content)); + + result->lines.next = &(result->lines); + result->lines.prev = &(result->lines); + result->name = strdup(name); + result->context = context; + + if (filePath) + { + result->path = strdup(filePath); + loadFile(result); + } + + return result; +} + +// General purpose line moosher. Used for appends, inserts, overwrites, and deletes. +// TODO - should have the same semantics as mooshStrings, only it deals with whole lines in double linked lists. +// We need content so we can adjust it's number of lines if needed. +void mooshLines(struct content *content, struct line *result, struct line *moosh, uint16_t index, uint16_t length, int insert) +{ +} + +// General purpose string moosher. Used for appends, inserts, overwrites, and deletes. +void mooshStrings(struct line *result, char *moosh, uint16_t index, uint16_t length, int insert) +{ + char *c, *pos; + int limit = strlen(result->line); + int mooshLen = 0, resultLen; + + if (moosh) + mooshLen = strlen(moosh); + + /* + * moosh == NULL a deletion + * length == 0 simple insertion + * length < mooshlen delete some, insert moosh + * length == mooshlen exact overwrite. + * length > mooshlen delete a lot, insert moosh + */ + + mooshLen -= length; + resultLen = limit + mooshLen; + + // If we need more space, allocate more. + if (resultLen > result->length) + { + result->length = resultLen + MEM_SIZE; + result->line = xrealloc(result->line, result->length); + } + + if (limit <= index) // At end, just add to end. + { + // TODO - Possibly add spaces to pad out to where index is? + // Would be needed for "go beyond end of line" and "column blocks". + // Both of those are advanced editing. + index = limit; + insert = 1; + } + + pos = &(result->line[index]); + + if (insert) // Insert / delete before current character, so move it and the rest up / down mooshLen bytes. + { + if (0 < mooshLen) // Gotta move things up. + { + c = &(result->line[limit]); + while (c >= pos) + { + *(c + mooshLen) = *c; + c--; + } + } + else if (0 > mooshLen) // Gotta move things down. + { + c = pos; + while (*c) + { + *c = *(c - mooshLen); // A double negative. + c++; + } + } + } + + if (moosh) + { + c = moosh; + do + { + *pos++ = *c++; + } + while (*c); + } +} + +// TODO - Should draw the current border in green, the text as default (or highlight / bright). +// Then allow one other box to be red bordered (MC / dired destination box). +// All other boxes with dark gray border, and dim text. +void drawLine(int y, int start, int end, char *left, char *internal, char *contents, char *right, int current) +{ + int size = strlen(internal); + int len = (end - start) * size, x = 0; + char line[len + 1]; + + if ('\0' != left[0]) // Assumes that if one side has a border, then so does the other. + len -= 2 * size; + + if (contents) + { + // strncpy wont add a null at the end if the source is longer, but will pad with nulls if source is shorter. + // So it's best to put a safety null in first. + line[len] = '\0'; + strncpy(line, contents, len); + // Make sure the following while loop pads out line with the internal character. + x = strlen(line); + } + while (x < len) + { + strcpy(&line[x], internal); + x += size; + } + line[x++] = '\0'; + if ('\0' == left[0]) // Assumes that if one side has a border, then so does the other. + { + if (current) + printf("\x1B[1m\x1B[%d;%dH%s\x1B[m", y + 1, start + 1, line); + else + printf("\x1B[m\x1B[%d;%dH%s", y + 1, start + 1, line); + } + else + { + if (current) + printf("\x1B[1m\x1B[%d;%dH%s%s%s\x1B[m", y + 1, start + 1, left, line, right); + else + printf("\x1B[m\x1B[%d;%dH%s%s%s", y + 1, start + 1, left, line, right); + } +} + +void formatCheckCursor(view *view, long *cX, long *cY, char *input) +{ + int i = 0, o = 0, direction = (*cX) - view->cX; + + // Adjust the cursor if needed, depending on the contents of the line, and the direction of travel. + while (input[i]) + { + // When o is equal to the cX position, update the iX position to be where i is. + if ('\t' == input[i]) + { + int j = 8 - (i % 8); + + // Check if the cursor is in the middle of the tab. + if (((*cX) > o) && ((*cX) < (o + j))) + { + if (0 <= direction) + { + *cX = (o + j); + view->iX = i + 1; + } + else + { + *cX = o; + view->iX = i; + } + } + o += j; + } + else + { + if ((*cX) == o) + view->iX = i; + o++; + } + i++; + } + // One more check in case the cursor is at the end of the line. + if ((*cX) == o) + view->iX = i; +} + +// TODO - Should convert control characters to reverse video, and deal with UTF8. + +/* FIXME - We get passed a NULL input, apparently when the file length is close to the screen length. - +> On Thu, 6 Sep 2012 11:56:17 +0800 Roy Tam wrote: +> +> > 2012/9/6 David Seikel : +> > >> Program received signal SIGSEGV, Segmentation fault. +> > >> formatLine (view=0x8069168, input=0x0, output=0x806919c) +> > >> at toys/other/boxes.c:843 +> > >> 843 int len = strlen(input), i = 0, o = 0; +> > >> (gdb) bt +> > >> #0 formatLine (view=0x8069168, input=0x0, output=0x806919c) +> > >> at toys/other/boxes.c:843 +> > >> #1 0x0804f1dd in moveCursorAbsolute (view=0x8069168, cX=0, +> > >> cY=10, sX=0, sY=0) at toys/other/boxes.c:951 +> > >> #2 0x0804f367 in moveCursorRelative (view=0x8069168, cX=0, +> > >> cY=10, sX=0, sY=0) at toys/other/boxes.c:1011 +> > >> #3 0x0804f479 in upLine (view=0x8069168, event=0x0) at +> > >> toys/other/boxes.c:1442 #4 0x0804fb63 in handleKey +> > >> (view=0x8069168, i=2, keyName=, buffer=0xbffffad8 +> > >> "\033[A") at toys/other/boxes.c:1593 #5 0x0805008d in editLine +> > >> (view=0x8069168, X=-1, Y=-1, W=-1, H=-1) at +> > >> toys/other/boxes.c:1785 #6 0x08050288 in boxes_main () at +> > >> toys/other/boxes.c:2482 #7 0x0804b262 in toy_exec +> > >> (argv=0xbffffd58) at main.c:104 #8 0x0804b29d in toybox_main () +> > >> at main.c:118 #9 0x0804b262 in toy_exec (argv=0xbffffd54) at +> > >> main.c:104 #10 0x0804b29d in toybox_main () at main.c:118 +> > >> #11 0x0804affa in main (argc=5, argv=0xbffffd54) at main.c:159 +> > > +> > > No segfault here when I try that, nor with different files. As I +> > > said, it has bugs. I have seen other cases before when NULL lines +> > > are passed around near that code. Guess I've not got them all. +> > > Might help to mention what terminal proggy you are using, it's +> > > size in characters, toybox version, OS, etc. Something is +> > > different between you and me. +> > +> > Terminal Program: PuTTY +> > Windows size: 80 * 25 chars +> > Toybox version: hg head +> > OS: Linux 3.2 (Debian wheezy) +> > File: Toybox LICENSE file +> +> I'll install PuTTY, try toybox hg head, and play with them later, see +> if I can reproduce that segfault. I use the last released tarball of +> toybox, an old Ubuntu, and a bunch of other terminal proggies. I did +> not know that PuTTY had been ported to Unix. +> +> But as I mentioned, not interested in a bug hunt right now. That will +> be more appropriate when it's less of a "sandbox for testing the +> ideas" and more of a "good enough to bother using". Always good to +> have other terminals for testing though. + +Seems to be sensitive to the number of lines. On my usual huge +roxterm, using either toybox 0.4 or hg head, doing this - + +./toybox boxes -m less LICENSE -h 25 + +and just hitting down arrow until you get to the bottom of the page +segfaults as well. -h means "pretend the terminal is that many lines +high", there's a similar -w as well. 80x25 in any other terminal did +the same. For that particular file (17 lines long), any value of -h +between 19 and 34 will segfault after some moving around. -h 35 makes +it segfault straight away. Less than 19 or over 35 is fine. Longer +files don't have that problem, though I suspect it will happen on +shortish files where the number of lines is about the length of the +screen. + +I guess that somewhere I'm doing maths wrong and getting an out of +bounds line number when the file length is close to the screen length. +I'll make note of that, and fix it when I next get time to beat on +boxes. + +Thanks for reporting it Roy. +*/ + +int formatLine(view *view, char *input, char **output) +{ + int len = strlen(input), i = 0, o = 0; + + *output = xrealloc(*output, len + 1); + + while (input[i]) + { + if ('\t' == input[i]) + { + int j = 8 - (i % 8); + + *output = xrealloc(*output, len + j + 1); + for (; j; j--) + { + (*output)[o++] = ' '; + len++; + } + len--; // Not counting the actual tab character itself. + } + else + (*output)[o++] = input[i]; + i++; + } + (*output)[o++] = '\0'; + + return len; +} + +void drawContentLine(view *view, int y, int start, int end, char *left, char *internal, char *contents, char *right, int current) +{ + char *temp = NULL; + int offset = view->offsetX, len; + + if (contents == view->line->line) + { + view->oW = formatLine(view, contents, &(view->output)); + temp = view->output; + len = view->oW; + } + else // Only time we are not drawing the current line, and only used for drawing the entire page. + len = formatLine(NULL, contents, &(temp)); + + if (offset > len) + offset = len; + drawLine(y, start, end, left, internal, &(temp[offset]), right, current); +} + +void updateLine(view *view) +{ + int y, len; + + // Coz things might change out from under us, find the current view. Again. + if (commandMode) view = commandLine; + else view = currentBox->view; + + // TODO - When doing scripts and such, might want to turn off the line update until finished. + // Draw the prompt and the current line. + y = view->Y + (view->cY - view->offsetY); + len = strlen(view->prompt); + drawLine(y, view->X, view->X + view->W, "", " ", view->prompt, "", 0); + drawContentLine(view, y, view->X + len, view->X + view->W, "", " ", view->line->line, "", 1); + // Move the cursor. + printf("\x1B[%d;%dH", y + 1, view->X + len + (view->cX - view->offsetX) + 1); + fflush(stdout); +} + +void doCommand(view *view, char *command) +{ + if (command) + { + struct function *functions = view->content->context->commands; + int i; + +// TODO - Some editors have a shortcut command concept. The smallest unique first part of each command will match, as well as anything longer. +// A further complication is if we are not implementing some commands that might change what is "shortest unique prefix". + + for (i = 0; functions[i].name; i++) + { + if (strcmp(functions[i].name, command) == 0) + { + if (functions[i].handler) + { + functions[i].handler(view); + updateLine(view); + } + break; + } + } + } +} + +int moveCursorAbsolute(view *view, long cX, long cY, long sX, long sY) +{ + struct line *newLine = view->line; + long oX = view->offsetX, oY = view->offsetY; + long lX = view->oW, lY = view->content->lines.length - 1; + long nY = view->cY; + uint16_t w = view->W - 1, h = view->H - 1; + int moved = 0, updatedY = 0, endOfLine = 0; + + // Check if it's still within the contents. + if (0 > cY) // Trying to move before the beginning of the content. + cY = 0; + else if (lY < cY) // Trying to move beyond end of the content. + cY = lY; + if (0 > cX) // Trying to move before the beginning of the line. + { + // See if we can move to the end of the previous line. + if (view->line->prev != &(view->content->lines)) + { + cY--; + endOfLine = 1; + } + else + cX = 0; + } + else if (lX < cX) // Trying to move beyond end of line. + { + // See if we can move to the begining of the next line. + if (view->line->next != &(view->content->lines)) + { + cY++; + cX = 0; + } + else + cX = lX; + } + + // Find the new line. + while (nY != cY) + { + updatedY = 1; + if (nY < cY) + { + newLine = newLine->next; + nY++; + if (view->content->lines.prev == newLine) // We are at the end if we have wrapped to the beginning. + break; + } + else + { + newLine = newLine->prev; + nY--; + if (view->content->lines.next == newLine) // We are at the end if we have wrapped to the beginning. + break; + } + } + cY = nY; + + // Check if we have moved past the end of the new line. + if (updatedY) + { + // Format it. + view->oW = formatLine(view, newLine->line, &(view->output)); + if (view->oW < cX) + endOfLine = 1; + if (endOfLine) + cX = view->oW; + } + + // Let the formatter decide if it wants to adjust things. + // It's up to the formatter to deal with things if it changes cY. + // On the other hand, changing cX is it's job. + formatCheckCursor(view, &cX, &cY, newLine->line); + + // Check the scrolling. + lY -= view->H - 1; + oX += sX; + oY += sY; + + if (oY > cY) // Trying to move above the box. + oY += cY - oY; + else if ((oY + h) < cY) // Trying to move below the box + oY += cY - (oY + h); + if (oX > cX) // Trying to move to the left of the box. + oX += cX - oX; + else if ((oX + w) <= cX) // Trying to move to the right of the box. + oX += cX - (oX + w); + + if (oY < 0) + oY = 0; + if (oY >= lY) + oY = lY; + if (oX < 0) + oX = 0; + // TODO - Should limit oX to less than the longest line, minus box width. + // Gonna be a pain figuring out what the longest line is. + // On the other hand, don't think that will be an actual issue unless "move past end of line" is enabled, and that's an advanced editor thing. + // Though still might want to do that for the longest line on the new page to be. + + if ((view->cX != cX) || (view->cY != cY)) + moved = 1; + + // Actually update stuff, though some have been done already. + view->cX = cX; + view->cY = cY; + view->line = newLine; + + // Handle scrolling. + if ((view->offsetX != oX) || (view->offsetY != oY)) + { + view->offsetX = oX; + view->offsetY = oY; + + if (view->box) + drawBox(view->box); + } + + return moved; +} + +int moveCursorRelative(view *view, long cX, long cY, long sX, long sY) +{ + return moveCursorAbsolute(view, view->cX + cX, view->cY + cY, sX, sY); +} + +void sizeViewToBox(box *box, int X, int Y, int W, int H) +{ + uint16_t one = 1, two = 2; + + if (!(box->flags & BOX_BORDER)) + { + one = 0; + two = 0; + } + box->view->X = X; + box->view->Y = Y; + box->view->W = W; + box->view->H = H; + if (0 > X) box->view->X = box->X + one; + if (0 > Y) box->view->Y = box->Y + one; + if (0 > W) box->view->W = box->W - two; + if (0 > H) box->view->H = box->H - two; +} + +view *addView(char *name, struct context *context, char *filePath, uint16_t X, uint16_t Y, uint16_t W, uint16_t H) +{ + view *result = xzalloc(sizeof(struct _view)); + + result->X = X; + result->Y = Y; + result->W = W; + result->H = H; + + result->content = addContent(name, context, filePath); + result->prompt = xzalloc(1); + // If there was content, format it's first line as usual, otherwise create an empty first line. + if (result->content->lines.next != &(result->content->lines)) + { + result->line = result->content->lines.next; + result->oW = formatLine(result, result->line->line, &(result->output)); + } + else + { + result->line = addLine(result->content, NULL, "\0", 0); + result->output = xzalloc(1); + } + + return result; +} + +box *addBox(char *name, struct context *context, char *filePath, uint16_t X, uint16_t Y, uint16_t W, uint16_t H) +{ + box *result = xzalloc(sizeof(struct _box)); + + result->X = X; + result->Y = Y; + result->W = W; + result->H = H; + result->view = addView(name, context, filePath, X, Y, W, H); + result->view->box = result; + sizeViewToBox(result, X, Y, W, H); + + return result; +} + +void freeBox(box *box) +{ + if (box) + { + freeBox(box->sub1); + freeBox(box->sub2); + if (box->view) + { + // In theory the line should not be part of the content if there is no content, so we should free it. + if (!box->view->content) + freeLine(NULL, box->view->line); + free(box->view->prompt); + free(box->view->output); + free(box->view); + } + free(box); + } +} + +void drawBox(box *box) +{ + // This could be heavily optimized, but let's keep things simple for now. + // Optimized for sending less characters I mean, on slow serial links for instance. + + char **bchars = (toys.optflags & FLAG_a) ? borderChars[0] : borderChars[1]; + char *left = "\0", *right = "\0"; + struct line *lines = NULL; + int y = box->Y, current = (box == currentBox); + uint16_t h = box->Y + box->H; + + if (current) + bchars = (toys.optflags & FLAG_a) ? borderCharsCurrent[0] : borderCharsCurrent[1]; + + // Slow and laborious way to figure out where in the linked list of lines we start from. + // Wont scale well, but is simple. + if (box->view && box->view->content) + { + int i = box->view->offsetY; + + lines = &(box->view->content->lines); + while (i--) + { + lines = lines->next; + if (&(box->view->content->lines) == lines) // We are at the end if we have wrapped to the beginning. + break; + } + } + + if (box->flags & BOX_BORDER) + { + h--; + left = right = bchars[1]; + drawLine(y++, box->X, box->X + box->W, bchars[2], bchars[0], NULL, bchars[3], current); + } + + while (y < h) + { + char *line = ""; + + if (lines) + { + lines = lines->next; + if (&(box->view->content->lines) == lines) // We are at the end if we have wrapped to the beginning. + lines = NULL; + else + line = lines->line; + // Figure out which line is our current line while we are here. + if (box->view->Y + (box->view->cY - box->view->offsetY) == y) + box->view->line = lines; + } + drawContentLine(box->view, y++, box->X, box->X + box->W, left, " ", line, right, current); + } + if (box->flags & BOX_BORDER) + drawLine(y++, box->X, box->X + box->W, bchars[4], bchars[0], NULL, bchars[5], current); + fflush(stdout); +} + +void drawBoxes(box *box) +{ + if (box->sub1) // If there's one sub box, there's always two. + { + drawBoxes(box->sub1); + drawBoxes(box->sub2); + } + else + drawBox(box); +} + +void calcBoxes(box *box) +{ + if (box->sub1) // If there's one sub box, there's always two. + { + box->sub1->X = box->X; + box->sub1->Y = box->Y; + box->sub1->W = box->W; + box->sub1->H = box->H; + box->sub2->X = box->X; + box->sub2->Y = box->Y; + box->sub2->W = box->W; + box->sub2->H = box->H; + if (box->flags & BOX_HSPLIT) + { + box->sub1->H *= box->split; + box->sub2->H -= box->sub1->H; + box->sub2->Y += box->sub1->H; + } + else + { + box->sub1->W *= box->split; + box->sub2->W -= box->sub1->W; + box->sub2->X += box->sub1->W; + } + sizeViewToBox(box->sub1, -1, -1, -1, -1); + calcBoxes(box->sub1); + sizeViewToBox(box->sub2, -1, -1, -1, -1); + calcBoxes(box->sub2); + } + // Move the cursor to where it is, to check it's not now outside the box. + moveCursorAbsolute(box->view, box->view->cX, box->view->cY, 0, 0); + + // We could call drawBoxes() here, but this is recursive, and so is drawBoxes(). + // The combination might be deadly. Drawing the content of a box might be an expensive operation. + // Later we might use a dirty box flag to deal with this, if it's not too much of a complication. +} + +void deleteBox(view *view) +{ + box *box = view->box; + + if (box->parent) + { + struct _box *oldBox = box, *otherBox = box->parent->sub1; + + if (otherBox == oldBox) + otherBox = box->parent->sub2; + if (currentBox->parent == box->parent) + currentBox = box->parent; + box = box->parent; + box->X = box->sub1->X; + box->Y = box->sub1->Y; + if (box->flags & BOX_HSPLIT) + box->H = box->sub1->H + box->sub2->H; + else + box->W = box->sub1->W + box->sub2->W; + box->flags &= ~BOX_HSPLIT; + // Move the other sub boxes contents up to this box. + box->sub1 = otherBox->sub1; + box->sub2 = otherBox->sub2; + if (box->sub1) + { + box->sub1->parent = box; + box->sub2->parent = box; + box->flags = otherBox->flags; + if (currentBox == box) + currentBox = box->sub1; + } + else + { + if (!box->parent) + box->flags &= ~BOX_BORDER; + box->split = 1.0; + } + otherBox->sub1 = NULL; + otherBox->sub2 = NULL; + // Safe to free the boxes now that we have all their contents. + freeBox(otherBox); + freeBox(oldBox); + } + // Otherwise it must be a single full screen box. Never delete that one, unless we are quitting. + + // Start the recursive recalculation of all the sub boxes. + calcBoxes(box); + drawBoxes(box); +} + +void cloneBox(struct _box *box, struct _box *sub) +{ + sub->parent = box; + // Only a full screen box has no border. + sub->flags |= BOX_BORDER; + sub->view = xmalloc(sizeof(struct _view)); + // TODO - After this is more stable, should check if the memcpy() is simpler than - xzalloc() then copy a few things manually. + // Might even be able to arrange the structure so we can memcpy just part of it, leaving the rest blank. + memcpy(sub->view, box->view, sizeof(struct _view)); + sub->view->damage = NULL; + sub->view->data = NULL; + sub->view->output = NULL; + sub->view->box = sub; + if (box->view->prompt) + sub->view->prompt = strdup(box->view->prompt); +} + +void splitBox(box *box, float split) +{ + uint16_t size; + int otherBox = 0; + + // First some sanity checks. + if (0.0 > split) + { + // TODO - put this in the status line, or just silently fail. Also, better message. lol + fprintf(stderr, "User is crazy.\n"); + return; + } + else if (1.0 <= split) // User meant to unsplit, and it may already be split. + { + // Actually, this means that the OTHER sub box gets deleted. + if (box->parent) + { + if (box == box->parent->sub1) + deleteBox(box->parent->sub2->view); + else + deleteBox(box->parent->sub1->view); + } + return; + } + else if (0.0 < split) // This is the normal case, so do nothing. + { + } + else // User meant to delete this, zero split. + { + deleteBox(box->view); + return; + } + if (box->flags & BOX_HSPLIT) + size = box->H; + else + size = box->W; + if (6 > size) // Is there room for 2 borders for each sub box and one character of content each? + // TODO - also should check the contents minimum size. + // No need to check the no border case, that's only for full screen. + // People using terminals smaller than 6 characters get what they deserve. + { + // TODO - put this in the status line, or just silently fail. + fprintf(stderr, "Box is too small to split.\n"); + return; + } + + // Note that a split box is actually three boxes. The parent, which is not drawn, and the two subs, which are. + // Based on the assumption that there wont be lots of boxes, this keeps things simple. + // It's possible that the box has already been split, and this is called just to update the split. + box->split = split; + if (NULL == box->sub1) // If not split already, do so. + { + box->sub1 = xzalloc(sizeof(struct _box)); + box->sub2 = xzalloc(sizeof(struct _box)); + cloneBox(box, box->sub1); + cloneBox(box, box->sub2); + if (box->flags & BOX_HSPLIT) + { + // Split the boxes in the middle of the content. + box->sub2->view->offsetY += (box->H * box->split) - 2; + // Figure out which sub box the cursor will be in, then update the cursor in the other box. + if (box->sub1->view->cY < box->sub2->view->offsetY) + box->sub2->view->cY = box->sub2->view->offsetY; + else + { + box->sub1->view->cY = box->sub2->view->offsetY - 1; + otherBox = 1; + } + } + else + { + // Split the boxes in the middle of the content. + box->sub2->view->offsetX += (box->W * box->split) - 2; + // Figure out which sub box the cursor will be in, then update the cursor in the other box. + if (box->sub1->view->cX < box->sub2->view->offsetX) + box->sub2->view->cX = box->sub2->view->offsetX; + else + { + box->sub1->view->cX = box->sub2->view->offsetX - 1; + otherBox = 1; + } + } + } + + if ((currentBox == box) && (box->sub1)) + { + if (otherBox) + currentBox = box->sub2; + else + currentBox = box->sub1; + } + + // Start the recursive recalculation of all the sub boxes. + calcBoxes(box); + drawBoxes(box); +} + +// TODO - Might be better to just have a double linked list of boxes, and traverse that instead. +// Except that leaves a problem when deleting boxes, could end up with a blank space. +void switchBoxes(view *view) +{ + box *box = view->box; + + // The assumption here is that box == currentBox. + struct _box *oldBox = currentBox; + struct _box *thisBox = box; + int backingUp = 0; + + // Depth first traversal. + while ((oldBox == currentBox) && (thisBox->parent)) + { + if (thisBox == thisBox->parent->sub1) + { + if (backingUp && (thisBox->parent)) + currentBox = thisBox->parent->sub2; + else if (thisBox->sub1) + currentBox = thisBox->sub1; + else + currentBox = thisBox->parent->sub2; + } + else if (thisBox == thisBox->parent->sub2) + { + thisBox = thisBox->parent; + backingUp = 1; + } + } + + // If we have not found the next box to move to, move back to the beginning. + if (oldBox == currentBox) + currentBox = rootBox; + + // If we ended up on a parent box, go to it's first sub. + while (currentBox->sub1) + currentBox = currentBox->sub1; + + // Just redraw them all. + drawBoxes(rootBox); +} + +// TODO - It might be better to do away with this bunch of single line functions +// and map script commands directly to lower functions. +// How to deal with the various argument needs? +// Might be where we can re use the toybox argument stuff. + +void halveBoxHorizontally(view *view) +{ + view->box->flags |= BOX_HSPLIT; + splitBox(view->box, 0.5); +} + +void halveBoxVertically(view *view) +{ + view->box->flags &= ~BOX_HSPLIT; + splitBox(view->box, 0.5); +} + +void switchMode(view *view) +{ + currentBox->view->mode++; + // Assumes that modes will always have a key mapping, which I think is a safe bet. + if (NULL == currentBox->view->content->context->modes[currentBox->view->mode].keys) + currentBox->view->mode = 0; + commandMode = currentBox->view->content->context->modes[currentBox->view->mode].flags & 1; +} + +void leftChar(view *view) +{ + moveCursorRelative(view, -1, 0, 0, 0); +} + +void rightChar(view *view) +{ + moveCursorRelative(view, 1, 0, 0, 0); +} + +void upLine(view *view) +{ + moveCursorRelative(view, 0, -1, 0, 0); +} + +void downLine(view *view) +{ + moveCursorRelative(view, 0, 1, 0, 0); +} + +void upPage(view *view) +{ + moveCursorRelative(view, 0, 0 - (view->H - 1), 0, 0 - (view->H - 1)); +} + +void downPage(view *view) +{ + moveCursorRelative(view, 0, view->H - 1, 0, view->H - 1); +} + +void endOfLine(view *view) +{ + moveCursorAbsolute(view, strlen(view->prompt) + view->oW, view->cY, 0, 0); +} + +void startOfLine(view *view) +{ + // TODO - add the advanced editing "smart home". + moveCursorAbsolute(view, strlen(view->prompt), view->cY, 0, 0); +} + +void splitLine(view *view) +{ + // TODO - should move this into mooshLines(). + addLine(view->content, view->line, &(view->line->line[view->iX]), 0); + view->line->line[view->iX] = '\0'; + moveCursorAbsolute(view, 0, view->cY + 1, 0, 0); + if (view->box) + drawBox(view->box); +} + +void deleteChar(view *view) +{ + // TODO - should move this into mooshLines(). + // If we are at the end of the line, then join this and the next line. + if (view->oW == view->cX) + { + // Only if there IS a next line. + if (&(view->content->lines) != view->line->next) + { + mooshStrings(view->line, view->line->next->line, view->iX, 1, !overWriteMode); + view->line->next->line = NULL; + freeLine(view->content, view->line->next); + // TODO - should check if we are on the last page, then deal with scrolling. + if (view->box) + drawBox(view->box); + } + } + else + mooshStrings(view->line, NULL, view->iX, 1, !overWriteMode); +} + +void backSpaceChar(view *view) +{ + if (moveCursorRelative(view, -1, 0, 0, 0)) + deleteChar(view); +} + +void saveContent(view *view) +{ + saveFile(view->content); +} + +void executeLine(view *view) +{ + struct line *result = view->line; + + // Don't bother doing much if there's nothing on this line. + if (result->line[0]) + { + doCommand(currentBox->view, result->line); + // If we are not at the end of the history contents. + if (&(view->content->lines) != result->next) + { + struct line *line = view->content->lines.prev; + + // Remove the line first. + result->next->prev = result->prev; + result->prev->next = result->next; + // Check if the last line is already blank, then remove it. + if ('\0' == line->line[0]) + { + freeLine(view->content, line); + line = view->content->lines.prev; + } + // Then add it to the end. + result->next = line->next; + result->prev = line; + line->next->prev = result; + line->next = result; + view->cY = view->content->lines.length - 1; + } + moveCursorAbsolute(view, 0, view->content->lines.length, 0, 0); + // Make sure there is one blank line at the end. + if ('\0' != view->line->line[0]) + { + endOfLine(view); + splitLine(view); + } + } + + saveFile(view->content); +} + +void quit(view *view) +{ + handle_keys_quit(); +} + +void nop(view *view) +{ + // 'tis a nop, don't actually do anything. +} + + +typedef void (*CSIhandler) (long extra, int *code, int count); + +struct CSI +{ + char *code; + CSIhandler func; +}; + +static void termSize(long extra, int *params, int count) +{ + struct _view *view = (struct _view *) extra; // Though we pretty much stomp on this straight away. + int r = params[0], c = params[1]; + + // The defaults are 1, which get ignored by the heuristic below. + // Check it's not an F3 key variation, coz some of them use the same CSI function code. + // This is a heuristic, we are checking against an unusable terminal size. + // TODO - Double check what the maximum F3 variations can be. + if ((2 == count) && (8 < r) && (8 < c)) + { + commandLine->Y = r; + commandLine->W = c; + rootBox->W = c; + rootBox->H = r - 1; + sizeViewToBox(rootBox, -1, -1, -1, -1); + calcBoxes(rootBox); + drawBoxes(rootBox); + + // Move the cursor to where it is, to check it's not now outside the terminal window. + moveCursorAbsolute(rootBox->view, rootBox->view->cX, rootBox->view->cY, 0, 0); + + // We have no idea which is the current view now. + if (commandMode) view = commandLine; + else view = currentBox->view; + updateLine(view); + } +} + +struct CSI CSIcommands[] = +{ + {"R", termSize} // Parameters are cursor line and column. Note this may be sent at other times, not just during terminal resize. +}; + + +// Callback for incoming sequences from the terminal. +static int handleEvent(long extra, struct keyevent *event) +{ + switch (event->type) + { + case HK_CSI : + { + int j; + + for (j = 0; j < ARRAY_LEN(CSIcommands); j++) + { + if (strcmp(CSIcommands[j].code, event->sequence) == 0) + { + CSIcommands[j].func(extra, event->params, event->count); + break; + } + } + break; + } + + case HK_KEYS : + { + struct _view *view = (struct _view *) extra; // Though we pretty much stomp on this straight away. + struct keyCommand *commands = currentBox->view->content->context->modes[currentBox->view->mode].keys; + int j, l = strlen(event->sequence); + + // Coz things might change out from under us, find the current view. + if (commandMode) view = commandLine; + else view = currentBox->view; + + // Search for a key sequence bound to a command. + for (j = 0; commands[j].key; j++) + { + if (strncmp(commands[j].key, event->sequence, l) == 0) + { + // If it's a partial match, keep accumulating them. + if (strlen(commands[j].key) != l) + return 0; + else + { + doCommand(view, commands[j].command); + return 1; + } + } + } + + // See if it's ordinary keys. + // NOTE - with vi style ordinary keys can be commands, + // but they would be found by the command check above first. + if (!event->isTranslated) + { + // TODO - Should check for tabs to, and insert them. + // Though better off having a function for that? + mooshStrings(view->line, event->sequence, view->iX, 0, !overWriteMode); + view->oW = formatLine(view, view->line->line, &(view->output)); + moveCursorRelative(view, strlen(event->sequence), 0, 0, 0); + updateLine(view); + } + break; + } + + default : break; + } + + // Tell handle_keys to drop it, coz we dealt with it, or it's not one of ours. + return 1; +} + + +// The default command to function mappings, with help text. Any editor that does not have it's own commands can use these for keystroke binding and such. +// Though most of the editors have their own variation. +// TODO - Maybe just use the joe one as default, it uses short names at least. +// Though vi is the only one in POSIX, so might be better to treat that one as the "standard" default. +// With some commands from others for stuff vi doesn't support. +struct function simpleEditCommands[] = +{ + {"backSpaceChar", "Back space last character.", 0, {backSpaceChar}}, + {"deleteBox", "Delete a box.", 0, {deleteBox}}, + {"deleteChar", "Delete current character.", 0, {deleteChar}}, + {"downLine", "Move cursor down one line.", 0, {downLine}}, + {"downPage", "Move cursor down one page.", 0, {downPage}}, + {"endOfLine", "Go to end of line.", 0, {endOfLine}}, + {"executeLine", "Execute a line as a script.", 0, {executeLine}}, + {"leftChar", "Move cursor left one character.", 0, {leftChar}}, + {"quit", "Quit the application.", 0, {quit}}, + {"rightChar", "Move cursor right one character.", 0, {rightChar}}, + {"save", "Save.", 0, {saveContent}}, + {"splitH", "Split box in half horizontally.", 0, {halveBoxHorizontally}}, + {"splitLine", "Split line at cursor.", 0, {splitLine}}, + {"splitV", "Split box in half vertically.", 0, {halveBoxVertically}}, + {"startOfLine", "Go to start of line.", 0, {startOfLine}}, + {"switchBoxes", "Switch to another box.", 0, {switchBoxes}}, + {"switchMode", "Switch between command and box.", 0, {switchMode}}, + {"upLine", "Move cursor up one line.", 0, {upLine}}, + {"upPage", "Move cursor up one page.", 0, {upPage}}, + {NULL, NULL, 0, {NULL}} +}; + +// Construct a simple command line. + +// The key to command mappings. +// TODO - Should not move off the ends of the line to the next / previous line. +struct keyCommand simpleCommandKeys[] = +{ + {"BS", "backSpaceChar"}, + {"Del", "deleteChar"}, + {"Down", "downLine"}, + {"End", "endOfLine"}, + {"F10", "quit"}, + {"Home", "startOfLine"}, + {"Left", "leftChar"}, + {"Enter", "executeLine"}, + {"Return", "executeLine"}, + {"Right", "rightChar"}, + {"Esc", "switchMode"}, + {"Up", "upLine"}, + {NULL, NULL} +}; + + +// Construct a simple emacs editor. + +// Mostly control keys, some meta keys. +// Ctrl-h and Ctrl-x have more keys in the commands. Some of those extra keys are commands by themselves. Up to "Ctrl-x 4 Ctrl-g" which apparently is identical to just Ctrl-g. shrugs +// Ctrl-h is backspace / del. Pffft. +// Meta key is either Alt-keystroke, Esc keystroke, or an actual Meta-keystroke (do they still exist?). +// TODO - Alt and Meta not supported yet, so using Esc. +// Windows commands. + +// readline uses these same commands, and defaults to emacs keystrokes. +struct function simpleEmacsCommands[] = +{ + {"delete-backward-char", "Back space last character.", 0, {backSpaceChar}}, + {"delete-window", "Delete a box.", 0, {deleteBox}}, + {"delete-char", "Delete current character.", 0, {deleteChar}}, + {"next-line", "Move cursor down one line.", 0, {downLine}}, + {"scroll-up", "Move cursor down one page.", 0, {downPage}}, + {"end-of-line", "Go to end of line.", 0, {endOfLine}}, + {"accept-line", "Execute a line as a script.", 0, {executeLine}}, // From readline, which uses emacs commands, coz mg at least does not seem to have this. + {"backward-char", "Move cursor left one character.", 0, {leftChar}}, + {"save-buffers-kill-emacs", "Quit the application.", 0, {quit}}, // TODO - Does more than just quit. + {"forward-char", "Move cursor right one character.", 0, {rightChar}}, + {"save-buffer", "Save.", 0, {saveContent}}, + {"split-window-horizontally", "Split box in half horizontally.", 0, {halveBoxHorizontally}}, // TODO - Making this one up for now, mg does not have it. + {"newline", "Split line at cursor.", 0, {splitLine}}, + {"split-window-vertically", "Split box in half vertically.", 0, {halveBoxVertically}}, + {"beginning-of-line", "Go to start of line.", 0, {startOfLine}}, + {"other-window", "Switch to another box.", 0, {switchBoxes}}, // There is also "previous-window" for going in the other direction, which we don't support yet. + {"execute-extended-command", "Switch between command and box.", 0, {switchMode}}, // Actually a one time invocation of the command line. + {"previous-line", "Move cursor up one line.", 0, {upLine}}, + {"scroll-down", "Move cursor up one page.", 0, {upPage}}, + {NULL, NULL, 0, {NULL}} +}; + +// The key to command mappings. +struct keyCommand simpleEmacsKeys[] = +{ + {"BS", "delete-backward-char"}, + {"Del", "delete-backward-char"}, + {"^D", "delete-char"}, + {"Down", "next-line"}, + {"^N", "next-line"}, + {"End", "end-of-line"}, + {"^E", "end-of-line"}, + {"^X^C", "save-buffers-kill-emacs"}, + {"^X^S", "save-buffer"}, + {"Home", "beginning-of-line"}, + {"^A", "beginning-of-line"}, + {"Left", "backward-char"}, + {"^B", "backward-char"}, + {"PgDn", "scroll-up"}, + {"^V", "scroll-up"}, + {"PgUp", "scroll-down"}, + {"Escv", "scroll-down"}, // M-v + {"Enter", "newline"}, + {"Return", "newline"}, + {"Right", "forward-char"}, + {"^F", "forward-char"}, + {"Escx", "execute-extended-command"}, // M-x + {"^X2", "split-window-vertically"}, + {"^XP", "other-window"}, + {"^X0", "delete-window"}, + {"Up", "previous-line"}, + {"^P", "previous-line"}, + {NULL, NULL} +}; + +struct keyCommand simpleEmacsCommandKeys[] = +{ + {"Del", "delete-backwards-char"}, + {"^D", "delete-char"}, + {"Down", "next-line"}, + {"^N", "next-line"}, + {"End", "end-of-line"}, + {"^E", "end-of-line"}, + {"Home", "beginning-of-line"}, + {"^A", "beginning-of-line"}, + {"Left", "backward-char"}, + {"^B", "backward-char"}, + {"Right", "forward-char"}, + {"^F", "forward-char"}, + {"Up", "previous-line"}, + {"^P", "previous-line"}, + {"Enter", "accept-line"}, + {"Return", "accept-line"}, + {"Escx", "execute-extended-command"}, + {NULL, NULL} +}; + +// An array of various modes. +struct mode simpleEmacsMode[] = +{ + {simpleEmacsKeys, NULL, NULL, 0}, + {simpleEmacsCommandKeys, NULL, NULL, 1}, + {NULL, NULL, NULL} +}; + +// Put it all together into a simple editor context. +struct context simpleEmacs = +{ + simpleEmacsCommands, + simpleEmacsMode, + NULL, + NULL, + NULL +}; + + +// Construct a simple joe / wordstar editor, using joe is the reference, seems to be the popular Unix variant. +// Esc x starts up the command line. +// Has multi control key combos. Mostly Ctrl-K, Ctrl-[ (Esc), (Ctrl-B, Ctrl-Q in wordstar and delphi), but might be others. +// Can't find a single list of command mappings for joe, gotta search all over. sigh +// Even the command line keystroke I stumbled on (Esc x) is not documented. +// Note that you don't have to let go of the Ctrl key for the second keystroke, but you can. + +// From http://joe-editor.sourceforge.net/list.html +// TODO - Some of these might be wrong. Just going by the inadequate joe docs for now. +struct function simpleJoeCommands[] = +{ + {"backs", "Back space last character.", 0, {backSpaceChar}}, + {"abort", "Delete a box.", 0, {deleteBox}}, // TODO - Should do quit if it's the last window. + {"delch", "Delete current character.", 0, {deleteChar}}, + {"dnarw", "Move cursor down one line.", 0, {downLine}}, + {"pgdn", "Move cursor down one page.", 0, {downPage}}, + {"eol", "Go to end of line.", 0, {endOfLine}}, + {"ltarw", "Move cursor left one character.", 0, {leftChar}}, + {"killjoe", "Quit the application.", 0, {quit}}, + {"rtarw", "Move cursor right one character.", 0, {rightChar}}, + {"save", "Save.", 0, {saveContent}}, + {"splitw", "Split box in half horizontally.", 0, {halveBoxHorizontally}}, + {"open", "Split line at cursor.", 0, {splitLine}}, + {"bol", "Go to start of line.", 0, {startOfLine}}, + {"home", "Go to start of line.", 0, {startOfLine}}, + {"nextw", "Switch to another box.", 0, {switchBoxes}}, // This is "next window", there's also "previous window" which we don't support yet. + {"execmd", "Switch between command and box.", 0, {switchMode}}, // Actually I think this just switches to the command mode, not back and forth. Or it might execute the actual command. + {"uparw", "Move cursor up one line.", 0, {upLine}}, + {"pgup", "Move cursor up one page.", 0, {upPage}}, + + // Not an actual joe command. + {"executeLine", "Execute a line as a script.", 0, {executeLine}}, // Perhaps this should be execmd? + {NULL, NULL, 0, {NULL}} +}; + +struct keyCommand simpleJoeKeys[] = +{ + {"BS", "backs"}, + {"^D", "delch"}, + {"Down", "dnarw"}, + {"^N", "dnarw"}, + {"^E", "eol"}, + {"^C", "killjoe"}, + {"^Kd", "save"}, + {"^K^D" "save"}, + {"^A", "bol"}, + {"Left", "ltarw"}, + {"^B", "ltarw"}, + {"^V", "pgdn"}, // Actually half a page. + {"^U", "pgup"}, // Actually half a page. + {"Enter", "open"}, + {"Return", "open"}, + {"Right", "rtarw"}, + {"^F", "rtarw"}, + {"Escx", "execmd"}, + {"Esc^X", "execmd"}, + {"^Ko", "splitw"}, + {"^K^O", "splitw"}, + {"^Kn", "nextw"}, + {"^K^N", "nextw"}, + {"^Kx", "killjoe"}, // TODO - Should ask if it should save if it's been modified. A good generic thing to do anyway. + {"^K^X", "abort"}, // TODO - These two both close a window, and quit if that was the last window. + {"Up", "uparw"}, + {"^P", "uparw"}, + {NULL, NULL} +}; + +struct keyCommand simpleJoeCommandKeys[] = +{ + {"BS", "backs"}, + {"^D", "delch"}, + {"Down", "dnarw"}, + {"^N", "dnarw"}, + {"^E", "eol"}, + {"^A", "bol"}, + {"Left", "ltarw"}, + {"^B", "ltarw"}, + {"Right", "rtarw"}, + {"^F", "rtarw"}, + {"Escx", "execmd"}, + {"Esc^X", "execmd"}, + {"Up", "uparw"}, + {"^P", "uparw"}, + {"Enter", "executeLine"}, + {"Return", "executeLine"}, + {NULL, NULL} +}; + +struct mode simpleJoeMode[] = +{ + {simpleJoeKeys, NULL, NULL, 0}, + {simpleJoeCommandKeys, NULL, NULL, 1}, + {NULL, NULL, NULL, 0} +}; + +struct context simpleJoe = +{ + simpleJoeCommands, + simpleJoeMode, + NULL, + NULL, + NULL +}; + + +// Simple more and / or less. +// '/' and '?' for search command mode. I think they both have some ex commands with the usual : command mode starter. +// No cursor movement, just scrolling. +// TODO - Put content into read only mode. +// TODO - actually implement read only mode where up and down one line do actual scrolling instead of cursor movement. + +struct keyCommand simpleLessKeys[] = +{ + {"Down", "downLine"}, + {"j", "downLine"}, + {"Enter", "downLine"}, + {"Return", "downLine"}, + {"End", "endOfLine"}, + {"q", "quit"}, + {":q", "quit"}, // TODO - A vi ism, should do ex command stuff instead. + {"ZZ", "quit"}, + {"PgDn", "downPage"}, + {"f", "downPage"}, + {" ", "downPage"}, + {"^F", "downPage"}, + {"Left", "leftChar"}, + {"Right", "rightChar"}, + {"PgUp", "upPage"}, + {"b", "upPage"}, + {"^B", "upPage"}, + {"Up", "upLine"}, + {"k", "upLine"}, + {NULL, NULL} +}; + +struct mode simpleLessMode[] = +{ + {simpleLessKeys, NULL, NULL, 0}, + {simpleCommandKeys, NULL, NULL, 1}, + {NULL, NULL, NULL} +}; + +struct context simpleLess = +{ + simpleEditCommands, + simpleLessMode, + NULL, + NULL, + NULL +}; + +struct keyCommand simpleMoreKeys[] = +{ + {"j", "downLine"}, + {"Enter", "downLine"}, + {"Return", "downLine"}, + {"q", "quit"}, + {":q", "quit"}, // See comments for "less". + {"ZZ", "quit"}, + {"f", "downPage"}, + {" ", "downPage"}, + {"^F", "downPage"}, + {"b", "upPage"}, + {"^B", "upPage"}, + {"k", "upLine"}, + {NULL, NULL} +}; + +struct mode simpleMoreMode[] = +{ + {simpleMoreKeys, NULL, NULL, 0}, + {simpleCommandKeys, NULL, NULL, 1}, + {NULL, NULL, NULL} +}; + +struct context simpleMore = +{ + simpleEditCommands, + simpleMoreMode, + NULL, + NULL, + NULL +}; + + +// Construct a simple mcedit / cool edit editor. + +struct keyCommand simpleMceditKeys[] = +{ + {"BS", "backSpaceChar"}, + {"Del", "deleteChar"}, + {"Down", "downLine"}, + {"End", "endOfLine"}, + {"F10", "quit"}, + {"Esc0", "quit"}, + {"F2", "save"}, + {"Esc2", "save"}, + {"Home", "startOfLine"}, + {"Left", "leftChar"}, + {"PgDn", "downPage"}, + {"PgUp", "upPage"}, + {"Enter", "splitLine"}, + {"Return", "splitLine"}, + {"Right", "rightChar"}, + {"Shift F2", "switchMode"}, // MC doesn't have a command mode. + {"Esc:", "switchMode"}, // Sorta vi like, and coz tmux is screwing with the shift function keys somehow. + {"Esc|", "splitV"}, // MC doesn't have a split window concept, so make these up to match tmux more or less. + {"Esc-", "splitH"}, + {"Esco", "switchBoxes"}, + {"Escx", "deleteBox"}, + {"Up", "upLine"}, +{"^C", "switchMode"}, // To test the signal stopper. +{"^D", "switchMode"}, // To test the signal stopper. +{"^Q", "switchMode"}, // To test the signal stopper. +{"^S", "switchMode"}, // To test the signal stopper. +{"^T", "switchMode"}, // To test the signal stopper. +{"^Z", "switchMode"}, // To test the signal stopper. +{"^\\", "switchMode"}, // To test the signal stopper. +{"F1", "switchMode"}, // To test various function keys. +{"F3", "switchMode"}, // To test various function keys. +{"F4", "switchMode"}, // To test various function keys. +{"F5", "switchMode"}, // To test various function keys. +{"F6", "switchMode"}, // To test various function keys. +{"F7", "switchMode"}, // To test various function keys. +{"F8", "switchMode"}, // To test various function keys. +{"F9", "switchMode"}, // To test various function keys. + {NULL, NULL} +}; + +struct mode simpleMceditMode[] = +{ + {simpleMceditKeys, NULL, NULL, 0}, + {simpleCommandKeys, NULL, NULL, 1}, + {NULL, NULL, NULL} +}; + +struct context simpleMcedit = +{ + simpleEditCommands, + simpleMceditMode, + NULL, + NULL, + NULL +}; + + +// Simple nano editor. +// Has key to function bindings, but no command line mode. Has "enter parameter on this line" mode for some commands. +// Control and meta keys, only singles, unlike emacs and joe. +// Can have multiple buffers, but no windows. Think I can skip that for simple editor. + +struct function simpleNanoCommands[] = +{ + {"backSpaceChar", "Back space last character.", 0, {backSpaceChar}}, + {"delete", "Delete current character.", 0, {deleteChar}}, + {"down", "Move cursor down one line.", 0, {downLine}}, + {"downPage", "Move cursor down one page.", 0, {downPage}}, + {"end", "Go to end of line.", 0, {endOfLine}}, + {"left", "Move cursor left one character.", 0, {leftChar}}, + {"exit", "Quit the application.", 0, {quit}}, + {"right", "Move cursor right one character.", 0, {rightChar}}, + {"writeout", "Save.", 0, {saveContent}}, + {"enter", "Split line at cursor.", 0, {splitLine}}, + {"home", "Go to start of line.", 0, {startOfLine}}, + {"up", "Move cursor up one line.", 0, {upLine}}, + {"upPage", "Move cursor up one page.", 0, {upPage}}, + {NULL, NULL, 0, {NULL}} +}; + + +// Hmm, back space, page up, and page down don't seem to have bindable commands according to the web page, but they are bound to keys anyway. +struct keyCommand simpleNanoKeys[] = +{ +// TODO - Delete key is ^H dammit. Find the alternate Esc sequence for Del. +// {"^H", "backSpaceChar"}, // ? + {"BS", "backSpaceChar"}, + {"^D", "delete"}, + {"Del", "delete"}, + {"^N", "down"}, + {"Down", "down"}, + {"^E", "end"}, + {"End", "end"}, + {"^X", "exit"}, + {"F2", "exit"}, + {"^O", "writeout"}, + {"F3", "writeout"}, + {"^A", "home"}, + {"Home", "home"}, + {"^B", "left"}, + {"Left", "left"}, + {"^V", "downPage"}, // ? + {"PgDn", "downPage"}, + {"^Y", "upPage"}, // ? + {"PgUp", "upPage"}, + {"Enter", "enter"}, // TODO - Not sure if this is correct. + {"Return", "enter"}, // TODO - Not sure if this is correct. + {"^F", "right"}, + {"Right", "right"}, + {"^P", "up"}, + {"Up", "up"}, + {NULL, NULL} +}; + +struct mode simpleNanoMode[] = +{ + {simpleNanoKeys, NULL, NULL, 0}, + {NULL, NULL, NULL} +}; + +struct context simpleNano = +{ + simpleNanoCommands, + simpleNanoMode, + NULL, + NULL, + NULL +}; + + +// Construct a simple vi editor. +// Only vi is not so simple. lol +// The "command line" modes are /, ?, :, and !, +// / is regex search. +// ? is regex search backwards. +// : is ex command mode. +// ! is replace text with output from shell command mode. +// Arrow keys do the right thing in "normal" mode, but not in insert mode. +// "i" goes into insert mode, "Esc" (or Ctrl-[ or Ctrl-C) gets you out. So much for "you can do it all touch typing on the home row". Pffft +// Ah, the Esc key WAS a lot closer to the home row (where Tab usually is now) on the original keyboard vi was designed for, ADM3A. +// Which is also the keyboard with the arrow keys marked on h, j, k, and l keys. +// Did I mention that vi is just a horrid historic relic that should have died long ago? +// Emacs looks to have the same problem, originally designed for an ancient keyboard that is nothing like what people actually use these days. +// "h", "j", "k", "l" move cursor, which is just random keys for dvorak users. +// ":" goes into ex command mode. +// ":q" deletes current window in vim. +// ":qa!" goes into ex mode and does some sort of quit command. +// The 'q' is short for quit, the ! is an optional argument to quit. No idea yet what the a is for, all windows? +// Del or "x" to delete a character. Del in insert mode. +// "X" to backspace. BS or Ctrl-H to backspace in insert mode. +// NOTE - Backspace in normal mode just moves left. +// Tab or Ctrl-I to insert a tab in insert mode. +// Return in normal mode goes to the start of the next line, or splits the line in insert mode. +// ":help" opens a window with help text. +// Vim window commands. + +// Vi needs extra variables and functions. +static int viTempExMode; + +void viMode(view *view) +{ + currentBox->view->mode = 0; + commandMode = 0; + viTempExMode = 0; +} + +void viInsertMode(view *view) +{ + currentBox->view->mode = 1; + commandMode = 0; +} + +void viExMode(view *view) +{ + currentBox->view->mode = 2; + commandMode = 1; + // TODO - Should change this based on the event, : or Q. + viTempExMode = 1; + commandLine->prompt = xrealloc(commandLine->prompt, 2); + strcpy(commandLine->prompt, ":"); +} + +void viBackSpaceChar(view *view) +{ + if ((2 == currentBox->view->mode) && (0 == view->cX) && viTempExMode) + viMode(view); + else + backSpaceChar(view); +} + +void viStartOfNextLine(view *view) +{ + startOfLine(view); + downLine(view); +} + +struct function simpleViCommands[] = +{ + // These are actual ex commands. + {"insert", "Switch to insert mode.", 0, {viInsertMode}}, + {"quit", "Quit the application.", 0, {quit}}, + {"visual", "Switch to visual mode.", 0, {viMode}}, + {"write", "Save.", 0, {saveContent}}, + + // These are not ex commands. + {"backSpaceChar", "Back space last character.", 0, {viBackSpaceChar}}, + {"deleteBox", "Delete a box.", 0, {deleteBox}}, + {"deleteChar", "Delete current character.", 0, {deleteChar}}, + {"downLine", "Move cursor down one line.", 0, {downLine}}, + {"downPage", "Move cursor down one page.", 0, {downPage}}, + {"endOfLine", "Go to end of line.", 0, {endOfLine}}, + {"executeLine", "Execute a line as a script.", 0, {executeLine}}, + {"exMode", "Switch to ex mode.", 0, {viExMode}}, + {"leftChar", "Move cursor left one character.", 0, {leftChar}}, + {"rightChar", "Move cursor right one character.", 0, {rightChar}}, + {"splitH", "Split box in half horizontally.", 0, {halveBoxHorizontally}}, + {"splitLine", "Split line at cursor.", 0, {splitLine}}, + {"splitV", "Split box in half vertically.", 0, {halveBoxVertically}}, + {"startOfLine", "Go to start of line.", 0, {startOfLine}}, + {"startOfNLine", "Go to start of next line.", 0, {viStartOfNextLine}}, + {"switchBoxes", "Switch to another box.", 0, {switchBoxes}}, + {"upLine", "Move cursor up one line.", 0, {upLine}}, + {"upPage", "Move cursor up one page.", 0, {upPage}}, + {NULL, NULL, 0, {NULL}} +}; + +struct keyCommand simpleViNormalKeys[] = +{ + {"BS", "leftChar"}, + {"X", "backSpaceChar"}, + {"Del", "deleteChar"}, + {"x", "deleteChar"}, + {"Down", "downLine"}, + {"j", "downLine"}, + {"End", "endOfLine"}, + {"Home", "startOfLine"}, + {"Left", "leftChar"}, + {"h", "leftChar"}, + {"PgDn", "downPage"}, + {"^F", "downPage"}, + {"PgUp", "upPage"}, + {"^B", "upPage"}, + {"Enter", "startOfNextLine"}, + {"Return", "startOfNextLine"}, + {"Right", "rightChar"}, + {"l", "rightChar"}, + {"i", "insert"}, + {":", "exMode"}, // This is the temporary ex mode that you can backspace out of. Or any command backs you out. + {"Q", "exMode"}, // This is the ex mode you need to do the "visual" command to get out of. + {"^Wv", "splitV"}, + {"^W^V", "splitV"}, + {"^Ws", "splitH"}, + {"^WS", "splitH"}, + {"^W^S", "splitH"}, + {"^Ww", "switchBoxes"}, + {"^W^W", "switchBoxes"}, + {"^Wq", "deleteBox"}, + {"^W^Q", "deleteBox"}, + {"Up", "upLine"}, + {"k", "upLine"}, + {NULL, NULL} +}; + +struct keyCommand simpleViInsertKeys[] = +{ + {"BS", "backSpaceChar"}, + {"Del", "deleteChar"}, + {"Return", "splitLine"}, + {"Esc", "visual"}, + {"^C", "visual"}, + {NULL, NULL} +}; + +struct keyCommand simpleExKeys[] = +{ + {"BS", "backSpaceChar"}, + {"Del", "deleteChar"}, + {"Down", "downLine"}, + {"End", "endOfLine"}, + {"Home", "startOfLine"}, + {"Left", "leftChar"}, + {"Enter", "executeLine"}, + {"Return", "executeLine"}, + {"Right", "rightChar"}, + {"Esc", "visual"}, + {"Up", "upLine"}, + {NULL, NULL} +}; + +struct mode simpleViMode[] = +{ + {simpleViNormalKeys, NULL, NULL, 0}, + {simpleViInsertKeys, NULL, NULL, 0}, + {simpleExKeys, NULL, NULL, 1}, + {NULL, NULL, NULL} +}; + +struct context simpleVi = +{ + simpleViCommands, + simpleViMode, + NULL, + NULL, + NULL +}; + + +// TODO - simple sed editor? May be out of scope for "simple", so leave it until later? +// Probably entirely useless for "simple". + + +// TODO - have any unrecognised escape key sequence start up a new box (split one) to show the "show keys" content. +// That just adds each "Key is X" to the end of the content, and allows scrolling, as well as switching between other boxes. + +void boxes_main(void) +{ + struct context *context = &simpleMcedit; // The default is mcedit, coz that's what I use. + struct termios termio, oldtermio; + char *prompt = "Enter a command : "; + unsigned W = 80, H = 24; + lua_State *L; + + /* + * All Lua contexts are held in this structure. We work with it almost + * all the time. + */ + L = luaL_newstate(); + +/* From http://luajit.org/install.html - +To change or extend the list of standard libraries to load, copy +src/lib_init.c to your project and modify it accordingly. Make sure the +jit library is loaded or the JIT compiler will not be activated. +*/ + luaL_openlibs(L); /* Load Lua libraries */ + + // For testing purposes, figure out which context we use. When this gets real, the toybox multiplexer will sort this out for us instead. + if (toys.optflags & FLAG_m) + { + if (strcmp(TT.mode, "emacs") == 0) + context = &simpleEmacs; + else if (strcmp(TT.mode, "joe") == 0) + context = &simpleJoe; + else if (strcmp(TT.mode, "less") == 0) + context = &simpleLess; + else if (strcmp(TT.mode, "mcedit") == 0) + context = &simpleMcedit; + else if (strcmp(TT.mode, "more") == 0) + context = &simpleMore; + else if (strcmp(TT.mode, "nano") == 0) + context = &simpleNano; + else if (strcmp(TT.mode, "vi") == 0) + context = &simpleVi; + } + + // TODO - Should do an isatty() here, though not sure about the usefullness of driving this from a script or redirected input, since it's supposed to be a UI for terminals. + // It would STILL need the terminal size for output though. Perhaps just bitch and abort if it's not a tty? + // On the other hand, sed don't need no stinkin' UI. And things like more or less should be usable on the end of a pipe. + + // Grab the old terminal settings and save it. + tcgetattr(0, &oldtermio); + tcflush(0, TCIFLUSH); + termio = oldtermio; + + // Mould the terminal to our will. + /* + IUCLC (not in POSIX) Map uppercase characters to lowercase on input. + IXON Enable XON/XOFF flow control on output. + IXOFF Enable XON/XOFF flow control on input. + IXANY (not in POSIX.1; XSI) Enable any character to restart output. + + ECHO Echo input characters. + ECHOE If ICANON is also set, the ERASE character erases the preceding input character, and WERASE erases the preceding word. + ECHOK If ICANON is also set, the KILL character erases the current line. + ECHONL If ICANON is also set, echo the NL character even if ECHO is not set. + TOSTOP Send the SIGTTOU signal to the process group of a background process which tries to write to its controlling terminal. + ICANON Enable canonical mode. This enables the special characters EOF, EOL, EOL2, ERASE, KILL, LNEXT, REPRINT, STATUS, and WERASE, and buffers by lines. + + VTIME Timeout in deciseconds for non-canonical read. + VMIN Minimum number of characters for non-canonical read. + + raw mode turning off ICANON, IEXTEN, and ISIG kills most special key processing. + termio.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON); + termio.c_oflag &= ~OPOST; + termio.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN); + termio.c_cflag &= ~(CSIZE | PARENB); + termio.c_cflag |= CS8; + + IGNBRK ignore BREAK + BRKINT complicated, bet in this context, sends BREAK as '\x00' + PARMRK characters with parity or frame erors are sent as '\x00' + ISTRIP strip 8th byte + INLCR translate LF to CR in input + IGLCR ignore CR + OPOST enable implementation defined output processing + ISIG generate signals on INTR (SIGINT on ^C), QUIT (SIGQUIT on ^\\), SUSP (SIGTSTP on ^Z), DSUSP (SIGTSTP on ^Y) + IEXTEN enable implementation defined input processing, turns on some key -> signal -tuff + CSIZE mask for character sizes, so in this case, we mask them all out + PARENB enable parity + CS8 8 bit characters + + VEOF "sends" EOF on ^D, ICANON turns that on. + VSTART restart output on ^Q, IXON turns that on. + VSTATUS display status info and sends SIGINFO (STATUS) on ^T. Not in POSIX, not supported in Linux. ICANON turns that on. + VSTOP stop output on ^S, IXON turns that on. + */ + termio.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IUCLC | IXON | IXOFF | IXANY); + termio.c_oflag &= ~OPOST; + termio.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL | TOSTOP | ICANON | ISIG | IEXTEN); + termio.c_cflag &= ~(CSIZE | PARENB); + termio.c_cflag |= CS8; + termio.c_cc[VTIME]=0; // deciseconds. + termio.c_cc[VMIN]=1; + tcsetattr(0, TCSANOW, &termio); + + terminal_size(&W, &H); + if (toys.optflags & FLAG_w) + W = TT.w; + if (toys.optflags & FLAG_h) + H = TT.h; + + // Create the main box. Right now the system needs one for wrapping around while switching. The H - 1 bit is to leave room for our example command line. + rootBox = addBox("root", context, toys.optargs[0], 0, 0, W, H - 1); + currentBox = rootBox; + + // Create the command line view, sharing the same context as the root. It will differentiate based on the view mode of the current box. + // Also load the command line history as it's file. + // TODO - different contexts will have different history files, though what to do about ones with no history, and ones with different histories for different modes? + commandLine = addView("command", rootBox->view->content->context, ".boxes.history", 0, H, W, 1); + // Add a prompt to it. + commandLine->prompt = xrealloc(commandLine->prompt, strlen(prompt) + 1); + strcpy(commandLine->prompt, prompt); + // Move to the end of the history. + moveCursorAbsolute(commandLine, 0, commandLine->content->lines.length, 0, 0); + + // All the mouse tracking methods suck one way or another. sigh + // http://rtfm.etla.org/xterm/ctlseq.html documents xterm stuff, near the bottom is the mouse stuff. + // http://leonerds-code.blogspot.co.uk/2012/04/wide-mouse-support-in-libvterm.html is helpful. + // Enable mouse (VT200 normal tracking mode, UTF8 encoding). The limit is 2015. Seems to only be in later xterms. +// fputs("\x1B[?1005h", stdout); + // Enable mouse (VT340 locator reporting mode). In theory has no limit. Wont actually work though. + // On the other hand, only allows for four buttons, so only half a mouse wheel. + // Responds with "\1B[e;p;r;c;p&w" where e is event type, p is a bitmap of buttons pressed, r and c are the mouse coords in decimal, and p is the "page number". +// fputs("\x1B[1;2'z\x1B[1;3'{", stdout); + // Enable mouse (VT200 normal tracking mode). Has a limit of 256 - 32 rows and columns. An xterm exclusive I think, but works in roxterm at least. No wheel reports. + // Responds with "\x1B[Mbxy" where x and y are the mouse coords, and b is bit encoded buttons and modifiers - 0=MB1 pressed, 1=MB2 pressed, 2=MB3 pressed, 3=release, 4=Shift, 8=Meta, 16=Control +// fputs("\x1B[?1000h", stdout); +// fflush(stdout); + + calcBoxes(currentBox); + drawBoxes(currentBox); + // Do the first cursor update. + updateLine(currentBox->view); + + // Run the main loop. + handle_keys((long) currentBox->view, handleEvent); + + // TODO - Should remember to turn off mouse reporting when we leave. + + // Restore the old terminal settings. + tcsetattr(0, TCSANOW, &oldtermio); + + + if (toys.optflags & FLAG_m) + { + if (strcmp(TT.mode, "sh") == 0) + { + int status, result, i; + double sum; + + /* Load the file containing the script we are going to run */ + status = luaL_loadfile(L, "toys/boxes/script.lua"); + if (status) + { + /* If something went wrong, error message is at the top of */ + /* the stack */ + fprintf(stderr, "Couldn't load file: %s\n", lua_tostring(L, -1)); + exit(1); + } + + /* + * Ok, now here we go: We pass data to the lua script on the stack. + * That is, we first have to prepare Lua's virtual stack the way we + * want the script to receive it, then ask Lua to run it. + */ + lua_newtable(L); /* We will pass a table */ + + /* + * To put values into the table, we first push the index, then the + * value, and then call lua_rawset() with the index of the table in the + * stack. Let's see why it's -3: In Lua, the value -1 always refers to + * the top of the stack. When you create the table with lua_newtable(), + * the table gets pushed into the top of the stack. When you push the + * index and then the cell value, the stack looks like: + * + * <- [stack bottom] -- table, index, value [top] + * + * So the -1 will refer to the cell value, thus -3 is used to refer to + * the table itself. Note that lua_rawset() pops the two last elements + * of the stack, so that after it has been called, the table is at the + * top of the stack. + */ + for (i = 1; i <= 5; i++) + { + lua_pushnumber(L, i); /* Push the table index */ + lua_pushnumber(L, i*2); /* Push the cell value */ + lua_rawset(L, -3); /* Stores the pair in the table */ + } + + /* By what name is the script going to reference our table? */ + lua_setglobal(L, "foo"); + + /* Ask Lua to run our little script */ + result = lua_pcall(L, 0, LUA_MULTRET, 0); + if (result) + { + fprintf(stderr, "Failed to run script: %s\n", lua_tostring(L, -1)); + exit(1); + } + + /* Get the returned value at the top of the stack (index -1) */ + sum = lua_tonumber(L, -1); + + printf("Script returned: %.0f\n", sum); + + lua_pop(L, 1); /* Take the returned value out of the stack */ + } + } + + lua_close(L); /* Cya, Lua */ + + puts(""); + fflush(stdout); +} -- cgit v1.1