diff --git a/src/modules/punk-0.1.tm b/src/modules/punk-0.1.tm index ac83133f..45a9215e 100644 --- a/src/modules/punk-0.1.tm +++ b/src/modules/punk-0.1.tm @@ -6017,13 +6017,24 @@ namespace eval punk { # important for pipeline & match_assign # -line trimline|trimleft|trimright -block trimhead|trimtail|triminner|trimall|trimhead1|trimtail1|collateempty -commandprefix {string length} ? # -block trimming only trims completely empty lines. use -line trimming to remove whitespace e.g -line trimright will clear empty lines without affecting leading whitespace on other lines that aren't pure whitespace - proc linelist {text args} { + proc linelist {args} { + set usage "linelist ?-line trimline|trimleft|trimright? ?-block trimhead|trimtail|triminner|trimall|trimhead1|trimtail1|collateempty? -commandprefix text" + if {[llength $args] == 0} { + error "linelist missing textchunk argument usage:$usage" + } + set text [lindex $args end] + set arglist [lrange $args 0 end-1] set defaults [dict create\ -block {trimhead1 trimtail1}\ -line {}\ -commandprefix ""\ ] - set opts [dict merge $defaults $args] + foreach {o v} $arglist { + if {$o ni [dict keys $defaults]} { + error "linelist: Unrecognized option '$o' usage:$usage" + } + } + set opts [dict merge $defaults $arglist] # -- --- --- --- --- --- set opt_block [dict get $opts -block] set known_blockopts [list trimhead trimtail triminner trimall trimhead1 trimtail1 collateempty] @@ -6131,11 +6142,13 @@ namespace eval punk { #e.g linesort -decreasing $data proc linesort {args} { + if {[llength $args] < 1} { + error "linesort missing lines argument" + } set lines [lindex $args end] - if {[llength $args] > 1} { - set opts [lrange $args 0 end-1] - } else { - set opts [list] + set opts [lrange $args 0 end-1] + if {[llength $opts] % 2 != 0} { + error "linesort options must come in pairs" } .= list $lines |@0,sortopts/1> linelist |> .=data>1,sortopts>1* lsort |> list_as_lines <| {*}$opts } diff --git a/src/modules/punk/char-999999.0a1.0.tm b/src/modules/punk/char-999999.0a1.0.tm index d1de5e72..1b8fbaa4 100644 --- a/src/modules/punk/char-999999.0a1.0.tm +++ b/src/modules/punk/char-999999.0a1.0.tm @@ -283,6 +283,16 @@ namespace eval punk::char { } return $outchar } + proc pagechar_info {page num} { + package require punk::console + puts -nonewline stdout \033\[s;flush stdout + set posn1 [punk::console::get_cursor_position] + set h [format %04x $num] + puts -nonewline stdout "[format %c [subst 0x$h]]";flush stdout + set posn2 [punk::console::get_cursor_position] + puts -nonewline stdout "\033\[u";flush stdout + return "$posn1 -> $posn2" + } proc pagebyte {page num} { set encpage $page @@ -461,10 +471,10 @@ namespace eval punk::char { #todo - benchmark peformance - improve punk pipeline proc asciidict128 {} { - regexp -all -inline {\S+} [concat {*}[linelist [ascii] -line trimleft]] + regexp -all -inline {\S+} [concat {*}[linelist -line trimleft [ascii]]] } proc _asciidict128 {} { - .= ascii |> .=>1 linelist -line trimleft |> .=* concat |> {regexp -all -inline {\S+} $data} + .= ascii |> .=> linelist -line trimleft |> .=* concat |> {regexp -all -inline {\S+} $data} } proc asciidict2 {} { @@ -523,6 +533,43 @@ namespace eval punk::char { puts "reencoded: [encoding convertfrom $encoding $eyat] [encoding convertfrom $encoding $ebun]" return $yatbun } + proc test_grave {} { + set g [format %c 0x300] + puts stdout "Testing console display of grave accented a in between letters x and y - accent should combine over the top of the letter a." + puts stdout "Apparent width should theoretically be 1 console-column" + package require punk::console + puts stdout "# -- --- --- ---" + puts -nonewline "xa${g}z";set cursorposn [punk::console::get_cursor_position] + puts stdout \n + puts stdout "cursor position immediately after outputing 4 bytes (expecting 3 glyphs): $cursorposn" + puts stdout "# -- --- --- ---" + puts -nonewline "xyz";set cursorposn [punk::console::get_cursor_position] + puts stdout \n + puts stdout "cursor position immediately after outputing 3 bytes (xyz): $cursorposn" + } + proc test_farmer {} { + #an interesting article re grapheme clustering problems in terminals https://mitchellh.com/writing/grapheme-clusters-in-terminals + #(similar to the problem with grave accent rendering width that the test_grave proc is written for) + set test_farmer1 🧑‍🌾 ;#contains zero-width joiner between + set test_farmer2 🧑🌾 + + set farmer1 "\U0001f9d1\U0000200d\U0001f33e" + set farmer2 "\U0001f9d1\U0001f33e" + puts stdout "farmer1 with zero-width joiner: $farmer1" + puts stdout "farmer2 with no joiner : $farmer2" + + package require punk::console + puts stdout "#2--5---9---C---" + puts -nonewline "${farmer1}";set cursorposn [punk::console::get_cursor_position] + puts stdout \n + puts stdout "cursor position immediately after outputing farmer1 (expecting 1 glyph 2 wide): $cursorposn" + puts stdout "#2--5---9---C---" + puts -nonewline "${farmer2}";set cursorposn [punk::console::get_cursor_position] + puts stdout \n + puts stdout "cursor position immediately after outputing farmer2 (expecting 1 glyph 2 wide): $cursorposn" + + return [list $farmer1 $farmer2] + } #G0 Sets Sequence G1 Sets Sequence Meaning #ESC ( A ESC ) A United Kingdom Set @@ -581,6 +628,29 @@ namespace eval punk::char { #... dict set charinfo 10175 [list desc "Double Curly Loop" short "dingbats_double_curly_loop"] + + + # -- -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- + #variation selectors 0xFe01 - 0xFE0F + dict set charsets "variation selectors" [list ranges [list {start 65024 end 65039}] description "Variation Selectors" note "combining character with previous char - variant glyph display"] + dict set charinfo 65024 [list desc "Variation Selector-1" short "VS1"] + dict set charinfo 65025 [list desc "Variation Selector-2" short "VS2"] + dict set charinfo 65026 [list desc "Variation Selector-3" short "VS3"] + dict set charinfo 65027 [list desc "Variation Selector-4" short "VS4"] + dict set charinfo 65027 [list desc "Variation Selector-5" short "VS5"] + dict set charinfo 65029 [list desc "Variation Selector-6" short "VS6"] + dict set charinfo 65030 [list desc "Variation Selector-7" short "VS7"] + dict set charinfo 65031 [list desc "Variation Selector-8" short "VS8"] + dict set charinfo 65032 [list desc "Variation Selector-9" short "VS9"] + dict set charinfo 65033 [list desc "Variation Selector-10" short "VS10"] + dict set charinfo 65034 [list desc "Variation Selector-11" short "VS11"] + dict set charinfo 65035 [list desc "Variation Selector-12" short "VS12"] + dict set charinfo 65036 [list desc "Variation Selector-13" short "VS13"] + dict set charinfo 65037 [list desc "Variation Selector-14" short "VS14"] + dict set charinfo 65038 [list desc "Variation Selector-15 text variation" short "VS15"] ;#still an image - just more suitable for text-presentation e.g word-processing doc + dict set charinfo 65039 [list desc "Variation Selector-16 emoji variation" short "VS16"] + + # -- -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # emoticons https://www.unicode.org/charts/PDF/U1F600.pdf dict set charsets "emoticons" [list ranges [list {start 128512 end 128591}] description "Emoticons"] @@ -850,6 +920,28 @@ namespace eval punk::char { return [charset_dict "box_drawing"] } + proc char_info {char} { + variable charinfo + variable charsets + set dec_char [scan $char %c] + set hex_char [format %x $dec_char] + set memberof [list] + dict for {setname setinfo} $charsets { + set ranges [dict get $setinfo ranges] + foreach r $ranges { + set s [dict get $r start] + set e [dict get $r end] + if {$dec_char >= $s && $dec_char <= $e} { + lappend member_of $setname + break + } + } + } + set returninfo [dict get $charinfo $dec_char] + dict set returninfo memberof $memberof + dict set returninfo hex $ + return $returninfo + } proc char_range_dict {start end args} { if {![string is integer -strict $start] || ![string is integer -strict $end]} { error "char_range_dict error start and end must be integers" @@ -909,6 +1001,10 @@ namespace eval punk::char { variable charsets return [dict keys $charsets] } + proc charset_def {charsetname} { + variable charsets + return [dict get $charsets $charsetname] + } proc charset_dict {name} { variable charsets if {$name ni [charset_names]} { diff --git a/src/modules/punk/char/emoji-variation-sequences.txt b/src/modules/punk/char/emoji-variation-sequences.txt new file mode 100644 index 00000000..25e59e70 --- /dev/null +++ b/src/modules/punk/char/emoji-variation-sequences.txt @@ -0,0 +1,758 @@ + +# emoji-variation-sequences.txt +# Date: 2023-02-01, 02:22:54 GMT +# © 2023 Unicode®, Inc. +# Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. +# For terms of use, see https://www.unicode.org/terms_of_use.html +# +# Emoji Variation Sequences for UTS #51 +# Used with Emoji Version 15.1 and subsequent minor revisions (if any) +# +# For documentation and usage, see https://www.unicode.org/reports/tr51 +# +0023 FE0E ; text style; # (1.1) NUMBER SIGN +0023 FE0F ; emoji style; # (1.1) NUMBER SIGN +002A FE0E ; text style; # (1.1) ASTERISK +002A FE0F ; emoji style; # (1.1) ASTERISK +0030 FE0E ; text style; # (1.1) DIGIT ZERO +0030 FE0F ; emoji style; # (1.1) DIGIT ZERO +0031 FE0E ; text style; # (1.1) DIGIT ONE +0031 FE0F ; emoji style; # (1.1) DIGIT ONE +0032 FE0E ; text style; # (1.1) DIGIT TWO +0032 FE0F ; emoji style; # (1.1) DIGIT TWO +0033 FE0E ; text style; # (1.1) DIGIT THREE +0033 FE0F ; emoji style; # (1.1) DIGIT THREE +0034 FE0E ; text style; # (1.1) DIGIT FOUR +0034 FE0F ; emoji style; # (1.1) DIGIT FOUR +0035 FE0E ; text style; # (1.1) DIGIT FIVE +0035 FE0F ; emoji style; # (1.1) DIGIT FIVE +0036 FE0E ; text style; # (1.1) DIGIT SIX +0036 FE0F ; emoji style; # (1.1) DIGIT SIX +0037 FE0E ; text style; # (1.1) DIGIT SEVEN +0037 FE0F ; emoji style; # (1.1) DIGIT SEVEN +0038 FE0E ; text style; # (1.1) DIGIT EIGHT +0038 FE0F ; emoji style; # (1.1) DIGIT EIGHT +0039 FE0E ; text style; # (1.1) DIGIT NINE +0039 FE0F ; emoji style; # (1.1) DIGIT NINE +00A9 FE0E ; text style; # (1.1) COPYRIGHT SIGN +00A9 FE0F ; emoji style; # (1.1) COPYRIGHT SIGN +00AE FE0E ; text style; # (1.1) REGISTERED SIGN +00AE FE0F ; emoji style; # (1.1) REGISTERED SIGN +203C FE0E ; text style; # (1.1) DOUBLE EXCLAMATION MARK +203C FE0F ; emoji style; # (1.1) DOUBLE EXCLAMATION MARK +2049 FE0E ; text style; # (3.0) EXCLAMATION QUESTION MARK +2049 FE0F ; emoji style; # (3.0) EXCLAMATION QUESTION MARK +2122 FE0E ; text style; # (1.1) TRADE MARK SIGN +2122 FE0F ; emoji style; # (1.1) TRADE MARK SIGN +2139 FE0E ; text style; # (3.0) INFORMATION SOURCE +2139 FE0F ; emoji style; # (3.0) INFORMATION SOURCE +2194 FE0E ; text style; # (1.1) LEFT RIGHT ARROW +2194 FE0F ; emoji style; # (1.1) LEFT RIGHT ARROW +2195 FE0E ; text style; # (1.1) UP DOWN ARROW +2195 FE0F ; emoji style; # (1.1) UP DOWN ARROW +2196 FE0E ; text style; # (1.1) NORTH WEST ARROW +2196 FE0F ; emoji style; # (1.1) NORTH WEST ARROW +2197 FE0E ; text style; # (1.1) NORTH EAST ARROW +2197 FE0F ; emoji style; # (1.1) NORTH EAST ARROW +2198 FE0E ; text style; # (1.1) SOUTH EAST ARROW +2198 FE0F ; emoji style; # (1.1) SOUTH EAST ARROW +2199 FE0E ; text style; # (1.1) SOUTH WEST ARROW +2199 FE0F ; emoji style; # (1.1) SOUTH WEST ARROW +21A9 FE0E ; text style; # (1.1) LEFTWARDS ARROW WITH HOOK +21A9 FE0F ; emoji style; # (1.1) LEFTWARDS ARROW WITH HOOK +21AA FE0E ; text style; # (1.1) RIGHTWARDS ARROW WITH HOOK +21AA FE0F ; emoji style; # (1.1) RIGHTWARDS ARROW WITH HOOK +231A FE0E ; text style; # (1.1) WATCH +231A FE0F ; emoji style; # (1.1) WATCH +231B FE0E ; text style; # (1.1) HOURGLASS +231B FE0F ; emoji style; # (1.1) HOURGLASS +2328 FE0E ; text style; # (1.1) KEYBOARD +2328 FE0F ; emoji style; # (1.1) KEYBOARD +23CF FE0E ; text style; # (4.0) EJECT SYMBOL +23CF FE0F ; emoji style; # (4.0) EJECT SYMBOL +23E9 FE0E ; text style; # (6.0) BLACK RIGHT-POINTING DOUBLE TRIANGLE +23E9 FE0F ; emoji style; # (6.0) BLACK RIGHT-POINTING DOUBLE TRIANGLE +23EA FE0E ; text style; # (6.0) BLACK LEFT-POINTING DOUBLE TRIANGLE +23EA FE0F ; emoji style; # (6.0) BLACK LEFT-POINTING DOUBLE TRIANGLE +23EB FE0E ; text style; # (6.0) BLACK UP-POINTING DOUBLE TRIANGLE +23EB FE0F ; emoji style; # (6.0) BLACK UP-POINTING DOUBLE TRIANGLE +23EC FE0E ; text style; # (6.0) BLACK DOWN-POINTING DOUBLE TRIANGLE +23EC FE0F ; emoji style; # (6.0) BLACK DOWN-POINTING DOUBLE TRIANGLE +23ED FE0E ; text style; # (6.0) BLACK RIGHT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR +23ED FE0F ; emoji style; # (6.0) BLACK RIGHT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR +23EE FE0E ; text style; # (6.0) BLACK LEFT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR +23EE FE0F ; emoji style; # (6.0) BLACK LEFT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR +23EF FE0E ; text style; # (6.0) BLACK RIGHT-POINTING TRIANGLE WITH DOUBLE VERTICAL BAR +23EF FE0F ; emoji style; # (6.0) BLACK RIGHT-POINTING TRIANGLE WITH DOUBLE VERTICAL BAR +23F0 FE0E ; text style; # (6.0) ALARM CLOCK +23F0 FE0F ; emoji style; # (6.0) ALARM CLOCK +23F1 FE0E ; text style; # (6.0) STOPWATCH +23F1 FE0F ; emoji style; # (6.0) STOPWATCH +23F2 FE0E ; text style; # (6.0) TIMER CLOCK +23F2 FE0F ; emoji style; # (6.0) TIMER CLOCK +23F3 FE0E ; text style; # (6.0) HOURGLASS WITH FLOWING SAND +23F3 FE0F ; emoji style; # (6.0) HOURGLASS WITH FLOWING SAND +23F8 FE0E ; text style; # (7.0) DOUBLE VERTICAL BAR +23F8 FE0F ; emoji style; # (7.0) DOUBLE VERTICAL BAR +23F9 FE0E ; text style; # (7.0) BLACK SQUARE FOR STOP +23F9 FE0F ; emoji style; # (7.0) BLACK SQUARE FOR STOP +23FA FE0E ; text style; # (7.0) BLACK CIRCLE FOR RECORD +23FA FE0F ; emoji style; # (7.0) BLACK CIRCLE FOR RECORD +24C2 FE0E ; text style; # (1.1) CIRCLED LATIN CAPITAL LETTER M +24C2 FE0F ; emoji style; # (1.1) CIRCLED LATIN CAPITAL LETTER M +25AA FE0E ; text style; # (1.1) BLACK SMALL SQUARE +25AA FE0F ; emoji style; # (1.1) BLACK SMALL SQUARE +25AB FE0E ; text style; # (1.1) WHITE SMALL SQUARE +25AB FE0F ; emoji style; # (1.1) WHITE SMALL SQUARE +25B6 FE0E ; text style; # (1.1) BLACK RIGHT-POINTING TRIANGLE +25B6 FE0F ; emoji style; # (1.1) BLACK RIGHT-POINTING TRIANGLE +25C0 FE0E ; text style; # (1.1) BLACK LEFT-POINTING TRIANGLE +25C0 FE0F ; emoji style; # (1.1) BLACK LEFT-POINTING TRIANGLE +25FB FE0E ; text style; # (3.2) WHITE MEDIUM SQUARE +25FB FE0F ; emoji style; # (3.2) WHITE MEDIUM SQUARE +25FC FE0E ; text style; # (3.2) BLACK MEDIUM SQUARE +25FC FE0F ; emoji style; # (3.2) BLACK MEDIUM SQUARE +25FD FE0E ; text style; # (3.2) WHITE MEDIUM SMALL SQUARE +25FD FE0F ; emoji style; # (3.2) WHITE MEDIUM SMALL SQUARE +25FE FE0E ; text style; # (3.2) BLACK MEDIUM SMALL SQUARE +25FE FE0F ; emoji style; # (3.2) BLACK MEDIUM SMALL SQUARE +2600 FE0E ; text style; # (1.1) BLACK SUN WITH RAYS +2600 FE0F ; emoji style; # (1.1) BLACK SUN WITH RAYS +2601 FE0E ; text style; # (1.1) CLOUD +2601 FE0F ; emoji style; # (1.1) CLOUD +2602 FE0E ; text style; # (1.1) UMBRELLA +2602 FE0F ; emoji style; # (1.1) UMBRELLA +2603 FE0E ; text style; # (1.1) SNOWMAN +2603 FE0F ; emoji style; # (1.1) SNOWMAN +2604 FE0E ; text style; # (1.1) COMET +2604 FE0F ; emoji style; # (1.1) COMET +260E FE0E ; text style; # (1.1) BLACK TELEPHONE +260E FE0F ; emoji style; # (1.1) BLACK TELEPHONE +2611 FE0E ; text style; # (1.1) BALLOT BOX WITH CHECK +2611 FE0F ; emoji style; # (1.1) BALLOT BOX WITH CHECK +2614 FE0E ; text style; # (4.0) UMBRELLA WITH RAIN DROPS +2614 FE0F ; emoji style; # (4.0) UMBRELLA WITH RAIN DROPS +2615 FE0E ; text style; # (4.0) HOT BEVERAGE +2615 FE0F ; emoji style; # (4.0) HOT BEVERAGE +2618 FE0E ; text style; # (4.1) SHAMROCK +2618 FE0F ; emoji style; # (4.1) SHAMROCK +261D FE0E ; text style; # (1.1) WHITE UP POINTING INDEX +261D FE0F ; emoji style; # (1.1) WHITE UP POINTING INDEX +2620 FE0E ; text style; # (1.1) SKULL AND CROSSBONES +2620 FE0F ; emoji style; # (1.1) SKULL AND CROSSBONES +2622 FE0E ; text style; # (1.1) RADIOACTIVE SIGN +2622 FE0F ; emoji style; # (1.1) RADIOACTIVE SIGN +2623 FE0E ; text style; # (1.1) BIOHAZARD SIGN +2623 FE0F ; emoji style; # (1.1) BIOHAZARD SIGN +2626 FE0E ; text style; # (1.1) ORTHODOX CROSS +2626 FE0F ; emoji style; # (1.1) ORTHODOX CROSS +262A FE0E ; text style; # (1.1) STAR AND CRESCENT +262A FE0F ; emoji style; # (1.1) STAR AND CRESCENT +262E FE0E ; text style; # (1.1) PEACE SYMBOL +262E FE0F ; emoji style; # (1.1) PEACE SYMBOL +262F FE0E ; text style; # (1.1) YIN YANG +262F FE0F ; emoji style; # (1.1) YIN YANG +2638 FE0E ; text style; # (1.1) WHEEL OF DHARMA +2638 FE0F ; emoji style; # (1.1) WHEEL OF DHARMA +2639 FE0E ; text style; # (1.1) WHITE FROWNING FACE +2639 FE0F ; emoji style; # (1.1) WHITE FROWNING FACE +263A FE0E ; text style; # (1.1) WHITE SMILING FACE +263A FE0F ; emoji style; # (1.1) WHITE SMILING FACE +2640 FE0E ; text style; # (1.1) FEMALE SIGN +2640 FE0F ; emoji style; # (1.1) FEMALE SIGN +2642 FE0E ; text style; # (1.1) MALE SIGN +2642 FE0F ; emoji style; # (1.1) MALE SIGN +2648 FE0E ; text style; # (1.1) ARIES +2648 FE0F ; emoji style; # (1.1) ARIES +2649 FE0E ; text style; # (1.1) TAURUS +2649 FE0F ; emoji style; # (1.1) TAURUS +264A FE0E ; text style; # (1.1) GEMINI +264A FE0F ; emoji style; # (1.1) GEMINI +264B FE0E ; text style; # (1.1) CANCER +264B FE0F ; emoji style; # (1.1) CANCER +264C FE0E ; text style; # (1.1) LEO +264C FE0F ; emoji style; # (1.1) LEO +264D FE0E ; text style; # (1.1) VIRGO +264D FE0F ; emoji style; # (1.1) VIRGO +264E FE0E ; text style; # (1.1) LIBRA +264E FE0F ; emoji style; # (1.1) LIBRA +264F FE0E ; text style; # (1.1) SCORPIUS +264F FE0F ; emoji style; # (1.1) SCORPIUS +2650 FE0E ; text style; # (1.1) SAGITTARIUS +2650 FE0F ; emoji style; # (1.1) SAGITTARIUS +2651 FE0E ; text style; # (1.1) CAPRICORN +2651 FE0F ; emoji style; # (1.1) CAPRICORN +2652 FE0E ; text style; # (1.1) AQUARIUS +2652 FE0F ; emoji style; # (1.1) AQUARIUS +2653 FE0E ; text style; # (1.1) PISCES +2653 FE0F ; emoji style; # (1.1) PISCES +265F FE0E ; text style; # (1.1) BLACK CHESS PAWN +265F FE0F ; emoji style; # (1.1) BLACK CHESS PAWN +2660 FE0E ; text style; # (1.1) BLACK SPADE SUIT +2660 FE0F ; emoji style; # (1.1) BLACK SPADE SUIT +2663 FE0E ; text style; # (1.1) BLACK CLUB SUIT +2663 FE0F ; emoji style; # (1.1) BLACK CLUB SUIT +2665 FE0E ; text style; # (1.1) BLACK HEART SUIT +2665 FE0F ; emoji style; # (1.1) BLACK HEART SUIT +2666 FE0E ; text style; # (1.1) BLACK DIAMOND SUIT +2666 FE0F ; emoji style; # (1.1) BLACK DIAMOND SUIT +2668 FE0E ; text style; # (1.1) HOT SPRINGS +2668 FE0F ; emoji style; # (1.1) HOT SPRINGS +267B FE0E ; text style; # (3.2) BLACK UNIVERSAL RECYCLING SYMBOL +267B FE0F ; emoji style; # (3.2) BLACK UNIVERSAL RECYCLING SYMBOL +267E FE0E ; text style; # (4.1) PERMANENT PAPER SIGN +267E FE0F ; emoji style; # (4.1) PERMANENT PAPER SIGN +267F FE0E ; text style; # (4.1) WHEELCHAIR SYMBOL +267F FE0F ; emoji style; # (4.1) WHEELCHAIR SYMBOL +2692 FE0E ; text style; # (4.1) HAMMER AND PICK +2692 FE0F ; emoji style; # (4.1) HAMMER AND PICK +2693 FE0E ; text style; # (4.1) ANCHOR +2693 FE0F ; emoji style; # (4.1) ANCHOR +2694 FE0E ; text style; # (4.1) CROSSED SWORDS +2694 FE0F ; emoji style; # (4.1) CROSSED SWORDS +2695 FE0E ; text style; # (4.1) STAFF OF AESCULAPIUS +2695 FE0F ; emoji style; # (4.1) STAFF OF AESCULAPIUS +2696 FE0E ; text style; # (4.1) SCALES +2696 FE0F ; emoji style; # (4.1) SCALES +2697 FE0E ; text style; # (4.1) ALEMBIC +2697 FE0F ; emoji style; # (4.1) ALEMBIC +2699 FE0E ; text style; # (4.1) GEAR +2699 FE0F ; emoji style; # (4.1) GEAR +269B FE0E ; text style; # (4.1) ATOM SYMBOL +269B FE0F ; emoji style; # (4.1) ATOM SYMBOL +269C FE0E ; text style; # (4.1) FLEUR-DE-LIS +269C FE0F ; emoji style; # (4.1) FLEUR-DE-LIS +26A0 FE0E ; text style; # (4.0) WARNING SIGN +26A0 FE0F ; emoji style; # (4.0) WARNING SIGN +26A1 FE0E ; text style; # (4.0) HIGH VOLTAGE SIGN +26A1 FE0F ; emoji style; # (4.0) HIGH VOLTAGE SIGN +26A7 FE0E ; text style; # (4.1) MALE WITH STROKE AND MALE AND FEMALE SIGN +26A7 FE0F ; emoji style; # (4.1) MALE WITH STROKE AND MALE AND FEMALE SIGN +26AA FE0E ; text style; # (4.1) MEDIUM WHITE CIRCLE +26AA FE0F ; emoji style; # (4.1) MEDIUM WHITE CIRCLE +26AB FE0E ; text style; # (4.1) MEDIUM BLACK CIRCLE +26AB FE0F ; emoji style; # (4.1) MEDIUM BLACK CIRCLE +26B0 FE0E ; text style; # (4.1) COFFIN +26B0 FE0F ; emoji style; # (4.1) COFFIN +26B1 FE0E ; text style; # (4.1) FUNERAL URN +26B1 FE0F ; emoji style; # (4.1) FUNERAL URN +26BD FE0E ; text style; # (5.2) SOCCER BALL +26BD FE0F ; emoji style; # (5.2) SOCCER BALL +26BE FE0E ; text style; # (5.2) BASEBALL +26BE FE0F ; emoji style; # (5.2) BASEBALL +26C4 FE0E ; text style; # (5.2) SNOWMAN WITHOUT SNOW +26C4 FE0F ; emoji style; # (5.2) SNOWMAN WITHOUT SNOW +26C5 FE0E ; text style; # (5.2) SUN BEHIND CLOUD +26C5 FE0F ; emoji style; # (5.2) SUN BEHIND CLOUD +26C8 FE0E ; text style; # (5.2) THUNDER CLOUD AND RAIN +26C8 FE0F ; emoji style; # (5.2) THUNDER CLOUD AND RAIN +26CE FE0E ; text style; # (6.0) OPHIUCHUS +26CE FE0F ; emoji style; # (6.0) OPHIUCHUS +26CF FE0E ; text style; # (5.2) PICK +26CF FE0F ; emoji style; # (5.2) PICK +26D1 FE0E ; text style; # (5.2) HELMET WITH WHITE CROSS +26D1 FE0F ; emoji style; # (5.2) HELMET WITH WHITE CROSS +26D3 FE0E ; text style; # (5.2) CHAINS +26D3 FE0F ; emoji style; # (5.2) CHAINS +26D4 FE0E ; text style; # (5.2) NO ENTRY +26D4 FE0F ; emoji style; # (5.2) NO ENTRY +26E9 FE0E ; text style; # (5.2) SHINTO SHRINE +26E9 FE0F ; emoji style; # (5.2) SHINTO SHRINE +26EA FE0E ; text style; # (5.2) CHURCH +26EA FE0F ; emoji style; # (5.2) CHURCH +26F0 FE0E ; text style; # (5.2) MOUNTAIN +26F0 FE0F ; emoji style; # (5.2) MOUNTAIN +26F1 FE0E ; text style; # (5.2) UMBRELLA ON GROUND +26F1 FE0F ; emoji style; # (5.2) UMBRELLA ON GROUND +26F2 FE0E ; text style; # (5.2) FOUNTAIN +26F2 FE0F ; emoji style; # (5.2) FOUNTAIN +26F3 FE0E ; text style; # (5.2) FLAG IN HOLE +26F3 FE0F ; emoji style; # (5.2) FLAG IN HOLE +26F4 FE0E ; text style; # (5.2) FERRY +26F4 FE0F ; emoji style; # (5.2) FERRY +26F5 FE0E ; text style; # (5.2) SAILBOAT +26F5 FE0F ; emoji style; # (5.2) SAILBOAT +26F7 FE0E ; text style; # (5.2) SKIER +26F7 FE0F ; emoji style; # (5.2) SKIER +26F8 FE0E ; text style; # (5.2) ICE SKATE +26F8 FE0F ; emoji style; # (5.2) ICE SKATE +26F9 FE0E ; text style; # (5.2) PERSON WITH BALL +26F9 FE0F ; emoji style; # (5.2) PERSON WITH BALL +26FA FE0E ; text style; # (5.2) TENT +26FA FE0F ; emoji style; # (5.2) TENT +26FD FE0E ; text style; # (5.2) FUEL PUMP +26FD FE0F ; emoji style; # (5.2) FUEL PUMP +2702 FE0E ; text style; # (1.1) BLACK SCISSORS +2702 FE0F ; emoji style; # (1.1) BLACK SCISSORS +2705 FE0E ; text style; # (6.0) WHITE HEAVY CHECK MARK +2705 FE0F ; emoji style; # (6.0) WHITE HEAVY CHECK MARK +2708 FE0E ; text style; # (1.1) AIRPLANE +2708 FE0F ; emoji style; # (1.1) AIRPLANE +2709 FE0E ; text style; # (1.1) ENVELOPE +2709 FE0F ; emoji style; # (1.1) ENVELOPE +270A FE0E ; text style; # (6.0) RAISED FIST +270A FE0F ; emoji style; # (6.0) RAISED FIST +270B FE0E ; text style; # (6.0) RAISED HAND +270B FE0F ; emoji style; # (6.0) RAISED HAND +270C FE0E ; text style; # (1.1) VICTORY HAND +270C FE0F ; emoji style; # (1.1) VICTORY HAND +270D FE0E ; text style; # (1.1) WRITING HAND +270D FE0F ; emoji style; # (1.1) WRITING HAND +270F FE0E ; text style; # (1.1) PENCIL +270F FE0F ; emoji style; # (1.1) PENCIL +2712 FE0E ; text style; # (1.1) BLACK NIB +2712 FE0F ; emoji style; # (1.1) BLACK NIB +2714 FE0E ; text style; # (1.1) HEAVY CHECK MARK +2714 FE0F ; emoji style; # (1.1) HEAVY CHECK MARK +2716 FE0E ; text style; # (1.1) HEAVY MULTIPLICATION X +2716 FE0F ; emoji style; # (1.1) HEAVY MULTIPLICATION X +271D FE0E ; text style; # (1.1) LATIN CROSS +271D FE0F ; emoji style; # (1.1) LATIN CROSS +2721 FE0E ; text style; # (1.1) STAR OF DAVID +2721 FE0F ; emoji style; # (1.1) STAR OF DAVID +2728 FE0E ; text style; # (6.0) SPARKLES +2728 FE0F ; emoji style; # (6.0) SPARKLES +2733 FE0E ; text style; # (1.1) EIGHT SPOKED ASTERISK +2733 FE0F ; emoji style; # (1.1) EIGHT SPOKED ASTERISK +2734 FE0E ; text style; # (1.1) EIGHT POINTED BLACK STAR +2734 FE0F ; emoji style; # (1.1) EIGHT POINTED BLACK STAR +2744 FE0E ; text style; # (1.1) SNOWFLAKE +2744 FE0F ; emoji style; # (1.1) SNOWFLAKE +2747 FE0E ; text style; # (1.1) SPARKLE +2747 FE0F ; emoji style; # (1.1) SPARKLE +274C FE0E ; text style; # (6.0) CROSS MARK +274C FE0F ; emoji style; # (6.0) CROSS MARK +274E FE0E ; text style; # (6.0) NEGATIVE SQUARED CROSS MARK +274E FE0F ; emoji style; # (6.0) NEGATIVE SQUARED CROSS MARK +2753 FE0E ; text style; # (6.0) BLACK QUESTION MARK ORNAMENT +2753 FE0F ; emoji style; # (6.0) BLACK QUESTION MARK ORNAMENT +2754 FE0E ; text style; # (6.0) WHITE QUESTION MARK ORNAMENT +2754 FE0F ; emoji style; # (6.0) WHITE QUESTION MARK ORNAMENT +2755 FE0E ; text style; # (6.0) WHITE EXCLAMATION MARK ORNAMENT +2755 FE0F ; emoji style; # (6.0) WHITE EXCLAMATION MARK ORNAMENT +2757 FE0E ; text style; # (5.2) HEAVY EXCLAMATION MARK SYMBOL +2757 FE0F ; emoji style; # (5.2) HEAVY EXCLAMATION MARK SYMBOL +2763 FE0E ; text style; # (1.1) HEAVY HEART EXCLAMATION MARK ORNAMENT +2763 FE0F ; emoji style; # (1.1) HEAVY HEART EXCLAMATION MARK ORNAMENT +2764 FE0E ; text style; # (1.1) HEAVY BLACK HEART +2764 FE0F ; emoji style; # (1.1) HEAVY BLACK HEART +2795 FE0E ; text style; # (6.0) HEAVY PLUS SIGN +2795 FE0F ; emoji style; # (6.0) HEAVY PLUS SIGN +2796 FE0E ; text style; # (6.0) HEAVY MINUS SIGN +2796 FE0F ; emoji style; # (6.0) HEAVY MINUS SIGN +2797 FE0E ; text style; # (6.0) HEAVY DIVISION SIGN +2797 FE0F ; emoji style; # (6.0) HEAVY DIVISION SIGN +27A1 FE0E ; text style; # (1.1) BLACK RIGHTWARDS ARROW +27A1 FE0F ; emoji style; # (1.1) BLACK RIGHTWARDS ARROW +27B0 FE0E ; text style; # (6.0) CURLY LOOP +27B0 FE0F ; emoji style; # (6.0) CURLY LOOP +27BF FE0E ; text style; # (6.0) DOUBLE CURLY LOOP +27BF FE0F ; emoji style; # (6.0) DOUBLE CURLY LOOP +2934 FE0E ; text style; # (3.2) ARROW POINTING RIGHTWARDS THEN CURVING UPWARDS +2934 FE0F ; emoji style; # (3.2) ARROW POINTING RIGHTWARDS THEN CURVING UPWARDS +2935 FE0E ; text style; # (3.2) ARROW POINTING RIGHTWARDS THEN CURVING DOWNWARDS +2935 FE0F ; emoji style; # (3.2) ARROW POINTING RIGHTWARDS THEN CURVING DOWNWARDS +2B05 FE0E ; text style; # (4.0) LEFTWARDS BLACK ARROW +2B05 FE0F ; emoji style; # (4.0) LEFTWARDS BLACK ARROW +2B06 FE0E ; text style; # (4.0) UPWARDS BLACK ARROW +2B06 FE0F ; emoji style; # (4.0) UPWARDS BLACK ARROW +2B07 FE0E ; text style; # (4.0) DOWNWARDS BLACK ARROW +2B07 FE0F ; emoji style; # (4.0) DOWNWARDS BLACK ARROW +2B1B FE0E ; text style; # (5.1) BLACK LARGE SQUARE +2B1B FE0F ; emoji style; # (5.1) BLACK LARGE SQUARE +2B1C FE0E ; text style; # (5.1) WHITE LARGE SQUARE +2B1C FE0F ; emoji style; # (5.1) WHITE LARGE SQUARE +2B50 FE0E ; text style; # (5.1) WHITE MEDIUM STAR +2B50 FE0F ; emoji style; # (5.1) WHITE MEDIUM STAR +2B55 FE0E ; text style; # (5.2) HEAVY LARGE CIRCLE +2B55 FE0F ; emoji style; # (5.2) HEAVY LARGE CIRCLE +3030 FE0E ; text style; # (1.1) WAVY DASH +3030 FE0F ; emoji style; # (1.1) WAVY DASH +303D FE0E ; text style; # (3.2) PART ALTERNATION MARK +303D FE0F ; emoji style; # (3.2) PART ALTERNATION MARK +3297 FE0E ; text style; # (1.1) CIRCLED IDEOGRAPH CONGRATULATION +3297 FE0F ; emoji style; # (1.1) CIRCLED IDEOGRAPH CONGRATULATION +3299 FE0E ; text style; # (1.1) CIRCLED IDEOGRAPH SECRET +3299 FE0F ; emoji style; # (1.1) CIRCLED IDEOGRAPH SECRET +1F004 FE0E ; text style; # (5.1) MAHJONG TILE RED DRAGON +1F004 FE0F ; emoji style; # (5.1) MAHJONG TILE RED DRAGON +1F170 FE0E ; text style; # (6.0) NEGATIVE SQUARED LATIN CAPITAL LETTER A +1F170 FE0F ; emoji style; # (6.0) NEGATIVE SQUARED LATIN CAPITAL LETTER A +1F171 FE0E ; text style; # (6.0) NEGATIVE SQUARED LATIN CAPITAL LETTER B +1F171 FE0F ; emoji style; # (6.0) NEGATIVE SQUARED LATIN CAPITAL LETTER B +1F17E FE0E ; text style; # (6.0) NEGATIVE SQUARED LATIN CAPITAL LETTER O +1F17E FE0F ; emoji style; # (6.0) NEGATIVE SQUARED LATIN CAPITAL LETTER O +1F17F FE0E ; text style; # (5.2) NEGATIVE SQUARED LATIN CAPITAL LETTER P +1F17F FE0F ; emoji style; # (5.2) NEGATIVE SQUARED LATIN CAPITAL LETTER P +1F202 FE0E ; text style; # (6.0) SQUARED KATAKANA SA +1F202 FE0F ; emoji style; # (6.0) SQUARED KATAKANA SA +1F21A FE0E ; text style; # (5.2) SQUARED CJK UNIFIED IDEOGRAPH-7121 +1F21A FE0F ; emoji style; # (5.2) SQUARED CJK UNIFIED IDEOGRAPH-7121 +1F22F FE0E ; text style; # (5.2) SQUARED CJK UNIFIED IDEOGRAPH-6307 +1F22F FE0F ; emoji style; # (5.2) SQUARED CJK UNIFIED IDEOGRAPH-6307 +1F237 FE0E ; text style; # (6.0) SQUARED CJK UNIFIED IDEOGRAPH-6708 +1F237 FE0F ; emoji style; # (6.0) SQUARED CJK UNIFIED IDEOGRAPH-6708 +1F30D FE0E ; text style; # (6.0) EARTH GLOBE EUROPE-AFRICA +1F30D FE0F ; emoji style; # (6.0) EARTH GLOBE EUROPE-AFRICA +1F30E FE0E ; text style; # (6.0) EARTH GLOBE AMERICAS +1F30E FE0F ; emoji style; # (6.0) EARTH GLOBE AMERICAS +1F30F FE0E ; text style; # (6.0) EARTH GLOBE ASIA-AUSTRALIA +1F30F FE0F ; emoji style; # (6.0) EARTH GLOBE ASIA-AUSTRALIA +1F315 FE0E ; text style; # (6.0) FULL MOON SYMBOL +1F315 FE0F ; emoji style; # (6.0) FULL MOON SYMBOL +1F31C FE0E ; text style; # (6.0) LAST QUARTER MOON WITH FACE +1F31C FE0F ; emoji style; # (6.0) LAST QUARTER MOON WITH FACE +1F321 FE0E ; text style; # (7.0) THERMOMETER +1F321 FE0F ; emoji style; # (7.0) THERMOMETER +1F324 FE0E ; text style; # (7.0) WHITE SUN WITH SMALL CLOUD +1F324 FE0F ; emoji style; # (7.0) WHITE SUN WITH SMALL CLOUD +1F325 FE0E ; text style; # (7.0) WHITE SUN BEHIND CLOUD +1F325 FE0F ; emoji style; # (7.0) WHITE SUN BEHIND CLOUD +1F326 FE0E ; text style; # (7.0) WHITE SUN BEHIND CLOUD WITH RAIN +1F326 FE0F ; emoji style; # (7.0) WHITE SUN BEHIND CLOUD WITH RAIN +1F327 FE0E ; text style; # (7.0) CLOUD WITH RAIN +1F327 FE0F ; emoji style; # (7.0) CLOUD WITH RAIN +1F328 FE0E ; text style; # (7.0) CLOUD WITH SNOW +1F328 FE0F ; emoji style; # (7.0) CLOUD WITH SNOW +1F329 FE0E ; text style; # (7.0) CLOUD WITH LIGHTNING +1F329 FE0F ; emoji style; # (7.0) CLOUD WITH LIGHTNING +1F32A FE0E ; text style; # (7.0) CLOUD WITH TORNADO +1F32A FE0F ; emoji style; # (7.0) CLOUD WITH TORNADO +1F32B FE0E ; text style; # (7.0) FOG +1F32B FE0F ; emoji style; # (7.0) FOG +1F32C FE0E ; text style; # (7.0) WIND BLOWING FACE +1F32C FE0F ; emoji style; # (7.0) WIND BLOWING FACE +1F336 FE0E ; text style; # (7.0) HOT PEPPER +1F336 FE0F ; emoji style; # (7.0) HOT PEPPER +1F378 FE0E ; text style; # (6.0) COCKTAIL GLASS +1F378 FE0F ; emoji style; # (6.0) COCKTAIL GLASS +1F37D FE0E ; text style; # (7.0) FORK AND KNIFE WITH PLATE +1F37D FE0F ; emoji style; # (7.0) FORK AND KNIFE WITH PLATE +1F393 FE0E ; text style; # (6.0) GRADUATION CAP +1F393 FE0F ; emoji style; # (6.0) GRADUATION CAP +1F396 FE0E ; text style; # (7.0) MILITARY MEDAL +1F396 FE0F ; emoji style; # (7.0) MILITARY MEDAL +1F397 FE0E ; text style; # (7.0) REMINDER RIBBON +1F397 FE0F ; emoji style; # (7.0) REMINDER RIBBON +1F399 FE0E ; text style; # (7.0) STUDIO MICROPHONE +1F399 FE0F ; emoji style; # (7.0) STUDIO MICROPHONE +1F39A FE0E ; text style; # (7.0) LEVEL SLIDER +1F39A FE0F ; emoji style; # (7.0) LEVEL SLIDER +1F39B FE0E ; text style; # (7.0) CONTROL KNOBS +1F39B FE0F ; emoji style; # (7.0) CONTROL KNOBS +1F39E FE0E ; text style; # (7.0) FILM FRAMES +1F39E FE0F ; emoji style; # (7.0) FILM FRAMES +1F39F FE0E ; text style; # (7.0) ADMISSION TICKETS +1F39F FE0F ; emoji style; # (7.0) ADMISSION TICKETS +1F3A7 FE0E ; text style; # (6.0) HEADPHONE +1F3A7 FE0F ; emoji style; # (6.0) HEADPHONE +1F3AC FE0E ; text style; # (6.0) CLAPPER BOARD +1F3AC FE0F ; emoji style; # (6.0) CLAPPER BOARD +1F3AD FE0E ; text style; # (6.0) PERFORMING ARTS +1F3AD FE0F ; emoji style; # (6.0) PERFORMING ARTS +1F3AE FE0E ; text style; # (6.0) VIDEO GAME +1F3AE FE0F ; emoji style; # (6.0) VIDEO GAME +1F3C2 FE0E ; text style; # (6.0) SNOWBOARDER +1F3C2 FE0F ; emoji style; # (6.0) SNOWBOARDER +1F3C4 FE0E ; text style; # (6.0) SURFER +1F3C4 FE0F ; emoji style; # (6.0) SURFER +1F3C6 FE0E ; text style; # (6.0) TROPHY +1F3C6 FE0F ; emoji style; # (6.0) TROPHY +1F3CA FE0E ; text style; # (6.0) SWIMMER +1F3CA FE0F ; emoji style; # (6.0) SWIMMER +1F3CB FE0E ; text style; # (7.0) WEIGHT LIFTER +1F3CB FE0F ; emoji style; # (7.0) WEIGHT LIFTER +1F3CC FE0E ; text style; # (7.0) GOLFER +1F3CC FE0F ; emoji style; # (7.0) GOLFER +1F3CD FE0E ; text style; # (7.0) RACING MOTORCYCLE +1F3CD FE0F ; emoji style; # (7.0) RACING MOTORCYCLE +1F3CE FE0E ; text style; # (7.0) RACING CAR +1F3CE FE0F ; emoji style; # (7.0) RACING CAR +1F3D4 FE0E ; text style; # (7.0) SNOW CAPPED MOUNTAIN +1F3D4 FE0F ; emoji style; # (7.0) SNOW CAPPED MOUNTAIN +1F3D5 FE0E ; text style; # (7.0) CAMPING +1F3D5 FE0F ; emoji style; # (7.0) CAMPING +1F3D6 FE0E ; text style; # (7.0) BEACH WITH UMBRELLA +1F3D6 FE0F ; emoji style; # (7.0) BEACH WITH UMBRELLA +1F3D7 FE0E ; text style; # (7.0) BUILDING CONSTRUCTION +1F3D7 FE0F ; emoji style; # (7.0) BUILDING CONSTRUCTION +1F3D8 FE0E ; text style; # (7.0) HOUSE BUILDINGS +1F3D8 FE0F ; emoji style; # (7.0) HOUSE BUILDINGS +1F3D9 FE0E ; text style; # (7.0) CITYSCAPE +1F3D9 FE0F ; emoji style; # (7.0) CITYSCAPE +1F3DA FE0E ; text style; # (7.0) DERELICT HOUSE BUILDING +1F3DA FE0F ; emoji style; # (7.0) DERELICT HOUSE BUILDING +1F3DB FE0E ; text style; # (7.0) CLASSICAL BUILDING +1F3DB FE0F ; emoji style; # (7.0) CLASSICAL BUILDING +1F3DC FE0E ; text style; # (7.0) DESERT +1F3DC FE0F ; emoji style; # (7.0) DESERT +1F3DD FE0E ; text style; # (7.0) DESERT ISLAND +1F3DD FE0F ; emoji style; # (7.0) DESERT ISLAND +1F3DE FE0E ; text style; # (7.0) NATIONAL PARK +1F3DE FE0F ; emoji style; # (7.0) NATIONAL PARK +1F3DF FE0E ; text style; # (7.0) STADIUM +1F3DF FE0F ; emoji style; # (7.0) STADIUM +1F3E0 FE0E ; text style; # (6.0) HOUSE BUILDING +1F3E0 FE0F ; emoji style; # (6.0) HOUSE BUILDING +1F3ED FE0E ; text style; # (6.0) FACTORY +1F3ED FE0F ; emoji style; # (6.0) FACTORY +1F3F3 FE0E ; text style; # (7.0) WAVING WHITE FLAG +1F3F3 FE0F ; emoji style; # (7.0) WAVING WHITE FLAG +1F3F5 FE0E ; text style; # (7.0) ROSETTE +1F3F5 FE0F ; emoji style; # (7.0) ROSETTE +1F3F7 FE0E ; text style; # (7.0) LABEL +1F3F7 FE0F ; emoji style; # (7.0) LABEL +1F408 FE0E ; text style; # (6.0) CAT +1F408 FE0F ; emoji style; # (6.0) CAT +1F415 FE0E ; text style; # (6.0) DOG +1F415 FE0F ; emoji style; # (6.0) DOG +1F41F FE0E ; text style; # (6.0) FISH +1F41F FE0F ; emoji style; # (6.0) FISH +1F426 FE0E ; text style; # (6.0) BIRD +1F426 FE0F ; emoji style; # (6.0) BIRD +1F43F FE0E ; text style; # (7.0) CHIPMUNK +1F43F FE0F ; emoji style; # (7.0) CHIPMUNK +1F441 FE0E ; text style; # (7.0) EYE +1F441 FE0F ; emoji style; # (7.0) EYE +1F442 FE0E ; text style; # (6.0) EAR +1F442 FE0F ; emoji style; # (6.0) EAR +1F446 FE0E ; text style; # (6.0) WHITE UP POINTING BACKHAND INDEX +1F446 FE0F ; emoji style; # (6.0) WHITE UP POINTING BACKHAND INDEX +1F447 FE0E ; text style; # (6.0) WHITE DOWN POINTING BACKHAND INDEX +1F447 FE0F ; emoji style; # (6.0) WHITE DOWN POINTING BACKHAND INDEX +1F448 FE0E ; text style; # (6.0) WHITE LEFT POINTING BACKHAND INDEX +1F448 FE0F ; emoji style; # (6.0) WHITE LEFT POINTING BACKHAND INDEX +1F449 FE0E ; text style; # (6.0) WHITE RIGHT POINTING BACKHAND INDEX +1F449 FE0F ; emoji style; # (6.0) WHITE RIGHT POINTING BACKHAND INDEX +1F44D FE0E ; text style; # (6.0) THUMBS UP SIGN +1F44D FE0F ; emoji style; # (6.0) THUMBS UP SIGN +1F44E FE0E ; text style; # (6.0) THUMBS DOWN SIGN +1F44E FE0F ; emoji style; # (6.0) THUMBS DOWN SIGN +1F453 FE0E ; text style; # (6.0) EYEGLASSES +1F453 FE0F ; emoji style; # (6.0) EYEGLASSES +1F46A FE0E ; text style; # (6.0) FAMILY +1F46A FE0F ; emoji style; # (6.0) FAMILY +1F47D FE0E ; text style; # (6.0) EXTRATERRESTRIAL ALIEN +1F47D FE0F ; emoji style; # (6.0) EXTRATERRESTRIAL ALIEN +1F4A3 FE0E ; text style; # (6.0) BOMB +1F4A3 FE0F ; emoji style; # (6.0) BOMB +1F4B0 FE0E ; text style; # (6.0) MONEY BAG +1F4B0 FE0F ; emoji style; # (6.0) MONEY BAG +1F4B3 FE0E ; text style; # (6.0) CREDIT CARD +1F4B3 FE0F ; emoji style; # (6.0) CREDIT CARD +1F4BB FE0E ; text style; # (6.0) PERSONAL COMPUTER +1F4BB FE0F ; emoji style; # (6.0) PERSONAL COMPUTER +1F4BF FE0E ; text style; # (6.0) OPTICAL DISC +1F4BF FE0F ; emoji style; # (6.0) OPTICAL DISC +1F4CB FE0E ; text style; # (6.0) CLIPBOARD +1F4CB FE0F ; emoji style; # (6.0) CLIPBOARD +1F4DA FE0E ; text style; # (6.0) BOOKS +1F4DA FE0F ; emoji style; # (6.0) BOOKS +1F4DF FE0E ; text style; # (6.0) PAGER +1F4DF FE0F ; emoji style; # (6.0) PAGER +1F4E4 FE0E ; text style; # (6.0) OUTBOX TRAY +1F4E4 FE0F ; emoji style; # (6.0) OUTBOX TRAY +1F4E5 FE0E ; text style; # (6.0) INBOX TRAY +1F4E5 FE0F ; emoji style; # (6.0) INBOX TRAY +1F4E6 FE0E ; text style; # (6.0) PACKAGE +1F4E6 FE0F ; emoji style; # (6.0) PACKAGE +1F4EA FE0E ; text style; # (6.0) CLOSED MAILBOX WITH LOWERED FLAG +1F4EA FE0F ; emoji style; # (6.0) CLOSED MAILBOX WITH LOWERED FLAG +1F4EB FE0E ; text style; # (6.0) CLOSED MAILBOX WITH RAISED FLAG +1F4EB FE0F ; emoji style; # (6.0) CLOSED MAILBOX WITH RAISED FLAG +1F4EC FE0E ; text style; # (6.0) OPEN MAILBOX WITH RAISED FLAG +1F4EC FE0F ; emoji style; # (6.0) OPEN MAILBOX WITH RAISED FLAG +1F4ED FE0E ; text style; # (6.0) OPEN MAILBOX WITH LOWERED FLAG +1F4ED FE0F ; emoji style; # (6.0) OPEN MAILBOX WITH LOWERED FLAG +1F4F7 FE0E ; text style; # (6.0) CAMERA +1F4F7 FE0F ; emoji style; # (6.0) CAMERA +1F4F9 FE0E ; text style; # (6.0) VIDEO CAMERA +1F4F9 FE0F ; emoji style; # (6.0) VIDEO CAMERA +1F4FA FE0E ; text style; # (6.0) TELEVISION +1F4FA FE0F ; emoji style; # (6.0) TELEVISION +1F4FB FE0E ; text style; # (6.0) RADIO +1F4FB FE0F ; emoji style; # (6.0) RADIO +1F4FD FE0E ; text style; # (7.0) FILM PROJECTOR +1F4FD FE0F ; emoji style; # (7.0) FILM PROJECTOR +1F508 FE0E ; text style; # (6.0) SPEAKER +1F508 FE0F ; emoji style; # (6.0) SPEAKER +1F50D FE0E ; text style; # (6.0) LEFT-POINTING MAGNIFYING GLASS +1F50D FE0F ; emoji style; # (6.0) LEFT-POINTING MAGNIFYING GLASS +1F512 FE0E ; text style; # (6.0) LOCK +1F512 FE0F ; emoji style; # (6.0) LOCK +1F513 FE0E ; text style; # (6.0) OPEN LOCK +1F513 FE0F ; emoji style; # (6.0) OPEN LOCK +1F549 FE0E ; text style; # (7.0) OM SYMBOL +1F549 FE0F ; emoji style; # (7.0) OM SYMBOL +1F54A FE0E ; text style; # (7.0) DOVE OF PEACE +1F54A FE0F ; emoji style; # (7.0) DOVE OF PEACE +1F550 FE0E ; text style; # (6.0) CLOCK FACE ONE OCLOCK +1F550 FE0F ; emoji style; # (6.0) CLOCK FACE ONE OCLOCK +1F551 FE0E ; text style; # (6.0) CLOCK FACE TWO OCLOCK +1F551 FE0F ; emoji style; # (6.0) CLOCK FACE TWO OCLOCK +1F552 FE0E ; text style; # (6.0) CLOCK FACE THREE OCLOCK +1F552 FE0F ; emoji style; # (6.0) CLOCK FACE THREE OCLOCK +1F553 FE0E ; text style; # (6.0) CLOCK FACE FOUR OCLOCK +1F553 FE0F ; emoji style; # (6.0) CLOCK FACE FOUR OCLOCK +1F554 FE0E ; text style; # (6.0) CLOCK FACE FIVE OCLOCK +1F554 FE0F ; emoji style; # (6.0) CLOCK FACE FIVE OCLOCK +1F555 FE0E ; text style; # (6.0) CLOCK FACE SIX OCLOCK +1F555 FE0F ; emoji style; # (6.0) CLOCK FACE SIX OCLOCK +1F556 FE0E ; text style; # (6.0) CLOCK FACE SEVEN OCLOCK +1F556 FE0F ; emoji style; # (6.0) CLOCK FACE SEVEN OCLOCK +1F557 FE0E ; text style; # (6.0) CLOCK FACE EIGHT OCLOCK +1F557 FE0F ; emoji style; # (6.0) CLOCK FACE EIGHT OCLOCK +1F558 FE0E ; text style; # (6.0) CLOCK FACE NINE OCLOCK +1F558 FE0F ; emoji style; # (6.0) CLOCK FACE NINE OCLOCK +1F559 FE0E ; text style; # (6.0) CLOCK FACE TEN OCLOCK +1F559 FE0F ; emoji style; # (6.0) CLOCK FACE TEN OCLOCK +1F55A FE0E ; text style; # (6.0) CLOCK FACE ELEVEN OCLOCK +1F55A FE0F ; emoji style; # (6.0) CLOCK FACE ELEVEN OCLOCK +1F55B FE0E ; text style; # (6.0) CLOCK FACE TWELVE OCLOCK +1F55B FE0F ; emoji style; # (6.0) CLOCK FACE TWELVE OCLOCK +1F55C FE0E ; text style; # (6.0) CLOCK FACE ONE-THIRTY +1F55C FE0F ; emoji style; # (6.0) CLOCK FACE ONE-THIRTY +1F55D FE0E ; text style; # (6.0) CLOCK FACE TWO-THIRTY +1F55D FE0F ; emoji style; # (6.0) CLOCK FACE TWO-THIRTY +1F55E FE0E ; text style; # (6.0) CLOCK FACE THREE-THIRTY +1F55E FE0F ; emoji style; # (6.0) CLOCK FACE THREE-THIRTY +1F55F FE0E ; text style; # (6.0) CLOCK FACE FOUR-THIRTY +1F55F FE0F ; emoji style; # (6.0) CLOCK FACE FOUR-THIRTY +1F560 FE0E ; text style; # (6.0) CLOCK FACE FIVE-THIRTY +1F560 FE0F ; emoji style; # (6.0) CLOCK FACE FIVE-THIRTY +1F561 FE0E ; text style; # (6.0) CLOCK FACE SIX-THIRTY +1F561 FE0F ; emoji style; # (6.0) CLOCK FACE SIX-THIRTY +1F562 FE0E ; text style; # (6.0) CLOCK FACE SEVEN-THIRTY +1F562 FE0F ; emoji style; # (6.0) CLOCK FACE SEVEN-THIRTY +1F563 FE0E ; text style; # (6.0) CLOCK FACE EIGHT-THIRTY +1F563 FE0F ; emoji style; # (6.0) CLOCK FACE EIGHT-THIRTY +1F564 FE0E ; text style; # (6.0) CLOCK FACE NINE-THIRTY +1F564 FE0F ; emoji style; # (6.0) CLOCK FACE NINE-THIRTY +1F565 FE0E ; text style; # (6.0) CLOCK FACE TEN-THIRTY +1F565 FE0F ; emoji style; # (6.0) CLOCK FACE TEN-THIRTY +1F566 FE0E ; text style; # (6.0) CLOCK FACE ELEVEN-THIRTY +1F566 FE0F ; emoji style; # (6.0) CLOCK FACE ELEVEN-THIRTY +1F567 FE0E ; text style; # (6.0) CLOCK FACE TWELVE-THIRTY +1F567 FE0F ; emoji style; # (6.0) CLOCK FACE TWELVE-THIRTY +1F56F FE0E ; text style; # (7.0) CANDLE +1F56F FE0F ; emoji style; # (7.0) CANDLE +1F570 FE0E ; text style; # (7.0) MANTELPIECE CLOCK +1F570 FE0F ; emoji style; # (7.0) MANTELPIECE CLOCK +1F573 FE0E ; text style; # (7.0) HOLE +1F573 FE0F ; emoji style; # (7.0) HOLE +1F574 FE0E ; text style; # (7.0) MAN IN BUSINESS SUIT LEVITATING +1F574 FE0F ; emoji style; # (7.0) MAN IN BUSINESS SUIT LEVITATING +1F575 FE0E ; text style; # (7.0) SLEUTH OR SPY +1F575 FE0F ; emoji style; # (7.0) SLEUTH OR SPY +1F576 FE0E ; text style; # (7.0) DARK SUNGLASSES +1F576 FE0F ; emoji style; # (7.0) DARK SUNGLASSES +1F577 FE0E ; text style; # (7.0) SPIDER +1F577 FE0F ; emoji style; # (7.0) SPIDER +1F578 FE0E ; text style; # (7.0) SPIDER WEB +1F578 FE0F ; emoji style; # (7.0) SPIDER WEB +1F579 FE0E ; text style; # (7.0) JOYSTICK +1F579 FE0F ; emoji style; # (7.0) JOYSTICK +1F587 FE0E ; text style; # (7.0) LINKED PAPERCLIPS +1F587 FE0F ; emoji style; # (7.0) LINKED PAPERCLIPS +1F58A FE0E ; text style; # (7.0) LOWER LEFT BALLPOINT PEN +1F58A FE0F ; emoji style; # (7.0) LOWER LEFT BALLPOINT PEN +1F58B FE0E ; text style; # (7.0) LOWER LEFT FOUNTAIN PEN +1F58B FE0F ; emoji style; # (7.0) LOWER LEFT FOUNTAIN PEN +1F58C FE0E ; text style; # (7.0) LOWER LEFT PAINTBRUSH +1F58C FE0F ; emoji style; # (7.0) LOWER LEFT PAINTBRUSH +1F58D FE0E ; text style; # (7.0) LOWER LEFT CRAYON +1F58D FE0F ; emoji style; # (7.0) LOWER LEFT CRAYON +1F590 FE0E ; text style; # (7.0) RAISED HAND WITH FINGERS SPLAYED +1F590 FE0F ; emoji style; # (7.0) RAISED HAND WITH FINGERS SPLAYED +1F5A5 FE0E ; text style; # (7.0) DESKTOP COMPUTER +1F5A5 FE0F ; emoji style; # (7.0) DESKTOP COMPUTER +1F5A8 FE0E ; text style; # (7.0) PRINTER +1F5A8 FE0F ; emoji style; # (7.0) PRINTER +1F5B1 FE0E ; text style; # (7.0) THREE BUTTON MOUSE +1F5B1 FE0F ; emoji style; # (7.0) THREE BUTTON MOUSE +1F5B2 FE0E ; text style; # (7.0) TRACKBALL +1F5B2 FE0F ; emoji style; # (7.0) TRACKBALL +1F5BC FE0E ; text style; # (7.0) FRAME WITH PICTURE +1F5BC FE0F ; emoji style; # (7.0) FRAME WITH PICTURE +1F5C2 FE0E ; text style; # (7.0) CARD INDEX DIVIDERS +1F5C2 FE0F ; emoji style; # (7.0) CARD INDEX DIVIDERS +1F5C3 FE0E ; text style; # (7.0) CARD FILE BOX +1F5C3 FE0F ; emoji style; # (7.0) CARD FILE BOX +1F5C4 FE0E ; text style; # (7.0) FILE CABINET +1F5C4 FE0F ; emoji style; # (7.0) FILE CABINET +1F5D1 FE0E ; text style; # (7.0) WASTEBASKET +1F5D1 FE0F ; emoji style; # (7.0) WASTEBASKET +1F5D2 FE0E ; text style; # (7.0) SPIRAL NOTE PAD +1F5D2 FE0F ; emoji style; # (7.0) SPIRAL NOTE PAD +1F5D3 FE0E ; text style; # (7.0) SPIRAL CALENDAR PAD +1F5D3 FE0F ; emoji style; # (7.0) SPIRAL CALENDAR PAD +1F5DC FE0E ; text style; # (7.0) COMPRESSION +1F5DC FE0F ; emoji style; # (7.0) COMPRESSION +1F5DD FE0E ; text style; # (7.0) OLD KEY +1F5DD FE0F ; emoji style; # (7.0) OLD KEY +1F5DE FE0E ; text style; # (7.0) ROLLED-UP NEWSPAPER +1F5DE FE0F ; emoji style; # (7.0) ROLLED-UP NEWSPAPER +1F5E1 FE0E ; text style; # (7.0) DAGGER KNIFE +1F5E1 FE0F ; emoji style; # (7.0) DAGGER KNIFE +1F5E3 FE0E ; text style; # (7.0) SPEAKING HEAD IN SILHOUETTE +1F5E3 FE0F ; emoji style; # (7.0) SPEAKING HEAD IN SILHOUETTE +1F5E8 FE0E ; text style; # (7.0) LEFT SPEECH BUBBLE +1F5E8 FE0F ; emoji style; # (7.0) LEFT SPEECH BUBBLE +1F5EF FE0E ; text style; # (7.0) RIGHT ANGER BUBBLE +1F5EF FE0F ; emoji style; # (7.0) RIGHT ANGER BUBBLE +1F5F3 FE0E ; text style; # (7.0) BALLOT BOX WITH BALLOT +1F5F3 FE0F ; emoji style; # (7.0) BALLOT BOX WITH BALLOT +1F5FA FE0E ; text style; # (7.0) WORLD MAP +1F5FA FE0F ; emoji style; # (7.0) WORLD MAP +1F610 FE0E ; text style; # (6.0) NEUTRAL FACE +1F610 FE0F ; emoji style; # (6.0) NEUTRAL FACE +1F687 FE0E ; text style; # (6.0) METRO +1F687 FE0F ; emoji style; # (6.0) METRO +1F68D FE0E ; text style; # (6.0) ONCOMING BUS +1F68D FE0F ; emoji style; # (6.0) ONCOMING BUS +1F691 FE0E ; text style; # (6.0) AMBULANCE +1F691 FE0F ; emoji style; # (6.0) AMBULANCE +1F694 FE0E ; text style; # (6.0) ONCOMING POLICE CAR +1F694 FE0F ; emoji style; # (6.0) ONCOMING POLICE CAR +1F698 FE0E ; text style; # (6.0) ONCOMING AUTOMOBILE +1F698 FE0F ; emoji style; # (6.0) ONCOMING AUTOMOBILE +1F6AD FE0E ; text style; # (6.0) NO SMOKING SYMBOL +1F6AD FE0F ; emoji style; # (6.0) NO SMOKING SYMBOL +1F6B2 FE0E ; text style; # (6.0) BICYCLE +1F6B2 FE0F ; emoji style; # (6.0) BICYCLE +1F6B9 FE0E ; text style; # (6.0) MENS SYMBOL +1F6B9 FE0F ; emoji style; # (6.0) MENS SYMBOL +1F6BA FE0E ; text style; # (6.0) WOMENS SYMBOL +1F6BA FE0F ; emoji style; # (6.0) WOMENS SYMBOL +1F6BC FE0E ; text style; # (6.0) BABY SYMBOL +1F6BC FE0F ; emoji style; # (6.0) BABY SYMBOL +1F6CB FE0E ; text style; # (7.0) COUCH AND LAMP +1F6CB FE0F ; emoji style; # (7.0) COUCH AND LAMP +1F6CD FE0E ; text style; # (7.0) SHOPPING BAGS +1F6CD FE0F ; emoji style; # (7.0) SHOPPING BAGS +1F6CE FE0E ; text style; # (7.0) BELLHOP BELL +1F6CE FE0F ; emoji style; # (7.0) BELLHOP BELL +1F6CF FE0E ; text style; # (7.0) BED +1F6CF FE0F ; emoji style; # (7.0) BED +1F6E0 FE0E ; text style; # (7.0) HAMMER AND WRENCH +1F6E0 FE0F ; emoji style; # (7.0) HAMMER AND WRENCH +1F6E1 FE0E ; text style; # (7.0) SHIELD +1F6E1 FE0F ; emoji style; # (7.0) SHIELD +1F6E2 FE0E ; text style; # (7.0) OIL DRUM +1F6E2 FE0F ; emoji style; # (7.0) OIL DRUM +1F6E3 FE0E ; text style; # (7.0) MOTORWAY +1F6E3 FE0F ; emoji style; # (7.0) MOTORWAY +1F6E4 FE0E ; text style; # (7.0) RAILWAY TRACK +1F6E4 FE0F ; emoji style; # (7.0) RAILWAY TRACK +1F6E5 FE0E ; text style; # (7.0) MOTOR BOAT +1F6E5 FE0F ; emoji style; # (7.0) MOTOR BOAT +1F6E9 FE0E ; text style; # (7.0) SMALL AIRPLANE +1F6E9 FE0F ; emoji style; # (7.0) SMALL AIRPLANE +1F6F0 FE0E ; text style; # (7.0) SATELLITE +1F6F0 FE0F ; emoji style; # (7.0) SATELLITE +1F6F3 FE0E ; text style; # (7.0) PASSENGER SHIP +1F6F3 FE0F ; emoji style; # (7.0) PASSENGER SHIP + +#Total sequences: 371 + +#EOF diff --git a/src/modules/punk/console-999999.0a1.0.tm b/src/modules/punk/console-999999.0a1.0.tm index f8eb6c5d..5c9c5ceb 100644 --- a/src/modules/punk/console-999999.0a1.0.tm +++ b/src/modules/punk/console-999999.0a1.0.tm @@ -31,102 +31,146 @@ namespace eval punk::console { if {"windows" eq $::tcl_platform(platform)} { proc enableAnsi {} { - define_windows_procs + #loopavoidancetoken (don't remove) + internal::define_windows_procs + internal::abort_if_loop tailcall enableAnsi } proc enableRaw {{channel stdin}} { - define_windows_procs + #loopavoidancetoken (don't remove) + internal::define_windows_procs + internal::abort_if_loop tailcall enableRaw $channel } proc disableRaw {{channel stdin}} { - define_windows_procs + #loopavoidancetoken (don't remove) + internal::define_windows_procs + internal::abort_if_loop tailcall disableRaw $channel } } else { - proc enableAnsi {} { - #todo? - } - proc enableRaw {{channel stdin}} { - set sttycmd [auto_execok stty] - exec {*}$sttycmd raw -echo <@$channel - } - proc disableRaw {{channel stdin}} { - set sttycmd [auto_execok stty] - exec {*}$sttycmd raw echo <@$channel - } + proc enableAnsi {} { + #todo? + } + proc enableRaw {{channel stdin}} { + set sttycmd [auto_execok stty] + exec {*}$sttycmd raw -echo <@$channel + } + proc disableRaw {{channel stdin}} { + set sttycmd [auto_execok stty] + exec {*}$sttycmd raw echo <@$channel + } } - proc define_windows_procs {} { - set loadstate [zzzload::pkg_require twapi] - if {$loadstate ni [list loading failed]} { - package require twapi ;#should be fast once twapi dll loaded in zzzload thread - set ::punk::console::has_twapi 1 - proc enableAnsi {} { - twapi::SetConsoleMode [twapi::get_console_handle stdout] 5 - } - proc enableRaw {{channel stdin}} { - #review - change to modify_console_input_mode - set console_handle [twapi::GetStdHandle -10] - set oldmode [twapi::GetConsoleMode $console_handle] - set newmode [expr {$oldmode & ~6}] ;# Turn off the echo and line-editing bits - twapi::SetConsoleMode $console_handle $newmode - } - proc disableRaw {{channel stdin}} { - set console_handle [twapi::GetStdHandle -10] - set oldmode [twapi::GetConsoleMode $console_handle] - set newmode [expr {$oldmode | 6}] ;# Turn on the echo and line-editing bits - twapi::SetConsoleMode $console_handle $newmode + namespace eval internal { + proc abort_if_loop {{failmsg ""}} { + #puts "il1 [info level 1]" + #puts "thisproc: [lindex [info level 0] 0]" + set would_loop [uplevel 1 {expr {[string match *loopavoidancetoken* [info body [namespace tail [lindex [info level 0] 0]]]]}}] + #puts "would_loop: $would_loop" + if {$would_loop} { + set procname [uplevel 1 {namespace tail [lindex [info level 0] 0]}] + if {$failmsg eq ""} { + set errmsg "[namespace current] Failed to redefine procedure $procname" + } else { + set errmsg $failmsg + } + error $errmsg } - - } else { - if {$loadstate eq "failed"} { - puts stderr "punk::console falling back to stty because twapi load failed" - proc enableAnsi {} { - puts stderr "punk::console::enableAnsi todo" + } + proc define_windows_procs {} { + set loadstate [zzzload::pkg_require twapi] + if {$loadstate ni [list loading failed]} { + package require twapi ;#should be fast once twapi dll loaded in zzzload thread + set ::punk::console::has_twapi 1 + proc [namespace parent]::enableAnsi {} { + #Enable virtual terminal processing (sometimes off in older windows terminals) + twapi::SetConsoleMode [twapi::get_console_handle stdout] 5 + } + proc [namespace parent]::enableRaw {{channel stdin}} { + #review - change to modify_console_input_mode + set console_handle [twapi::GetStdHandle -10] + set oldmode [twapi::GetConsoleMode $console_handle] + set newmode [expr {$oldmode & ~6}] ;# Turn off the echo and line-editing bits + twapi::SetConsoleMode $console_handle $newmode } - proc enableRaw {{channel stdin}} { - set sttycmd [auto_execok stty] - exec {*}$sttycmd raw -echo <@$channel + proc [namespace parent]::disableRaw {{channel stdin}} { + set console_handle [twapi::GetStdHandle -10] + set oldmode [twapi::GetConsoleMode $console_handle] + set newmode [expr {$oldmode | 6}] ;# Turn on the echo and line-editing bits + twapi::SetConsoleMode $console_handle $newmode } - proc disableRaw {{channel stdin}} { - set sttycmd [auto_execok stty] - exec {*}$sttycmd raw echo <@$channel + + } else { + if {$loadstate eq "failed"} { + puts stderr "punk::console falling back to stty because twapi load failed" + proc [namespace parent]::enableAnsi {} { + puts stderr "punk::console::enableAnsi todo" + } + proc [namespace parent]::enableRaw {{channel stdin}} { + set sttycmd [auto_execok stty] + exec {*}$sttycmd raw -echo <@$channel + } + proc [namespace parent]::disableRaw {{channel stdin}} { + set sttycmd [auto_execok stty] + exec {*}$sttycmd raw echo <@$channel + } } } - tailcall du_dirlisting_generic $folderpath {*}$args } - } - - proc ansi_response_handler {chan} { - set status [catch {read $chan 1} bytes] - if { $status != 0 } { - # Error on the channel - puts "error reading $chan: $bytes" - set ::punk::console::chunkdone 2 - } elseif {$bytes ne ""} { - # Successfully read the channel - #puts "got: [string length $bytes]" - append ::punk::console::chunk $bytes - if {$bytes eq "R"} { - set ::punk::console::chunkdone 4 + proc ansi_response_handler {chan} { + set status [catch {read $chan 1} bytes] + if { $status != 0 } { + # Error on the channel + puts "error reading $chan: $bytes" + set ::punk::console::chunkdone 2 + } elseif {$bytes ne ""} { + # Successfully read the channel + #puts "got: [string length $bytes]" + append ::punk::console::chunk $bytes + if {$bytes eq "R"} { + set ::punk::console::chunkdone 4 + } else { + fileevent stdin readable [list ::punk::console::internal::ansi_response_handler stdin] + } + } elseif { [eof $chan] } { + # End of file on the channel + #review + puts "ansi_response_handler end of file" + set ::punk::console::chunkdone 1 + } elseif { [fblocked $chan] } { + # Read blocked. Just return } else { - fileevent stdin readable [list ::punk::console::ansi_response_handler stdin] + # Something else + puts "ansi_response_handler can't happen" + set ::punk::console::chunkdone 3 } - } elseif { [eof $chan] } { - # End of file on the channel - #review - puts "ansi_response_handler end of file" - set ::punk::console::chunkdone 1 - } elseif { [fblocked $chan] } { - # Read blocked. Just return - } else { - # Something else - puts "ansi_response_handler can't happen" - set ::punk::console::chunkdone 3 + } + proc splitn {str {len 1}} { + #textutil::split::splitn + if {$len <= 0} { + return -code error "len must be > 0" + } + if {$len == 1} { + return [split $str {}] + } + set result [list] + set max [string length $str] + set i 0 + set j [expr {$len -1}] + while {$i < $max} { + lappend result [string range $str $i $j] + incr i $len + incr j $len + } + return $result } } + proc reset {} { + tailcall puts -nonewline stdout \u001bc + } proc get_cursor_position {} { set ::punk::console::chunk "" enableRaw @@ -134,7 +178,7 @@ namespace eval punk::console { #e.g \033\[46;1R #todo - reset fconfigure stdin -blocking 0 - fileevent stdin readable [list ::punk::console::ansi_response_handler stdin] + fileevent stdin readable [list ::punk::console::internal::ansi_response_handler stdin] set info "" vwait ::punk::console::chunkdone fileevent stdin readable {} @@ -157,6 +201,67 @@ namespace eval punk::console { set data [string range [string trim $info] 2 end-1] return [split $data ";"] } + + #string to 2digit hex - e.g used by XTGETTCAP + proc str2hex {input} { + set 2hex "" + foreach ch [split $input ""] { + append 2hex [format %02X [scan $ch %c]] + } + return $2hex + } + proc hex2str {2digithexchars} { + if {$2digithexchars eq ""} { + return "" + } + if {[string length $2digithexchars] % 2 != 0} { + error "hex2str requires an even number of hex digits (2 per character)" + } + set 2str "" + foreach pair [internal::splitn $2digithexchars 2] { + append 2str [format %c 0x$pair] + } + return $2str + } + + # -- --- --- --- --- --- + #XTGETTCAP + # xterm responds with + # DCS 1 + r Pt ST for valid requests, adding to Pt an = , and + # the value of the corresponding string that xterm would send, + # or + # DCS 0 + r ST for invalid requests. + # The strings are encoded in hexadecimal (2 digits per + # character). If more than one name is given, xterm replies + # with each name/value pair in the same response. An invalid + # name (one not found in xterm's tables) ends processing of the + # list of names. + proc getcap {keylist} { + #ESC P = 0x90 = DCS = Device Control String + set hexkeys [list] + foreach k $keylist { + lappend hexkeys [str2hex $k] + } + set payload [join $hexkeys ";"] + return "\x1bP+q$payload\x1b\\" + } + proc getcap2 {keylist} { + #ESC P = 0x90 = DCS = Device Control String + set hexkeys [list] + foreach k $keylist { + lappend hexkeys [str2hex $k] + } + set payload [join $hexkeys ";"] + return "\u0090+q$payload\u009c" + } + # -- --- --- --- --- --- + + proc test_decaln {} { + #Screen Alignment Test + #Reset margins, move cursor to the top left, and fill the screen with 'E' + #(doesn't work on many terminals - seems to work in FreeBSD 13.2 and wezterm on windows) + return \x1b#8 + } } diff --git a/src/modules/punk/ns-999999.0a1.0.tm b/src/modules/punk/ns-999999.0a1.0.tm index 036355e8..c9683723 100644 --- a/src/modules/punk/ns-999999.0a1.0.tm +++ b/src/modules/punk/ns-999999.0a1.0.tm @@ -898,7 +898,7 @@ namespace eval punk::ns { #nscommands returns exactly one line per entry + a trailing newline. If there is an empty line other than at the end - that is because there is a command named as the empty string. # By default 'linelist' trims 1st and last empty line. Turn off all block trimming with -block {} - set commands [.= nscommands -raw [nsjoin $ch $glob] |> .=>1 linelist -block {}] + set commands [.= nscommands -raw [nsjoin $ch $glob] |> .=> linelist -block {}] #by convention - returning just \n represents a single result of the empty string whereas no results #after passing through linelist this becomes {} {} which appears as a list of two empty strings. #this is because there isn't a way to represent unambiguously all 3 cases of: empty-list, list of single empty string, list of two empty strings just using \n separated lines diff --git a/src/modules/punk/repo-999999.0a1.0.tm b/src/modules/punk/repo-999999.0a1.0.tm index 05786f6a..d7cb773f 100644 --- a/src/modules/punk/repo-999999.0a1.0.tm +++ b/src/modules/punk/repo-999999.0a1.0.tm @@ -388,6 +388,8 @@ namespace eval punk::repo { #set checkrevision [fossil_revision $abspath] + dict set resultdict ahead "" + dict set resultdict behind "" foreach ln [split $fossilstate \n] { if {[string trim $ln] eq ""} {continue} @@ -439,6 +441,18 @@ namespace eval punk::repo { puts stderr "workingdir_state: git revision is (initial) - no file state to gather" break } + dict set resultdict ahead "" + dict set resultdict behind "" + set aheadbehind [lindex [grep {# branch.ab *} $gitstate] 0] + if {[llength $aheadbehind] > 0} { + lassign [lrange $aheadbehind 2 3] a b + if {$a > 0} { + dict set resultdict ahead [expr {abs($a)}] + } + if {$b < 0} { + dict set resultdict behind [expr {abs($b)}] + } + } #set checkrevision [git_revision $abspath] if {[catch {punk::mix::util::do_in_path $repodir [list exec {*}$git_cmd ls-tree -r $revision $abspath]} gitfiles]} { error "workingdir_state error: Unable to retrieve files for revision '$revision' using git. Errormsg: $gitfiles" @@ -519,7 +533,7 @@ namespace eval punk::repo { } package require overtype set defaults [dict create\ - -fields {unchanged changed new missing extra}\ + -fields {ahead behind unchanged changed new missing extra}\ ] set opts [dict merge $defaults $args] # -- --- --- --- --- --- --- --- --- --- @@ -532,6 +546,8 @@ namespace eval punk::repo { repodir repodir\ subpath subpath\ revision revision\ + ahead ahead\ + behind behind\ repotype repotype\ unchanged unchanged\ changed changed\ @@ -573,7 +589,7 @@ namespace eval punk::repo { } set filestates [dict values [dict get $repostate paths]] set path_count_fields [list unchanged changed new missing extra] - set state_fields [list repodir subpath repotype revision] + set state_fields [list ahead behind repodir subpath repotype revision] set dresult [dict create] foreach f $state_fields { dict set dresult $f [dict get $repostate $f] diff --git a/src/modules/shellfilter-0.1.8.tm b/src/modules/shellfilter-0.1.8.tm index 43ac4c73..4b0af14b 100644 --- a/src/modules/shellfilter-0.1.8.tm +++ b/src/modules/shellfilter-0.1.8.tm @@ -165,9 +165,62 @@ namespace eval shellfilter::ansi { #return "\x1b\[0m" ;#reset color only } + #maint warning - from overtype package + proc stripcodes {text} { + #single "final byte" in the range 0x40–0x7E (ASCII @A–Z[\]^_`a–z{|}~). + dict set escape_terminals CSI [list @ \\ ^ _ ` | ~ a b c d e f g h i j k l m n o p q r s t u v w x y z A B C D E F G H I J K L M N O P Q R S T U V W X Y Z "\{" "\}"] + #dict set escape_terminals CSI [list J K m n A B C D E F G s u] ;#basic + dict set escape_terminals OSC [list \007 \033\\] ;#note mix of 1 and 2-byte terminals + #we process char by char - line-endings whether \r\n or \n should be processed as per any other character. + #line endings can theoretically occur within an ansi escape sequence (review e.g title?) + set inputlist [split $text ""] + set outputlist [list] + + #self-contained 2 byte ansi escape sequences - review more? + set 2bytecodes_dict [dict create\ + "reset_terminal" "\033c"\ + "save_cursor_posn" "\u001b7"\ + "restore_cursor_posn" "\u001b8"\ + "cursor_up_one" "\u001bM"\ + ] + set 2bytecodes [dict values $2bytecodes_dict] + + set in_escapesequence 0 + #assumption - undertext already 'rendered' - ie no backspaces or carriagereturns or other cursor movement controls + set i 0 + foreach u $inputlist { + set v [lindex $inputlist $i+1] + set uv ${u}${v} + if {$in_escapesequence eq "2b"} { + #2nd byte - done. + set in_escapesequence 0 + } elseif {$in_escapesequence != 0} { + set escseq [dict get $escape_terminals $in_escapesequence] + if {$u in $escseq} { + set in_escapesequence 0 + } elseif {$uv in $escseq} { + set in_escapseequence 2b ;#flag next byte as last in sequence + } + } else { + #handle both 7-bit and 8-bit CSI and OSC + if {[regexp {^(?:\033\[|\u009b)} $uv]} { + set in_escapesequence CSI + } elseif {[regexp {^(?:\033\]|\u009c)} $uv]} { + set in_escapesequence OSC + } elseif {$uv in $2bytecodes} { + #self-contained e.g terminal reset - don't pass through. + set in_escapesequence 2b + } else { + lappend outputlist $u + } + } + incr i + } + return [join $outputlist ""] + } #maintenance warning - also in 'overtype' pkg #strip ansi codes from text - basic! assumes we don't get data split in the middle of an ansi-code ie best used with line-buffering - proc stripcodes {text} { + proc stripcodes1_bogus {text} { if {[set posn [string first "\033\[" $text]] >= 0} { set mnext [string first m [string range $text $posn end]] if {$mnext >= 0} { diff --git a/src/modules/textblock-999999.0a1.0.tm b/src/modules/textblock-999999.0a1.0.tm index a4f59445..5bcc335b 100644 --- a/src/modules/textblock-999999.0a1.0.tm +++ b/src/modules/textblock-999999.0a1.0.tm @@ -25,6 +25,9 @@ package require textutil # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +# +#Note: A textblock does not necessarily have lines the same length - either in number of characters or print-width +# namespace eval textblock { namespace eval cd { #todo - save and restore existing namespace export in case macros::cd has default exports in future @@ -33,6 +36,30 @@ namespace eval textblock { namespace eval ::term::ansi::code::macros::cd {namespace export -clear} } + #return a homogenous block of characters - ie lines all same length, all same character + #printing-width in terminal columns is not necessarily same as blockcols even if a single char is passed (could be full-width unicode character) + proc block {blockrows blockcols {char " "}} { + if {($blockrows <= 0 || $blockcols <= 0)} { + error "textblock::block blockrows and blockcols must be positive integers" + } + if {$char eq ""} {return ""} + if {[string length $char] == 1} { + set row [string repeat $char $blockcols] + set mtrx [lrepeat $blockrows $row] + return [::join $mtrx \n] + } else { + set charblock [string map [list \r\n \n] $char] + + if {$blockcols > 1} { + set row [.= val $charblock {*}[lrepeat [expr {$blockcols -1}] |> piper_blockjoin $charblock]] + } else { + set row $charblock + } + set mtrx [lrepeat $blockrows $row] + return [::join $mtrx \n] + } + } + #must be able to handle block as string with or without newlines #if no newlines - attempt to treat as a list #must handle whitespace-only string,list elements, and/or lines. @@ -53,31 +80,74 @@ namespace eval textblock { } return [tcl::mathfunc::max {*}[lmap v $block {string length [stripansi $v]}]] } + pipealias ::textblock::padleft .= {list $input [string repeat " " $indent]} |/0,padding/1> linelist |> .= {lmap v $data {overtype::right $padding $v}} |> list_as_lines linelist |> .= {lmap v $data {overtype::left $padding $v}} |> list_as_lines ? ?-which right|left|centre? -width " + foreach {k v} $args { + if {$k ni [dict keys $defaults]} { + error "textblock::pad unrecognised option '$k'. Usage: $usage" + } + } + set opts [dict merge $defaults $args] + # -- --- --- --- --- --- --- --- --- --- + set padchar [dict get $opts -padchar] + # -- --- --- --- --- --- --- --- --- --- + set known_whiches [list l left r right c center centre] + set which [string tolower [dict get $opts -which]] + if {$which in [list centre center]} {set which "c"} + if {$which in [list left]} {set which "l"} + if {$which in [list right]} {set which "r"} + if {$which ni $known_whiches} { + error "textblock::pad unrecognised value for -which option. Known values $known_whiches" + } + # -- --- --- --- --- --- --- --- --- --- + set width [dict get $opts -width] + # -- --- --- --- --- --- --- --- --- --- + + if {$width = ""} { + + } + + + } - pipealias ::textblock::padleft .= {list $input [string repeat " " $indent]} |/0,padding/1> linelist |> .= {lmap v $data {val "$padding$v"}} |> list_as_lines linelist |> .= {lmap v $data {val "$v$padding"}} |> list_as_lines } .=>1 linelist -block {} {| + >} linelist -block {} {| data2 - >} .=lhs>1 linelist -block {} {| + >} .=lhs> linelist -block {} {| >} .= {lmap v $data w $data2 {val "[overtype::left $col1 $v][overtype::left $col2 $w]"}} {| >} list_as_lines } .=>1 linelist -block {} {| + >} .=> linelist -block {} {| data2 - >} .=lhs>1 linelist -block {} {| + >} .=lhs> linelist -block {} {| >} .= {lmap v $data w $data2 {val "[overtype::left $col1 $v][overtype::left $col2 $w]"}} {| >} list_as_lines } .=>1 linelist -block {} {| + >} .=> linelist -block {} {| data2 - >} .=lhs>1 linelist -block {} {| + >} .=lhs> linelist -block {} {| >} .= {lmap v $data w $data2 {val "[overtype::right $col1 $v][overtype::right $col2 $w]"}} {| >} list_as_lines punk . lhs] |> .=>1 textblock::join " " |> .=>1 textblock::join $text |> .=>1 textblock::join [>punk . rhs] |> .=>1 textblock::join [lrepeat 7 " | "] } - proc frame {string args} { + proc frame {args} { + package require punk::char + + set contents [lindex $args end] + set arglist [lrange $args 0 end-1] + if {[llength $arglist] % 2 != 0} { + error "Usage frame ?-ansi 0|1? " + } #todo args -justify left|centre|right (center) - set string [textutil::tabify::untabify2 $string] - set string [string map [list \r\n \n] $string] - if {[string first \n $string] >= 0} { - set width [width $string] + + set defaults [dict create\ + -ansi 0\ + ] + set opts [dict merge $defaults $arglist] + # -- --- --- --- --- --- + set ansi [dict get $opts -ansi] + # -- --- --- --- --- --- + + set contents [textutil::tabify::untabify2 $contents] + set contents [string map [list \r\n \n] $contents] + if {[string first \n $contents] >= 0} { + set width [width $contents] } else { - set width [width [list $string]] + set width [width [list $contents]] } - set lines [split $string \n] - append fs [cd::tlc][string repeat [cd::hl] $width][cd::trc]\n - foreach l $lines { - append fs [cd::vl]${l}[string repeat " " [expr {$width-[string length [stripansi $l]]}]][cd::vl]\n + set lines [split $contents \n] + + if {$ansi} { + #old style ansi escape sequences with alternate graphics page G0 + append fs [cd::tlc][string repeat [cd::hl] $width][cd::trc]\n + foreach l $lines { + append fs [cd::vl]${l}[string repeat " " [expr {$width-[string length [stripansi $l]]}]][cd::vl]\n + } + append fs [cd::blc][string repeat [cd::hl] $width][cd::brc] + return [cd::groptim $fs] + } else { + #unicode box drawing set + set hz [punk::char::charshort boxd_lhz] ;# light horizontal + append fs [punk::char::charshort boxd_ldr][string repeat $hz $width][punk::char::charshort boxd_ldl]\n + set vl [punk::char::charshort boxd_lv] ;#light vertical + foreach l $lines { + append fs $vl${l}[string repeat " " [expr {$width-[string length [stripansi $l]]}]]$vl\n + } + append fs [punk::char::charshort boxd_lur][string repeat $hz $width][punk::char::charshort boxd_lul] + return $fs } - append fs [cd::blc][string repeat [cd::hl] $width][cd::brc] - return [cd::groptim $fs] + } namespace import ::overtype::stripansi } diff --git a/src/punk86.vfs/lib/app-punk/repl.tcl b/src/punk86.vfs/lib/app-punk/repl.tcl index ac81940d..e0e31cfc 100644 --- a/src/punk86.vfs/lib/app-punk/repl.tcl +++ b/src/punk86.vfs/lib/app-punk/repl.tcl @@ -14,8 +14,13 @@ package provide app-punk 1.0 set original_tm_list [tcl::tm::list] tcl::tm::remove {*}$original_tm_list +#tm list first added end up later in the list - and then override earlier ones if version the same - so add pwd-relative 1st to give higher priority +#1 +if {[file exists [pwd]/modules]} { + catch {tcl::tm::add [pwd]/modules} +} -#1) +#2) if {[string match "*.vfs/*" [info script]]} { #src/xxx.vfs/lib/app-punk/repl.tcl #we assume if calling directly into .vfs that the user would prefer to use src/modules - so go up 4 levels @@ -31,16 +36,17 @@ if {[file exists $modulefolder]} { } else { puts stderr "Warning unable to find module folder at: $modulefolder" } -if {[file exists [pwd]/modules]} { - catch {tcl::tm::add [pwd]/modules} -} -if {[file exists [pwd]/lib]} { - lappend ::auto_path [pwd]/lib -} + +#libs are appended to end - so add higher prioriy libraries last (opposite to modules) +#auto_path - add exe-relative after exe-relative path set libfolder [file dirname [file dirname [info nameofexecutable]]]/lib if {[file exists $libfolder]} { lappend ::auto_path $libfolder } +if {[file exists [pwd]/lib]} { + lappend ::auto_path [pwd]/lib +} + #2) #now add current dir (if no conflict with above) diff --git a/src/vendormodules/overtype-1.5.0.tm b/src/vendormodules/overtype-1.5.0.tm index 39ae2cb9..4916ed44 100644 --- a/src/vendormodules/overtype-1.5.0.tm +++ b/src/vendormodules/overtype-1.5.0.tm @@ -61,26 +61,67 @@ proc overtype::about {} { proc overtype::stripcodes {text} { tailcall overtype::stripansi $text } +namespace eval overtype { + variable escape_terminals + #single "final byte" in the range 0x40–0x7E (ASCII @A–Z[\]^_`a–z{|}~). + dict set escape_terminals CSI [list @ \\ ^ _ ` | ~ a b c d e f g h i j k l m n o p q r s t u v w x y z A B C D E F G H I J K L M N O P Q R S T U V W X Y Z "\{" "\}"] + #dict set escape_terminals CSI [list J K m n A B C D E F G s u] ;#basic + dict set escape_terminals OSC [list \007 \033\\] ;#note mix of 1 and 2-byte terminals + + #self-contained 2 byte ansi escape sequences - review more? + variable ansi_2byte_codes_dict + set ansi_2byte_codes_dict [dict create\ + "reset_terminal" "\u001bc"\ + "save_cursor_posn" "\u001b7"\ + "restore_cursor_posn" "\u001b8"\ + "cursor_up_one" "\u001bM"\ + "NEL - Next Line" "\u001bE"\ + "IND - Down one line" "\u001bD"\ + "HTS - Set Tab Stop" "\u001bH"\ + ] + + #debatable whether strip should reveal the somethinghidden - some terminals don't hide it anyway. + # "PM - Privacy Message" "\u001b^somethinghidden\033\\"\ +} proc overtype::stripansi {text} { + variable escape_terminals ;#dict + variable ansi_2byte_codes_dict + + set text [convert_g0 $text] + + #we process char by char - line-endings whether \r\n or \n should be processed as per any other character. + #line endings can theoretically occur within an ansi escape sequence (review e.g title?) set inputlist [split $text ""] - set outputlist [list] - dict set escape_terminals 1 [list J K m n A B C D E F G s u] ;#review - dict set escape_terminals 2 [list \007] + set outputlist [list] + + set 2bytecodes [dict values $ansi_2byte_codes_dict] + set in_escapesequence 0 #assumption - undertext already 'rendered' - ie no backspaces or carriagereturns or other cursor movement controls set i 0 foreach u $inputlist { set v [lindex $inputlist $i+1] - if {$in_escapesequence != 0} { - if {$u in [dict get $escape_terminals $in_escapesequence]} { + set uv ${u}${v} + if {$in_escapesequence eq "2b"} { + #2nd byte - done. + set in_escapesequence 0 + } elseif {$in_escapesequence != 0} { + set escseq [dict get $escape_terminals $in_escapesequence] + if {$u in $escseq} { set in_escapesequence 0 + } elseif {$uv in $escseq} { + set in_escapseequence 2b ;#flag next byte as last in sequence } } else { - if {$u eq "\033" && $v eq "\["} { - set in_escapesequence 1 - } elseif {$u eq "\033" && $v eq "\]"} { - set in_escapesequence 2 + #handle both 7-bit and 8-bit CSI and OSC + if {[regexp {^(?:\033\[|\u009b)} $uv]} { + set in_escapesequence CSI + } elseif {[regexp {^(?:\033\]|\u009c)} $uv]} { + set in_escapesequence OSC + } elseif {$uv in $2bytecodes} { + #self-contained e.g terminal reset - don't pass through. + set in_escapesequence 2b } else { lappend outputlist $u } @@ -88,65 +129,50 @@ proc overtype::stripansi {text} { incr i } return [join $outputlist ""] +} +#review +#todo - map other chars to unicode equivs +proc overtype::convert_g0 {text} { + #using not \033 inside to stop greediness - review how does it compare to ".*?" + set re {\033\(0[^\033]*\033\(B} + set re2 {\033\(0(.*)\033\(B} ;#capturing + set parts [ta::_perlish_split $re $text] + set out "" + foreach {pt g} $parts { + append out $pt + if {$g ne ""} { + #puts --$g-- + #box sample + #lqk + #x x + #mqj + #m = boxd_lur + #set map [list l \u250f k \u2513] ;#heavy + set map [list l \u250c q \u2500 k \u2510 x \u2502 m \u2514 j \u2518] ;#light + + regexp $re2 $g _match contents + append out [string map $map $contents] + } + } + return $out } -#maintenance warning - also in 'shellfilter' pkg -#strip ansi codes from text - basic! assumes we don't get data split in the middle of an ansi-code ie best used with line-buffering -proc overtype::stripansi2 {text} { - variable lasttext "" - if {[set posn [string first "\033" $text]] < 0} { - return $text - } - if {[set posn [string first "\033(" $text]] >= 0} { +#todo - convert esc(0 graphics sequences to single char unicode equivalents e.g box drawing set +# esc) ?? +proc overtype::stripansi_gx {text} { #e.g "\033(0" - select VT100 graphics for character set G0 - #e.g "\033(X" - where X is any char other than 0 to reset - set text [string range $text 0 $posn-1][string range $text $posn+3 end] - } - if {[set posn [string first "\033)" $text]] >= 0} { + #e.g "\033(B" - reset #e.g "\033)0" - select VT100 graphics for character set G1 - #e.g "\033)X" - where X is any char other than 0 to reset - set text [string range $text 0 $posn-1][string range $text $posn+3 end] - } - #strip title including inner text - the title is an instruction to the window - so the title-text doesn't form part of the line-data in the text block - if {[set posn [string first "\033\]" $text]] >= 0} { - set next2 [string range $text $posn+2 $posn+3] - if {$next2 in [list {0;} {1;} {2;}]} { - #0; set icon name and window title - #1; set icon name - #2; set window title - set tail [string range $text $posn end] - set endoffset [string first \007 $tail] - if {$endoffset > 3} { - set text [string range $text 0 $posn-1][string range $text $posn+$offset+1 end] - } else { - #unexpected.. we seem to be missing terminating BEL character - puts stderr "stripansi failure: missing terminator BEL on title" - } - } - } - if {[set posn [string first "\033\[" $text]] >= 0} { - set mnext [string first m [string range $text $posn end]] - if {$mnext >= 0} { - set mpos [expr {$posn + $mnext}] - set stripped1 [string range $text 0 $posn-1][string range $text $mpos+1 end] - #return [stripansi $stripped1] ;#recurse to get any others - tailcall ::overtype::stripansi $stripped1 - } else { - #partial or not actually a basic ansi code.. pass it all through - return $text - } - } - if {[set posn [string first "\033" $text]] >= 0} { - if {$lasttext eq $text} { - return $text - } - set lasttext $text - tailcall ::overtype::stripansi $text - } - return $text + #e.g "\033)X" - where X is any char other than 0 to reset ?? + return [convert_g0 $text] } -proc overtype::strip_nonprinting {str} { + + +proc overtype::strip_nonprinting_ascii {str} { + #review - some single-byte 'control' chars have visual representations e.g ETX as heart + #It is currently used for screen display width calculations + #equivalent for various unicode combining chars etc? set map [list\ \007 ""\ [format %c 0] ""\ @@ -156,7 +182,8 @@ proc overtype::strip_nonprinting {str} { } #length of text for printing characters only -#review - unicode and other non-printing chars? +#review - unicode and other non-printing chars and combining sequences? +#certain unicode chars are full-width (single char 2 columns wide) e.g see "Halfwdith and fullwidth forms" and ascii_fuillwidth blocks in punk::char::charset_names #review - is there an existing library or better method? print to a terminal and query cursor position? #Note this length calculation is only suitable for lines being appended to other strings if the line is pre-processed to account for backspace and carriage returns first #If the raw line is appended to another string without such processing - the backspaces & carriage returns can affect data prior to the start of the string. @@ -165,7 +192,7 @@ proc overtype::printing_length {line} { error "line_print_length must not contain newline characters" } set line [stripansi $line] - set line [strip_nonprinting $line] ;#only strip nonprinting after stripansi - some like BEL are part of ansi + set line [strip_nonprinting_ascii $line] ;#only strip nonprinting after stripansi - some like BEL are part of ansi #backspace 0x08 only erases* printing characters anyway - so presumably order of processing doesn't matter #(* more correctly - moves cursor back) #backspace will not move beyond a preceding newline - but we have disallowed newlines for this function already @@ -183,10 +210,12 @@ proc overtype::printing_length {line} { #set line [string map [list "\r${bs}" "\r"] $line] ;#backsp following a \r will have no effect set line [string trim $line $bs] set n 0 + set chars [split $line ""] #build an output set idx 0 - set out [list] + set outchars [list] + set outsizes [list] foreach c $chars { if {$c eq $bs} { if {$idx > 0} { @@ -199,17 +228,18 @@ proc overtype::printing_length {line} { incr idx } } - set line2 [join $out ""] + set line2 [join $outchars ""] return [string length $line2] } namespace eval overtype::priv { proc printing_length_addchar {i c} { - upvar out o - set nxt [llength $o] + upvar outchars outc + upvar outsizes outs + set nxt [llength $outc] if {$i < $nxt} { - lset o $i $c + lset outc $i $c } else { - lappend o $c + lappend outc $c } } } @@ -258,7 +288,7 @@ proc overtype::left {args} { set diff [expr {$overlen - $colwidth}] #review - append overtext "\033\[0m" + #append overtext "\033\[0m" if {$diff > 0} { #background line is narrower @@ -369,6 +399,101 @@ proc overtype::centre {args} { return [join $outputlines \n] } +proc overtype::right {args} { + variable default_ellipsis_horizontal + # @d !todo - implement overflow, length checks etc + + if {[llength $args] < 2} { + error {usage: ?-overflow [1|0]? undertext overtext} + } + foreach {underblock overblock} [lrange $args end-1 end] break + + set defaults [dict create\ + -bias left\ + -ellipsis 0\ + -ellipsistext $default_ellipsis_horizontal\ + -overflow 0\ + -transparent 0\ + ] + set known_opts [dict keys $defaults] + set argsflags [lrange $args 0 end-2] + dict for {k v} $argsflags { + if {$k ni $known_opts} { + error "overtype::centre unknown option '$k'. Known options: $known_opts" + } + } + set opts [dict merge $defaults $argsflags] + # -- --- --- --- --- --- + set opt_transparent [dict get $opts -transparent] + set opt_ellipsis [dict get $opts -ellipsis] + set opt_ellipsistext [dict get $opts -ellipsistext] + set opt_overflow [dict get $opts -overflow] + # -- --- --- --- --- --- + + set norm [list \r\n \n] + set underblock [string map $norm $underblock] + set overblock [string map $norm $overblock] + + set underlines [split $underblock \n] + set colwidth [tcl::mathfunc::max {*}[lmap v $underlines {printing_length $v}]] + set overlines [split $overblock \n] + + set outputlines [list] + foreach undertext $underlines overtext $overlines { + set olen [printing_length $overtext] + set ulen [printing_length $undertext] + if {$ulen < $colwidth} { + set udiff [expr {$colwidth - $ulen}] + set undertext "$undertext[string repeat { } $udiff]" + } + #review + #append overtext "\033\[0m" + + set overflowlength [expr {$olen - $colwidth}] + if {$overflowlength > 0} { + #overtext wider than undertext column + set rendered [renderline -transparent $opt_transparent -overflow $opt_overflow -start 0 $undertext $overtext] + if {!$opt_overflow} { + if {$opt_ellipsis} { + set rendered [overtype::right $rendered $opt_ellipsistext] + } + } + lappend outputlines $rendered + } else { + #lappend outputlines [string range $undertext 0 end-$olen]$overtext + lappend outputlines [renderline -transparent $opt_transparent -start [expr {$colwidth - $olen}] $undertext $overtext] + } + } + + return [join $outputlines \n] +} +proc overtype::right_prev {args} { + # @d !todo - implement overflow, length checks etc + + if {[llength $args] < 2} { + error {usage: ?-overflow [1|0]? undertext overtext} + } + foreach {undertext overtext} [lrange $args end-1 end] break + + set opt(-overflow) 0 + array set opt [lrange $args 0 end-2] + + + set olen [printing_length $overtext] + set ulen [printing_length $undertext] + + if {$opt(-overflow)} { + return [string range $undertext 0 end-$olen]$overtext + } else { + if {$olen > $ulen} { + set diff [expr {$olen - $ulen}] + return [string range $undertext 0 end-$olen][string range $overtext 0 end-$diff] + } else { + return [string range $undertext 0 end-$olen]$overtext + } + } +} + # -- --- --- --- --- --- --- --- --- --- --- #todo - ansi proc overtype::transparentline {args} { @@ -381,12 +506,24 @@ proc overtype::transparentline {args} { tailcall overtype::renderline {*}$newargs $under $over } -#renderline may not make sense as it is in the long run. We are trying to handle ansi codes in a block of text which is acting like a mini-terminal in some sense. +#renderline may not make sense as it is in the long run for blocks of text - but is handy in the single-line-handling form anyway. +# We are trying to handle ansi codes in a block of text which is acting like a mini-terminal in some sense. #We can process standard cursor moves such as \b \r - but no way to respond to other cursor movements e.g moving to other lines. # +namespace eval overtype::piper { + proc renderline {args} { + if {[llength $args] < 2} { + error {usage: ?-start ? ?-transparent [0|1|]? ?-overflow [1|0]? overtext pipelinedata} + } + foreach {over under} [lrange $args end-1 end] break + set argsflags [lrange $args 0 end-2] + tailcall overtype::renderline {*}$argsflags $under $over + } +} +interp alias "" piper_renderline "" overtype::piper::renderline proc overtype::renderline {args} { if {[llength $args] < 2} { - error {usage: ?-transparent [0|1|]? ?-overflow [1|0]? undertext overtext} + error {usage: ?-start ? ?-transparent [0|1|]? ?-overflow [1|0]? undertext overtext} } foreach {under over} [lrange $args end-1 end] break if {[string first \n $under] >=0 || [string first \n $over] >= 0} { @@ -395,6 +532,7 @@ proc overtype::renderline {args} { set defaults [dict create\ -overflow 0\ -transparent 0\ + -start 0\ ] set known_opts [dict keys $defaults] set argsflags [lrange $args 0 end-2] @@ -406,6 +544,163 @@ proc overtype::renderline {args} { set opts [dict merge $defaults $argsflags] # -- --- --- --- --- --- --- --- --- --- --- --- set opt_overflow [dict get $opts -overflow] + set opt_colstart [dict get $opts -start] + # -- --- --- --- --- --- --- --- --- --- --- --- + set opt_transparent [dict get $opts -transparent] + if {$opt_transparent eq "0"} { + set do_transparency 0 + } else { + set do_transparency 1 + if {$opt_transparent eq "1"} { + set opt_transparent {[\s]} + } + } + # -- --- --- --- --- --- --- --- --- --- --- --- + + if {[string first \t $under] >= 0} { + set under [textutil::tabify::untabify2 $under] + } + set overdata $over + if {[string first \t $over] >= 0} { + set overdata [textutil::tabify::untabify2 $over] + } + set over [string repeat " " $opt_colstart] + append over $overdata ;#overdata with left padding spaces based on col-start under will show through for left-padding portion regardless of -transparency + + set undermap [overtype::ta::split_codes_single $under] + set overmap [overtype::ta::split_codes_single $over] + + set understacks [dict create] + + set i_u 0 + set i_o 0 + set out [list] + set u_codestack [list] + foreach {pt code} $undermap { + foreach ch [split $pt ""] { + dict set understacks $i_u $u_codestack + incr i_u + lappend out $ch + } + if {[priv::is_csi_reset $code]} { + set u_codestack [list] + } else { + lappend u_codestack $code + } + } + set overstacks [dict create] + set o_codestack [list] + foreach {pt code} $overmap { + foreach ch [split $pt ""] { + dict set overstacks $i_o $o_codestack + incr i_o + } + if {[priv::is_csi_reset $code]} { + set o_codestack [list] + } else { + lappend o_codestack $code + } + } + + set bs [format %c 0x08] + set idx 0 + set idx_over -1 + foreach {pt code} $overmap { + foreach ch [split $pt ""] { + incr idx_over + if {$ch eq "\r"} { + set idx $opt_colstart + } elseif {$ch eq "\b"} { + if {$idx > $opt_colstart} { + incr idx -1 + } + } elseif {$idx < $opt_colstart} { + incr idx + } elseif {$do_transparency && [regexp $opt_transparent $ch]} { + if {$idx > [llength $out]-1} { + lappend out " " + dict set understacks $idx [list] ;#review - use idx-1 codestack? + } + incr idx + } else { + priv::render_addchar $idx $ch [dict get $overstacks $idx_over] + incr idx + } + } + } + + #coalesce and replay codestacks for out char list + set outstring "" + set i 0 + set cstack [list] + set prevstack [list] + foreach ch $out { + set cstack [dict get $understacks $i] + if {$cstack ne $prevstack} { + if {[llength $prevstack]} { + append outstring \033\[m + } + foreach code $cstack { + append outstring $code + } + } + append outstring $ch + set prevstack $cstack + incr i + } + #pdict $understacks + return $outstring + #return [join $out ""] +} +namespace eval overtype::priv { + proc is_csi_reset {code} { + #todo 8-bit csi + regexp {\033\[0*m} $code + } + #whether this code has 0 (or equivalently empty) parameter (but may set others) + proc has_csi_reset {code} { + + } + proc render_addchar {i c stack} { + upvar out o + upvar understacks ustacks + set nxt [llength $o] + if {$i < $nxt} { + lset o $i $c + } else { + lappend o $c + } + dict set ustacks $i $stack + } + +} + + + +proc overtype::renderline1 {args} { + if {[llength $args] < 2} { + error {usage: ?-start ? ?-transparent [0|1|]? ?-overflow [1|0]? undertext overtext} + } + foreach {under over} [lrange $args end-1 end] break + if {[string first \n $under] >=0 || [string first \n $over] >= 0} { + error "overtype::renderline not allowed to contain newlines" + } + set defaults [dict create\ + -overflow 0\ + -transparent 0\ + -start 0\ + ] + set known_opts [dict keys $defaults] + set argsflags [lrange $args 0 end-2] + dict for {k v} $argsflags { + if {$k ni $known_opts} { + error "overtype::renderline unknown option '$k'. Known options: $known_opts" + } + } + set opts [dict merge $defaults $argsflags] + # -- --- --- --- --- --- --- --- --- --- --- --- + set opt_overflow [dict get $opts -overflow] + set opt_colstart [dict get $opts -start] # -- --- --- --- --- --- --- --- --- --- --- --- set opt_transparent [dict get $opts -transparent] if {$opt_transparent eq "0"} { @@ -422,9 +717,12 @@ proc overtype::renderline {args} { if {[string first \t $under] >= 0} { set under [textutil::tabify::untabify2 $under] } + set overdata $over if {[string first \t $over] >= 0} { - set over [textutil::tabify::untabify2 $over] + set overdata [textutil::tabify::untabify2 $over] } + set over [string repeat " " $opt_colstart] + append over $overdata ;#overdata with left padding spaces based on col-start under will show through for left-padding portion regardless of -transparency set bs [format %c 0x08] @@ -435,7 +733,7 @@ proc overtype::renderline {args} { set out [split $under ""] set under_printables [list] set in_escapesequence 0 - #assumption - undertext already 'rendered' - ie no backspaces or carriagereturns or other cursor movement controls + #assumption - undertext already 'rendered/wrapped' - ie no backspaces or carriagereturns or other cursor movement controls #this is basically a form of stripansi... review! foreach u [split $under ""] { if {$in_escapesequence} { @@ -491,6 +789,9 @@ proc overtype::renderline {args} { if {$i_printable > 0} { incr i_printable -1 } + } elseif {$i_printable < $opt_colstart} { + incr i + incr i_printable } elseif {$do_transparency && [regexp $opt_transparent $o]} { if {$i > [llength $out]-1} { lappend out " " @@ -501,8 +802,6 @@ proc overtype::renderline {args} { } incr i_printable } else { - priv::renderline_addchar $i $o - priv::renderline_addprintable $i_printable $o if {!$opt_overflow} { if {$i_printable == $original_under_printlen} { #we have reached our cutoff length - but there could be a closing ansi code or other control characters that would change the already-processed output. @@ -512,6 +811,8 @@ proc overtype::renderline {args} { break } } + priv::renderline_addchar $i $o + priv::renderline_addprintable $i_printable $o incr i incr i_printable } @@ -550,75 +851,117 @@ namespace eval overtype::priv { } } # -- --- --- --- --- --- --- --- --- --- --- +namespace eval overtype::ta { + namespace path ::overtype + #*based* on but not identical to: + #https://github.com/perlancar/perl-Text-ANSI-Util/blob/master/lib/Text/ANSI/BaseUtil.pm + + #handle both 7-bit and 8-bit csi + #review - does codepage affect this? e.g ebcdic has 8bit csi in different position + + #CSI + variable re_csi_open {(?:\033\[|\u009b)[0-9;]+} + + #colour and style + variable re_csi_colour {(?:\033\[|\u009b)[0-9;]*m} ;#e.g \033\[31m \033\[m \033\[0m \033\[m0000m + #single "final byte" in the range 0x40–0x7E (ASCII @A–Z[\]^_`a–z{|}~). + variable re_csi_code {(?:\033\[|\u009b])[0-9;]*[a-zA-Z\\@^_|~`]} + + #OSC - termnate with BEL (\a \007) or ST (string terminator \033\\) + #variable re_esc_osc1 {(?:\033\]|\u009c).*\007} + #variable re_esc_osc2 {(?:\033\]|\u009c).*\033\\} + + #test - non-greedy + variable re_esc_osc1 {(?:\033\]|\u009c).*?\007} + variable re_esc_osc2 {(?:\033\]|\u009c).*?\033\\} + + #detect any ansi escapes + proc detect {text} { + variable re_csi_open + variable re_esc_osc1 + variable re_esc_osc2 + #todo - other escape sequences + expr {[regexp $re_csi_open $text] || [regexp $re_esc_osc1 $text] || [regexp $re_esc_osc2 $text]} + } + #not in perl ta + proc detect_csi {text} { + variable re_csi_colour + expr {[regexp $re_csi_colour $text]} + } + proc strip {text} { + tailcall stripansi $text + } + #note this is character length after stripping ansi codes - not the printing length + proc length {text} { + string length [overtype::stripansi $text] + } + #todo - handle newlines + #not in perl ta + proc printing_length {text} { -proc overtype::centre_prev {args} { - if {[llength $args] < 2} { - error {usage: ?-bias [left|right]? ?-overflow [1|0]? undertext overtext} - } - foreach {undertext overtext} [lrange $args end-1 end] break - - set opt(-bias) left - set opt(-overflow) 0 - array set opt [lrange $args 0 end-2] - - - set olen [printing_length $overtext] - set ulen [printing_length $undertext] - set diff [expr {$ulen - $olen}] - if {$diff > 0} { - set half [expr {round(int($diff / 2))}] - if {[string match right $opt(-bias)]} { - if {[expr {2 * $half}] < $diff} { - incr half - } - } - - set rhs [expr {$diff - $half - 1}] - set lhs [expr {$half - 1}] - - set a [string range $undertext 0 $lhs] - set b $overtext - set c [string range $undertext end-$rhs end] - return $a$b$c - } else { - if {$diff < 0} { - if {$opt(-overflow)} { - return $overtext - } else { - return [string range $overtext 0 [expr {$ulen - 1}]] - } - } else { - return $overtext - } - } -} -proc overtype::right {args} { - # @d !todo - implement overflow, length checks etc - - if {[llength $args] < 2} { - error {usage: ?-overflow [1|0]? undertext overtext} - } - foreach {undertext overtext} [lrange $args end-1 end] break - - set opt(-overflow) 0 - array set opt [lrange $args 0 end-2] + } + #not in perl ta + #returns just the plaintext portions in a list + proc split_at_codes {text} { + variable re_esc_osc1 + variable re_esc_osc2 + variable re_csi_code + textutil::splitx $text "${re_csi_code}|${re_esc_osc1}|${re_esc_osc2}" + } + + # -- --- --- --- --- --- + #Split $text to a list containing alternating ANSI color codes and text. + #ANSI color codes are always on the second element, fourth, and so on. + #(ie plaintext on odd list-indices ansi on even indices) + # Example: + #ta_split_codes "" # => "" + #ta_split_codes "a" # => "a" + #ta_split_codes "a\e[31m" # => {"a" "\e[31m"} + #ta_split_codes "\e[31ma" # => {"" "\e[31m" "a"} + #ta_split_codes "\e[31ma\e[0m" # => {"" "\e[31m" "a" "\e[0m"} + #ta_split_codes "\e[31ma\e[0mb" # => {"" "\e[31m" "a" "\e[0m", "b"} + #ta_split_codes "\e[31m\e[0mb" # => {"" "\e[31m\e[0m" "b"} + # + proc split_codes {text} { + variable re_esc_osc1 + variable re_esc_osc2 + variable re_csi_code + set re "(?:${re_csi_code}|${re_esc_osc1}|${re_esc_osc2})+" + return [_perlish_split $re $text] + } + #like split_codes - but each ansi-escape is split out separately (with empty string of plaintext between codes so odd/even plain ansi still holds) + proc split_codes_single {text} { + variable re_esc_osc1 + variable re_esc_osc2 + variable re_csi_code + set re "${re_csi_code}|${re_esc_osc1}|${re_esc_osc2}" + return [_perlish_split $re $text] + } - set olen [printing_length $overtext] - set ulen [printing_length $undertext] + #review - tcl greedy expressions may match multiple in one element + proc _perlish_split {re text} { + if {[string length $text] == 0} { + return {} + } + set list [list] + set start 0 + while {[regexp -start $start -indices -- $re $text match]} { + lassign $match matchStart matchEnd + lappend list [string range $text $start $matchStart-1] [string range $text $matchStart $matchEnd] + set start [expr {$matchEnd+1}] + } + lappend list [string range $text $start end] + return $list + } + proc _ws_split {text} { + regexp -all -inline {(?:\S+)|(?:\s+)} $text + } + # -- --- --- --- --- --- - if {$opt(-overflow)} { - return [string range $undertext 0 end-$olen]$overtext - } else { - if {$olen > $ulen} { - set diff [expr {$olen - $ulen}] - return [string range $undertext 0 end-$olen][string range $overtext 0 end-$diff] - } else { - return [string range $undertext 0 end-$olen]$overtext - } - } } +# -- --- --- --- --- --- --- --- --- --- --- namespace eval overtype { interp alias {} ::overtype::center {} ::overtype::centre }