FTXUI 7.1.0
C++ functional terminal UI.
Loading...
Searching...
No Matches
src/ftxui/component/input.cpp
Go to the documentation of this file.
1// Copyright 2022 Arthur Sonzogni. All rights reserved.
2// Use of this source code is governed by the MIT license that can be found in
3// the LICENSE file.
4#include <algorithm> // for max, min
5#include <cstddef> // for size_t
6#include <cstdint> // for uint32_t
7#include <functional> // for function
8#include <sstream> // for basic_istream, stringstream
9#include <string> // for string, basic_string, operator==, getline
10#include <utility> // for move
11#include <vector> // for vector
12
13#include "ftxui/component/app.hpp" // for Component
14#include "ftxui/component/component.hpp" // for Make, Input
15#include "ftxui/component/component_base.hpp" // for ComponentBase
16#include "ftxui/component/component_options.hpp" // for InputOption
17#include "ftxui/component/event.hpp" // for Event, Event::ArrowDown, Event::ArrowLeft, Event::ArrowLeftCtrl, Event::ArrowRight, Event::ArrowRightCtrl, Event::ArrowUp, Event::Backspace, Event::Delete, Event::End, Event::Home, Event::Return
18#include "ftxui/component/mouse.hpp" // for Mouse, Mouse::Left, Mouse::Pressed
19#include "ftxui/dom/elements.hpp" // for operator|, reflect, text, Element, xflex, hbox, Elements, frame, operator|=, vbox, focus, focusCursorBarBlinking, select
20#include "ftxui/screen/box.hpp" // for Box
21#include "ftxui/screen/string.hpp" // for string_width
22#include "ftxui/screen/string_internal.hpp" // for GlyphNext, GlyphPrevious, WordBreakProperty, EatCodePoint, CodepointToWordBreakProperty, IsFullWidth, WordBreakProperty::ALetter, WordBreakProperty::CR, WordBreakProperty::Double_Quote, WordBreakProperty::Extend, WordBreakProperty::ExtendNumLet, WordBreakProperty::Format, WordBreakProperty::Hebrew_Letter, WordBreakProperty::Katakana, WordBreakProperty::LF, WordBreakProperty::MidLetter, WordBreakProperty::MidNum, WordBreakProperty::MidNumLet, WordBreakProperty::Newline, WordBreakProperty::Numeric, WordBreakProperty::Regional_Indicator, WordBreakProperty::Single_Quote, WordBreakProperty::WSegSpace, WordBreakProperty::ZWJ
23#include "ftxui/screen/util.hpp" // for clamp
24#include "ftxui/util/ref.hpp" // for StringRef, Ref
25
26namespace ftxui {
27
28namespace {
29
30std::vector<std::string> SplitLines(std::string_view input) {
31 std::vector<std::string> output;
32 size_t start = 0;
33 size_t end = input.find('\n');
34 while (end != std::string_view::npos) {
35 output.push_back(std::string(input.substr(start, end - start)));
36 start = end + 1;
37 end = input.find('\n', start);
38 }
39 output.push_back(std::string(input.substr(start)));
40 return output;
41}
42
43size_t GlyphWidth(std::string_view input, size_t iter) {
44 uint32_t ucs = 0;
45 if (!EatCodePoint(input, iter, &iter, &ucs)) {
46 return 0;
47 }
48 if (IsFullWidth(ucs)) {
49 return 2;
50 }
51 return 1;
52}
53
54bool IsWordCodePoint(uint32_t codepoint) {
55 switch (CodepointToWordBreakProperty(codepoint)) {
60 return true;
61
71 // Unexpected/Unsure
77 return false;
78 }
79 return false; // NOT_REACHED();
80}
81
82bool IsWordCharacter(std::string_view input, size_t iter) {
83 uint32_t ucs = 0;
84 if (!EatCodePoint(input, iter, &iter, &ucs)) {
85 return false;
86 }
87
88 return IsWordCodePoint(ucs);
89}
90
91// An input box. The user can type text into it.
92class InputBase : public ComponentBase, public InputOption {
93 public:
94 // NOLINTNEXTLINE
95 InputBase(InputOption option) : InputOption(std::move(option)) {}
96
97 private:
98 // Component implementation:
99 Element OnRender() override {
100 const bool is_focused = Focused();
101 const auto focused = (!is_focused && !hovered_) ? focus
102 : insert() ? focusCursorBarBlinking
103 : focusCursorBlockBlinking;
104
105 auto transform_func =
106 transform ? transform : InputOption::Default().transform;
107
108 // placeholder.
109 if (content->empty()) {
110 auto element = text(placeholder()) | focused | xflex | frame;
111
112 return transform_func({
113 std::move(element), hovered_, is_focused,
114 true // placeholder
115 }) |
116 reflect(box_);
117 }
118
119 Elements elements;
120 const std::vector<std::string> lines = SplitLines(*content);
121
122 cursor_position() = util::clamp(cursor_position(), 0, (int)content->size());
123
124 // Find the line and index of the cursor.
125 int cursor_line = 0;
126 int cursor_char_index = cursor_position();
127 for (const auto& line : lines) {
128 if (cursor_char_index <= (int)line.size()) {
129 break;
130 }
131
132 cursor_char_index -= static_cast<int>(line.size() + 1);
133 cursor_line++;
134 }
135
136 if (lines.empty()) {
137 elements.push_back(text("") | focused);
138 }
139
140 elements.reserve(lines.size());
141 for (size_t i = 0; i < lines.size(); ++i) {
142 const std::string& line = lines[i];
143
144 // This is not the cursor line.
145 if (int(i) != cursor_line) {
146 elements.push_back(Text(line));
147 continue;
148 }
149
150 // The cursor is at the end of the line.
151 const std::string cursor_cell = is_focused ? " " : "";
152 if (cursor_char_index >= (int)line.size()) {
153 elements.push_back(
154 hbox({
155 Text(line),
156 text(cursor_cell) | focused | reflect(cursor_box_),
157 }) |
158 xflex);
159 continue;
160 }
161
162 // The cursor is on this line.
163 const int glyph_start = cursor_char_index;
164 const int glyph_end = static_cast<int>(GlyphNext(line, glyph_start));
165 const std::string part_before_cursor = line.substr(0, glyph_start);
166 const std::string part_at_cursor =
167 line.substr(glyph_start, glyph_end - glyph_start);
168 const std::string part_after_cursor = line.substr(glyph_end);
169 auto element = hbox({
170 Text(part_before_cursor),
171 Text(part_at_cursor) | focused | reflect(cursor_box_),
172 Text(part_after_cursor),
173 }) |
174 xflex;
175 elements.push_back(element);
176 }
177
178 auto element = vbox(std::move(elements)) | frame;
179 return transform_func({
180 std::move(element), hovered_, is_focused,
181 false // placeholder
182 }) |
183 xflex | reflect(box_);
184 }
185
186 Element Text(const std::string& input) {
187 if (!password()) {
188 return text(input);
189 }
190
191 const size_t glyph_count = GlyphCount(input);
192 std::string out;
193 out.reserve(glyph_count * 3);
194 for (size_t i = 0; i < glyph_count; ++i) {
195 out += "•";
196 }
197 return text(out);
198 }
199
200 bool HandleBackspace() {
201 if (cursor_position() == 0) {
202 return false;
203 }
204 const size_t start = GlyphPrevious(content(), cursor_position());
205 const size_t end = cursor_position();
206 content->erase(start, end - start);
207 cursor_position() = static_cast<int>(start);
208 App::PostEventOrExecute(on_change);
209 return true;
210 }
211
212 bool HandleBackspaceWord() {
213 if (cursor_position() == 0) {
214 return false;
215 }
216
217 size_t start = cursor_position();
218 const size_t previous = GlyphPrevious(content(), start);
219 if (content()[previous] == '\n') {
220 start = previous;
221 } else {
222 // Phase 1: Skip non-word characters going backward (stopping at '\n').
223 while (start > 0) {
224 const size_t prev = GlyphPrevious(content(), start);
225 if (content()[prev] == '\n' || IsWordCharacter(content(), prev)) {
226 break;
227 }
228 start = prev;
229 }
230
231 // Phase 2: Skip word characters going backward (stopping at '\n').
232 while (start > 0) {
233 const size_t prev = GlyphPrevious(content(), start);
234 if (content()[prev] == '\n' || !IsWordCharacter(content(), prev)) {
235 break;
236 }
237 start = prev;
238 }
239 }
240
241 const size_t end = cursor_position();
242 content->erase(start, end - start);
243 cursor_position() = static_cast<int>(start);
244 App::PostEventOrExecute(on_change);
245 return true;
246 }
247
248 bool HandleDeleteWord() {
249 if (cursor_position() == (int)content->size()) {
250 return false;
251 }
252
253 size_t end = cursor_position();
254 if (content()[end] == '\n') {
255 end++;
256 } else {
257 // Phase 1: Skip non-word characters going forward (stopping at '\n').
258 while (end < content->size() && content()[end] != '\n' &&
259 !IsWordCharacter(content(), end)) {
260 end = GlyphNext(content(), end);
261 }
262
263 // Phase 2: Skip word characters going forward (stopping at '\n').
264 while (end < content->size() && content()[end] != '\n' &&
265 IsWordCharacter(content(), end)) {
266 end = GlyphNext(content(), end);
267 }
268 }
269
270 const size_t start = cursor_position();
271 content->erase(start, end - start);
272 App::PostEventOrExecute(on_change);
273 return true;
274 }
275
276 // Delete from the beginning of the line to the cursor.
277 bool HandleBackspaceLine() {
278 if (cursor_position() == 0) {
279 return false;
280 }
281
282 size_t start = cursor_position();
283 const size_t previous = GlyphPrevious(content(), start);
284 if (content()[previous] == '\n') {
285 start = previous;
286 } else {
287 while (start > 0) {
288 const size_t prev = GlyphPrevious(content(), start);
289 if (content()[prev] == '\n') {
290 break;
291 }
292 start = prev;
293 }
294 }
295
296 const size_t end = cursor_position();
297 content->erase(start, end - start);
298 cursor_position() = static_cast<int>(start);
299 App::PostEventOrExecute(on_change);
300 return true;
301 }
302
303 bool DeleteImpl() {
304 if (cursor_position() == (int)content->size()) {
305 return false;
306 }
307 const size_t start = cursor_position();
308 const size_t end = GlyphNext(content(), cursor_position());
309 content->erase(start, end - start);
310 return true;
311 }
312
313 bool HandleDelete() {
314 if (DeleteImpl()) {
315 App::PostEventOrExecute(on_change);
316 return true;
317 }
318 return false;
319 }
320
321 bool HandleArrowLeft() {
322 if (cursor_position() == 0) {
323 return false;
324 }
325
326 cursor_position() =
327 static_cast<int>(GlyphPrevious(content(), cursor_position()));
328 return true;
329 }
330
331 bool HandleArrowRight() {
332 if (cursor_position() == (int)content->size()) {
333 return false;
334 }
335
336 cursor_position() =
337 static_cast<int>(GlyphNext(content(), cursor_position()));
338 return true;
339 }
340
341 size_t CursorColumn() {
342 size_t iter = cursor_position();
343 int width = 0;
344 while (true) {
345 if (iter == 0) {
346 break;
347 }
348 iter = GlyphPrevious(content(), iter);
349 if (content()[iter] == '\n') {
350 break;
351 }
352 if (password()) {
353 width += 1;
354 } else {
355 width += static_cast<int>(GlyphWidth(content(), iter));
356 }
357 }
358 return width;
359 }
360
361 // Move the cursor `columns` on the right, if possible.
362 void MoveCursorColumn(int columns) {
363 while (columns > 0) {
364 if (cursor_position() == (int)content().size() ||
365 content()[cursor_position()] == '\n') {
366 return;
367 }
368
369 if (password()) {
370 columns -= 1;
371 } else {
372 columns -= static_cast<int>(GlyphWidth(content(), cursor_position()));
373 }
374 cursor_position() =
375 static_cast<int>(GlyphNext(content(), cursor_position()));
376 }
377 }
378
379 bool HandleArrowUp() {
380 if (cursor_position() == 0) {
381 return false;
382 }
383
384 const size_t columns = CursorColumn();
385
386 // Move cursor at the beginning of 2 lines above.
387 while (true) {
388 if (cursor_position() == 0) {
389 return true;
390 }
391 const size_t previous = GlyphPrevious(content(), cursor_position());
392 if (content()[previous] == '\n') {
393 break;
394 }
395 cursor_position() = static_cast<int>(previous);
396 }
397 cursor_position() =
398 static_cast<int>(GlyphPrevious(content(), cursor_position()));
399 while (true) {
400 if (cursor_position() == 0) {
401 break;
402 }
403 const size_t previous = GlyphPrevious(content(), cursor_position());
404 if (content()[previous] == '\n') {
405 break;
406 }
407 cursor_position() = static_cast<int>(previous);
408 }
409
410 MoveCursorColumn(static_cast<int>(columns));
411 return true;
412 }
413
414 bool HandleArrowDown() {
415 if (cursor_position() == (int)content->size()) {
416 return false;
417 }
418
419 const size_t columns = CursorColumn();
420
421 // Move cursor at the beginning of the next line
422 while (true) {
423 if (content()[cursor_position()] == '\n') {
424 break;
425 }
426 cursor_position() =
427 static_cast<int>(GlyphNext(content(), cursor_position()));
428 if (cursor_position() == (int)content().size()) {
429 return true;
430 }
431 }
432 cursor_position() =
433 static_cast<int>(GlyphNext(content(), cursor_position()));
434
435 MoveCursorColumn(static_cast<int>(columns));
436 return true;
437 }
438
439 bool HandleHome() {
440 cursor_position() = 0;
441 return true;
442 }
443
444 bool HandleEnd() {
445 cursor_position() = static_cast<int>(content->size());
446 return true;
447 }
448
449 bool HandleLineStart() {
450 while (cursor_position() > 0) {
451 const size_t previous = GlyphPrevious(content(), cursor_position());
452 if (content()[previous] == '\n') {
453 break;
454 }
455 cursor_position() = static_cast<int>(previous);
456 }
457 return true;
458 }
459
460 bool HandleLineEnd() {
461 while (cursor_position() < (int)content->size() &&
462 content()[cursor_position()] != '\n') {
463 cursor_position() =
464 static_cast<int>(GlyphNext(content(), cursor_position()));
465 }
466 return true;
467 }
468
469 bool HandleReturn() {
470 if (multiline()) {
471 HandleCharacter("\n");
472 }
473 App::PostEventOrExecute(on_enter);
474 return true;
475 }
476
477 bool HandleCharacter(const std::string& character) {
478 if (!insert() && cursor_position() < (int)content->size() &&
479 content()[cursor_position()] != '\n') {
480 DeleteImpl();
481 }
482 content->insert(cursor_position(), character);
483 cursor_position() += static_cast<int>(character.size());
484 App::PostEventOrExecute(on_change);
485 return true;
486 }
487
488 bool OnEvent(Event event) override {
489 cursor_position() = util::clamp(cursor_position(), 0, (int)content->size());
490
491 if (event == Event::Return) {
492 return HandleReturn();
493 }
494 if (event.is_character()) {
495 return HandleCharacter(event.character());
496 }
497 if (event.is_mouse()) {
498 return HandleMouse(event);
499 }
500 if (event == Event::Backspace) {
501 return HandleBackspace();
502 }
503 if (event == Event::BackspaceAlt || event == Event::AltBackspace ||
504 event == Event::CtrlW) {
505 return HandleBackspaceWord();
506 }
507 if (event == Event::CtrlU) {
508 return HandleBackspaceLine();
509 }
510 if (event == Event::Delete) {
511 return HandleDelete();
512 }
513 if (event == Event::DeleteAlt || event == Event::DeleteCtrl ||
514 event == Event::AltD) {
515 return HandleDeleteWord();
516 }
517 if (event == Event::ArrowLeft) {
518 return HandleArrowLeft();
519 }
520 if (event == Event::ArrowRight) {
521 return HandleArrowRight();
522 }
523 if (event == Event::ArrowUp) {
524 return HandleArrowUp();
525 }
526 if (event == Event::ArrowDown) {
527 return HandleArrowDown();
528 }
529 if (event == Event::Home) {
530 return HandleHome();
531 }
532 if (event == Event::End) {
533 return HandleEnd();
534 }
535 if (event == Event::CtrlA) {
536 return HandleLineStart();
537 }
538 if (event == Event::CtrlE) {
539 return HandleLineEnd();
540 }
541 if (event == Event::ArrowLeftCtrl || event == Event::ArrowLeftAlt ||
542 event == Event::AltB) {
543 return HandleLeftCtrl();
544 }
545 if (event == Event::ArrowRightCtrl || event == Event::ArrowRightAlt ||
546 event == Event::AltF) {
547 return HandleRightCtrl();
548 }
549 if (event == Event::ArrowUpAlt) {
550 return HandleArrowUp();
551 }
552 if (event == Event::ArrowDownAlt) {
553 return HandleArrowDown();
554 }
555 if (event == Event::Insert) {
556 return HandleInsert();
557 }
558 return false;
559 }
560
561 bool HandleLeftCtrl() {
562 if (cursor_position() == 0) {
563 return false;
564 }
565
566 // Move left, as long as left it not a word.
567 while (cursor_position()) {
568 const size_t previous = GlyphPrevious(content(), cursor_position());
569 if (IsWordCharacter(content(), previous)) {
570 break;
571 }
572 cursor_position() = static_cast<int>(previous);
573 }
574 // Move left, as long as left is a word character:
575 while (cursor_position()) {
576 const size_t previous = GlyphPrevious(content(), cursor_position());
577 if (!IsWordCharacter(content(), previous)) {
578 break;
579 }
580 cursor_position() = static_cast<int>(previous);
581 }
582 return true;
583 }
584
585 bool HandleRightCtrl() {
586 if (cursor_position() == (int)content().size()) {
587 return false;
588 }
589
590 // Move right, until entering a word.
591 while (cursor_position() < (int)content().size()) {
592 cursor_position() =
593 static_cast<int>(GlyphNext(content(), cursor_position()));
594 if (IsWordCharacter(content(), cursor_position())) {
595 break;
596 }
597 }
598 // Move right, as long as right is a word character:
599 while (cursor_position() < (int)content().size()) {
600 const size_t next = GlyphNext(content(), cursor_position());
601 if (!IsWordCharacter(content(), cursor_position())) {
602 break;
603 }
604 cursor_position() = static_cast<int>(next);
605 }
606
607 return true;
608 }
609
610 bool HandleMouse(Event event) {
611 hovered_ = box_.Contain(event.mouse().x, //
612 event.mouse().y) &&
613 CaptureMouse(event);
614 if (!hovered_) {
615 return false;
616 }
617
618 if (event.mouse().button != Mouse::Left) {
619 return false;
620 }
621 if (event.mouse().motion != Mouse::Pressed) {
622 return false;
623 }
624
625 TakeFocus();
626
627 if (content->empty()) {
628 cursor_position() = 0;
629 return true;
630 }
631
632 // Find the line and index of the cursor.
633 std::vector<std::string> lines = SplitLines(*content);
634 int cursor_line = 0;
635 int cursor_char_index = cursor_position();
636 for (const auto& line : lines) {
637 if (cursor_char_index <= (int)line.size()) {
638 break;
639 }
640
641 cursor_char_index -= static_cast<int>(line.size() + 1);
642 cursor_line++;
643 }
644 const int cursor_column =
645 password()
646 ? GlyphCount(lines[cursor_line].substr(0, cursor_char_index))
647 : string_width(lines[cursor_line].substr(0, cursor_char_index));
648
649 int new_cursor_column = cursor_column + event.mouse().x - cursor_box_.x_min;
650 int new_cursor_line = cursor_line + event.mouse().y - cursor_box_.y_min;
651
652 // Fix the new cursor position:
653 new_cursor_line = std::max(std::min(new_cursor_line, (int)lines.size()), 0);
654
655 const std::string empty_string;
656 const std::string& line = new_cursor_line < (int)lines.size()
657 ? lines[new_cursor_line]
658 : empty_string;
659 new_cursor_column =
660 util::clamp(new_cursor_column, 0,
661 password() ? GlyphCount(line) : string_width(line));
662
663 if (new_cursor_column == cursor_column && //
664 new_cursor_line == cursor_line) {
665 return false;
666 }
667
668 // Convert back the new_cursor_{line,column} toward cursor_position:
669 cursor_position() = 0;
670 for (int i = 0; i < new_cursor_line; ++i) {
671 cursor_position() += static_cast<int>(lines[i].size() + 1);
672 }
673 while (new_cursor_column > 0) {
674 if (password()) {
675 new_cursor_column -= 1;
676 } else {
677 new_cursor_column -=
678 static_cast<int>(GlyphWidth(content(), cursor_position()));
679 }
680 cursor_position() =
681 static_cast<int>(GlyphNext(content(), cursor_position()));
682 }
683
684 App::PostEventOrExecute(on_change);
685 return true;
686 }
687
688 bool HandleInsert() {
689 insert() = !insert();
690 return true;
691 }
692
693 bool Focusable() const final { return true; }
694
695 bool hovered_ = false;
696
697 Box box_;
698 Box cursor_box_;
699};
700
701} // namespace
702
703/// @brief An input box for editing text.
704/// @param option Additional optional parameters.
705/// @ingroup component
706/// @see InputBase
707///
708/// ### Example
709///
710/// ```cpp
711/// auto screen = App::FitComponent();
712/// std::string content= "";
713/// std::string placeholder = "placeholder";
714/// Component input = Input({
715/// .content = &content,
716/// .placeholder = &placeholder,
717/// })
718/// screen.Loop(input);
719/// ```
720///
721/// ### Output
722///
723/// ```bash
724/// placeholder
725/// ```
726Component Input(InputOption option) {
727 return Make<InputBase>(std::move(option));
728}
729
730/// @brief An input box for editing text.
731/// @param content The editable content.
732/// @param option Additional optional parameters.
733/// @ingroup component
734/// @see InputBase
735///
736/// ### Example
737///
738/// ```cpp
739/// auto screen = App::FitComponent();
740/// std::string content= "";
741/// std::string placeholder = "placeholder";
742/// Component input = Input(content, {
743/// .placeholder = &placeholder,
744/// .password = true,
745/// })
746/// screen.Loop(input);
747/// ```
748///
749/// ### Output
750///
751/// ```bash
752/// placeholder
753/// ```
754Component Input(StringRef content, InputOption option) {
755 option.content = std::move(content);
756 return Make<InputBase>(std::move(option));
757}
758
759/// @brief An input box for editing text.
760/// @param content The editable content.
761/// @param placeholder The placeholder text.
762/// @param option Additional optional parameters.
763/// @ingroup component
764/// @see InputBase
765///
766/// ### Example
767///
768/// ```cpp
769/// auto screen = App::FitComponent();
770/// std::string content= "";
771/// std::string placeholder = "placeholder";
772/// Component input = Input(content, placeholder);
773/// screen.Loop(input);
774/// ```
775///
776/// ### Output
777///
778/// ```bash
779/// placeholder
780/// ```
781Component Input(StringRef content, StringRef placeholder, InputOption option) {
782 option.content = std::move(content);
783 option.placeholder = std::move(placeholder);
784 return Make<InputBase>(std::move(option));
785}
786
787} // namespace ftxui
Component Input(StringRef content, InputOption options={})
An input box for editing text.
Element text(std::string_view text)
Display a piece of UTF8 encoded unicode text.
Element xflex(Element)
Expand/Minimize if possible/needed on the X axis.
Definition flex.cpp:101
Element vbox(Elements children)
A container displaying elements vertically one by one.
Definition vbox.cpp:96
constexpr const T & clamp(const T &v, const T &lo, const T &hi)
Definition util.hpp:13
The FTXUI ftxui:: namespace.
Definition animation.hpp:11
WordBreakProperty CodepointToWordBreakProperty(uint32_t codepoint)
Definition string.cpp:276
size_t GlyphPrevious(std::string_view input, size_t start)
Definition string.cpp:414
std::shared_ptr< T > Make(Args &&... args)
Definition component.hpp:28
std::shared_ptr< Node > Element
Definition app.hpp:25
int string_width(std::string_view input)
Definition string.cpp:304
Element hbox(Elements children)
A container displaying elements horizontally one by one.
Definition hbox.cpp:94
std::vector< Element > Elements
Definition elements.hpp:26
bool EatCodePoint(std::string_view input, size_t start, size_t *end, uint32_t *ucs)
Definition string.cpp:142
int GlyphCount(std::string_view input)
Definition string.cpp:528
const Element & element
Definition node.hpp:95
Decorator reflect(Box &box)
Definition reflect.cpp:43
bool IsFullWidth(uint32_t ucs)
Definition string.cpp:254
Element frame(Element child)
Allow an element to be displayed inside a 'virtual' area. It size can be larger than its container....
Definition frame.cpp:118
std::shared_ptr< ComponentBase > Component
Definition app.hpp:23
size_t GlyphNext(std::string_view input, size_t start)
Definition string.cpp:439
size_t size
Definition screen.cpp:82