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