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