tuile 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +11 -0
- data/README.md +4 -1
- data/examples/sampler.rb +33 -0
- data/lib/tuile/ansi.rb +14 -0
- data/lib/tuile/component/label.rb +64 -26
- data/lib/tuile/component/list.rb +155 -69
- data/lib/tuile/component/text_area.rb +1 -3
- data/lib/tuile/component/text_field.rb +1 -4
- data/lib/tuile/component/text_view.rb +351 -0
- data/lib/tuile/component/window.rb +2 -2
- data/lib/tuile/styled_string.rb +761 -0
- data/lib/tuile/version.rb +1 -1
- data/sig/tuile.rbs +640 -85
- metadata +4 -2
- data/lib/tuile/truncate.rb +0 -83
data/sig/tuile.rbs
CHANGED
|
@@ -16,6 +16,14 @@ module Tuile
|
|
|
16
16
|
class Error < StandardError
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
+
# ANSI escape sequence constants. Tuile emits colors and text attributes
|
|
20
|
+
# via Rainbow, which produces **SGR** sequences ("Select Graphic
|
|
21
|
+
# Rendition", `ESC [ <params> m` — e.g. `\e[31m` red, `\e[1m` bold,
|
|
22
|
+
# `\e[0m` reset).
|
|
23
|
+
module Ansi
|
|
24
|
+
RESET: String
|
|
25
|
+
end
|
|
26
|
+
|
|
19
27
|
# Constants for keys returned by {.getkey} and helpers for reading them from
|
|
20
28
|
# stdin. The constants are the raw escape sequences emitted by the terminal;
|
|
21
29
|
# see https://en.wikipedia.org/wiki/ANSI_escape_code for the encoding.
|
|
@@ -373,39 +381,6 @@ module Tuile
|
|
|
373
381
|
attr_accessor focused: Component?
|
|
374
382
|
end
|
|
375
383
|
|
|
376
|
-
# Truncates a string to a given column width, preserving ANSI escape
|
|
377
|
-
# sequences and accounting for Unicode display width. Truncated output is
|
|
378
|
-
# suffixed with an ellipsis (`…`).
|
|
379
|
-
#
|
|
380
|
-
# Extracted from `strings-truncation` 0.1.0 (MIT, Piotr Murach) — only the
|
|
381
|
-
# default end-position, default-omission, no-separator path Tuile uses.
|
|
382
|
-
module Truncate
|
|
383
|
-
ANSI_REGEXP: Regexp
|
|
384
|
-
RESET: String
|
|
385
|
-
RESET_REGEXP: Regexp
|
|
386
|
-
END_REGEXP: Regexp
|
|
387
|
-
OMISSION: String
|
|
388
|
-
OMISSION_WIDTH: Integer
|
|
389
|
-
|
|
390
|
-
# Truncate `text` to at most `length` display columns. ANSI escape
|
|
391
|
-
# sequences pass through without consuming budget; when characters are
|
|
392
|
-
# dropped, an ellipsis (`…`) is appended (and counts toward `length`).
|
|
393
|
-
#
|
|
394
|
-
# _@param_ `text`
|
|
395
|
-
#
|
|
396
|
-
# _@param_ `length` — target column width. A `nil` returns `text` unchanged.
|
|
397
|
-
def truncate: (String text, length: Integer?) -> String
|
|
398
|
-
|
|
399
|
-
# Truncate `text` to at most `length` display columns. ANSI escape
|
|
400
|
-
# sequences pass through without consuming budget; when characters are
|
|
401
|
-
# dropped, an ellipsis (`…`) is appended (and counts toward `length`).
|
|
402
|
-
#
|
|
403
|
-
# _@param_ `text`
|
|
404
|
-
#
|
|
405
|
-
# _@param_ `length` — target column width. A `nil` returns `text` unchanged.
|
|
406
|
-
def self.truncate: (String text, length: Integer?) -> String
|
|
407
|
-
end
|
|
408
|
-
|
|
409
384
|
# A UI component which is positioned on the screen and draws characters into
|
|
410
385
|
# its bounding rectangle (in {#repaint}).
|
|
411
386
|
#
|
|
@@ -589,24 +564,34 @@ module Tuile
|
|
|
589
564
|
# no parent.
|
|
590
565
|
attr_accessor parent: Component?
|
|
591
566
|
|
|
592
|
-
# A scrollable list of
|
|
567
|
+
# A scrollable list of items with cursor support.
|
|
593
568
|
#
|
|
594
|
-
# Items are
|
|
595
|
-
#
|
|
596
|
-
# {#
|
|
597
|
-
#
|
|
569
|
+
# Items are modeled as {StyledString}s and painted directly into the
|
|
570
|
+
# component's {#rect}. Lines wider than the viewport are ellipsized via
|
|
571
|
+
# {StyledString#ellipsize} (span styles are preserved across the cut —
|
|
572
|
+
# unlike the older ANSI-as-bytes truncation, color does *not* get
|
|
573
|
+
# dropped on the surviving characters). Vertical scrolling is supported
|
|
574
|
+
# via {#top_line}; the list can also automatically scroll to the bottom
|
|
575
|
+
# if {#auto_scroll} is enabled.
|
|
598
576
|
#
|
|
599
577
|
# Cursor is supported; call {#cursor=} to change cursor behavior. The
|
|
600
|
-
# cursor responds to arrows, `jk`, Home/End, Ctrl+U/D and scrolls the
|
|
601
|
-
# automatically.
|
|
578
|
+
# cursor responds to arrows, `jk`, Home/End, Ctrl+U/D and scrolls the
|
|
579
|
+
# list automatically. The cursor highlight overlays a dark background
|
|
580
|
+
# while preserving each span's foreground color.
|
|
602
581
|
class List < Component
|
|
582
|
+
CURSOR_BG: Integer
|
|
583
|
+
|
|
603
584
|
def initialize: () -> void
|
|
604
585
|
|
|
605
|
-
# Sets new lines. Each entry is coerced
|
|
606
|
-
#
|
|
607
|
-
# {
|
|
586
|
+
# Sets new lines. Each entry is coerced into a {StyledString} (a
|
|
587
|
+
# `String` is parsed via {StyledString.parse}, so embedded ANSI is
|
|
588
|
+
# honored; a {StyledString} is used as-is; anything else is stringified
|
|
589
|
+
# via `#to_s` first), then split on `\n` into separate lines via
|
|
590
|
+
# {StyledString#lines}, with trailing empty pieces dropped and trailing
|
|
591
|
+
# ASCII whitespace stripped — symmetric with {#add_lines}, so the
|
|
592
|
+
# stored `@lines` is always `Array<StyledString>`.
|
|
608
593
|
#
|
|
609
|
-
# _@param_ `lines` —
|
|
594
|
+
# _@param_ `lines` — entries are `String`, `StyledString`, or anything that responds to `#to_s`.
|
|
610
595
|
def lines=: (::Array[untyped] lines) -> void
|
|
611
596
|
|
|
612
597
|
# Without a block, returns the current lines. With a block, fully
|
|
@@ -617,19 +602,21 @@ module Tuile
|
|
|
617
602
|
# end
|
|
618
603
|
# ```
|
|
619
604
|
#
|
|
620
|
-
# _@return_ — current lines (when called without a
|
|
621
|
-
|
|
605
|
+
# _@return_ — current lines (when called without a
|
|
606
|
+
# block).
|
|
607
|
+
def lines: () ?{ (::Array[untyped] buffer) -> void } -> ::Array[StyledString]
|
|
622
608
|
|
|
609
|
+
# sord duck - #to_s looks like a duck type with an equivalent RBS interface, replacing with _ToS
|
|
623
610
|
# Adds a line.
|
|
624
611
|
#
|
|
625
612
|
# _@param_ `line`
|
|
626
|
-
def add_line: (String line) -> void
|
|
613
|
+
def add_line: ((String | StyledString | _ToS) line) -> void
|
|
627
614
|
|
|
628
|
-
# Appends given lines. Each entry is
|
|
629
|
-
#
|
|
630
|
-
#
|
|
615
|
+
# Appends given lines. Each entry is parsed the same way as in
|
|
616
|
+
# {#lines=}: coerced to a {StyledString}, split on `\n`, with trailing
|
|
617
|
+
# empty pieces dropped and trailing ASCII whitespace stripped.
|
|
631
618
|
#
|
|
632
|
-
# _@param_ `lines` — entries
|
|
619
|
+
# _@param_ `lines` — entries are `String`, `StyledString`, or anything that responds to `#to_s`.
|
|
633
620
|
def add_lines: (::Array[untyped] lines) -> void
|
|
634
621
|
|
|
635
622
|
def content_size: () -> Size
|
|
@@ -646,6 +633,8 @@ module Tuile
|
|
|
646
633
|
# Moves the cursor to the next line whose text contains `query`
|
|
647
634
|
# (case-insensitive substring match). Search wraps around the end of the
|
|
648
635
|
# list. Only lines reachable by the current {#cursor} are considered.
|
|
636
|
+
# Matching uses the line's plain text — span styles do not affect the
|
|
637
|
+
# match.
|
|
649
638
|
#
|
|
650
639
|
# _@param_ `query` — substring to match. Empty query never matches.
|
|
651
640
|
#
|
|
@@ -669,11 +658,38 @@ module Tuile
|
|
|
669
658
|
# Paints the list items into {#rect}.
|
|
670
659
|
#
|
|
671
660
|
# Skips the {Component#repaint} default's auto-clear: every row of
|
|
672
|
-
# {#rect} is painted below (with
|
|
661
|
+
# {#rect} is painted below (with blank padding past the last item),
|
|
673
662
|
# so the parent contract — "fully draw over your rect" — is met
|
|
674
663
|
# without an upfront wipe.
|
|
675
664
|
def repaint: () -> void
|
|
676
665
|
|
|
666
|
+
# Rebuilds pre-padded lines when the wrap width changes. The wrap width
|
|
667
|
+
# depends on {#rect}`.width` and the scrollbar gutter, both of which
|
|
668
|
+
# trigger this hook.
|
|
669
|
+
def on_width_changed: () -> void
|
|
670
|
+
|
|
671
|
+
# Coerces and flattens a list of input entries into trimmed
|
|
672
|
+
# {StyledString} lines. Each entry becomes a {StyledString} (String
|
|
673
|
+
# via {StyledString.parse}, StyledString passed through, anything else
|
|
674
|
+
# via `#to_s`), then split on `\n` via {StyledString#lines} — with
|
|
675
|
+
# trailing empty pieces dropped (matching `String#split("\n")`'s
|
|
676
|
+
# default behavior, so `add_line ""` is a no-op) — and trailing ASCII
|
|
677
|
+
# whitespace stripped on each resulting line.
|
|
678
|
+
#
|
|
679
|
+
# _@param_ `entries`
|
|
680
|
+
def parse_input_lines: (::Array[untyped] entries) -> ::Array[StyledString]
|
|
681
|
+
|
|
682
|
+
# _@param_ `entry`
|
|
683
|
+
def split_to_lines: (Object entry) -> ::Array[StyledString]
|
|
684
|
+
|
|
685
|
+
# Returns `line` with trailing ASCII whitespace (space/tab) dropped,
|
|
686
|
+
# preserving span styles on the surviving prefix. Whitespace chars are
|
|
687
|
+
# all single-column ASCII, so byte-count delta equals column-count
|
|
688
|
+
# delta and {StyledString#slice} can do the cut.
|
|
689
|
+
#
|
|
690
|
+
# _@param_ `line`
|
|
691
|
+
def rstrip_styled: (StyledString line) -> StyledString
|
|
692
|
+
|
|
677
693
|
# _@return_ — true if the cursor sits on a real content line.
|
|
678
694
|
def cursor_on_item?: () -> bool
|
|
679
695
|
|
|
@@ -681,9 +697,9 @@ module Tuile
|
|
|
681
697
|
# Caller must ensure {#cursor_on_item?}.
|
|
682
698
|
def fire_item_chosen: () -> void
|
|
683
699
|
|
|
684
|
-
# _@return_ — `[position, line_at_position]`,
|
|
685
|
-
#
|
|
686
|
-
def cursor_state: () -> [[Integer,
|
|
700
|
+
# _@return_ — `[position, line_at_position]`, with `line` nil when the cursor is
|
|
701
|
+
# off-content.
|
|
702
|
+
def cursor_state: () -> [[Integer, StyledString, NilClass]]
|
|
687
703
|
|
|
688
704
|
# Fires {#on_cursor_changed} if {#cursor_state} differs from the last
|
|
689
705
|
# fired state. Idempotent — safe to call after any mutation.
|
|
@@ -734,45 +750,53 @@ module Tuile
|
|
|
734
750
|
# _@return_ — whether the scrollbar should be drawn right now.
|
|
735
751
|
def scrollbar_visible?: () -> bool
|
|
736
752
|
|
|
737
|
-
#
|
|
753
|
+
# _@return_ — column width available for line content (rect width
|
|
754
|
+
# minus the scrollbar gutter, when visible). `0` when {#rect}'s width
|
|
755
|
+
# is non-positive.
|
|
756
|
+
def content_width: () -> Integer
|
|
757
|
+
|
|
758
|
+
# Recomputes {@padded_lines} for the current rect width and scrollbar
|
|
759
|
+
# visibility. Each line is ellipsized to fit and pre-padded with
|
|
760
|
+
# single-space gutters on each side, so {#paintable_line} only has to
|
|
761
|
+
# apply the cursor highlight (if any) and append the scrollbar glyph.
|
|
762
|
+
def rebuild_padded_lines: () -> void
|
|
763
|
+
|
|
764
|
+
# Pads `line` to one full row of the viewport (scrollbar gutter
|
|
765
|
+
# excluded). Lines wider than the content area are ellipsized via
|
|
766
|
+
# {StyledString#ellipsize} (span styles survive the cut); shorter
|
|
767
|
+
# lines are padded with default-styled spaces.
|
|
738
768
|
#
|
|
739
|
-
# _@param_ `
|
|
769
|
+
# _@param_ `line`
|
|
740
770
|
#
|
|
741
|
-
# _@
|
|
742
|
-
|
|
771
|
+
# _@return_ — exactly {#content_width} display columns wide
|
|
772
|
+
# (or {StyledString::EMPTY} when content_width is non-positive).
|
|
773
|
+
def pad_to_row: (StyledString line) -> StyledString
|
|
743
774
|
|
|
744
775
|
# _@param_ `index` — 0-based index into {#lines}.
|
|
745
776
|
#
|
|
746
777
|
# _@param_ `row_in_viewport` — 0-based row within the viewport.
|
|
747
778
|
#
|
|
748
|
-
# _@param_ `width` — number of columns the line should occupy.
|
|
749
|
-
#
|
|
750
779
|
# _@param_ `scrollbar` — scrollbar instance, or nil if not shown.
|
|
751
780
|
#
|
|
752
|
-
# _@return_ — paintable line exactly `width`
|
|
753
|
-
# highlighted if cursor is here.
|
|
754
|
-
def paintable_line: (
|
|
755
|
-
Integer index,
|
|
756
|
-
Integer row_in_viewport,
|
|
757
|
-
Integer width,
|
|
758
|
-
VerticalScrollBar? scrollbar
|
|
759
|
-
) -> String
|
|
781
|
+
# _@return_ — paintable ANSI-encoded line exactly `rect.width`
|
|
782
|
+
# columns wide; highlighted if cursor is here.
|
|
783
|
+
def paintable_line: (Integer index, Integer row_in_viewport, VerticalScrollBar? scrollbar) -> String
|
|
760
784
|
|
|
761
785
|
# _@return_ — callback fired when an item is chosen — by pressing
|
|
762
786
|
# Enter on the cursor's item, or by left-clicking an item. Called as
|
|
763
|
-
# `proc.call(index, line)` with the chosen 0-based index and its
|
|
764
|
-
# Never fires when the cursor's position is
|
|
765
|
-
# {Cursor::None}, or empty content).
|
|
787
|
+
# `proc.call(index, line)` with the chosen 0-based index and its
|
|
788
|
+
# {StyledString} line. Never fires when the cursor's position is
|
|
789
|
+
# outside the content (e.g. {Cursor::None}, or empty content).
|
|
766
790
|
attr_accessor on_item_chosen: Proc?
|
|
767
791
|
|
|
768
792
|
# _@return_ — callback fired when the `(index, line)` tuple under
|
|
769
793
|
# the cursor changes. Called as `proc.call(index, line)` where `line`
|
|
770
|
-
# is `nil` when the cursor is
|
|
771
|
-
# or `index` past the last
|
|
772
|
-
#
|
|
773
|
-
# at the cursor's index
|
|
774
|
-
# flips). Useful for
|
|
775
|
-
# highlighted row.
|
|
794
|
+
# is the {StyledString} at the cursor, or `nil` when the cursor is
|
|
795
|
+
# off-content ({Cursor::None}, empty list, or `index` past the last
|
|
796
|
+
# line). Fires on cursor moves (key, mouse, search), on {#cursor=},
|
|
797
|
+
# and on {#lines=}/{#add_lines} when the line at the cursor's index
|
|
798
|
+
# changes (or its in-range/out-of-range status flips). Useful for
|
|
799
|
+
# keeping a details pane in sync with the highlighted row.
|
|
776
800
|
attr_accessor on_cursor_changed: Proc?
|
|
777
801
|
|
|
778
802
|
# _@return_ — if true and a line is added or new content is set,
|
|
@@ -900,20 +924,44 @@ module Tuile
|
|
|
900
924
|
end
|
|
901
925
|
end
|
|
902
926
|
|
|
903
|
-
# A label which shows static text. No word-wrapping;
|
|
927
|
+
# A label which shows static text. No word-wrapping; long lines are
|
|
928
|
+
# truncated with an ellipsis. Text is modeled as a {StyledString};
|
|
929
|
+
# {#text=} accepts a {String} (parsed via {StyledString.parse}, so
|
|
930
|
+
# embedded ANSI is honored) or a {StyledString} directly. {#text}
|
|
931
|
+
# always returns the {StyledString}.
|
|
904
932
|
class Label < Component
|
|
905
933
|
def initialize: () -> void
|
|
906
934
|
|
|
907
|
-
# _@
|
|
908
|
-
|
|
909
|
-
|
|
935
|
+
# _@return_ — longest hard-line's display width × number of hard
|
|
936
|
+
# lines. Reported on the *unclipped* text — sizing is intrinsic to
|
|
937
|
+
# the content, not the viewport. Empty text returns `Size.new(0, 0)`.
|
|
910
938
|
def content_size: () -> Size
|
|
911
939
|
|
|
940
|
+
# Paints the text into {#rect}.
|
|
941
|
+
#
|
|
942
|
+
# Skips the {Component#repaint} default's auto-clear: every row is
|
|
943
|
+
# painted explicitly (with pre-padded blanks past the last line), so
|
|
944
|
+
# the "fully draw over your rect" contract is met without an upfront
|
|
945
|
+
# wipe.
|
|
912
946
|
def repaint: () -> void
|
|
913
947
|
|
|
914
948
|
def on_width_changed: () -> void
|
|
915
949
|
|
|
916
|
-
|
|
950
|
+
# Recomputes {@clipped_lines} for the current text and rect width.
|
|
951
|
+
# Each line is ellipsized to fit, padded with trailing spaces out to
|
|
952
|
+
# the full width, and pre-rendered to ANSI so {#repaint} is just a
|
|
953
|
+
# lookup + screen.print per row. {@blank_line} covers rows past the
|
|
954
|
+
# last text line.
|
|
955
|
+
def update_clipped_lines: () -> void
|
|
956
|
+
|
|
957
|
+
# _@param_ `line`
|
|
958
|
+
#
|
|
959
|
+
# _@param_ `width`
|
|
960
|
+
def pad_to: (StyledString line, Integer width) -> StyledString
|
|
961
|
+
|
|
962
|
+
# _@return_ — the current text. Defaults to an empty
|
|
963
|
+
# {StyledString}.
|
|
964
|
+
attr_accessor text: (StyledString | String)?
|
|
917
965
|
end
|
|
918
966
|
|
|
919
967
|
# A modal overlay that wraps any {Component} as its content. Popup itself
|
|
@@ -1199,7 +1247,6 @@ module Tuile
|
|
|
1199
1247
|
class TextArea < Component
|
|
1200
1248
|
ACTIVE_BG_SGR: String
|
|
1201
1249
|
INACTIVE_BG_SGR: String
|
|
1202
|
-
SGR_RESET: String
|
|
1203
1250
|
|
|
1204
1251
|
def initialize: () -> void
|
|
1205
1252
|
|
|
@@ -1293,6 +1340,169 @@ module Tuile
|
|
|
1293
1340
|
attr_accessor on_change: (Proc | Method)?
|
|
1294
1341
|
end
|
|
1295
1342
|
|
|
1343
|
+
# A read-only viewer for prose: chunks of formatted text that scroll
|
|
1344
|
+
# vertically. Shape-wise a hybrid between {Label} (string-shaped content
|
|
1345
|
+
# via {#text=}) and {List} (scroll keys, optional scrollbar, auto-scroll).
|
|
1346
|
+
#
|
|
1347
|
+
# Text is modeled as a {StyledString}: embedded `\n` are hard line breaks,
|
|
1348
|
+
# lines wider than the viewport are word-wrapped via {StyledString#wrap}
|
|
1349
|
+
# (style spans are preserved across wrap boundaries — unlike the older
|
|
1350
|
+
# ANSI-as-bytes wrapping, color does *not* get dropped on continuation
|
|
1351
|
+
# rows). {#text=} accepts a {String} (parsed via {StyledString.parse},
|
|
1352
|
+
# so embedded ANSI is honored) or a {StyledString} directly; {#text}
|
|
1353
|
+
# always returns the {StyledString}. Use {#append} for incremental "log
|
|
1354
|
+
# line" style updates; turn on {#auto_scroll} to keep the latest content
|
|
1355
|
+
# in view.
|
|
1356
|
+
#
|
|
1357
|
+
# TextView is meant to be the content of a {Window} — focus indication and
|
|
1358
|
+
# keyboard-hint surfacing rely on the surrounding window chrome.
|
|
1359
|
+
class TextView < Component
|
|
1360
|
+
def initialize: () -> void
|
|
1361
|
+
|
|
1362
|
+
# _@return_ — the current text. Defaults to an empty
|
|
1363
|
+
# {StyledString}. Internally the text is stored as an array of hard
|
|
1364
|
+
# lines so {#append} can stay O(appended) instead of re-scanning the
|
|
1365
|
+
# whole buffer; the joined {StyledString} returned here is
|
|
1366
|
+
# reconstructed on first read after a mutation and cached, so
|
|
1367
|
+
# repeated reads are O(1) but the first read after {#append} pays
|
|
1368
|
+
# O(total spans).
|
|
1369
|
+
def text: () -> StyledString
|
|
1370
|
+
|
|
1371
|
+
# Replaces the text. Embedded `\n` characters become hard line breaks.
|
|
1372
|
+
# A `String` is parsed via {StyledString.parse} (so embedded ANSI is
|
|
1373
|
+
# honored); a `StyledString` is used as-is; `nil` is coerced to an
|
|
1374
|
+
# empty {StyledString}.
|
|
1375
|
+
#
|
|
1376
|
+
# _@param_ `value`
|
|
1377
|
+
def text=: ((String | StyledString)? value) -> void
|
|
1378
|
+
|
|
1379
|
+
# Appends `str` as a new physical line. If the current text is empty,
|
|
1380
|
+
# behaves like `text = str`; otherwise prepends a newline so the new
|
|
1381
|
+
# content lands on a fresh line. Accepts the same input forms as
|
|
1382
|
+
# {#text=}.
|
|
1383
|
+
#
|
|
1384
|
+
# Cost is O(appended) rather than O(total) — the existing wrapped
|
|
1385
|
+
# buffer is reused, only the new hard line(s) are wrapped and padded,
|
|
1386
|
+
# and `@content_size` is updated incrementally. The cached
|
|
1387
|
+
# {#text} is invalidated and rebuilt on demand.
|
|
1388
|
+
#
|
|
1389
|
+
# _@param_ `str`
|
|
1390
|
+
def append: ((String | StyledString)? str) -> void
|
|
1391
|
+
|
|
1392
|
+
# Clears the text. Equivalent to `text = ""`.
|
|
1393
|
+
def clear: () -> void
|
|
1394
|
+
|
|
1395
|
+
def focusable?: () -> bool
|
|
1396
|
+
|
|
1397
|
+
def tab_stop?: () -> bool
|
|
1398
|
+
|
|
1399
|
+
# _@param_ `key`
|
|
1400
|
+
def handle_key: (String key) -> bool
|
|
1401
|
+
|
|
1402
|
+
# _@param_ `event`
|
|
1403
|
+
def handle_mouse: (MouseEvent event) -> void
|
|
1404
|
+
|
|
1405
|
+
# Paints the text into {#rect}.
|
|
1406
|
+
#
|
|
1407
|
+
# Skips the {Component#repaint} default's auto-clear: every row is
|
|
1408
|
+
# painted explicitly (with padded blanks past the last line), so the
|
|
1409
|
+
# "fully draw over your rect" contract is met without an upfront wipe.
|
|
1410
|
+
def repaint: () -> void
|
|
1411
|
+
|
|
1412
|
+
# Rewraps the text on width changes. Wrap width depends on
|
|
1413
|
+
# {#rect}`.width` and the scrollbar gutter, both of which trigger
|
|
1414
|
+
# this hook.
|
|
1415
|
+
def on_width_changed: () -> void
|
|
1416
|
+
|
|
1417
|
+
# _@return_ — number of visible lines.
|
|
1418
|
+
def viewport_lines: () -> Integer
|
|
1419
|
+
|
|
1420
|
+
# _@return_ — the max value of {#top_line} for scroll-key clamping.
|
|
1421
|
+
def top_line_max: () -> Integer
|
|
1422
|
+
|
|
1423
|
+
# Recomputes {@physical_lines} for the current text and wrap width,
|
|
1424
|
+
# pre-padding every line to `wrap_width` so {#paintable_line} is just
|
|
1425
|
+
# a lookup + optional scrollbar-char append at paint time (and the
|
|
1426
|
+
# rendered ANSI is cached on each line via {StyledString#to_ansi}'s
|
|
1427
|
+
# memoization, so re-painting on scroll is near-free). Clamps
|
|
1428
|
+
# {@top_line} if the new line count puts it out of range.
|
|
1429
|
+
def rewrap: () -> void
|
|
1430
|
+
|
|
1431
|
+
# Wraps `hard_line` at `width` and appends the padded physical lines
|
|
1432
|
+
# to {@physical_lines}. Empty hard lines (e.g. from a `"\n\n"` run)
|
|
1433
|
+
# and degenerate `width <= 0` both emit a single {@blank_line} row,
|
|
1434
|
+
# matching what `@text.wrap(width).map { |l| pad_to(l, width) }`
|
|
1435
|
+
# would have produced for those cases.
|
|
1436
|
+
#
|
|
1437
|
+
# _@param_ `hard_line` — one hard-broken line (no embedded `"\n"`).
|
|
1438
|
+
#
|
|
1439
|
+
# _@param_ `width`
|
|
1440
|
+
def append_physical_lines: (StyledString hard_line, Integer width) -> void
|
|
1441
|
+
|
|
1442
|
+
# Rebuilds the joined {StyledString} from {@hard_lines}, inserting a
|
|
1443
|
+
# default-styled `"\n"` between hard lines. Called from the {#text}
|
|
1444
|
+
# reader when the cache is cold. Cost is O(total spans).
|
|
1445
|
+
def build_text: () -> StyledString
|
|
1446
|
+
|
|
1447
|
+
# _@return_ — {#content_size} computed from {@hard_lines}.
|
|
1448
|
+
def compute_content_size: () -> Size
|
|
1449
|
+
|
|
1450
|
+
# _@return_ — column width available for wrapped text — viewport
|
|
1451
|
+
# width minus the scrollbar gutter (when visible). `0` when {#rect}'s
|
|
1452
|
+
# width is non-positive, which yields a degenerate "no wrap" result.
|
|
1453
|
+
def wrap_width: () -> Integer
|
|
1454
|
+
|
|
1455
|
+
# _@param_ `delta` — negative scrolls up, positive scrolls down.
|
|
1456
|
+
def move_top_line_by: (Integer delta) -> void
|
|
1457
|
+
|
|
1458
|
+
# _@param_ `target` — desired top line; clamped to `[0, top_line_max]`.
|
|
1459
|
+
def move_top_line_to: (Integer target) -> void
|
|
1460
|
+
|
|
1461
|
+
def update_top_line_if_auto_scroll: () -> void
|
|
1462
|
+
|
|
1463
|
+
def scrollbar_visible?: () -> bool
|
|
1464
|
+
|
|
1465
|
+
# Pads `line` with trailing default-styled spaces out to `width` display
|
|
1466
|
+
# columns. Callers rely on {StyledString#wrap} having already
|
|
1467
|
+
# constrained the line to `<= width`, so no truncation is performed.
|
|
1468
|
+
# `width <= 0` returns {StyledString::EMPTY} to handle the degenerate
|
|
1469
|
+
# `wrap_width == 0` case (rect.width == 1 with scrollbar).
|
|
1470
|
+
#
|
|
1471
|
+
# _@param_ `line`
|
|
1472
|
+
#
|
|
1473
|
+
# _@param_ `width`
|
|
1474
|
+
def pad_to: (StyledString line, Integer width) -> StyledString
|
|
1475
|
+
|
|
1476
|
+
# _@param_ `index` — 0-based index into `@physical_lines`.
|
|
1477
|
+
#
|
|
1478
|
+
# _@param_ `row_in_viewport` — 0-based row within the viewport.
|
|
1479
|
+
#
|
|
1480
|
+
# _@param_ `scrollbar`
|
|
1481
|
+
#
|
|
1482
|
+
# _@return_ — paintable ANSI-encoded line exactly `rect.width`
|
|
1483
|
+
# columns wide. Body lines come pre-padded from {#rewrap}, so this
|
|
1484
|
+
# reduces to a memoized {StyledString#to_ansi} read plus an
|
|
1485
|
+
# ASCII-string concat of the scrollbar glyph when one is present.
|
|
1486
|
+
def paintable_line: (Integer index, Integer row_in_viewport, VerticalScrollBar? scrollbar) -> String
|
|
1487
|
+
|
|
1488
|
+
# _@return_ — index of the first visible physical line.
|
|
1489
|
+
attr_accessor top_line: Integer
|
|
1490
|
+
|
|
1491
|
+
# _@return_ — `:gone` or `:visible`.
|
|
1492
|
+
attr_accessor scrollbar_visibility: Symbol
|
|
1493
|
+
|
|
1494
|
+
# _@return_ — if true, mutating the text scrolls the viewport so
|
|
1495
|
+
# the last line stays in view. Default `false`.
|
|
1496
|
+
attr_accessor auto_scroll: bool
|
|
1497
|
+
|
|
1498
|
+
# _@return_ — longest hard-line's display width × number of hard
|
|
1499
|
+
# lines. Reported on the *unwrapped* text — wrap-aware sizing would
|
|
1500
|
+
# be circular (width depends on width). Empty text returns
|
|
1501
|
+
# `Size.new(0, 0)`. Maintained incrementally by {#text=} and
|
|
1502
|
+
# {#append}, so reads are O(1).
|
|
1503
|
+
attr_reader content_size: Size
|
|
1504
|
+
end
|
|
1505
|
+
|
|
1296
1506
|
# Shows a log. Construct your logger pointed at a {LogWindow::IO} to route
|
|
1297
1507
|
# log lines into this window:
|
|
1298
1508
|
#
|
|
@@ -1338,7 +1548,6 @@ module Tuile
|
|
|
1338
1548
|
class TextField < Component
|
|
1339
1549
|
ACTIVE_BG_SGR: String
|
|
1340
1550
|
INACTIVE_BG_SGR: String
|
|
1341
|
-
SGR_RESET: String
|
|
1342
1551
|
|
|
1343
1552
|
def initialize: () -> void
|
|
1344
1553
|
|
|
@@ -1801,6 +2010,352 @@ module Tuile
|
|
|
1801
2010
|
attr_reader status_bar: Component::Label
|
|
1802
2011
|
end
|
|
1803
2012
|
|
|
2013
|
+
# An immutable string-with-styling, modeled as a sequence of {Span}s where
|
|
2014
|
+
# each span carries a complete {Style} (`fg`, `bg`, `bold`, `italic`,
|
|
2015
|
+
# `underline`). Spans are non-overlapping and fully tile the string — every
|
|
2016
|
+
# character has exactly one resolved style, no overlay layers to merge.
|
|
2017
|
+
#
|
|
2018
|
+
# Where this differs from threading SGR escapes through a plain `String`:
|
|
2019
|
+
# slicing, wrapping, and concatenation operate on the structured spans, so
|
|
2020
|
+
# they never have to "figure out what SGR state is active at column N" —
|
|
2021
|
+
# the answer is just the containing span's `style`. The flip side is one
|
|
2022
|
+
# extra type to construct (or parse) before doing styled-text math.
|
|
2023
|
+
#
|
|
2024
|
+
# ## Constructors
|
|
2025
|
+
#
|
|
2026
|
+
# ```ruby
|
|
2027
|
+
# StyledString.new # empty
|
|
2028
|
+
# StyledString.plain("hello") # default style
|
|
2029
|
+
# StyledString.styled("hello", fg: :red, bold: true)
|
|
2030
|
+
# StyledString.parse("\e[31mhello\e[0m world") # ANSI → spans
|
|
2031
|
+
# ```
|
|
2032
|
+
#
|
|
2033
|
+
# ## Algebra
|
|
2034
|
+
#
|
|
2035
|
+
# All operations return a fresh {StyledString} — the underlying spans are
|
|
2036
|
+
# frozen and shared. `+` coerces a `String` operand via {.parse}.
|
|
2037
|
+
#
|
|
2038
|
+
# ```ruby
|
|
2039
|
+
# a + b # concatenate
|
|
2040
|
+
# ss.slice(2, 5) # 5 display columns starting at column 2
|
|
2041
|
+
# ss.slice(2..5) # range (inclusive end)
|
|
2042
|
+
# ss.lines # split on "\n" → Array<StyledString>
|
|
2043
|
+
# ss.each_char_with_style { |ch, style| ... }
|
|
2044
|
+
# ```
|
|
2045
|
+
#
|
|
2046
|
+
# ## Rendering
|
|
2047
|
+
#
|
|
2048
|
+
# - `#to_s` — plain text, no SGR.
|
|
2049
|
+
# - `#to_ansi` — minimal-diff SGR rendering, ending with `\e[0m` only when
|
|
2050
|
+
# the last span carried a non-default style. Transitions to the default
|
|
2051
|
+
# style emit `\e[0m` (shorter than re-emitting every off-code).
|
|
2052
|
+
#
|
|
2053
|
+
# ## Parser
|
|
2054
|
+
#
|
|
2055
|
+
# {.parse} is strict by design: it recognizes only the SGR codes
|
|
2056
|
+
# corresponding to {Style}'s supported attributes (fg/bg/bold/italic/
|
|
2057
|
+
# underline). Anything else — unmodeled attributes (dim, blink, reverse,
|
|
2058
|
+
# strike, conceal, double-underline, overline, ...), unknown SGR codes, or
|
|
2059
|
+
# non-SGR escapes (cursor moves, OSC) — raises {ParseError}. This keeps the
|
|
2060
|
+
# round-trip parse(to_ansi(x)) == x contract honest.
|
|
2061
|
+
class StyledString
|
|
2062
|
+
EMPTY: StyledString
|
|
2063
|
+
|
|
2064
|
+
# sord duck - #to_s looks like a duck type with an equivalent RBS interface, replacing with _ToS
|
|
2065
|
+
# _@param_ `text`
|
|
2066
|
+
def self.plain: (_ToS text) -> StyledString
|
|
2067
|
+
|
|
2068
|
+
# sord duck - #to_s looks like a duck type with an equivalent RBS interface, replacing with _ToS
|
|
2069
|
+
# _@param_ `text`
|
|
2070
|
+
#
|
|
2071
|
+
# _@param_ `style_kwargs` — forwarded to {Style.new}.
|
|
2072
|
+
def self.styled: (_ToS text, **::Hash[Symbol, Object] style_kwargs) -> StyledString
|
|
2073
|
+
|
|
2074
|
+
# Parses an ANSI/SGR-coded string into a {StyledString}. A {StyledString}
|
|
2075
|
+
# input is returned as-is. `nil` and the empty string both fast-path to
|
|
2076
|
+
# {EMPTY}. Strings without any `\e` byte fast-path to a single
|
|
2077
|
+
# default-styled span.
|
|
2078
|
+
#
|
|
2079
|
+
# _@param_ `input`
|
|
2080
|
+
def self.parse: ((String | StyledString)? input) -> StyledString
|
|
2081
|
+
|
|
2082
|
+
# _@param_ `spans`
|
|
2083
|
+
def initialize: (?::Array[Span] spans) -> void
|
|
2084
|
+
|
|
2085
|
+
# Total display width in terminal columns, accounting for Unicode wide
|
|
2086
|
+
# characters (fullwidth CJK = 2 columns, combining marks = 0, etc.).
|
|
2087
|
+
# Memoized — safe because spans are frozen and immutable.
|
|
2088
|
+
def display_width: () -> Integer
|
|
2089
|
+
|
|
2090
|
+
def empty?: () -> bool
|
|
2091
|
+
|
|
2092
|
+
# Plain text concatenation across all spans — no SGR codes.
|
|
2093
|
+
def to_s: () -> String
|
|
2094
|
+
|
|
2095
|
+
# Rendered ANSI string. Minimal-diff between adjacent spans: only the
|
|
2096
|
+
# attributes that changed are emitted. A transition to the default style
|
|
2097
|
+
# emits `\e[0m` (one code) instead of the longer "turn each attribute
|
|
2098
|
+
# off" form. Always closes with `\e[0m` when the last span carried a
|
|
2099
|
+
# non-default style, so the styled run doesn't bleed into subsequent
|
|
2100
|
+
# output. Memoized — safe because spans are frozen and immutable.
|
|
2101
|
+
def to_ansi: () -> String
|
|
2102
|
+
|
|
2103
|
+
# _@param_ `other`
|
|
2104
|
+
def ==: (Object other) -> bool
|
|
2105
|
+
|
|
2106
|
+
def hash: () -> Integer
|
|
2107
|
+
|
|
2108
|
+
# Concatenation. A `String` operand is parsed via {.parse} before joining
|
|
2109
|
+
# (so embedded ANSI escapes round-trip through spans).
|
|
2110
|
+
#
|
|
2111
|
+
# _@param_ `other`
|
|
2112
|
+
def +: ((StyledString | String) other) -> StyledString
|
|
2113
|
+
|
|
2114
|
+
# Substring by display columns, preserving spans. Characters whose column
|
|
2115
|
+
# range only partially overlaps the slice (e.g. a 2-column CJK character
|
|
2116
|
+
# straddling the start or end boundary) are dropped — never split.
|
|
2117
|
+
#
|
|
2118
|
+
# Accepts either `slice(start_col, len_col)` or `slice(range)`. Both
|
|
2119
|
+
# forms support negative indices counting from the end of the string.
|
|
2120
|
+
#
|
|
2121
|
+
# _@param_ `start_col`
|
|
2122
|
+
#
|
|
2123
|
+
# _@param_ `len_col`
|
|
2124
|
+
def slice: (Integer start_col, Integer len_col) -> StyledString
|
|
2125
|
+
|
|
2126
|
+
# Truncates to a target column width, appending an ellipsis when
|
|
2127
|
+
# characters were dropped. The ellipsis counts toward the target — the
|
|
2128
|
+
# returned {StyledString}'s `display_width` never exceeds
|
|
2129
|
+
# `display_width`. When `self` already fits, `self` is returned. When
|
|
2130
|
+
# `display_width` is smaller than the ellipsis's own width, the ellipsis
|
|
2131
|
+
# is sliced down to fit and no original content is included.
|
|
2132
|
+
#
|
|
2133
|
+
# _@param_ `display_width` — target column width.
|
|
2134
|
+
#
|
|
2135
|
+
# _@param_ `ellipsis` — appended when truncation occurs. Defaults to the Unicode horizontal-ellipsis `…` (one column). A `String` is parsed via {.parse}, so ANSI in it is preserved.
|
|
2136
|
+
def ellipsize: (Integer display_width, ?(String | StyledString) ellipsis) -> StyledString
|
|
2137
|
+
|
|
2138
|
+
# Splits on `"\n"`, preserving spans on each side. A trailing newline
|
|
2139
|
+
# produces a trailing empty {StyledString} (matches `split("\n", -1)`).
|
|
2140
|
+
# An empty {StyledString} returns a single empty entry, like `"".split`.
|
|
2141
|
+
def lines: () -> ::Array[StyledString]
|
|
2142
|
+
|
|
2143
|
+
# Word-wraps to physical lines that each fit within `width` display
|
|
2144
|
+
# columns, preserving spans and styles across breaks. Greedy word-wrap,
|
|
2145
|
+
# hard-break for words wider than `width`, leading whitespace dropped on
|
|
2146
|
+
# wrapped continuations, hard `"\n"` breaks preserved as separate output
|
|
2147
|
+
# lines.
|
|
2148
|
+
#
|
|
2149
|
+
# Whitespace runs are space or tab; other characters are treated as word
|
|
2150
|
+
# content. When a single character is wider than `width` (e.g. a 2-column
|
|
2151
|
+
# CJK character with `width = 1`), it is still emitted on its own line at
|
|
2152
|
+
# its natural width. The "no line exceeds `width`" guarantee therefore
|
|
2153
|
+
# holds whenever every character is at most `width` columns wide.
|
|
2154
|
+
#
|
|
2155
|
+
# _@param_ `width` — target column width. `nil` or `<= 0` skips wrapping and returns each hard-line as-is, so callers can pass a stale viewport width without crashing.
|
|
2156
|
+
#
|
|
2157
|
+
# _@return_ — one entry per physical (output) line.
|
|
2158
|
+
# An empty receiver returns `[]`.
|
|
2159
|
+
def wrap: (Integer? width) -> ::Array[StyledString]
|
|
2160
|
+
|
|
2161
|
+
# Yields each character (per `String#each_char`) along with the {Style}
|
|
2162
|
+
# it carries. Returns an `Enumerator` without a block.
|
|
2163
|
+
def each_char_with_style: () -> (::Enumerator[untyped] | self)
|
|
2164
|
+
|
|
2165
|
+
# Returns a new {StyledString} with `bg` applied to every span, preserving
|
|
2166
|
+
# each span's text and other style attributes (`fg`, `bold`, `italic`,
|
|
2167
|
+
# `underline`). Useful for row-level highlights — the new bg overlays
|
|
2168
|
+
# without dropping foreground colors the original styling carried.
|
|
2169
|
+
#
|
|
2170
|
+
# _@param_ `bg` — background color, in any of the forms accepted by {Style.new}. `nil` clears bg back to the terminal default.
|
|
2171
|
+
def with_bg: ((Symbol | Integer | ::Array[Integer])? bg) -> StyledString
|
|
2172
|
+
|
|
2173
|
+
def inspect: () -> String
|
|
2174
|
+
|
|
2175
|
+
def build_ansi: () -> String
|
|
2176
|
+
|
|
2177
|
+
# _@param_ `spans`
|
|
2178
|
+
def normalize: (::Array[Span] spans) -> ::Array[Span]
|
|
2179
|
+
|
|
2180
|
+
# _@param_ `from`
|
|
2181
|
+
#
|
|
2182
|
+
# _@param_ `to`
|
|
2183
|
+
def sgr_diff: (Style from, Style to) -> String
|
|
2184
|
+
|
|
2185
|
+
# _@param_ `color`
|
|
2186
|
+
#
|
|
2187
|
+
# _@param_ `base` — base SGR code — 30 for fg, 40 for bg.
|
|
2188
|
+
#
|
|
2189
|
+
# _@param_ `ext` — extended-color SGR code — 38 for fg, 48 for bg.
|
|
2190
|
+
def color_codes: ((Symbol | Integer | ::Array[Integer])? color, base: Integer, ext: Integer) -> ::Array[Integer]
|
|
2191
|
+
|
|
2192
|
+
# _@param_ `start_or_range`
|
|
2193
|
+
#
|
|
2194
|
+
# _@param_ `len`
|
|
2195
|
+
#
|
|
2196
|
+
# _@param_ `total` — receiver's full display width.
|
|
2197
|
+
#
|
|
2198
|
+
# _@return_ — normalized `[start_col, len_col]`.
|
|
2199
|
+
def resolve_slice_bounds: ((Integer | ::Range[untyped]) start_or_range, Integer? len, Integer total) -> [Integer, Integer]
|
|
2200
|
+
|
|
2201
|
+
# _@param_ `start`
|
|
2202
|
+
#
|
|
2203
|
+
# _@param_ `len`
|
|
2204
|
+
def slice_spans: (Integer start, Integer len) -> StyledString
|
|
2205
|
+
|
|
2206
|
+
# _@param_ `hard_line` — one hard-broken line — no embedded `"\n"`.
|
|
2207
|
+
#
|
|
2208
|
+
# _@param_ `width`
|
|
2209
|
+
def wrap_one: (StyledString hard_line, Integer width) -> ::Array[StyledString]
|
|
2210
|
+
|
|
2211
|
+
# _@param_ `hard_line`
|
|
2212
|
+
#
|
|
2213
|
+
# _@return_ — tokens shaped `[type, chars, w]` where `type` is
|
|
2214
|
+
# `:space` or `:word`, `chars` is an `Array<[String, Style, Integer]>`
|
|
2215
|
+
# (char, style, display width), and `w` is the token's total width.
|
|
2216
|
+
def tokenize_for_wrap: (StyledString hard_line) -> ::Array[::Array[untyped]]
|
|
2217
|
+
|
|
2218
|
+
# _@param_ `chars` — `[char, style, width]` triples.
|
|
2219
|
+
#
|
|
2220
|
+
# _@param_ `width`
|
|
2221
|
+
#
|
|
2222
|
+
# _@return_ — each inner Array is a `chars`-shaped chunk.
|
|
2223
|
+
def hard_break_chars: (::Array[::Array[untyped]] chars, Integer width) -> ::Array[::Array[::Array[untyped]]]
|
|
2224
|
+
|
|
2225
|
+
# _@param_ `chars` — `[char, style, width]` triples.
|
|
2226
|
+
def chars_to_styled: (::Array[::Array[untyped]] chars) -> StyledString
|
|
2227
|
+
|
|
2228
|
+
# _@param_ `text`
|
|
2229
|
+
#
|
|
2230
|
+
# _@param_ `start_col`
|
|
2231
|
+
#
|
|
2232
|
+
# _@param_ `len_col`
|
|
2233
|
+
def slice_text_by_columns: (String text, Integer start_col, Integer len_col) -> String
|
|
2234
|
+
|
|
2235
|
+
# _@return_ — the frozen, normalized span list — no empty-text
|
|
2236
|
+
# entries, no two adjacent entries sharing a style.
|
|
2237
|
+
attr_reader spans: ::Array[Span]
|
|
2238
|
+
|
|
2239
|
+
# Raised by {.parse} on malformed or unsupported escape sequences.
|
|
2240
|
+
class ParseError < Tuile::Error
|
|
2241
|
+
end
|
|
2242
|
+
|
|
2243
|
+
# A frozen value type describing the visual style of a {Span}.
|
|
2244
|
+
#
|
|
2245
|
+
# `fg` and `bg` accept:
|
|
2246
|
+
# - `nil` — the terminal default (SGR 39 / 49)
|
|
2247
|
+
# - a symbol from {COLOR_SYMBOLS} — 8 standard + 8 bright ANSI colors
|
|
2248
|
+
# - an Integer 0..255 — 256-color palette index (SGR 38;5;N / 48;5;N)
|
|
2249
|
+
# - an `[r, g, b]` Array of three 0..255 Integers — 24-bit RGB
|
|
2250
|
+
#
|
|
2251
|
+
# @!attribute [r] fg
|
|
2252
|
+
# @return [Symbol, Integer, Array<Integer>, nil]
|
|
2253
|
+
# @!attribute [r] bg
|
|
2254
|
+
# @return [Symbol, Integer, Array<Integer>, nil]
|
|
2255
|
+
# @!attribute [r] bold
|
|
2256
|
+
# @return [Boolean]
|
|
2257
|
+
# @!attribute [r] italic
|
|
2258
|
+
# @return [Boolean]
|
|
2259
|
+
# @!attribute [r] underline
|
|
2260
|
+
# @return [Boolean]
|
|
2261
|
+
class Style
|
|
2262
|
+
COLOR_SYMBOLS: ::Array[Symbol]
|
|
2263
|
+
DEFAULT: Style
|
|
2264
|
+
|
|
2265
|
+
# _@param_ `fg`
|
|
2266
|
+
#
|
|
2267
|
+
# _@param_ `bg`
|
|
2268
|
+
#
|
|
2269
|
+
# _@param_ `bold`
|
|
2270
|
+
#
|
|
2271
|
+
# _@param_ `italic`
|
|
2272
|
+
#
|
|
2273
|
+
# _@param_ `underline`
|
|
2274
|
+
def self.new: (
|
|
2275
|
+
?fg: (Symbol | Integer | ::Array[Integer])?,
|
|
2276
|
+
?bg: (Symbol | Integer | ::Array[Integer])?,
|
|
2277
|
+
?bold: bool,
|
|
2278
|
+
?italic: bool,
|
|
2279
|
+
?underline: bool
|
|
2280
|
+
) -> Style
|
|
2281
|
+
|
|
2282
|
+
# _@param_ `color`
|
|
2283
|
+
#
|
|
2284
|
+
# _@param_ `which`
|
|
2285
|
+
def self.validate_color!: (Object color, Symbol which) -> void
|
|
2286
|
+
|
|
2287
|
+
def default?: () -> bool
|
|
2288
|
+
|
|
2289
|
+
# Returns a new {Style} with the given attributes overridden.
|
|
2290
|
+
#
|
|
2291
|
+
# _@param_ `overrides`
|
|
2292
|
+
def merge: (**::Hash[Symbol, Object] overrides) -> Style
|
|
2293
|
+
|
|
2294
|
+
attr_reader fg: (Symbol | Integer | ::Array[Integer])?
|
|
2295
|
+
|
|
2296
|
+
attr_reader bg: (Symbol | Integer | ::Array[Integer])?
|
|
2297
|
+
|
|
2298
|
+
attr_reader bold: bool
|
|
2299
|
+
|
|
2300
|
+
attr_reader italic: bool
|
|
2301
|
+
|
|
2302
|
+
attr_reader underline: bool
|
|
2303
|
+
end
|
|
2304
|
+
|
|
2305
|
+
# A maximal run of text sharing a single {Style}. `text` is plain — it
|
|
2306
|
+
# never contains ANSI escape sequences. Spans inside a {StyledString} are
|
|
2307
|
+
# normalized: no empty text, no two adjacent spans share a style.
|
|
2308
|
+
#
|
|
2309
|
+
# @!attribute [r] text
|
|
2310
|
+
# @return [String] frozen plain text.
|
|
2311
|
+
# @!attribute [r] style
|
|
2312
|
+
# @return [Style]
|
|
2313
|
+
class Span
|
|
2314
|
+
# _@param_ `text`
|
|
2315
|
+
#
|
|
2316
|
+
# _@param_ `style`
|
|
2317
|
+
def initialize: (text: String, style: Style) -> void
|
|
2318
|
+
|
|
2319
|
+
# _@return_ — frozen plain text.
|
|
2320
|
+
attr_reader text: String
|
|
2321
|
+
|
|
2322
|
+
attr_reader style: Style
|
|
2323
|
+
end
|
|
2324
|
+
|
|
2325
|
+
# @api private
|
|
2326
|
+
# Hand-rolled SGR parser. State machine over a {StringScanner}: plain
|
|
2327
|
+
# text accumulates into the current span; each `\e[...m` flushes the
|
|
2328
|
+
# current span and updates the running {Style}. Anything outside the
|
|
2329
|
+
# supported SGR alphabet raises {ParseError}.
|
|
2330
|
+
class Parser
|
|
2331
|
+
STANDARD_COLORS: ::Array[Symbol]
|
|
2332
|
+
BRIGHT_COLORS: ::Array[Symbol]
|
|
2333
|
+
|
|
2334
|
+
# _@param_ `input`
|
|
2335
|
+
def initialize: (String input) -> void
|
|
2336
|
+
|
|
2337
|
+
def parse: () -> StyledString
|
|
2338
|
+
|
|
2339
|
+
def consume_text: () -> void
|
|
2340
|
+
|
|
2341
|
+
def consume_escape: () -> void
|
|
2342
|
+
|
|
2343
|
+
# _@param_ `params_str`
|
|
2344
|
+
def apply_sgr: (String params_str) -> void
|
|
2345
|
+
|
|
2346
|
+
# _@param_ `codes`
|
|
2347
|
+
#
|
|
2348
|
+
# _@param_ `index`
|
|
2349
|
+
#
|
|
2350
|
+
# _@param_ `target` — either `:fg` or `:bg`.
|
|
2351
|
+
#
|
|
2352
|
+
# _@return_ — how many SGR codes were consumed (3 for 256-color, 5 for RGB).
|
|
2353
|
+
def consume_extended_color: (::Array[Integer] codes, Integer index, Symbol target) -> Integer
|
|
2354
|
+
|
|
2355
|
+
def flush: () -> void
|
|
2356
|
+
end
|
|
2357
|
+
end
|
|
2358
|
+
|
|
1804
2359
|
# A "synchronous" event queue – no loop is run, submitted blocks are run right
|
|
1805
2360
|
# away and submitted events are thrown away. Intended for testing only.
|
|
1806
2361
|
class FakeEventQueue
|