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