Measure the width of text in the terminal and build simple layouts!
0

Configure Feed

Select the types of activity you want to include in your feed.

soft_wrap

+429 -217
+409 -215
src/string_width.gleam
··· 17 17 //// #### Layout & Positioning 18 18 //// 19 19 //// [limit](#limit "Word wrapping and truncation")<small>[[_with](#limit_with)]</small>, 20 + //// [soft_wrap](#soft_wrap "Word wrapping with custom indentation logic")<small>[[_with](#soft_wrap_with)]</small>, 20 21 //// [align](#align "Text alignment")<small>[[_with](#align_with)]</small>, 21 22 //// [tabs_to_spaces](#tabs_to_spaces "Pin tab widths")<small>[[_with](#tabs_to_spaces_with)]</small> 22 23 //// ··· 73 74 // - try poslen -> split for binary states (align, limit) 74 75 // so far, switching away from string.append was never worth it. 75 76 // - cprof, eprof, eflamb`e 76 - // - propose a @inline attribute 77 - // - propose changing the js codegen to generate constructor calls instead of withFields 78 77 79 78 // v3.3.0: 80 79 // - what about grid?? ··· 461 460 limit_with(str, max_size, default_options, ellipsis) 462 461 } 463 462 464 - type LimitState { 465 - LimitState( 466 - str: String, 467 - row: Int, 468 - col: Int, 469 - spaces: String, 470 - spaces_width: Int, 471 - non_spaces: String, 472 - non_spaces_width: Int, 473 - overflow: String, 474 - overflow_width: Int, 475 - ) 463 + /// Like `limit`, but also customise the options used for measuring. 464 + /// 465 + /// <div style="text-align: right;"> 466 + /// <a href="#"> 467 + /// <small>Back to top ↑</small> 468 + /// </a> 469 + /// </div> 470 + pub fn limit_with( 471 + str: String, 472 + to max_size: Size, 473 + using options: Options, 474 + ellipsis ellipsis: String, 475 + ) -> String { 476 + soft_wrap_with(str, max_size, options, ellipsis, fn(_) { "" }) 476 477 } 477 478 478 - fn limit_state_col(state: LimitState) { 479 - state.col + state.spaces_width + state.non_spaces_width + state.overflow_width 479 + /// Limit the dimensions of a string, either by wrapping on white space 480 + /// characters using a custom indentation function, or by truncating the last 481 + /// line and appending an ellipsis. 482 + /// 483 + /// This function gives a little bit more control over what happens on soft 484 + /// line breaks than `limit` and allows you to inspect the line printed so far 485 + /// to figure out the indentation for all soft-wrapped lines. The `to_indent` 486 + /// callback is called with the line so far, and should return an indentation 487 + /// string (usually just the proper amount of spaces) that is prepended to all 488 + /// the following soft-wrapped lines. 489 + /// 490 + /// Behaves like `limit` otherwise. 491 + /// 492 + /// ### Examples 493 + /// 494 + /// ```gleam 495 + /// fn to_indent(str) { 496 + /// case str { 497 + /// // if it is a list, indent with 2 spaces 498 + /// "- " <> _ -> " " 499 + /// // if it is a blockquote, prepend another blockquote marker 500 + /// "> " <> _ -> "> " 501 + /// // else, don't indent (like limit) 502 + /// _ -> "" 503 + /// } 504 + /// } 505 + /// 506 + /// // Girly Girl Productions - 10 drunk cigarettes 507 + /// let lyrics = " 508 + /// > Getting girls rich, yeah, that's a part of my plan. 509 + /// > And I could name 10 things us girls need, before we ever need a man: 510 + /// 511 + /// - One new vape, two lines of coke 512 + /// - Three drinks from the bar, four more lines of coke 513 + /// - Five Guys fries, six hits from my blunt 514 + /// - Seven more lines of coke, eight pairs of shoes 515 + /// - Nine BB belts, and 10 drunk cigarettes. 516 + /// " 517 + /// 518 + /// lyrics 519 + /// |> soft_wrap(to: Size(rows: 24, columns: 34), ellipsis: "...", to_indent:) 520 + /// |> io.println 521 + /// ``` 522 + /// 523 + /// **Output:** 524 + /// 525 + /// ```txt 526 + /// > Getting girls rich, yeah, that's 527 + /// > a part of my plan. 528 + /// > And I could name 10 things us 529 + /// > girls need, before we ever need 530 + /// > a man: 531 + /// 532 + /// - One new vape, two lines of coke 533 + /// - Three drinks from the bar, four 534 + /// more lines of coke 535 + /// - Five Guys fries, six hits from 536 + /// my blunt 537 + /// - Seven more lines of coke, eight 538 + /// pairs of shoes 539 + /// - Nine BB belts, and 10 drunk 540 + /// cigarettes. 541 + /// ``` 542 + /// 543 + /// <div style="text-align: right;"> 544 + /// <a href="#"> 545 + /// <small>Back to top ↑</small> 546 + /// </a> 547 + /// </div> 548 + pub fn soft_wrap( 549 + str: String, 550 + to max_size: Size, 551 + ellipsis ellipsis: String, 552 + to_indent to_indent: fn(String) -> String, 553 + ) -> String { 554 + soft_wrap_with(str, max_size, default_options, ellipsis, to_indent) 480 555 } 481 556 482 - // find the grapheme cluster boundary to set the new overflow. 483 - fn limit_state_overflow( 484 - state: LimitState, 485 - piece: String, 486 - options: Options, 487 - ) -> #(String, String, Int) { 488 - let str = state.non_spaces <> state.overflow <> piece 489 - 490 - let #(before, after, width) = { 491 - use #(before, after, width), piece <- fold_with(str, options, #("", "", 0)) 492 - case piece.column + piece.width > state.non_spaces_width { 493 - True -> #(before, after <> piece.piece, width + piece.width) 494 - False -> #(before <> piece.piece, after, width) 495 - } 496 - } 497 - 498 - case before == "" { 499 - True -> #(before, after, width) 500 - False -> #(state.spaces <> before, after, width) 501 - } 502 - } 503 - 504 - /// Like `limit`, but also customise the options used for measuring. 557 + /// Like `soft_wrap`, but using custom options to measure the strings and indents. 505 558 /// 506 559 /// <div style="text-align: right;"> 507 560 /// <a href="#"> 508 561 /// <small>Back to top ↑</small> 509 562 /// </a> 510 563 /// </div> 511 - pub fn limit_with( 564 + pub fn soft_wrap_with( 512 565 str: String, 513 566 to max_size: Size, 514 567 using options: Options, 515 568 ellipsis ellipsis: String, 569 + to_indent to_indent: fn(String) -> String, 516 570 ) -> String { 517 571 let ellipsis_width = spacer_width(options, ellipsis) 518 572 let max_row = max_size.rows 519 573 520 - // we encode a few states, but we do it this way such that spread works... 521 - // - if state.row == max_size.rows, we have reached the end and should do nothing 522 - // - if we have oveflow, we are at the last line and stuff doesn't fit. 523 - // - if we have non_spaces or overflow, we are collecting word characters. 524 - // - if we do not, we are collecting spaces _before_ a word. 525 - // - on the first space we collect, if we notice that we have word characters, 526 - // we try to push this word into the accumulator. 527 - // if it doesn't fit on the last line, we have overflow, and can append the ellipsis. 528 - // afterwards, we set row = max_sie.rows to indicate we are finished. 529 - let commit = fn(max_col: Int, state: LimitState, piece: String, width: Int) { 530 - let new_col = limit_state_col(state) 574 + let push = fn(state, piece, width) { 575 + // io.debug(#("push", state, piece, width)) 576 + limit_state_push( 577 + options, 578 + max_size, 579 + ellipsis, 580 + ellipsis_width, 581 + to_indent, 582 + state, 583 + piece, 584 + width, 585 + ) 586 + } 531 587 532 - // make sure we definitely have room for the ellipsis on the last line 533 - let is_last_line = state.row + 1 >= max_row 534 - 535 - case state.non_spaces == "" && state.overflow == "" { 536 - // we do not have non-spaces, so we are still collecting spaces. 537 - True -> 538 - case state.str { 539 - // skip spaces at the start, since we skip them at the end. 540 - "" -> state 541 - _ -> 542 - LimitState( 543 - spaces: state.spaces <> piece, 544 - spaces_width: state.spaces_width + width, 545 - str: state.str, 546 - row: state.row, 547 - col: state.col, 548 - non_spaces: state.non_spaces, 549 - non_spaces_width: state.non_spaces_width, 550 - overflow: state.overflow, 551 - overflow_width: state.overflow_width, 552 - ) 553 - } 554 - 555 - // we have non-spaces, so we want to push this word into the accumulator 556 - // and then start again collecting spaces. 557 - False -> 558 - // would it fit into the line just normally? 559 - case new_col <= max_col { 560 - True -> { 561 - // it fits normally, nothing special required! 562 - let str = 563 - state.str <> state.spaces <> state.non_spaces <> state.overflow 564 - LimitState( 565 - str:, 566 - row: state.row, 567 - col: new_col, 568 - spaces: piece, 569 - spaces_width: width, 570 - non_spaces: "", 571 - non_spaces_width: 0, 572 - overflow: "", 573 - overflow_width: 0, 574 - ) 575 - } 576 - 577 - False -> { 578 - // it doesn't; if we are at the last line, truncate. else, wrap. 579 - case is_last_line { 580 - True -> { 581 - let #(truncated, _, _) = 582 - limit_state_overflow(state, "", options) 583 - LimitState( 584 - str: state.str <> truncated <> ellipsis, 585 - row: state.row + 1, 586 - col: max_size.columns, 587 - spaces: "", 588 - spaces_width: 0, 589 - non_spaces: "", 590 - non_spaces_width: 0, 591 - overflow: "", 592 - overflow_width: 0, 593 - ) 594 - } 595 - 596 - False -> 597 - LimitState( 598 - str: state.str <> "\n" <> state.non_spaces <> state.overflow, 599 - row: state.row + 1, 600 - col: state.non_spaces_width + state.overflow_width, 601 - spaces: piece, 602 - spaces_width: width, 603 - non_spaces: "", 604 - non_spaces_width: 0, 605 - overflow: "", 606 - overflow_width: 0, 607 - ) 608 - } 609 - } 610 - } 611 - } 588 + let commit = fn(state, piece, width) { 589 + // io.debug(#("commit", state, piece, width)) 590 + limit_state_commit( 591 + options, 592 + max_size, 593 + ellipsis, 594 + to_indent, 595 + state, 596 + piece, 597 + width, 598 + ) 612 599 } 613 600 614 601 let initial = 615 602 LimitState( 616 603 str: "", 604 + line: "", 617 605 row: 0, 618 606 col: 0, 619 607 spaces: "", ··· 622 610 non_spaces_width: 0, 623 611 overflow: "", 624 612 overflow_width: 0, 613 + indent: IndentUnknown, 625 614 ) 626 615 627 616 let state = { 628 617 use state, piece, width <- fold_raw(str, options, initial) 629 - // io.debug(#(state, piece, width)) 630 - let is_last_line = state.row + 1 >= max_row 631 - let max_col = case is_last_line { 632 - True -> max_size.columns - ellipsis_width 633 - False -> max_size.columns 634 - } 635 618 636 619 case state.row >= max_row { 637 620 // reached the end, but we still want to collect ansi sequences ··· 639 622 case is_ansi_component(piece, options) { 640 623 True -> 641 624 LimitState( 642 - str: state.str <> piece, 625 + line: state.line <> piece, 626 + str: state.str, 643 627 row: state.row, 644 628 col: state.col, 645 629 spaces: state.spaces, ··· 648 632 non_spaces_width: state.non_spaces_width, 649 633 overflow: state.overflow, 650 634 overflow_width: state.overflow_width, 635 + indent: state.indent, 651 636 ) 652 637 False -> state 653 638 } ··· 656 641 case piece { 657 642 // Line break that we recognise as such 658 643 "\n" -> { 659 - let state = commit(max_col, state, "", 0) 644 + let state = commit(state, "", 0) 660 645 LimitState( 661 - str: state.str <> "\n", 646 + str: state.str <> state.line <> "\n", 647 + line: "", 662 648 row: state.row + 1, 663 649 col: 0, 650 + indent: IndentUnknown, 664 651 spaces: state.spaces, 665 652 spaces_width: state.spaces_width, 666 653 non_spaces: state.non_spaces, ··· 673 660 "\t" -> { 674 661 // tab has a different width depending on whether or not we will wrap 675 662 let curr_col = limit_state_col(state) 676 - let width = case curr_col <= max_col { 663 + let width = case curr_col <= max_size.columns { 677 664 True -> tab(options, options.tab_offset + curr_col) 678 665 False -> { 679 666 let wrapped_col = state.non_spaces_width + state.overflow_width ··· 681 668 } 682 669 } 683 670 684 - commit(max_col, state, piece, width) 671 + commit(state, piece, width) 685 672 } 686 673 687 674 // White_Space=Y, excluding control characters and non-breaking spaces ··· 698 685 | "\u{2009}" 699 686 | "\u{200a}" 700 687 | "\u{205f}" 701 - | "\u{3000}" -> commit(max_col, state, piece, width) 688 + | "\u{3000}" -> commit(state, piece, width) 702 689 703 690 // not a space or a line break 704 - _ -> { 705 - case limit_state_col(state) + width <= max_col { 706 - True -> 707 - // everything's fine and normal :) 708 - LimitState( 709 - non_spaces: state.non_spaces <> piece, 710 - non_spaces_width: state.non_spaces_width + width, 711 - str: state.str, 712 - row: state.row, 713 - col: state.col, 714 - spaces: state.spaces, 715 - spaces_width: state.spaces_width, 716 - overflow: state.overflow, 717 - overflow_width: state.overflow_width, 718 - ) 691 + _ -> push(state, piece, width) 692 + } 693 + } 694 + } 719 695 720 - False -> { 721 - // these non_space characters no longer fit the line, 722 - // but what if overflow is too long as well? 723 - case 724 - state.non_spaces_width + state.overflow_width + width 725 - > max_size.columns 726 - { 727 - True -> { 728 - let #(truncated, overflow, overflow_width) = 729 - limit_state_overflow(state, piece, options) 730 - // it _is_! do we have room for another row? 731 - case is_last_line { 732 - False -> 733 - LimitState( 734 - str: state.str <> truncated <> "\n", 735 - row: state.row + 1, 736 - non_spaces: overflow, 737 - non_spaces_width: overflow_width, 738 - col: 0, 739 - spaces: "", 740 - spaces_width: 0, 741 - overflow: "", 742 - overflow_width: 0, 743 - ) 696 + // commit the last word, if any 697 + let state = commit(state, "", 0) 698 + state.str <> state.line 699 + } 744 700 745 - True -> 746 - // no, word to long this is the last row, sorry. 747 - LimitState( 748 - str: state.str <> truncated <> ellipsis, 749 - row: state.row + 1, 750 - col: max_size.columns, 751 - spaces: "", 752 - spaces_width: 0, 753 - non_spaces: "", 754 - non_spaces_width: 0, 755 - overflow: "", 756 - overflow_width: 0, 757 - ) 758 - } 759 - } 701 + type Indent { 702 + IndentUnknown 703 + Indent(str: String, width: Int) 704 + } 760 705 761 - False -> 762 - LimitState( 763 - overflow: state.overflow <> piece, 764 - overflow_width: state.overflow_width + width, 765 - str: state.str, 766 - row: state.row, 767 - col: state.col, 768 - spaces: state.spaces, 769 - spaces_width: state.spaces_width, 770 - non_spaces: state.non_spaces, 771 - non_spaces_width: state.non_spaces_width, 772 - ) 773 - } 774 - } 706 + type LimitState { 707 + LimitState( 708 + str: String, 709 + line: String, 710 + row: Int, 711 + col: Int, 712 + spaces: String, 713 + spaces_width: Int, 714 + non_spaces: String, 715 + non_spaces_width: Int, 716 + overflow: String, 717 + overflow_width: Int, 718 + indent: Indent, 719 + ) 720 + } 721 + 722 + fn limit_state_col(state: LimitState) { 723 + state.col + state.spaces_width + state.non_spaces_width + state.overflow_width 724 + } 725 + 726 + // find the grapheme cluster boundary to set the new overflow. 727 + fn limit_state_overflow( 728 + state: LimitState, 729 + piece: String, 730 + options: Options, 731 + ) -> #(String, String, Int) { 732 + let str = state.non_spaces <> state.overflow <> piece 733 + 734 + let #(before, after, width) = { 735 + use #(before, after, width), piece <- fold_with(str, options, #("", "", 0)) 736 + case piece.column + piece.width > state.non_spaces_width { 737 + True -> #(before, after <> piece.piece, width + piece.width) 738 + False -> #(before <> piece.piece, after, width) 739 + } 740 + } 741 + 742 + case before { 743 + "" -> #(before, after, width) 744 + _ -> #(state.spaces <> before, after, width) 745 + } 746 + } 747 + 748 + fn limit_state_indent( 749 + get_indent: fn(String) -> String, 750 + options: Options, 751 + state: LimitState, 752 + ) { 753 + case state.indent { 754 + IndentUnknown -> { 755 + let indent = get_indent(state.line) 756 + let indent_width = line_with(indent, options) 757 + #(indent, indent_width) 758 + } 759 + Indent(indent, indent_width) -> #(indent, indent_width) 760 + } 761 + } 762 + 763 + /// push a word-character into LimitState 764 + fn limit_state_push( 765 + options: Options, 766 + max_size: Size, 767 + ellipsis: String, 768 + ellipsis_width: Int, 769 + get_indent: fn(String) -> String, 770 + state: LimitState, 771 + piece: String, 772 + width: Int, 773 + ) { 774 + let is_last_line = state.row + 1 >= max_size.rows 775 + let max_col = case is_last_line { 776 + True -> max_size.columns - ellipsis_width 777 + False -> max_size.columns 778 + } 779 + case limit_state_col(state) + width <= max_col { 780 + True -> 781 + // everything's fine and normal :) 782 + LimitState( 783 + non_spaces: state.non_spaces <> piece, 784 + non_spaces_width: state.non_spaces_width + width, 785 + str: state.str, 786 + line: state.line, 787 + row: state.row, 788 + col: state.col, 789 + spaces: state.spaces, 790 + spaces_width: state.spaces_width, 791 + overflow: state.overflow, 792 + overflow_width: state.overflow_width, 793 + indent: state.indent, 794 + ) 795 + // these non_space characters no longer fit the line, 796 + // but what if overflow is too long as well? 797 + False -> 798 + case 799 + state.non_spaces_width + state.overflow_width + width > max_size.columns 800 + { 801 + True -> { 802 + let #(truncated, overflow, overflow_width) = 803 + limit_state_overflow(state, piece, options) 804 + // it _is_! do we have room for another row? 805 + case is_last_line { 806 + True -> 807 + // no, word to long this is the last row, sorry. 808 + LimitState( 809 + str: state.str <> state.line <> truncated <> ellipsis, 810 + line: "", 811 + row: state.row + 1, 812 + col: max_size.columns, 813 + spaces: "", 814 + spaces_width: 0, 815 + non_spaces: "", 816 + non_spaces_width: 0, 817 + overflow: "", 818 + overflow_width: 0, 819 + indent: IndentUnknown, 820 + ) 821 + 822 + False -> { 823 + let #(indent, indent_width) = 824 + limit_state_indent(get_indent, options, state) 825 + LimitState( 826 + str: state.str <> state.line <> truncated <> "\n", 827 + line: indent, 828 + row: state.row + 1, 829 + non_spaces: overflow, 830 + non_spaces_width: overflow_width, 831 + col: indent_width, 832 + spaces: "", 833 + spaces_width: 0, 834 + overflow: "", 835 + overflow_width: 0, 836 + indent: Indent(indent, indent_width), 837 + ) 775 838 } 776 839 } 777 840 } 778 - } 841 + 842 + False -> 843 + LimitState( 844 + overflow: state.overflow <> piece, 845 + overflow_width: state.overflow_width + width, 846 + str: state.str, 847 + line: state.line, 848 + row: state.row, 849 + col: state.col, 850 + spaces: state.spaces, 851 + spaces_width: state.spaces_width, 852 + non_spaces: state.non_spaces, 853 + non_spaces_width: state.non_spaces_width, 854 + indent: state.indent, 855 + ) 856 + } 779 857 } 858 + } 780 859 781 - // commit the last word, if any 782 - let state = commit(max_size.columns, state, "", 0) 783 - state.str 860 + /// Push a non-word character into LimitState, commiting the currently accumulated word. 861 + fn limit_state_commit( 862 + options: Options, 863 + max_size: Size, 864 + ellipsis: String, 865 + get_indent: fn(String) -> String, 866 + state: LimitState, 867 + piece: String, 868 + width: Int, 869 + ) { 870 + // we encode a few states, but we do it this way such that spread works... 871 + // - if state.row == max_size.rows, we have reached the end and should do nothing 872 + // - if we have oveflow, we are at the last line and stuff doesn't fit. 873 + // - if we have non_spaces or overflow, we are collecting word characters. 874 + // - if we do not, we are collecting spaces _before_ a word. 875 + // - on the first space we collect, if we notice that we have word characters, 876 + // we try to push this word into the accumulator. 877 + // if it doesn't fit on the last line, we have overflow, and can append the ellipsis. 878 + // afterwards, we set row = max_sie.rows to indicate we are finished. 879 + let new_col = limit_state_col(state) 880 + 881 + // make sure we definitely have room for the ellipsis on the last line 882 + let max_row = max_size.rows 883 + let is_last_line = state.row + 1 >= max_row 884 + 885 + case state.non_spaces == "" && state.overflow == "" { 886 + // we do not have non-spaces, so we are still collecting spaces. 887 + True -> 888 + case state.str { 889 + // skip spaces at the start, since we skip them at the end. 890 + "" -> state 891 + _ -> 892 + LimitState( 893 + spaces: state.spaces <> piece, 894 + spaces_width: state.spaces_width + width, 895 + str: state.str, 896 + line: state.line, 897 + row: state.row, 898 + col: state.col, 899 + non_spaces: state.non_spaces, 900 + non_spaces_width: state.non_spaces_width, 901 + overflow: state.overflow, 902 + overflow_width: state.overflow_width, 903 + indent: state.indent, 904 + ) 905 + } 906 + 907 + // we have non-spaces, so we want to push this word into the accumulator 908 + // and then start again collecting spaces. 909 + // 910 + // we already know that non-spaces will fit, otherwise we would have pushed 911 + // into overflow. 912 + False -> 913 + // would it fit into the line just normally? 914 + case state.overflow { 915 + "" -> { 916 + // it fits normally, nothing special required! 917 + let line = 918 + state.line <> state.spaces <> state.non_spaces <> state.overflow 919 + LimitState( 920 + str: state.str, 921 + line: line, 922 + row: state.row, 923 + col: new_col, 924 + spaces: piece, 925 + spaces_width: width, 926 + non_spaces: "", 927 + non_spaces_width: 0, 928 + overflow: "", 929 + overflow_width: 0, 930 + indent: state.indent, 931 + ) 932 + } 933 + 934 + _ -> { 935 + // it doesn't; if we are at the last line, truncate. else, wrap. 936 + case is_last_line { 937 + True -> { 938 + let #(truncated, _, _) = limit_state_overflow(state, "", options) 939 + LimitState( 940 + str: state.str <> state.line <> truncated <> ellipsis, 941 + line: "", 942 + row: state.row + 1, 943 + col: max_size.columns, 944 + spaces: "", 945 + spaces_width: 0, 946 + non_spaces: "", 947 + non_spaces_width: 0, 948 + overflow: "", 949 + overflow_width: 0, 950 + indent: IndentUnknown, 951 + ) 952 + } 953 + 954 + False -> { 955 + let #(indent, indent_width) = 956 + limit_state_indent(get_indent, options, state) 957 + 958 + LimitState( 959 + str: state.str <> state.line <> "\n", 960 + line: indent <> state.non_spaces <> state.overflow, 961 + row: state.row + 1, 962 + col: indent_width 963 + + state.non_spaces_width 964 + + state.overflow_width, 965 + spaces: piece, 966 + spaces_width: width, 967 + non_spaces: "", 968 + non_spaces_width: 0, 969 + overflow: "", 970 + overflow_width: 0, 971 + indent: Indent(indent, indent_width), 972 + ) 973 + } 974 + } 975 + } 976 + } 977 + } 784 978 } 785 979 786 980 /// Replace all tab characters found in the string with the amount of spaces
+6 -1
test/human_rights.gleam
··· 1 1 import gleam/int 2 + import gleam/string 2 3 import string_width.{Center, Middle, Size} 3 4 4 5 const human_rights = " ··· 190 191 191 192 fn view(model: Model) { 192 193 human_rights 193 - |> string_width.limit(Size(5000, model.scroll_width), ellipsis: "...") 194 + |> string_width.soft_wrap( 195 + Size(5000, model.scroll_width), 196 + ellipsis: "...", 197 + to_indent: fn(_) { " " }, 198 + ) 194 199 |> string_width.scroll( 195 200 top: model.scroll_top, 196 201 left: 0,
+14 -1
test/layout_test.gleam
··· 2 2 import gleeunit/should 3 3 import string_width.{ 4 4 Bottom, Center, Left, Middle, Right, Size, Top, align, limit, padding, 5 - position, scroll, stack_horizontal, stack_vertical, tabs_to_spaces, 5 + position, scroll, soft_wrap, stack_horizontal, stack_vertical, tabs_to_spaces, 6 6 } 7 7 8 8 pub fn limit_test() { ··· 68 68 |> should.equal( 69 69 "Nabis sit vel \u{1b}[31mpraesentium\u{1b}[m quod provident\nhic.\t\taliquid harum perferendis", 70 70 ) 71 + } 72 + 73 + pub fn soft_wrap_test() { 74 + soft_wrap("Hello World", Size(2, 10), "...", fn(_) { " " }) 75 + |> should.equal("Hello\n World") 76 + 77 + soft_wrap( 78 + "Lorem ipsum dolor sit amet\nIs a common placeholder string", 79 + to: Size(3, 20), 80 + ellipsis: "...", 81 + to_indent: fn(_) { " " }, 82 + ) 83 + |> should.equal("Lorem ipsum dolor\n sit amet\nIs a common place...") 71 84 } 72 85 73 86 pub fn tabs_to_spaces_test() {