aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/src/boxes.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/boxes.c')
-rw-r--r--src/boxes.c2607
1 files changed, 2607 insertions, 0 deletions
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 @@
1/* boxes.c - Generic editor development sandbox.
2 *
3 * Copyright 2012 David Seikel <won_fang@yahoo.com.au>
4 *
5 * Not in SUSv4. An entirely new invention, thus no web site either.
6 * See -
7 * http://pubs.opengroup.org/onlinepubs/9699919799/utilities/ex.html
8 * http://pubs.opengroup.org/onlinepubs/9699919799/utilities/more.html
9 * http://pubs.opengroup.org/onlinepubs/9699919799/utilities/sed.html
10 * http://pubs.opengroup.org/onlinepubs/9699919799/utilities/vi.html
11 * http://linux.die.net/man/1/less
12
13USE_BOXES(NEWTOY(boxes, "w#h#m(mode):a(stickchars)1", TOYFLAG_USR|TOYFLAG_BIN))
14
15config BOXES
16 bool "boxes"
17 default y
18 help
19 usage: boxes [-m|--mode mode] [-a|--stickchars] [-w width] [-h height]
20
21 Generic text editor and pager.
22
23 Mode selects which editor or text viewr it emulates, the choices are -
24 emacs is a microemacs type editor.
25 joe is a joe / wordstar type editor.
26 less is a less type pager.
27 mcedit (the default) is cooledit / mcedit type editor.
28 more is a more type pager.
29 nano is a nano / pico type editor.
30 vi is a vi type editor.
31
32 Stick chars means to use ASCII for the boxes instead of "graphics" characters.
33*/
34
35
36#include "toys.h"
37#include "lib/handlekeys.h"
38
39#include <lua.h>
40#include <lualib.h>
41#include <lauxlib.h>
42
43
44
45GLOBALS(
46 char *mode;
47 long h, w;
48)
49
50#define TT this.boxes
51
52#define FLAG_a 2
53#define FLAG_m 4
54#define FLAG_h 8
55#define FLAG_w 16
56
57
58/* This is trying to be a generic text editing, text viewing, and terminal
59 * handling system. The current code is a work in progress, and the design
60 * may change. Certainly at this moment it's only partly written. It is
61 * "usable" though, for a very small value of "usable". In the following
62 * I'll use "editors" to refer to the toys using this, though not all will
63 * be editors.
64 *
65 * The things it is targeting are - vi and more (part of the standards, so
66 * part of the toybox TODO), less (also on the toybox TODO), joe and
67 * wordstar (coz Rob said they would be good to include), nano (again Rob
68 * thinks it would be good and I agree), microemacs (to avoid religous
69 * wars), and mcedit (coz that's what I actually use). The ex editor comes
70 * along for the ride coz vi is basically a screen editor wrapper around
71 * the ex line editor. Sed might be supported coz I'll need to do basic
72 * editing functions that are common to the editors, and sed needs the same
73 * editing functions.
74 *
75 * I will also use this for a midnight commander clone as discussed on the
76 * mailing list. This would have things in common with emacs dired, so we
77 * might get that as well. Parts of this code could also be used for a
78 * file chooser, as used by some of the editors we are targeting. Finally,
79 * the terminal handling stuff might be useful for other toys, so should be
80 * generic in it's own right. Oh, screen is listed in the toybox TODO as
81 * "maybe", so I'll poke at that to.
82 *
83 * The basic building blocks are box, content, context, edit line, and
84 * view. A box is an on screen rectanglur area. Content is a file, and
85 * the text that is in that file. A context represents a particular editor
86 * type, it has key mappings and other similar stuff. The edit line is a
87 * single line where editing happens, it's similar to readline. A view is
88 * a view into a content, there can be many, it represents that portion of
89 * the content that is on screen right now.
90 *
91 * I plan on splitting these things up a bit so they can be used
92 * separately. Then I can make actually toybox libraries out of them. For
93 * now it's all one big file for ease of development.
94 *
95 * The screen is split into boxes, by default there are only two, the main
96 * text area and the command line at the bottom. Each box contains a view,
97 * and each view points to a content (file) for it's text. A content can
98 * have many views. Each content has a context (editor). There is only
99 * ever one edit line, it's the line that is being edited at the moment.
100 * The edit line moves within and between boxes (including the command
101 * line) as needed.
102 *
103 * The justification for boxes is that most of the editors we are trying to
104 * emulate include some splitting up of the screen area for various
105 * reasons, and some of them include a split window system as well. So
106 * this boxes concept covers command line, main editing area, split windows,
107 * menus, on screen display of command keys, file selection, and anything
108 * else that might be needed.
109 *
110 * To keep things simple boxes are organised as a binary tree of boxes.
111 * There is a root box, it's a global. Each box can have two sub boxes.
112 * Only the leaf nodes of the box tree are visible on the screen. Each box
113 * with sub boxes is split either horizontally or vertically. Navigating
114 * through the boxes goes depth first.
115 *
116 * A content keeps track of a file and it's text. Each content also has a
117 * context, which is a collection of the things that define a particular
118 * editor. (I might move the context pointer from content to view, makes
119 * more sense I think.)
120 *
121 * A context is the heart of the generic part of the system. Any given
122 * toybox command that uses this system would basically define a context
123 * and pass that to the rest of the system. See the end of this file for a
124 * few examples. A context holds a list of command to C function mappings,
125 * key to command mappings, and a list of modes.
126 *
127 * Most of the editors targetted include a command line where the user
128 * types in editor commands, and each of those editors has different
129 * commands. They would mostly use the same editing C functions though, or
130 * short wrappers around them. The vi context at the end of this file is a
131 * good example, it has a bunch of short C wrappers around some editing
132 * functions, or uses standard C editing functions directly. So a context
133 * has an editor command to C function mapping.
134 *
135 * The editors respond to keystrokes, and those keystrokes invoke editor
136 * commands, often in a modal way. So there are keystroke to editor
137 * command mappings. To cater for editing modes, each context has a list
138 * of modes, and each mode can have key to command mappings, as well as
139 * menu to command mappings, and a list of displayed key/command pairs.
140 * Menu and key/command pair display is not written yet. Most editors have
141 * a system for remapping key to command mappings, that's not supported
142 * yet. Emacs has a heirarchy of key to command mappings, that's not
143 * supported yet. Some twiddling of the current design would be needed for
144 * those.
145 *
146 * The key mappings used can be multiple keystrokes in a sequence, the
147 * system caters for that. Some can be multi byte like function keys, and
148 * even different strings of bytes depending on the terminal type. To
149 * simplify this, there is a table that maps various terminals ideas of
150 * special keys to key names, and the mapping of keys to commands uses
151 * those key names.
152 *
153 * A view represents the on screen visible portion of a content within a
154 * box. To cater for split window style editors, a content can have many
155 * views. It deals with keeping track of what's shown on screen, mapping
156 * the on screen representation of the text to the stored text during input
157 * and output. Each box holds one view.
158 *
159 * The edit line is basically a movable readline. There are editing C
160 * functions for moving it up and down lines within a view, and for
161 * shifting the edit line to some other box. So an editor would map the
162 * cursor keys for "up a line" and "down a line" to these C functions for
163 * example. Actual readline style functionality is just the bottom command
164 * box being a single line view, with the history file loaded into it's
165 * content, and the Enter key mapped to the editor contexts "do this
166 * command" function. Using most of the system with not much special casing.
167 *
168 *
169 * I assume that there wont be a terribly large number of boxes.
170 * Things like minimum box sizes, current maximum screen sizes, and the fact
171 * that they all have to be on the screen mean that this assumption should
172 * be safe. It's likely that most of the time there will be only a few at
173 * most. The point is there is a built in limit, there's only so many non
174 * overlapping boxes with textual contents that you can squeeze onto one
175 * terminal screen at once.
176 *
177 * I assume that there will only be one command line, no matter how many boxes,
178 * and that the command line is for the current box.
179 *
180 * I use a wide screen monitor and small font.
181 * My usual terminal is 104 x 380 characters.
182 * There will be people with bigger monitors and smaller fonts.
183 * So use sixteen bits for storing screen positions and the like.
184 * Eight bits wont cut it.
185 *
186 *
187 * These are the escape sequences we send -
188 * \x1B[m reset attributes and colours
189 * \x1B[1m turn on bold
190 * \x1B[%d;%dH move cursor
191 * Plus some experimentation with turning on mouse reporting that's not
192 * currently used.
193 *
194 *
195 * TODO - disentangle boxes from views.
196 *
197 * TODO - should split this up into editing, UI, and boxes parts,
198 * so the developer can leave out bits they are not using.
199 *
200 * TODO - Show status line instead of command line when it's not being edited.
201 *
202 * TODO - should review it all for UTF8 readiness. Think I can pull that off
203 * by keeping everything on the output side as "screen position", and using
204 * the formatter to sort out the input to output mapping.
205 *
206 * TODO - see if there are any simple shortcuts to avoid recalculating
207 * everything all the time. And to avoid screen redraws.
208 */
209
210/* Robs "It's too big" lament.
211
212> So when you give me code, assume I'm dumber than you. Because in this
213> context, I am. I need bite sized pieces, each of which I can
214> understand in its entirety before moving on to the next.
215
216As I mentioned in my last email to the Aboriginal linux list, I wrote
217the large blob so I could see how the ideas all work to do the generic
218editor stuff and other things. It kinda needs to be that big to do
219anything that is actually useful. So, onto musings about cutting it up
220into bite sized bits...
221
222You mentioned on the Aboriginal Linux list that you wanted a
223"readline", and you listed some features. My reply was that you had
224basically listed the features of my "basic editor". The main
225difference between "readline" and a full screen editor, is that the
226editor is fullscreen, while the "readline" is one line. They both have
227to deal with a list of lines, going up and down through those lines,
228editing the contents of those lines, one line at a time. For persistent
229line history, they both have to save and load those lines to a file.
230Other than that "readline" adds other behaviour, like moving the
231current line to the end when you hit return and presenting that line to
232the caller (here's the command). So just making "readline" wont cut
233out much of the code. In fact, my "readline" is really just a thin
234wrapper around the rest of the code.
235
236Starting from the other end of the size spectrum, I guess "find out
237terminal size" is a small enough bite sized chunk. To actually do
238anything useful with that you would probably start to write stuff I've
239already written though. Would be better off just using the stuff I've
240already written. On the other hand, maybe that would be useful for
241"ls" listings and the like, then we can start with just that bit?
242
243I guess the smallest useful command I have in my huge blob of code
244would be "more". I could even cut it down more. Show a page of text,
245show the next page of text when you hit the space bar, quit when you
246hit "q". Even then, I would estimate it would only cut out a third of
247the code.
248
249On the other hand, there's a bunch of crap in that blob that is for
250future plans, and is not doing anything now.
251
252In the end though, breaking it up into suitable bite sized bits might
253be almost as hard as writing it in the first place. I'll see what I
254can do, when I find some time. I did want to break it up into three
255bits anyway, and there's a TODO to untangle a couple of bits that are
256currently too tightly coupled.
257
258*/
259
260/* Robs contradiction
261
262On Thu, 27 Dec 2012 06:06:53 -0600 Rob Landley <rob@landley.net> wrote:
263
264> On 12/27/2012 12:56:07 AM, David Seikel wrote:
265> > On Thu, 27 Dec 2012 00:37:46 -0600 Rob Landley <rob@landley.net>
266> > wrote:
267> > > Since ls isn't doiing any of that, probing would just be awkward
268> > > so toysh should set COLUMNS to a sane value. The problem is,
269> > > we're not using toysh yet because it's still just a stub...
270> >
271> > I got some or all of that terminal sizing stuff in my editor thingy
272> > already if I remember correctly. I remember you wanted me to cut it
273> > down into tiny bits to start with, so you don't have to digest the
274> > huge lump I sent.
275>
276> Basically what I'd like is a self-contained line reader. More or less
277> a getline variant that can handle cursoring left and right to insert
278> stuff, handle backspace past a wordwrap (which on unix requires
279> knowing the screen width and your current cursor position), and one
280> that lets up/down escape sequences be hooked for less/more screen
281> scrolling, vi line movement, shell command history, and so on.
282
283Which is most of an editor already, so how to break it up into bite
284sized morsels?
285
286> There's sort of three levels here, the first is "parse raw input,
287> assembling escape sequences into cursor-left and similar as
288> necessary". (That's the one I had to redo in busybox vi because they
289> didn't do it right.)
290>
291> The second is "edit a line of text, handling cursoring _within_ the
292> line". That's the better/interactive getline().
293>
294> The third is handling escapes from that line triggering behavior in
295> the larger command (cursor up and cursor down do different things in
296> less, in vi, and in shell command history).
297>
298> The fiddly bit is that terminal_size() sort of has to integrate with
299> all of these. Possibly I need to have width and height be members of
300> struct toy_context. Or have the better_getline() take a pointer to a
301> state structure it can update, containing that info...
302
303*/
304
305
306static char *borderChars[][6] =
307{
308 {"-", "|", "+", "+", "+", "+"}, // "stick" characters.
309 {"\xE2\x94\x80", "\xE2\x94\x82", "\xE2\x94\x8C", "\xE2\x94\x90", "\xE2\x94\x94", "\xE2\x94\x98"}, // UTF-8
310 {"\x71", "\x78", "\x6C", "\x6B", "\x6D", "\x6A"}, // VT100 alternate character set.
311 {"\xC4", "\xB3", "\xDA", "\xBF", "\xC0", "\xD9"} // DOS
312};
313
314static char *borderCharsCurrent[][6] =
315{
316 {"=", "#", "+", "+", "+", "+"}, // "stick" characters.
317 {"\xE2\x95\x90", "\xE2\x95\x91", "\xE2\x95\x94", "\xE2\x95\x97", "\xE2\x95\x9A", "\xE2\x95\x9D"}, // UTF-8
318 {"\x71", "\x78", "\x6C", "\x6B", "\x6D", "\x6A"}, // VT100 alternate character set has none of these. B-(
319 {"\xCD", "\xBA", "\xC9", "\xBB", "\xC8", "\xBC"} // DOS
320};
321
322
323typedef struct _box box;
324typedef struct _view view;
325
326typedef void (*boxFunction) (box *box);
327typedef void (*eventHandler) (view *view);
328
329struct function
330{
331 char *name; // Name for script purposes.
332 char *description; // Human name for the menus.
333 char type;
334 union
335 {
336 eventHandler handler;
337 char *scriptCallback;
338 };
339};
340
341struct keyCommand
342{
343 char *key, *command;
344};
345
346struct item
347{
348 char *text; // What's shown to humans.
349 struct key *key; // Shortcut key while the menu is displayed.
350 // If there happens to be a key bound to the same command, the menu system should find that and show it to.
351 char type;
352 union
353 {
354 char *command;
355 struct item *items; // An array of next level menu items.
356 };
357};
358
359struct borderWidget
360{
361 char *text, *command;
362};
363
364// TODO - a generic "part of text", and what is used to define them.
365// For instance - word, line, paragraph, section.
366// Each context can have a collection of these.
367
368struct mode
369{
370 struct keyCommand *keys; // An array of key to command mappings.
371 struct item *items; // An array of top level menu items.
372 struct item *functionKeys; // An array of single level "menus". Used to show key commands.
373 uint8_t flags; // commandMode.
374};
375
376/*
377Have a common menu up the top.
378 MC has a menu that changes per mode.
379 Nano has no menu.
380Have a common display of certain keys down the bottom.
381 MC is one row of F1 to F10, but changes for edit / view / file browse. But those are contexts here.
382 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.
383*/
384struct context // Defines a context for content. Text viewer, editor, file browser for instance.
385{
386 struct function *commands; // The master list, the ones pointed to by the menus etc should be in this list.
387 struct mode *modes; // A possible empty array of modes, indexed by the view.
388 // OR might be better to have these as a linked list, so that things like Emacs can have it's mode keymap hierarcy.
389 eventHandler handler; // TODO - Might be better to put this in the modes. I think vi will need that.
390 // Should set the damage list if it needs a redraw, and flags if border or status line needs updating.
391 // Keyboard / mouse events if the box did not handle them itself.
392 // 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.
393 // Scroll event if the content wants to handle that itself.
394 // Timer event for things like top that might want to have this called regularly.
395 boxFunction doneRedraw; // The box is done with it's redraw, so we can free the damage list or whatever now.
396 boxFunction delete;
397 // This can be used as the sub struct for various context types. Like viewer, editor, file browser, top, etc.
398 // Could even be an object hierarchy, like generic editor, which Basic vi inherits from.
399 // Or not, since the commands might be different / more of them.
400};
401
402// TODO - might be better off just having a general purpose "widget" which includes details of where it gets attached.
403// Status lines can have them to.
404struct border
405{
406 struct borderWidget *topLeft, *topMiddle, *topRight;
407 struct borderWidget *bottomLeft, *bottomMiddle, *bottomRight;
408 struct borderWidget *left, *right;
409};
410
411struct line
412{
413 struct line *next, *prev;
414 uint32_t length; // Careful, this is the length of the allocated memory for real lines, but the number of lines in the header node.
415 char *line; // Should be blank for the header.
416};
417
418struct damage
419{
420 struct damage *next; // A list for faster draws?
421 uint16_t X, Y, W, H; // The rectangle to be redrawn.
422 uint16_t offset; // Offest from the left for showing lines.
423 struct line *lines; // Pointer to a list of text lines, or NULL.
424 // Note - likely a pointer into the middle of the line list in a content.
425};
426
427struct content // For various instances of context types.
428 // Editor / text viewer might have several files open, so one of these per file.
429 // MC might have several directories open, one of these per directory. No idea why you might want to do this. lol
430{
431 struct context *context;
432 char *name, *file, *path;
433 struct line lines;
434// file type
435// double linked list of bookmarks, pointer to line, character position, length (or ending position?), type, blob for types to keep context.
436 uint16_t minW, minH, maxW, maxH;
437 uint8_t flags; // readOnly, modified.
438 // This can be used as the sub struct for various content types.
439};
440
441struct _view
442{
443 struct content *content;
444 box *box;
445 struct border *border; // Can be NULL.
446 char *statusLine; // Text of the status line, or NULL if none.
447 int mode; // For those contexts that can be modal. Normally used to index the keys, menus, and key displays.
448 struct damage *damage; // Can be NULL. If not NULL after context->doneRedraw(), box will free it and it's children.
449 // TODO - Gotta be careful of overlapping views.
450 void *data; // The context controls this blob, it's specific to each box.
451 uint32_t offsetX, offsetY; // Offset within the content, coz box handles scrolling, usually.
452 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.
453 uint16_t cX, cY; // Cursor position within the content.
454 uint16_t iX, oW; // Cursor position inside the lines input text, in case the formatter makes it different, and output length.
455 char *output; // The current line formatted for output.
456 uint8_t flags; // redrawStatus, redrawBorder;
457
458 // 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).
459 struct line *line; // Pointer to the current line, might be the only line.
460 char *prompt; // Optional prompt for the editLine.
461
462// Display mode / format hook.
463// view specific bookmarks, including highlighted block and it's type.
464// Linked list of selected lines for a filtered view, or processing only those lines.
465// 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.
466};
467
468struct _box
469{
470 box *sub1, *sub2, *parent;
471 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.
472 // If it's just a parent box, it wont have this, so just make it a damn pointer, that's the simplest thing. lol
473 // TODO - Are parent boxes getting a view anyway?
474 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.
475 float split; // Ratio of sub1's part of the split, the sub2 box gets the rest.
476 uint8_t flags; // Various flags.
477};
478
479
480// Sometimes you just can't avoid circular definitions.
481void drawBox(box *box);
482
483
484#define BOX_HSPLIT 1 // Marks if it's a horizontally or vertically split.
485#define BOX_BORDER 2 // Mark if it has a border, often full screen boxes wont.
486
487static int overWriteMode;
488static box *rootBox; // Parent of the rest of the boxes, or the only box. Always a full screen.
489static box *currentBox;
490static view *commandLine;
491static int commandMode;
492
493#define MEM_SIZE 128 // Chunk size for line memory allocation.
494
495// Inserts the line after the given line, or at the end of content if no line.
496struct line *addLine(struct content *content, struct line *line, char *text, uint32_t length)
497{
498 struct line *result = NULL;
499 uint32_t len;
500
501 if (!length)
502 length = strlen(text);
503 // Round length up.
504 len = (((length + 1) / MEM_SIZE) + 1) * MEM_SIZE;
505 result = xzalloc(sizeof(struct line));
506 result->line = xzalloc(len);
507 result->length = len;
508 strncpy(result->line, text, length);
509
510 if (content)
511 {
512 if (!line)
513 line = content->lines.prev;
514
515 result->next = line->next;
516 result->prev = line;
517
518 line->next->prev = result;
519 line->next = result;
520
521 content->lines.length++;
522 }
523 else
524 {
525 result->next = result;
526 result->prev = result;
527 }
528
529 return result;
530}
531
532void freeLine(struct content *content, struct line *line)
533{
534 line->next->prev = line->prev;
535 line->prev->next = line->next;
536 if (content)
537 content->lines.length--;
538 free(line->line);
539 free(line);
540}
541
542void loadFile(struct content *content)
543{
544 int fd = open(content->path, O_RDONLY);
545
546 if (-1 != fd)
547 {
548 char *temp = NULL;
549 long len;
550
551 do
552 {
553 // TODO - get_line() is slow, and wont help much with DOS and Mac line endings.
554 temp = get_line(fd);
555 if (temp)
556 addLine(content, NULL, temp, len);
557 } while (temp);
558 close(fd);
559 }
560}
561
562// TODO - load and save should be able to deal with pipes, and with loading only parts of files, to load more parts later.
563
564void saveFile(struct content *content)
565{
566// TODO - Should do "Save as" as well. Which is just a matter of changing content->path before calling this.
567 int fd;
568
569 fd = open(content->path, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH);
570
571 if (-1 != fd)
572 {
573 struct line *line = content->lines.next;
574
575 while (&(content->lines) != line) // We are at the end if we have wrapped to the beginning.
576 {
577 write(fd, line->line, strlen(line->line));
578 write(fd, "\n", 1);
579 line = line->next;
580 }
581 close(fd);
582 }
583 else
584 {
585 fprintf(stderr, "Can't open file %s\n", content->path);
586 exit(1);
587 }
588}
589
590struct content *addContent(char *name, struct context *context, char *filePath)
591{
592 struct content *result = xzalloc(sizeof(struct content));
593
594 result->lines.next = &(result->lines);
595 result->lines.prev = &(result->lines);
596 result->name = strdup(name);
597 result->context = context;
598
599 if (filePath)
600 {
601 result->path = strdup(filePath);
602 loadFile(result);
603 }
604
605 return result;
606}
607
608// General purpose line moosher. Used for appends, inserts, overwrites, and deletes.
609// TODO - should have the same semantics as mooshStrings, only it deals with whole lines in double linked lists.
610// We need content so we can adjust it's number of lines if needed.
611void mooshLines(struct content *content, struct line *result, struct line *moosh, uint16_t index, uint16_t length, int insert)
612{
613}
614
615// General purpose string moosher. Used for appends, inserts, overwrites, and deletes.
616void mooshStrings(struct line *result, char *moosh, uint16_t index, uint16_t length, int insert)
617{
618 char *c, *pos;
619 int limit = strlen(result->line);
620 int mooshLen = 0, resultLen;
621
622 if (moosh)
623 mooshLen = strlen(moosh);
624
625 /*
626 * moosh == NULL a deletion
627 * length == 0 simple insertion
628 * length < mooshlen delete some, insert moosh
629 * length == mooshlen exact overwrite.
630 * length > mooshlen delete a lot, insert moosh
631 */
632
633 mooshLen -= length;
634 resultLen = limit + mooshLen;
635
636 // If we need more space, allocate more.
637 if (resultLen > result->length)
638 {
639 result->length = resultLen + MEM_SIZE;
640 result->line = xrealloc(result->line, result->length);
641 }
642
643 if (limit <= index) // At end, just add to end.
644 {
645 // TODO - Possibly add spaces to pad out to where index is?
646 // Would be needed for "go beyond end of line" and "column blocks".
647 // Both of those are advanced editing.
648 index = limit;
649 insert = 1;
650 }
651
652 pos = &(result->line[index]);
653
654 if (insert) // Insert / delete before current character, so move it and the rest up / down mooshLen bytes.
655 {
656 if (0 < mooshLen) // Gotta move things up.
657 {
658 c = &(result->line[limit]);
659 while (c >= pos)
660 {
661 *(c + mooshLen) = *c;
662 c--;
663 }
664 }
665 else if (0 > mooshLen) // Gotta move things down.
666 {
667 c = pos;
668 while (*c)
669 {
670 *c = *(c - mooshLen); // A double negative.
671 c++;
672 }
673 }
674 }
675
676 if (moosh)
677 {
678 c = moosh;
679 do
680 {
681 *pos++ = *c++;
682 }
683 while (*c);
684 }
685}
686
687// TODO - Should draw the current border in green, the text as default (or highlight / bright).
688// Then allow one other box to be red bordered (MC / dired destination box).
689// All other boxes with dark gray border, and dim text.
690void drawLine(int y, int start, int end, char *left, char *internal, char *contents, char *right, int current)
691{
692 int size = strlen(internal);
693 int len = (end - start) * size, x = 0;
694 char line[len + 1];
695
696 if ('\0' != left[0]) // Assumes that if one side has a border, then so does the other.
697 len -= 2 * size;
698
699 if (contents)
700 {
701 // strncpy wont add a null at the end if the source is longer, but will pad with nulls if source is shorter.
702 // So it's best to put a safety null in first.
703 line[len] = '\0';
704 strncpy(line, contents, len);
705 // Make sure the following while loop pads out line with the internal character.
706 x = strlen(line);
707 }
708 while (x < len)
709 {
710 strcpy(&line[x], internal);
711 x += size;
712 }
713 line[x++] = '\0';
714 if ('\0' == left[0]) // Assumes that if one side has a border, then so does the other.
715 {
716 if (current)
717 printf("\x1B[1m\x1B[%d;%dH%s\x1B[m", y + 1, start + 1, line);
718 else
719 printf("\x1B[m\x1B[%d;%dH%s", y + 1, start + 1, line);
720 }
721 else
722 {
723 if (current)
724 printf("\x1B[1m\x1B[%d;%dH%s%s%s\x1B[m", y + 1, start + 1, left, line, right);
725 else
726 printf("\x1B[m\x1B[%d;%dH%s%s%s", y + 1, start + 1, left, line, right);
727 }
728}
729
730void formatCheckCursor(view *view, long *cX, long *cY, char *input)
731{
732 int i = 0, o = 0, direction = (*cX) - view->cX;
733
734 // Adjust the cursor if needed, depending on the contents of the line, and the direction of travel.
735 while (input[i])
736 {
737 // When o is equal to the cX position, update the iX position to be where i is.
738 if ('\t' == input[i])
739 {
740 int j = 8 - (i % 8);
741
742 // Check if the cursor is in the middle of the tab.
743 if (((*cX) > o) && ((*cX) < (o + j)))
744 {
745 if (0 <= direction)
746 {
747 *cX = (o + j);
748 view->iX = i + 1;
749 }
750 else
751 {
752 *cX = o;
753 view->iX = i;
754 }
755 }
756 o += j;
757 }
758 else
759 {
760 if ((*cX) == o)
761 view->iX = i;
762 o++;
763 }
764 i++;
765 }
766 // One more check in case the cursor is at the end of the line.
767 if ((*cX) == o)
768 view->iX = i;
769}
770
771// TODO - Should convert control characters to reverse video, and deal with UTF8.
772
773/* FIXME - We get passed a NULL input, apparently when the file length is close to the screen length. -
774> On Thu, 6 Sep 2012 11:56:17 +0800 Roy Tam <roytam@gmail.com> wrote:
775>
776> > 2012/9/6 David Seikel <onefang@gmail.com>:
777> > >> Program received signal SIGSEGV, Segmentation fault.
778> > >> formatLine (view=0x8069168, input=0x0, output=0x806919c)
779> > >> at toys/other/boxes.c:843
780> > >> 843 int len = strlen(input), i = 0, o = 0;
781> > >> (gdb) bt
782> > >> #0 formatLine (view=0x8069168, input=0x0, output=0x806919c)
783> > >> at toys/other/boxes.c:843
784> > >> #1 0x0804f1dd in moveCursorAbsolute (view=0x8069168, cX=0,
785> > >> cY=10, sX=0, sY=0) at toys/other/boxes.c:951
786> > >> #2 0x0804f367 in moveCursorRelative (view=0x8069168, cX=0,
787> > >> cY=10, sX=0, sY=0) at toys/other/boxes.c:1011
788> > >> #3 0x0804f479 in upLine (view=0x8069168, event=0x0) at
789> > >> toys/other/boxes.c:1442 #4 0x0804fb63 in handleKey
790> > >> (view=0x8069168, i=2, keyName=<optimized out>, buffer=0xbffffad8
791> > >> "\033[A") at toys/other/boxes.c:1593 #5 0x0805008d in editLine
792> > >> (view=0x8069168, X=-1, Y=-1, W=-1, H=-1) at
793> > >> toys/other/boxes.c:1785 #6 0x08050288 in boxes_main () at
794> > >> toys/other/boxes.c:2482 #7 0x0804b262 in toy_exec
795> > >> (argv=0xbffffd58) at main.c:104 #8 0x0804b29d in toybox_main ()
796> > >> at main.c:118 #9 0x0804b262 in toy_exec (argv=0xbffffd54) at
797> > >> main.c:104 #10 0x0804b29d in toybox_main () at main.c:118
798> > >> #11 0x0804affa in main (argc=5, argv=0xbffffd54) at main.c:159
799> > >
800> > > No segfault here when I try that, nor with different files. As I
801> > > said, it has bugs. I have seen other cases before when NULL lines
802> > > are passed around near that code. Guess I've not got them all.
803> > > Might help to mention what terminal proggy you are using, it's
804> > > size in characters, toybox version, OS, etc. Something is
805> > > different between you and me.
806> >
807> > Terminal Program: PuTTY
808> > Windows size: 80 * 25 chars
809> > Toybox version: hg head
810> > OS: Linux 3.2 (Debian wheezy)
811> > File: Toybox LICENSE file
812>
813> I'll install PuTTY, try toybox hg head, and play with them later, see
814> if I can reproduce that segfault. I use the last released tarball of
815> toybox, an old Ubuntu, and a bunch of other terminal proggies. I did
816> not know that PuTTY had been ported to Unix.
817>
818> But as I mentioned, not interested in a bug hunt right now. That will
819> be more appropriate when it's less of a "sandbox for testing the
820> ideas" and more of a "good enough to bother using". Always good to
821> have other terminals for testing though.
822
823Seems to be sensitive to the number of lines. On my usual huge
824roxterm, using either toybox 0.4 or hg head, doing this -
825
826./toybox boxes -m less LICENSE -h 25
827
828and just hitting down arrow until you get to the bottom of the page
829segfaults as well. -h means "pretend the terminal is that many lines
830high", there's a similar -w as well. 80x25 in any other terminal did
831the same. For that particular file (17 lines long), any value of -h
832between 19 and 34 will segfault after some moving around. -h 35 makes
833it segfault straight away. Less than 19 or over 35 is fine. Longer
834files don't have that problem, though I suspect it will happen on
835shortish files where the number of lines is about the length of the
836screen.
837
838I guess that somewhere I'm doing maths wrong and getting an out of
839bounds line number when the file length is close to the screen length.
840I'll make note of that, and fix it when I next get time to beat on
841boxes.
842
843Thanks for reporting it Roy.
844*/
845
846int formatLine(view *view, char *input, char **output)
847{
848 int len = strlen(input), i = 0, o = 0;
849
850 *output = xrealloc(*output, len + 1);
851
852 while (input[i])
853 {
854 if ('\t' == input[i])
855 {
856 int j = 8 - (i % 8);
857
858 *output = xrealloc(*output, len + j + 1);
859 for (; j; j--)
860 {
861 (*output)[o++] = ' ';
862 len++;
863 }
864 len--; // Not counting the actual tab character itself.
865 }
866 else
867 (*output)[o++] = input[i];
868 i++;
869 }
870 (*output)[o++] = '\0';
871
872 return len;
873}
874
875void drawContentLine(view *view, int y, int start, int end, char *left, char *internal, char *contents, char *right, int current)
876{
877 char *temp = NULL;
878 int offset = view->offsetX, len;
879
880 if (contents == view->line->line)
881 {
882 view->oW = formatLine(view, contents, &(view->output));
883 temp = view->output;
884 len = view->oW;
885 }
886 else // Only time we are not drawing the current line, and only used for drawing the entire page.
887 len = formatLine(NULL, contents, &(temp));
888
889 if (offset > len)
890 offset = len;
891 drawLine(y, start, end, left, internal, &(temp[offset]), right, current);
892}
893
894void updateLine(view *view)
895{
896 int y, len;
897
898 // Coz things might change out from under us, find the current view. Again.
899 if (commandMode) view = commandLine;
900 else view = currentBox->view;
901
902 // TODO - When doing scripts and such, might want to turn off the line update until finished.
903 // Draw the prompt and the current line.
904 y = view->Y + (view->cY - view->offsetY);
905 len = strlen(view->prompt);
906 drawLine(y, view->X, view->X + view->W, "", " ", view->prompt, "", 0);
907 drawContentLine(view, y, view->X + len, view->X + view->W, "", " ", view->line->line, "", 1);
908 // Move the cursor.
909 printf("\x1B[%d;%dH", y + 1, view->X + len + (view->cX - view->offsetX) + 1);
910 fflush(stdout);
911}
912
913void doCommand(view *view, char *command)
914{
915 if (command)
916 {
917 struct function *functions = view->content->context->commands;
918 int i;
919
920// TODO - Some editors have a shortcut command concept. The smallest unique first part of each command will match, as well as anything longer.
921// A further complication is if we are not implementing some commands that might change what is "shortest unique prefix".
922
923 for (i = 0; functions[i].name; i++)
924 {
925 if (strcmp(functions[i].name, command) == 0)
926 {
927 if (functions[i].handler)
928 {
929 functions[i].handler(view);
930 updateLine(view);
931 }
932 break;
933 }
934 }
935 }
936}
937
938int moveCursorAbsolute(view *view, long cX, long cY, long sX, long sY)
939{
940 struct line *newLine = view->line;
941 long oX = view->offsetX, oY = view->offsetY;
942 long lX = view->oW, lY = view->content->lines.length - 1;
943 long nY = view->cY;
944 uint16_t w = view->W - 1, h = view->H - 1;
945 int moved = 0, updatedY = 0, endOfLine = 0;
946
947 // Check if it's still within the contents.
948 if (0 > cY) // Trying to move before the beginning of the content.
949 cY = 0;
950 else if (lY < cY) // Trying to move beyond end of the content.
951 cY = lY;
952 if (0 > cX) // Trying to move before the beginning of the line.
953 {
954 // See if we can move to the end of the previous line.
955 if (view->line->prev != &(view->content->lines))
956 {
957 cY--;
958 endOfLine = 1;
959 }
960 else
961 cX = 0;
962 }
963 else if (lX < cX) // Trying to move beyond end of line.
964 {
965 // See if we can move to the begining of the next line.
966 if (view->line->next != &(view->content->lines))
967 {
968 cY++;
969 cX = 0;
970 }
971 else
972 cX = lX;
973 }
974
975 // Find the new line.
976 while (nY != cY)
977 {
978 updatedY = 1;
979 if (nY < cY)
980 {
981 newLine = newLine->next;
982 nY++;
983 if (view->content->lines.prev == newLine) // We are at the end if we have wrapped to the beginning.
984 break;
985 }
986 else
987 {
988 newLine = newLine->prev;
989 nY--;
990 if (view->content->lines.next == newLine) // We are at the end if we have wrapped to the beginning.
991 break;
992 }
993 }
994 cY = nY;
995
996 // Check if we have moved past the end of the new line.
997 if (updatedY)
998 {
999 // Format it.
1000 view->oW = formatLine(view, newLine->line, &(view->output));
1001 if (view->oW < cX)
1002 endOfLine = 1;
1003 if (endOfLine)
1004 cX = view->oW;
1005 }
1006
1007 // Let the formatter decide if it wants to adjust things.
1008 // It's up to the formatter to deal with things if it changes cY.
1009 // On the other hand, changing cX is it's job.
1010 formatCheckCursor(view, &cX, &cY, newLine->line);
1011
1012 // Check the scrolling.
1013 lY -= view->H - 1;
1014 oX += sX;
1015 oY += sY;
1016
1017 if (oY > cY) // Trying to move above the box.
1018 oY += cY - oY;
1019 else if ((oY + h) < cY) // Trying to move below the box
1020 oY += cY - (oY + h);
1021 if (oX > cX) // Trying to move to the left of the box.
1022 oX += cX - oX;
1023 else if ((oX + w) <= cX) // Trying to move to the right of the box.
1024 oX += cX - (oX + w);
1025
1026 if (oY < 0)
1027 oY = 0;
1028 if (oY >= lY)
1029 oY = lY;
1030 if (oX < 0)
1031 oX = 0;
1032 // TODO - Should limit oX to less than the longest line, minus box width.
1033 // Gonna be a pain figuring out what the longest line is.
1034 // 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.
1035 // Though still might want to do that for the longest line on the new page to be.
1036
1037 if ((view->cX != cX) || (view->cY != cY))
1038 moved = 1;
1039
1040 // Actually update stuff, though some have been done already.
1041 view->cX = cX;
1042 view->cY = cY;
1043 view->line = newLine;
1044
1045 // Handle scrolling.
1046 if ((view->offsetX != oX) || (view->offsetY != oY))
1047 {
1048 view->offsetX = oX;
1049 view->offsetY = oY;
1050
1051 if (view->box)
1052 drawBox(view->box);
1053 }
1054
1055 return moved;
1056}
1057
1058int moveCursorRelative(view *view, long cX, long cY, long sX, long sY)
1059{
1060 return moveCursorAbsolute(view, view->cX + cX, view->cY + cY, sX, sY);
1061}
1062
1063void sizeViewToBox(box *box, int X, int Y, int W, int H)
1064{
1065 uint16_t one = 1, two = 2;
1066
1067 if (!(box->flags & BOX_BORDER))
1068 {
1069 one = 0;
1070 two = 0;
1071 }
1072 box->view->X = X;
1073 box->view->Y = Y;
1074 box->view->W = W;
1075 box->view->H = H;
1076 if (0 > X) box->view->X = box->X + one;
1077 if (0 > Y) box->view->Y = box->Y + one;
1078 if (0 > W) box->view->W = box->W - two;
1079 if (0 > H) box->view->H = box->H - two;
1080}
1081
1082view *addView(char *name, struct context *context, char *filePath, uint16_t X, uint16_t Y, uint16_t W, uint16_t H)
1083{
1084 view *result = xzalloc(sizeof(struct _view));
1085
1086 result->X = X;
1087 result->Y = Y;
1088 result->W = W;
1089 result->H = H;
1090
1091 result->content = addContent(name, context, filePath);
1092 result->prompt = xzalloc(1);
1093 // If there was content, format it's first line as usual, otherwise create an empty first line.
1094 if (result->content->lines.next != &(result->content->lines))
1095 {
1096 result->line = result->content->lines.next;
1097 result->oW = formatLine(result, result->line->line, &(result->output));
1098 }
1099 else
1100 {
1101 result->line = addLine(result->content, NULL, "\0", 0);
1102 result->output = xzalloc(1);
1103 }
1104
1105 return result;
1106}
1107
1108box *addBox(char *name, struct context *context, char *filePath, uint16_t X, uint16_t Y, uint16_t W, uint16_t H)
1109{
1110 box *result = xzalloc(sizeof(struct _box));
1111
1112 result->X = X;
1113 result->Y = Y;
1114 result->W = W;
1115 result->H = H;
1116 result->view = addView(name, context, filePath, X, Y, W, H);
1117 result->view->box = result;
1118 sizeViewToBox(result, X, Y, W, H);
1119
1120 return result;
1121}
1122
1123void freeBox(box *box)
1124{
1125 if (box)
1126 {
1127 freeBox(box->sub1);
1128 freeBox(box->sub2);
1129 if (box->view)
1130 {
1131 // In theory the line should not be part of the content if there is no content, so we should free it.
1132 if (!box->view->content)
1133 freeLine(NULL, box->view->line);
1134 free(box->view->prompt);
1135 free(box->view->output);
1136 free(box->view);
1137 }
1138 free(box);
1139 }
1140}
1141
1142void drawBox(box *box)
1143{
1144 // This could be heavily optimized, but let's keep things simple for now.
1145 // Optimized for sending less characters I mean, on slow serial links for instance.
1146
1147 char **bchars = (toys.optflags & FLAG_a) ? borderChars[0] : borderChars[1];
1148 char *left = "\0", *right = "\0";
1149 struct line *lines = NULL;
1150 int y = box->Y, current = (box == currentBox);
1151 uint16_t h = box->Y + box->H;
1152
1153 if (current)
1154 bchars = (toys.optflags & FLAG_a) ? borderCharsCurrent[0] : borderCharsCurrent[1];
1155
1156 // Slow and laborious way to figure out where in the linked list of lines we start from.
1157 // Wont scale well, but is simple.
1158 if (box->view && box->view->content)
1159 {
1160 int i = box->view->offsetY;
1161
1162 lines = &(box->view->content->lines);
1163 while (i--)
1164 {
1165 lines = lines->next;
1166 if (&(box->view->content->lines) == lines) // We are at the end if we have wrapped to the beginning.
1167 break;
1168 }
1169 }
1170
1171 if (box->flags & BOX_BORDER)
1172 {
1173 h--;
1174 left = right = bchars[1];
1175 drawLine(y++, box->X, box->X + box->W, bchars[2], bchars[0], NULL, bchars[3], current);
1176 }
1177
1178 while (y < h)
1179 {
1180 char *line = "";
1181
1182 if (lines)
1183 {
1184 lines = lines->next;
1185 if (&(box->view->content->lines) == lines) // We are at the end if we have wrapped to the beginning.
1186 lines = NULL;
1187 else
1188 line = lines->line;
1189 // Figure out which line is our current line while we are here.
1190 if (box->view->Y + (box->view->cY - box->view->offsetY) == y)
1191 box->view->line = lines;
1192 }
1193 drawContentLine(box->view, y++, box->X, box->X + box->W, left, " ", line, right, current);
1194 }
1195 if (box->flags & BOX_BORDER)
1196 drawLine(y++, box->X, box->X + box->W, bchars[4], bchars[0], NULL, bchars[5], current);
1197 fflush(stdout);
1198}
1199
1200void drawBoxes(box *box)
1201{
1202 if (box->sub1) // If there's one sub box, there's always two.
1203 {
1204 drawBoxes(box->sub1);
1205 drawBoxes(box->sub2);
1206 }
1207 else
1208 drawBox(box);
1209}
1210
1211void calcBoxes(box *box)
1212{
1213 if (box->sub1) // If there's one sub box, there's always two.
1214 {
1215 box->sub1->X = box->X;
1216 box->sub1->Y = box->Y;
1217 box->sub1->W = box->W;
1218 box->sub1->H = box->H;
1219 box->sub2->X = box->X;
1220 box->sub2->Y = box->Y;
1221 box->sub2->W = box->W;
1222 box->sub2->H = box->H;
1223 if (box->flags & BOX_HSPLIT)
1224 {
1225 box->sub1->H *= box->split;
1226 box->sub2->H -= box->sub1->H;
1227 box->sub2->Y += box->sub1->H;
1228 }
1229 else
1230 {
1231 box->sub1->W *= box->split;
1232 box->sub2->W -= box->sub1->W;
1233 box->sub2->X += box->sub1->W;
1234 }
1235 sizeViewToBox(box->sub1, -1, -1, -1, -1);
1236 calcBoxes(box->sub1);
1237 sizeViewToBox(box->sub2, -1, -1, -1, -1);
1238 calcBoxes(box->sub2);
1239 }
1240 // Move the cursor to where it is, to check it's not now outside the box.
1241 moveCursorAbsolute(box->view, box->view->cX, box->view->cY, 0, 0);
1242
1243 // We could call drawBoxes() here, but this is recursive, and so is drawBoxes().
1244 // The combination might be deadly. Drawing the content of a box might be an expensive operation.
1245 // Later we might use a dirty box flag to deal with this, if it's not too much of a complication.
1246}
1247
1248void deleteBox(view *view)
1249{
1250 box *box = view->box;
1251
1252 if (box->parent)
1253 {
1254 struct _box *oldBox = box, *otherBox = box->parent->sub1;
1255
1256 if (otherBox == oldBox)
1257 otherBox = box->parent->sub2;
1258 if (currentBox->parent == box->parent)
1259 currentBox = box->parent;
1260 box = box->parent;
1261 box->X = box->sub1->X;
1262 box->Y = box->sub1->Y;
1263 if (box->flags & BOX_HSPLIT)
1264 box->H = box->sub1->H + box->sub2->H;
1265 else
1266 box->W = box->sub1->W + box->sub2->W;
1267 box->flags &= ~BOX_HSPLIT;
1268 // Move the other sub boxes contents up to this box.
1269 box->sub1 = otherBox->sub1;
1270 box->sub2 = otherBox->sub2;
1271 if (box->sub1)
1272 {
1273 box->sub1->parent = box;
1274 box->sub2->parent = box;
1275 box->flags = otherBox->flags;
1276 if (currentBox == box)
1277 currentBox = box->sub1;
1278 }
1279 else
1280 {
1281 if (!box->parent)
1282 box->flags &= ~BOX_BORDER;
1283 box->split = 1.0;
1284 }
1285 otherBox->sub1 = NULL;
1286 otherBox->sub2 = NULL;
1287 // Safe to free the boxes now that we have all their contents.
1288 freeBox(otherBox);
1289 freeBox(oldBox);
1290 }
1291 // Otherwise it must be a single full screen box. Never delete that one, unless we are quitting.
1292
1293 // Start the recursive recalculation of all the sub boxes.
1294 calcBoxes(box);
1295 drawBoxes(box);
1296}
1297
1298void cloneBox(struct _box *box, struct _box *sub)
1299{
1300 sub->parent = box;
1301 // Only a full screen box has no border.
1302 sub->flags |= BOX_BORDER;
1303 sub->view = xmalloc(sizeof(struct _view));
1304 // TODO - After this is more stable, should check if the memcpy() is simpler than - xzalloc() then copy a few things manually.
1305 // Might even be able to arrange the structure so we can memcpy just part of it, leaving the rest blank.
1306 memcpy(sub->view, box->view, sizeof(struct _view));
1307 sub->view->damage = NULL;
1308 sub->view->data = NULL;
1309 sub->view->output = NULL;
1310 sub->view->box = sub;
1311 if (box->view->prompt)
1312 sub->view->prompt = strdup(box->view->prompt);
1313}
1314
1315void splitBox(box *box, float split)
1316{
1317 uint16_t size;
1318 int otherBox = 0;
1319
1320 // First some sanity checks.
1321 if (0.0 > split)
1322 {
1323 // TODO - put this in the status line, or just silently fail. Also, better message. lol
1324 fprintf(stderr, "User is crazy.\n");
1325 return;
1326 }
1327 else if (1.0 <= split) // User meant to unsplit, and it may already be split.
1328 {
1329 // Actually, this means that the OTHER sub box gets deleted.
1330 if (box->parent)
1331 {
1332 if (box == box->parent->sub1)
1333 deleteBox(box->parent->sub2->view);
1334 else
1335 deleteBox(box->parent->sub1->view);
1336 }
1337 return;
1338 }
1339 else if (0.0 < split) // This is the normal case, so do nothing.
1340 {
1341 }
1342 else // User meant to delete this, zero split.
1343 {
1344 deleteBox(box->view);
1345 return;
1346 }
1347 if (box->flags & BOX_HSPLIT)
1348 size = box->H;
1349 else
1350 size = box->W;
1351 if (6 > size) // Is there room for 2 borders for each sub box and one character of content each?
1352 // TODO - also should check the contents minimum size.
1353 // No need to check the no border case, that's only for full screen.
1354 // People using terminals smaller than 6 characters get what they deserve.
1355 {
1356 // TODO - put this in the status line, or just silently fail.
1357 fprintf(stderr, "Box is too small to split.\n");
1358 return;
1359 }
1360
1361 // Note that a split box is actually three boxes. The parent, which is not drawn, and the two subs, which are.
1362 // Based on the assumption that there wont be lots of boxes, this keeps things simple.
1363 // It's possible that the box has already been split, and this is called just to update the split.
1364 box->split = split;
1365 if (NULL == box->sub1) // If not split already, do so.
1366 {
1367 box->sub1 = xzalloc(sizeof(struct _box));
1368 box->sub2 = xzalloc(sizeof(struct _box));
1369 cloneBox(box, box->sub1);
1370 cloneBox(box, box->sub2);
1371 if (box->flags & BOX_HSPLIT)
1372 {
1373 // Split the boxes in the middle of the content.
1374 box->sub2->view->offsetY += (box->H * box->split) - 2;
1375 // Figure out which sub box the cursor will be in, then update the cursor in the other box.
1376 if (box->sub1->view->cY < box->sub2->view->offsetY)
1377 box->sub2->view->cY = box->sub2->view->offsetY;
1378 else
1379 {
1380 box->sub1->view->cY = box->sub2->view->offsetY - 1;
1381 otherBox = 1;
1382 }
1383 }
1384 else
1385 {
1386 // Split the boxes in the middle of the content.
1387 box->sub2->view->offsetX += (box->W * box->split) - 2;
1388 // Figure out which sub box the cursor will be in, then update the cursor in the other box.
1389 if (box->sub1->view->cX < box->sub2->view->offsetX)
1390 box->sub2->view->cX = box->sub2->view->offsetX;
1391 else
1392 {
1393 box->sub1->view->cX = box->sub2->view->offsetX - 1;
1394 otherBox = 1;
1395 }
1396 }
1397 }
1398
1399 if ((currentBox == box) && (box->sub1))
1400 {
1401 if (otherBox)
1402 currentBox = box->sub2;
1403 else
1404 currentBox = box->sub1;
1405 }
1406
1407 // Start the recursive recalculation of all the sub boxes.
1408 calcBoxes(box);
1409 drawBoxes(box);
1410}
1411
1412// TODO - Might be better to just have a double linked list of boxes, and traverse that instead.
1413// Except that leaves a problem when deleting boxes, could end up with a blank space.
1414void switchBoxes(view *view)
1415{
1416 box *box = view->box;
1417
1418 // The assumption here is that box == currentBox.
1419 struct _box *oldBox = currentBox;
1420 struct _box *thisBox = box;
1421 int backingUp = 0;
1422
1423 // Depth first traversal.
1424 while ((oldBox == currentBox) && (thisBox->parent))
1425 {
1426 if (thisBox == thisBox->parent->sub1)
1427 {
1428 if (backingUp && (thisBox->parent))
1429 currentBox = thisBox->parent->sub2;
1430 else if (thisBox->sub1)
1431 currentBox = thisBox->sub1;
1432 else
1433 currentBox = thisBox->parent->sub2;
1434 }
1435 else if (thisBox == thisBox->parent->sub2)
1436 {
1437 thisBox = thisBox->parent;
1438 backingUp = 1;
1439 }
1440 }
1441
1442 // If we have not found the next box to move to, move back to the beginning.
1443 if (oldBox == currentBox)
1444 currentBox = rootBox;
1445
1446 // If we ended up on a parent box, go to it's first sub.
1447 while (currentBox->sub1)
1448 currentBox = currentBox->sub1;
1449
1450 // Just redraw them all.
1451 drawBoxes(rootBox);
1452}
1453
1454// TODO - It might be better to do away with this bunch of single line functions
1455// and map script commands directly to lower functions.
1456// How to deal with the various argument needs?
1457// Might be where we can re use the toybox argument stuff.
1458
1459void halveBoxHorizontally(view *view)
1460{
1461 view->box->flags |= BOX_HSPLIT;
1462 splitBox(view->box, 0.5);
1463}
1464
1465void halveBoxVertically(view *view)
1466{
1467 view->box->flags &= ~BOX_HSPLIT;
1468 splitBox(view->box, 0.5);
1469}
1470
1471void switchMode(view *view)
1472{
1473 currentBox->view->mode++;
1474 // Assumes that modes will always have a key mapping, which I think is a safe bet.
1475 if (NULL == currentBox->view->content->context->modes[currentBox->view->mode].keys)
1476 currentBox->view->mode = 0;
1477 commandMode = currentBox->view->content->context->modes[currentBox->view->mode].flags & 1;
1478}
1479
1480void leftChar(view *view)
1481{
1482 moveCursorRelative(view, -1, 0, 0, 0);
1483}
1484
1485void rightChar(view *view)
1486{
1487 moveCursorRelative(view, 1, 0, 0, 0);
1488}
1489
1490void upLine(view *view)
1491{
1492 moveCursorRelative(view, 0, -1, 0, 0);
1493}
1494
1495void downLine(view *view)
1496{
1497 moveCursorRelative(view, 0, 1, 0, 0);
1498}
1499
1500void upPage(view *view)
1501{
1502 moveCursorRelative(view, 0, 0 - (view->H - 1), 0, 0 - (view->H - 1));
1503}
1504
1505void downPage(view *view)
1506{
1507 moveCursorRelative(view, 0, view->H - 1, 0, view->H - 1);
1508}
1509
1510void endOfLine(view *view)
1511{
1512 moveCursorAbsolute(view, strlen(view->prompt) + view->oW, view->cY, 0, 0);
1513}
1514
1515void startOfLine(view *view)
1516{
1517 // TODO - add the advanced editing "smart home".
1518 moveCursorAbsolute(view, strlen(view->prompt), view->cY, 0, 0);
1519}
1520
1521void splitLine(view *view)
1522{
1523 // TODO - should move this into mooshLines().
1524 addLine(view->content, view->line, &(view->line->line[view->iX]), 0);
1525 view->line->line[view->iX] = '\0';
1526 moveCursorAbsolute(view, 0, view->cY + 1, 0, 0);
1527 if (view->box)
1528 drawBox(view->box);
1529}
1530
1531void deleteChar(view *view)
1532{
1533 // TODO - should move this into mooshLines().
1534 // If we are at the end of the line, then join this and the next line.
1535 if (view->oW == view->cX)
1536 {
1537 // Only if there IS a next line.
1538 if (&(view->content->lines) != view->line->next)
1539 {
1540 mooshStrings(view->line, view->line->next->line, view->iX, 1, !overWriteMode);
1541 view->line->next->line = NULL;
1542 freeLine(view->content, view->line->next);
1543 // TODO - should check if we are on the last page, then deal with scrolling.
1544 if (view->box)
1545 drawBox(view->box);
1546 }
1547 }
1548 else
1549 mooshStrings(view->line, NULL, view->iX, 1, !overWriteMode);
1550}
1551
1552void backSpaceChar(view *view)
1553{
1554 if (moveCursorRelative(view, -1, 0, 0, 0))
1555 deleteChar(view);
1556}
1557
1558void saveContent(view *view)
1559{
1560 saveFile(view->content);
1561}
1562
1563void executeLine(view *view)
1564{
1565 struct line *result = view->line;
1566
1567 // Don't bother doing much if there's nothing on this line.
1568 if (result->line[0])
1569 {
1570 doCommand(currentBox->view, result->line);
1571 // If we are not at the end of the history contents.
1572 if (&(view->content->lines) != result->next)
1573 {
1574 struct line *line = view->content->lines.prev;
1575
1576 // Remove the line first.
1577 result->next->prev = result->prev;
1578 result->prev->next = result->next;
1579 // Check if the last line is already blank, then remove it.
1580 if ('\0' == line->line[0])
1581 {
1582 freeLine(view->content, line);
1583 line = view->content->lines.prev;
1584 }
1585 // Then add it to the end.
1586 result->next = line->next;
1587 result->prev = line;
1588 line->next->prev = result;
1589 line->next = result;
1590 view->cY = view->content->lines.length - 1;
1591 }
1592 moveCursorAbsolute(view, 0, view->content->lines.length, 0, 0);
1593 // Make sure there is one blank line at the end.
1594 if ('\0' != view->line->line[0])
1595 {
1596 endOfLine(view);
1597 splitLine(view);
1598 }
1599 }
1600
1601 saveFile(view->content);
1602}
1603
1604void quit(view *view)
1605{
1606 handle_keys_quit();
1607}
1608
1609void nop(view *view)
1610{
1611 // 'tis a nop, don't actually do anything.
1612}
1613
1614
1615typedef void (*CSIhandler) (long extra, int *code, int count);
1616
1617struct CSI
1618{
1619 char *code;
1620 CSIhandler func;
1621};
1622
1623static void termSize(long extra, int *params, int count)
1624{
1625 struct _view *view = (struct _view *) extra; // Though we pretty much stomp on this straight away.
1626 int r = params[0], c = params[1];
1627
1628 // The defaults are 1, which get ignored by the heuristic below.
1629 // Check it's not an F3 key variation, coz some of them use the same CSI function code.
1630 // This is a heuristic, we are checking against an unusable terminal size.
1631 // TODO - Double check what the maximum F3 variations can be.
1632 if ((2 == count) && (8 < r) && (8 < c))
1633 {
1634 commandLine->Y = r;
1635 commandLine->W = c;
1636 rootBox->W = c;
1637 rootBox->H = r - 1;
1638 sizeViewToBox(rootBox, -1, -1, -1, -1);
1639 calcBoxes(rootBox);
1640 drawBoxes(rootBox);
1641
1642 // Move the cursor to where it is, to check it's not now outside the terminal window.
1643 moveCursorAbsolute(rootBox->view, rootBox->view->cX, rootBox->view->cY, 0, 0);
1644
1645 // We have no idea which is the current view now.
1646 if (commandMode) view = commandLine;
1647 else view = currentBox->view;
1648 updateLine(view);
1649 }
1650}
1651
1652struct CSI CSIcommands[] =
1653{
1654 {"R", termSize} // Parameters are cursor line and column. Note this may be sent at other times, not just during terminal resize.
1655};
1656
1657
1658// Callback for incoming sequences from the terminal.
1659static int handleEvent(long extra, struct keyevent *event)
1660{
1661 switch (event->type)
1662 {
1663 case HK_CSI :
1664 {
1665 int j;
1666
1667 for (j = 0; j < ARRAY_LEN(CSIcommands); j++)
1668 {
1669 if (strcmp(CSIcommands[j].code, event->sequence) == 0)
1670 {
1671 CSIcommands[j].func(extra, event->params, event->count);
1672 break;
1673 }
1674 }
1675 break;
1676 }
1677
1678 case HK_KEYS :
1679 {
1680 struct _view *view = (struct _view *) extra; // Though we pretty much stomp on this straight away.
1681 struct keyCommand *commands = currentBox->view->content->context->modes[currentBox->view->mode].keys;
1682 int j, l = strlen(event->sequence);
1683
1684 // Coz things might change out from under us, find the current view.
1685 if (commandMode) view = commandLine;
1686 else view = currentBox->view;
1687
1688 // Search for a key sequence bound to a command.
1689 for (j = 0; commands[j].key; j++)
1690 {
1691 if (strncmp(commands[j].key, event->sequence, l) == 0)
1692 {
1693 // If it's a partial match, keep accumulating them.
1694 if (strlen(commands[j].key) != l)
1695 return 0;
1696 else
1697 {
1698 doCommand(view, commands[j].command);
1699 return 1;
1700 }
1701 }
1702 }
1703
1704 // See if it's ordinary keys.
1705 // NOTE - with vi style ordinary keys can be commands,
1706 // but they would be found by the command check above first.
1707 if (!event->isTranslated)
1708 {
1709 // TODO - Should check for tabs to, and insert them.
1710 // Though better off having a function for that?
1711 mooshStrings(view->line, event->sequence, view->iX, 0, !overWriteMode);
1712 view->oW = formatLine(view, view->line->line, &(view->output));
1713 moveCursorRelative(view, strlen(event->sequence), 0, 0, 0);
1714 updateLine(view);
1715 }
1716 break;
1717 }
1718
1719 default : break;
1720 }
1721
1722 // Tell handle_keys to drop it, coz we dealt with it, or it's not one of ours.
1723 return 1;
1724}
1725
1726
1727// 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.
1728// Though most of the editors have their own variation.
1729// TODO - Maybe just use the joe one as default, it uses short names at least.
1730// Though vi is the only one in POSIX, so might be better to treat that one as the "standard" default.
1731// With some commands from others for stuff vi doesn't support.
1732struct function simpleEditCommands[] =
1733{
1734 {"backSpaceChar", "Back space last character.", 0, {backSpaceChar}},
1735 {"deleteBox", "Delete a box.", 0, {deleteBox}},
1736 {"deleteChar", "Delete current character.", 0, {deleteChar}},
1737 {"downLine", "Move cursor down one line.", 0, {downLine}},
1738 {"downPage", "Move cursor down one page.", 0, {downPage}},
1739 {"endOfLine", "Go to end of line.", 0, {endOfLine}},
1740 {"executeLine", "Execute a line as a script.", 0, {executeLine}},
1741 {"leftChar", "Move cursor left one character.", 0, {leftChar}},
1742 {"quit", "Quit the application.", 0, {quit}},
1743 {"rightChar", "Move cursor right one character.", 0, {rightChar}},
1744 {"save", "Save.", 0, {saveContent}},
1745 {"splitH", "Split box in half horizontally.", 0, {halveBoxHorizontally}},
1746 {"splitLine", "Split line at cursor.", 0, {splitLine}},
1747 {"splitV", "Split box in half vertically.", 0, {halveBoxVertically}},
1748 {"startOfLine", "Go to start of line.", 0, {startOfLine}},
1749 {"switchBoxes", "Switch to another box.", 0, {switchBoxes}},
1750 {"switchMode", "Switch between command and box.", 0, {switchMode}},
1751 {"upLine", "Move cursor up one line.", 0, {upLine}},
1752 {"upPage", "Move cursor up one page.", 0, {upPage}},
1753 {NULL, NULL, 0, {NULL}}
1754};
1755
1756// Construct a simple command line.
1757
1758// The key to command mappings.
1759// TODO - Should not move off the ends of the line to the next / previous line.
1760struct keyCommand simpleCommandKeys[] =
1761{
1762 {"BS", "backSpaceChar"},
1763 {"Del", "deleteChar"},
1764 {"Down", "downLine"},
1765 {"End", "endOfLine"},
1766 {"F10", "quit"},
1767 {"Home", "startOfLine"},
1768 {"Left", "leftChar"},
1769 {"Enter", "executeLine"},
1770 {"Return", "executeLine"},
1771 {"Right", "rightChar"},
1772 {"Esc", "switchMode"},
1773 {"Up", "upLine"},
1774 {NULL, NULL}
1775};
1776
1777
1778// Construct a simple emacs editor.
1779
1780// Mostly control keys, some meta keys.
1781// 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
1782// Ctrl-h is backspace / del. Pffft.
1783// Meta key is either Alt-keystroke, Esc keystroke, or an actual Meta-keystroke (do they still exist?).
1784// TODO - Alt and Meta not supported yet, so using Esc.
1785// Windows commands.
1786
1787// readline uses these same commands, and defaults to emacs keystrokes.
1788struct function simpleEmacsCommands[] =
1789{
1790 {"delete-backward-char", "Back space last character.", 0, {backSpaceChar}},
1791 {"delete-window", "Delete a box.", 0, {deleteBox}},
1792 {"delete-char", "Delete current character.", 0, {deleteChar}},
1793 {"next-line", "Move cursor down one line.", 0, {downLine}},
1794 {"scroll-up", "Move cursor down one page.", 0, {downPage}},
1795 {"end-of-line", "Go to end of line.", 0, {endOfLine}},
1796 {"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.
1797 {"backward-char", "Move cursor left one character.", 0, {leftChar}},
1798 {"save-buffers-kill-emacs", "Quit the application.", 0, {quit}}, // TODO - Does more than just quit.
1799 {"forward-char", "Move cursor right one character.", 0, {rightChar}},
1800 {"save-buffer", "Save.", 0, {saveContent}},
1801 {"split-window-horizontally", "Split box in half horizontally.", 0, {halveBoxHorizontally}}, // TODO - Making this one up for now, mg does not have it.
1802 {"newline", "Split line at cursor.", 0, {splitLine}},
1803 {"split-window-vertically", "Split box in half vertically.", 0, {halveBoxVertically}},
1804 {"beginning-of-line", "Go to start of line.", 0, {startOfLine}},
1805 {"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.
1806 {"execute-extended-command", "Switch between command and box.", 0, {switchMode}}, // Actually a one time invocation of the command line.
1807 {"previous-line", "Move cursor up one line.", 0, {upLine}},
1808 {"scroll-down", "Move cursor up one page.", 0, {upPage}},
1809 {NULL, NULL, 0, {NULL}}
1810};
1811
1812// The key to command mappings.
1813struct keyCommand simpleEmacsKeys[] =
1814{
1815 {"BS", "delete-backward-char"},
1816 {"Del", "delete-backward-char"},
1817 {"^D", "delete-char"},
1818 {"Down", "next-line"},
1819 {"^N", "next-line"},
1820 {"End", "end-of-line"},
1821 {"^E", "end-of-line"},
1822 {"^X^C", "save-buffers-kill-emacs"},
1823 {"^X^S", "save-buffer"},
1824 {"Home", "beginning-of-line"},
1825 {"^A", "beginning-of-line"},
1826 {"Left", "backward-char"},
1827 {"^B", "backward-char"},
1828 {"PgDn", "scroll-up"},
1829 {"^V", "scroll-up"},
1830 {"PgUp", "scroll-down"},
1831 {"Escv", "scroll-down"}, // M-v
1832 {"Enter", "newline"},
1833 {"Return", "newline"},
1834 {"Right", "forward-char"},
1835 {"^F", "forward-char"},
1836 {"Escx", "execute-extended-command"}, // M-x
1837 {"^X2", "split-window-vertically"},
1838 {"^XP", "other-window"},
1839 {"^X0", "delete-window"},
1840 {"Up", "previous-line"},
1841 {"^P", "previous-line"},
1842 {NULL, NULL}
1843};
1844
1845struct keyCommand simpleEmacsCommandKeys[] =
1846{
1847 {"Del", "delete-backwards-char"},
1848 {"^D", "delete-char"},
1849 {"Down", "next-line"},
1850 {"^N", "next-line"},
1851 {"End", "end-of-line"},
1852 {"^E", "end-of-line"},
1853 {"Home", "beginning-of-line"},
1854 {"^A", "beginning-of-line"},
1855 {"Left", "backward-char"},
1856 {"^B", "backward-char"},
1857 {"Right", "forward-char"},
1858 {"^F", "forward-char"},
1859 {"Up", "previous-line"},
1860 {"^P", "previous-line"},
1861 {"Enter", "accept-line"},
1862 {"Return", "accept-line"},
1863 {"Escx", "execute-extended-command"},
1864 {NULL, NULL}
1865};
1866
1867// An array of various modes.
1868struct mode simpleEmacsMode[] =
1869{
1870 {simpleEmacsKeys, NULL, NULL, 0},
1871 {simpleEmacsCommandKeys, NULL, NULL, 1},
1872 {NULL, NULL, NULL}
1873};
1874
1875// Put it all together into a simple editor context.
1876struct context simpleEmacs =
1877{
1878 simpleEmacsCommands,
1879 simpleEmacsMode,
1880 NULL,
1881 NULL,
1882 NULL
1883};
1884
1885
1886// Construct a simple joe / wordstar editor, using joe is the reference, seems to be the popular Unix variant.
1887// Esc x starts up the command line.
1888// Has multi control key combos. Mostly Ctrl-K, Ctrl-[ (Esc), (Ctrl-B, Ctrl-Q in wordstar and delphi), but might be others.
1889// Can't find a single list of command mappings for joe, gotta search all over. sigh
1890// Even the command line keystroke I stumbled on (Esc x) is not documented.
1891// Note that you don't have to let go of the Ctrl key for the second keystroke, but you can.
1892
1893// From http://joe-editor.sourceforge.net/list.html
1894// TODO - Some of these might be wrong. Just going by the inadequate joe docs for now.
1895struct function simpleJoeCommands[] =
1896{
1897 {"backs", "Back space last character.", 0, {backSpaceChar}},
1898 {"abort", "Delete a box.", 0, {deleteBox}}, // TODO - Should do quit if it's the last window.
1899 {"delch", "Delete current character.", 0, {deleteChar}},
1900 {"dnarw", "Move cursor down one line.", 0, {downLine}},
1901 {"pgdn", "Move cursor down one page.", 0, {downPage}},
1902 {"eol", "Go to end of line.", 0, {endOfLine}},
1903 {"ltarw", "Move cursor left one character.", 0, {leftChar}},
1904 {"killjoe", "Quit the application.", 0, {quit}},
1905 {"rtarw", "Move cursor right one character.", 0, {rightChar}},
1906 {"save", "Save.", 0, {saveContent}},
1907 {"splitw", "Split box in half horizontally.", 0, {halveBoxHorizontally}},
1908 {"open", "Split line at cursor.", 0, {splitLine}},
1909 {"bol", "Go to start of line.", 0, {startOfLine}},
1910 {"home", "Go to start of line.", 0, {startOfLine}},
1911 {"nextw", "Switch to another box.", 0, {switchBoxes}}, // This is "next window", there's also "previous window" which we don't support yet.
1912 {"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.
1913 {"uparw", "Move cursor up one line.", 0, {upLine}},
1914 {"pgup", "Move cursor up one page.", 0, {upPage}},
1915
1916 // Not an actual joe command.
1917 {"executeLine", "Execute a line as a script.", 0, {executeLine}}, // Perhaps this should be execmd?
1918 {NULL, NULL, 0, {NULL}}
1919};
1920
1921struct keyCommand simpleJoeKeys[] =
1922{
1923 {"BS", "backs"},
1924 {"^D", "delch"},
1925 {"Down", "dnarw"},
1926 {"^N", "dnarw"},
1927 {"^E", "eol"},
1928 {"^C", "killjoe"},
1929 {"^Kd", "save"},
1930 {"^K^D" "save"},
1931 {"^A", "bol"},
1932 {"Left", "ltarw"},
1933 {"^B", "ltarw"},
1934 {"^V", "pgdn"}, // Actually half a page.
1935 {"^U", "pgup"}, // Actually half a page.
1936 {"Enter", "open"},
1937 {"Return", "open"},
1938 {"Right", "rtarw"},
1939 {"^F", "rtarw"},
1940 {"Escx", "execmd"},
1941 {"Esc^X", "execmd"},
1942 {"^Ko", "splitw"},
1943 {"^K^O", "splitw"},
1944 {"^Kn", "nextw"},
1945 {"^K^N", "nextw"},
1946 {"^Kx", "killjoe"}, // TODO - Should ask if it should save if it's been modified. A good generic thing to do anyway.
1947 {"^K^X", "abort"}, // TODO - These two both close a window, and quit if that was the last window.
1948 {"Up", "uparw"},
1949 {"^P", "uparw"},
1950 {NULL, NULL}
1951};
1952
1953struct keyCommand simpleJoeCommandKeys[] =
1954{
1955 {"BS", "backs"},
1956 {"^D", "delch"},
1957 {"Down", "dnarw"},
1958 {"^N", "dnarw"},
1959 {"^E", "eol"},
1960 {"^A", "bol"},
1961 {"Left", "ltarw"},
1962 {"^B", "ltarw"},
1963 {"Right", "rtarw"},
1964 {"^F", "rtarw"},
1965 {"Escx", "execmd"},
1966 {"Esc^X", "execmd"},
1967 {"Up", "uparw"},
1968 {"^P", "uparw"},
1969 {"Enter", "executeLine"},
1970 {"Return", "executeLine"},
1971 {NULL, NULL}
1972};
1973
1974struct mode simpleJoeMode[] =
1975{
1976 {simpleJoeKeys, NULL, NULL, 0},
1977 {simpleJoeCommandKeys, NULL, NULL, 1},
1978 {NULL, NULL, NULL, 0}
1979};
1980
1981struct context simpleJoe =
1982{
1983 simpleJoeCommands,
1984 simpleJoeMode,
1985 NULL,
1986 NULL,
1987 NULL
1988};
1989
1990
1991// Simple more and / or less.
1992// '/' and '?' for search command mode. I think they both have some ex commands with the usual : command mode starter.
1993// No cursor movement, just scrolling.
1994// TODO - Put content into read only mode.
1995// TODO - actually implement read only mode where up and down one line do actual scrolling instead of cursor movement.
1996
1997struct keyCommand simpleLessKeys[] =
1998{
1999 {"Down", "downLine"},
2000 {"j", "downLine"},
2001 {"Enter", "downLine"},
2002 {"Return", "downLine"},
2003 {"End", "endOfLine"},
2004 {"q", "quit"},
2005 {":q", "quit"}, // TODO - A vi ism, should do ex command stuff instead.
2006 {"ZZ", "quit"},
2007 {"PgDn", "downPage"},
2008 {"f", "downPage"},
2009 {" ", "downPage"},
2010 {"^F", "downPage"},
2011 {"Left", "leftChar"},
2012 {"Right", "rightChar"},
2013 {"PgUp", "upPage"},
2014 {"b", "upPage"},
2015 {"^B", "upPage"},
2016 {"Up", "upLine"},
2017 {"k", "upLine"},
2018 {NULL, NULL}
2019};
2020
2021struct mode simpleLessMode[] =
2022{
2023 {simpleLessKeys, NULL, NULL, 0},
2024 {simpleCommandKeys, NULL, NULL, 1},
2025 {NULL, NULL, NULL}
2026};
2027
2028struct context simpleLess =
2029{
2030 simpleEditCommands,
2031 simpleLessMode,
2032 NULL,
2033 NULL,
2034 NULL
2035};
2036
2037struct keyCommand simpleMoreKeys[] =
2038{
2039 {"j", "downLine"},
2040 {"Enter", "downLine"},
2041 {"Return", "downLine"},
2042 {"q", "quit"},
2043 {":q", "quit"}, // See comments for "less".
2044 {"ZZ", "quit"},
2045 {"f", "downPage"},
2046 {" ", "downPage"},
2047 {"^F", "downPage"},
2048 {"b", "upPage"},
2049 {"^B", "upPage"},
2050 {"k", "upLine"},
2051 {NULL, NULL}
2052};
2053
2054struct mode simpleMoreMode[] =
2055{
2056 {simpleMoreKeys, NULL, NULL, 0},
2057 {simpleCommandKeys, NULL, NULL, 1},
2058 {NULL, NULL, NULL}
2059};
2060
2061struct context simpleMore =
2062{
2063 simpleEditCommands,
2064 simpleMoreMode,
2065 NULL,
2066 NULL,
2067 NULL
2068};
2069
2070
2071// Construct a simple mcedit / cool edit editor.
2072
2073struct keyCommand simpleMceditKeys[] =
2074{
2075 {"BS", "backSpaceChar"},
2076 {"Del", "deleteChar"},
2077 {"Down", "downLine"},
2078 {"End", "endOfLine"},
2079 {"F10", "quit"},
2080 {"Esc0", "quit"},
2081 {"F2", "save"},
2082 {"Esc2", "save"},
2083 {"Home", "startOfLine"},
2084 {"Left", "leftChar"},
2085 {"PgDn", "downPage"},
2086 {"PgUp", "upPage"},
2087 {"Enter", "splitLine"},
2088 {"Return", "splitLine"},
2089 {"Right", "rightChar"},
2090 {"Shift F2", "switchMode"}, // MC doesn't have a command mode.
2091 {"Esc:", "switchMode"}, // Sorta vi like, and coz tmux is screwing with the shift function keys somehow.
2092 {"Esc|", "splitV"}, // MC doesn't have a split window concept, so make these up to match tmux more or less.
2093 {"Esc-", "splitH"},
2094 {"Esco", "switchBoxes"},
2095 {"Escx", "deleteBox"},
2096 {"Up", "upLine"},
2097{"^C", "switchMode"}, // To test the signal stopper.
2098{"^D", "switchMode"}, // To test the signal stopper.
2099{"^Q", "switchMode"}, // To test the signal stopper.
2100{"^S", "switchMode"}, // To test the signal stopper.
2101{"^T", "switchMode"}, // To test the signal stopper.
2102{"^Z", "switchMode"}, // To test the signal stopper.
2103{"^\\", "switchMode"}, // To test the signal stopper.
2104{"F1", "switchMode"}, // To test various function keys.
2105{"F3", "switchMode"}, // To test various function keys.
2106{"F4", "switchMode"}, // To test various function keys.
2107{"F5", "switchMode"}, // To test various function keys.
2108{"F6", "switchMode"}, // To test various function keys.
2109{"F7", "switchMode"}, // To test various function keys.
2110{"F8", "switchMode"}, // To test various function keys.
2111{"F9", "switchMode"}, // To test various function keys.
2112 {NULL, NULL}
2113};
2114
2115struct mode simpleMceditMode[] =
2116{
2117 {simpleMceditKeys, NULL, NULL, 0},
2118 {simpleCommandKeys, NULL, NULL, 1},
2119 {NULL, NULL, NULL}
2120};
2121
2122struct context simpleMcedit =
2123{
2124 simpleEditCommands,
2125 simpleMceditMode,
2126 NULL,
2127 NULL,
2128 NULL
2129};
2130
2131
2132// Simple nano editor.
2133// Has key to function bindings, but no command line mode. Has "enter parameter on this line" mode for some commands.
2134// Control and meta keys, only singles, unlike emacs and joe.
2135// Can have multiple buffers, but no windows. Think I can skip that for simple editor.
2136
2137struct function simpleNanoCommands[] =
2138{
2139 {"backSpaceChar", "Back space last character.", 0, {backSpaceChar}},
2140 {"delete", "Delete current character.", 0, {deleteChar}},
2141 {"down", "Move cursor down one line.", 0, {downLine}},
2142 {"downPage", "Move cursor down one page.", 0, {downPage}},
2143 {"end", "Go to end of line.", 0, {endOfLine}},
2144 {"left", "Move cursor left one character.", 0, {leftChar}},
2145 {"exit", "Quit the application.", 0, {quit}},
2146 {"right", "Move cursor right one character.", 0, {rightChar}},
2147 {"writeout", "Save.", 0, {saveContent}},
2148 {"enter", "Split line at cursor.", 0, {splitLine}},
2149 {"home", "Go to start of line.", 0, {startOfLine}},
2150 {"up", "Move cursor up one line.", 0, {upLine}},
2151 {"upPage", "Move cursor up one page.", 0, {upPage}},
2152 {NULL, NULL, 0, {NULL}}
2153};
2154
2155
2156// 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.
2157struct keyCommand simpleNanoKeys[] =
2158{
2159// TODO - Delete key is ^H dammit. Find the alternate Esc sequence for Del.
2160// {"^H", "backSpaceChar"}, // ?
2161 {"BS", "backSpaceChar"},
2162 {"^D", "delete"},
2163 {"Del", "delete"},
2164 {"^N", "down"},
2165 {"Down", "down"},
2166 {"^E", "end"},
2167 {"End", "end"},
2168 {"^X", "exit"},
2169 {"F2", "exit"},
2170 {"^O", "writeout"},
2171 {"F3", "writeout"},
2172 {"^A", "home"},
2173 {"Home", "home"},
2174 {"^B", "left"},
2175 {"Left", "left"},
2176 {"^V", "downPage"}, // ?
2177 {"PgDn", "downPage"},
2178 {"^Y", "upPage"}, // ?
2179 {"PgUp", "upPage"},
2180 {"Enter", "enter"}, // TODO - Not sure if this is correct.
2181 {"Return", "enter"}, // TODO - Not sure if this is correct.
2182 {"^F", "right"},
2183 {"Right", "right"},
2184 {"^P", "up"},
2185 {"Up", "up"},
2186 {NULL, NULL}
2187};
2188
2189struct mode simpleNanoMode[] =
2190{
2191 {simpleNanoKeys, NULL, NULL, 0},
2192 {NULL, NULL, NULL}
2193};
2194
2195struct context simpleNano =
2196{
2197 simpleNanoCommands,
2198 simpleNanoMode,
2199 NULL,
2200 NULL,
2201 NULL
2202};
2203
2204
2205// Construct a simple vi editor.
2206// Only vi is not so simple. lol
2207// The "command line" modes are /, ?, :, and !,
2208// / is regex search.
2209// ? is regex search backwards.
2210// : is ex command mode.
2211// ! is replace text with output from shell command mode.
2212// Arrow keys do the right thing in "normal" mode, but not in insert mode.
2213// "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
2214// 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.
2215// Which is also the keyboard with the arrow keys marked on h, j, k, and l keys.
2216// Did I mention that vi is just a horrid historic relic that should have died long ago?
2217// Emacs looks to have the same problem, originally designed for an ancient keyboard that is nothing like what people actually use these days.
2218// "h", "j", "k", "l" move cursor, which is just random keys for dvorak users.
2219// ":" goes into ex command mode.
2220// ":q" deletes current window in vim.
2221// ":qa!" goes into ex mode and does some sort of quit command.
2222// The 'q' is short for quit, the ! is an optional argument to quit. No idea yet what the a is for, all windows?
2223// Del or "x" to delete a character. Del in insert mode.
2224// "X" to backspace. BS or Ctrl-H to backspace in insert mode.
2225// NOTE - Backspace in normal mode just moves left.
2226// Tab or Ctrl-I to insert a tab in insert mode.
2227// Return in normal mode goes to the start of the next line, or splits the line in insert mode.
2228// ":help" opens a window with help text.
2229// Vim window commands.
2230
2231// Vi needs extra variables and functions.
2232static int viTempExMode;
2233
2234void viMode(view *view)
2235{
2236 currentBox->view->mode = 0;
2237 commandMode = 0;
2238 viTempExMode = 0;
2239}
2240
2241void viInsertMode(view *view)
2242{
2243 currentBox->view->mode = 1;
2244 commandMode = 0;
2245}
2246
2247void viExMode(view *view)
2248{
2249 currentBox->view->mode = 2;
2250 commandMode = 1;
2251 // TODO - Should change this based on the event, : or Q.
2252 viTempExMode = 1;
2253 commandLine->prompt = xrealloc(commandLine->prompt, 2);
2254 strcpy(commandLine->prompt, ":");
2255}
2256
2257void viBackSpaceChar(view *view)
2258{
2259 if ((2 == currentBox->view->mode) && (0 == view->cX) && viTempExMode)
2260 viMode(view);
2261 else
2262 backSpaceChar(view);
2263}
2264
2265void viStartOfNextLine(view *view)
2266{
2267 startOfLine(view);
2268 downLine(view);
2269}
2270
2271struct function simpleViCommands[] =
2272{
2273 // These are actual ex commands.
2274 {"insert", "Switch to insert mode.", 0, {viInsertMode}},
2275 {"quit", "Quit the application.", 0, {quit}},
2276 {"visual", "Switch to visual mode.", 0, {viMode}},
2277 {"write", "Save.", 0, {saveContent}},
2278
2279 // These are not ex commands.
2280 {"backSpaceChar", "Back space last character.", 0, {viBackSpaceChar}},
2281 {"deleteBox", "Delete a box.", 0, {deleteBox}},
2282 {"deleteChar", "Delete current character.", 0, {deleteChar}},
2283 {"downLine", "Move cursor down one line.", 0, {downLine}},
2284 {"downPage", "Move cursor down one page.", 0, {downPage}},
2285 {"endOfLine", "Go to end of line.", 0, {endOfLine}},
2286 {"executeLine", "Execute a line as a script.", 0, {executeLine}},
2287 {"exMode", "Switch to ex mode.", 0, {viExMode}},
2288 {"leftChar", "Move cursor left one character.", 0, {leftChar}},
2289 {"rightChar", "Move cursor right one character.", 0, {rightChar}},
2290 {"splitH", "Split box in half horizontally.", 0, {halveBoxHorizontally}},
2291 {"splitLine", "Split line at cursor.", 0, {splitLine}},
2292 {"splitV", "Split box in half vertically.", 0, {halveBoxVertically}},
2293 {"startOfLine", "Go to start of line.", 0, {startOfLine}},
2294 {"startOfNLine", "Go to start of next line.", 0, {viStartOfNextLine}},
2295 {"switchBoxes", "Switch to another box.", 0, {switchBoxes}},
2296 {"upLine", "Move cursor up one line.", 0, {upLine}},
2297 {"upPage", "Move cursor up one page.", 0, {upPage}},
2298 {NULL, NULL, 0, {NULL}}
2299};
2300
2301struct keyCommand simpleViNormalKeys[] =
2302{
2303 {"BS", "leftChar"},
2304 {"X", "backSpaceChar"},
2305 {"Del", "deleteChar"},
2306 {"x", "deleteChar"},
2307 {"Down", "downLine"},
2308 {"j", "downLine"},
2309 {"End", "endOfLine"},
2310 {"Home", "startOfLine"},
2311 {"Left", "leftChar"},
2312 {"h", "leftChar"},
2313 {"PgDn", "downPage"},
2314 {"^F", "downPage"},
2315 {"PgUp", "upPage"},
2316 {"^B", "upPage"},
2317 {"Enter", "startOfNextLine"},
2318 {"Return", "startOfNextLine"},
2319 {"Right", "rightChar"},
2320 {"l", "rightChar"},
2321 {"i", "insert"},
2322 {":", "exMode"}, // This is the temporary ex mode that you can backspace out of. Or any command backs you out.
2323 {"Q", "exMode"}, // This is the ex mode you need to do the "visual" command to get out of.
2324 {"^Wv", "splitV"},
2325 {"^W^V", "splitV"},
2326 {"^Ws", "splitH"},
2327 {"^WS", "splitH"},
2328 {"^W^S", "splitH"},
2329 {"^Ww", "switchBoxes"},
2330 {"^W^W", "switchBoxes"},
2331 {"^Wq", "deleteBox"},
2332 {"^W^Q", "deleteBox"},
2333 {"Up", "upLine"},
2334 {"k", "upLine"},
2335 {NULL, NULL}
2336};
2337
2338struct keyCommand simpleViInsertKeys[] =
2339{
2340 {"BS", "backSpaceChar"},
2341 {"Del", "deleteChar"},
2342 {"Return", "splitLine"},
2343 {"Esc", "visual"},
2344 {"^C", "visual"},
2345 {NULL, NULL}
2346};
2347
2348struct keyCommand simpleExKeys[] =
2349{
2350 {"BS", "backSpaceChar"},
2351 {"Del", "deleteChar"},
2352 {"Down", "downLine"},
2353 {"End", "endOfLine"},
2354 {"Home", "startOfLine"},
2355 {"Left", "leftChar"},
2356 {"Enter", "executeLine"},
2357 {"Return", "executeLine"},
2358 {"Right", "rightChar"},
2359 {"Esc", "visual"},
2360 {"Up", "upLine"},
2361 {NULL, NULL}
2362};
2363
2364struct mode simpleViMode[] =
2365{
2366 {simpleViNormalKeys, NULL, NULL, 0},
2367 {simpleViInsertKeys, NULL, NULL, 0},
2368 {simpleExKeys, NULL, NULL, 1},
2369 {NULL, NULL, NULL}
2370};
2371
2372struct context simpleVi =
2373{
2374 simpleViCommands,
2375 simpleViMode,
2376 NULL,
2377 NULL,
2378 NULL
2379};
2380
2381
2382// TODO - simple sed editor? May be out of scope for "simple", so leave it until later?
2383// Probably entirely useless for "simple".
2384
2385
2386// TODO - have any unrecognised escape key sequence start up a new box (split one) to show the "show keys" content.
2387// That just adds each "Key is X" to the end of the content, and allows scrolling, as well as switching between other boxes.
2388
2389void boxes_main(void)
2390{
2391 struct context *context = &simpleMcedit; // The default is mcedit, coz that's what I use.
2392 struct termios termio, oldtermio;
2393 char *prompt = "Enter a command : ";
2394 unsigned W = 80, H = 24;
2395 lua_State *L;
2396
2397 /*
2398 * All Lua contexts are held in this structure. We work with it almost
2399 * all the time.
2400 */
2401 L = luaL_newstate();
2402
2403/* From http://luajit.org/install.html -
2404To change or extend the list of standard libraries to load, copy
2405src/lib_init.c to your project and modify it accordingly. Make sure the
2406jit library is loaded or the JIT compiler will not be activated.
2407*/
2408 luaL_openlibs(L); /* Load Lua libraries */
2409
2410 // For testing purposes, figure out which context we use. When this gets real, the toybox multiplexer will sort this out for us instead.
2411 if (toys.optflags & FLAG_m)
2412 {
2413 if (strcmp(TT.mode, "emacs") == 0)
2414 context = &simpleEmacs;
2415 else if (strcmp(TT.mode, "joe") == 0)
2416 context = &simpleJoe;
2417 else if (strcmp(TT.mode, "less") == 0)
2418 context = &simpleLess;
2419 else if (strcmp(TT.mode, "mcedit") == 0)
2420 context = &simpleMcedit;
2421 else if (strcmp(TT.mode, "more") == 0)
2422 context = &simpleMore;
2423 else if (strcmp(TT.mode, "nano") == 0)
2424 context = &simpleNano;
2425 else if (strcmp(TT.mode, "vi") == 0)
2426 context = &simpleVi;
2427 }
2428
2429 // 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.
2430 // It would STILL need the terminal size for output though. Perhaps just bitch and abort if it's not a tty?
2431 // 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.
2432
2433 // Grab the old terminal settings and save it.
2434 tcgetattr(0, &oldtermio);
2435 tcflush(0, TCIFLUSH);
2436 termio = oldtermio;
2437
2438 // Mould the terminal to our will.
2439 /*
2440 IUCLC (not in POSIX) Map uppercase characters to lowercase on input.
2441 IXON Enable XON/XOFF flow control on output.
2442 IXOFF Enable XON/XOFF flow control on input.
2443 IXANY (not in POSIX.1; XSI) Enable any character to restart output.
2444
2445 ECHO Echo input characters.
2446 ECHOE If ICANON is also set, the ERASE character erases the preceding input character, and WERASE erases the preceding word.
2447 ECHOK If ICANON is also set, the KILL character erases the current line.
2448 ECHONL If ICANON is also set, echo the NL character even if ECHO is not set.
2449 TOSTOP Send the SIGTTOU signal to the process group of a background process which tries to write to its controlling terminal.
2450 ICANON Enable canonical mode. This enables the special characters EOF, EOL, EOL2, ERASE, KILL, LNEXT, REPRINT, STATUS, and WERASE, and buffers by lines.
2451
2452 VTIME Timeout in deciseconds for non-canonical read.
2453 VMIN Minimum number of characters for non-canonical read.
2454
2455 raw mode turning off ICANON, IEXTEN, and ISIG kills most special key processing.
2456 termio.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
2457 termio.c_oflag &= ~OPOST;
2458 termio.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
2459 termio.c_cflag &= ~(CSIZE | PARENB);
2460 termio.c_cflag |= CS8;
2461
2462 IGNBRK ignore BREAK
2463 BRKINT complicated, bet in this context, sends BREAK as '\x00'
2464 PARMRK characters with parity or frame erors are sent as '\x00'
2465 ISTRIP strip 8th byte
2466 INLCR translate LF to CR in input
2467 IGLCR ignore CR
2468 OPOST enable implementation defined output processing
2469 ISIG generate signals on INTR (SIGINT on ^C), QUIT (SIGQUIT on ^\\), SUSP (SIGTSTP on ^Z), DSUSP (SIGTSTP on ^Y)
2470 IEXTEN enable implementation defined input processing, turns on some key -> signal -tuff
2471 CSIZE mask for character sizes, so in this case, we mask them all out
2472 PARENB enable parity
2473 CS8 8 bit characters
2474
2475 VEOF "sends" EOF on ^D, ICANON turns that on.
2476 VSTART restart output on ^Q, IXON turns that on.
2477 VSTATUS display status info and sends SIGINFO (STATUS) on ^T. Not in POSIX, not supported in Linux. ICANON turns that on.
2478 VSTOP stop output on ^S, IXON turns that on.
2479 */
2480 termio.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IUCLC | IXON | IXOFF | IXANY);
2481 termio.c_oflag &= ~OPOST;
2482 termio.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL | TOSTOP | ICANON | ISIG | IEXTEN);
2483 termio.c_cflag &= ~(CSIZE | PARENB);
2484 termio.c_cflag |= CS8;
2485 termio.c_cc[VTIME]=0; // deciseconds.
2486 termio.c_cc[VMIN]=1;
2487 tcsetattr(0, TCSANOW, &termio);
2488
2489 terminal_size(&W, &H);
2490 if (toys.optflags & FLAG_w)
2491 W = TT.w;
2492 if (toys.optflags & FLAG_h)
2493 H = TT.h;
2494
2495 // 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.
2496 rootBox = addBox("root", context, toys.optargs[0], 0, 0, W, H - 1);
2497 currentBox = rootBox;
2498
2499 // Create the command line view, sharing the same context as the root. It will differentiate based on the view mode of the current box.
2500 // Also load the command line history as it's file.
2501 // 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?
2502 commandLine = addView("command", rootBox->view->content->context, ".boxes.history", 0, H, W, 1);
2503 // Add a prompt to it.
2504 commandLine->prompt = xrealloc(commandLine->prompt, strlen(prompt) + 1);
2505 strcpy(commandLine->prompt, prompt);
2506 // Move to the end of the history.
2507 moveCursorAbsolute(commandLine, 0, commandLine->content->lines.length, 0, 0);
2508
2509 // All the mouse tracking methods suck one way or another. sigh
2510 // http://rtfm.etla.org/xterm/ctlseq.html documents xterm stuff, near the bottom is the mouse stuff.
2511 // http://leonerds-code.blogspot.co.uk/2012/04/wide-mouse-support-in-libvterm.html is helpful.
2512 // Enable mouse (VT200 normal tracking mode, UTF8 encoding). The limit is 2015. Seems to only be in later xterms.
2513// fputs("\x1B[?1005h", stdout);
2514 // Enable mouse (VT340 locator reporting mode). In theory has no limit. Wont actually work though.
2515 // On the other hand, only allows for four buttons, so only half a mouse wheel.
2516 // 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".
2517// fputs("\x1B[1;2'z\x1B[1;3'{", stdout);
2518 // 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.
2519 // 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
2520// fputs("\x1B[?1000h", stdout);
2521// fflush(stdout);
2522
2523 calcBoxes(currentBox);
2524 drawBoxes(currentBox);
2525 // Do the first cursor update.
2526 updateLine(currentBox->view);
2527
2528 // Run the main loop.
2529 handle_keys((long) currentBox->view, handleEvent);
2530
2531 // TODO - Should remember to turn off mouse reporting when we leave.
2532
2533 // Restore the old terminal settings.
2534 tcsetattr(0, TCSANOW, &oldtermio);
2535
2536
2537 if (toys.optflags & FLAG_m)
2538 {
2539 if (strcmp(TT.mode, "sh") == 0)
2540 {
2541 int status, result, i;
2542 double sum;
2543
2544 /* Load the file containing the script we are going to run */
2545 status = luaL_loadfile(L, "toys/boxes/script.lua");
2546 if (status)
2547 {
2548 /* If something went wrong, error message is at the top of */
2549 /* the stack */
2550 fprintf(stderr, "Couldn't load file: %s\n", lua_tostring(L, -1));
2551 exit(1);
2552 }
2553
2554 /*
2555 * Ok, now here we go: We pass data to the lua script on the stack.
2556 * That is, we first have to prepare Lua's virtual stack the way we
2557 * want the script to receive it, then ask Lua to run it.
2558 */
2559 lua_newtable(L); /* We will pass a table */
2560
2561 /*
2562 * To put values into the table, we first push the index, then the
2563 * value, and then call lua_rawset() with the index of the table in the
2564 * stack. Let's see why it's -3: In Lua, the value -1 always refers to
2565 * the top of the stack. When you create the table with lua_newtable(),
2566 * the table gets pushed into the top of the stack. When you push the
2567 * index and then the cell value, the stack looks like:
2568 *
2569 * <- [stack bottom] -- table, index, value [top]
2570 *
2571 * So the -1 will refer to the cell value, thus -3 is used to refer to
2572 * the table itself. Note that lua_rawset() pops the two last elements
2573 * of the stack, so that after it has been called, the table is at the
2574 * top of the stack.
2575 */
2576 for (i = 1; i <= 5; i++)
2577 {
2578 lua_pushnumber(L, i); /* Push the table index */
2579 lua_pushnumber(L, i*2); /* Push the cell value */
2580 lua_rawset(L, -3); /* Stores the pair in the table */
2581 }
2582
2583 /* By what name is the script going to reference our table? */
2584 lua_setglobal(L, "foo");
2585
2586 /* Ask Lua to run our little script */
2587 result = lua_pcall(L, 0, LUA_MULTRET, 0);
2588 if (result)
2589 {
2590 fprintf(stderr, "Failed to run script: %s\n", lua_tostring(L, -1));
2591 exit(1);
2592 }
2593
2594 /* Get the returned value at the top of the stack (index -1) */
2595 sum = lua_tonumber(L, -1);
2596
2597 printf("Script returned: %.0f\n", sum);
2598
2599 lua_pop(L, 1); /* Take the returned value out of the stack */
2600 }
2601 }
2602
2603 lua_close(L); /* Cya, Lua */
2604
2605 puts("");
2606 fflush(stdout);
2607}