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

source code of imgui/imstb_textedit.h