1// [DEAR IMGUI]
2// This is a slightly modified version of stb_textedit.h 1.13.
3// Those changes would need to be pushed into nothings/stb:
4// - Fix in stb_textedit_discard_redo (see https://github.com/nothings/stb/issues/321)
5// Grep for [DEAR IMGUI] to find the changes.
6
7// stb_textedit.h - v1.13  - public domain - Sean Barrett
8// Development of this library was sponsored by RAD Game Tools
9//
10// This C header file implements the guts of a multi-line text-editing
11// widget; you implement display, word-wrapping, and low-level string
12// insertion/deletion, and stb_textedit will map user inputs into
13// insertions & deletions, plus updates to the cursor position,
14// selection state, and undo state.
15//
16// It is intended for use in games and other systems that need to build
17// their own custom widgets and which do not have heavy text-editing
18// requirements (this library is not recommended for use for editing large
19// texts, as its performance does not scale and it has limited undo).
20//
21// Non-trivial behaviors are modelled after Windows text controls.
22//
23//
24// LICENSE
25//
26// See end of file for license information.
27//
28//
29// DEPENDENCIES
30//
31// Uses the C runtime function 'memmove', which you can override
32// by defining STB_TEXTEDIT_memmove before the implementation.
33// Uses no other functions. Performs no runtime allocations.
34//
35//
36// VERSION HISTORY
37//
38//   1.13 (2019-02-07) fix bug in undo size management
39//   1.12 (2018-01-29) user can change STB_TEXTEDIT_KEYTYPE, fix redo to avoid crash
40//   1.11 (2017-03-03) fix HOME on last line, dragging off single-line textfield
41//   1.10 (2016-10-25) supress warnings about casting away const with -Wcast-qual
42//   1.9  (2016-08-27) customizable move-by-word
43//   1.8  (2016-04-02) better keyboard handling when mouse button is down
44//   1.7  (2015-09-13) change y range handling in case baseline is non-0
45//   1.6  (2015-04-15) allow STB_TEXTEDIT_memmove
46//   1.5  (2014-09-10) add support for secondary keys for OS X
47//   1.4  (2014-08-17) fix signed/unsigned warnings
48//   1.3  (2014-06-19) fix mouse clicking to round to nearest char boundary
49//   1.2  (2014-05-27) fix some RAD types that had crept into the new code
50//   1.1  (2013-12-15) move-by-word (requires STB_TEXTEDIT_IS_SPACE )
51//   1.0  (2012-07-26) improve documentation, initial public release
52//   0.3  (2012-02-24) bugfixes, single-line mode; insert mode
53//   0.2  (2011-11-28) fixes to undo/redo
54//   0.1  (2010-07-08) initial version
55//
56// ADDITIONAL CONTRIBUTORS
57//
58//   Ulf Winklemann: move-by-word in 1.1
59//   Fabian Giesen: secondary key inputs in 1.5
60//   Martins Mozeiko: STB_TEXTEDIT_memmove in 1.6
61//
62//   Bugfixes:
63//      Scott Graham
64//      Daniel Keller
65//      Omar Cornut
66//      Dan Thompson
67//
68// USAGE
69//
70// This file behaves differently depending on what symbols you define
71// before including it.
72//
73//
74// Header-file mode:
75//
76//   If you do not define STB_TEXTEDIT_IMPLEMENTATION before including this,
77//   it will operate in "header file" mode. In this mode, it declares a
78//   single public symbol, STB_TexteditState, which encapsulates the current
79//   state of a text widget (except for the string, which you will store
80//   separately).
81//
82//   To compile in this mode, you must define STB_TEXTEDIT_CHARTYPE to a
83//   primitive type that defines a single character (e.g. char, wchar_t, etc).
84//
85//   To save space or increase undo-ability, you can optionally define the
86//   following things that are used by the undo system:
87//
88//      STB_TEXTEDIT_POSITIONTYPE         small int type encoding a valid cursor position
89//      STB_TEXTEDIT_UNDOSTATECOUNT       the number of undo states to allow
90//      STB_TEXTEDIT_UNDOCHARCOUNT        the number of characters to store in the undo buffer
91//
92//   If you don't define these, they are set to permissive types and
93//   moderate sizes. The undo system does no memory allocations, so
94//   it grows STB_TexteditState by the worst-case storage which is (in bytes):
95//
96//        [4 + 3 * sizeof(STB_TEXTEDIT_POSITIONTYPE)] * STB_TEXTEDIT_UNDOSTATE_COUNT
97//      +          sizeof(STB_TEXTEDIT_CHARTYPE)      * STB_TEXTEDIT_UNDOCHAR_COUNT
98//
99//
100// Implementation mode:
101//
102//   If you define STB_TEXTEDIT_IMPLEMENTATION before including this, it
103//   will compile the implementation of the text edit widget, depending
104//   on a large number of symbols which must be defined before the include.
105//
106//   The implementation is defined only as static functions. You will then
107//   need to provide your own APIs in the same file which will access the
108//   static functions.
109//
110//   The basic concept is that you provide a "string" object which
111//   behaves like an array of characters. stb_textedit uses indices to
112//   refer to positions in the string, implicitly representing positions
113//   in the displayed textedit. This is true for both plain text and
114//   rich text; even with rich text stb_truetype interacts with your
115//   code as if there was an array of all the displayed characters.
116//
117// Symbols that must be the same in header-file and implementation mode:
118//
119//     STB_TEXTEDIT_CHARTYPE             the character type
120//     STB_TEXTEDIT_POSITIONTYPE         small type that is a valid cursor position
121//     STB_TEXTEDIT_UNDOSTATECOUNT       the number of undo states to allow
122//     STB_TEXTEDIT_UNDOCHARCOUNT        the number of characters to store in the undo buffer
123//
124// Symbols you must define for implementation mode:
125//
126//    STB_TEXTEDIT_STRING               the type of object representing a string being edited,
127//                                      typically this is a wrapper object with other data you need
128//
129//    STB_TEXTEDIT_STRINGLEN(obj)       the length of the string (ideally O(1))
130//    STB_TEXTEDIT_LAYOUTROW(&r,obj,n)  returns the results of laying out a line of characters
131//                                        starting from character #n (see discussion below)
132//    STB_TEXTEDIT_GETWIDTH(obj,n,i)    returns the pixel delta from the xpos of the i'th character
133//                                        to the xpos of the i+1'th char for a line of characters
134//                                        starting at character #n (i.e. accounts for kerning
135//                                        with previous char)
136//    STB_TEXTEDIT_KEYTOTEXT(k)         maps a keyboard input to an insertable character
137//                                        (return type is int, -1 means not valid to insert)
138//    STB_TEXTEDIT_GETCHAR(obj,i)       returns the i'th character of obj, 0-based
139//    STB_TEXTEDIT_NEWLINE              the character returned by _GETCHAR() we recognize
140//                                        as manually wordwrapping for end-of-line positioning
141//
142//    STB_TEXTEDIT_DELETECHARS(obj,i,n)      delete n characters starting at i
143//    STB_TEXTEDIT_INSERTCHARS(obj,i,c*,n)   insert n characters at i (pointed to by STB_TEXTEDIT_CHARTYPE*)
144//
145//    STB_TEXTEDIT_K_SHIFT       a power of two that is or'd in to a keyboard input to represent the shift key
146//
147//    STB_TEXTEDIT_K_LEFT        keyboard input to move cursor left
148//    STB_TEXTEDIT_K_RIGHT       keyboard input to move cursor right
149//    STB_TEXTEDIT_K_UP          keyboard input to move cursor up
150//    STB_TEXTEDIT_K_DOWN        keyboard input to move cursor down
151//    STB_TEXTEDIT_K_LINESTART   keyboard input to move cursor to start of line  // e.g. HOME
152//    STB_TEXTEDIT_K_LINEEND     keyboard input to move cursor to end of line    // e.g. END
153//    STB_TEXTEDIT_K_TEXTSTART   keyboard input to move cursor to start of text  // e.g. ctrl-HOME
154//    STB_TEXTEDIT_K_TEXTEND     keyboard input to move cursor to end of text    // e.g. ctrl-END
155//    STB_TEXTEDIT_K_DELETE      keyboard input to delete selection or character under cursor
156//    STB_TEXTEDIT_K_BACKSPACE   keyboard input to delete selection or character left of cursor
157//    STB_TEXTEDIT_K_UNDO        keyboard input to perform undo
158//    STB_TEXTEDIT_K_REDO        keyboard input to perform redo
159//
160// Optional:
161//    STB_TEXTEDIT_K_INSERT              keyboard input to toggle insert mode
162//    STB_TEXTEDIT_IS_SPACE(ch)          true if character is whitespace (e.g. 'isspace'),
163//                                          required for default WORDLEFT/WORDRIGHT handlers
164//    STB_TEXTEDIT_MOVEWORDLEFT(obj,i)   custom handler for WORDLEFT, returns index to move cursor to
165//    STB_TEXTEDIT_MOVEWORDRIGHT(obj,i)  custom handler for WORDRIGHT, returns index to move cursor to
166//    STB_TEXTEDIT_K_WORDLEFT            keyboard input to move cursor left one word // e.g. ctrl-LEFT
167//    STB_TEXTEDIT_K_WORDRIGHT           keyboard input to move cursor right one word // e.g. ctrl-RIGHT
168//    STB_TEXTEDIT_K_LINESTART2          secondary keyboard input to move cursor to start of line
169//    STB_TEXTEDIT_K_LINEEND2            secondary keyboard input to move cursor to end of line
170//    STB_TEXTEDIT_K_TEXTSTART2          secondary keyboard input to move cursor to start of text
171//    STB_TEXTEDIT_K_TEXTEND2            secondary keyboard input to move cursor to end of text
172//
173// Todo:
174//    STB_TEXTEDIT_K_PGUP        keyboard input to move cursor up a page
175//    STB_TEXTEDIT_K_PGDOWN      keyboard input to move cursor down a page
176//
177// Keyboard input must be encoded as a single integer value; e.g. a character code
178// and some bitflags that represent shift states. to simplify the interface, SHIFT must
179// be a bitflag, so we can test the shifted state of cursor movements to allow selection,
180// i.e. (STB_TEXTED_K_RIGHT|STB_TEXTEDIT_K_SHIFT) should be shifted right-arrow.
181//
182// You can encode other things, such as CONTROL or ALT, in additional bits, and
183// then test for their presence in e.g. STB_TEXTEDIT_K_WORDLEFT. For example,
184// my Windows implementations add an additional CONTROL bit, and an additional KEYDOWN
185// bit. Then all of the STB_TEXTEDIT_K_ values bitwise-or in the KEYDOWN bit,
186// and I pass both WM_KEYDOWN and WM_CHAR events to the "key" function in the
187// API below. The control keys will only match WM_KEYDOWN events because of the
188// keydown bit I add, and STB_TEXTEDIT_KEYTOTEXT only tests for the KEYDOWN
189// bit so it only decodes WM_CHAR events.
190//
191// STB_TEXTEDIT_LAYOUTROW returns information about the shape of one displayed
192// row of characters assuming they start on the i'th character--the width and
193// the height and the number of characters consumed. This allows this library
194// to traverse the entire layout incrementally. You need to compute word-wrapping
195// here.
196//
197// Each textfield keeps its own insert mode state, which is not how normal
198// applications work. To keep an app-wide insert mode, update/copy the
199// "insert_mode" field of STB_TexteditState before/after calling API functions.
200//
201// API
202//
203//    void stb_textedit_initialize_state(STB_TexteditState *state, int is_single_line)
204//
205//    void stb_textedit_click(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y)
206//    void stb_textedit_drag(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y)
207//    int  stb_textedit_cut(STB_TEXTEDIT_STRING *str, STB_TexteditState *state)
208//    int  stb_textedit_paste(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_CHARTYPE *text, int len)
209//    void stb_textedit_key(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXEDIT_KEYTYPE key)
210//
211//    Each of these functions potentially updates the string and updates the
212//    state.
213//
214//      initialize_state:
215//          set the textedit state to a known good default state when initially
216//          constructing the textedit.
217//
218//      click:
219//          call this with the mouse x,y on a mouse down; it will update the cursor
220//          and reset the selection start/end to the cursor point. the x,y must
221//          be relative to the text widget, with (0,0) being the top left.
222//
223//      drag:
224//          call this with the mouse x,y on a mouse drag/up; it will update the
225//          cursor and the selection end point
226//
227//      cut:
228//          call this to delete the current selection; returns true if there was
229//          one. you should FIRST copy the current selection to the system paste buffer.
230//          (To copy, just copy the current selection out of the string yourself.)
231//
232//      paste:
233//          call this to paste text at the current cursor point or over the current
234//          selection if there is one.
235//
236//      key:
237//          call this for keyboard inputs sent to the textfield. you can use it
238//          for "key down" events or for "translated" key events. if you need to
239//          do both (as in Win32), or distinguish Unicode characters from control
240//          inputs, set a high bit to distinguish the two; then you can define the
241//          various definitions like STB_TEXTEDIT_K_LEFT have the is-key-event bit
242//          set, and make STB_TEXTEDIT_KEYTOCHAR check that the is-key-event bit is
243//          clear. STB_TEXTEDIT_KEYTYPE defaults to int, but you can #define it to
244//          anything other type you wante before including.
245//
246//
247//   When rendering, you can read the cursor position and selection state from
248//   the STB_TexteditState.
249//
250//
251// Notes:
252//
253// This is designed to be usable in IMGUI, so it allows for the possibility of
254// running in an IMGUI that has NOT cached the multi-line layout. For this
255// reason, it provides an interface that is compatible with computing the
256// layout incrementally--we try to make sure we make as few passes through
257// as possible. (For example, to locate the mouse pointer in the text, we
258// could define functions that return the X and Y positions of characters
259// and binary search Y and then X, but if we're doing dynamic layout this
260// will run the layout algorithm many times, so instead we manually search
261// forward in one pass. Similar logic applies to e.g. up-arrow and
262// down-arrow movement.)
263//
264// If it's run in a widget that *has* cached the layout, then this is less
265// efficient, but it's not horrible on modern computers. But you wouldn't
266// want to edit million-line files with it.
267
268
269////////////////////////////////////////////////////////////////////////////
270////////////////////////////////////////////////////////////////////////////
271////
272////   Header-file mode
273////
274////
275
276#ifndef INCLUDE_STB_TEXTEDIT_H
277#define INCLUDE_STB_TEXTEDIT_H
278
279////////////////////////////////////////////////////////////////////////
280//
281//     STB_TexteditState
282//
283// Definition of STB_TexteditState which you should store
284// per-textfield; it includes cursor position, selection state,
285// and undo state.
286//
287
288#ifndef STB_TEXTEDIT_UNDOSTATECOUNT
289#define STB_TEXTEDIT_UNDOSTATECOUNT   99
290#endif
291#ifndef STB_TEXTEDIT_UNDOCHARCOUNT
292#define STB_TEXTEDIT_UNDOCHARCOUNT   999
293#endif
294#ifndef STB_TEXTEDIT_CHARTYPE
295#define STB_TEXTEDIT_CHARTYPE        int
296#endif
297#ifndef STB_TEXTEDIT_POSITIONTYPE
298#define STB_TEXTEDIT_POSITIONTYPE    int
299#endif
300
301typedef struct
302{
303   // private data
304   STB_TEXTEDIT_POSITIONTYPE  where;
305   STB_TEXTEDIT_POSITIONTYPE  insert_length;
306   STB_TEXTEDIT_POSITIONTYPE  delete_length;
307   int                        char_storage;
308} StbUndoRecord;
309
310typedef struct
311{
312   // private data
313   StbUndoRecord          undo_rec [STB_TEXTEDIT_UNDOSTATECOUNT];
314   STB_TEXTEDIT_CHARTYPE  undo_char[STB_TEXTEDIT_UNDOCHARCOUNT];
315   short undo_point, redo_point;
316   int undo_char_point, redo_char_point;
317} StbUndoState;
318
319typedef struct
320{
321   /////////////////////
322   //
323   // public data
324   //
325
326   int cursor;
327   // position of the text cursor within the string
328
329   int select_start;          // selection start point
330   int select_end;
331   // selection start and end point in characters; if equal, no selection.
332   // note that start may be less than or greater than end (e.g. when
333   // dragging the mouse, start is where the initial click was, and you
334   // can drag in either direction)
335
336   unsigned char insert_mode;
337   // each textfield keeps its own insert mode state. to keep an app-wide
338   // insert mode, copy this value in/out of the app state
339
340   /////////////////////
341   //
342   // private data
343   //
344   unsigned char cursor_at_end_of_line; // not implemented yet
345   unsigned char initialized;
346   unsigned char has_preferred_x;
347   unsigned char single_line;
348   unsigned char padding1, padding2, padding3;
349   float preferred_x; // this determines where the cursor up/down tries to seek to along x
350   StbUndoState undostate;
351} STB_TexteditState;
352
353
354////////////////////////////////////////////////////////////////////////
355//
356//     StbTexteditRow
357//
358// Result of layout query, used by stb_textedit to determine where
359// the text in each row is.
360
361// result of layout query
362typedef struct
363{
364   float x0,x1;             // starting x location, end x location (allows for align=right, etc)
365   float baseline_y_delta;  // position of baseline relative to previous row's baseline
366   float ymin,ymax;         // height of row above and below baseline
367   int num_chars;
368} StbTexteditRow;
369#endif //INCLUDE_STB_TEXTEDIT_H
370
371
372////////////////////////////////////////////////////////////////////////////
373////////////////////////////////////////////////////////////////////////////
374////
375////   Implementation mode
376////
377////
378
379
380// implementation isn't include-guarded, since it might have indirectly
381// included just the "header" portion
382#ifdef STB_TEXTEDIT_IMPLEMENTATION
383
384#ifndef STB_TEXTEDIT_memmove
385#include <string.h>
386#define STB_TEXTEDIT_memmove memmove
387#endif
388
389
390/////////////////////////////////////////////////////////////////////////////
391//
392//      Mouse input handling
393//
394
395// traverse the layout to locate the nearest character to a display position
396static int stb_text_locate_coord(STB_TEXTEDIT_STRING *str, float x, float y)
397{
398   StbTexteditRow r;
399   int n = STB_TEXTEDIT_STRINGLEN(str);
400   float base_y = 0, prev_x;
401   int i=0, k;
402
403   r.x0 = r.x1 = 0;
404   r.ymin = r.ymax = 0;
405   r.num_chars = 0;
406
407   // search rows to find one that straddles 'y'
408   while (i < n) {
409      STB_TEXTEDIT_LAYOUTROW(&r, str, i);
410      if (r.num_chars <= 0)
411         return n;
412
413      if (i==0 && y < base_y + r.ymin)
414         return 0;
415
416      if (y < base_y + r.ymax)
417         break;
418
419      i += r.num_chars;
420      base_y += r.baseline_y_delta;
421   }
422
423   // below all text, return 'after' last character
424   if (i >= n)
425      return n;
426
427   // check if it's before the beginning of the line
428   if (x < r.x0)
429      return i;
430
431   // check if it's before the end of the line
432   if (x < r.x1) {
433      // search characters in row for one that straddles 'x'
434      prev_x = r.x0;
435      for (k=0; k < r.num_chars; ++k) {
436         float w = STB_TEXTEDIT_GETWIDTH(str, i, k);
437         if (x < prev_x+w) {
438            if (x < prev_x+w/2)
439               return k+i;
440            else
441               return k+i+1;
442         }
443         prev_x += w;
444      }
445      // shouldn't happen, but if it does, fall through to end-of-line case
446   }
447
448   // if the last character is a newline, return that. otherwise return 'after' the last character
449   if (STB_TEXTEDIT_GETCHAR(str, i+r.num_chars-1) == STB_TEXTEDIT_NEWLINE)
450      return i+r.num_chars-1;
451   else
452      return i+r.num_chars;
453}
454
455// API click: on mouse down, move the cursor to the clicked location, and reset the selection
456static void stb_textedit_click(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y)
457{
458   // In single-line mode, just always make y = 0. This lets the drag keep working if the mouse
459   // goes off the top or bottom of the text
460   if( state->single_line )
461   {
462      StbTexteditRow r;
463      STB_TEXTEDIT_LAYOUTROW(&r, str, 0);
464      y = r.ymin;
465   }
466
467   state->cursor = stb_text_locate_coord(str, x, y);
468   state->select_start = state->cursor;
469   state->select_end = state->cursor;
470   state->has_preferred_x = 0;
471}
472
473// API drag: on mouse drag, move the cursor and selection endpoint to the clicked location
474static void stb_textedit_drag(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y)
475{
476   int p = 0;
477
478   // In single-line mode, just always make y = 0. This lets the drag keep working if the mouse
479   // goes off the top or bottom of the text
480   if( state->single_line )
481   {
482      StbTexteditRow r;
483      STB_TEXTEDIT_LAYOUTROW(&r, str, 0);
484      y = r.ymin;
485   }
486
487   if (state->select_start == state->select_end)
488      state->select_start = state->cursor;
489
490   p = stb_text_locate_coord(str, x, y);
491   state->cursor = state->select_end = p;
492}
493
494/////////////////////////////////////////////////////////////////////////////
495//
496//      Keyboard input handling
497//
498
499// forward declarations
500static void stb_text_undo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state);
501static void stb_text_redo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state);
502static void stb_text_makeundo_delete(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int length);
503static void stb_text_makeundo_insert(STB_TexteditState *state, int where, int length);
504static void stb_text_makeundo_replace(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int old_length, int new_length);
505
506typedef struct
507{
508   float x,y;    // position of n'th character
509   float height; // height of line
510   int first_char, length; // first char of row, and length
511   int prev_first;  // first char of previous row
512} StbFindState;
513
514// find the x/y location of a character, and remember info about the previous row in
515// case we get a move-up event (for page up, we'll have to rescan)
516static void stb_textedit_find_charpos(StbFindState *find, STB_TEXTEDIT_STRING *str, int n, int single_line)
517{
518   StbTexteditRow r;
519   int prev_start = 0;
520   int z = STB_TEXTEDIT_STRINGLEN(str);
521   int i=0, first;
522
523   if (n == z) {
524      // if it's at the end, then find the last line -- simpler than trying to
525      // explicitly handle this case in the regular code
526      if (single_line) {
527         STB_TEXTEDIT_LAYOUTROW(&r, str, 0);
528         find->y = 0;
529         find->first_char = 0;
530         find->length = z;
531         find->height = r.ymax - r.ymin;
532         find->x = r.x1;
533      } else {
534         find->y = 0;
535         find->x = 0;
536         find->height = 1;
537         while (i < z) {
538            STB_TEXTEDIT_LAYOUTROW(&r, str, i);
539            prev_start = i;
540            i += r.num_chars;
541         }
542         find->first_char = i;
543         find->length = 0;
544         find->prev_first = prev_start;
545      }
546      return;
547   }
548
549   // search rows to find the one that straddles character n
550   find->y = 0;
551
552   for(;;) {
553      STB_TEXTEDIT_LAYOUTROW(&r, str, i);
554      if (n < i + r.num_chars)
555         break;
556      prev_start = i;
557      i += r.num_chars;
558      find->y += r.baseline_y_delta;
559   }
560
561   find->first_char = first = i;
562   find->length = r.num_chars;
563   find->height = r.ymax - r.ymin;
564   find->prev_first = prev_start;
565
566   // now scan to find xpos
567   find->x = r.x0;
568   for (i=0; first+i < n; ++i)
569      find->x += STB_TEXTEDIT_GETWIDTH(str, first, i);
570}
571
572#define STB_TEXT_HAS_SELECTION(s)   ((s)->select_start != (s)->select_end)
573
574// make the selection/cursor state valid if client altered the string
575static void stb_textedit_clamp(STB_TEXTEDIT_STRING *str, STB_TexteditState *state)
576{
577   int n = STB_TEXTEDIT_STRINGLEN(str);
578   if (STB_TEXT_HAS_SELECTION(state)) {
579      if (state->select_start > n) state->select_start = n;
580      if (state->select_end   > n) state->select_end = n;
581      // if clamping forced them to be equal, move the cursor to match
582      if (state->select_start == state->select_end)
583         state->cursor = state->select_start;
584   }
585   if (state->cursor > n) state->cursor = n;
586}
587
588// delete characters while updating undo
589static void stb_textedit_delete(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int len)
590{
591   stb_text_makeundo_delete(str, state, where, len);
592   STB_TEXTEDIT_DELETECHARS(str, where, len);
593   state->has_preferred_x = 0;
594}
595
596// delete the section
597static void stb_textedit_delete_selection(STB_TEXTEDIT_STRING *str, STB_TexteditState *state)
598{
599   stb_textedit_clamp(str, state);
600   if (STB_TEXT_HAS_SELECTION(state)) {
601      if (state->select_start < state->select_end) {
602         stb_textedit_delete(str, state, state->select_start, state->select_end - state->select_start);
603         state->select_end = state->cursor = state->select_start;
604      } else {
605         stb_textedit_delete(str, state, state->select_end, state->select_start - state->select_end);
606         state->select_start = state->cursor = state->select_end;
607      }
608      state->has_preferred_x = 0;
609   }
610}
611
612// canoncialize the selection so start <= end
613static void stb_textedit_sortselection(STB_TexteditState *state)
614{
615   if (state->select_end < state->select_start) {
616      int temp = state->select_end;
617      state->select_end = state->select_start;
618      state->select_start = temp;
619   }
620}
621
622// move cursor to first character of selection
623static void stb_textedit_move_to_first(STB_TexteditState *state)
624{
625   if (STB_TEXT_HAS_SELECTION(state)) {
626      stb_textedit_sortselection(state);
627      state->cursor = state->select_start;
628      state->select_end = state->select_start;
629      state->has_preferred_x = 0;
630   }
631}
632
633// move cursor to last character of selection
634static void stb_textedit_move_to_last(STB_TEXTEDIT_STRING *str, STB_TexteditState *state)
635{
636   if (STB_TEXT_HAS_SELECTION(state)) {
637      stb_textedit_sortselection(state);
638      stb_textedit_clamp(str, state);
639      state->cursor = state->select_end;
640      state->select_start = state->select_end;
641      state->has_preferred_x = 0;
642   }
643}
644
645#ifdef STB_TEXTEDIT_IS_SPACE
646static int is_word_boundary( STB_TEXTEDIT_STRING *str, int idx )
647{
648   return idx > 0 ? (STB_TEXTEDIT_IS_SPACE( STB_TEXTEDIT_GETCHAR(str,idx-1) ) && !STB_TEXTEDIT_IS_SPACE( STB_TEXTEDIT_GETCHAR(str, idx) ) ) : 1;
649}
650
651#ifndef STB_TEXTEDIT_MOVEWORDLEFT
652static int stb_textedit_move_to_word_previous( STB_TEXTEDIT_STRING *str, int c )
653{
654   --c; // always move at least one character
655   while( c >= 0 && !is_word_boundary( str, c ) )
656      --c;
657
658   if( c < 0 )
659      c = 0;
660
661   return c;
662}
663#define STB_TEXTEDIT_MOVEWORDLEFT stb_textedit_move_to_word_previous
664#endif
665
666#ifndef STB_TEXTEDIT_MOVEWORDRIGHT
667static int stb_textedit_move_to_word_next( STB_TEXTEDIT_STRING *str, int c )
668{
669   const int len = STB_TEXTEDIT_STRINGLEN(str);
670   ++c; // always move at least one character
671   while( c < len && !is_word_boundary( str, c ) )
672      ++c;
673
674   if( c > len )
675      c = len;
676
677   return c;
678}
679#define STB_TEXTEDIT_MOVEWORDRIGHT stb_textedit_move_to_word_next
680#endif
681
682#endif
683
684// update selection and cursor to match each other
685static void stb_textedit_prep_selection_at_cursor(STB_TexteditState *state)
686{
687   if (!STB_TEXT_HAS_SELECTION(state))
688      state->select_start = state->select_end = state->cursor;
689   else
690      state->cursor = state->select_end;
691}
692
693// API cut: delete selection
694static int stb_textedit_cut(STB_TEXTEDIT_STRING *str, STB_TexteditState *state)
695{
696   if (STB_TEXT_HAS_SELECTION(state)) {
697      stb_textedit_delete_selection(str,state); // implicitly clamps
698      state->has_preferred_x = 0;
699      return 1;
700   }
701   return 0;
702}
703
704// API paste: replace existing selection with passed-in text
705static int stb_textedit_paste_internal(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_CHARTYPE *text, int len)
706{
707   // if there's a selection, the paste should delete it
708   stb_textedit_clamp(str, state);
709   stb_textedit_delete_selection(str,state);
710   // try to insert the characters
711   if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, len)) {
712      stb_text_makeundo_insert(state, state->cursor, len);
713      state->cursor += len;
714      state->has_preferred_x = 0;
715      return 1;
716   }
717   // remove the undo since we didn't actually insert the characters
718   if (state->undostate.undo_point)
719      --state->undostate.undo_point;
720   return 0;
721}
722
723#ifndef STB_TEXTEDIT_KEYTYPE
724#define STB_TEXTEDIT_KEYTYPE int
725#endif
726
727// API key: process a keyboard input
728static void stb_textedit_key(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_KEYTYPE key)
729{
730retry:
731   switch (key) {
732      default: {
733         int c = STB_TEXTEDIT_KEYTOTEXT(key);
734         if (c > 0) {
735            STB_TEXTEDIT_CHARTYPE ch = (STB_TEXTEDIT_CHARTYPE) c;
736
737            // can't add newline in single-line mode
738            if (c == '\n' && state->single_line)
739               break;
740
741            if (state->insert_mode && !STB_TEXT_HAS_SELECTION(state) && state->cursor < STB_TEXTEDIT_STRINGLEN(str)) {
742               stb_text_makeundo_replace(str, state, state->cursor, 1, 1);
743               STB_TEXTEDIT_DELETECHARS(str, state->cursor, 1);
744               if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, &ch, 1)) {
745                  ++state->cursor;
746                  state->has_preferred_x = 0;
747               }
748            } else {
749               stb_textedit_delete_selection(str,state); // implicitly clamps
750               if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, &ch, 1)) {
751                  stb_text_makeundo_insert(state, state->cursor, 1);
752                  ++state->cursor;
753                  state->has_preferred_x = 0;
754               }
755            }
756         }
757         break;
758      }
759
760#ifdef STB_TEXTEDIT_K_INSERT
761      case STB_TEXTEDIT_K_INSERT:
762         state->insert_mode = !state->insert_mode;
763         break;
764#endif
765
766      case STB_TEXTEDIT_K_UNDO:
767         stb_text_undo(str, state);
768         state->has_preferred_x = 0;
769         break;
770
771      case STB_TEXTEDIT_K_REDO:
772         stb_text_redo(str, state);
773         state->has_preferred_x = 0;
774         break;
775
776      case STB_TEXTEDIT_K_LEFT:
777         // if currently there's a selection, move cursor to start of selection
778         if (STB_TEXT_HAS_SELECTION(state))
779            stb_textedit_move_to_first(state);
780         else
781            if (state->cursor > 0)
782               --state->cursor;
783         state->has_preferred_x = 0;
784         break;
785
786      case STB_TEXTEDIT_K_RIGHT:
787         // if currently there's a selection, move cursor to end of selection
788         if (STB_TEXT_HAS_SELECTION(state))
789            stb_textedit_move_to_last(str, state);
790         else
791            ++state->cursor;
792         stb_textedit_clamp(str, state);
793         state->has_preferred_x = 0;
794         break;
795
796      case STB_TEXTEDIT_K_LEFT | STB_TEXTEDIT_K_SHIFT:
797         stb_textedit_clamp(str, state);
798         stb_textedit_prep_selection_at_cursor(state);
799         // move selection left
800         if (state->select_end > 0)
801            --state->select_end;
802         state->cursor = state->select_end;
803         state->has_preferred_x = 0;
804         break;
805
806#ifdef STB_TEXTEDIT_MOVEWORDLEFT
807      case STB_TEXTEDIT_K_WORDLEFT:
808         if (STB_TEXT_HAS_SELECTION(state))
809            stb_textedit_move_to_first(state);
810         else {
811            state->cursor = STB_TEXTEDIT_MOVEWORDLEFT(str, state->cursor);
812            stb_textedit_clamp( str, state );
813         }
814         break;
815
816      case STB_TEXTEDIT_K_WORDLEFT | STB_TEXTEDIT_K_SHIFT:
817         if( !STB_TEXT_HAS_SELECTION( state ) )
818            stb_textedit_prep_selection_at_cursor(state);
819
820         state->cursor = STB_TEXTEDIT_MOVEWORDLEFT(str, state->cursor);
821         state->select_end = state->cursor;
822
823         stb_textedit_clamp( str, state );
824         break;
825#endif
826
827#ifdef STB_TEXTEDIT_MOVEWORDRIGHT
828      case STB_TEXTEDIT_K_WORDRIGHT:
829         if (STB_TEXT_HAS_SELECTION(state))
830            stb_textedit_move_to_last(str, state);
831         else {
832            state->cursor = STB_TEXTEDIT_MOVEWORDRIGHT(str, state->cursor);
833            stb_textedit_clamp( str, state );
834         }
835         break;
836
837      case STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT:
838         if( !STB_TEXT_HAS_SELECTION( state ) )
839            stb_textedit_prep_selection_at_cursor(state);
840
841         state->cursor = STB_TEXTEDIT_MOVEWORDRIGHT(str, state->cursor);
842         state->select_end = state->cursor;
843
844         stb_textedit_clamp( str, state );
845         break;
846#endif
847
848      case STB_TEXTEDIT_K_RIGHT | STB_TEXTEDIT_K_SHIFT:
849         stb_textedit_prep_selection_at_cursor(state);
850         // move selection right
851         ++state->select_end;
852         stb_textedit_clamp(str, state);
853         state->cursor = state->select_end;
854         state->has_preferred_x = 0;
855         break;
856
857      case STB_TEXTEDIT_K_DOWN:
858      case STB_TEXTEDIT_K_DOWN | STB_TEXTEDIT_K_SHIFT: {
859         StbFindState find;
860         StbTexteditRow row;
861         int i, sel = (key & STB_TEXTEDIT_K_SHIFT) != 0;
862
863         if (state->single_line) {
864            // on windows, up&down in single-line behave like left&right
865            key = STB_TEXTEDIT_K_RIGHT | (key & STB_TEXTEDIT_K_SHIFT);
866            goto retry;
867         }
868
869         if (sel)
870            stb_textedit_prep_selection_at_cursor(state);
871         else if (STB_TEXT_HAS_SELECTION(state))
872            stb_textedit_move_to_last(str,state);
873
874         // compute current position of cursor point
875         stb_textedit_clamp(str, state);
876         stb_textedit_find_charpos(&find, str, state->cursor, state->single_line);
877
878         // now find character position down a row
879         if (find.length) {
880            float goal_x = state->has_preferred_x ? state->preferred_x : find.x;
881            float x;
882            int start = find.first_char + find.length;
883            state->cursor = start;
884            STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor);
885            x = row.x0;
886            for (i=0; i < row.num_chars; ++i) {
887               float dx = STB_TEXTEDIT_GETWIDTH(str, start, i);
888               #ifdef STB_TEXTEDIT_GETWIDTH_NEWLINE
889               if (dx == STB_TEXTEDIT_GETWIDTH_NEWLINE)
890                  break;
891               #endif
892               x += dx;
893               if (x > goal_x)
894                  break;
895               ++state->cursor;
896            }
897            stb_textedit_clamp(str, state);
898
899            state->has_preferred_x = 1;
900            state->preferred_x = goal_x;
901
902            if (sel)
903               state->select_end = state->cursor;
904         }
905         break;
906      }
907
908      case STB_TEXTEDIT_K_UP:
909      case STB_TEXTEDIT_K_UP | STB_TEXTEDIT_K_SHIFT: {
910         StbFindState find;
911         StbTexteditRow row;
912         int i, sel = (key & STB_TEXTEDIT_K_SHIFT) != 0;
913
914         if (state->single_line) {
915            // on windows, up&down become left&right
916            key = STB_TEXTEDIT_K_LEFT | (key & STB_TEXTEDIT_K_SHIFT);
917            goto retry;
918         }
919
920         if (sel)
921            stb_textedit_prep_selection_at_cursor(state);
922         else if (STB_TEXT_HAS_SELECTION(state))
923            stb_textedit_move_to_first(state);
924
925         // compute current position of cursor point
926         stb_textedit_clamp(str, state);
927         stb_textedit_find_charpos(&find, str, state->cursor, state->single_line);
928
929         // can only go up if there's a previous row
930         if (find.prev_first != find.first_char) {
931            // now find character position up a row
932            float goal_x = state->has_preferred_x ? state->preferred_x : find.x;
933            float x;
934            state->cursor = find.prev_first;
935            STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor);
936            x = row.x0;
937            for (i=0; i < row.num_chars; ++i) {
938               float dx = STB_TEXTEDIT_GETWIDTH(str, find.prev_first, i);
939               #ifdef STB_TEXTEDIT_GETWIDTH_NEWLINE
940               if (dx == STB_TEXTEDIT_GETWIDTH_NEWLINE)
941                  break;
942               #endif
943               x += dx;
944               if (x > goal_x)
945                  break;
946               ++state->cursor;
947            }
948            stb_textedit_clamp(str, state);
949
950            state->has_preferred_x = 1;
951            state->preferred_x = goal_x;
952
953            if (sel)
954               state->select_end = state->cursor;
955         }
956         break;
957      }
958
959      case STB_TEXTEDIT_K_DELETE:
960      case STB_TEXTEDIT_K_DELETE | STB_TEXTEDIT_K_SHIFT:
961         if (STB_TEXT_HAS_SELECTION(state))
962            stb_textedit_delete_selection(str, state);
963         else {
964            int n = STB_TEXTEDIT_STRINGLEN(str);
965            if (state->cursor < n)
966               stb_textedit_delete(str, state, state->cursor, 1);
967         }
968         state->has_preferred_x = 0;
969         break;
970
971      case STB_TEXTEDIT_K_BACKSPACE:
972      case STB_TEXTEDIT_K_BACKSPACE | STB_TEXTEDIT_K_SHIFT:
973         if (STB_TEXT_HAS_SELECTION(state))
974            stb_textedit_delete_selection(str, state);
975         else {
976            stb_textedit_clamp(str, state);
977            if (state->cursor > 0) {
978               stb_textedit_delete(str, state, state->cursor-1, 1);
979               --state->cursor;
980            }
981         }
982         state->has_preferred_x = 0;
983         break;
984
985#ifdef STB_TEXTEDIT_K_TEXTSTART2
986      case STB_TEXTEDIT_K_TEXTSTART2:
987#endif
988      case STB_TEXTEDIT_K_TEXTSTART:
989         state->cursor = state->select_start = state->select_end = 0;
990         state->has_preferred_x = 0;
991         break;
992
993#ifdef STB_TEXTEDIT_K_TEXTEND2
994      case STB_TEXTEDIT_K_TEXTEND2:
995#endif
996      case STB_TEXTEDIT_K_TEXTEND:
997         state->cursor = STB_TEXTEDIT_STRINGLEN(str);
998         state->select_start = state->select_end = 0;
999         state->has_preferred_x = 0;
1000         break;
1001
1002#ifdef STB_TEXTEDIT_K_TEXTSTART2
1003      case STB_TEXTEDIT_K_TEXTSTART2 | STB_TEXTEDIT_K_SHIFT:
1004#endif
1005      case STB_TEXTEDIT_K_TEXTSTART | STB_TEXTEDIT_K_SHIFT:
1006         stb_textedit_prep_selection_at_cursor(state);
1007         state->cursor = state->select_end = 0;
1008         state->has_preferred_x = 0;
1009         break;
1010
1011#ifdef STB_TEXTEDIT_K_TEXTEND2
1012      case STB_TEXTEDIT_K_TEXTEND2 | STB_TEXTEDIT_K_SHIFT:
1013#endif
1014      case STB_TEXTEDIT_K_TEXTEND | STB_TEXTEDIT_K_SHIFT:
1015         stb_textedit_prep_selection_at_cursor(state);
1016         state->cursor = state->select_end = STB_TEXTEDIT_STRINGLEN(str);
1017         state->has_preferred_x = 0;
1018         break;
1019
1020
1021#ifdef STB_TEXTEDIT_K_LINESTART2
1022      case STB_TEXTEDIT_K_LINESTART2:
1023#endif
1024      case STB_TEXTEDIT_K_LINESTART:
1025         stb_textedit_clamp(str, state);
1026         stb_textedit_move_to_first(state);
1027         if (state->single_line)
1028            state->cursor = 0;
1029         else while (state->cursor > 0 && STB_TEXTEDIT_GETCHAR(str, state->cursor-1) != STB_TEXTEDIT_NEWLINE)
1030            --state->cursor;
1031         state->has_preferred_x = 0;
1032         break;
1033
1034#ifdef STB_TEXTEDIT_K_LINEEND2
1035      case STB_TEXTEDIT_K_LINEEND2:
1036#endif
1037      case STB_TEXTEDIT_K_LINEEND: {
1038         int n = STB_TEXTEDIT_STRINGLEN(str);
1039         stb_textedit_clamp(str, state);
1040         stb_textedit_move_to_first(state);
1041         if (state->single_line)
1042             state->cursor = n;
1043         else while (state->cursor < n && STB_TEXTEDIT_GETCHAR(str, state->cursor) != STB_TEXTEDIT_NEWLINE)
1044             ++state->cursor;
1045         state->has_preferred_x = 0;
1046         break;
1047      }
1048
1049#ifdef STB_TEXTEDIT_K_LINESTART2
1050      case STB_TEXTEDIT_K_LINESTART2 | STB_TEXTEDIT_K_SHIFT:
1051#endif
1052      case STB_TEXTEDIT_K_LINESTART | STB_TEXTEDIT_K_SHIFT:
1053         stb_textedit_clamp(str, state);
1054         stb_textedit_prep_selection_at_cursor(state);
1055         if (state->single_line)
1056            state->cursor = 0;
1057         else while (state->cursor > 0 && STB_TEXTEDIT_GETCHAR(str, state->cursor-1) != STB_TEXTEDIT_NEWLINE)
1058            --state->cursor;
1059         state->select_end = state->cursor;
1060         state->has_preferred_x = 0;
1061         break;
1062
1063#ifdef STB_TEXTEDIT_K_LINEEND2
1064      case STB_TEXTEDIT_K_LINEEND2 | STB_TEXTEDIT_K_SHIFT:
1065#endif
1066      case STB_TEXTEDIT_K_LINEEND | STB_TEXTEDIT_K_SHIFT: {
1067         int n = STB_TEXTEDIT_STRINGLEN(str);
1068         stb_textedit_clamp(str, state);
1069         stb_textedit_prep_selection_at_cursor(state);
1070         if (state->single_line)
1071             state->cursor = n;
1072         else while (state->cursor < n && STB_TEXTEDIT_GETCHAR(str, state->cursor) != STB_TEXTEDIT_NEWLINE)
1073            ++state->cursor;
1074         state->select_end = state->cursor;
1075         state->has_preferred_x = 0;
1076         break;
1077      }
1078
1079// @TODO:
1080//    STB_TEXTEDIT_K_PGUP      - move cursor up a page
1081//    STB_TEXTEDIT_K_PGDOWN    - move cursor down a page
1082   }
1083}
1084
1085/////////////////////////////////////////////////////////////////////////////
1086//
1087//      Undo processing
1088//
1089// @OPTIMIZE: the undo/redo buffer should be circular
1090
1091static void stb_textedit_flush_redo(StbUndoState *state)
1092{
1093   state->redo_point = STB_TEXTEDIT_UNDOSTATECOUNT;
1094   state->redo_char_point = STB_TEXTEDIT_UNDOCHARCOUNT;
1095}
1096
1097// discard the oldest entry in the undo list
1098static void stb_textedit_discard_undo(StbUndoState *state)
1099{
1100   if (state->undo_point > 0) {
1101      // if the 0th undo state has characters, clean those up
1102      if (state->undo_rec[0].char_storage >= 0) {
1103         int n = state->undo_rec[0].insert_length, i;
1104         // delete n characters from all other records
1105         state->undo_char_point -= n;
1106         STB_TEXTEDIT_memmove(state->undo_char, state->undo_char + n, (size_t) (state->undo_char_point*sizeof(STB_TEXTEDIT_CHARTYPE)));
1107         for (i=0; i < state->undo_point; ++i)
1108            if (state->undo_rec[i].char_storage >= 0)
1109               state->undo_rec[i].char_storage -= n; // @OPTIMIZE: get rid of char_storage and infer it
1110      }
1111      --state->undo_point;
1112      STB_TEXTEDIT_memmove(state->undo_rec, state->undo_rec+1, (size_t) (state->undo_point*sizeof(state->undo_rec[0])));
1113   }
1114}
1115
1116// discard the oldest entry in the redo list--it's bad if this
1117// ever happens, but because undo & redo have to store the actual
1118// characters in different cases, the redo character buffer can
1119// fill up even though the undo buffer didn't
1120static void stb_textedit_discard_redo(StbUndoState *state)
1121{
1122   int k = STB_TEXTEDIT_UNDOSTATECOUNT-1;
1123
1124   if (state->redo_point <= k) {
1125      // if the k'th undo state has characters, clean those up
1126      if (state->undo_rec[k].char_storage >= 0) {
1127         int n = state->undo_rec[k].insert_length, i;
1128         // move the remaining redo character data to the end of the buffer
1129         state->redo_char_point += n;
1130         STB_TEXTEDIT_memmove(state->undo_char + state->redo_char_point, state->undo_char + state->redo_char_point-n, (size_t) ((STB_TEXTEDIT_UNDOCHARCOUNT - state->redo_char_point)*sizeof(STB_TEXTEDIT_CHARTYPE)));
1131         // adjust the position of all the other records to account for above memmove
1132         for (i=state->redo_point; i < k; ++i)
1133            if (state->undo_rec[i].char_storage >= 0)
1134               state->undo_rec[i].char_storage += n;
1135      }
1136      // now move all the redo records towards the end of the buffer; the first one is at 'redo_point'
1137      // {DEAR IMGUI]
1138      size_t move_size = (size_t)((STB_TEXTEDIT_UNDOSTATECOUNT - state->redo_point - 1) * sizeof(state->undo_rec[0]));
1139      const char* buf_begin = (char*)state->undo_rec; (void)buf_begin;
1140      const char* buf_end   = (char*)state->undo_rec + sizeof(state->undo_rec); (void)buf_end;
1141      IM_ASSERT(((char*)(state->undo_rec + state->redo_point)) >= buf_begin);
1142      IM_ASSERT(((char*)(state->undo_rec + state->redo_point + 1) + move_size) <= buf_end);
1143      STB_TEXTEDIT_memmove(state->undo_rec + state->redo_point+1, state->undo_rec + state->redo_point, move_size);
1144
1145      // now move redo_point to point to the new one
1146      ++state->redo_point;
1147   }
1148}
1149
1150static StbUndoRecord *stb_text_create_undo_record(StbUndoState *state, int numchars)
1151{
1152   // any time we create a new undo record, we discard redo
1153   stb_textedit_flush_redo(state);
1154
1155   // if we have no free records, we have to make room, by sliding the
1156   // existing records down
1157   if (state->undo_point == STB_TEXTEDIT_UNDOSTATECOUNT)
1158      stb_textedit_discard_undo(state);
1159
1160   // if the characters to store won't possibly fit in the buffer, we can't undo
1161   if (numchars > STB_TEXTEDIT_UNDOCHARCOUNT) {
1162      state->undo_point = 0;
1163      state->undo_char_point = 0;
1164      return NULL;
1165   }
1166
1167   // if we don't have enough free characters in the buffer, we have to make room
1168   while (state->undo_char_point + numchars > STB_TEXTEDIT_UNDOCHARCOUNT)
1169      stb_textedit_discard_undo(state);
1170
1171   return &state->undo_rec[state->undo_point++];
1172}
1173
1174static STB_TEXTEDIT_CHARTYPE *stb_text_createundo(StbUndoState *state, int pos, int insert_len, int delete_len)
1175{
1176   StbUndoRecord *r = stb_text_create_undo_record(state, insert_len);
1177   if (r == NULL)
1178      return NULL;
1179
1180   r->where = pos;
1181   r->insert_length = (STB_TEXTEDIT_POSITIONTYPE) insert_len;
1182   r->delete_length = (STB_TEXTEDIT_POSITIONTYPE) delete_len;
1183
1184   if (insert_len == 0) {
1185      r->char_storage = -1;
1186      return NULL;
1187   } else {
1188      r->char_storage = state->undo_char_point;
1189      state->undo_char_point += insert_len;
1190      return &state->undo_char[r->char_storage];
1191   }
1192}
1193
1194static void stb_text_undo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state)
1195{
1196   StbUndoState *s = &state->undostate;
1197   StbUndoRecord u, *r;
1198   if (s->undo_point == 0)
1199      return;
1200
1201   // we need to do two things: apply the undo record, and create a redo record
1202   u = s->undo_rec[s->undo_point-1];
1203   r = &s->undo_rec[s->redo_point-1];
1204   r->char_storage = -1;
1205
1206   r->insert_length = u.delete_length;
1207   r->delete_length = u.insert_length;
1208   r->where = u.where;
1209
1210   if (u.delete_length) {
1211      // if the undo record says to delete characters, then the redo record will
1212      // need to re-insert the characters that get deleted, so we need to store
1213      // them.
1214
1215      // there are three cases:
1216      //    there's enough room to store the characters
1217      //    characters stored for *redoing* don't leave room for redo
1218      //    characters stored for *undoing* don't leave room for redo
1219      // if the last is true, we have to bail
1220
1221      if (s->undo_char_point + u.delete_length >= STB_TEXTEDIT_UNDOCHARCOUNT) {
1222         // the undo records take up too much character space; there's no space to store the redo characters
1223         r->insert_length = 0;
1224      } else {
1225         int i;
1226
1227         // there's definitely room to store the characters eventually
1228         while (s->undo_char_point + u.delete_length > s->redo_char_point) {
1229            // should never happen:
1230            if (s->redo_point == STB_TEXTEDIT_UNDOSTATECOUNT)
1231               return;
1232            // there's currently not enough room, so discard a redo record
1233            stb_textedit_discard_redo(s);
1234         }
1235         r = &s->undo_rec[s->redo_point-1];
1236
1237         r->char_storage = s->redo_char_point - u.delete_length;
1238         s->redo_char_point = s->redo_char_point - u.delete_length;
1239
1240         // now save the characters
1241         for (i=0; i < u.delete_length; ++i)
1242            s->undo_char[r->char_storage + i] = STB_TEXTEDIT_GETCHAR(str, u.where + i);
1243      }
1244
1245      // now we can carry out the deletion
1246      STB_TEXTEDIT_DELETECHARS(str, u.where, u.delete_length);
1247   }
1248
1249   // check type of recorded action:
1250   if (u.insert_length) {
1251      // easy case: was a deletion, so we need to insert n characters
1252      STB_TEXTEDIT_INSERTCHARS(str, u.where, &s->undo_char[u.char_storage], u.insert_length);
1253      s->undo_char_point -= u.insert_length;
1254   }
1255
1256   state->cursor = u.where + u.insert_length;
1257
1258   s->undo_point--;
1259   s->redo_point--;
1260}
1261
1262static void stb_text_redo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state)
1263{
1264   StbUndoState *s = &state->undostate;
1265   StbUndoRecord *u, r;
1266   if (s->redo_point == STB_TEXTEDIT_UNDOSTATECOUNT)
1267      return;
1268
1269   // we need to do two things: apply the redo record, and create an undo record
1270   u = &s->undo_rec[s->undo_point];
1271   r = s->undo_rec[s->redo_point];
1272
1273   // we KNOW there must be room for the undo record, because the redo record
1274   // was derived from an undo record
1275
1276   u->delete_length = r.insert_length;
1277   u->insert_length = r.delete_length;
1278   u->where = r.where;
1279   u->char_storage = -1;
1280
1281   if (r.delete_length) {
1282      // the redo record requires us to delete characters, so the undo record
1283      // needs to store the characters
1284
1285      if (s->undo_char_point + u->insert_length > s->redo_char_point) {
1286         u->insert_length = 0;
1287         u->delete_length = 0;
1288      } else {
1289         int i;
1290         u->char_storage = s->undo_char_point;
1291         s->undo_char_point = s->undo_char_point + u->insert_length;
1292
1293         // now save the characters
1294         for (i=0; i < u->insert_length; ++i)
1295            s->undo_char[u->char_storage + i] = STB_TEXTEDIT_GETCHAR(str, u->where + i);
1296      }
1297
1298      STB_TEXTEDIT_DELETECHARS(str, r.where, r.delete_length);
1299   }
1300
1301   if (r.insert_length) {
1302      // easy case: need to insert n characters
1303      STB_TEXTEDIT_INSERTCHARS(str, r.where, &s->undo_char[r.char_storage], r.insert_length);
1304      s->redo_char_point += r.insert_length;
1305   }
1306
1307   state->cursor = r.where + r.insert_length;
1308
1309   s->undo_point++;
1310   s->redo_point++;
1311}
1312
1313static void stb_text_makeundo_insert(STB_TexteditState *state, int where, int length)
1314{
1315   stb_text_createundo(&state->undostate, where, 0, length);
1316}
1317
1318static void stb_text_makeundo_delete(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int length)
1319{
1320   int i;
1321   STB_TEXTEDIT_CHARTYPE *p = stb_text_createundo(&state->undostate, where, length, 0);
1322   if (p) {
1323      for (i=0; i < length; ++i)
1324         p[i] = STB_TEXTEDIT_GETCHAR(str, where+i);
1325   }
1326}
1327
1328static void stb_text_makeundo_replace(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int old_length, int new_length)
1329{
1330   int i;
1331   STB_TEXTEDIT_CHARTYPE *p = stb_text_createundo(&state->undostate, where, old_length, new_length);
1332   if (p) {
1333      for (i=0; i < old_length; ++i)
1334         p[i] = STB_TEXTEDIT_GETCHAR(str, where+i);
1335   }
1336}
1337
1338// reset the state to default
1339static void stb_textedit_clear_state(STB_TexteditState *state, int is_single_line)
1340{
1341   state->undostate.undo_point = 0;
1342   state->undostate.undo_char_point = 0;
1343   state->undostate.redo_point = STB_TEXTEDIT_UNDOSTATECOUNT;
1344   state->undostate.redo_char_point = STB_TEXTEDIT_UNDOCHARCOUNT;
1345   state->select_end = state->select_start = 0;
1346   state->cursor = 0;
1347   state->has_preferred_x = 0;
1348   state->preferred_x = 0;
1349   state->cursor_at_end_of_line = 0;
1350   state->initialized = 1;
1351   state->single_line = (unsigned char) is_single_line;
1352   state->insert_mode = 0;
1353}
1354
1355// API initialize
1356static void stb_textedit_initialize_state(STB_TexteditState *state, int is_single_line)
1357{
1358   stb_textedit_clear_state(state, is_single_line);
1359}
1360
1361#if defined(__GNUC__) || defined(__clang__)
1362#pragma GCC diagnostic push
1363#pragma GCC diagnostic ignored "-Wcast-qual"
1364#endif
1365
1366static int stb_textedit_paste(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_CHARTYPE const *ctext, int len)
1367{
1368   return stb_textedit_paste_internal(str, state, (STB_TEXTEDIT_CHARTYPE *) ctext, len);
1369}
1370
1371#if defined(__GNUC__) || defined(__clang__)
1372#pragma GCC diagnostic pop
1373#endif
1374
1375#endif//STB_TEXTEDIT_IMPLEMENTATION
1376
1377/*
1378------------------------------------------------------------------------------
1379This software is available under 2 licenses -- choose whichever you prefer.
1380------------------------------------------------------------------------------
1381ALTERNATIVE A - MIT License
1382Copyright (c) 2017 Sean Barrett
1383Permission is hereby granted, free of charge, to any person obtaining a copy of
1384this software and associated documentation files (the "Software"), to deal in
1385the Software without restriction, including without limitation the rights to
1386use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
1387of the Software, and to permit persons to whom the Software is furnished to do
1388so, subject to the following conditions:
1389The above copyright notice and this permission notice shall be included in all
1390copies or substantial portions of the Software.
1391THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1392IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1393FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1394AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1395LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
1396OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
1397SOFTWARE.
1398------------------------------------------------------------------------------
1399ALTERNATIVE B - Public Domain (www.unlicense.org)
1400This is free and unencumbered software released into the public domain.
1401Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
1402software, either in source code form or as a compiled binary, for any purpose,
1403commercial or non-commercial, and by any means.
1404In jurisdictions that recognize copyright laws, the author or authors of this
1405software dedicate any and all copyright interest in the software to the public
1406domain. We make this dedication for the benefit of the public at large and to
1407the detriment of our heirs and successors. We intend this dedication to be an
1408overt act of relinquishment in perpetuity of all present and future rights to
1409this software under copyright law.
1410THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1411IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1412FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1413AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
1414ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
1415WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1416------------------------------------------------------------------------------
1417*/
1418