-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy patheditor.cpp
1793 lines (1575 loc) · 85.2 KB
/
editor.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#ifndef IMGUI_DEFINE_MATH_OPERATORS
#define IMGUI_DEFINE_MATH_OPERATORS
#endif
#include "imgui.h"
#include "imgui_internal.h"
#include "editor.h"
#include <algorithm>
#include "imfilebrowser/imfilebrowser.h"
namespace LLMStb {
static bool ReplaceTail(LLMTextState* obj, int pos, const char* new_text, int new_text_len);
}
void CEditor::Init()
{
llmst.llm.init();
llmst.Ctx = ImGui::GetCurrentContext();
llmst.llm.notify_new_predictions = [this]() { llmst.invalidate_predictions=true; };
llmst.llm.notify_change_tail = [this](int n, std::string u) { LLMStb::ReplaceTail(&llmst, n, u.c_str(), u.length() ); };
buf = new char[40960];
buf[0]=0;
}
void CEditor::SettingsWindow()
{
if(p_settings) {
ImGui::SetNextWindowSize(ImVec2(300, 300), ImGuiCond_FirstUseEver);
if(ImGui::Begin("Settings##sw", &p_settings)) {
ImGui::PushItemWidth(100);
ImGui::InputInt("Snapshot interval", &llmst.llm.snapshot_freq);
ImGui::SetItemTooltip("LLM state will be stored at this interval. Lower values make predictions faster, but use more RAM.");
ImGui::InputInt("Main prediction depth", &llmst.llm.predict_main);
ImGui::SetItemTooltip("How many tokens to predict for the currently selected branch");
ImGui::InputInt("Side prediction depth", &llmst.llm.predict_alt);
ImGui::SetItemTooltip("How many tokens to predict for alternative branches");
ImGui::PopItemWidth();
ImGui::ColorEdit3("Logit color", &c_highlight.Value.x);
ImGui::SetItemTooltip("Color to highlight token logits in");
ImGui::Separator();
static ImGui::FileBrowser fileDialog;
fileDialog.SetTitle("Select GGUF model");
fileDialog.SetTypeFilters({".gguf"});
ImGui::Text("Loaded model: "); ImGui::SameLine();
if(llmst.llm.model) {
if(ImGui::Button(llmst.llm.model_fn.c_str()))
fileDialog.Open();
fileDialog.Display();
if(fileDialog.HasSelected()) {
llmst.llm.load_model(fileDialog.GetSelected().string().c_str());
fileDialog.ClearSelected();
}
ImGui::Text("Type: %s %s",llmst.llm.model_arch.c_str(), llmst.llm.model_size.c_str());
ImGui::Text("Params: %lld Layers: %d Heads: %d", llama_model_n_params(llmst.llm.model), llama_model_n_layer(llmst.llm.model), llama_model_n_head(llmst.llm.model));
bool open = ImGui::CollapsingHeader("Model metadata");
if(open) {
for(int i=0; i<llama_model_meta_count(llmst.llm.model); i++) {
char k_buf[256], v_buf[256];
llama_model_meta_key_by_index(llmst.llm.model, i, k_buf, 256);
llama_model_meta_val_str_by_index(llmst.llm.model, i, v_buf, 256);
ImGui::Text("%s = %s", k_buf, v_buf);
}
}
} else {
if(ImGui::Button("None. Please select."))
fileDialog.Open();
fileDialog.Display();
if(fileDialog.HasSelected()) {
llmst.llm.load_model(fileDialog.GetSelected().string().c_str());
fileDialog.ClearSelected();
}
}
}
ImGui::End();
}
}
void CEditor::AboutWindow()
{
ImGui::PushOverrideID(ImHashStr("A"));
ImVec2 center = ImGui::GetMainViewport()->GetCenter();
ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
//ImGui::OpenPopup("About Autopen");
if (ImGui::BeginPopupModal("About Autopen", NULL, ImGuiWindowFlags_AlwaysAutoResize))
{
ImGui::Text("Autopen (ver. alpha)\n " __DATE__ " " __TIME__);
ImGui::Spacing();
ImGui::Text("(C) 2024-2025, Matvey Soloviev");
if(ImGui::Button("Ok",ImVec2(-1,0))) ImGui::CloseCurrentPopup();
ImGui::EndPopup();
}
ImGui::PopID();
}
void CEditor::Render()
{
llmst.llm.CheckWork();
ImGui::PushFont(font_ui);
if (ImGui::BeginMainMenuBar())
{
if (ImGui::BeginMenu("File"))
{
ImGui::MenuItem("Load buffer", NULL, false, false);
ImGui::MenuItem("Save buffer", NULL, false, false);
ImGui::Separator();
if(ImGui::MenuItem("Settings", NULL, false, true))
p_settings = true;
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Windows"))
{
ImGui::MenuItem("Work queue", NULL, &p_wqueue);
ImGui::Separator();
if(ImGui::MenuItem("About", NULL, false, true)) {
ImGui::PushOverrideID(ImHashStr("A"));
ImGui::OpenPopup("About Autopen");
ImGui::PopID();
}
ImGui::EndMenu();
}
AboutWindow();
ImGui::EndMainMenuBar();
}
ImGuiWindowFlags flags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoBringToFrontOnFocus;
const ImGuiViewport* viewport = ImGui::GetMainViewport();
ImGui::SetNextWindowPos(viewport->WorkPos);
ImGui::SetNextWindowSize(viewport->WorkSize);
bool p_open = true;
ImGui::Begin("Editor", &p_open, flags);
if(llmst.llm.model) {
ImGui::PushFont(font_editor);
EditorWidget("##source", "", buf, 40960, ImVec2(-FLT_MIN, -20.0), ImGuiInputTextFlags_Multiline | ImGuiInputTextFlags_NoUndoRedo);
ImGui::PopFont();
if(llmst.current_tok) {
ImGui::Text("DEPTH: %3d (+%3d) -- CHILDREN: %d/%d -- LOG.L: %2.3f -- TOP: %2.3f -- TOK: %d '%s'",
llmst.current_tok->depth, llmst.current_tok->base_pos, llmst.current_tok->sel, llmst.current_tok->children.size(), llmst.current_tok->logit, llmst.current_tok->max_logit, llmst.current_tok->tok, llmst.current_tok->str.c_str());
}
ImGui::End();
//ImGui::ShowDemoWindow();
if(p_wqueue) {
ImGui::SetNextWindowSize(ImVec2(300, 300), ImGuiCond_FirstUseEver);
if(ImGui::Begin("Work queue", &p_wqueue)) {
if (ImGui::BeginListBox("##wq", ImVec2(-FLT_MIN, -FLT_MIN)))
{
const char *wl_typenames[3] = { "SCORE ", "PREDICT", "BRANCH " };
for(auto i = llmst.llm.wq_head_invalid?++llmst.llm.wq.begin():llmst.llm.wq.begin(); i!=llmst.llm.wq.end(); ++i) {
ImGui::Text("%s %16lX %d (+%d) '%s'..", wl_typenames[i->wl_type], i->target, i->depth, i->base_pos, i->target->str.c_str());
}
ImGui::EndListBox();
}
}
ImGui::End();
}
} else {
ImGui::End();
p_settings=true;
ImGui::SetNextWindowSize(ImVec2(300, 300), ImGuiCond_Always);
ImGui::SetNextWindowPos(ImVec2(30, 30), ImGuiCond_Always);
}
SettingsWindow();
ImGui::PopFont();
}
/* editor widget */
using namespace ImGui;
static bool InputTextFilterCharacter(ImGuiContext* ctx, unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data, bool input_source_is_clipboard = false);
static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end);
static ImVec2 InputTextCalcTextSize(ImGuiContext* ctx, const char* text_begin, const char* text_end, const char** remaining = NULL, ImVec2* out_offset = NULL, bool stop_on_new_line = false);
namespace LLMStb
{
#undef INCLUDE_IMSTB_TEXTEDIT_H
#include "imstb_textedit.h"
}
// This is only used in the path where the multiline widget is inactivate.
static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end)
{
int line_count = 0;
const char* s = text_begin;
while (true)
{
const char* s_eol = strchr(s, '\n');
line_count++;
if (s_eol == NULL)
{
s = s + strlen(s);
break;
}
s = s_eol + 1;
}
*out_text_end = s;
return line_count;
}
// FIXME: Ideally we'd share code with ImFont::CalcTextSizeA()
static ImVec2 InputTextCalcTextSize(ImGuiContext* ctx, const char* text_begin, const char* text_end, const char** remaining, ImVec2* out_offset, bool stop_on_new_line)
{
ImGuiContext& g = *ctx;
ImFont* font = g.Font;
const float line_height = g.FontSize;
const float scale = line_height / font->FontSize;
ImVec2 text_size = ImVec2(0, 0);
float line_width = 0.0f;
const char* s = text_begin;
while (s < text_end)
{
unsigned int c = (unsigned int)*s;
if (c < 0x80)
s += 1;
else
s += ImTextCharFromUtf8(&c, s, text_end);
if (c == '\n')
{
text_size.x = ImMax(text_size.x, line_width);
text_size.y += line_height;
line_width = 0.0f;
if (stop_on_new_line)
break;
continue;
}
if (c == '\r')
continue;
const float char_width = ((int)c < font->IndexAdvanceX.Size ? font->IndexAdvanceX.Data[c] : font->FallbackAdvanceX) * scale;
line_width += char_width;
}
if (text_size.x < line_width)
text_size.x = line_width;
if (out_offset)
*out_offset = ImVec2(line_width, text_size.y + line_height); // offset allow for the possibility of sitting after a trailing \n
if (line_width > 0 || text_size.y == 0.0f) // whereas size.y will ignore the trailing \n
text_size.y += line_height;
if (remaining)
*remaining = s;
return text_size;
}
namespace LLMStb
{
#undef IMSTB_TEXTEDIT_STRING
#define IMSTB_TEXTEDIT_STRING LLMTextState
static int STB_TEXTEDIT_STRINGLEN(const LLMTextState* obj) { return obj->TextLen; }
static char STB_TEXTEDIT_GETCHAR(const LLMTextState* obj, int idx) { IM_ASSERT(idx <= obj->TextLen); return obj->TextSrc[idx]; }
static float STB_TEXTEDIT_GETWIDTH(LLMTextState* obj, int line_start_idx, int char_idx) { unsigned int c; ImTextCharFromUtf8(&c, obj->TextSrc + line_start_idx + char_idx, obj->TextSrc + obj->TextLen); if ((ImWchar)c == '\n') return IMSTB_TEXTEDIT_GETWIDTH_NEWLINE; ImGuiContext& g = *obj->Ctx; return g.Font->GetCharAdvance((ImWchar)c) * g.FontScale; }
static char STB_TEXTEDIT_NEWLINE = '\n';
static void STB_TEXTEDIT_LAYOUTROW(StbTexteditRow* r, LLMTextState* obj, int line_start_idx)
{
const char* text = obj->TextSrc;
const char* text_remaining = NULL;
const ImVec2 size = InputTextCalcTextSize(obj->Ctx, text + line_start_idx, text + obj->TextLen, &text_remaining, NULL, true);
r->x0 = 0.0f;
r->x1 = size.x;
r->baseline_y_delta = size.y;
r->ymin = 0.0f;
r->ymax = size.y;
r->num_chars = (int)(text_remaining - (text + line_start_idx));
}
#define IMSTB_TEXTEDIT_GETNEXTCHARINDEX IMSTB_TEXTEDIT_GETNEXTCHARINDEX_IMPL
#define IMSTB_TEXTEDIT_GETPREVCHARINDEX IMSTB_TEXTEDIT_GETPREVCHARINDEX_IMPL
static int IMSTB_TEXTEDIT_GETNEXTCHARINDEX_IMPL(LLMTextState* obj, int idx)
{
if (idx >= obj->TextLen)
return obj->TextLen + 1;
unsigned int c;
return idx + ImTextCharFromUtf8(&c, obj->TextSrc + idx, obj->TextSrc + obj->TextLen);
}
static int IMSTB_TEXTEDIT_GETPREVCHARINDEX_IMPL(LLMTextState* obj, int idx)
{
if (idx <= 0)
return -1;
const char* p = ImTextFindPreviousUtf8Codepoint(obj->TextSrc, obj->TextSrc + idx);
return (int)(p - obj->TextSrc);
}
static bool ImCharIsSeparatorW(unsigned int c)
{
static const unsigned int separator_list[] =
{
',', 0x3001, '.', 0x3002, ';', 0xFF1B, '(', 0xFF08, ')', 0xFF09, '{', 0xFF5B, '}', 0xFF5D,
'[', 0x300C, ']', 0x300D, '|', 0xFF5C, '!', 0xFF01, '\\', 0xFFE5, '/', 0x30FB, 0xFF0F,
'\n', '\r',
};
for (unsigned int separator : separator_list)
if (c == separator)
return true;
return false;
}
static int is_word_boundary_from_right(LLMTextState* obj, int idx)
{
// When ImGuiInputTextFlags_Password is set, we don't want actions such as CTRL+Arrow to leak the fact that underlying data are blanks or separators.
if ((obj->Flags & ImGuiInputTextFlags_Password) || idx <= 0)
return 0;
const char* curr_p = obj->TextSrc + idx;
const char* prev_p = ImTextFindPreviousUtf8Codepoint(obj->TextSrc, curr_p);
unsigned int curr_c; ImTextCharFromUtf8(&curr_c, curr_p, obj->TextSrc + obj->TextLen);
unsigned int prev_c; ImTextCharFromUtf8(&prev_c, prev_p, obj->TextSrc + obj->TextLen);
bool prev_white = ImCharIsBlankW(prev_c);
bool prev_separ = ImCharIsSeparatorW(prev_c);
bool curr_white = ImCharIsBlankW(curr_c);
bool curr_separ = ImCharIsSeparatorW(curr_c);
return ((prev_white || prev_separ) && !(curr_separ || curr_white)) || (curr_separ && !prev_separ);
}
static int is_word_boundary_from_left(LLMTextState* obj, int idx)
{
if ((obj->Flags & ImGuiInputTextFlags_Password) || idx <= 0)
return 0;
const char* curr_p = obj->TextSrc + idx;
const char* prev_p = ImTextFindPreviousUtf8Codepoint(obj->TextSrc, curr_p);
unsigned int prev_c; ImTextCharFromUtf8(&prev_c, curr_p, obj->TextSrc + obj->TextLen);
unsigned int curr_c; ImTextCharFromUtf8(&curr_c, prev_p, obj->TextSrc + obj->TextLen);
bool prev_white = ImCharIsBlankW(prev_c);
bool prev_separ = ImCharIsSeparatorW(prev_c);
bool curr_white = ImCharIsBlankW(curr_c);
bool curr_separ = ImCharIsSeparatorW(curr_c);
return ((prev_white) && !(curr_separ || curr_white)) || (curr_separ && !prev_separ);
}
static int STB_TEXTEDIT_MOVEWORDLEFT_IMPL(LLMTextState* obj, int idx)
{
idx = IMSTB_TEXTEDIT_GETPREVCHARINDEX(obj, idx);
while (idx >= 0 && !is_word_boundary_from_right(obj, idx))
idx = IMSTB_TEXTEDIT_GETPREVCHARINDEX(obj, idx);
return idx < 0 ? 0 : idx;
}
static int STB_TEXTEDIT_MOVEWORDRIGHT_MAC(LLMTextState* obj, int idx)
{
int len = obj->TextLen;
idx = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(obj, idx);
while (idx < len && !is_word_boundary_from_left(obj, idx))
idx = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(obj, idx);
return idx > len ? len : idx;
}
static int STB_TEXTEDIT_MOVEWORDRIGHT_WIN(LLMTextState* obj, int idx)
{
idx = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(obj, idx);
int len = obj->TextLen;
while (idx < len && !is_word_boundary_from_right(obj, idx))
idx = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(obj, idx);
return idx > len ? len : idx;
}
static int STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(LLMTextState* obj, int idx) { ImGuiContext& g = *obj->Ctx; if (g.IO.ConfigMacOSXBehaviors) return STB_TEXTEDIT_MOVEWORDRIGHT_MAC(obj, idx); else return STB_TEXTEDIT_MOVEWORDRIGHT_WIN(obj, idx); }
#define STB_TEXTEDIT_MOVEWORDLEFT STB_TEXTEDIT_MOVEWORDLEFT_IMPL // They need to be #define for stb_textedit.h
#define STB_TEXTEDIT_MOVEWORDRIGHT STB_TEXTEDIT_MOVEWORDRIGHT_IMPL
static void STB_TEXTEDIT_DELETECHARS(LLMTextState* obj, int pos, int n)
{
// Offset remaining text (+ copy zero terminator)
IM_ASSERT(obj->TextSrc == obj->TextA.Data);
char* dst = obj->TextA.Data + pos;
char* src = obj->TextA.Data + pos + n;
memmove(dst, src, obj->TextLen - n - pos + 1);
// notify LLM
obj->llm.erase(pos, pos+n);
obj->llm.req_alts_at_pos(pos);
obj->last_tok = NULL;
obj->Edited = true;
obj->TextLen -= n;
}
static bool STB_TEXTEDIT_INSERTCHARS(LLMTextState* obj, int pos, const char* new_text, int new_text_len)
{
const bool is_resizable = (obj->Flags & ImGuiInputTextFlags_CallbackResize) != 0;
const int text_len = obj->TextLen;
IM_ASSERT(pos <= text_len);
if (!is_resizable && (new_text_len + obj->TextLen + 1 > obj->BufCapacity))
return false;
// Grow internal buffer if needed
IM_ASSERT(obj->TextSrc == obj->TextA.Data);
if (new_text_len + text_len + 1 > obj->TextA.Size)
{
if (!is_resizable)
return false;
obj->TextA.resize(text_len + ImClamp(new_text_len, 32, ImMax(256, new_text_len)) + 1);
obj->TextSrc = obj->TextA.Data;
}
char* text = obj->TextA.Data;
if (pos != text_len)
memmove(text + pos + new_text_len, text + pos, (size_t)(text_len - pos));
memcpy(text + pos, new_text, (size_t)new_text_len);
// notify LLM
obj->llm.insert(pos, new_text);
obj->llm.req_alts_at_pos(pos + new_text_len);
obj->Edited = true;
obj->TextLen += new_text_len;
obj->TextA[obj->TextLen] = '\0';
return true;
}
static bool ReplaceTail(LLMTextState* obj, int pos, const char* new_text, int new_text_len)
{
const bool is_resizable = (obj->Flags & ImGuiInputTextFlags_CallbackResize) != 0;
const int text_len = obj->TextLen;
IM_ASSERT(pos <= text_len);
if (!is_resizable && (new_text_len + obj->TextLen + 1 > obj->BufCapacity))
return false;
// Grow internal buffer if needed
IM_ASSERT(obj->TextSrc == obj->TextA.Data);
if (new_text_len + pos + 1 > obj->TextA.Size)
{
if (!is_resizable)
return false;
obj->TextA.resize(pos + ImMin(new_text_len, 32) + 1);
obj->TextSrc = obj->TextA.Data;
}
char* text = obj->TextA.Data;
memcpy(text + pos, new_text, (size_t)new_text_len);
obj->Edited = true;
obj->TextLen = pos + new_text_len;
obj->TextA[obj->TextLen] = '\0';
return true;
}
// We don't use an enum so we can build even with conflicting symbols (if another user of stb_textedit.h leak their STB_TEXTEDIT_K_* symbols)
#define STB_TEXTEDIT_K_LEFT 0x200000 // keyboard input to move cursor left
#define STB_TEXTEDIT_K_RIGHT 0x200001 // keyboard input to move cursor right
#define STB_TEXTEDIT_K_UP 0x200002 // keyboard input to move cursor up
#define STB_TEXTEDIT_K_DOWN 0x200003 // keyboard input to move cursor down
#define STB_TEXTEDIT_K_LINESTART 0x200004 // keyboard input to move cursor to start of line
#define STB_TEXTEDIT_K_LINEEND 0x200005 // keyboard input to move cursor to end of line
#define STB_TEXTEDIT_K_TEXTSTART 0x200006 // keyboard input to move cursor to start of text
#define STB_TEXTEDIT_K_TEXTEND 0x200007 // keyboard input to move cursor to end of text
#define STB_TEXTEDIT_K_DELETE 0x200008 // keyboard input to delete selection or character under cursor
#define STB_TEXTEDIT_K_BACKSPACE 0x200009 // keyboard input to delete selection or character left of cursor
#define STB_TEXTEDIT_K_UNDO 0x20000A // keyboard input to perform undo
#define STB_TEXTEDIT_K_REDO 0x20000B // keyboard input to perform redo
#define STB_TEXTEDIT_K_WORDLEFT 0x20000C // keyboard input to move cursor left one word
#define STB_TEXTEDIT_K_WORDRIGHT 0x20000D // keyboard input to move cursor right one word
#define STB_TEXTEDIT_K_PGUP 0x20000E // keyboard input to move cursor up a page
#define STB_TEXTEDIT_K_PGDOWN 0x20000F // keyboard input to move cursor down a page
#define STB_TEXTEDIT_K_SHIFT 0x400000
#define IMSTB_TEXTEDIT_IMPLEMENTATION
#define IMSTB_TEXTEDIT_memmove memmove
#include "imstb_textedit.h"
// stb_textedit internally allows for a single undo record to do addition and deletion, but somehow, calling
// the stb_textedit_paste() function creates two separate records, so we perform it manually. (FIXME: Report to nothings/stb?)
static void stb_textedit_replace(LLMTextState* str, STB_TexteditState* state, const IMSTB_TEXTEDIT_CHARTYPE* text, int text_len)
{
stb_text_makeundo_replace(str, state, 0, str->TextLen, text_len);
LLMStb::STB_TEXTEDIT_DELETECHARS(str, 0, str->TextLen);
state->cursor = state->select_start = state->select_end = 0;
if (text_len <= 0)
return;
if (LLMStb::STB_TEXTEDIT_INSERTCHARS(str, 0, text, text_len))
{
state->cursor = state->select_start = state->select_end = text_len;
state->has_preferred_x = 0;
return;
}
IM_ASSERT(0); // Failed to insert character, normally shouldn't happen because of how we currently use stb_textedit_replace()
}
} // namespace LLMStb
LLMTextState::LLMTextState()
{
//memset(this, 0, sizeof(*this)); // -- this destroys our root node, so let's not
Stb = IM_NEW(LLMStbTexteditState);
memset(Stb, 0, sizeof(*Stb));
}
LLMTextState::~LLMTextState()
{
IM_DELETE(Stb);
}
void LLMTextState::OnKeyPressed(int key)
{
stb_textedit_key(this, Stb, key);
CursorFollow = true;
CursorAnimReset();
}
void LLMTextState::OnCharPressed(unsigned int c)
{
// Convert the key to a UTF8 byte sequence.
// The changes we had to make to stb_textedit_key made it very much UTF-8 specific which is not too great.
char utf8[5];
ImTextCharToUtf8(utf8, c);
stb_textedit_text(this, Stb, utf8, (int)strlen(utf8));
CursorFollow = true;
CursorAnimReset();
}
// Those functions are not inlined in imgui_internal.h, allowing us to hide ImStbTexteditState from that header.
void LLMTextState::CursorAnimReset() { CursorAnim = -0.30f; } // After a user-input the cursor stays on for a while without blinking
void LLMTextState::CursorClamp() { Stb->cursor = ImMin(Stb->cursor, TextLen); Stb->select_start = ImMin(Stb->select_start, TextLen); Stb->select_end = ImMin(Stb->select_end, TextLen); }
bool LLMTextState::HasSelection() const { return Stb->select_start != Stb->select_end; }
void LLMTextState::ClearSelection() { Stb->select_start = Stb->select_end = Stb->cursor; }
int LLMTextState::GetCursorPos() const { return Stb->cursor; }
int LLMTextState::GetSelectionStart() const { return Stb->select_start; }
int LLMTextState::GetSelectionEnd() const { return Stb->select_end; }
void LLMTextState::SelectAll() { Stb->select_start = 0; Stb->cursor = Stb->select_end = TextLen; Stb->has_preferred_x = 0; }
void LLMTextState::ReloadUserBufAndSelectAll() { WantReloadUserBuf = true; ReloadSelectionStart = 0; ReloadSelectionEnd = INT_MAX; }
void LLMTextState::ReloadUserBufAndKeepSelection() { WantReloadUserBuf = true; ReloadSelectionStart = Stb->select_start; ReloadSelectionEnd = Stb->select_end; }
void LLMTextState::ReloadUserBufAndMoveToEnd() { WantReloadUserBuf = true; ReloadSelectionStart = ReloadSelectionEnd = INT_MAX; }
static void InputTextReconcileUndoState(LLMTextState* state, const char* old_buf, int old_length, const char* new_buf, int new_length)
{
const int shorter_length = ImMin(old_length, new_length);
int first_diff;
for (first_diff = 0; first_diff < shorter_length; first_diff++)
if (old_buf[first_diff] != new_buf[first_diff])
break;
if (first_diff == old_length && first_diff == new_length)
return;
int old_last_diff = old_length - 1;
int new_last_diff = new_length - 1;
for (; old_last_diff >= first_diff && new_last_diff >= first_diff; old_last_diff--, new_last_diff--)
if (old_buf[old_last_diff] != new_buf[new_last_diff])
break;
const int insert_len = new_last_diff - first_diff + 1;
const int delete_len = old_last_diff - first_diff + 1;
if (insert_len > 0 || delete_len > 0)
if (IMSTB_TEXTEDIT_CHARTYPE* p = LLMStb::stb_text_createundo(&state->Stb->undostate, first_diff, delete_len, insert_len))
for (int i = 0; i < delete_len; i++)
p[i] = old_buf[first_diff + i];
}
// Return false to discard a character.
static bool InputTextFilterCharacter(ImGuiContext* ctx, unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data, bool input_source_is_clipboard)
{
unsigned int c = *p_char;
// Filter non-printable (NB: isprint is unreliable! see #2467)
bool apply_named_filters = true;
if (c < 0x20)
{
bool pass = false;
pass |= (c == '\n') && (flags & ImGuiInputTextFlags_Multiline) != 0; // Note that an Enter KEY will emit \r and be ignored (we poll for KEY in InputText() code)
pass |= (c == '\t') && (flags & ImGuiInputTextFlags_AllowTabInput) != 0;
if (!pass)
return false;
apply_named_filters = false; // Override named filters below so newline and tabs can still be inserted.
}
if (input_source_is_clipboard == false)
{
// We ignore Ascii representation of delete (emitted from Backspace on OSX, see #2578, #2817)
if (c == 127)
return false;
// Filter private Unicode range. GLFW on OSX seems to send private characters for special keys like arrow keys (FIXME)
if (c >= 0xE000 && c <= 0xF8FF)
return false;
}
// Filter Unicode ranges we are not handling in this build
if (c > IM_UNICODE_CODEPOINT_MAX)
return false;
// Generic named filters
if (apply_named_filters && (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase | ImGuiInputTextFlags_CharsNoBlank | ImGuiInputTextFlags_CharsScientific | (ImGuiInputTextFlags)ImGuiInputTextFlags_LocalizeDecimalPoint)))
{
// The libc allows overriding locale, with e.g. 'setlocale(LC_NUMERIC, "de_DE.UTF-8");' which affect the output/input of printf/scanf to use e.g. ',' instead of '.'.
// The standard mandate that programs starts in the "C" locale where the decimal point is '.'.
// We don't really intend to provide widespread support for it, but out of empathy for people stuck with using odd API, we support the bare minimum aka overriding the decimal point.
// Change the default decimal_point with:
// ImGui::GetPlatformIO()->Platform_LocaleDecimalPoint = *localeconv()->decimal_point;
// Users of non-default decimal point (in particular ',') may be affected by word-selection logic (is_word_boundary_from_right/is_word_boundary_from_left) functions.
ImGuiContext& g = *ctx;
const unsigned c_decimal_point = (unsigned int)g.PlatformIO.Platform_LocaleDecimalPoint;
if (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsScientific | (ImGuiInputTextFlags)ImGuiInputTextFlags_LocalizeDecimalPoint))
if (c == '.' || c == ',')
c = c_decimal_point;
// Full-width -> half-width conversion for numeric fields (https://en.wikipedia.org/wiki/Halfwidth_and_Fullwidth_Forms_(Unicode_block)
// While this is mostly convenient, this has the side-effect for uninformed users accidentally inputting full-width characters that they may
// scratch their head as to why it works in numerical fields vs in generic text fields it would require support in the font.
if (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsScientific | ImGuiInputTextFlags_CharsHexadecimal))
if (c >= 0xFF01 && c <= 0xFF5E)
c = c - 0xFF01 + 0x21;
// Allow 0-9 . - + * /
if (flags & ImGuiInputTextFlags_CharsDecimal)
if (!(c >= '0' && c <= '9') && (c != c_decimal_point) && (c != '-') && (c != '+') && (c != '*') && (c != '/'))
return false;
// Allow 0-9 . - + * / e E
if (flags & ImGuiInputTextFlags_CharsScientific)
if (!(c >= '0' && c <= '9') && (c != c_decimal_point) && (c != '-') && (c != '+') && (c != '*') && (c != '/') && (c != 'e') && (c != 'E'))
return false;
// Allow 0-9 a-F A-F
if (flags & ImGuiInputTextFlags_CharsHexadecimal)
if (!(c >= '0' && c <= '9') && !(c >= 'a' && c <= 'f') && !(c >= 'A' && c <= 'F'))
return false;
// Turn a-z into A-Z
if (flags & ImGuiInputTextFlags_CharsUppercase)
if (c >= 'a' && c <= 'z')
c += (unsigned int)('A' - 'a');
if (flags & ImGuiInputTextFlags_CharsNoBlank)
if (ImCharIsBlankW(c))
return false;
*p_char = c;
}
// Custom callback filter
if (flags & ImGuiInputTextFlags_CallbackCharFilter)
{
ImGuiContext& g = *GImGui;
ImGuiInputTextCallbackData callback_data;
callback_data.Ctx = &g;
callback_data.EventFlag = ImGuiInputTextFlags_CallbackCharFilter;
callback_data.EventChar = (ImWchar)c;
callback_data.Flags = flags;
callback_data.UserData = user_data;
if (callback(&callback_data) != 0)
return false;
*p_char = callback_data.EventChar;
if (!callback_data.EventChar)
return false;
}
return true;
}
bool CEditor::EditorWidget(const char* label, const char* hint, char* buf, int buf_size, const ImVec2& size_arg, ImGuiInputTextFlags flags)
{
ImGuiInputTextCallback callback = NULL;
void *callback_user_data = NULL;
ImGuiWindow* window = GetCurrentWindow();
if (window->SkipItems)
return false;
IM_ASSERT(buf != NULL && buf_size >= 0);
IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackHistory) && (flags & ImGuiInputTextFlags_Multiline))); // Can't use both together (they both use up/down keys)
IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackCompletion) && (flags & ImGuiInputTextFlags_AllowTabInput))); // Can't use both together (they both use tab key)
IM_ASSERT(!((flags & ImGuiInputTextFlags_ElideLeft) && (flags & ImGuiInputTextFlags_Multiline))); // Multiline will not work with left-trimming
ImGuiContext& g = *GImGui;
ImGuiIO& io = g.IO;
const ImGuiStyle& style = g.Style;
int line_pitch = g.FontSize; // extra space per line
int line_size = line_pitch + g.FontSize;
const bool RENDER_SELECTION_WHEN_INACTIVE = false;
const bool is_multiline = (flags & ImGuiInputTextFlags_Multiline) != 0;
if (is_multiline) // Open group before calling GetID() because groups tracks id created within their scope (including the scrollbar)
BeginGroup();
const ImGuiID id = window->GetID(label);
const ImVec2 label_size = CalcTextSize(label, NULL, true);
const ImVec2 frame_size = CalcItemSize(size_arg, CalcItemWidth(), (is_multiline ? g.FontSize * 8.0f : label_size.y) + style.FramePadding.y * 2.0f); // Arbitrary default of 8 lines high for multi-line
const ImVec2 total_size = ImVec2(frame_size.x + (label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f), frame_size.y);
const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size);
const ImRect total_bb(frame_bb.Min, frame_bb.Min + total_size);
ImGuiWindow* draw_window = window;
ImVec2 inner_size = frame_size;
ImGuiLastItemData item_data_backup;
if (is_multiline)
{
ImVec2 backup_pos = window->DC.CursorPos;
ItemSize(total_bb, style.FramePadding.y);
if (!ItemAdd(total_bb, id, &frame_bb, ImGuiItemFlags_Inputable))
{
EndGroup();
return false;
}
item_data_backup = g.LastItemData;
window->DC.CursorPos = backup_pos;
// Prevent NavActivation from Tabbing when our widget accepts Tab inputs: this allows cycling through widgets without stopping.
if (g.NavActivateId == id && (g.NavActivateFlags & ImGuiActivateFlags_FromTabbing) && (flags & ImGuiInputTextFlags_AllowTabInput))
g.NavActivateId = 0;
// Prevent NavActivate reactivating in BeginChild() when we are already active.
const ImGuiID backup_activate_id = g.NavActivateId;
if (g.ActiveId == id) // Prevent reactivation
g.NavActivateId = 0;
// We reproduce the contents of BeginChildFrame() in order to provide 'label' so our window internal data are easier to read/debug.
PushStyleColor(ImGuiCol_ChildBg, style.Colors[ImGuiCol_FrameBg]);
PushStyleVar(ImGuiStyleVar_ChildRounding, style.FrameRounding);
PushStyleVar(ImGuiStyleVar_ChildBorderSize, style.FrameBorderSize);
PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); // Ensure no clip rect so mouse hover can reach FramePadding edges
bool child_visible = BeginChildEx(label, id, frame_bb.GetSize(), ImGuiChildFlags_Borders, ImGuiWindowFlags_NoMove);
g.NavActivateId = backup_activate_id;
PopStyleVar(3);
PopStyleColor();
if (!child_visible)
{
EndChild();
EndGroup();
return false;
}
draw_window = g.CurrentWindow; // Child window
draw_window->DC.NavLayersActiveMaskNext |= (1 << draw_window->DC.NavLayerCurrent); // This is to ensure that EndChild() will display a navigation highlight so we can "enter" into it.
draw_window->DC.CursorPos += style.FramePadding;
inner_size.x -= draw_window->ScrollbarSizes.x;
}
else
{
// Support for internal ImGuiInputTextFlags_MergedItem flag, which could be redesigned as an ItemFlags if needed (with test performed in ItemAdd)
ItemSize(total_bb, style.FramePadding.y);
if (!(flags & ImGuiInputTextFlags_MergedItem))
if (!ItemAdd(total_bb, id, &frame_bb, ImGuiItemFlags_Inputable))
return false;
}
// Ensure mouse cursor is set even after switching to keyboard/gamepad mode. May generalize further? (#6417)
bool hovered = ItemHoverable(frame_bb, id, g.LastItemData.ItemFlags | ImGuiItemFlags_NoNavDisableMouseHover);
if (hovered)
SetMouseCursor(ImGuiMouseCursor_TextInput);
if (hovered && g.NavHighlightItemUnderNav)
hovered = false;
// Load our private text state.
LLMTextState* state = &llmst;
// in case we don't find it later
state->current_tok = NULL;
if (g.LastItemData.ItemFlags & ImGuiItemFlags_ReadOnly)
flags |= ImGuiInputTextFlags_ReadOnly;
const bool is_readonly = (flags & ImGuiInputTextFlags_ReadOnly) != 0;
const bool is_password = (flags & ImGuiInputTextFlags_Password) != 0;
const bool is_undoable = (flags & ImGuiInputTextFlags_NoUndoRedo) == 0;
const bool is_resizable = (flags & ImGuiInputTextFlags_CallbackResize) != 0;
if (is_resizable)
IM_ASSERT(callback != NULL); // Must provide a callback if you set the ImGuiInputTextFlags_CallbackResize flag!
const bool input_requested_by_nav = (g.ActiveId != id) && ((g.NavActivateId == id) && ((g.NavActivateFlags & ImGuiActivateFlags_PreferInput) || (g.NavInputSource == ImGuiInputSource_Keyboard)));
const bool user_clicked = hovered && io.MouseClicked[0];
const bool user_scroll_finish = is_multiline && state != NULL && g.ActiveId == 0 && g.ActiveIdPreviousFrame == GetWindowScrollbarID(draw_window, ImGuiAxis_Y);
const bool user_scroll_active = is_multiline && state != NULL && g.ActiveId == GetWindowScrollbarID(draw_window, ImGuiAxis_Y);
bool clear_active_id = false;
bool select_all = false;
float scroll_y = is_multiline ? draw_window->Scroll.y : FLT_MAX;
const bool init_reload_from_user_buf = (state != NULL && state->WantReloadUserBuf);
const bool init_changed_specs = (state != NULL && state->Stb->single_line != !is_multiline); // state != NULL means its our state.
const bool init_make_active = (user_clicked || user_scroll_finish || input_requested_by_nav);
const bool init_state = (init_make_active || user_scroll_active);
if (false && init_reload_from_user_buf)
{
int new_len = (int)strlen(buf);
IM_ASSERT(new_len + 1 <= buf_size && "Is your input buffer properly zero-terminated?");
state->WantReloadUserBuf = false;
InputTextReconcileUndoState(state, state->TextA.Data, state->TextLen, buf, new_len);
state->TextA.resize(buf_size + 1); // we use +1 to make sure that .Data is always pointing to at least an empty string.
state->TextLen = new_len;
memcpy(state->TextA.Data, buf, state->TextLen + 1);
state->Stb->select_start = state->ReloadSelectionStart;
state->Stb->cursor = state->Stb->select_end = state->ReloadSelectionEnd;
state->CursorClamp();
}
else if ((init_state && g.ActiveId != id) || init_changed_specs)
{
// Access state even if we don't own it yet.
state = &llmst;
state->CursorAnimReset();
// Backup state of deactivating item so they'll have a chance to do a write to output buffer on the same frame they report IsItemDeactivatedAfterEdit (#4714)
InputTextDeactivateHook(state->ID);
// Take a copy of the initial buffer value.
// From the moment we focused we are normally ignoring the content of 'buf' (unless we are in read-only mode)
const int buf_len = (int)strlen(buf);
IM_ASSERT(buf_len + 1 <= buf_size && "Is your input buffer properly zero-terminated?");
state->TextToRevertTo.resize(buf_len + 1); // UTF-8. we use +1 to make sure that .Data is always pointing to at least an empty string.
memcpy(state->TextToRevertTo.Data, buf, buf_len + 1);
// Preserve cursor position and undo/redo stack if we come back to same widget
// FIXME: Since we reworked this on 2022/06, may want to differentiate recycle_cursor vs recycle_undostate?
bool recycle_state = (state->ID == id && !init_changed_specs);
if (recycle_state && (state->TextLen != buf_len || (state->TextA.Data == NULL || strncmp(state->TextA.Data, buf, buf_len) != 0)))
recycle_state = false;
// Start edition
state->ID = id;
state->TextLen = buf_len;
if (!is_readonly)
{
state->TextA.resize(buf_size + 1); // we use +1 to make sure that .Data is always pointing to at least an empty string.
memcpy(state->TextA.Data, buf, state->TextLen + 1);
}
// Find initial scroll position for right alignment
state->Scroll = ImVec2(0.0f, 0.0f);
if (flags & ImGuiInputTextFlags_ElideLeft)
state->Scroll.x += ImMax(0.0f, CalcTextSize(buf).x - frame_size.x + style.FramePadding.x * 2.0f);
// Recycle existing cursor/selection/undo stack but clamp position
// Note a single mouse click will override the cursor/position immediately by calling stb_textedit_click handler.
if (recycle_state)
state->CursorClamp();
else
stb_textedit_initialize_state(state->Stb, !is_multiline);
if (!is_multiline)
{
if (flags & ImGuiInputTextFlags_AutoSelectAll)
select_all = true;
if (input_requested_by_nav && (!recycle_state || !(g.NavActivateFlags & ImGuiActivateFlags_TryToPreserveState)))
select_all = true;
if (user_clicked && io.KeyCtrl)
select_all = true;
}
if (flags & ImGuiInputTextFlags_AlwaysOverwrite)
state->Stb->insert_mode = 1; // stb field name is indeed incorrect (see #2863)
}
const bool is_osx = io.ConfigMacOSXBehaviors;
if (g.ActiveId != id && init_make_active)
{
IM_ASSERT(state && state->ID == id);
SetActiveID(id, window);
SetFocusID(id, window);
FocusWindow(window);
}
if (g.ActiveId == id)
{
// Declare some inputs, the other are registered and polled via Shortcut() routing system.
// FIXME: The reason we don't use Shortcut() is we would need a routing flag to specify multiple mods, or to all mods combinaison into individual shortcuts.
const ImGuiKey always_owned_keys[] = { ImGuiKey_LeftArrow, ImGuiKey_RightArrow, ImGuiKey_Enter, ImGuiKey_KeypadEnter, ImGuiKey_Delete, ImGuiKey_Backspace, ImGuiKey_Home, ImGuiKey_End };
for (ImGuiKey key : always_owned_keys)
SetKeyOwner(key, id);
if (user_clicked)
SetKeyOwner(ImGuiKey_MouseLeft, id);
g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right);
if (is_multiline || (flags & ImGuiInputTextFlags_CallbackHistory))
{
g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);
SetKeyOwner(ImGuiKey_UpArrow, id);
SetKeyOwner(ImGuiKey_DownArrow, id);
}
if (is_multiline)
{
SetKeyOwner(ImGuiKey_PageUp, id);
SetKeyOwner(ImGuiKey_PageDown, id);
}
SetKeyOwner(ImGuiMod_Alt, id);
// Expose scroll in a manner that is agnostic to us using a child window
if (is_multiline && state != NULL)
state->Scroll.y = draw_window->Scroll.y;
// Read-only mode always ever read from source buffer. Refresh TextLen when active.
if (is_readonly && state != NULL)
state->TextLen = (int)strlen(buf);
//if (is_readonly && state != NULL)
// state->TextA.clear(); // Uncomment to facilitate debugging, but we otherwise prefer to keep/amortize th allocation.
}
if (state != NULL)
state->TextSrc = is_readonly ? buf : state->TextA.Data;
// We have an edge case if ActiveId was set through another widget (e.g. widget being swapped), clear id immediately (don't wait until the end of the function)
if (g.ActiveId == id && state == NULL)
ClearActiveID();
// Release focus when we click outside
if (g.ActiveId == id && io.MouseClicked[0] && !init_state && !init_make_active) //-V560
clear_active_id = true;
// Lock the decision of whether we are going to take the path displaying the cursor or selection
bool render_cursor = (g.ActiveId == id) || (state && user_scroll_active);
bool render_selection = state && (state->HasSelection() || select_all) && (RENDER_SELECTION_WHEN_INACTIVE || render_cursor);
bool value_changed = false;
bool validated = false;
// Select the buffer to render.
const bool buf_display_from_state = (render_cursor || render_selection || g.ActiveId == id) && !is_readonly && state;
const bool is_displaying_hint = (hint != NULL && (buf_display_from_state ? state->TextA.Data : buf)[0] == 0);
// Process mouse inputs and character inputs
if (g.ActiveId == id)
{
IM_ASSERT(state != NULL);
state->Edited = false;
state->BufCapacity = buf_size;
state->Flags = flags;
// Although we are active we don't prevent mouse from hovering other elements unless we are interacting right now with the widget.
// Down the line we should have a cleaner library-wide concept of Selected vs Active.
g.ActiveIdAllowOverlap = !io.MouseDown[0];
// Edit in progress
const float mouse_x = (io.MousePos.x - frame_bb.Min.x - style.FramePadding.x) + state->Scroll.x;
const float mouse_y = (is_multiline ? (io.MousePos.y - draw_window->DC.CursorPos.y)/2 : (g.FontSize * 0.5f));
if (select_all)
{
state->SelectAll();
state->SelectedAllMouseLock = true;
}
else if (hovered && io.MouseClickedCount[0] >= 2 && !io.KeyShift)
{
LLMStb::stb_textedit_click(state, state->Stb, mouse_x, mouse_y);
const int multiclick_count = (io.MouseClickedCount[0] - 2);
if ((multiclick_count % 2) == 0)
{
// Double-click: Select word
// We always use the "Mac" word advance for double-click select vs CTRL+Right which use the platform dependent variant:
// FIXME: There are likely many ways to improve this behavior, but there's no "right" behavior (depends on use-case, software, OS)
const bool is_bol = (state->Stb->cursor == 0) || LLMStb::STB_TEXTEDIT_GETCHAR(state, state->Stb->cursor - 1) == '\n';
if (STB_TEXT_HAS_SELECTION(state->Stb) || !is_bol)
state->OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT);
//state->OnKeyPressed(STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT);
if (!STB_TEXT_HAS_SELECTION(state->Stb))
LLMStb::stb_textedit_prep_selection_at_cursor(state->Stb);
state->Stb->cursor = LLMStb::STB_TEXTEDIT_MOVEWORDRIGHT_MAC(state, state->Stb->cursor);
state->Stb->select_end = state->Stb->cursor;
LLMStb::stb_textedit_clamp(state, state->Stb);
}
else
{
// Triple-click: Select line
const bool is_eol = LLMStb::STB_TEXTEDIT_GETCHAR(state, state->Stb->cursor) == '\n';
state->OnKeyPressed(STB_TEXTEDIT_K_LINESTART);
state->OnKeyPressed(STB_TEXTEDIT_K_LINEEND | STB_TEXTEDIT_K_SHIFT);
state->OnKeyPressed(STB_TEXTEDIT_K_RIGHT | STB_TEXTEDIT_K_SHIFT);
if (!is_eol && is_multiline)
{
ImSwap(state->Stb->select_start, state->Stb->select_end);
state->Stb->cursor = state->Stb->select_end;
}
state->CursorFollow = false;
}
state->CursorAnimReset();